Tool 是怎么工作的

用「问 BTC 价格」这个真实例子讲一遍,最后对照到 Berth 的代码。

模型本身只会读文字、写文字,不能上网,也不会执行代码。
所谓 tool,就是你提前告诉模型「有这些工具可用」。模型需要时写一张申请单, 你的程序照着去执行,再把结果当作新的一段文字交回给模型。

打个比方:餐厅

顾客 = 用户

问:「现在 BTC 多少钱?」

服务员 = 模型

很会说话,但进不了后厨。手里有一份菜单(tool 清单),会写点单。

点单 = tool call

「hl__get_market,coin=BTC」,写在纸上递出去。

后厨 = 你的代码

真正干活:发 HTTP 请求、查数据库、下单……做完把菜端回来。

服务员不会自己炒菜。模型说「我要调 tool」,只是一段格式固定的文字;真正执行的永远是程序。

一次完整的来回

你的程序(agent 运行时) 模型(LLM)
  1. 程序
    把三样东西一起发给模型:系统提示词、聊天记录(含「现在 btc 的价格是多少?」)、tool 清单(每个 tool 的名字、用途说明、参数格式)。
  2. 模型
    读完判断:「这要实时数据,我自己不知道。」于是不直接回答,而是输出一条 tool call:
    { "tool": "hl__get_market", "input": { "coin": "BTC" } }
    然后停下来等结果。
  3. 程序
    收到这条 tool call,校验参数格式,找到对应函数去执行。这里就是向 Hyperliquid 发 POST /info 请求,拿回标记价、资金费率、盘口等数据。
  4. 程序
    把执行结果(一段 JSON)作为「tool 结果」消息追加进聊天记录,再把整个对话发给模型一次。
  5. 模型
    这次看到数据了,用自然语言写出回答:「BTC 标记价格 79,377……」。如果还缺信息,它可以回到第 2 步再申请别的 tool。
你的程序 执行 tool、拼对话 模型 只读写文字 对话 + tool 清单 + 上次的 tool 结果 「请调 hl__get_market(BTC)」或 最终回答 循环进行,直到模型给出最终回答(不再申请 tool)

tool 清单里写了什么

每个 tool 给模型的就是一张「说明书」,三要素:

{
  "name": "hl__get_market",
  "description": "读取某个 Hyperliquid 市场的实时行情……",   // 模型靠这句话决定用不用它
  "input_schema": {                                          // 参数格式(JSON Schema)
    "coin":       "string,必填,比如 BTC",
    "marketType": "perp | spot,默认 perp",
    "bookDepth":  "可选,盘口精度"
  }
}

示意写法,不是 Berth 的原文。真实定义在 contexts/bot/src/domain/tools/hl/hlMarkets.ts,用 zod 写 schema,框架会自动转成 JSON Schema 发给模型。

说明书写得好不好,直接决定模型会不会选对 tool。模型看不到你的代码,只看得到名字、描述和参数格式。

新人最该记住的几件事

模型从不亲手执行

它只「申请」,执行权、网络、密钥都在程序手里。所以安全检查放在程序这边。

tool 结果就是普通文本

对模型来说,结果和用户的话一样是输入。网页内容可能夹带「忽略之前的指令」这类陷阱,Berth 会把它包在 untrusted 标记里,并告诉模型这些只是数据。

一次回答可调多次 tool

先查价格,再查 K 线……每次都是「申请 → 执行 → 喂回」。Berth 给每次 Run 设了 tool 调用次数上限,防止无限循环。

数字要来自 tool

模型很会「编得像真的」。Berth 的系统提示词要求:所有数字必须从 tool 结果原样抄,不许自己算或估。

用不用 tool 是模型自己判断的

程序只提供菜单。想引导它,就改描述和系统提示词,比如「实时行情一律走 Hyperliquid tools」。

tool 越多越难选

菜单太长,模型容易挑错,也更费 token。Berth 有「deferred」机制:不常用的 tool 先不放进清单,需要时用 tool_search 再加载。

对照到 Berth 代码

环节 在哪
tool 说明书(名字、参数 schema) contexts/bot/src/domain/tools/hl/hlToolPack.ts、hlMarkets.ts
系统提示词(何时用哪个 tool) apps/bot/src/owner/systemPrompt.ts
收到 tool call → 分派执行 apps/bot/src/owner/thinkRuntime.ts 的 executeHlReadTool
执行逻辑(读哪些数据、怎么整理) contexts/bot/src/application/hl/HlReadService.ts
真正发网络请求 SDK HttpTransport 的 fetch(),外面套一层缓存(apps/bot/src/platform/hlInfo.ts)
「请求 → 执行 → 喂回」这个循环本身 交给框架(AI SDK)驱动,Berth 只在执行前后挂钩子,比如 beforeToolCall 做次数限制
一句话总结:tool = 模型写申请,程序去执行,结果再喂回给模型。