|
|
"""Test cases for Tool schema methods.""" |
|
|
|
|
|
from typing import Any |
|
|
|
|
|
import pytest |
|
|
|
|
|
from mini_agent.tools.base import Tool, ToolResult |
|
|
|
|
|
|
|
|
class MockWeatherTool(Tool): |
|
|
"""Mock weather tool for testing.""" |
|
|
|
|
|
@property |
|
|
def name(self) -> str: |
|
|
return "get_weather" |
|
|
|
|
|
@property |
|
|
def description(self) -> str: |
|
|
return "Get weather information" |
|
|
|
|
|
@property |
|
|
def parameters(self) -> dict[str, Any]: |
|
|
return { |
|
|
"type": "object", |
|
|
"properties": { |
|
|
"location": { |
|
|
"type": "string", |
|
|
"description": "Location name", |
|
|
}, |
|
|
}, |
|
|
"required": ["location"], |
|
|
} |
|
|
|
|
|
async def execute(self, **kwargs) -> ToolResult: |
|
|
return ToolResult(success=True, content="Weather data") |
|
|
|
|
|
|
|
|
class MockCalculatorTool(Tool): |
|
|
"""Mock calculator tool for testing.""" |
|
|
|
|
|
@property |
|
|
def name(self) -> str: |
|
|
return "calculator" |
|
|
|
|
|
@property |
|
|
def description(self) -> str: |
|
|
return "Perform calculations" |
|
|
|
|
|
@property |
|
|
def parameters(self) -> dict[str, Any]: |
|
|
return { |
|
|
"type": "object", |
|
|
"properties": { |
|
|
"expression": { |
|
|
"type": "string", |
|
|
"description": "Math expression", |
|
|
}, |
|
|
}, |
|
|
"required": ["expression"], |
|
|
} |
|
|
|
|
|
async def execute(self, **kwargs) -> ToolResult: |
|
|
return ToolResult(success=True, content="42") |
|
|
|
|
|
|
|
|
class MockSearchTool(Tool): |
|
|
"""Mock search tool with complex schema.""" |
|
|
|
|
|
@property |
|
|
def name(self) -> str: |
|
|
return "search_database" |
|
|
|
|
|
@property |
|
|
def description(self) -> str: |
|
|
return "Search the database" |
|
|
|
|
|
@property |
|
|
def parameters(self) -> dict[str, Any]: |
|
|
return { |
|
|
"type": "object", |
|
|
"properties": { |
|
|
"query": { |
|
|
"type": "string", |
|
|
"description": "Search query", |
|
|
}, |
|
|
"filters": { |
|
|
"type": "object", |
|
|
"properties": { |
|
|
"category": {"type": "string"}, |
|
|
"min_price": {"type": "number"}, |
|
|
"max_price": {"type": "number"}, |
|
|
}, |
|
|
}, |
|
|
"limit": { |
|
|
"type": "integer", |
|
|
"minimum": 1, |
|
|
"maximum": 100, |
|
|
"default": 10, |
|
|
}, |
|
|
}, |
|
|
"required": ["query"], |
|
|
} |
|
|
|
|
|
async def execute(self, **kwargs) -> ToolResult: |
|
|
return ToolResult(success=True, content="Search results") |
|
|
|
|
|
|
|
|
class MockEnumTool(Tool): |
|
|
"""Mock tool with enum parameter.""" |
|
|
|
|
|
@property |
|
|
def name(self) -> str: |
|
|
return "set_status" |
|
|
|
|
|
@property |
|
|
def description(self) -> str: |
|
|
return "Set status" |
|
|
|
|
|
@property |
|
|
def parameters(self) -> dict[str, Any]: |
|
|
return { |
|
|
"type": "object", |
|
|
"properties": { |
|
|
"status": { |
|
|
"type": "string", |
|
|
"enum": ["active", "inactive", "pending"], |
|
|
"description": "Status value", |
|
|
} |
|
|
}, |
|
|
"required": ["status"], |
|
|
} |
|
|
|
|
|
async def execute(self, **kwargs) -> ToolResult: |
|
|
return ToolResult(success=True, content="Status set") |
|
|
|
|
|
|
|
|
def test_tool_to_schema(): |
|
|
"""Test Tool.to_schema() method.""" |
|
|
tool = MockWeatherTool() |
|
|
schema = tool.to_schema() |
|
|
|
|
|
assert isinstance(schema, dict) |
|
|
assert schema["name"] == "get_weather" |
|
|
assert schema["description"] == "Get weather information" |
|
|
assert "input_schema" in schema |
|
|
assert schema["input_schema"]["type"] == "object" |
|
|
assert "location" in schema["input_schema"]["properties"] |
|
|
assert schema["input_schema"]["required"] == ["location"] |
|
|
|
|
|
|
|
|
def test_tool_to_openai_schema(): |
|
|
"""Test Tool.to_openai_schema() method.""" |
|
|
tool = MockWeatherTool() |
|
|
schema = tool.to_openai_schema() |
|
|
|
|
|
assert isinstance(schema, dict) |
|
|
assert schema["type"] == "function" |
|
|
assert "function" in schema |
|
|
assert schema["function"]["name"] == "get_weather" |
|
|
assert schema["function"]["description"] == "Get weather information" |
|
|
assert "parameters" in schema["function"] |
|
|
assert schema["function"]["parameters"]["type"] == "object" |
|
|
assert "location" in schema["function"]["parameters"]["properties"] |
|
|
|
|
|
|
|
|
def test_tool_schema_complex(): |
|
|
"""Test tool with complex input schema.""" |
|
|
tool = MockSearchTool() |
|
|
schema = tool.to_schema() |
|
|
|
|
|
assert schema["name"] == "search_database" |
|
|
assert "query" in schema["input_schema"]["properties"] |
|
|
assert "filters" in schema["input_schema"]["properties"] |
|
|
assert "limit" in schema["input_schema"]["properties"] |
|
|
assert schema["input_schema"]["required"] == ["query"] |
|
|
|
|
|
|
|
|
def test_tool_openai_schema_complex(): |
|
|
"""Test OpenAI schema conversion for complex tool.""" |
|
|
tool = MockSearchTool() |
|
|
schema = tool.to_openai_schema() |
|
|
|
|
|
assert schema["type"] == "function" |
|
|
params = schema["function"]["parameters"] |
|
|
assert "query" in params["properties"] |
|
|
assert "filters" in params["properties"] |
|
|
assert "limit" in params["properties"] |
|
|
assert params["required"] == ["query"] |
|
|
|
|
|
|
|
|
def test_multiple_tools(): |
|
|
"""Test creating multiple tool instances.""" |
|
|
tool1 = MockWeatherTool() |
|
|
tool2 = MockCalculatorTool() |
|
|
|
|
|
tools = [tool1, tool2] |
|
|
assert len(tools) == 2 |
|
|
assert tools[0].name == "get_weather" |
|
|
assert tools[1].name == "calculator" |
|
|
|
|
|
|
|
|
anthropic_schemas = [t.to_schema() for t in tools] |
|
|
assert len(anthropic_schemas) == 2 |
|
|
assert all(isinstance(s, dict) for s in anthropic_schemas) |
|
|
assert all("name" in s and "description" in s and "input_schema" in s for s in anthropic_schemas) |
|
|
|
|
|
|
|
|
openai_schemas = [t.to_openai_schema() for t in tools] |
|
|
assert len(openai_schemas) == 2 |
|
|
assert all(isinstance(s, dict) for s in openai_schemas) |
|
|
assert all(s["type"] == "function" for s in openai_schemas) |
|
|
|
|
|
|
|
|
def test_tool_with_enum(): |
|
|
"""Test tool with enum parameter.""" |
|
|
tool = MockEnumTool() |
|
|
schema = tool.to_schema() |
|
|
|
|
|
status_prop = schema["input_schema"]["properties"]["status"] |
|
|
assert "enum" in status_prop |
|
|
assert status_prop["enum"] == ["active", "inactive", "pending"] |
|
|
|
|
|
|
|
|
openai_schema = tool.to_openai_schema() |
|
|
status_prop_openai = openai_schema["function"]["parameters"]["properties"]["status"] |
|
|
assert "enum" in status_prop_openai |
|
|
assert status_prop_openai["enum"] == ["active", "inactive", "pending"] |
|
|
|
|
|
|
|
|
def test_tool_schema_consistency(): |
|
|
"""Test that both schema methods produce consistent data.""" |
|
|
tool = MockCalculatorTool() |
|
|
|
|
|
anthropic_schema = tool.to_schema() |
|
|
openai_schema = tool.to_openai_schema() |
|
|
|
|
|
|
|
|
assert anthropic_schema["name"] == openai_schema["function"]["name"] |
|
|
|
|
|
assert anthropic_schema["description"] == openai_schema["function"]["description"] |
|
|
|
|
|
assert anthropic_schema["input_schema"] == openai_schema["function"]["parameters"] |
|
|
|
|
|
|
|
|
@pytest.mark.asyncio |
|
|
async def test_tool_execute(): |
|
|
"""Test that tools can be executed.""" |
|
|
tool = MockWeatherTool() |
|
|
result = await tool.execute(location="Tokyo") |
|
|
|
|
|
assert isinstance(result, ToolResult) |
|
|
assert result.success is True |
|
|
assert result.content == "Weather data" |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
pytest.main([__file__, "-v"]) |
|
|
|