第 6 章 · 01 工具注册表模式


文档摘要

第 6 章 · 01 工具注册表模式 本节摘要:本节是「LLM 工具调用体系」的开篇,讲一个常被忽略却很关键的设计—— 。整个文件只有十几行,却用最朴素的方式回答了一个问题:「Agent 到底能用哪些函数?」。它的做法是写一个 函数,返回一个函数对象的列表(不是字符串、不是字典),把分散在各模块的工具统一汇聚到一个入口。这种「注册表」模式把「工具实现」和「工具消费方」解耦——Agent 不需要知道工具在哪个文件,只要从注册表拿就行。本节逐行拆解这个最小注册表,并讨论它的优缺点,为后两节(具体工具、调用机制)打底。 内容来源:原项目源码 ,精读并套用体系化模板。 学习目标 阅读完本节,你应当能够: 读懂 的全部代码(它很短)。 说清「注册表模式」要解决的解耦问题。

第 6 章 · 01 工具注册表模式

本节摘要:本节是「LLM 工具调用体系」的开篇,讲一个常被忽略却很关键的设计——tools_registry.py。整个文件只有十几行,却用最朴素的方式回答了一个问题:「Agent 到底能用哪些函数?」。它的做法是写一个 get_tools() 函数,返回一个函数对象的列表(不是字符串、不是字典),把分散在各模块的工具统一汇聚到一个入口。这种「注册表」模式把「工具实现」和「工具消费方」解耦——Agent 不需要知道工具在哪个文件,只要从注册表拿就行。本节逐行拆解这个最小注册表,并讨论它的优缺点,为后两节(具体工具、调用机制)打底。

内容来源:原项目源码 autohedge/tools/tools_registry.py,精读并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 读懂 tools_registry.py全部代码(它很短)。
  2. 说清「注册表模式」要解决的解耦问题。
  3. 解释为什么 get_tools() 返回的是函数对象列表,而不是字符串或字典。
  4. 评价这个极简注册表的优缺点,并知道生产级注册表该补什么。

一、注册表要解决的问题

先想一个朴素的问题:Agent 想调工具,它怎么知道有哪些工具可调?

最直接的写法是,在实例化 Agent 的地方直接 import 并塞进 tools=[...]:

from autohedge.tools.jupiter_search import search_tokens from autohedge.tools.ultra_tools import execute_trade agent = Agent(tools=[search_tokens, execute_trade])

这样写能用,但有个毛病:工具的「清单」散落在各个消费方。如果今天新增一个工具,你得去每个用工具的地方都改一遍;如果某个工具改了名字,所有引用处都要跟着改。消费方(谁用工具)和实现方(工具在哪)耦合在了一起。

注册表模式就是来解这个耦的:单独弄一个地方登记「全部可用工具」,谁都别直接 import 实现方,统一从注册表要。AutoHedge 的 tools_registry.py 就是这个登记处。

二、tools_registry.py 全文逐行

整个文件只有 15 行,逐行看:

from autohedge.tools.jupiter_search import search_tokens # ① 导入:Jupiter 代币搜索 from autohedge.tools.jupiter_price import get_token_price # ② 导入:Jupiter 代币价格 from autohedge.tools.ultra_tools import execute_trade, get_holdings # ③ 导入:Solana 执行+持仓 from autohedge.tools.ultra_tools import get_order # ④ 导入:Jupiter 取未签名交易 def get_tools(): # ⑤ 注册表入口函数 return [ search_tokens, # ⑥ 列表元素是函数对象本身 get_token_price, execute_trade, get_holdings, get_order, ]

几个关键点:

  • ①~④ 是导入语句:把四个模块里的五个函数拉进当前命名空间。注意第 ③ 行一次从 ultra_tools 导入了两个函数(execute_tradeget_holdings),第 ④ 行又单独导入 get_order——这是源码里实际的写法,分两行不影响功能。
  • get_tools():注册表的对外接口。它是个函数,不是变量;每次调用都会构造一个新列表返回(列表是可变对象,这样避免共享可变状态)。
  • ⑥ 列表元素是「函数对象」本身:写的是 search_tokens,没有括号。有括号就变成「调用结果」了,这里要的是函数本身,供后续 LLM 框架按需调用。

💡 核心心法:「函数对象」是一等公民——可以放进列表、当参数传、被别人调用。get_tools() 返回的就是「五个待调用的函数」,至于什么时候调、调哪个,交给 LLM 框架在运行时决定(下一节详讲)。

三、为什么返回「函数对象列表」

对比几种可能的注册表设计,体会这种选择:

