"""金融 NL2SQL Agent 的注册、声明和只读工具调用契约。""" from typing import Any, cast import pytest from app.core.contracts import ( AgentDefinition, AgentRequest, CoreResult, RequestContext, ResolvedAgentConfig, ToolCallRecord, ) from app.service.agent.base import BaseAgent from app.service.agent.factory import AgentFactory from app.service.agent.implementations.financial_nl2sql import FinancialNL2SQLAgent from app.service.tool_executor import ToolExecution class StubGovernance: async def resolve( self, definition: AgentDefinition, context: RequestContext ) -> ResolvedAgentConfig: del definition, context return ResolvedAgentConfig( config_version="test", prompt_version="test", model_endpoint="", allowed_tools_by_intent={ "financial_query": ("query_financial_data",), "general": ("query_financial_data",), }, ) async def recall(self, context: RequestContext) -> tuple[Any, ...]: del context return () async def review(self, result: Any, context: RequestContext, config: ResolvedAgentConfig, memories: tuple[Any, ...], *, agent_type: str = "") -> Any: del context, config, memories, agent_type return result class StubExecutor: def __init__(self) -> None: self.calls: list[tuple[str, str, dict[str, Any]]] = [] async def execute( self, *, name: str, arguments: dict[str, Any], intent: str, configured_tools: dict[str, tuple[str, ...]], context: RequestContext, ) -> ToolExecution: del context assert name in configured_tools[intent] self.calls.append((name, intent, arguments)) return ToolExecution( output={ "message": "查询成功", "data": {"total": 1, "rows": [{"nav": "1.250000"}]}, "sql": "SELECT n.nav AS nav FROM fin_nav n WHERE 1=1 LIMIT 50", }, record=ToolCallRecord(tool_name=name, status="succeeded"), ) @pytest.mark.asyncio async def test_financial_agent_is_registered_and_calls_read_only_tool() -> None: executor = StubExecutor() factory = AgentFactory(cast(Any, StubGovernance()), tool_executor=cast(Any, executor)) factory.register(FinancialNL2SQLAgent.definition, lambda _context: FinancialNL2SQLAgent(FinancialNL2SQLAgent.definition)) context = RequestContext(user_id="10001", trace_id="financial-contract", roles=("operator",), permissions=("agent:run", "financial:nl2sql:read"), data_scope="all") agent = factory.create("financial_nl2sql", context) assert isinstance(agent, BaseAgent) await agent.resolve_config(context) request = AgentRequest(agent_type="financial_nl2sql", message="查询15911最新净值", session_id="financial-session", idempotency_key="financial-key-123456") result = await agent.handle(request, context) assert result.text == "查询成功" assert result.data == {"total": 1, "rows": [{"nav": "1.250000"}]} assert result.sql == "SELECT n.nav AS nav FROM fin_nav n WHERE 1=1 LIMIT 50" assert executor.calls[0][0:2] == ("query_financial_data", "financial_query")