AI Agent 工具调用:一个关于"意识如何控制世界"的故事
一句话说清本质:
AI 从来没有"碰到"过文件系统。它只是在文本空间里描述了一个动作,Runtime 替它实现了这个动作,再把结果翻译回文本喂给它。这个循环就是 Tool Calling 的全部秘密,也是唯一秘密。
你有没有想过一个问题:一个 AI,它活在纯文本里,看不见摸不着,它是怎么"动手"去读你的文件、执行你的命令的?
我第一次接触 Agent 时,最震撼的不是它多聪明,而是这个问题的答案——它根本没有"动手"。它从始至终都在做同一件事:接龙文字。
那它是怎么控制电脑的?从头说起。
一、三句话讲清楚原理
先不绕弯子,直接上答案——
你:帮我查一下服务器的配置
↓
AI(纯文本空间):思考 → 输出一个 JSON
→ {"tool":"ssh","params":{"host":"192.168.x.x","cmd":"free -h"}}
↓
Runtime(框架):看到这个 JSON → 真的去执行 ssh
↓
操作系统:跑命令 → 把结果还给 Runtime
↓
Runtime:转成文本 → 塞回给 AI
↓
AI:哦,内存是 15G → 写成答案回复你
就是这样一个循环:AI 描述想法 → JSON → Runtime 执行 → 结果回传 → AI 再看 → 再想 → 再到下一个想法……
关键在哪里?
Runtime 没有任何智能。它就是个机械的 JSON 解析器。整个系统的"智商"全靠 AI 输出格式的准确性撑着。AI 输出格式一乱,Runtime 直接报错,AI 必须自己发现并修正。
这不是玄学,纯工程。
二、这件事的源头:四条思想脉络
这个循环不是凭空冒出来的,它有四条清晰的传承线。
🌀 控制论(1940s)
诺伯特·维纳在 1948 年写了一本书,叫《控制论》。核心思想就一句话:系统通过观察自身行为的结果来调整下一步行动。
你房间里的恒温器就是最简单的例子——温度低了就加热,到了就停。传感器、控制器、执行器、反馈,四个组件构成一个闭环。
Agent 的 Tool Calling 本质上就是同一个结构:
传感器 = AI 收到用户消息 + 工具返回的结果 控制器 = AI 的推理层 执行器 = Runtime 执行工具调用 反馈 = 工具结果回传
🔧 Unix 管道(1970s)
这条线是工程层面的。Unix 的设计哲学是:每个程序只做一件事,通过文本流串联起来,组合成更复杂的功能。
cmd1 | cmd2 | cmd3
映射到 Agent 上:
每个 Tool 聚焦一个能力(读文件、查天气、发邮件) JSON 作为通用的调用格式(类比 Unix 的文本流) 多个 Tool 串联完成复杂任务
🧠 ReAct 论文(2022,Google)
这是直接的学术源头。在这之前,AI 做推理有两种主流方式:
Chain of Thought(CoT):思考 → 思考 → 思考 → 答案。只推理,不行动。 单纯行动:直接调工具,但不思考为什么调。
Yao 等人在 2022 年 10 月发表的 ReAct 论文(ICLR 2023),把这两件事合在了一起:
CoT: 思考 → 思考 → 思考 → 答案 ReAct: 思考 → 行动 → 观察 → 再思考 → 再行动 → 观察 → … → 答案
举个例子:
问:"纽约到东京的时差是多少?"
CoT 模式: "纽约在东五区?不对,纽约是西五区,东京是东九区,差 14 个小时……"(纯推理,全靠模型知识,错了就是错了)
ReAct 模式:
思考:我需要查纽约和东京的时区
行动:[搜索 "New York timezone UTC offset"]
观察:UTC-5
思考:再查东京
行动:[搜索 "Tokyo timezone UTC offset"]
观察:UTC+9
思考:相差 14 小时,纽约比东京晚 14 小时
答案:纽约比东京晚 14 个小时
今天所有 Agent 的工具调用机制——OpenAI Function Calling、Claude Tool Use、MCP——都是在 ReAct 这个模式上的工程化包装。
🚀 OpenAI Function Calling(2023.6)
ReAct 提出了模式,但留给开发者一个问题:"我怎么知道 AI 想调用什么工具?"
答案是猜。开发者得自己解析 AI 的输出文本,试图从中理解"它是想查天气还是想发邮件"。
2023 年 6 月 13 日,OpenAI 用 API 解决了这个问题。他们在 GPT-4 的接口里加了一个 functions 参数:
{
"functions": [{
"name": "get_weather",
"description": "查天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
}
}
}]
}
然后 AI 的回复里就可能出现标准化的 tool_calls JSON。这是第一次在商用 API 层面定义了"AI 输出结构化行动指令"的标准格式。开发者不需要猜了,直接拿到结构化的调用参数。
再后来(2023.11),OpenAI 支持了一次返回多个 tool_calls——单步可以并行调用多个工具。参数名也从 functions 改成了 tools(2024),反映了从"函数调用"到"通用工具"的概念升级。
三、真实项目拆解:三个例子
☁️ MCP Weather Server
社区中最常见的 MCP 入门示例——一个只有 50 行代码的天气查询服务。
class WeatherServer:
@tool()
def get_forecast(self, city: str) -> str:
"""获取指定城市未来 7 天的天气预报"""
data = requests.get(f"https://api.weather.com/{city}")
return data.text
@tool()
def get_alerts(self, region: str) -> list:
"""获取指定地区的天气预警"""
return alert_service.query(region)
接入流程:启动 Server → 在 Claude/Hermes 配置里加上这个 server → AI 自动发现工具 → 用户说"北京明天天气怎么样" → AI 输出 {"tool":"get_forecast","args":{"city":"北京"}} → Runtime 调用 → 结果返回。
这个项目展示了 MCP 协议的核心设计:工具和 Runtime 之间加了一层标准化接口。开发者只需要实现 @tool() 装饰器,不用关心 AI 端的细节。
📊 drawio-mcp(真·实战)
这是另一个真实的 MCP Server,把 draw.io 的图表生成能力封装成了 AI 可调用的工具。
当我想画架构图时,整个流程是这样的:
你:帮我画个架构图
↓
AI:理解需求 → 写出 Mermaid 代码
↓
输出 tool_call:
{"tool": "open_drawio_mermaid", "args": {"content": "..."}}↓
Runtime:看到 tool_call → 调用 drawio-mcp
↓
drawio-mcp:生成 SVG → 打开浏览器窗口
↓
结果回传 → AI 告诉你:"画好了,你窗口里能看到"
这就是每天都在用的流程。没有魔法,全是工程。
🧰 Headroom(55.6K Star 的 Token 压缩工具)
Headroom 是 Netflix 高级工程师 Tejas Chopra 的个人开源项目,2026 年 1 月发布,至今已在 GitHub 收获超过 55,600 Star。虽然并非 Netflix 公司官方出品,但 Netflix 内部已有多个团队在用。
它号称可节省 60%-95% 的 Token 消耗(README 第一句:"60–95% fewer tokens, same answers")。仓库自带可复现的评测套件,分场景数据如下:
| 场景 | 压缩前 Token | 压缩后 Token | 节省率 |
|---|---|---|---|
| 代码搜索(100 结果) | 17,765 | 1,408 | 92% |
| SRE 事故调试 | 65,694 | 5,118 | 92% |
| GitHub Issue 分类 | 54,174 | 14,761 | 73% |
| 代码库探索 | 78,502 | 41,254 | 47% |
它通过 MCP 服务器模式暴露三个工具:
| 工具 | 功能 |
|---|---|
| headroom_compress | 压缩发给 LLM 的内容(工具输出、日志、RAG 检索片段等) |
| headroom_retrieve | CCR 机制——从本地缓存(Redis/SQLite)取回压缩前的原文 |
| headroom_stats | 查询压缩统计与节省金额(按当前提供商定价计算) |
当一个 Agent 跑长任务时,调用链可能是这样的:
Agent 调用 headroom_compress(压缩日志)
→ Runtime 执行压缩
→ 返回压缩后的文本
→ Agent 继续推理
→ 需要看细节时,调用 headroom_retrieve(取回原文)
→ Runtime 从缓存读取
→ 返回原文
Chopra 在近期的开源峰会上分享了一组数据:Headroom 累计已为用户节省约 70 万美元、释放超过 2000 亿 Token。
他的起因是一张 $287 的 Claude Sonnet API 账单——仔细分析后发现,大量成本并非来自他手写的提示词,而是嵌套的 JSON 结构、重复的 API 响应和冗余的数据库字段。有研究指出,AI 应用中约 76% 的 Token 消耗仅用于读取用户输入。
关键洞察:工具之间也可以互相调用。 一个工具的输出可以成为另一个工具的输入。这已经不是"AI 调工具"了,这是"工具调工具"——A2A(Agent-to-Agent)协议的雏形。
四、各家的实现差异
虽然底层原理一样,但各家在细节上有明显区别。
| 厂商 | 特色 | 一句话总结 |
|---|---|---|
| OpenAI | Structured Outputs 强制格式正确,并行调用最成熟 | 亲爹优势,格式最标准 |
| Claude | 每次调用前会说"我要调 XX 工具了,因为……" | 可解释性强,复杂任务表现出色 |
| Gemini | 200 万 token 超长上下文 | 你不需要拆分任务,一次全告诉我 |
| Hermes | Skill 系统按需加载工具 | 不一次性注册,根据任务暴露最相关工具集 |
Hermes 的按需加载尤其值得一提——它不是在每个对话里把 60 个工具全塞给模型,而是根据当前任务上下文,只暴露最相关的几个。这减少了 AI 的选择负担,提高了工具调用的准确率。我现在写这篇文章,就在用这个机制。
五、加餐:工业级框架的代码到底长什么样
这部分稍微硬核一些。理解了它,你就理解了为什么工业级 Agent 框架和玩具 Demo 之间隔着一个太平洋。
(对代码不感兴趣可以跳到第六节看瓶颈和总结。)
🌀 双层循环,不是一层
大多数人脑子里 Agent 是这样的:
LLM 回复 → 解析工具 → 执行 → 结果返回 → 下一轮
实际代码是两层 while:
外层 while(true): ← 处理你中途插话、发新指令
内层 while(有工具调用): ← LLM→工具→结果→再调 LLM 的自动链
1. 把消息发给 LLM,拿到回复
2. 解析回复中的 tool_calls
3. 并行执行所有工具
4. 把结果塞回上下文
5. 回到步骤 1(内层继续)
对话泡完了,检查有没有新消息
没有 → 退出外层
为什么分两层? 因为用户可能中途插话。你在 Agent 跑一半时发消息,外层循环负责把这条消息注入到下一轮推理中。内层只负责"装弹→开枪→装弹→开枪"的自动循环。
🗣️ Provider 系统——最大的设计亮点
这是 pi-agent 最独特的设计。大多数框架依赖 LLM 提供商的原生 tool calling API,但 pi-agent 多了一个文本编码层。它不用提供商的 tool calling,而是把工具描述写成提示语塞给模型,让模型以文本格式回应,自己再解析出来。
看 DeepSeek 的格式:
<|tool▁calls▁begin|>
<|tool▁call▁begin|>
{"name": "read", "arguments": {"path": "/tmp/x"}}
<|tool▁call▁end|>
<|tool▁calls▁end|>
pi 的 packages/ai/src/providers/ 目录目前覆盖了超过 25 个独立 Provider(含区域变体共 36 个 .ts 文件),从 Anthropic、DeepSeek、Google、OpenAI 到国内的 Kimi、MiniMax、Moonshot、小米,再到 Amazon Bedrock、Azure、Cerebras、Cloudflare、Fireworks、Groq、HuggingFace、Mistral、NVIDIA、Together、OpenRouter 等海外平台,一应俱全:
| Provider | 说明 |
|---|---|
| anthropic | Anthropic Claude |
| deepseek | DeepSeek |
| Google Gemini | |
| google-vertex | Google Vertex AI |
| kimi-coding | Kimi(Moonshot)编程版 |
| minimax / minimax-cn | MiniMax 及中国区 |
| moonshotai / moonshotai-cn | Moonshot AI 及中国区 |
| openai | OpenAI GPT 系列 |
| openai-codex | OpenAI Codex |
| openrouter | OpenRouter 路由 |
| xai | xAI Grok |
| amazon-bedrock | Amazon Bedrock |
| azure-openai-responses | Azure OpenAI |
| cerebras | Cerebras |
| cloudflare-ai-gateway | Cloudflare AI Gateway |
| cloudflare-workers-ai | Cloudflare Workers AI |
| fireworks | Fireworks AI |
| github-copilot | GitHub Copilot |
| groq | Groq |
| huggingface | Hugging Face |
| mistral | Mistral AI |
| nvidia | NVIDIA NIM |
| together | Together AI |
| opencode / opencode-go | OpenCode |
| vercel-ai-gateway | Vercel AI Gateway |
| ant-ling | Ant Ling |
| xiaomi(含 ams/cn/sgp 变体) | 小米系列 |
| zai / zai-coding-cn | ZAI 及中国区变体 |
三个好处:
兼容任何模型——只要模型会生成文本,就能走这套流程 流式解析——模型一边生成,一边就能识别出工具调用 统一处理——所有模型都走同一套工具执行逻辑
🧩 工具注册——工厂模式 + 条件加载
omp 有 32 个内置工具,但不是全部注册。下面是完整列表:
文件与搜索(7 个)
| 工具 | 功能 |
|---|---|
| read | 读取文件、目录、归档、SQLite、PDF、notebook、URL 及内部 :// 方案 |
| write | 创建或覆盖文件、归档条目、SQLite 行 |
| edit | Hashline 修补,使用内容哈希锚点与过期锚点恢复 |
| ast_edit | 通过 ast-grep 预览后执行结构化重写 |
| ast_grep | 基于 50+ tree-sitter 语法的结构化代码查询 |
| search | 正则搜索文件、glob 及内部 URL |
| find | glob 路径查找;需要内容匹配时用 search |
运行时(3 个)
| 工具 | 功能 |
|---|---|
| bash | 工作区 shell,可选 PTY 或后台任务派发 |
| eval | 持久化 Python/JS cell,共享 prelude 且支持工具重入 |
| ssh | 对配置主机执行一条远程命令 |
代码智能(2 个)
| 工具 | 功能 |
|---|---|
| lsp | 诊断、导航、符号、重命名、代码动作、原始请求 |
| debug | 驱动 DAP 会话——断点、步进、线程、栈、变量 |
协调(5 个)
| 工具 | 功能 |
|---|---|
| task | 并行派出子代理,可选工作区隔离 |
| irc | 本进程内活跃代理间的短文本通信 |
| todo | 会话 todo 列表的有序变更与阶段追踪 |
| job | 等待或取消后台任务 |
| ask | 交互式运行的结构化追问 |
外部能力(6 个)
| 工具 | 功能 |
|---|---|
| browser | Puppeteer 标签页,支持无头 Chromium 或 CDP 附接 |
| web_search | 跨配置提供商的一次查询,返回答案+引用 |
| github | GitHub CLI 操作——repo、PR、issues、代码搜索、Actions 运行监控 |
| generate_image | 通过 Gemini/GPT/xAI Grok 图像模型生成或编辑图像 |
| inspect_image | 视觉模型分析本地图像文件 |
| tts | xAI Grok Voice 文转语——5 种内置语音,WAV 或 MP3 |
记忆与状态(5 个)
| 工具 | 功能 |
|---|---|
| checkpoint | 标记对话状态以便后续折叠并生成报告 |
| rewind | 修剪探索性上下文,保留精炼报告 |
| retain | 向活跃 Hindsight bank 队列持久事实 |
| recall | 搜索 Hindsight bank 中的原始记忆 |
| reflect | 让 Hindsight 综合 bank 中的答案 |
杂项(2 个)
| 工具 | 功能 |
|---|---|
| resolve | 应用或丢弃排队中的预览动作 |
| search_tool_bm25 | BM25 搜索隐藏工具索引;会话中期激活匹配工具 |
其中 8 个为设置门控、默认关闭:github、inspect_image、tts、checkpoint、rewind、search_tool_bm25、retain、recall、reflect。需按项目配置手动开启。
每个工具分三个等级:
essential(核心): read、bash、edit、write、find、eval——始终可用 conditional(条件): SSH 工具只在有 SSH 配置时才注册,GitHub 工具只在有 Token 时 discovery(按需): web_search、search_tool_bm25 等,模型通过搜索工具名来激活
好处: 上下文只包含当前可用的工具,模型不会在几十个工具中挑花了眼。
💡 软工具要求——一种缓存优化
当 Agent 框架想让模型务必使用某个工具时,通常的做法是设 tool_choice 强制调用。但有一个问题:强制 tool_choice 会让 prompt 缓存失效。 很多 LLM API 的 prompt caching 在 tool_choice 改变时会重新计算缓存。
pi-agent 的做法是先软后硬:
注入一条 remind 消息,tool_choice 保持 auto → 缓存保留
模型没调 → 再来一次 → 缓存继续保留
超过阈值 → 强制 tool_choice(忍痛丢弃缓存)
最多试 3 次,不行就报错
🛡️ 错误恢复不只一种
Agent 跑着跑着挂掉是常态。工业级框架处理了多种异常:
| 故障 | 处理方式 |
|---|---|
| 输出被拦截 | 保留部分输出,发射可见错误事件 |
| 流中断 | 保留已完成工具,丢弃失败的 |
| 参数校验失败 | 宽容模式继续执行或发射错误 tool_result |
🏗️ 整体架构一览
omp (CLI 层)
工具工厂 (32 built-in + MCP + Extensions + Custom)
│
↓
pi-agent-core (Agent 层)
AgentLoop (双层循环: 外 steering + 内 LLM→Tools)
normalizeTools → 工具 schema 标准化
convertToLlm → AgentMessage → Message 格式转换
│
↓
pi-ai (LLM 层)
Provider 系统 (25+ providers, 文本协议编解码)
streamSimple → 流式 LLM 调用
EventStream → 逐事件处理
│
↓
Provider API (DeepSeek / Anthropic / OpenAI / Gemini …)
📌 从代码中学到的四条原则
抽象一层自己的工具协议——不直接依赖 LLM 提供商的函数调用格式,换模型基本不用改代码 错误恢复是第一要务——Agent 跑长任务一定会出各种错,分层恢复策略比一次重试更靠谱 上下文管理比推理更重要——工具描述太长就裁剪、频繁的 tool_choice 会破坏缓存、软要求比硬强制更省 token 可观测性要内建——每一种事件都是可订阅的,上层可以实时渲染
这就是从概念到代码之间的鸿沟——原理看似简单,但要让它稳定地跑在 25+ 个模型提供商、32 个内置工具、几十个 MCP 插件的组合爆炸中,必须有一套健壮的抽象层。
六、演化全景图
从思想源头到今天的生态,整条脉络如下:
1940s 控制论 → 反馈循环概念
↓
1970s Unix 管道 → 工具链哲学
↓
2022.10 ReAct 论文 → 推理+行动的学术奠基(ICLR 2023)
↓
2023.06 OpenAI Function Calling → 第一次商用 API 级标准
↓
2023.11 并行函数调用 → 单步多工具并行
↓
2023-2024 生态爆发 → LangChain / Claude / Gemini / Hermes
↓
2024.11 MCP 协议 → 工具接口标准化
↓
2025.04 A2A 协议 → Agent 互联标准
↓
2026.01 Headroom → Token 压缩层(60-95% fewer tokens, same answers)
↓
2026 pi-agent → Provider 系统(25+ providers)+ 32 内置工具
七、瓶颈在哪里
工具调用这个机制已经基本成熟了。当前真正的瓶颈不在工具本身,而在三个地方:
1️⃣ 格式稳定性
Runtime 是瞎的——你输出格式错了它就报错,它不会帮你修正。Agent 能不能稳定输出正确的 JSON,决定了这个系统靠不靠谱。这也是为什么 OpenAI 的 Structured Outputs 和 omp 的 Hashline 编辑本质上在解决同一个问题:让 AI 的输出格式不再随机波动。
(关于 Hashline 如何把编辑成功率从 6.7% 提到 68.3%,之前在 oh-my-pi 的文章里聊过)
2️⃣ 多步推理
简单问题(查天气)调一个工具就够了。复杂问题("帮我做一个竞品分析报告")可能需要调用十几个工具,每一步的推理质量都会影响最终结果。
一步错,步步错。
3️⃣ Token 效率
每调一次工具,工具的描述、参数、返回结果都要写进上下文。步数一多,上下文就炸了。这也是为什么 Headroom 之类的 Token 压缩工具会在 2026 年火起来——Chopra 在开源峰会上分享的数据显示,Headroom 累计已为用户节省约 70 万美元、释放超过 2000 亿 Token。
这个数字说明:Token 效率不是锦上添花,是刚需。
写在最后
AI 从来没有"碰到"过文件系统。它只是在文本空间里描述了一个动作,Runtime 替它实现了这个动作,再把结果翻译回文本喂给它。
这个循环就是"意识控制世界"的全部秘密。没有魔法,纯工程。
但正因为没有魔法,它才可靠,才可预测,才能被我们拿来踏踏实实地干活。
这就是 Tool Calling 的本质——一个接口加一个反馈环。
参考来源
Wiener (1948). Cybernetics. Salus (1994). A Quarter-Century of Unix. Yao et al. (2022). ReAct: Synergizing Reasoning and Acting in Language Models. ICLR 2023. Patil et al. (2023). Gorilla: Large Language Model Connected with Massive APIs. OpenAI (2023). Function Calling 官方文档. Anthropic (2024). Tool Use 官方文档 / MCP 协议. Google (2025). Agent-to-Agent (A2A) Protocol. Hermes Agent 官方文档. https://hermes-agent.nousresearch.com/docs[1] IT之家 (2026.6.20). Netflix 工程师开源项目 Headroom,可节省 60%-95% Token 消耗. InfoQ / Joab Jackson (2026.6). 砍掉 90% 冗余词元,省下 70 万美元:Netflix 开源工具狙击 AI 账单黑洞. andrew.ooo (2026). Headroom Review: 60-95% LLM Token Compression. earendil-works/pi 仓库. https://github.com/earendil-works/pi[2] can1357/oh-my-pi 仓库. https://github.com/can1357/oh-my-pi[3] headroomlabs-ai/headroom 仓库. https://github.com/headroomlabs-ai/headroom[4]
引用链接
[1]https://hermes-agent.nousresearch.com/docs
[2]https://github.com/earendil-works/pi
[3]https://github.com/can1357/oh-my-pi
[4]https://github.com/headroomlabs-ai/headroom
夜雨聆风