mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-06-24 14:18:05 +02:00
changes. Support for separate workspaces Addition of workspace CLI support for test purposes
199 lines
6.8 KiB
Python
199 lines
6.8 KiB
Python
"""
|
|
ReactPattern — extracted from the existing agent_manager.py.
|
|
|
|
Implements the ReACT (Reasoning + Acting) loop: think, select a tool,
|
|
observe the result, repeat until a final answer is produced.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import uuid
|
|
|
|
from ... schema import AgentRequest, AgentResponse, AgentStep
|
|
|
|
from trustgraph.provenance import (
|
|
agent_iteration_uri,
|
|
agent_thought_uri,
|
|
agent_observation_uri,
|
|
agent_final_uri,
|
|
agent_decomposition_uri,
|
|
)
|
|
|
|
from ..react.agent_manager import AgentManager
|
|
from ..react.types import Action, Final
|
|
from ..tool_filter import get_next_state
|
|
|
|
from . pattern_base import PatternBase, UsageTracker
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ReactPattern(PatternBase):
|
|
"""
|
|
ReACT pattern: interleaved reasoning and action.
|
|
|
|
Each iterate() call performs one reason/act cycle. If the LLM
|
|
produces a Final answer the dialog completes; otherwise the action
|
|
result is appended to history and a next-request is emitted.
|
|
"""
|
|
|
|
async def iterate(self, request, respond, next, flow, usage=None,
|
|
pattern_decision_uri=None):
|
|
|
|
if usage is None:
|
|
usage = UsageTracker()
|
|
|
|
streaming = getattr(request, 'streaming', False)
|
|
session_id = getattr(request, 'session_id', '') or str(uuid.uuid4())
|
|
collection = getattr(request, 'collection', 'default')
|
|
|
|
history = self.build_history(request)
|
|
iteration_num = len(history) + 1
|
|
session_uri = self.processor.provenance_session_uri(session_id)
|
|
|
|
# Emit session provenance on first iteration
|
|
if iteration_num == 1:
|
|
# Subagents link back to the parent's decomposition
|
|
parent_session_id = getattr(request, 'parent_session_id', '')
|
|
parent_uri = (
|
|
agent_decomposition_uri(parent_session_id)
|
|
if parent_session_id else None
|
|
)
|
|
await self.emit_session_triples(
|
|
flow, session_uri, request.question,
|
|
request.user, collection, respond, streaming,
|
|
parent_uri=parent_uri,
|
|
)
|
|
|
|
logger.info(f"ReactPattern iteration {iteration_num}: {request.question}")
|
|
|
|
if len(history) >= self.processor.max_iterations:
|
|
raise RuntimeError("Too many agent iterations")
|
|
|
|
# Compute URIs upfront for message_id
|
|
thought_msg_id = agent_thought_uri(session_id, iteration_num)
|
|
observation_msg_id = agent_observation_uri(session_id, iteration_num)
|
|
answer_msg_id = agent_final_uri(session_id)
|
|
|
|
# Build callbacks
|
|
think = self.make_think_callback(respond, streaming, message_id=thought_msg_id)
|
|
observe = self.make_observe_callback(respond, streaming, message_id=observation_msg_id)
|
|
answer_cb = self.make_answer_callback(respond, streaming, message_id=answer_msg_id)
|
|
|
|
# Look up the per-workspace agent
|
|
agent = self.processor.agents.get(flow.workspace)
|
|
if agent is None:
|
|
raise RuntimeError(
|
|
f"No agent configuration for workspace {flow.workspace}"
|
|
)
|
|
|
|
# Filter tools
|
|
filtered_tools = self.filter_tools(
|
|
agent.tools, request,
|
|
)
|
|
|
|
# Create temporary agent with filtered tools and optional framing
|
|
additional_context = agent.additional_context
|
|
framing = getattr(request, 'framing', '')
|
|
if framing:
|
|
if additional_context:
|
|
additional_context = f"{additional_context}\n\n{framing}"
|
|
else:
|
|
additional_context = framing
|
|
|
|
temp_agent = AgentManager(
|
|
tools=filtered_tools,
|
|
additional_context=additional_context,
|
|
)
|
|
|
|
context = self.make_context(
|
|
flow, request.user,
|
|
respond=respond, streaming=streaming,
|
|
)
|
|
|
|
# Set current explain URI so tools can link sub-traces
|
|
context.current_explain_uri = agent_iteration_uri(
|
|
session_id, iteration_num,
|
|
)
|
|
|
|
# Tool names available to the LLM for this iteration
|
|
tool_candidates = [t.name for t in filtered_tools.values()]
|
|
|
|
# Use pattern decision as derivation source if available
|
|
derive_from_uri = pattern_decision_uri or session_uri
|
|
|
|
# Callback: emit Analysis+ToolUse triples before tool executes
|
|
async def on_action(act):
|
|
await self.emit_iteration_triples(
|
|
flow, session_id, iteration_num, derive_from_uri,
|
|
act, request, respond, streaming,
|
|
tool_candidates=tool_candidates,
|
|
step_number=iteration_num,
|
|
llm_duration_ms=getattr(act, 'llm_duration_ms', None),
|
|
in_token=getattr(act, 'in_token', None),
|
|
out_token=getattr(act, 'out_token', None),
|
|
model=getattr(act, 'llm_model', None),
|
|
)
|
|
|
|
act = await temp_agent.react(
|
|
question=request.question,
|
|
history=history,
|
|
think=think,
|
|
observe=observe,
|
|
answer=answer_cb,
|
|
context=context,
|
|
streaming=streaming,
|
|
on_action=on_action,
|
|
usage=usage,
|
|
)
|
|
|
|
logger.debug(f"Action: {act}")
|
|
|
|
if isinstance(act, Final):
|
|
|
|
if isinstance(act.final, str):
|
|
f = act.final
|
|
else:
|
|
f = json.dumps(act.final)
|
|
|
|
# Emit final provenance
|
|
await self.emit_final_triples(
|
|
flow, session_id, iteration_num, derive_from_uri,
|
|
f, request, respond, streaming,
|
|
termination_reason="final-answer",
|
|
)
|
|
|
|
if self.is_subagent(request):
|
|
await self.emit_subagent_completion(request, next, f)
|
|
else:
|
|
await self.send_final_response(
|
|
respond, streaming, f, already_streamed=streaming,
|
|
message_id=answer_msg_id,
|
|
usage=usage,
|
|
)
|
|
return
|
|
|
|
# Emit observation provenance after tool execution
|
|
await self.emit_observation_triples(
|
|
flow, session_id, iteration_num,
|
|
act.observation, request, respond,
|
|
context=context,
|
|
tool_duration_ms=getattr(act, 'tool_duration_ms', None),
|
|
tool_error=getattr(act, 'tool_error', None),
|
|
)
|
|
|
|
history.append(act)
|
|
|
|
# Handle state transitions
|
|
next_state = request.state
|
|
if act.name in filtered_tools:
|
|
executed_tool = filtered_tools[act.name]
|
|
next_state = get_next_state(executed_tool, request.state or "undefined")
|
|
|
|
r = self.build_next_request(
|
|
request, history, session_id, collection,
|
|
streaming, next_state,
|
|
)
|
|
await next(r)
|
|
|
|
logger.debug("ReactPattern iteration complete")
|