wip: add tools to agent control and improve tool class

This commit is contained in:
Shivering Ocelot committed 2026-06-08 05:18:38 -05:00
1 parent 6134c026a6
commit 9585008433
7 files changed
+144 -13

No files matched your search

+74 -8
View File
@@ -1,3 +1,6 @@
import inspect
from typing import get_type_hints
class ToolParameter:
"""Describes a single input parameter for a tool.
@@ -55,6 +58,14 @@ class ToolSpec:
self.description = description
self.parameters = parameters or []
self.func = func
def clone (self):
return ToolSpec(
name = self.name,
description = self.description,
parameters = self.parameters,
func = self.func,
)
# -- serialization helpers -------------------------------------------
@@ -119,26 +130,81 @@ class Tool:
"""Base class for tools that implement their logic in a 'run' method.
Subclasses should override 'run' to provide the tool's functionality.
The Tool class provides a method to_spec() that automatically generates
a ToolSpec from the class name, docstring, and run method signature.
This allows tools to be used with LLM function calling without manually
creating ToolSpec objects. The generated ToolSpec is cached at initialization.
"""
def __init__(self):
pass
def __init__(self, name=None):
# Generate and cache the ToolSpec during initialization
self.name = name or self.__class__.tool_name()
self._spec = self._generate_spec()
def run(self, **kwargs):
"""Execute the tool. To be overridden by subclasses."""
raise NotImplementedError("Tool subclasses must override the 'run' method.")
@classmethod
def tool_name(cls):
return ''.join(['_'+c.lower() if c.isupper() else c for c in cls.__name__]).lstrip('_')
def to_spec(self):
"""Generate a ToolSpec from this tool instance."""
def _generate_spec(self):
"""Generate a ToolSpec from this tool instance.
Automatically creates a ToolSpec by:
- Using the class name (converted to snake_case) as the tool name
- Using the class docstring as the description
- Inspecting the run method signature to create parameters
"""
# Use the class docstring as the description
description = inspect.getdoc(self.__class__) or ""
# Get parameters from the run method signature
parameters = []
run_signature = inspect.signature(self.run)
type_hints = get_type_hints(self.run)
for param_name, param in run_signature.parameters.items():
# Skip 'self' parameter
if param_name == 'self':
continue
# Get type information
param_type = type_hints.get(param_name, "string")
if isinstance(param_type, type):
type_name = param_type.__name__.lower()
else:
type_name = str(param_type).lower()
# Check if parameter is required (no default or default is None)
is_required = param.default is inspect.Parameter.empty or param.default is None
# Create ToolParameter
tool_param = ToolParameter(
name=param_name,
type=type_name,
description="",
required=is_required
)
parameters.append(tool_param)
return ToolSpec(
name=None, # TODO: Computed from class name, or from class attribute
description=None, # TODO: From class doc comment
parameters=None, # TODO: Class attribute
name=self.name,
description=description,
parameters=parameters,
func=self.run,
)
def get_spec(self):
"""Return the cached ToolSpec for this tool."""
return self._spec
def __repr__(self):
return f"Tool(name={self.name!r}, params={[p.name for p in self.parameters]})"
return f"Tool(name={self._spec.name!r}, params={[p.name for p in self._spec.parameters]})"
class ToolCall:
"""Represents a single tool invocation request emitted by the model.