mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-06 11:24:13 +00:00
new feature: frontend with separated responses
This commit is contained in:
@@ -248,6 +248,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -576,6 +603,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -248,6 +248,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -576,6 +603,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -246,6 +246,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -574,6 +601,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -248,6 +248,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -576,6 +603,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -246,6 +246,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -574,6 +601,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -246,6 +246,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -574,6 +601,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -246,6 +246,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -574,6 +601,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -246,6 +246,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -574,6 +601,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -248,6 +248,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -576,6 +603,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -248,6 +248,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -576,6 +603,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -338,9 +338,43 @@ class LoopRail(Guardrail):
|
||||
code = "VLOOP"
|
||||
stage = "input"
|
||||
|
||||
@staticmethod
|
||||
def _transaction_status(ctx: dict[str, Any]) -> str:
|
||||
nonterminal = {
|
||||
"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION", "EXECUTING",
|
||||
"PAUSED", "WAITING_INPUT",
|
||||
}
|
||||
for result in reversed(list(ctx.get("mcp_results") or [])):
|
||||
if isinstance(result, dict):
|
||||
result_status = str(result.get("transaction_status") or "").strip().upper()
|
||||
if result_status in nonterminal:
|
||||
return result_status
|
||||
active_transaction = ctx.get("active_transaction")
|
||||
if isinstance(active_transaction, dict):
|
||||
active_status = str(active_transaction.get("status") or "").strip().upper()
|
||||
if active_status in nonterminal:
|
||||
return active_status
|
||||
return str(ctx.get("transaction_status") or "").strip().upper()
|
||||
|
||||
async def evaluate(self, text: str, context: dict[str, Any]) -> RailDecision:
|
||||
ctx = _ctx(context)
|
||||
transaction_status = self._transaction_status(ctx)
|
||||
if transaction_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason=f"continuidade_transacional:{transaction_status}",
|
||||
sanitized_text=text,
|
||||
metadata={
|
||||
"history_window": len(list(ctx.get("history_texts") or [])[-6:]),
|
||||
"repeated": False,
|
||||
"mechanism": "deterministic_transaction_bypass",
|
||||
"transaction_status": transaction_status,
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
normalized = _lower(text).strip()
|
||||
history = [_lower(h).strip() for h in _ctx(context).get("history_texts", [])[-6:]]
|
||||
history = [_lower(h).strip() for h in ctx.get("history_texts", [])[-6:]]
|
||||
repeated = history.count(normalized) >= 2 if normalized else False
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
|
||||
@@ -358,30 +358,38 @@ class LoopRail(Guardrail):
|
||||
|
||||
@staticmethod
|
||||
def _transaction_status(ctx: dict[str, Any]) -> str:
|
||||
status = str(ctx.get("transaction_status") or "").strip().upper()
|
||||
if status:
|
||||
return status
|
||||
nonterminal = {
|
||||
"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION", "EXECUTING",
|
||||
"PAUSED", "WAITING_INPUT",
|
||||
}
|
||||
# A queued multi-intent transaction may be promoted immediately after
|
||||
# the previous one completed. Prefer the newest live MCP evidence over
|
||||
# a stale top-level COMPLETED status left by checkpoint merge order.
|
||||
for result in reversed(list(ctx.get("mcp_results") or [])):
|
||||
if isinstance(result, dict):
|
||||
result_status = str(result.get("transaction_status") or "").strip().upper()
|
||||
if result_status in nonterminal:
|
||||
return result_status
|
||||
active_transaction = ctx.get("active_transaction")
|
||||
if isinstance(active_transaction, dict):
|
||||
return str(active_transaction.get("status") or "").strip().upper()
|
||||
return ""
|
||||
active_status = str(active_transaction.get("status") or "").strip().upper()
|
||||
if active_status in nonterminal:
|
||||
return active_status
|
||||
return str(ctx.get("transaction_status") or "").strip().upper()
|
||||
|
||||
async def evaluate(self, text: str, context: dict[str, Any]) -> RailDecision:
|
||||
ctx = _ctx(context)
|
||||
transaction_status = self._transaction_status(ctx)
|
||||
|
||||
# A short confirmation (for example ``sim``/``não``) may legitimately
|
||||
# appear several times in the same session because each transaction has
|
||||
# its own confirmation boundary. VLOOP protects against conversational
|
||||
# repetition; it must not consume the input that belongs to an active
|
||||
# transaction awaiting confirmation. The transaction runtime/classifier
|
||||
# remains responsible for deciding whether the utterance is actually a
|
||||
# valid confirm/reject response.
|
||||
if transaction_status == "AWAITING_CONFIRMATION":
|
||||
# Parameter values and short confirmations may legitimately repeat in
|
||||
# the same session. VLOOP must not consume input owned by the active
|
||||
# transaction contract; extraction/confirmation remains responsible
|
||||
# for deciding whether the value is valid.
|
||||
if transaction_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="continuidade_transacional:AWAITING_CONFIRMATION",
|
||||
reason=f"continuidade_transacional:{transaction_status}",
|
||||
sanitized_text=text,
|
||||
metadata={
|
||||
"history_window": len(list(ctx.get("history_texts") or [])[-6:]),
|
||||
|
||||
@@ -69,17 +69,30 @@ class MultiIntentPlanner:
|
||||
|
||||
def _intent_for_clause(self, clause: str) -> IntentDefinition | None:
|
||||
normalized = self._normalize(clause)
|
||||
clause_tokens = set(re.findall(r"[\w]+", normalized, flags=re.UNICODE))
|
||||
ordered_clause_tokens = re.findall(r"[\w]+", normalized, flags=re.UNICODE)
|
||||
clause_tokens = set(ordered_clause_tokens)
|
||||
matches: list[tuple[int, int, IntentDefinition]] = []
|
||||
for intent in self.intents:
|
||||
for keyword in intent.keywords:
|
||||
token = self._normalize(keyword)
|
||||
keyword_tokens = re.findall(r"[\w]+", token, flags=re.UNICODE)
|
||||
# Expressões configuradas representam conceitos, não somente
|
||||
# substrings literais. Preserve a ordem dos termos e aceite
|
||||
# qualificadores intermediários: "cancelar pedido" também
|
||||
# reconhece "cancelar meu pedido", sem enumerar variações no YAML.
|
||||
cursor = 0
|
||||
ordered_match = bool(keyword_tokens)
|
||||
for keyword_token in keyword_tokens:
|
||||
try:
|
||||
cursor = ordered_clause_tokens.index(keyword_token, cursor) + 1
|
||||
except ValueError:
|
||||
ordered_match = False
|
||||
break
|
||||
matched = bool(
|
||||
keyword_tokens
|
||||
and (
|
||||
(len(keyword_tokens) == 1 and keyword_tokens[0] in clause_tokens)
|
||||
or (len(keyword_tokens) > 1 and token in normalized)
|
||||
or (len(keyword_tokens) > 1 and ordered_match)
|
||||
)
|
||||
)
|
||||
if matched:
|
||||
@@ -146,7 +159,7 @@ class MultiIntentPlanner:
|
||||
if len(operations) < 2:
|
||||
return None
|
||||
transactional = [item for item in operations if self._is_transactional(item)]
|
||||
if len(transactional) == 1:
|
||||
if transactional:
|
||||
primary_operation = transactional[0]
|
||||
operations.sort(key=lambda item: item is not primary_operation)
|
||||
for operation in operations[1:]:
|
||||
|
||||
@@ -16,6 +16,29 @@ class PendingTopicDrain:
|
||||
handled_topics: list[dict[str, Any]]
|
||||
mcp_results: list[dict[str, Any]]
|
||||
rag_results: list[dict[str, Any]]
|
||||
state_patch: dict[str, Any]
|
||||
|
||||
|
||||
_LIVE_TRANSACTION_STATUSES = {
|
||||
"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION", "WAITING_CONFIRMATION",
|
||||
"PENDING", "RUNNING", "IN_PROGRESS",
|
||||
}
|
||||
|
||||
_TRANSACTION_STATE_KEYS = {
|
||||
"active_transaction", "transaction_status", "transaction_pre_validation",
|
||||
"confirmation_required", "confirmation_received", "selected_tool_call",
|
||||
"pending_tool_call", "pending_tool_clarification", "pending_domain_workflow",
|
||||
"missing_parameters", "tool_policy_result", "next_state", "workflow_id",
|
||||
}
|
||||
|
||||
|
||||
def _has_live_transaction(state: dict[str, Any]) -> bool:
|
||||
active = state.get("active_transaction")
|
||||
active_status = active.get("status") if isinstance(active, dict) else None
|
||||
status = str(active_status or state.get("transaction_status") or "").upper()
|
||||
if status:
|
||||
return status in _LIVE_TRANSACTION_STATUSES
|
||||
return bool(state.get("confirmation_required") or state.get("pending_tool_call"))
|
||||
|
||||
|
||||
async def drain_pending_topics(
|
||||
@@ -34,6 +57,8 @@ 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] = []
|
||||
state_patch: dict[str, Any] = {}
|
||||
may_promote_deferred = not _has_live_transaction(state)
|
||||
agent_responses = list(state.get("agent_responses") or [])
|
||||
if not agent_responses and str(candidate or "").strip():
|
||||
primary_agent = str(
|
||||
@@ -53,9 +78,15 @@ async def drain_pending_topics(
|
||||
})
|
||||
for topic in topics:
|
||||
disposition = topic.get("disposition")
|
||||
promoted = False
|
||||
if disposition == "defer":
|
||||
remaining.append(topic)
|
||||
continue
|
||||
if not may_promote_deferred:
|
||||
remaining.append(topic)
|
||||
continue
|
||||
topic = {**topic, "disposition": "execute", "status": "pending"}
|
||||
disposition = "execute"
|
||||
promoted = True
|
||||
may_promote_deferred = False
|
||||
if disposition != "execute":
|
||||
handled.append({**topic, "status": "completed"})
|
||||
continue
|
||||
@@ -123,14 +154,42 @@ async def drain_pending_topics(
|
||||
if rag_evidence not in combined_rag_results:
|
||||
combined_rag_results.append(rag_evidence)
|
||||
secondary_answers.append(answer)
|
||||
if promoted:
|
||||
agent_responses = [{**item, "primary": False} for item in agent_responses]
|
||||
agent_responses.append({
|
||||
"operation_id": topic.get("operation_id"),
|
||||
"agent": topic.get("agent"),
|
||||
"intent": topic.get("intent"),
|
||||
"answer": answer,
|
||||
"primary": False,
|
||||
"primary": promoted,
|
||||
"status": "completed",
|
||||
})
|
||||
if promoted:
|
||||
for key in _TRANSACTION_STATE_KEYS:
|
||||
if key in (result or {}):
|
||||
state_patch[key] = result[key]
|
||||
state_patch.update({
|
||||
"route": topic.get("agent"),
|
||||
"active_agent": topic.get("agent"),
|
||||
"intent": topic.get("intent"),
|
||||
"domain": topic.get("domain"),
|
||||
"mcp_tools": topic.get("tools") or [],
|
||||
# Replace the completed transaction's continuity snapshot so
|
||||
# the next reply cannot be routed back to the previous agent.
|
||||
"route_decision": {
|
||||
"route": topic.get("agent"),
|
||||
"agent": topic.get("agent"),
|
||||
"intent": topic.get("intent"),
|
||||
"domain": topic.get("domain"),
|
||||
"mcp_tools": topic.get("tools") or [],
|
||||
"next_state": (result or {}).get("next_state"),
|
||||
"metadata": {
|
||||
"multi_intent_secondary": True,
|
||||
"promoted_from_pending_topics": True,
|
||||
"operation_id": topic.get("operation_id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
handled.append({**topic, "status": "completed"})
|
||||
|
||||
secondary_answers.extend(MultiIntentPlanner.public_messages(plan))
|
||||
@@ -148,4 +207,5 @@ async def drain_pending_topics(
|
||||
handled_topics=handled,
|
||||
mcp_results=combined_mcp_results,
|
||||
rag_results=combined_rag_results,
|
||||
state_patch=state_patch,
|
||||
)
|
||||
|
||||
@@ -249,6 +249,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -267,6 +289,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -576,6 +603,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -655,6 +683,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -247,6 +247,28 @@ class AgentWorkflow:
|
||||
input=state.get("user_text"),
|
||||
):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
guardrail_active_transaction = (
|
||||
state.get("active_transaction")
|
||||
if isinstance(state.get("active_transaction"), dict)
|
||||
else {}
|
||||
)
|
||||
guardrail_transaction_status = str(
|
||||
guardrail_active_transaction.get("status")
|
||||
or state.get("transaction_status")
|
||||
or ""
|
||||
).strip().upper()
|
||||
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
|
||||
if state.get("confirmation_required") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "AWAITING_CONFIRMATION"
|
||||
elif state.get("missing_parameters") and pending_call.get("tool_name"):
|
||||
guardrail_transaction_status = "COLLECTING_PARAMETERS"
|
||||
for mcp_result in reversed(list(state.get("mcp_results") or [])):
|
||||
if not isinstance(mcp_result, dict):
|
||||
continue
|
||||
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
|
||||
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||
guardrail_transaction_status = mcp_status
|
||||
break
|
||||
await self.observer.emit_grl(
|
||||
"001",
|
||||
{
|
||||
@@ -265,6 +287,10 @@ class AgentWorkflow:
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"agent_profile": state.get("agent_profile") or {},
|
||||
"transaction_status": guardrail_transaction_status,
|
||||
"missing_parameters": list(state.get("missing_parameters") or []),
|
||||
"active_transaction": guardrail_active_transaction,
|
||||
"mcp_results": list(state.get("mcp_results") or []),
|
||||
},
|
||||
)
|
||||
for _decision in decisions:
|
||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
||||
} if decision.method == "continuity" else {},
|
||||
"pending_topics": (
|
||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
||||
if (decision.metadata or {}).get("multi_intent_plan") else []
|
||||
if (decision.metadata or {}).get("multi_intent_plan")
|
||||
else list(state.get("pending_topics") or [])
|
||||
),
|
||||
}
|
||||
|
||||
@@ -574,6 +601,7 @@ class AgentWorkflow:
|
||||
"output_guardrails_already_applied": False,
|
||||
"supervisor_action": "disabled",
|
||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||
**drained.state_patch,
|
||||
}
|
||||
|
||||
context = {
|
||||
@@ -653,6 +681,7 @@ class AgentWorkflow:
|
||||
"agent_responses": drained.agent_responses,
|
||||
"mcp_results": drained.mcp_results,
|
||||
"rag_results": drained.rag_results,
|
||||
**drained.state_patch,
|
||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
|
||||
}
|
||||
|
||||
@@ -40,6 +40,28 @@ def test_transactional_intent_is_primary_even_when_mentioned_second():
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
|
||||
|
||||
def test_multiword_keyword_accepts_intermediate_qualifier_without_yaml_variant():
|
||||
plan = _planner().plan("quero cancelar meu pedido e preciso da segunda via")
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
|
||||
|
||||
def test_first_of_multiple_transactions_is_primary_and_next_is_deferred():
|
||||
planner = MultiIntentPlanner([
|
||||
IntentDefinition(name="invoice", agent="billing", priority=30, keywords=["fatura"], mcp_tools=["consultar_fatura"]),
|
||||
IntentDefinition(name="return", agent="support", priority=20, keywords=["devolver pedido"], mcp_tools=["solicitar_devolucao"]),
|
||||
IntentDefinition(name="cancel", agent="orders", priority=10, keywords=["cancelar pedido"], mcp_tools=["cancelar_pedido"]),
|
||||
], transactional_tools={"solicitar_devolucao", "cancelar_pedido"})
|
||||
|
||||
plan = planner.plan(
|
||||
"quero consultar minha fatura. quero devolver pedido. quero cancelar pedido"
|
||||
)
|
||||
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["return", "invoice", "cancel"]
|
||||
assert [item.disposition for item in plan.operations] == ["execute", "execute", "defer"]
|
||||
|
||||
|
||||
def test_sentence_boundary_does_not_leak_previous_intent_into_knowledge_query():
|
||||
planner = MultiIntentPlanner([
|
||||
IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["fatura"]),
|
||||
|
||||
@@ -66,3 +66,52 @@ async def test_secondary_topic_does_not_inherit_primary_transaction_state():
|
||||
]
|
||||
assert result.rag_results[0]["metadata"]["provider"] == "kbdb"
|
||||
assert result.rag_results[0]["context"] == "Documento recuperado da base de conhecimento."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deferred_transaction_is_promoted_after_primary_completes():
|
||||
async def cancellation_agent(state):
|
||||
assert state["user_text"] == "quero cancelar meu pedido"
|
||||
return {
|
||||
"answer": "Você confirma o cancelamento do pedido PED-1001?",
|
||||
"transaction_status": "AWAITING_CONFIRMATION",
|
||||
"confirmation_required": True,
|
||||
"pending_tool_call": {
|
||||
"tool_name": "cancelar_pedido",
|
||||
"arguments": {"order_id": "PED-1001"},
|
||||
},
|
||||
"active_transaction": {
|
||||
"tool_name": "cancelar_pedido",
|
||||
"status": "AWAITING_CONFIRMATION",
|
||||
},
|
||||
}
|
||||
|
||||
state = {
|
||||
"answer": "A devolução foi registrada com sucesso.",
|
||||
"route": "support_agent",
|
||||
"active_agent": "support_agent",
|
||||
"intent": "retail_support_exchange_return",
|
||||
"transaction_status": "COMPLETED",
|
||||
"pending_topics": [{
|
||||
"operation_id": "op-3",
|
||||
"intent": "retail_order_cancel",
|
||||
"agent": "orders_agent",
|
||||
"domain": "retail",
|
||||
"tools": ["consultar_pedido", "cancelar_pedido"],
|
||||
"source_text": "quero cancelar meu pedido",
|
||||
"disposition": "defer",
|
||||
"status": "pending",
|
||||
}],
|
||||
}
|
||||
|
||||
result = await drain_pending_topics(
|
||||
state, state["answer"], {"orders_agent": cancellation_agent}
|
||||
)
|
||||
|
||||
assert result.pending_topics == []
|
||||
assert result.state_patch["route"] == "orders_agent"
|
||||
assert result.state_patch["intent"] == "retail_order_cancel"
|
||||
assert result.state_patch["transaction_status"] == "AWAITING_CONFIRMATION"
|
||||
assert result.state_patch["pending_tool_call"]["tool_name"] == "cancelar_pedido"
|
||||
assert result.agent_responses[-1]["primary"] is True
|
||||
assert result.agent_responses[-1]["agent"] == "orders_agent"
|
||||
|
||||
Reference in New Issue
Block a user