From f00c2f721aa67f81d3cc5996de02daf1a192b0bb Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Fri, 11 Sep 2026 09:33:50 -0300 Subject: [PATCH] new feature: frontend with separated responses --- .../app/main.py | 2 + .../app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../app/main.py | 2 + .../app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../app/main.py | 2 + .../app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../agent_template_backend/app/main.py | 2 + .../agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + apps/agent_frontend/app.js | 468 ++++++------------ apps/agent_frontend/styles.css | 2 +- .../agent_framework/routing/pending_topics.py | 33 +- templates/agent_template_backend/app/main.py | 2 + templates/agent_template_backend/app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + .../app/main.py | 2 + .../app/state.py | 1 + .../app/workflows/agent_graph.py | 3 + tests/unit/test_pending_topics_isolation.py | 3 + 40 files changed, 248 insertions(+), 330 deletions(-) diff --git a/Tuning-Performance/Authentication/agent_template_backend_authentication/app/main.py b/Tuning-Performance/Authentication/agent_template_backend_authentication/app/main.py index b2d27c25..5908b5fb 100644 --- a/Tuning-Performance/Authentication/agent_template_backend_authentication/app/main.py +++ b/Tuning-Performance/Authentication/agent_template_backend_authentication/app/main.py @@ -369,6 +369,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py b/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py index 27d4f7b9..9533ad02 100644 --- a/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py +++ b/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py b/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py index 9fd902f4..0dcd6b49 100644 --- a/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py +++ b/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py @@ -490,6 +490,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -569,6 +570,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -662,6 +664,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/main.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/main.py index e8383282..9c9eca42 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/main.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/main.py @@ -363,6 +363,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py index 27d4f7b9..9533ad02 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py index 9fd902f4..0dcd6b49 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py @@ -490,6 +490,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -569,6 +570,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -662,6 +664,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/main.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/main.py index 7e9401f6..451574df 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/main.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/main.py @@ -358,6 +358,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py index 9ec69485..288e79c3 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py index 2b3ff2d2..8be1347c 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py @@ -488,6 +488,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -567,6 +568,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -660,6 +662,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/main.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/main.py index e8383282..9c9eca42 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/main.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/main.py @@ -363,6 +363,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py index 27d4f7b9..9533ad02 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py index 9fd902f4..0dcd6b49 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py @@ -490,6 +490,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -569,6 +570,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -662,6 +664,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/main.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/main.py index 7e9401f6..451574df 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/main.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/main.py @@ -358,6 +358,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py index 9ec69485..288e79c3 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py index 2b3ff2d2..8be1347c 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py @@ -488,6 +488,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -567,6 +568,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -660,6 +662,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/main.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/main.py index 7e9401f6..451574df 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/main.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/main.py @@ -358,6 +358,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py index 9ec69485..288e79c3 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 2b3ff2d2..8be1347c 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -488,6 +488,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -567,6 +568,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -660,6 +662,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/main.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/main.py index 7e9401f6..451574df 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/main.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/main.py @@ -358,6 +358,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py index 9ec69485..288e79c3 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py index 2b3ff2d2..8be1347c 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py @@ -488,6 +488,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -567,6 +568,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -660,6 +662,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/main.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/main.py index 7e9401f6..451574df 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/main.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/main.py @@ -358,6 +358,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py index 9ec69485..288e79c3 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py @@ -36,6 +36,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 2b3ff2d2..8be1347c 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -488,6 +488,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -567,6 +568,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -660,6 +662,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/main.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/main.py index 52585276..f594f59e 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/main.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/main.py @@ -457,6 +457,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py index fbb54b63..970df839 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py @@ -39,6 +39,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py index 72ed72e5..cd16da66 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py @@ -490,6 +490,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -569,6 +570,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -662,6 +664,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/main.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/main.py index 891f0e06..eab131ec 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/main.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/main.py @@ -457,6 +457,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py index aee4d926..e048cf13 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py @@ -40,6 +40,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py index 72ed72e5..cd16da66 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py @@ -490,6 +490,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -569,6 +570,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -662,6 +664,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/apps/agent_frontend/app.js b/apps/agent_frontend/app.js index b9bb182b..f18024a2 100644 --- a/apps/agent_frontend/app.js +++ b/apps/agent_frontend/app.js @@ -1,364 +1,176 @@ -const chat=document.getElementById('chat'); -const form=document.getElementById('form'); -let eventSource=null; +const chat = document.getElementById("chat"); +const form = document.getElementById("form"); +let eventSource = null; let currentSessionId = null; +const renderedResponses = new Set(); -function add(role,text){ - const d=document.createElement('div'); - d.className='msg '+role; - d.textContent=text; - chat.appendChild(d); - chat.scrollTop=chat.scrollHeight; +function status(text) { const el = document.getElementById("status"); if (el) el.textContent = text; } +function val(id) { return (document.getElementById(id)?.value || "").trim(); } +function uuid() { return crypto.randomUUID(); } +function humanizeAgentName(value) { + return String(value || "agent").trim().replace(/([a-zà-ÿ])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()); } -function status(text){const el=document.getElementById('status'); if(el) el.textContent=text;} -function val(id){return (document.getElementById(id)?.value || '').trim();} -function uuid(){return crypto.randomUUID();} - -function buildBusinessContext(session, messageId){ - return { - customer_key: val('customerKey') || null, - contract_key: val('contractKey') || null, - interaction_key: val('interactionKey') || messageId, - account_key: val('accountKey') || null, - resource_key: val('resourceKey') || null, - session_key: session || null, - metadata: {frontend: 'agent_frontend', version: 'business-context-v2'} - }; +function responseText(value) { + if (typeof value === "string") return value; + if (value == null) return ""; + return JSON.stringify(value, null, 2); } - -function syncDomainAliases(payload, businessContext){ - const agent=val('agent'); - if(agent === 'retail_orders'){ - payload.customer_id = businessContext.customer_key; - payload.order_id = businessContext.contract_key; - } else { - payload.msisdn = businessContext.customer_key; - payload.invoice_id = businessContext.contract_key; - payload.ura_call_id = businessContext.interaction_key; - payload.asset_id = businessContext.resource_key; - } +function splitTaggedAgentMessages(value) { + const text = responseText(value).trim(); + if (!text) return []; + const marker = /^\[([^\]\r\n]*Agent)\]\s*/gim; + const matches = [...text.matchAll(marker)]; + if (!matches.length || matches[0].index !== 0) return []; + return matches.map((match, index) => { + const start = match.index + match[0].length; + const end = index + 1 < matches.length ? matches[index + 1].index : text.length; + return { agent: match[1].trim(), text: text.slice(start, end).trim() }; + }).filter((item) => item.agent && item.text); } - -function adicionarMensagem(role, text) { - const chat = - document.getElementById("chat") || - document.getElementById("messages") || - document.querySelector(".chat") || - document.querySelector(".messages") || - document.querySelector("[data-chat]"); - - if (!chat) { - console.error("Não encontrei o container do chat no HTML."); - console.log("Mensagem que seria exibida:", role, text); - return; - } - +function stripOwnAgentMarker(value, agent) { + const text = responseText(value).trim(); + const marker = text.match(/^\[([^\]\r\n]*Agent)\]\s*/i); + if (!marker) return text; + const normalize = (name) => String(name || "").replace(/[_\s-]+/g, "").toLowerCase(); + return normalize(marker[1]) === normalize(agent) ? text.slice(marker[0].length).trim() : text; +} +function addUserMessage(text) { const div = document.createElement("div"); - - if (role === "user") { - div.className = "msg user chat-bubble--user"; - } else { - div.className = "msg assistant chat-bubble--agent"; - } - - div.textContent = text || ""; - + div.className = "msg user chat-bubble--user"; + div.textContent = text; chat.appendChild(div); chat.scrollTop = chat.scrollHeight; } - -function abrirSSE(sessionId) { - if (!sessionId) { - console.error("Não vou abrir SSE sem sessionId."); - return; +function addAgentMessage(agentId, text, group) { + const article = document.createElement("article"); + article.className = "agent-message"; + article.dataset.agentId = agentId || "agent"; + const header = document.createElement("header"); + header.className = "agent-message__header"; + const avatar = document.createElement("span"); + avatar.className = "agent-message__avatar"; + avatar.textContent = humanizeAgentName(agentId).charAt(0) || "A"; + avatar.setAttribute("aria-hidden", "true"); + const name = document.createElement("span"); + name.className = "agent-message__name"; + name.textContent = humanizeAgentName(agentId); + const body = document.createElement("div"); + body.className = "agent-message__body"; + body.textContent = responseText(text); + header.append(avatar, name); + article.append(header, body); + group.appendChild(article); +} +function getAgentMessages(data) { + const metadata = data?.metadata || {}; + const candidates = data?.supervisor_results || metadata.supervisor_results || data?.agent_responses || metadata.agent_responses || (Array.isArray(data?.messages) ? data.messages : null); + if (Array.isArray(candidates)) { + const messages = candidates.map((item) => ({ + agent: item?.agent || item?.agent_id || item?.agentId || item?.name, + text: stripOwnAgentMarker(item?.answer ?? item?.text ?? item?.message ?? item?.response ?? item?.content, item?.agent || item?.agent_id || item?.agentId || item?.name), + })).filter((item) => item.agent && responseText(item.text).trim()); + if (messages.length) return messages; } - - if (eventSource) { - eventSource.close(); - eventSource = null; - } - - const url = `${backend}/gateway/events/${sessionId}`; - - eventSource = new EventSource(url); - - eventSource.onopen = () => { - console.log("SSE OPEN"); + const text = data?.text ?? data?.speak ?? data?.message ?? data?.response ?? data?.content ?? data?.output; + const taggedMessages = splitTaggedAgentMessages(text); + if (taggedMessages.length) return taggedMessages; + const agent = metadata.active_agent || metadata.route || data?.agent_id || metadata.agent_id || val("agent") || "agent"; + return responseText(text).trim() ? [{ agent, text }] : []; +} +function responseKey(data) { + const stableId = data?.metadata?.message_id || data?.message_id; + return stableId ? `id:${stableId}` : JSON.stringify(getAgentMessages(data)); +} +function renderAgentResponse(data) { + const normalized = typeof data === "string" ? { text: data } : (data || {}); + const messages = getAgentMessages(normalized); + if (!messages.length) return false; + const key = responseKey(normalized); + if (renderedResponses.has(key)) return false; + renderedResponses.add(key); + const group = document.createElement("section"); + group.className = "agent-response-group"; + group.setAttribute("aria-label", "Respostas dos agentes"); + messages.forEach((message) => addAgentMessage(message.agent, message.text, group)); + chat.appendChild(group); + chat.scrollTop = chat.scrollHeight; + return true; +} +function buildBusinessContext(session, messageId) { + return { + customer_key: val("customerKey") || null, contract_key: val("contractKey") || null, + interaction_key: val("interactionKey") || messageId, account_key: val("accountKey") || null, + resource_key: val("resourceKey") || null, session_key: session || null, + metadata: { frontend: "agent_frontend", version: "business-context-v2" }, }; - - eventSource.onerror = (err) => { - console.error("SSE ERROR:", err); - }; - - const eventos = [ - "connected", - "waiting", - "backend.selected", - "flow.start", - "workflow.started", - "message.responded", - "workflow.completed", - "flow.end", - "error" - ]; - - for (const nome of eventos) { - eventSource.addEventListener(nome, (event) => { - - if (nome === "message.responded") { - try { - const data = JSON.parse(event.data); - - const text = - data.text || - data.message || - data.response || - data.content || - data.output || - event.data; - - adicionarMensagem("assistant", text); - } catch { - adicionarMensagem("assistant", event.data); - } - } - - if (nome === "error") { - adicionarMensagem("assistant", `Erro SSE: ${event.data}`); - } - }); +} +function syncDomainAliases(payload, businessContext) { + if (val("agent") === "retail_orders") { + payload.customer_id = businessContext.customer_key; payload.order_id = businessContext.contract_key; + } else { + payload.msisdn = businessContext.customer_key; payload.invoice_id = businessContext.contract_key; + payload.ura_call_id = businessContext.interaction_key; payload.asset_id = businessContext.resource_key; } } - function normalizeSessionId(value) { if (!value) return uuid(); - - const parts = value.split(":"); - return parts[parts.length - 1]; // mantém só o UUID final + const parts = value.split(":"); return parts[parts.length - 1]; } - function connectSSE(backend, sessionId) { - if (!sessionId) { - console.warn("SSE não aberto: sessionId ausente."); - return; - } - - if (!backend) { - console.warn("SSE não aberto: backend ausente."); - return; - } - - if (eventSource) { - console.log("Fechando SSE anterior:", eventSource.url); - eventSource.close(); - eventSource = null; - } - + if (!backend || !sessionId) return; + if (eventSource) eventSource.close(); const url = `${backend.replace(/\/$/, "")}/gateway/events/${encodeURIComponent(sessionId)}`; - - console.log("Abrindo SSE:", url); - eventSource = new EventSource(url); - eventSource._sessionId = sessionId; - - eventSource.onopen = () => { - console.log("SSE OPEN:", url); - status("SSE conectado"); + eventSource.onopen = () => status("SSE conectado"); + eventSource.onerror = () => { + if (eventSource?.readyState === EventSource.CONNECTING) status("SSE aguardando/reconectando"); + else if (eventSource?.readyState === EventSource.CLOSED) status("SSE fechado"); + else status("SSE com erro"); }; - - eventSource.onerror = (err) => { - console.error("SSE ERROR raw:", err); - console.error("SSE readyState:", eventSource?.readyState); - console.error("SSE url:", eventSource?.url); - - if (eventSource?.readyState === EventSource.CONNECTING) { - status("SSE aguardando/reconectando"); - return; - } - - if (eventSource?.readyState === EventSource.CLOSED) { - status("SSE fechado"); - return; - } - - status("SSE com erro"); - }; - - eventSource.addEventListener("connected", (event) => { - console.log("SSE connected:", event.data); - status("SSE conectado"); - }); - - eventSource.addEventListener("waiting", (event) => { - console.log("SSE waiting:", event.data); - status("SSE aguardando backend"); - }); - - eventSource.addEventListener("backend.selected", (event) => { - console.log("SSE backend.selected:", event.data); - status("Backend selecionado"); - }); - - eventSource.addEventListener("flow.start", (event) => { - console.log("SSE flow.start:", event.data); - status("Fluxo iniciado"); - }); - - eventSource.addEventListener("workflow.started", (event) => { - console.log("SSE workflow.started:", event.data); - status("Workflow em execução"); - }); - - eventSource.addEventListener("session.upserted", (event) => { - try { - const data = JSON.parse(event.data); - if (data.business_context) { - console.debug("business_context", data.business_context); - } - } catch (e) { - console.warn("Não consegui interpretar session.upserted:", event.data); - } - }); - + const statuses = { waiting: "SSE aguardando backend", "backend.selected": "Backend selecionado", "flow.start": "Fluxo iniciado", "workflow.started": "Workflow em execução", "workflow.completed": "Workflow concluído", "flow.end": "Fluxo finalizado" }; + Object.entries(statuses).forEach(([eventName, label]) => eventSource.addEventListener(eventName, () => status(label))); eventSource.addEventListener("message.responded", (event) => { - try { - const data = JSON.parse(event.data); - - const text = - data.text || - data.message || - data.response || - data.content || - data.output || - event.data; - - if (text) { - add("assistant", text); - } - - if (data.metadata?.business_context) { - console.debug("metadata.business_context", data.metadata.business_context); - } - - status("Resposta recebida"); - } catch (e) { - add("assistant", event.data); - status("Resposta recebida"); - } + try { renderAgentResponse(JSON.parse(event.data)); } catch { renderAgentResponse(event.data); } + status("Resposta recebida"); }); - - eventSource.addEventListener("workflow.completed", (event) => { - console.log("SSE workflow.completed:", event.data); - status("Workflow concluído"); - }); - - eventSource.addEventListener("flow.end", (event) => { - console.log("SSE flow.end:", event.data); - status("Fluxo finalizado"); - }); - - // Use um nome diferente de "error" para erro enviado pelo servidor. - // "error" é reservado/conflituoso com erro nativo do EventSource. eventSource.addEventListener("server.error", (event) => { - console.error("SSE server.error:", event.data); - add("assistant", `Erro SSE: ${event.data || "erro informado pelo servidor"}`); + renderAgentResponse({ text: `Erro SSE: ${event.data || "erro informado pelo servidor"}`, agent_id: "sistema" }); status("Erro no fluxo SSE"); }); } -form.addEventListener('submit', async (e) => { - e.preventDefault(); - - const input = document.getElementById('message'); +form.addEventListener("submit", async (event) => { + event.preventDefault(); + const input = document.getElementById("message"); const text = input.value.trim(); - if (!text) return; - - adicionarMensagem('user', text); - input.value = ''; - - const backend = val('backend').replace(/\/$/, ''); - const channel = val('channel'); - // const session = val('session') || uuid(); - const session = normalizeSessionId(val('session')); + addUserMessage(text); input.value = ""; + const backend = val("backend").replace(/\/$/, ""); + const channel = val("channel"); + const session = normalizeSessionId(val("session")); const messageId = uuid(); - const tenantId = val('tenant') || 'default'; - const agentId = val('agent') || 'telecom_contas'; - - document.getElementById('session').value = session; - + const tenantId = val("tenant") || "default"; + const agentId = val("agent") || "telecom_contas"; + document.getElementById("session").value = session; const businessContext = buildBusinessContext(session, messageId); - - const commonContext = { - channel_id: 'browser', - tenant_id: tenantId, - agent_id: agentId, - business_context: businessContext - }; - - const payload = channel === 'voice' - ? { - transcript: text, - session_id: session, - ani: businessContext.customer_key, - message_id: messageId, - tenant_id: tenantId, - agent_id: agentId, - context: commonContext - } - : { - message: text, - text: text, - session_id: session, - user_id: businessContext.customer_key || 'web-user', - message_id: messageId, - tenant_id: tenantId, - agent_id: agentId, - context: commonContext - }; - + const commonContext = { channel_id: "browser", tenant_id: tenantId, agent_id: agentId, business_context: businessContext }; + const payload = channel === "voice" + ? { transcript: text, session_id: session, ani: businessContext.customer_key, message_id: messageId, tenant_id: tenantId, agent_id: agentId, context: commonContext } + : { message: text, text, session_id: session, user_id: businessContext.customer_key || "web-user", message_id: messageId, tenant_id: tenantId, agent_id: agentId, context: commonContext }; syncDomainAliases(payload, businessContext); - + if (document.getElementById("useSse")?.checked) connectSSE(backend, session); try { - status('Enviando mensagem'); - - const res = await fetch(`${backend}/gateway/message`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - channel, - tenant_id: tenantId, - agent_id: agentId, - payload - }) - }); - - if (!res.ok) { - throw new Error(`${res.status} ${res.statusText}`); - } - - const data = await res.json(); - + status("Enviando mensagem"); + const response = await fetch(`${backend}/gateway/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ channel, tenant_id: tenantId, agent_id: agentId, payload }) }); + if (!response.ok) throw new Error(`${response.status} ${response.statusText}`); + const data = await response.json(); const returnedSessionId = data.session_id || session; currentSessionId = returnedSessionId; - document.getElementById('session').value = returnedSessionId; - - const resposta = - data.text || - data.speak || - data.message || - data.response || - data.content || - data.output || - JSON.stringify(data); - - adicionarMensagem('assistant', resposta); - status('Resposta recebida'); - - } catch (err) { - adicionarMensagem('assistant', `Erro ao chamar backend: ${err.message}`); - status('Erro de conexão'); + document.getElementById("session").value = returnedSessionId; + renderAgentResponse(data); + status("Resposta recebida"); + } catch (error) { + renderAgentResponse({ text: `Erro ao chamar backend: ${error.message}`, agent_id: "sistema" }); + status("Erro de conexão"); } -}); \ No newline at end of file +}); diff --git a/apps/agent_frontend/styles.css b/apps/agent_frontend/styles.css index caf85923..7fd84306 100644 --- a/apps/agent_frontend/styles.css +++ b/apps/agent_frontend/styles.css @@ -1 +1 @@ -body{font-family:system-ui,Arial,sans-serif;background:#f6f7fb;margin:0}.app{max-width:900px;margin:0 auto;padding:24px}header{margin-bottom:16px}.config{display:grid;grid-template-columns:1fr 160px 1fr;gap:12px;margin-bottom:16px}.config input,.config select,#message{width:100%;padding:10px;border:1px solid #ccc;border-radius:8px}.chat{background:#fff;border:1px solid #ddd;border-radius:12px;min-height:420px;padding:16px;overflow:auto}.msg{padding:10px 12px;border-radius:10px;margin:8px 0;max-width:75%}.user{background:#e8f0ff;margin-left:auto}.assistant{background:#f0f0f0}form{display:flex;gap:8px;margin-top:12px}button{padding:10px 16px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer} +body{font-family:system-ui,Arial,sans-serif;background:#f6f7fb;margin:0;color:#202124}.app{max-width:900px;margin:0 auto;padding:24px}header{margin-bottom:16px}.config{display:grid;grid-template-columns:1fr 160px 1fr;gap:12px;margin-bottom:16px}.config input,.config select,#message{box-sizing:border-box;width:100%;padding:10px;border:1px solid #ccc;border-radius:8px}.chat{background:#fff;border:1px solid #ddd;border-radius:12px;min-height:420px;max-height:65vh;padding:16px;overflow:auto}.msg{padding:10px 12px;border-radius:10px;margin:8px 0;max-width:75%;white-space:pre-wrap}.user{background:#e8f0ff;margin-left:auto}.agent-response-group{display:grid;gap:10px;margin:12px 0;max-width:82%}.agent-message{overflow:hidden;border:1px solid #dfe3eb;border-radius:12px;background:#f8f9fb;box-shadow:0 1px 2px rgba(20,30,50,.05)}.agent-message__header{display:flex;align-items:center;gap:8px;margin:0;padding:8px 12px;border-bottom:1px solid #e5e8ee;background:#eef1f6}.agent-message__avatar{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;background:#263f73;color:#fff;font-size:12px;font-weight:700}.agent-message__name{font-size:13px;font-weight:700;color:#34425e}.agent-message__body{padding:11px 12px;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.45}form{display:flex;gap:8px;margin-top:12px}button{padding:10px 16px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer}@media(max-width:720px){.app{padding:12px}.config{grid-template-columns:1fr}.agent-response-group,.msg{max-width:92%}} diff --git a/libs/agent_framework/src/agent_framework/routing/pending_topics.py b/libs/agent_framework/src/agent_framework/routing/pending_topics.py index 8996e88f..37693c48 100644 --- a/libs/agent_framework/src/agent_framework/routing/pending_topics.py +++ b/libs/agent_framework/src/agent_framework/routing/pending_topics.py @@ -11,6 +11,7 @@ AgentHandler = Callable[[dict[str, Any]], Awaitable[dict[str, Any]]] @dataclass(frozen=True) class PendingTopicDrain: answer: str + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] mcp_results: list[dict[str, Any]] @@ -33,6 +34,23 @@ async def drain_pending_topics( combined_mcp_results = list(state.get("mcp_results") or []) combined_rag_results = list(state.get("rag_results") or []) secondary_answers: list[str] = [] + agent_responses = list(state.get("agent_responses") or []) + if not agent_responses and str(candidate or "").strip(): + primary_agent = str( + state.get("active_agent") + or state.get("route") + or route.get("agent") + or route.get("route") + or state.get("agent_id") + or "agent" + ) + agent_responses.append({ + "agent": primary_agent, + "intent": state.get("intent") or route.get("intent"), + "answer": str(candidate).strip(), + "primary": True, + "status": "completed", + }) for topic in topics: disposition = topic.get("disposition") if disposition == "defer": @@ -105,6 +123,14 @@ async def drain_pending_topics( if rag_evidence not in combined_rag_results: combined_rag_results.append(rag_evidence) secondary_answers.append(answer) + agent_responses.append({ + "operation_id": topic.get("operation_id"), + "agent": topic.get("agent"), + "intent": topic.get("intent"), + "answer": answer, + "primary": False, + "status": "completed", + }) handled.append({**topic, "status": "completed"}) secondary_answers.extend(MultiIntentPlanner.public_messages(plan)) @@ -116,5 +142,10 @@ async def drain_pending_topics( secondary = "\n\n".join(unique) candidate = f"{candidate.rstrip()}\n\n{secondary}".strip() return PendingTopicDrain( - candidate, remaining, handled, combined_mcp_results, combined_rag_results + answer=candidate, + agent_responses=agent_responses, + pending_topics=remaining, + handled_topics=handled, + mcp_results=combined_mcp_results, + rag_results=combined_rag_results, ) diff --git a/templates/agent_template_backend/app/main.py b/templates/agent_template_backend/app/main.py index 891f0e06..eab131ec 100644 --- a/templates/agent_template_backend/app/main.py +++ b/templates/agent_template_backend/app/main.py @@ -457,6 +457,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/templates/agent_template_backend/app/state.py b/templates/agent_template_backend/app/state.py index aee4d926..e048cf13 100644 --- a/templates/agent_template_backend/app/state.py +++ b/templates/agent_template_backend/app/state.py @@ -40,6 +40,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/templates/agent_template_backend/app/workflows/agent_graph.py b/templates/agent_template_backend/app/workflows/agent_graph.py index f76c2c7d..33f65688 100644 --- a/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/templates/agent_template_backend/app/workflows/agent_graph.py @@ -490,6 +490,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -569,6 +570,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -650,6 +652,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/templates/agent_template_backend_day_zero/app/main.py b/templates/agent_template_backend_day_zero/app/main.py index 0b9f0a6e..1128d7c2 100644 --- a/templates/agent_template_backend_day_zero/app/main.py +++ b/templates/agent_template_backend_day_zero/app/main.py @@ -452,6 +452,8 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) "route": result.get("route"), "intent": result.get("intent"), "route_decision": result.get("route_decision"), + "supervisor_results": result.get("supervisor_results") or [], + "agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [], "domain": result.get("domain"), "mcp_tools": result.get("mcp_tools"), "mcp_results": result.get("mcp_results"), diff --git a/templates/agent_template_backend_day_zero/app/state.py b/templates/agent_template_backend_day_zero/app/state.py index a0016e73..b27c56ad 100644 --- a/templates/agent_template_backend_day_zero/app/state.py +++ b/templates/agent_template_backend_day_zero/app/state.py @@ -37,6 +37,7 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] active_agent: str diff --git a/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index aa4726f6..bcc4e6a6 100644 --- a/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -488,6 +488,7 @@ class AgentWorkflow: return { "answer": answer, "supervisor_results": partials, + "agent_responses": partials, "mcp_results": mcp_results, "next_state": "SUPERVISOR_ACTIVE", } @@ -567,6 +568,7 @@ class AgentWorkflow: "final_answer": candidate, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "output_guardrails_already_applied": False, @@ -648,6 +650,7 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "pending_topics": drained.pending_topics, "handled_topics": drained.handled_topics, + "agent_responses": drained.agent_responses, "mcp_results": drained.mcp_results, "rag_results": drained.rag_results, "guardrail_decisions": state.get("guardrail_decisions", []) diff --git a/tests/unit/test_pending_topics_isolation.py b/tests/unit/test_pending_topics_isolation.py index e2dbb948..618fad2a 100644 --- a/tests/unit/test_pending_topics_isolation.py +++ b/tests/unit/test_pending_topics_isolation.py @@ -56,6 +56,9 @@ async def test_secondary_topic_does_not_inherit_primary_transaction_state(): assert captured["domain"] == "telecom_contas" assert result.answer.startswith("Você confirma o cancelamento") assert result.answer.endswith("Aqui está a sua segunda via.") + assert [item["agent"] for item in result.agent_responses] == ["agent", "faturas_agent"] + assert result.agent_responses[0]["primary"] is True + assert result.agent_responses[1]["primary"] is False assert result.pending_topics == [] assert result.mcp_results == [ {"tool_name": "consultar_vas"},