设计方案 形式 优缺点
字符串列表 ["search_tokens", ...] 简单,但调用时要按名字反射查函数,运行时易出错
字典 {"search_tokens": search_tokens, ...} 可按名查,但 LLM 框架通常不按名取,按函数签名取更直接
函数对象列表(本项目) [search_tokens, ...] 直接是可调用对象,框架读 __name____doc__、签名就能用
装饰器注册 @register_tool 装饰函数 更优雅、自动收集,但要维护全局表,复杂度高

swarms(以及多数 LLM 工具框架)拿到的就是函数对象,然后通过 Python 内省机制读出:

  • func.__name__:工具名(如 search_tokens)。
  • func.__doc__:工具说明(给 LLM 看的「这个工具干嘛的」)。
  • inspect.signature(func):参数名与类型(给 LLM 决定怎么传参)。

所以,函数本身的命名、文档字符串、参数注解就是工具的「说明书」——这也是为什么本项目的工具函数(下一节看 exa_search)都写了详尽的 docstring。

四、注册表内容一览

这五个被注册的函数,覆盖了 Solana 链上交易的全套动作:

函数 来源模块 作用
search_tokens jupiter_search 按 symbol/mint 搜 Solana 代币
get_token_price jupiter_price 按 mint 查 Solana 代币 USD 价格
get_order ultra_tools 向 Jupiter 拿一笔未签名交易
execute_trade ultra_tools 用私钥签名并广播交易
get_holdings ultra_tools 查钱包持仓(SOL + 各代币)

注意一个显著缺口:这个注册表里没有任何股票端工具——没有 yahoo_api、没有 polygon_api,也没有情绪分析用的 exa_search。换句话说,这个统一注册表只收录了 Solana 链路工具。

💡 关键观察:这与第 1 章看到的现象一致——项目里情绪 Agent 是单独在 workers.py 里直接绑了 tools=[exa_search],绕开了注册表;而 yahoo/polygon 等工具根本没被绑给任何 Agent。注册表是个「好设计」,但本项目并没有把它贯彻到所有工具——这是「未完成」特征的又一处体现。

五、谁在调用 get_tools()

在整个项目里搜 get_tools,会发现它主要被外部脚本(example.py 一类的入口)或可扩展点引用,用来「拿一套工具配给某个 Agent」。典型用法:

from autohedge.tools.tools_registry import get_tools agent = Agent( agent_name="Solana-Agent", tools=get_tools(), # 一次性把五个工具全配上 ... )

注意 get_tools() 后面有括号——调用函数、拿到列表;而列表里的元素是函数对象(无括号)。两者别混淆。

六、这个极简注册表的优缺点

优点:

  1. 单一入口:所有工具的清单集中一处,新增工具只改这个文件。
  2. 解耦:消费方只依赖 get_tools,不直接依赖各实现模块,实现方可自由重构。
  3. 零依赖:没有额外的元数据格式,纯 Python 函数对象,框架通用。

缺点(也是改进方向):

  1. 无元数据:没有「工具分类」「是否需要私钥」「是否有副作用」这类标签。生产系统会想要这些(比如把「读操作」和「写操作」分开,写操作要二次确认)。
  2. 静态注册:硬编码在代码里,无法运行时动态增减。
  3. 覆盖不全(本项目特有问题):只收了 Solana 工具,股票/情绪工具游离在外。
  4. 无权限控制:任何人拿到注册表就能调全部工具,包括动钱的 execute_trade——生产里应分级授权。

⚠️ 现实澄清:这个注册表本身是个干净的设计,但它注册的五个工具里有 execute_trade 这种真实动钱的函数,且没有任何分级或确认机制。把「读数据」和「花钱」混在同一个列表里、用同一套权限对待,在生产环境是危险的。

本节要点回顾

  1. 注册表模式:tools_registry.py 用一个 get_tools() 函数把分散的工具汇聚成单一入口,解耦「实现方」与「消费方」。
  2. 全文只有 15 行:导入五个函数(search_tokensget_token_priceexecute_tradeget_holdingsget_order),get_tools() 返回它们的列表。
  3. 返回函数对象本身:列表元素是无括号的函数对象;框架靠 __name____doc__、签名做内省,所以工具函数的 docstring 就是给 LLM 的说明书。
  4. 覆盖范围有限:只收 Solana 链路工具;情绪的 exa_search 在 workers.py 单独绑,股票工具根本没进注册表——「好设计但没贯彻」。
  5. 优缺点:优点是单一入口、零依赖、解耦;缺点是无元数据、无分级、读写操作混在一起、动钱工具无确认——生产化要补这些。

下一节,我们精读注册表外的另一个工具——情绪 Agent 用的 exa_search_tool.py,看一个完整的 LLM 工具函数长什么样。


发布者: 作者: 灏天文库 转发
评论区 (0)
U