在 LangChain 这类 Agent 框架中,新增一种 Tool 类型时需要手动到处注册,有没有更优雅的方案?
手动注册 Tool 的痛点我经历过,一个项目里对接了 20 多个工具,每次新人加一个 Tool 得改四处:工具类文件、中央注册表、init.py 的导入、以及路由分发代码。一旦漏掉一个,调半天找不到工具,非常难受。
优雅解决的核心就是:让工具类自己“报到”,而不是人工替它报名。在 Python 里想做到这个,相当于在类被定义的瞬间,偷偷执行一段注册逻辑。
🧭 原理:类定义时自动触发注册¶
Python 的类创建有两个钩子能帮我们截胡:
-
元类
new:类对象生成前介入,能力最强,能改类结构。 -
init_subclass:类对象生成后立刻调用,轻量简洁。
对自动注册这种“附加动作”,init_subclass 通常更合适——它不会改变类的类型,避免多重继承冲突,而且语义就是“当有子类被定义时通知我”。
整个自动注册流程用图表示就是这样:

上层 Agent 调度时直接从 ToolRegistry 里按名字取类实例化即可,完全不碰手动注册。
🛠️ 落地实现:基于 init_subclass 的自注册 Tool 基类¶
我们写一个 AutoTool 基类,任何工具只要继承它,自动注册到统一的 ToolRegistry:
from typing import Dict, Type
class ToolRegistry:
"""全局工具库,只读访问"""
_tools: Dict[str, Type] = {}
@classmethod
def register(cls, tool_cls: Type):
name = getattr(tool_cls, 'name', tool_cls.__name__.lower())
if not name.isidentifier():
raise ValueError(f"Tool name '{name}' is not a valid identifier")
if name in cls._tools:
raise ValueError(f"Tool '{name}' already registered")
cls._tools[name] = tool_cls
@classmethod
def get(cls, name: str):
return cls._tools.get(name)
@classmethod
def all(cls):
return dict(cls._tools)
class AutoTool:
"""继承这个类,自动注册"""
name: str = "" # 子类定义工具名
description: str = "" # 可选描述
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 跳过基类本身
if cls.__name__ == 'AutoTool':
return
if not hasattr(cls, 'execute') or not callable(cls.execute):
raise TypeError(f"Tool '{cls.__name__}' must implement 'execute' method")
ToolRegistry.register(cls)
现在开发者只用写工具类,什么注册代码都不用管:
class SearchTool(AutoTool):
name = "search"
description = "搜索网页内容"
def execute(self, query: str):
return f"搜索结果: {query}..."
class CalcTool(AutoTool):
name = "calculator"
def execute(self, expression: str):
return eval(expression)
# 工具就已经自动在注册表里了
print(ToolRegistry.all()) # {'search': SearchTool, 'calculator': CalcTool}
和 LangChain 的集成也很顺滑:我们可以在框架初始化时,把所有自注册工具包装成 LangChain 的 Tool 对象:
from langchain.tools import Tool as LcTool
def build_langchain_tools():
return [
LcTool.from_function(
func=tool_cls().execute,
name=tool_cls.name,
description=tool_cls.description
)
for tool_cls in ToolRegistry.all().values()
]
这个模式把“注册”和“使用”完全解耦,新增工具只关心一个文件里的业务逻辑,不再到处改配置。