2024-10-23 09:28:12 +08:00
|
|
|
"""
|
2024-11-18 04:20:16 +08:00
|
|
|
This module provides a flexible foundation for creating and managing tools in the AgentPress system.
|
2024-10-23 09:28:12 +08:00
|
|
|
|
|
|
|
The tool system allows for easy creation of function-like tools that can be used by AI models.
|
2024-11-18 04:20:16 +08:00
|
|
|
It provides a way to define any schema format for these tools, making it compatible with
|
|
|
|
various AI model interfaces and custom implementations.
|
2024-10-23 09:28:12 +08:00
|
|
|
|
|
|
|
Key components:
|
2024-11-18 04:20:16 +08:00
|
|
|
- ToolResult: A dataclass representing the result of a tool execution
|
|
|
|
- Tool: An abstract base class that all tools should inherit from
|
|
|
|
- tool_schema: A decorator for defining tool schemas in any format
|
2024-10-23 09:28:12 +08:00
|
|
|
|
|
|
|
Usage:
|
2024-11-18 04:20:16 +08:00
|
|
|
1. Create a new tool by subclassing Tool
|
|
|
|
2. Define methods and decorate them with @tool_schema
|
|
|
|
3. The Tool class will automatically register these schemas
|
|
|
|
4. Use the tool in your ThreadManager
|
2024-10-23 09:28:12 +08:00
|
|
|
|
2024-11-18 04:20:16 +08:00
|
|
|
Example OpenAPI Schema:
|
2024-11-02 07:25:30 +08:00
|
|
|
class CalculatorTool(Tool):
|
2024-10-23 09:28:12 +08:00
|
|
|
@tool_schema({
|
2024-11-18 04:20:16 +08:00
|
|
|
"type": "function",
|
|
|
|
"function": {
|
|
|
|
"name": "divide",
|
|
|
|
"description": "Divide two numbers",
|
|
|
|
"parameters": {
|
|
|
|
"type": "object",
|
|
|
|
"properties": {
|
|
|
|
"a": {"type": "number", "description": "Numerator"},
|
|
|
|
"b": {"type": "number", "description": "Denominator"}
|
|
|
|
},
|
|
|
|
"required": ["a", "b"]
|
|
|
|
}
|
2024-10-23 09:28:12 +08:00
|
|
|
}
|
|
|
|
})
|
2024-11-02 07:25:30 +08:00
|
|
|
async def divide(self, a: float, b: float) -> ToolResult:
|
|
|
|
if b == 0:
|
|
|
|
return self.fail_response("Cannot divide by zero")
|
2024-11-18 04:20:16 +08:00
|
|
|
return self.success_response(f"Result: {a/b}")
|
2024-10-23 09:28:12 +08:00
|
|
|
|
2024-11-18 04:20:16 +08:00
|
|
|
Example Custom Schema:
|
|
|
|
class DeliveryTool(Tool):
|
|
|
|
@tool_schema({
|
|
|
|
"name": "get_delivery_date",
|
|
|
|
"description": "Get delivery date for order",
|
|
|
|
"input_format": "order_id: string",
|
|
|
|
"output_format": "delivery_date: ISO-8601 date string",
|
|
|
|
"examples": [
|
|
|
|
{"input": "ORD123", "output": "2024-03-25"}
|
|
|
|
],
|
|
|
|
"error_handling": {
|
|
|
|
"invalid_order": "Returns error if order not found",
|
|
|
|
"system_error": "Returns error on system failures"
|
|
|
|
}
|
|
|
|
})
|
|
|
|
async def get_delivery_date(self, order_id: str) -> ToolResult:
|
|
|
|
date = await self.fetch_delivery_date(order_id)
|
|
|
|
return self.success_response(date)
|
2024-10-23 09:28:12 +08:00
|
|
|
"""
|
|
|
|
|
2024-11-02 08:01:26 +08:00
|
|
|
from typing import Dict, Any, Union
|
2024-10-06 01:04:15 +08:00
|
|
|
from dataclasses import dataclass
|
2024-10-23 09:28:12 +08:00
|
|
|
from abc import ABC
|
2024-10-06 01:04:15 +08:00
|
|
|
import json
|
2024-10-23 09:28:12 +08:00
|
|
|
import inspect
|
2024-10-06 01:04:15 +08:00
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class ToolResult:
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Container for tool execution results."""
|
2024-10-06 01:04:15 +08:00
|
|
|
success: bool
|
|
|
|
output: str
|
|
|
|
|
|
|
|
class Tool(ABC):
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Abstract base class for all tools.
|
|
|
|
|
|
|
|
This class provides the foundation for creating tools with flexible schema definitions.
|
|
|
|
Subclasses can implement specific tool methods and define schemas in any format.
|
2024-10-23 09:28:12 +08:00
|
|
|
"""
|
2024-11-18 04:20:16 +08:00
|
|
|
|
2024-10-06 01:04:15 +08:00
|
|
|
def __init__(self):
|
2024-10-23 09:28:12 +08:00
|
|
|
self._schemas = {}
|
|
|
|
self._register_schemas()
|
|
|
|
|
|
|
|
def _register_schemas(self):
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Register schemas from all decorated methods."""
|
2024-10-23 09:28:12 +08:00
|
|
|
for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
|
|
|
|
if hasattr(method, 'schema'):
|
|
|
|
self._schemas[name] = method.schema
|
2024-10-06 01:04:15 +08:00
|
|
|
|
|
|
|
def get_schemas(self) -> Dict[str, Dict[str, Any]]:
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Get all registered tool schemas."""
|
2024-10-23 09:28:12 +08:00
|
|
|
return self._schemas
|
2024-10-06 01:04:15 +08:00
|
|
|
|
2024-11-02 08:01:26 +08:00
|
|
|
def success_response(self, data: Union[Dict[str, Any], str]) -> ToolResult:
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Create a successful tool result."""
|
2024-10-06 01:04:15 +08:00
|
|
|
if isinstance(data, str):
|
|
|
|
text = data
|
|
|
|
else:
|
|
|
|
text = json.dumps(data, indent=2)
|
|
|
|
return ToolResult(success=True, output=text)
|
|
|
|
|
|
|
|
def fail_response(self, msg: str) -> ToolResult:
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Create a failed tool result."""
|
2024-10-06 01:04:15 +08:00
|
|
|
return ToolResult(success=False, output=msg)
|
|
|
|
|
2024-10-23 09:28:12 +08:00
|
|
|
def tool_schema(schema: Dict[str, Any]):
|
2024-11-18 04:20:16 +08:00
|
|
|
"""Decorator for defining tool schemas.
|
|
|
|
|
|
|
|
Allows attaching any schema format to tool methods. The schema can follow
|
|
|
|
any specification (OpenAPI, custom format, etc.) as long as it's serializable
|
|
|
|
to JSON.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
# OpenAPI-style schema
|
2024-10-23 09:28:12 +08:00
|
|
|
@tool_schema({
|
2024-11-18 04:20:16 +08:00
|
|
|
"type": "function",
|
|
|
|
"function": {
|
|
|
|
"name": "get_weather",
|
|
|
|
"description": "Get weather forecast",
|
|
|
|
"parameters": {
|
|
|
|
"type": "object",
|
|
|
|
"properties": {
|
|
|
|
"location": {"type": "string"}
|
|
|
|
}
|
|
|
|
}
|
2024-10-23 09:28:12 +08:00
|
|
|
}
|
|
|
|
})
|
2024-11-18 04:20:16 +08:00
|
|
|
|
|
|
|
# Custom schema format
|
|
|
|
@tool_schema({
|
|
|
|
"tool_name": "analyze_sentiment",
|
|
|
|
"input": {
|
|
|
|
"text": "string - Text to analyze",
|
|
|
|
"language": "optional string - Language code"
|
|
|
|
},
|
|
|
|
"output": {
|
|
|
|
"sentiment": "string - Positive/Negative/Neutral",
|
|
|
|
"confidence": "float - Confidence score"
|
|
|
|
},
|
|
|
|
"error_cases": [
|
|
|
|
"invalid_language",
|
|
|
|
"text_too_long"
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
# Minimal schema
|
|
|
|
@tool_schema({
|
|
|
|
"name": "ping",
|
|
|
|
"description": "Check if service is alive"
|
|
|
|
})
|
2024-10-23 09:28:12 +08:00
|
|
|
"""
|
|
|
|
def decorator(func):
|
2024-11-18 04:20:16 +08:00
|
|
|
func.schema = schema
|
2024-10-23 09:28:12 +08:00
|
|
|
return func
|
|
|
|
return decorator
|