registry.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. from __future__ import annotations
  2. from collections.abc import Callable
  3. from typing import Any
  4. from supply_agent.tools.base import Tool
  5. from supply_agent.tools.errors import content_indicates_error
  6. from supply_agent.types import ToolDefinition, ToolResult
  7. class ToolRegistry:
  8. """Registry for managing and executing agent tools."""
  9. def __init__(self) -> None:
  10. self._tools: dict[str, Tool] = {}
  11. def register(
  12. self,
  13. func: Callable[..., Any],
  14. name: str | None = None,
  15. description: str | None = None,
  16. ) -> Tool:
  17. """Register a function as a tool."""
  18. tool = Tool(func, name=name, description=description)
  19. self._tools[tool.name] = tool
  20. return tool
  21. def register_tool(self, tool: Tool) -> None:
  22. """Register an existing Tool instance."""
  23. self._tools[tool.name] = tool
  24. def unregister(self, name: str) -> None:
  25. self._tools.pop(name, None)
  26. def get(self, name: str) -> Tool | None:
  27. return self._tools.get(name)
  28. def list_tools(self) -> list[str]:
  29. return list(self._tools.keys())
  30. @property
  31. def definitions(self) -> list[ToolDefinition]:
  32. return [t.definition for t in self._tools.values()]
  33. def execute(self, tool_call_id: str, name: str, arguments: str) -> ToolResult:
  34. """Execute a tool by name and return the result."""
  35. tool = self._tools.get(name)
  36. if not tool:
  37. return ToolResult(
  38. tool_call_id=tool_call_id,
  39. name=name,
  40. content=f'{{"error": "Tool \'{name}\' not found"}}',
  41. is_error=True,
  42. )
  43. content = tool.execute(arguments)
  44. return ToolResult(
  45. tool_call_id=tool_call_id,
  46. name=name,
  47. content=content,
  48. is_error=content_indicates_error(content),
  49. )
  50. async def aexecute(self, tool_call_id: str, name: str, arguments: str) -> ToolResult:
  51. """Async execute a tool."""
  52. tool = self._tools.get(name)
  53. if not tool:
  54. return ToolResult(
  55. tool_call_id=tool_call_id,
  56. name=name,
  57. content=f'{{"error": "Tool \'{name}\' not found"}}',
  58. is_error=True,
  59. )
  60. content = await tool.aexecute(arguments)
  61. return ToolResult(
  62. tool_call_id=tool_call_id,
  63. name=name,
  64. content=content,
  65. is_error=content_indicates_error(content),
  66. )
  67. def from_decorated(self, *funcs: Callable[..., Any]) -> ToolRegistry:
  68. """Register multiple @tool-decorated functions."""
  69. for func in funcs:
  70. if getattr(func, "_is_tool", False):
  71. self.register(func)
  72. return self
  73. def __len__(self) -> int:
  74. return len(self._tools)
  75. def __contains__(self, name: str) -> bool:
  76. return name in self._tools