This chapter has not been translated yet — the Chinese original is shown below.

工具设计:让模型选择哪些动作,怎样说明这些动作

所属:第二部分 · 让它做对事的四块地基(第 5–8 章) 前置:第 5 章(本轮工作集、外置、检索)、第 4 章(工具调用协议)、第 3 章(路径自主性) 本章新概念:工具粒度、模型决策边界、工具合同、结果状态、结构化错误、代码沙箱


一、上一章的搜索工具,包办得太多

上一章先把注意力放在上下文管理上:搜索拿回候选材料,运行系统把原文放到窗口外,只把标题和短说明装进主循环;模型选中来源后,读取工具再按来源编号取回内容。这样,主循环不必在后续每一轮都带着三篇正文。

上一章留下了一个没有展开的问题:搜索这一步为什么要一次取得标题、链接和正文?如果模型只想先看候选标题,再决定读哪一篇,正文就不该在搜索阶段全部抓回。若把搜索、翻页、读取、保存和制卡全包进一个工具,模型更没有机会在中途选择。

另一个极端也不理想:把后端的每个步骤都注册成模型工具。搜索、翻页、读取网页、解析内容、保存文件和生成摘要都让模型逐个安排,调用次数会变多,模型还要记住每一步进行到哪里。可是这些固定步骤之间未必存在值得模型重新判断的新信息。

真正的问题不是「工具越细还是越粗」,而是:

任务进行到哪一步时,模型需要先看到结果,再决定下一步?

二、在哪一步把结果交回模型

程序内部可以有很多函数,但只有列在模型可用动作中、需要模型提出调用请求的函数,才是本章讨论的模型工具。

先把一项模型工具从头到尾展开:

运行系统提供工具合同 → 模型选择动作和参数 → 运行系统检查并执行 → 返回结果状态 → 模型继续、改参数、换工具或停止

这里的工具合同不是一段说明文字,而是这条链上的整套约定:调用前,名称、用途和参数帮助模型提出请求;执行时,程序检查参数、权限与确认条件;调用后,成功或失败的结果说明刚才发生了什么。

每当程序把中间结果交回模型,让它重新作一次选择,就形成一道模型决策边界。把一段工作切成多少个模型可见动作,就是本章所说的工具粒度

工具粒度只回答其中一个问题:这条链要在什么位置回到模型。切分时先用一条规则:

前一步的结果若需要模型结合任务语义,重新判断下一步做不做、做什么或对谁做,就把结果交回模型;若可靠的预写规则已经能决定后续动作,就由程序继续。

例如,搜索候选来源的工具 search_sources 先返回标题、日期、短片段和来源编号。模型要结合当前问题,判断哪些来源相关、是否足够权威、说法是否冲突;它可能直接回答,也可能再调用读取正文的工具 read_source。这个决定依赖任务语义,难以用一条可靠的固定规则代替,所以搜索与读取适合成为两个模型可见动作。

还要单独考虑权限和副作用。读取资料通常是只读动作,发送邮件或付款却会影响外部世界。高风险动作在执行前应有独立的权限检查或用户确认点,不能因为业务上经常相连就悄悄执行。

确认点不等于新的模型工具。程序可以在同一个工具执行过程中暂停并请求确认;只有模型也需要把两个动作分别选择时,才需要把它们拆成两个模型可见工具。工具切分解决模型何时重新判断,权限与确认解决动作何时获准执行。

三、同一个调研任务,可以切成不同粒度

仍以「先找候选来源,再选出值得读的材料,最后比较」为例:

工具切法 模型实际决定什么 能否先看标题再选正文 主要代价
搜索、翻页、读取、保存和摘要各自成为工具 决定每一个后端步骤 决策点和中间状态很多,容易漏步骤或重复调用
从搜索到摘要合成一个工具 只决定调研主题 不能 调用少,但中间选择被藏起来,可能读取过多材料
search_sourcesread_source 两个工具 先决定搜什么,再决定是否读取以及读哪一篇 多一次模型往返;边界切错仍可能漏读或过度读取

工具少,不代表模型一定更容易选对;工具细,也不代表模型一定更自由。关键是把真正需要模型结合任务重新判断的结果交给它。

对本章的调研任务,合适的切法是把搜索候选与读取正文分开。搜索工具给每个候选来源分配一个稳定的来源编号 source_id,读取工具再用这个编号指定正文:

工具 模型为什么需要看到这个动作 返回什么
search_sources(query) 模型要决定当前搜什么 候选来源的标题、日期、短片段、URL 和 source_id;不返回正文
read_source(source_id) 搜索结果会改变模型是否读取以及读取哪一篇 一个已选来源的正文、元数据和原文保存位置

搜索结果出现后,模型可能作出三种合理选择:

这些选择展示了搜索与读取为什么要分开:第一步的结果会改变第二步是否发生、对哪个来源发生,而且选择取决于当前任务的含义。

