mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-06 03:14:12 +00:00
new feature: Multi-Intent
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from agent_framework.routing.enterprise_router import EnterpriseRouter
|
||||
from agent_framework.routing.models import IntentDefinition
|
||||
from agent_framework.routing.multi_intent import MultiIntentPlanner
|
||||
|
||||
@@ -64,3 +70,114 @@ def test_single_word_keyword_does_not_match_a_longer_word_prefix():
|
||||
assert plan is not None
|
||||
assert all(item.intent != "cancel" for item in plan.operations)
|
||||
assert [item.intent for item in plan.operations] == ["invoice", "off_context"]
|
||||
|
||||
|
||||
def test_semantic_fallback_hydrates_ownership_from_configured_intents():
|
||||
plan = _planner().plan_from_semantic({
|
||||
"confidence": 0.92,
|
||||
"operations": [
|
||||
{"intent": "invoice", "source_text": "me encaminhe o boleto"},
|
||||
{"intent": "cancel", "source_text": "não quero mais o pedido"},
|
||||
],
|
||||
})
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
assert plan.operations[0].agent == "orders"
|
||||
assert plan.operations[0].tools == ["cancelar_pedido"]
|
||||
|
||||
|
||||
def test_semantic_fallback_rejects_invented_intent_and_low_confidence():
|
||||
assert _planner().plan_from_semantic({
|
||||
"confidence": 0.99,
|
||||
"operations": [
|
||||
{"intent": "cancel", "source_text": "não quero mais o pedido"},
|
||||
{"intent": "invented", "source_text": "faça outra coisa"},
|
||||
],
|
||||
}) is None
|
||||
assert _planner().plan_from_semantic({
|
||||
"confidence": 0.2,
|
||||
"operations": [
|
||||
{"intent": "cancel", "source_text": "não quero mais o pedido"},
|
||||
{"intent": "invoice", "source_text": "me encaminhe o boleto"},
|
||||
],
|
||||
}) is None
|
||||
|
||||
|
||||
def test_semantic_confirmation_preserves_primary_transaction():
|
||||
plan = _planner().plan_confirmation_from_semantic(
|
||||
{
|
||||
"confidence": 0.9,
|
||||
"operations": [
|
||||
{"intent": "invoice", "source_text": "também gostaria do boleto"},
|
||||
],
|
||||
},
|
||||
primary_intent="cancel",
|
||||
primary_agent="orders",
|
||||
)
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_router_uses_semantic_multi_intent_only_after_deterministic_miss(tmp_path):
|
||||
routing = tmp_path / "routing.yaml"
|
||||
routing.write_text("""
|
||||
router:
|
||||
fallback_agent: billing
|
||||
intents:
|
||||
- name: cancel
|
||||
agent: orders
|
||||
description: Encerrar uma encomenda existente
|
||||
keywords: [cancelar pedido]
|
||||
mcp_tools: [cancelar_pedido]
|
||||
- name: invoice
|
||||
agent: billing
|
||||
description: Obter documento de cobrança ou boleto
|
||||
keywords: [segunda via]
|
||||
mcp_tools: [consultar_fatura]
|
||||
""", encoding="utf-8")
|
||||
policies = tmp_path / "tool_policies.yaml"
|
||||
policies.write_text("""
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
""", encoding="utf-8")
|
||||
|
||||
class SemanticMultiIntentLLM:
|
||||
calls = 0
|
||||
|
||||
async def ainvoke(self, messages, **kwargs):
|
||||
self.calls += 1
|
||||
assert kwargs["generation_name"] == "llm.router.multi_intent"
|
||||
return json.dumps({
|
||||
"confidence": 0.94,
|
||||
"operations": [
|
||||
{"intent": "cancel", "source_text": "encerrar minha encomenda"},
|
||||
{"intent": "invoice", "source_text": "encaminhe o boleto"},
|
||||
],
|
||||
})
|
||||
|
||||
llm = SemanticMultiIntentLLM()
|
||||
router = EnterpriseRouter(SimpleNamespace(
|
||||
ROUTING_CONFIG_PATH=str(routing),
|
||||
TOOL_POLICIES_PATH=str(policies),
|
||||
ENABLE_LLM_ROUTER=True,
|
||||
ENABLE_ROUTE_STICKINESS=False,
|
||||
), llm=llm)
|
||||
decision = await router.route({
|
||||
"user_text": "quero encerrar minha encomenda e encaminhe o boleto",
|
||||
"context": {},
|
||||
})
|
||||
|
||||
assert llm.calls == 1
|
||||
assert decision.method == "llm"
|
||||
assert decision.intent == "cancel"
|
||||
assert decision.agent == "orders"
|
||||
assert decision.metadata["multi_intent_classifier"] == "llm"
|
||||
assert [
|
||||
item["intent"]
|
||||
for item in decision.metadata["multi_intent_plan"]["operations"]
|
||||
] == ["cancel", "invoice"]
|
||||
|
||||
@@ -9,7 +9,10 @@ async def test_secondary_topic_does_not_inherit_primary_transaction_state():
|
||||
|
||||
async def billing_agent(state):
|
||||
captured.update(state)
|
||||
return {"answer": "Aqui está a sua segunda via."}
|
||||
return {
|
||||
"answer": "Aqui está a sua segunda via.",
|
||||
"mcp_results": [{"tool_name": "consultar_faturas", "ok": True}],
|
||||
}
|
||||
|
||||
state = {
|
||||
"answer": "Você confirma o cancelamento do Tamboro?",
|
||||
@@ -49,5 +52,10 @@ async def test_secondary_topic_does_not_inherit_primary_transaction_state():
|
||||
assert captured["mcp_tools"] == ["consultar_faturas"]
|
||||
assert captured["available_mcp_tools"] == ["consultar_faturas"]
|
||||
assert captured["domain"] == "telecom_contas"
|
||||
assert result.answer.startswith("Aqui está a sua segunda via.")
|
||||
assert result.answer.startswith("Você confirma o cancelamento")
|
||||
assert result.answer.endswith("Aqui está a sua segunda via.")
|
||||
assert result.pending_topics == []
|
||||
assert result.mcp_results == [
|
||||
{"tool_name": "consultar_vas"},
|
||||
{"tool_name": "consultar_faturas", "ok": True},
|
||||
]
|
||||
|
||||
@@ -12,6 +12,8 @@ def test_all_tuning_backend_graphs_support_pending_topics():
|
||||
assert "from agent_framework.routing.pending_topics import drain_pending_topics" in source, path
|
||||
assert '"pending_topics": drained.pending_topics' in source, path
|
||||
assert '"handled_topics": drained.handled_topics' in source, path
|
||||
assert '"mcp_results": drained.mcp_results' in source, path
|
||||
assert '"evidence": drained.mcp_results' in source, path
|
||||
|
||||
|
||||
def test_all_tuning_backend_states_expose_pending_topics():
|
||||
@@ -33,6 +35,8 @@ def test_canonical_templates_delegate_drain_to_framework_helper():
|
||||
assert "drained = await drain_pending_topics(" in graph_source
|
||||
assert '"pending_topics": drained.pending_topics' in graph_source
|
||||
assert '"handled_topics": drained.handled_topics' in graph_source
|
||||
assert '"mcp_results": drained.mcp_results' in graph_source
|
||||
assert '"evidence": drained.mcp_results' in graph_source
|
||||
assert "MultiIntentPlanner.public_messages" not in graph_source
|
||||
assert "pending_topics: list[dict[str, Any]]" in state_source
|
||||
assert "handled_topics: list[dict[str, Any]]" in state_source
|
||||
|
||||
Reference in New Issue
Block a user