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:
66
tests/unit/test_multi_intent_planner.py
Normal file
66
tests/unit/test_multi_intent_planner.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from agent_framework.routing.models import IntentDefinition
|
||||
from agent_framework.routing.multi_intent import MultiIntentPlanner
|
||||
|
||||
|
||||
def _planner():
|
||||
return MultiIntentPlanner([
|
||||
IntentDefinition(name="cancel", agent="orders", priority=10, keywords=["cancelar pedido"], mcp_tools=["cancelar_pedido"]),
|
||||
IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["segunda via"], mcp_tools=["consultar_fatura"]),
|
||||
], {
|
||||
"enabled": True,
|
||||
}, transactional_tools={"cancelar_pedido"})
|
||||
|
||||
|
||||
def test_builds_validated_plan_and_keeps_primary_first():
|
||||
plan = _planner().plan("quero cancelar pedido e preciso da segunda via")
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
assert plan.operations[1].disposition == "execute"
|
||||
assert plan.operations[1].tools == ["consultar_fatura"]
|
||||
|
||||
|
||||
def test_confirmation_with_secondary_does_not_lose_confirmation():
|
||||
plan = _planner().plan_confirmation_secondary(
|
||||
"sim e preciso da segunda via", primary_intent="cancel", primary_agent="orders"
|
||||
)
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
assert MultiIntentPlanner.public_messages(plan) == []
|
||||
|
||||
|
||||
def test_transactional_intent_is_primary_even_when_mentioned_second():
|
||||
plan = _planner().plan("preciso da segunda via e quero cancelar pedido")
|
||||
assert plan is not None
|
||||
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
|
||||
|
||||
|
||||
def test_does_not_plan_single_intent_or_invent_unknown_intent():
|
||||
assert _planner().plan("quero cancelar pedido") is None
|
||||
assert _planner().plan("quero cancelar pedido e saber o clima") is None
|
||||
|
||||
|
||||
def test_known_primary_with_actionable_unknown_creates_off_context_topic():
|
||||
plan = _planner().plan("quero cancelar pedido e alterar meu CNPJ")
|
||||
assert plan is not None
|
||||
assert plan.operations[1].intent == "off_context"
|
||||
assert plan.operations[1].disposition == "unsupported"
|
||||
|
||||
|
||||
def test_confirmation_with_unknown_secondary_keeps_confirmation():
|
||||
plan = _planner().plan_confirmation_secondary(
|
||||
"sim e altere meu CNPJ", primary_intent="cancel", primary_agent="orders"
|
||||
)
|
||||
assert plan is not None
|
||||
assert plan.operations[0].intent == "cancel"
|
||||
assert plan.operations[1].intent == "off_context"
|
||||
|
||||
|
||||
def test_single_word_keyword_does_not_match_a_longer_word_prefix():
|
||||
planner = MultiIntentPlanner([
|
||||
IntentDefinition(name="cancel", agent="orders", priority=10, keywords=["cancela"]),
|
||||
IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["segunda via"]),
|
||||
])
|
||||
plan = planner.plan("sim, pode cancelar e preciso da segunda via")
|
||||
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"]
|
||||
53
tests/unit/test_pending_topics_isolation.py
Normal file
53
tests/unit/test_pending_topics_isolation.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import pytest
|
||||
|
||||
from agent_framework.routing.pending_topics import drain_pending_topics
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_secondary_topic_does_not_inherit_primary_transaction_state():
|
||||
captured = {}
|
||||
|
||||
async def billing_agent(state):
|
||||
captured.update(state)
|
||||
return {"answer": "Aqui está a sua segunda via."}
|
||||
|
||||
state = {
|
||||
"answer": "Você confirma o cancelamento do Tamboro?",
|
||||
"sanitized_input": "quero cancelar Tamboro e quero minha segunda via",
|
||||
"confirmation_required": True,
|
||||
"active_transaction": {"tool_name": "cancelar_vas_avulso"},
|
||||
"transaction_status": "AWAITING_CONFIRMATION",
|
||||
"selected_tool_call": {"tool_name": "cancelar_vas_avulso"},
|
||||
"pending_tool_call": {"tool_name": "cancelar_vas_avulso"},
|
||||
"pending_domain_workflow": {"workflow_name": "cancelamento_vas_avulso"},
|
||||
"missing_parameters": ["subject"],
|
||||
"next_state": "WAITING_CONTESTACAO_CONFIRMATION",
|
||||
"mcp_results": [{"tool_name": "consultar_vas"}],
|
||||
"pending_topics": [{
|
||||
"operation_id": "op-2",
|
||||
"intent": "contas_invoice_query",
|
||||
"agent": "faturas_agent",
|
||||
"domain": "telecom_contas",
|
||||
"tools": ["consultar_faturas"],
|
||||
"source_text": "quero minha segunda via",
|
||||
"disposition": "execute",
|
||||
"status": "pending",
|
||||
}],
|
||||
}
|
||||
|
||||
result = await drain_pending_topics(
|
||||
state, state["answer"], {"faturas_agent": billing_agent}
|
||||
)
|
||||
|
||||
assert captured["active_transaction"] is None
|
||||
assert captured["selected_tool_call"] == {}
|
||||
assert captured["pending_tool_call"] == {}
|
||||
assert captured["pending_domain_workflow"] is None
|
||||
assert captured["next_state"] is None
|
||||
assert captured["sanitized_input"] == "quero minha segunda via"
|
||||
assert captured["mcp_results"] == []
|
||||
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.pending_topics == []
|
||||
39
tests/unit/test_readme_feature_catalog.py
Normal file
39
tests/unit/test_readme_feature_catalog.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
from urllib.parse import unquote
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _local_links(path: Path) -> list[str]:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
return [
|
||||
target for target in re.findall(r"\[[^]]*\]\(([^)]+)\)", text)
|
||||
if not target.startswith(("http://", "https://", "#", "mailto:"))
|
||||
]
|
||||
|
||||
|
||||
def test_main_readmes_have_no_broken_local_links():
|
||||
for name in ("README.md", "README_en.md"):
|
||||
readme = ROOT / name
|
||||
for target in _local_links(readme):
|
||||
relative = unquote(target.split("#", 1)[0])
|
||||
assert not relative or (readme.parent / relative).exists(), (name, target)
|
||||
|
||||
|
||||
def test_every_feature_guide_is_listed_in_corresponding_main_readme():
|
||||
pairs = (("pt-BR", "README.md"), ("en", "README_en.md"))
|
||||
for language, readme_name in pairs:
|
||||
readme = (ROOT / readme_name).read_text(encoding="utf-8")
|
||||
guides = sorted((ROOT / "docs" / "features" / language).glob("[0-9][0-9]_*.md"))
|
||||
assert guides
|
||||
for guide in guides:
|
||||
expected = f"docs/features/{language}/{guide.name}"
|
||||
assert expected in readme, (readme_name, expected)
|
||||
|
||||
|
||||
def test_portuguese_and_english_feature_catalogs_are_symmetric():
|
||||
pt = {path.name for path in (ROOT / "docs" / "features" / "pt-BR").glob("*.md")}
|
||||
en = {path.name for path in (ROOT / "docs" / "features" / "en").glob("*.md")}
|
||||
assert pt == en
|
||||
38
tests/unit/test_tuning_pending_topics_sync.py
Normal file
38
tests/unit/test_tuning_pending_topics_sync.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_all_tuning_backend_graphs_support_pending_topics():
|
||||
graphs = sorted((ROOT / "Tuning-Performance").glob("**/app/workflows/agent_graph.py"))
|
||||
assert graphs
|
||||
for path in graphs:
|
||||
source = path.read_text(encoding="utf-8")
|
||||
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
|
||||
|
||||
|
||||
def test_all_tuning_backend_states_expose_pending_topics():
|
||||
states = sorted((ROOT / "Tuning-Performance").glob("**/app/state.py"))
|
||||
assert states
|
||||
for path in states:
|
||||
source = path.read_text(encoding="utf-8")
|
||||
assert "pending_topics: list[dict[str, Any]]" in source, path
|
||||
assert "handled_topics: list[dict[str, Any]]" in source, path
|
||||
|
||||
|
||||
def test_canonical_templates_delegate_drain_to_framework_helper():
|
||||
for template in ("agent_template_backend", "agent_template_backend_day_zero"):
|
||||
graph = ROOT / "templates" / template / "app" / "workflows" / "agent_graph.py"
|
||||
state = ROOT / "templates" / template / "app" / "state.py"
|
||||
graph_source = graph.read_text(encoding="utf-8")
|
||||
state_source = state.read_text(encoding="utf-8")
|
||||
assert "from agent_framework.routing.pending_topics import drain_pending_topics" in graph_source
|
||||
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 "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