import pytest from agent_service.llm.tool_validator import ToolCallValidator, AgentConfigError SAMPLE_TOOLS = [ {'name': 'get_invoices', 'parameters': { 'state': {'type': 'string', 'optional': True}, 'limit': {'type': 'integer', 'optional': True}, 'partner_id': {'type': 'integer'}, }}, {'name': 'send_reminder', 'parameters': { 'invoice_id': {'type': 'integer'}, 'message': {'type': 'string', 'optional': True}, }}, ] def test_validator_init(): v = ToolCallValidator(SAMPLE_TOOLS) assert 'get_invoices' in v._tool_map def test_raises_on_too_many_tools(): many_tools = [{'name': f'tool_{i}', 'parameters': {}} for i in range(9)] with pytest.raises(AgentConfigError, match='MAX_TOOLS_PER_AGENT'): ToolCallValidator(many_tools) def test_valid_tool_call(): v = ToolCallValidator(SAMPLE_TOOLS) result = v.validate({'name': 'get_invoices', 'arguments': {'partner_id': 42}}) assert result.name == 'get_invoices' assert result.arguments['partner_id'] == 42 def test_strips_hallucinated_params(): v = ToolCallValidator(SAMPLE_TOOLS) result = v.validate({'name': 'get_invoices', 'arguments': { 'partner_id': 1, 'nonexistent_param': 'bad', }}) assert 'nonexistent_param' not in result.arguments def test_missing_required_param_raises(): v = ToolCallValidator(SAMPLE_TOOLS) with pytest.raises(ValueError, match='partner_id'): v.validate({'name': 'get_invoices', 'arguments': {}}) def test_type_coercion_int(): v = ToolCallValidator(SAMPLE_TOOLS) result = v.validate({'name': 'get_invoices', 'arguments': {'partner_id': '42'}}) assert result.arguments['partner_id'] == 42 def test_unknown_tool_raises(): v = ToolCallValidator(SAMPLE_TOOLS) with pytest.raises(ValueError, match='Unknown tool'): v.validate({'name': 'nonexistent_tool', 'arguments': {}}) def test_parse_or_fallback_returns_none_on_bad_json(): v = ToolCallValidator(SAMPLE_TOOLS) result = v.parse_or_fallback('not json at all', context='test') assert result is None def test_parse_or_fallback_valid_json(): v = ToolCallValidator(SAMPLE_TOOLS) import json raw = json.dumps({'name': 'send_reminder', 'arguments': {'invoice_id': 5}}) result = v.parse_or_fallback(raw, context='test') assert result is not None assert result.name == 'send_reminder'