Upload mythos/tool.py
Browse files- mythos/tool.py +102 -0
mythos/tool.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tool abstraction for Mythos agents — MCP-native design."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import asyncio
|
| 6 |
+
import inspect
|
| 7 |
+
from typing import Any, Awaitable, Callable, Optional
|
| 8 |
+
|
| 9 |
+
from pydantic import BaseModel, Field
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class ToolSpec(BaseModel):
|
| 13 |
+
"""Specification for a tool, compatible with MCP and function-calling APIs."""
|
| 14 |
+
|
| 15 |
+
name: str
|
| 16 |
+
description: str
|
| 17 |
+
parameters: dict[str, Any] = Field(default_factory=dict)
|
| 18 |
+
required: list[str] = Field(default_factory=list)
|
| 19 |
+
returns: Optional[str] = None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Tool(BaseModel):
|
| 23 |
+
"""A callable tool that agents can use."""
|
| 24 |
+
|
| 25 |
+
spec: ToolSpec
|
| 26 |
+
_func: Optional[Callable[..., Any]] = None
|
| 27 |
+
|
| 28 |
+
class Config:
|
| 29 |
+
arbitrary_types_allowed = True
|
| 30 |
+
|
| 31 |
+
def __init__(self, **data):
|
| 32 |
+
super().__init__(**data)
|
| 33 |
+
self._func = data.get("_func")
|
| 34 |
+
|
| 35 |
+
async def run(self, **kwargs) -> Any:
|
| 36 |
+
"""Execute the tool with given arguments."""
|
| 37 |
+
if self._func is None:
|
| 38 |
+
raise RuntimeError(f"Tool '{self.spec.name}' has no implementation")
|
| 39 |
+
if asyncio.iscoroutinefunction(self._func):
|
| 40 |
+
return await self._func(**kwargs)
|
| 41 |
+
return self._func(**kwargs)
|
| 42 |
+
|
| 43 |
+
@classmethod
|
| 44 |
+
def from_function(
|
| 45 |
+
cls,
|
| 46 |
+
func: Callable[..., Any],
|
| 47 |
+
name: Optional[str] = None,
|
| 48 |
+
description: Optional[str] = None,
|
| 49 |
+
) -> Tool:
|
| 50 |
+
"""Create a Tool from a Python function."""
|
| 51 |
+
sig = inspect.signature(func)
|
| 52 |
+
params = {}
|
| 53 |
+
required = []
|
| 54 |
+
for param_name, param in sig.parameters.items():
|
| 55 |
+
param_type = param.annotation if param.annotation is not inspect.Parameter.empty else "string"
|
| 56 |
+
params[param_name] = {"type": str(param_type)}
|
| 57 |
+
if param.default is inspect.Parameter.empty:
|
| 58 |
+
required.append(param_name)
|
| 59 |
+
|
| 60 |
+
spec = ToolSpec(
|
| 61 |
+
name=name or func.__name__,
|
| 62 |
+
description=description or (func.__doc__ or ""),
|
| 63 |
+
parameters=params,
|
| 64 |
+
required=required,
|
| 65 |
+
)
|
| 66 |
+
return cls(spec=spec, _func=func)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class ToolRegistry(BaseModel):
|
| 70 |
+
"""Registry of tools available to agents."""
|
| 71 |
+
|
| 72 |
+
tools: dict[str, Tool] = Field(default_factory=dict)
|
| 73 |
+
|
| 74 |
+
def register(self, tool: Tool) -> None:
|
| 75 |
+
"""Register a tool."""
|
| 76 |
+
self.tools[tool.spec.name] = tool
|
| 77 |
+
|
| 78 |
+
def register_function(
|
| 79 |
+
self,
|
| 80 |
+
func: Callable[..., Any],
|
| 81 |
+
name: Optional[str] = None,
|
| 82 |
+
description: Optional[str] = None,
|
| 83 |
+
) -> Tool:
|
| 84 |
+
"""Register a function as a tool."""
|
| 85 |
+
tool = Tool.from_function(func, name, description)
|
| 86 |
+
self.register(tool)
|
| 87 |
+
return tool
|
| 88 |
+
|
| 89 |
+
def get(self, name: str) -> Optional[Tool]:
|
| 90 |
+
"""Get a tool by name."""
|
| 91 |
+
return self.tools.get(name)
|
| 92 |
+
|
| 93 |
+
def list_tools(self) -> list[ToolSpec]:
|
| 94 |
+
"""List all tool specifications."""
|
| 95 |
+
return [t.spec for t in self.tools.values()]
|
| 96 |
+
|
| 97 |
+
async def call(self, name: str, **kwargs) -> Any:
|
| 98 |
+
"""Call a tool by name."""
|
| 99 |
+
tool = self.get(name)
|
| 100 |
+
if tool is None:
|
| 101 |
+
raise KeyError(f"Tool '{name}' not found")
|
| 102 |
+
return await tool.run(**kwargs)
|