new feature: Multi-Intent

This commit is contained in:
2026-09-11 00:35:05 -03:00
parent a9bbad22a5
commit 6c81b21aaa
315 changed files with 855 additions and 35 deletions

View File

@@ -40,6 +40,20 @@ def test_transactional_intent_is_primary_even_when_mentioned_second():
assert [item.intent for item in plan.operations] == ["cancel", "invoice"]
def test_sentence_boundary_does_not_leak_previous_intent_into_knowledge_query():
planner = MultiIntentPlanner([
IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["fatura"]),
IntentDefinition(name="knowledge", agent="support", priority=30, keywords=["tarifação"]),
])
plan = planner.plan(
"quero ver minha fatura e o valor. depois gostaria de saber como funciona a tarifação do plano"
)
assert plan is not None
knowledge = next(item for item in plan.operations if item.intent == "knowledge")
assert knowledge.source_text == "depois gostaria de saber como funciona a tarifação do plano"
assert not knowledge.source_text.startswith("valor")
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

View File

@@ -12,6 +12,8 @@ async def test_secondary_topic_does_not_inherit_primary_transaction_state():
return {
"answer": "Aqui está a sua segunda via.",
"mcp_results": [{"tool_name": "consultar_faturas", "ok": True}],
"rag": {"provider": "kbdb", "attempted": True, "status": "executed", "document_count": 1},
"rag_context": "Documento recuperado da base de conhecimento.",
}
state = {
@@ -59,3 +61,5 @@ async def test_secondary_topic_does_not_inherit_primary_transaction_state():
{"tool_name": "consultar_vas"},
{"tool_name": "consultar_faturas", "ok": True},
]
assert result.rag_results[0]["metadata"]["provider"] == "kbdb"
assert result.rag_results[0]["context"] == "Documento recuperado da base de conhecimento."

View File

@@ -1,4 +1,5 @@
from types import SimpleNamespace
from contextlib import contextmanager
import pytest
@@ -115,3 +116,68 @@ def test_kbdb_does_not_fallback_to_framework_adb_credentials():
)
with pytest.raises(RuntimeError, match="KBDB_DB_USER"):
KbdbRagService(settings)
def test_kbdb_discovers_number_flags_and_clob_output(monkeypatch):
import sys
from agent_framework.rag.kbdb_service import KbdbRagService
captured = {}
class Var:
def __init__(self, value=None):
self.value = value
def getvalue(self):
return self.value
class Cursor:
def execute(self, statement):
captured["signature_query"] = statement
def fetchall(self):
names = [
("P_SEARCH_TYPE", "VARCHAR2"), ("P_QUERY", "VARCHAR2"),
("P_TOP_K", "NUMBER"), ("P_NODE_EXPANSION", "NUMBER"),
("P_NODE_MAX_RELATED", "NUMBER"), ("P_GRAPH_CROSS_REF", "NUMBER"),
("P_MAX_CROSS_REF_HOPS", "NUMBER"), ("P_DOCUMENT_TYPE", "VARCHAR2"),
("P_METADATA_JSON", "CLOB"), ("P_RESULT", "CLOB"),
]
return [
(name, index, "OUT" if index == 10 else "IN", data_type, None)
for index, (name, data_type) in enumerate(names, start=1)
]
def var(self, data_type, size=None):
captured["out_type"] = data_type
return Var('{"units": [], "seeds": []}')
def callproc(self, name, binds):
captured["procedure"] = name
captured["binds"] = binds
class Connection:
def cursor(self):
return Cursor()
class FakeOracleDb:
DB_TYPE_JSON = "JSON"
DB_TYPE_CLOB = "CLOB"
DB_TYPE_VARCHAR = "VARCHAR"
service = KbdbRagService(_settings())
@contextmanager
def connect():
yield Connection()
monkeypatch.setitem(sys.modules, "oracledb", FakeOracleDb)
monkeypatch.setattr(service, "_connect", connect)
result = service._search_sync("como funciona?", 5)
assert result == {"units": [], "seeds": []}
assert captured["procedure"] == "PKG_KB_SERVING.search_knowledge_base"
assert captured["binds"][3] == 1
assert captured["binds"][5] == 0
assert captured["out_type"] == "CLOB"

View File

@@ -13,7 +13,8 @@ def test_all_tuning_backend_graphs_support_pending_topics():
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
assert '"rag_results": drained.rag_results' in source, path
assert 'list(drained.mcp_results) + list(drained.rag_results)' in source, path
def test_all_tuning_backend_states_expose_pending_topics():
@@ -23,6 +24,7 @@ def test_all_tuning_backend_states_expose_pending_topics():
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
assert "rag_results: list[dict[str, Any]]" in source, path
def test_canonical_templates_delegate_drain_to_framework_helper():
@@ -36,7 +38,35 @@ def test_canonical_templates_delegate_drain_to_framework_helper():
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 '"rag_results": drained.rag_results' in graph_source
assert 'list(drained.mcp_results) + list(drained.rag_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
assert "rag_results: list[dict[str, Any]]" in state_source
def test_all_rag_agents_expose_metadata_and_context_as_state_evidence():
roots = [ROOT / "templates", ROOT / "Tuning-Performance"]
agents = sorted(
path
for root in roots
for path in root.glob("**/app/agents/*.py")
if '"rag": rag_metadata' in path.read_text(encoding="utf-8")
)
assert agents
for path in agents:
source = path.read_text(encoding="utf-8")
assert '"rag_context": rag_context' in source, path
assert '"rag_results": [{' in source, path
def test_templates_and_tuning_expose_rag_results_in_api_and_telemetry():
roots = [ROOT / "templates", ROOT / "Tuning-Performance"]
mains = sorted(path for root in roots for path in root.glob("**/app/main.py"))
graphs = sorted(path for root in roots for path in root.glob("**/app/workflows/agent_graph.py"))
assert mains and graphs
for path in mains:
assert '"rag_results": result.get("rag_results")' in path.read_text(encoding="utf-8"), path
for path in graphs:
assert '"rag_results": state.get("rag_results", [])' in path.read_text(encoding="utf-8"), path