"""Beaver 工具注册表。 这层只做三件事: 1. 注册工具 2. 按名称查找工具 3. 导出 provider 可消费的 tool schemas 不要把执行逻辑塞进这里。 执行属于 runtime/executor,那样边界更清晰。 """ from __future__ import annotations from collections.abc import Sequence from typing import Iterable from beaver.tools.base import BaseTool, ToolSpec class ToolRegistry: """统一维护当前 runtime 可用的工具集合。""" def __init__(self) -> None: self._tools: dict[str, BaseTool] = {} def register(self, tool: BaseTool, *, replace: bool = False) -> None: """注册一个工具。 默认不允许重名覆盖,避免 loader/runtime 不小心把同名工具静默冲掉。 """ name = tool.spec.name if not replace and name in self._tools: raise ValueError(f"Tool '{name}' is already registered") self._tools[name] = tool def register_many(self, tools: Iterable[BaseTool], *, replace: bool = False) -> None: for tool in tools: self.register(tool, replace=replace) def get(self, name: str) -> BaseTool | None: return self._tools.get(name) def require(self, name: str) -> BaseTool: tool = self.get(name) if tool is None: raise KeyError(f"Unknown tool '{name}'") return tool def list_specs(self) -> list[ToolSpec]: return [tool.spec for tool in self._tools.values()] def list_always_specs(self) -> list[ToolSpec]: """列出每轮 run 都应该暴露给模型的基础工具。""" return [spec for spec in self.list_specs() if spec.always_available] def get_specs(self, names: Sequence[str]) -> list[ToolSpec]: """按名称顺序返回已注册工具 spec,忽略未知工具。""" specs: list[ToolSpec] = [] seen: set[str] = set() for name in names: tool = self.get(name) if tool is None or name in seen: continue specs.append(tool.spec) seen.add(name) return specs def export_provider_schemas(self) -> list[dict]: """导出给 provider 的函数工具 schema 列表。""" return [spec.to_provider_schema() for spec in self.list_specs()] def export_selected_provider_schemas(self, specs: Sequence[ToolSpec]) -> list[dict]: """导出一组已选择工具的 provider schema。""" return [spec.to_provider_schema() for spec in specs]