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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -264,6 +286,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -665,6 +693,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -266,6 +288,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -667,6 +695,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"
|
code = "VLOOP"
|
||||||
stage = "input"
|
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:
|
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()
|
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
|
repeated = history.count(normalized) >= 2 if normalized else False
|
||||||
return RailDecision(
|
return RailDecision(
|
||||||
code=self.code,
|
code=self.code,
|
||||||
|
|||||||
@@ -358,30 +358,38 @@ class LoopRail(Guardrail):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _transaction_status(ctx: dict[str, Any]) -> str:
|
def _transaction_status(ctx: dict[str, Any]) -> str:
|
||||||
status = str(ctx.get("transaction_status") or "").strip().upper()
|
nonterminal = {
|
||||||
if status:
|
"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION", "EXECUTING",
|
||||||
return status
|
"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")
|
active_transaction = ctx.get("active_transaction")
|
||||||
if isinstance(active_transaction, dict):
|
if isinstance(active_transaction, dict):
|
||||||
return str(active_transaction.get("status") or "").strip().upper()
|
active_status = str(active_transaction.get("status") or "").strip().upper()
|
||||||
return ""
|
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:
|
async def evaluate(self, text: str, context: dict[str, Any]) -> RailDecision:
|
||||||
ctx = _ctx(context)
|
ctx = _ctx(context)
|
||||||
transaction_status = self._transaction_status(ctx)
|
transaction_status = self._transaction_status(ctx)
|
||||||
|
|
||||||
# A short confirmation (for example ``sim``/``não``) may legitimately
|
# Parameter values and short confirmations may legitimately repeat in
|
||||||
# appear several times in the same session because each transaction has
|
# the same session. VLOOP must not consume input owned by the active
|
||||||
# its own confirmation boundary. VLOOP protects against conversational
|
# transaction contract; extraction/confirmation remains responsible
|
||||||
# repetition; it must not consume the input that belongs to an active
|
# for deciding whether the value is valid.
|
||||||
# transaction awaiting confirmation. The transaction runtime/classifier
|
if transaction_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
|
||||||
# remains responsible for deciding whether the utterance is actually a
|
|
||||||
# valid confirm/reject response.
|
|
||||||
if transaction_status == "AWAITING_CONFIRMATION":
|
|
||||||
return RailDecision(
|
return RailDecision(
|
||||||
code=self.code,
|
code=self.code,
|
||||||
allowed=True,
|
allowed=True,
|
||||||
reason="continuidade_transacional:AWAITING_CONFIRMATION",
|
reason=f"continuidade_transacional:{transaction_status}",
|
||||||
sanitized_text=text,
|
sanitized_text=text,
|
||||||
metadata={
|
metadata={
|
||||||
"history_window": len(list(ctx.get("history_texts") or [])[-6:]),
|
"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:
|
def _intent_for_clause(self, clause: str) -> IntentDefinition | None:
|
||||||
normalized = self._normalize(clause)
|
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]] = []
|
matches: list[tuple[int, int, IntentDefinition]] = []
|
||||||
for intent in self.intents:
|
for intent in self.intents:
|
||||||
for keyword in intent.keywords:
|
for keyword in intent.keywords:
|
||||||
token = self._normalize(keyword)
|
token = self._normalize(keyword)
|
||||||
keyword_tokens = re.findall(r"[\w]+", token, flags=re.UNICODE)
|
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(
|
matched = bool(
|
||||||
keyword_tokens
|
keyword_tokens
|
||||||
and (
|
and (
|
||||||
(len(keyword_tokens) == 1 and keyword_tokens[0] in clause_tokens)
|
(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:
|
if matched:
|
||||||
@@ -146,7 +159,7 @@ class MultiIntentPlanner:
|
|||||||
if len(operations) < 2:
|
if len(operations) < 2:
|
||||||
return None
|
return None
|
||||||
transactional = [item for item in operations if self._is_transactional(item)]
|
transactional = [item for item in operations if self._is_transactional(item)]
|
||||||
if len(transactional) == 1:
|
if transactional:
|
||||||
primary_operation = transactional[0]
|
primary_operation = transactional[0]
|
||||||
operations.sort(key=lambda item: item is not primary_operation)
|
operations.sort(key=lambda item: item is not primary_operation)
|
||||||
for operation in operations[1:]:
|
for operation in operations[1:]:
|
||||||
|
|||||||
@@ -16,6 +16,29 @@ class PendingTopicDrain:
|
|||||||
handled_topics: list[dict[str, Any]]
|
handled_topics: list[dict[str, Any]]
|
||||||
mcp_results: list[dict[str, Any]]
|
mcp_results: list[dict[str, Any]]
|
||||||
rag_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(
|
async def drain_pending_topics(
|
||||||
@@ -34,6 +57,8 @@ async def drain_pending_topics(
|
|||||||
combined_mcp_results = list(state.get("mcp_results") or [])
|
combined_mcp_results = list(state.get("mcp_results") or [])
|
||||||
combined_rag_results = list(state.get("rag_results") or [])
|
combined_rag_results = list(state.get("rag_results") or [])
|
||||||
secondary_answers: list[str] = []
|
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 [])
|
agent_responses = list(state.get("agent_responses") or [])
|
||||||
if not agent_responses and str(candidate or "").strip():
|
if not agent_responses and str(candidate or "").strip():
|
||||||
primary_agent = str(
|
primary_agent = str(
|
||||||
@@ -53,9 +78,15 @@ async def drain_pending_topics(
|
|||||||
})
|
})
|
||||||
for topic in topics:
|
for topic in topics:
|
||||||
disposition = topic.get("disposition")
|
disposition = topic.get("disposition")
|
||||||
|
promoted = False
|
||||||
if disposition == "defer":
|
if disposition == "defer":
|
||||||
|
if not may_promote_deferred:
|
||||||
remaining.append(topic)
|
remaining.append(topic)
|
||||||
continue
|
continue
|
||||||
|
topic = {**topic, "disposition": "execute", "status": "pending"}
|
||||||
|
disposition = "execute"
|
||||||
|
promoted = True
|
||||||
|
may_promote_deferred = False
|
||||||
if disposition != "execute":
|
if disposition != "execute":
|
||||||
handled.append({**topic, "status": "completed"})
|
handled.append({**topic, "status": "completed"})
|
||||||
continue
|
continue
|
||||||
@@ -123,14 +154,42 @@ async def drain_pending_topics(
|
|||||||
if rag_evidence not in combined_rag_results:
|
if rag_evidence not in combined_rag_results:
|
||||||
combined_rag_results.append(rag_evidence)
|
combined_rag_results.append(rag_evidence)
|
||||||
secondary_answers.append(answer)
|
secondary_answers.append(answer)
|
||||||
|
if promoted:
|
||||||
|
agent_responses = [{**item, "primary": False} for item in agent_responses]
|
||||||
agent_responses.append({
|
agent_responses.append({
|
||||||
"operation_id": topic.get("operation_id"),
|
"operation_id": topic.get("operation_id"),
|
||||||
"agent": topic.get("agent"),
|
"agent": topic.get("agent"),
|
||||||
"intent": topic.get("intent"),
|
"intent": topic.get("intent"),
|
||||||
"answer": answer,
|
"answer": answer,
|
||||||
"primary": False,
|
"primary": promoted,
|
||||||
"status": "completed",
|
"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"})
|
handled.append({**topic, "status": "completed"})
|
||||||
|
|
||||||
secondary_answers.extend(MultiIntentPlanner.public_messages(plan))
|
secondary_answers.extend(MultiIntentPlanner.public_messages(plan))
|
||||||
@@ -148,4 +207,5 @@ async def drain_pending_topics(
|
|||||||
handled_topics=handled,
|
handled_topics=handled,
|
||||||
mcp_results=combined_mcp_results,
|
mcp_results=combined_mcp_results,
|
||||||
rag_results=combined_rag_results,
|
rag_results=combined_rag_results,
|
||||||
|
state_patch=state_patch,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -249,6 +249,28 @@ class AgentWorkflow:
|
|||||||
input=state.get("user_text"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -267,6 +289,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -412,7 +438,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -655,6 +683,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"),
|
input=state.get("user_text"),
|
||||||
):
|
):
|
||||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
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(
|
await self.observer.emit_grl(
|
||||||
"001",
|
"001",
|
||||||
{
|
{
|
||||||
@@ -265,6 +287,10 @@ class AgentWorkflow:
|
|||||||
"tenant_id": state.get("tenant_id"),
|
"tenant_id": state.get("tenant_id"),
|
||||||
"agent_id": state.get("agent_id"),
|
"agent_id": state.get("agent_id"),
|
||||||
"agent_profile": state.get("agent_profile") or {},
|
"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:
|
for _decision in decisions:
|
||||||
@@ -410,7 +436,8 @@ class AgentWorkflow:
|
|||||||
} if decision.method == "continuity" else {},
|
} if decision.method == "continuity" else {},
|
||||||
"pending_topics": (
|
"pending_topics": (
|
||||||
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
|
(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,
|
"output_guardrails_already_applied": False,
|
||||||
"supervisor_action": "disabled",
|
"supervisor_action": "disabled",
|
||||||
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
|
||||||
|
**drained.state_patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -653,6 +681,7 @@ class AgentWorkflow:
|
|||||||
"agent_responses": drained.agent_responses,
|
"agent_responses": drained.agent_responses,
|
||||||
"mcp_results": drained.mcp_results,
|
"mcp_results": drained.mcp_results,
|
||||||
"rag_results": drained.rag_results,
|
"rag_results": drained.rag_results,
|
||||||
|
**drained.state_patch,
|
||||||
"guardrail_decisions": state.get("guardrail_decisions", [])
|
"guardrail_decisions": state.get("guardrail_decisions", [])
|
||||||
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_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"]
|
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():
|
def test_sentence_boundary_does_not_leak_previous_intent_into_knowledge_query():
|
||||||
planner = MultiIntentPlanner([
|
planner = MultiIntentPlanner([
|
||||||
IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["fatura"]),
|
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]["metadata"]["provider"] == "kbdb"
|
||||||
assert result.rag_results[0]["context"] == "Documento recuperado da base de conhecimento."
|
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