mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-05 19:04:13 +00:00
191 lines
6.4 KiB
Python
191 lines
6.4 KiB
Python
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
|