adjustments: manual
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import MethodType, SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FRAMEWORK_SRC = ROOT / "libs" / "agent_framework" / "src"
|
||||
TEMPLATE_ROOT = ROOT / "templates" / "agent_template_backend"
|
||||
for path in (FRAMEWORK_SRC, TEMPLATE_ROOT):
|
||||
if str(path) not in sys.path:
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
|
||||
EXAMPLES = (
|
||||
("README.md", "**COPIAR — arquivo completo canônico:**"),
|
||||
("README_en.md", "**COPY — complete canonical file:**"),
|
||||
)
|
||||
|
||||
|
||||
class FakeLLM:
|
||||
def __init__(self):
|
||||
self.calls = 0
|
||||
|
||||
async def ainvoke(self, messages, **kwargs):
|
||||
self.calls += 1
|
||||
assert messages
|
||||
assert kwargs["profile_name"] == "FinanceiroAgent"
|
||||
return "resposta documentada"
|
||||
|
||||
|
||||
class CaptureObserver:
|
||||
def __init__(self):
|
||||
self.events: list[tuple[str, dict, str | None]] = []
|
||||
|
||||
async def emit_ic(self, code, payload, component=None):
|
||||
self.events.append((code, payload, component))
|
||||
|
||||
|
||||
def extract_documented_class(readme_name: str, marker: str):
|
||||
text = (ROOT / readme_name).read_text(encoding="utf-8")
|
||||
marked = text[text.index(marker) :]
|
||||
match = re.search(r"```python\n(.*?)\n```", marked, re.DOTALL)
|
||||
assert match, f"Bloco Python canônico ausente em {readme_name}"
|
||||
source = match.group(1)
|
||||
ast.parse(source, filename=readme_name)
|
||||
namespace: dict[str, object] = {}
|
||||
exec(compile(source, readme_name, "exec"), namespace)
|
||||
return namespace["FinanceiroAgent"], source
|
||||
|
||||
|
||||
def make_agent(agent_class, *, observer=None):
|
||||
llm = FakeLLM()
|
||||
agent = agent_class(
|
||||
llm,
|
||||
settings=SimpleNamespace(CACHE_TTL_SECONDS=0, RAG_PROVIDER="standard"),
|
||||
observer=observer,
|
||||
)
|
||||
return agent, llm
|
||||
|
||||
|
||||
def base_state():
|
||||
return {
|
||||
"user_text": "Qual é a situação do meu pagamento?",
|
||||
"sanitized_input": "Qual é a situação do meu pagamento?",
|
||||
"session_id": "readme-test",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "financeiro_agent",
|
||||
"route": "financeiro_agent",
|
||||
"intent": "financeiro_pagamentos",
|
||||
"mcp_tools": [],
|
||||
"context": {},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("readme_name", "marker"), EXAMPLES)
|
||||
def test_documented_financeiro_agent_compiles_and_uses_current_contract(readme_name, marker):
|
||||
_, source = extract_documented_class(readme_name, marker)
|
||||
required = {
|
||||
"transaction_clarification_message",
|
||||
"transaction_confirmation_message",
|
||||
"build_direct_mcp_answer",
|
||||
"prepare_memory_context",
|
||||
"build_messages",
|
||||
"transaction_state_patch",
|
||||
"rag_results",
|
||||
"IC.FINANCEIRO_RAG_CONTEXT_EVALUATED",
|
||||
}
|
||||
assert all(item in source for item in required)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("readme_name", "marker"), EXAMPLES)
|
||||
async def test_documented_financeiro_agent_runs_normal_composition(readme_name, marker):
|
||||
agent_class, _ = extract_documented_class(readme_name, marker)
|
||||
observer = CaptureObserver()
|
||||
agent, llm = make_agent(agent_class, observer=observer)
|
||||
|
||||
result = await agent.run(base_state())
|
||||
|
||||
assert result["answer"] == "[FinanceiroAgent] resposta documentada"
|
||||
assert result["next_state"] == "FINANCEIRO_ACTIVE"
|
||||
assert result["mcp_results"] == []
|
||||
assert result["rag"]["status"] == "no_service"
|
||||
assert result["rag_results"][0]["agent"] == "financeiro_agent"
|
||||
assert llm.calls == 1
|
||||
codes = [event[0] for event in observer.events]
|
||||
assert "IC.FINANCEIRO_AGENT_STARTED" in codes
|
||||
assert "IC.FINANCEIRO_RAG_CONTEXT_EVALUATED" in codes
|
||||
assert "IC.FINANCEIRO_AGENT_COMPLETED" in codes
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("readme_name", "marker"), EXAMPLES)
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "message", "expected_state"),
|
||||
(
|
||||
("transaction_clarification_message", "Informe o contrato.", "COLLECTING_PARAMETERS"),
|
||||
("transaction_confirmation_message", "Confirma a operação?", "AWAITING_CONFIRMATION"),
|
||||
),
|
||||
)
|
||||
async def test_documented_financeiro_agent_returns_before_llm_for_transaction_prompts(
|
||||
readme_name, marker, method_name, message, expected_state
|
||||
):
|
||||
agent_class, _ = extract_documented_class(readme_name, marker)
|
||||
agent, llm = make_agent(agent_class)
|
||||
state = base_state()
|
||||
state["next_state"] = expected_state
|
||||
|
||||
setattr(agent, method_name, MethodType(lambda self, state: message, agent))
|
||||
result = await agent.run(state)
|
||||
|
||||
assert result["answer"] == f"[FinanceiroAgent] {message}"
|
||||
assert result["next_state"] == expected_state
|
||||
assert llm.calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("readme_name", "marker"), EXAMPLES)
|
||||
async def test_documented_financeiro_agent_honors_direct_mcp_response(readme_name, marker):
|
||||
agent_class, _ = extract_documented_class(readme_name, marker)
|
||||
agent, llm = make_agent(agent_class)
|
||||
|
||||
async def collect(self, state):
|
||||
return [{"ok": True, "tool_name": "consultar_pagamento", "result": {"status": "PAGO"}}]
|
||||
|
||||
agent._collect_tool_context = MethodType(collect, agent)
|
||||
agent.build_direct_mcp_answer = MethodType(
|
||||
lambda self, state, results, agent_label: "[FinanceiroAgent] Pagamento confirmado.", agent
|
||||
)
|
||||
result = await agent.run(base_state())
|
||||
|
||||
assert result["answer"] == "[FinanceiroAgent] Pagamento confirmado."
|
||||
assert result["rag"]["reason"] == "direct_mcp_answer"
|
||||
assert llm.calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("readme_name", "marker"), EXAMPLES)
|
||||
async def test_documented_financeiro_agent_emits_complete_rag_evaluation(readme_name, marker):
|
||||
agent_class, _ = extract_documented_class(readme_name, marker)
|
||||
observer = CaptureObserver()
|
||||
agent, llm = make_agent(agent_class, observer=observer)
|
||||
|
||||
async def retrieve(self, state):
|
||||
return "contexto financeiro", {
|
||||
"provider": "test",
|
||||
"status": "executed",
|
||||
"attempted": True,
|
||||
"enabled": True,
|
||||
"document_count": 1,
|
||||
"graph_neighbors": 0,
|
||||
"latency_ms": 1,
|
||||
"query": state["sanitized_input"],
|
||||
"namespace": "financeiro_agent",
|
||||
}
|
||||
|
||||
agent._retrieve_rag_context = MethodType(retrieve, agent)
|
||||
result = await agent.run(base_state())
|
||||
|
||||
codes = [event[0] for event in observer.events]
|
||||
assert "IC.FINANCEIRO_RAG_CONTEXT_EVALUATED" in codes
|
||||
assert "IC.FINANCEIRO_RAG_CONTEXT_RETRIEVED" in codes
|
||||
assert result["rag_context"] == "contexto financeiro"
|
||||
assert llm.calls == 1
|
||||
Reference in New Issue
Block a user