读取工具内部仍可以合并一串固定工作,例如检查来源编号、取得内容、保存原文和限制返回长度。模型不需要逐个安排这些步骤,因为它们不会产生新的任务选择。

「两个工具」只是这个调研例子的合适切法,不是通用数量。换一个任务,中间结果和权限边界不同,合适的工具数量也会不同。

四、工具设计不只有粗细

只问「工具应该粗还是细」,容易把几类不同问题混在一起。至少要分别看四件事:

设计问题 它决定什么 调研例子
模型选择粒度 哪些动作由模型分别决定是否执行、何时执行和对谁执行 搜索与读取是否分开
工具内部工作 一次调用里包含多少固定步骤 读取后是否自动保存和整理结果
返回结果的详细程度 哪些信息会回到模型当前上下文 只回标题、短片段,还是整篇正文
权限与能力范围 工具能接触什么数据,能否产生外部影响 只读资料、发邮件、付款

这四件事可以独立变化。一个只暴露少量动作的工具,权限范围仍可能很大;一个拆成多个动作的资料工具,也可以始终保持只读。一次调用内部完成很多固定步骤,不代表内部的成本和失败机会消失,只是由程序负责协调。

因此,「工具粒度对齐任务」不是要求后端服务也按同样方式拆开,而是:

只把模型真正需要选择的动作暴露成工具;固定步骤、校验和权限控制继续由程序承担。

代码沙箱改变的是模型怎样组合动作

函数工具让模型从预先定义的动作中选择;代码沙箱则让模型在受限环境中写一段程序,自行组合循环、过滤、排序和批处理。

当任务需要反复加工大量结构化结果时,代码沙箱可能比连续调用许多小工具更合适。但它也让模型生成的程序接触文件、网络和计算资源。名字里有「沙箱」不等于这些范围已经被限制,仍要检查它实际能访问什么、最多使用多少资源,以及哪些动作需要单独确认。

付款、删除生产数据、发送外部消息等敏感动作,通常不应藏进一段自由组合的代码里,而应保留为权限清楚、可以单独确认和记录的直接工具。

五、一道工具边界,需要完整的合同

粒度决定模型在哪些位置重新选择,合同决定它在每个位置能否作出有依据的选择。回到前面的完整动作链,一道工具边界至少要照顾调用前、执行时和调用后三个阶段。

调用前:说明帮助模型选择动作和参数

调用工具之前,模型看不到背后的实现。它主要能看到工具的名称、文字说明和参数。模型会把当前任务与这些信息进行匹配,再决定调用哪个工具以及传入什么内容。

因此,工具说明不只是给人看的注释。它也是模型作出调用选择时能够使用的条件。调用前至少要说清:

项目 最少要说清什么
名称 这是一个什么动作;通常使用动词加对象
作用 它做什么,也明确不做什么
使用时机 什么情况下使用,什么情况下不使用
参数 每个参数表示什么、使用什么格式、从哪里取得
成功结果 会返回什么,以及结果的大致范围
副作用 是否写入数据、对外发送或触发真实动作

对本章的两个工具,只写一句「搜索资料」是不够的。模型无法知道它搜索什么、拿回标题还是正文,也无法区分什么时候应该搜索,什么时候应该读取已有来源。

search_sources 的说明可以写成:

需要先查看候选来源的标题、日期和短片段,再决定读哪一篇时使用。输入查询词;返回候选来源和稳定的 source_id,不返回正文。

当任务只要求先看候选来源时,模型可以把任务需求与「返回标题和短片段,不返回正文」匹配起来,选择 search_sources。当它随后需要核对正文时,又可以从搜索结果中取出 source_id,调用 read_source。工具说明由此把任务需求、工具选择和参数来源连接起来。

说明不能保证模型每次都选对,但它给模型提供了可判断的依据。只写一个模糊名称,相当于要求模型猜。

执行时:程序检查请求能否真正执行

工具说明负责告诉模型「应该怎样选」,真正的限制仍由程序负责。说明里写着「只读」,不会自动阻止底层实现删除文件;程序必须真的限制它只能读取。

同样,参数是否存在、当前用户是否有权使用、发送前是否需要确认,都要在模型之外检查。文字说明能影响模型提出什么请求,不能决定请求一定获准执行。

调用后:结果状态告诉模型下一步能做什么

工具执行之后,模型只能根据返回结果决定下一步。因此,结果不能只分成「成功」和「出错」,至少要让模型区分:

这些对下一步有不同含义的类别,本章称为结果状态。把失败状态使用稳定名称返回,并说明模型接下来可以改参数、换来源、请求权限还是停止,通常称为结构化错误。结构化错误只是失败结果的表达方式,不是全部结果状态。重点不在某一种 JSON 写法,而在于不同结果支持的下一步不同。

例如,搜索没有结果说明工具已经正常完成,模型可以换一个查询或如实报告没有命中;搜索服务暂时不可用则说明这次执行没有完成,模型要判断是否等待、重试或换工具。付款超时更不能直接再付一次,因为超时并不能证明第一次付款没有发生。

