diff --git a/trustgraph-base/trustgraph/api/explainability.py b/trustgraph-base/trustgraph/api/explainability.py index 7b406a59..ee7fd05e 100644 --- a/trustgraph-base/trustgraph/api/explainability.py +++ b/trustgraph-base/trustgraph/api/explainability.py @@ -999,8 +999,6 @@ class ExplainabilityClient: trace = { "question": None, "steps": [], - "iterations": [], # Backwards compatibility for ReAct - "conclusion": None, } # Fetch question/session @@ -1015,11 +1013,6 @@ class ExplainabilityClient: is_first=True, max_depth=50, ) - # Backwards compat: populate iterations from steps - trace["iterations"] = [ - s for s in trace["steps"] if isinstance(s, Analysis) - ] - return trace def _follow_provenance_chain( @@ -1081,7 +1074,6 @@ class ExplainabilityClient: elif isinstance(entity, (Conclusion, Synthesis)): trace["steps"].append(entity) - trace["conclusion"] = entity def list_sessions( self, diff --git a/trustgraph-cli/trustgraph/cli/invoke_agent.py b/trustgraph-cli/trustgraph/cli/invoke_agent.py index 2a1ba7c2..c82c78f6 100644 --- a/trustgraph-cli/trustgraph/cli/invoke_agent.py +++ b/trustgraph-cli/trustgraph/cli/invoke_agent.py @@ -267,7 +267,8 @@ def question_explainable( def question( url, question, flow_id, user, collection, - plan=None, state=None, group=None, verbose=False, streaming=True, + plan=None, state=None, group=None, pattern=None, + verbose=False, streaming=True, token=None, explainable=False, debug=False ): # Explainable mode uses the API to capture and process provenance events @@ -307,6 +308,8 @@ def question( request_params["state"] = state if group is not None: request_params["group"] = group + if pattern is not None: + request_params["pattern"] = pattern try: # Call agent @@ -430,6 +433,12 @@ def main(): help=f'Agent plan (default: unspecified)' ) + parser.add_argument( + '-p', '--pattern', + choices=['react', 'plan-then-execute', 'supervisor'], + help='Force execution pattern (default: auto-selected by meta-router)' + ) + parser.add_argument( '-s', '--state', help=f'Agent initial state (default: unspecified)' @@ -478,6 +487,7 @@ def main(): plan = args.plan, state = args.state, group = args.group, + pattern = args.pattern, verbose = args.verbose, streaming = not args.no_streaming, token = args.token, diff --git a/trustgraph-cli/trustgraph/cli/show_explain_trace.py b/trustgraph-cli/trustgraph/cli/show_explain_trace.py index a38476cb..c4da0d5a 100644 --- a/trustgraph-cli/trustgraph/cli/show_explain_trace.py +++ b/trustgraph-cli/trustgraph/cli/show_explain_trace.py @@ -27,6 +27,10 @@ from trustgraph.api import ( Synthesis, Analysis, Conclusion, + Decomposition, + Finding, + Plan, + StepResult, ) default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/') @@ -297,6 +301,23 @@ def print_docrag_text(trace, explain_client, api, user): print("No synthesis data found") +def _print_document_content(explain_client, api, user, document_uri, label="Answer"): + """Fetch and print document content, or fall back to URI.""" + if not document_uri: + return + content = "" + if api: + content = explain_client.fetch_document_content( + document_uri, api, user + ) + if content: + print(f"{label}:") + for line in content.split("\n"): + print(f" {line}") + else: + print(f"Document: {document_uri}") + + def print_agent_text(trace, explain_client, api, user): """Print Agent trace in text format.""" question = trace.get("question") @@ -310,82 +331,143 @@ def print_agent_text(trace, explain_client, api, user): print(f"Time: {question.timestamp}") print() - # Analysis steps - print("--- Analysis ---") - iterations = trace.get("iterations", []) - if iterations: - for i, analysis in enumerate(iterations, 1): - print(f"Analysis {i}:") - print(f" Thought: {analysis.thought or 'N/A'}") - print(f" Action: {analysis.action or 'N/A'}") + # Walk the steps list which contains all entity types + steps = trace.get("steps", []) + for step in steps: - if analysis.arguments: - # Try to pretty-print JSON arguments + if isinstance(step, Decomposition): + print("--- Decomposition ---") + print(f"Decomposed into {len(step.goals)} research threads:") + for i, goal in enumerate(step.goals): + print(f" {i}: {goal}") + print() + + elif isinstance(step, Finding): + print("--- Finding ---") + print(f"Goal: {step.goal}") + _print_document_content( + explain_client, api, user, step.document, "Result", + ) + print() + + elif isinstance(step, Plan): + print("--- Plan ---") + print(f"Plan with {len(step.steps)} steps:") + for i, s in enumerate(step.steps): + print(f" {i}: {s}") + print() + + elif isinstance(step, StepResult): + print("--- Step Result ---") + print(f"Step: {step.step}") + _print_document_content( + explain_client, api, user, step.document, "Result", + ) + print() + + elif isinstance(step, Analysis): + print("--- Analysis ---") + print(f" Action: {step.action or 'N/A'}") + + if step.arguments: try: - args_obj = json.loads(analysis.arguments) + args_obj = json.loads(step.arguments) args_str = json.dumps(args_obj, indent=4) print(f" Arguments:") for line in args_str.split('\n'): print(f" {line}") except Exception: - print(f" Arguments: {analysis.arguments}") - else: - print(f" Arguments: N/A") + print(f" Arguments: {step.arguments}") - obs = analysis.observation or 'N/A' + obs = step.observation or 'N/A' if obs and len(obs) > 200: obs = obs[:200] + "... [truncated]" print(f" Observation: {obs}") print() - else: - print("No analysis steps recorded") - print() - # Conclusion - print("--- Conclusion ---") - conclusion = trace.get("conclusion") - if conclusion: - content = "" - if conclusion.document and api: - content = explain_client.fetch_document_content( - conclusion.document, api, user + elif isinstance(step, Synthesis): + print("--- Synthesis ---") + _print_document_content( + explain_client, api, user, step.document, "Answer", ) - if content: - print("Answer:") - for line in content.split("\n"): - print(f" {line}") - elif conclusion.document: - print(f"Document: {conclusion.document}") - else: - print("No conclusion recorded") - else: - print("No conclusion recorded") + print() + + elif isinstance(step, Conclusion): + print("--- Conclusion ---") + _print_document_content( + explain_client, api, user, step.document, "Answer", + ) + print() + + if not steps: + print("No trace steps recorded") + print() def trace_to_dict(trace, trace_type): """Convert trace entities to JSON-serializable dict.""" if trace_type == "agent": question = trace.get("question") + + def _step_to_dict(step): + if isinstance(step, Decomposition): + return { + "type": "decomposition", + "id": step.uri, + "goals": step.goals, + } + elif isinstance(step, Finding): + return { + "type": "finding", + "id": step.uri, + "goal": step.goal, + "document": step.document, + } + elif isinstance(step, Plan): + return { + "type": "plan", + "id": step.uri, + "steps": step.steps, + } + elif isinstance(step, StepResult): + return { + "type": "step-result", + "id": step.uri, + "step": step.step, + "document": step.document, + } + elif isinstance(step, Analysis): + return { + "type": "analysis", + "id": step.uri, + "action": step.action, + "arguments": step.arguments, + "thought": step.thought, + "observation": step.observation, + } + elif isinstance(step, Synthesis): + return { + "type": "synthesis", + "id": step.uri, + "document": step.document, + } + elif isinstance(step, Conclusion): + return { + "type": "conclusion", + "id": step.uri, + "document": step.document, + } + return {"type": step.entity_type, "id": step.uri} + + steps = trace.get("steps", []) + return { "type": "agent", "session_id": question.uri if question else None, "question": question.query if question else None, "time": question.timestamp if question else None, - "iterations": [ - { - "id": a.uri, - "thought": a.thought, - "action": a.action, - "arguments": a.arguments, - "observation": a.observation, - } - for a in trace.get("iterations", []) - ], - "conclusion": { - "id": trace["conclusion"].uri, - "document": trace["conclusion"].document, - } if trace.get("conclusion") else None, + "steps": [_step_to_dict(s) for s in steps], } elif trace_type == "docrag": question = trace.get("question") diff --git a/trustgraph-flow/trustgraph/agent/orchestrator/aggregator.py b/trustgraph-flow/trustgraph/agent/orchestrator/aggregator.py index 9187f21e..cc5eb85c 100644 --- a/trustgraph-flow/trustgraph/agent/orchestrator/aggregator.py +++ b/trustgraph-flow/trustgraph/agent/orchestrator/aggregator.py @@ -57,7 +57,7 @@ class Aggregator: "request_template": request_template, "created_at": time.time(), } - logger.info( + logger.debug( f"Aggregator: registered fan-out {correlation_id}, " f"expecting {expected_siblings} subagents" ) @@ -82,7 +82,7 @@ class Aggregator: completed = len(entry["results"]) expected = entry["expected"] - logger.info( + logger.debug( f"Aggregator: {correlation_id} — " f"{completed}/{expected} subagents complete" ) diff --git a/trustgraph-flow/trustgraph/agent/orchestrator/pattern_base.py b/trustgraph-flow/trustgraph/agent/orchestrator/pattern_base.py index ddc4aed9..4faa7ce6 100644 --- a/trustgraph-flow/trustgraph/agent/orchestrator/pattern_base.py +++ b/trustgraph-flow/trustgraph/agent/orchestrator/pattern_base.py @@ -106,7 +106,7 @@ class PatternBase: ) await next(completion_request) - logger.info( + logger.debug( f"Subagent completion emitted for " f"correlation={request.correlation_id}, " f"goal={getattr(request, 'subagent_goal', '')}" diff --git a/trustgraph-flow/trustgraph/agent/orchestrator/react_pattern.py b/trustgraph-flow/trustgraph/agent/orchestrator/react_pattern.py index a03dc194..32261809 100644 --- a/trustgraph-flow/trustgraph/agent/orchestrator/react_pattern.py +++ b/trustgraph-flow/trustgraph/agent/orchestrator/react_pattern.py @@ -60,10 +60,6 @@ class ReactPattern(PatternBase): filtered_tools = self.filter_tools( self.processor.agent.tools, request, ) - logger.info( - f"Filtered from {len(self.processor.agent.tools)} " - f"to {len(filtered_tools)} available tools" - ) # Create temporary agent with filtered tools and optional framing additional_context = self.processor.agent.additional_context diff --git a/trustgraph-flow/trustgraph/agent/orchestrator/service.py b/trustgraph-flow/trustgraph/agent/orchestrator/service.py index 9ca3fe59..ed4c3983 100644 --- a/trustgraph-flow/trustgraph/agent/orchestrator/service.py +++ b/trustgraph-flow/trustgraph/agent/orchestrator/service.py @@ -414,7 +414,6 @@ class Processor(AgentService): self.meta_router = MetaRouter(config=config) logger.info(f"Loaded {len(tools)} tools") - logger.info("Tool configuration reloaded.") except Exception as e: logger.error( @@ -436,7 +435,7 @@ class Processor(AgentService): answer_text = step.observation break - logger.info( + logger.debug( f"Received subagent completion: " f"correlation={correlation_id}, goal={subagent_goal}" ) diff --git a/trustgraph-flow/trustgraph/agent/react/service.py b/trustgraph-flow/trustgraph/agent/react/service.py index 1bca9627..6c06f71a 100755 --- a/trustgraph-flow/trustgraph/agent/react/service.py +++ b/trustgraph-flow/trustgraph/agent/react/service.py @@ -550,8 +550,6 @@ class Processor(AgentService): current_state=getattr(request, 'state', None) ) - logger.info(f"Filtered from {len(self.agent.tools)} to {len(filtered_tools)} available tools") - # Create temporary agent with filtered tools temp_agent = AgentManager( tools=filtered_tools, diff --git a/trustgraph-flow/trustgraph/agent/tool_filter.py b/trustgraph-flow/trustgraph/agent/tool_filter.py index d1bac3e4..8a50bd41 100644 --- a/trustgraph-flow/trustgraph/agent/tool_filter.py +++ b/trustgraph-flow/trustgraph/agent/tool_filter.py @@ -34,17 +34,17 @@ def filter_tools_by_group_and_state( if current_state is None or current_state == "": current_state = "undefined" - logger.info(f"Filtering tools with groups={requested_groups}, state={current_state}") - + logger.debug(f"Filtering tools with groups={requested_groups}, state={current_state}") + filtered_tools = {} - + for tool_name, tool in tools.items(): if _is_tool_available(tool, requested_groups, current_state): filtered_tools[tool_name] = tool else: logger.debug(f"Tool {tool_name} filtered out") - - logger.info(f"Filtered {len(tools)} tools to {len(filtered_tools)} available tools") + + logger.debug(f"Filtered {len(tools)} tools to {len(filtered_tools)} available tools") return filtered_tools