上一篇讲的是 Tool Registry。
如果说第一篇是在确定 Agent 的最小闭环,第二篇就是在给“行动”这件事建立协议。工具不能只是散落在 Agent Loop 里的几个函数。它们需要被描述、注册、调度、记录和限制,也需要把执行结果变成 observation,重新交还给模型。
但协议本身还不会让 Agent 变得有用。
一个系统可以有很干净的 Tool Registry,可以有统一的风险等级,可以有稳定的执行入口,但如果它只能写一点记忆、看一下时间,它仍然只是一个能验证闭环的原型。
从这一篇开始,工具层要往真实世界迈一步。
只有 memory 和 time 还不够
早期 Agent 原型里,记忆和时间通常是最先出现的两个工具。
它们很适合作为最小闭环的验证工具。记忆工具能证明 Agent 可以把一次行动沉淀到持久状态里。时间工具能证明 Agent 可以从系统环境里取到外部信息。它们都简单、稳定、低风险,也很容易测试。
但它们不能解决一个更现实的问题:用户真正交给 Agent 的工作,往往不在对话本身里。
项目说明在文件里,历史决策在文档里,错误日志在本地目录里,参考资料在网页上。用户问“这个项目现在做到哪一步了”“帮我看一下这份资料”“这个网页里的信息能不能总结一下”,这些问题都要求 Agent 接触对话之外的材料。
如果 Agent 只能依赖用户把所有上下文复制进聊天框,它就很难成为工作系统的一部分。它更像一个聪明的文本接口,而不是一个能参与环境的 Agent。
所以,Tool Registry 之后,第一批真实工具应该解决的问题不是“让 Agent 变得炫”,而是“让 Agent 能接触资料”。
第一批工具应该克制
工具一多,系统就容易失焦。
最吸引人的能力往往是高风险能力:执行 shell 命令,控制浏览器,修改文件,调用外部服务,甚至自动提交代码。这些能力当然重要,但它们不一定适合作为第一批真实工具。
因为第一批工具承担的任务不是展示上限,而是建立习惯。
它们要证明几个更基础的事情:
- 工具描述能不能让模型正确理解能力边界
- 工具结果能不能稳定地回到 observation
- 工具失败会不会让 Agent Loop 崩掉
- 工具有没有默认的读取量和搜索范围限制
- 工具的风险等级是不是从第一天就能表达出来
在这个阶段,最合适的能力通常是文件资料和网页资料。
文件资料让 Agent 理解本地项目和用户材料。网页资料让 Agent 获取对话之外的公开文本。它们都足够常用,也足够有风险,正好能逼着工具层从 demo 走向工程系统。
文件工具不是“读取文件”这么简单
让 Agent 读取文件,听起来像是一个很小的功能。
给它一个路径,读出内容,再把内容交给模型。好像这就结束了。
但真正的问题从这里才开始。
第一个问题是路径。
路径不是普通字符串。它指向本地系统里的资源。路径可能不存在,可能是目录,可能是二进制文件,也可能是用户根本不希望 Agent 读取的地方。第一版可以暂时不做完整路径白名单,但至少不能把缺失路径默默解释成当前目录,也不能把所有路径错误都变成同一种失败。
第二个问题是大小。
文件可能很大。目录可能很多层。一次搜索可能扫到成千上万个文件。如果工具没有读取上限、目录条目上限和搜索扫描上限,模型只要给出一个过大的参数,就可能让工具占用大量内存或长时间运行。
所以文件工具必须从第一版就承认:读取不是无限的,搜索也不是无限的。
第三个问题是文本边界。
Agent 需要的是可读文本,不是任意字节流。二进制内容不应该被乱码形式塞回模型。另一方面,文本截断也要小心。如果刚好截断在多字节字符中间,一个有效的文本文件不应该被误判成非法内容。
这些细节看起来琐碎,但它们决定了工具是否真的可用。
一个可用的文件工具,不只是能读出内容,而是能在路径不对、内容不对、范围太大、结果被截断时,都给出模型能理解的 observation。
搜索能力尤其需要边界
文件搜索比文件读取更容易被低估。
读取一个文件,至少目标是明确的。搜索一个目录时,目标就变成了一片空间。
如果搜索只限制返回多少结果,却不限制扫描多少文件,那么在没有命中的情况下,它仍然可能遍历完整个目录。对用户来说,它看起来只是“搜索一下”;对系统来说,它可能是在读一个巨大的项目树。
所以搜索需要两个边界。
一个边界是返回结果数量。模型不需要一次收到几千个命中结果,过长的 observation 只会污染上下文。
另一个边界是扫描范围。工具应该能在检查到一定数量的文件后停下来,并明确告诉模型:这次搜索被截断了。
这不是为了让工具变弱,而是为了让工具可预测。
Agent 最怕的不是工具失败,而是工具在看似普通的请求里做了不可控的事情。
网页工具也应该从文本开始
网页能力也很容易被过度设计。
一提到网页,很多人会直接想到真实浏览器:打开页面、执行 JavaScript、点击按钮、输入表单、截图、下载文件。
这些能力确实强,但它们不是第一版最需要的。
第一版网页工具更适合从文本抓取开始。给定一个 HTTP 或 HTTPS 地址,拿到状态码、最终地址、页面标题和正文文本。HTML 页面先提取可读内容,纯文本响应就直接返回。这样 Agent 就可以阅读公开资料,而不需要一上来承担浏览器自动化的复杂性。
这里同样有边界。
只支持 HTTP 和 HTTPS,是协议边界。
正文有长度上限,是资源边界。
HTML 要去掉脚本、样式和模板噪声,是内容边界。
redirect 也必须遵守协议限制,是执行边界。
这些限制会让工具看起来没那么强,但它们让第一版网页能力更稳定。它不是一个浏览器机器人,而是一个可控的网页文本读取器。
工具失败也是信息
工具设计里,一个很重要的转变是:失败不是异常,失败也是 observation。
文件不存在,是信息。
路径不是文件,是信息。
内容不是可读文本,是信息。
网页返回 404,是信息。
URL 不合法,是信息。
工具被策略拒绝,也是信息。
这些失败不应该直接把 Agent Loop 打断。它们应该以统一格式回到模型,让模型知道刚才发生了什么,然后决定下一步怎么做。
这也是 ToolResult 的价值。
它不是普通函数返回值,而是工具世界和模型世界之间的一层翻译。成功结果要可读,失败结果也要可读。否则 Agent 只会在 happy path 上表现得像 Agent,一旦遇到真实环境里的噪声,就退化成报错脚本。
为什么测试要覆盖边界
常用工具最容易只测 happy path。
文件能读出来,网页能抓下来,测试就过了。
但对 Agent 来说,真正危险的地方往往不在 happy path。
更应该测试的是:
- 路径缺失时会不会误读当前目录
- 二进制内容会不会被塞给模型
- 大文件会不会被截断
- 目录列表会不会无限展开
- 搜索会不会无限扫描
- 写入能力会不会被默认拒绝
- 网页错误状态会不会被包装成 observation
- 非 HTTP 协议会不会被拒绝
- redirect 会不会绕过协议边界
这些测试不是为了追求覆盖率数字,而是为了固定工具的边界。
工具边界一旦没有测试,后续重构时很容易被无意间放宽。尤其是 Agent 工具,很多调用来自模型生成的参数,而模型生成的参数天然不稳定。测试就是在替系统记住:哪些事情永远不能默默发生。
可用工具等于能力加边界
到这里,工具层开始从协议走向能力。
Agent 不再只能记一点事实、看一下时间。它开始能接触本地资料和网页资料,能把对话之外的信息带回上下文,能让模型基于新的 observation 继续判断。
但这一步最重要的不是工具数量。
真正重要的是,第一批工具有没有形成正确的工程习惯:
可用工具 = 能力 + 边界
只有能力,没有边界,工具会变成风险入口。
只有边界,没有能力,Agent 仍然停留在原型阶段。
一个能长期演进的 Agent 系统,需要在这两者之间找到第一版平衡:工具足够有用,可以接触真实资料;工具也足够克制,不会因为一次普通调用就失控。
这一篇的结论
Tool Registry 解决的是工具调用的协议问题。
常用工具解决的是 Agent 接触真实材料的问题。
文件资料和网页资料,是很适合作为第一批真实工具的能力。它们离用户工作足够近,又能逼着系统认真处理边界:路径、大小、搜索范围、协议、redirect、失败 observation 和风险等级。
从这一层开始,Agent 才慢慢从“会回答问题”走向“能参与工作环境”。
Discussion