这三个阶段缺一不可。只有调用前说明,没有执行检查,危险请求仍可能被执行;只有输入检查,没有清楚的结果状态,模型就不知道该继续、修正还是停止。

六、怎样验证工具切分是否更好

一次运行顺利,只能说明这一次走通了,不能证明这组工具适合所有任务。可以准备几类任务,观察它是否作出合理选择:

测试任务 合理行为
只要候选来源清单 搜索后直接回答,不读取正文
只核对一个原始来源 搜索后只读取最相关的一篇
比较两个互相矛盾的说法 读取两个来源,并保留冲突和限定
使用不存在的 source_id 得到明确错误,再回到搜索结果重新选择
搜索成功但没有结果 明确没有命中,不伪装成系统故障
读取暂时失败 根据任务需要决定等待、重试或换来源

评测时至少检查五件事:

  1. 最终回答是否完成任务,引用的材料是否真的支持结论;
  2. 模型是否选择了合适的工具和参数;
  3. 是否出现漏读、过度读取、重复调用或无意义循环;
  4. 失败后能否根据结果安全恢复;
  5. 是否越过权限边界,以及调用次数、等待时间和成本是否可以接受。

不要把「严格按照一条标准轨迹运行」直接当成正确。只要任务结果、权限边界和恢复方式都正确,同一个任务可以有多条合理路径;只有当顺序本身影响安全或结果时,轨迹才需要固定。

七、三条结论

一,工具粒度由模型需要作任务判断的位置决定,不由后端接口数量决定。 中间结果需要模型结合任务语义重新选择,就把结果交回模型;可靠的预写规则能继续处理,就留在程序内部。

二,完整工具合同贯穿调用前、执行时和调用后。 说明帮助模型选择动作和参数;运行系统负责权限、确认、校验与真实执行;结果状态再告诉模型接下来能做什么。

三,结构化错误只是结果状态的一部分。 成功有数据、成功无数据、参数无效、权限不足、暂时失败和结果未知,会导向不同的下一步,不能压成一个笼统的「出错」。

八、这一章引出的问题

现在,Agent 可以先找候选来源,再按需要读取正文。但每次新任务开始,它仍可能重新搜索;磁盘上即使已经保存了很多旧材料,模型也不会自动知道该用哪一份。

下一章会继续解决这个问题:面对磁盘上的大量旧材料,怎样找回当前任务真正需要的部分,并把它交给模型使用。

九、自检题

  1. 后端有搜索、翻页、读取、解析和保存五个步骤,为什么不一定要注册成五个模型工具?
  2. 搜索结果会改变模型是否读取正文以及读取哪一篇。搜索与读取应该合并还是拆开?为什么?
  3. 两个动作总是连续发生,但第二个动作是发送外部邮件,需要用户确认。应该保留什么边界?这是否一定意味着增加一个模型工具?
  4. search_sources 只写「搜索资料」,为什么不足以帮助模型选择工具和参数?
  5. 搜索成功但没有结果,与搜索服务暂时不可用,为什么要返回不同状态?
  6. 付款工具超时后,为什么不能立即再付一次?
  7. 工具说明写着「只读」,哪一层才能真正阻止它删除文件?
  8. 一个环境名叫「代码沙箱」,为什么不能只凭名称判断它安全?

十、参考答案

先自己答完再往下看。

  1. 模型不需要安排所有固定步骤。 如果翻页、读取、解析和保存总是按固定规则发生,中间结果也不会改变模型选择,就应留在工具内部;只有真正需要重新判断的动作才值得交给模型。
  2. 拆开。 模型要结合当前问题,判断候选来源是否相关、权威或互相冲突;标题、日期和短片段会改变是否读取以及读取对象,所以这里存在一道真实的模型决策边界。
  3. 至少要保留独立的执行确认点,但不一定增加模型工具。 程序可以在发送前暂停并请求用户确认;只有模型也需要分别选择「准备内容」与「发送」时,才需要拆成两个模型可见动作。
  4. 它没有提供匹配条件。 模型不知道工具搜索什么、何时使用、返回标题还是正文,也不知道参数从哪里取得,因此只能猜。
  5. 两种状态支持的下一步不同。 空结果说明调用正常完成,模型可以换查询或如实报告;服务不可用说明执行失败,模型要决定等待、重试或换工具。
  6. 超时不证明第一次付款没有发生。 在确认第一次执行结果之前再次付款,可能造成重复交易。
  7. 程序的权限和实现边界。 文字说明只能影响模型怎样提出请求,不能限制底层代码真正能做什么。
  8. 名称不是隔离机制。 要检查它实际能访问哪些文件、网络和资源,哪些敏感动作需要另行确认,以及执行过程能否被记录和检查。

上一章:第 5 章 上下文:Agent 真正的瓶颈 下一章:第 7 章 记忆与检索:让旧信息在需要时重新进入上下文