o. OceanCT / notes
← Agent 专栏
/ LEARNING NOTES

Agent学习笔记(四)工具调用如何学会,又如何验收

先讲 Toolformer、Gorilla 的工具学习与调用生成方法,再介绍 BFCL benchmark 的题目与判分,区分方法设计和能力评测。

我们已经有了调研计划,也知道怎样保存资料,但助手最后还得做一些具体的事:搜索论文、读取 PDF、计算压缩率、把报告写入文件。原来的调研者会根据需要打开不同软件;假设把这些工作交给 Agent,就要把能力整理成它能理解和调用的接口。

最初可能只有两个工具,随着任务增加,列表里会出现多个相似的搜索接口、不同版本的文档服务,以及参数各不相同的计算工具。模型既要判断是否需要调用,也要选对接口、填写参数,并理解返回结果。Toolformer 和 Gorilla 研究怎样获得或改善工具调用能力;BFCL 是检验这些能力的 benchmark(评测基准)。我们先讲训练与生成方法,再单独看评测题目和判分规则。

Toolformer:从哪里获得“什么时候用工具”的训练数据

假设助手正在写压缩率,手上有原始字节数和压缩后字节数。它可以自己生成一个数字,也可以调用计算器。我们希望它在需要精确计算时选择后者,但如果每个训练样本都靠人手工标注调用位置和参数,成本会很高。

2023 年的 Toolformer 用少量工具示例引导模型,在普通文本中采样候选 API 调用。随后执行候选,检查调用结果是否降低后续文本的预测损失,把有帮助的调用保留下来,形成增强数据,再微调模型。论文研究的工具包括计算器、问答、搜索、翻译和日历。

Toolformer 原论文 Figure 2:采样、执行、筛选 API 调用

图源:Schick 等,Toolformer: Language Models Can Teach Themselves to Use Tools,arXiv v1,第 2 页 Figure 2。

图里值得留意的是第三步:调用能成功返回,只说明工具执行了;训练数据还要检查返回的信息是否帮助模型预测后续内容。论文把“得到调用及结果”的损失,与“不调用”和“有调用但不给结果”的基线比较,达到筛选阈值才保留。

这里确实有训练过程。把工具描述写进 prompt,让已有模型现场决定调用,与 Toolformer 的数据构造和微调属于不同环节。迁移到我们的计算任务时,也还要另做业务验证:模型可能准确算出了一个比值,却把“压缩后占比”和“节省比例”混在一起。训练信号无法直接替代字段语义和最终结果检查。

Gorilla:怎样把一个需求变成具体的 API 调用

假设用户说:“我想识别照片里的动物,模型要小一点。”我们最终需要交给程序执行的,却可能是一个包含仓库名、模型名和选项的函数调用。这里至少有两次转换:先理解任务需要什么能力,再从可用接口中选择具体实现、写出参数。模型可能知道什么是图像分类,却记错模型仓库,或者编出一个看似合理的 API。

2023 年的 Gorilla 就研究这段转换。它的核心设想是:用真实 API 文档构造专门的指令训练数据,让模型学习“需求对应哪种调用”;再让训练输入包含参考文档,使模型能够依据推理时提供的文档调整调用。这里的 Gorilla 是基于 LLaMA-7B 微调的模型及相应方法,APIBench 是作者构建的数据集。下面按 Gorilla 的 2023 年 v1 原论文 展开。

训练样本从哪里来

作者收集了 Hugging Face、Torch Hub 和 TensorFlow Hub 的 API 资料。它们主要涉及机器学习模型的加载与使用,例如图像分类、目标检测和文本生成。文档被整理成结构化记录,包含接口名称、功能、调用形式、参数、环境要求、示例代码和性能信息。这样,训练目标就有真实接口作为依据。

但文档通常从“这个接口有什么用”出发,用户从“我想做什么”出发。为了补上两者之间的对应关系,作者让 GPT-4 阅读文档,生成可能需要这个 API 的自然语言需求,并要求需求中不要直接透露 API 名称。否则,“请调用某某接口”会把选工具这一步的答案写进题目。

这一步采用 Self-Instruct 的方式:先为三个模型平台各手写 6 个“需求—API”示例,再从相应平台的示例中取 3 个,连同新文档一起提供给 GPT-4。论文报告为 1,645 条 API 记录各生成 10 组指令与调用配对。这里的 GPT-4 用来生成训练材料,部署 Gorilla 时无需再让 GPT-4 替它作答。数据构造依据:原论文第 3.1 节。

可以把一条样本理解成下面的关系。这个例子用于解释样本结构,调用形式取自论文讨论的 Torch Hub 接口:

1
2
输入需求:给我一个可以做图像分类的 DenseNet-121 模型。
目标调用:torch.hub.load('pytorch/vision', 'densenet121', pretrained=True)

其中 torch.hub.load 是加载函数,pytorch/vision 指明模型仓库,densenet121 指明模型。只把函数名写对还不够,后两个参数共同决定加载什么。论文还讨论带约束的需求,例如“参数量小于 10M,ImageNet 准确率至少 70%”。这种题需要同时核对功能和性能条件,找到一个能加载的模型只是其中一步。

微调究竟教了什么,检索又发生在哪里

作者把每组需求与调用改成一轮用户—助手对话,对 LLaMA-7B 做标准指令微调。训练时,输入是需求,目标输出是参考 API 调用;模型通过预测目标输出更新权重。这里没有 Toolformer 那种“执行候选调用,再按后续文本损失筛选”的步骤,两者的数据来源和训练信号不同。

Gorilla 还比较了带参考文档的训练方式。输入变为“用户需求 + 参考 API 文档”,目标仍然是正确调用。论文称它为 retriever-aware training,可以理解为让模型在训练中就练习根据文档完成调用,而不是只背需求与接口的对应关系。论文实验也使用正确参考文档进行训练,用来检验文档条件下的学习效果。

到了实际推理时,文档检索在生成调用之前发生:

1
2
3
4
5
用户需求
→ 检索器在 API 文档库中寻找相关文档
→ 把需求与检索文档一起放入 Gorilla 的输入
→ Gorilla 生成函数名及参数
→ 执行程序接收这个调用

论文比较了 BM25 和 GPT-Index 检索设置。BM25 主要依据查询与文档的词项匹配及权重排序;这里知道它负责选出参考文档,就能理解后续流程。Gorilla 也支持不检索、直接根据需求生成调用的模式,因此不能把“Gorilla”与“一个文档检索器”画等号。训练与推理依据:原论文第 3.2 节。

从 RAG 的角度看,带检索的 Gorilla 符合“先检索资料,再据此生成”的结构;它检索的是 API 文档,生成的是调用。Gorilla 在这个结构上研究了专门的数据构造和微调方式。普通模型临时收到一份文档,也可以尝试写调用;这里关心的是经过相应训练后,模型是否更能利用文档、遵守接口约束。

这也解释了文档更新为什么有用。假设我们的文档服务把 write_document(path, text) 改为 create_document(folder_id, content),检索到的新文档能向模型提供新的函数名和参数。模型需要依据它调整输出;文档不会自动修改权重,也不会保证运行端已经同步升级。原论文展示的是模型名称和模型仓库等 API 信息变化的案例,不能据此推断它能处理任意复杂的接口迁移。

作者怎样判断它真的改进了

首先要区分几种结果。调用符合需求是一种;调用了真实存在但不适合任务的 API 是另一种;生成了数据库中找不到的 API,又是另一种。最后一类被论文记为 hallucination,也就是这个基准定义下的虚构接口。选择错误的真实接口归入调用错误,不能与虚构接口混为同一个指标。

作者用 AST(抽象语法树) 分析生成代码。它把代码拆成函数及参数等结构,因此不会只因空格或排版不同就判错。例如前面的调用可以拆出 torch.hub.load、仓库和模型参数,再与参考 API 的关键节点比较。论文会指定需要匹配哪些参数;其示例中检查仓库和模型,未要求可选的 pretrained=True 节点完全匹配。因此,这个分数依赖基准选定的检查条件,不能解释成执行环境中所有细节都已验证。判分依据:原论文第 3.3 节及 Figure 4。

实验把 Gorilla 与其他模型放到不同文档条件下比较:不提供文档、使用实际检索器,以及由 Oracle 直接提供正确参考文档。Oracle 是实验中的理想条件,用来观察“如果资料一定找对,生成端能做到什么”;它不是现实部署中已经解决的检索能力。

这组比较支持了一个有条件的结论:专门的 API 指令微调能改善该基准中的调用生成,结合文档的训练有助于模型使用参考资料。与此同时,检索器找到错误文档时,加入检索反而可能降低准确率。论文还展示了依据变更后的文档调整输出的案例。这里有训练、检索质量和文档变化三个变量,不能把结果缩成“接上 RAG 就更准”。实验依据:原论文第 4 节、Table 1–2 与 Figure 6。

所以,Gorilla 的贡献可以具体落在 APIBench 数据、面向 API 调用的指令微调、利用参考文档的训练方式,以及相应的结构化评测上。它把“需求怎样对上一个真实 API”变成可训练、可比较的问题。原论文主要考察单次 API 调用生成,连续查资料、执行失败后恢复、权限检查等运行机制仍要由 Agent 系统另行实现。

BFCL Benchmark:怎样评价工具调用能力

Gorilla 让我们看到调用生成可以怎样训练,接下来还要回答:换一个模型后,怎样判断它的工具能力够不够?只给几个演示案例,很容易漏掉“工具名称相似”“一次需要查两处”“缺参数就该停下”这样的情况。

BFCL 是工具调用能力的 benchmark(评测基准),全称是 Berkeley Function Calling Leaderboard。它提供测试题、工具描述、参考答案和判分程序,用同一套条件比较模型。论文的主要贡献是评测任务与判分方法的设计,以及由实验发现的能力缺口。

学习它,可以知道系统应该接受哪些测试、怎样才算完成任务。它没有提出一套供应用照着实现的 Agent 架构;下文的 AST 匹配、执行测试、状态与响应检查,都属于评测端的判分机制,不是要求每个 Agent 加入的运行组件。这里讨论 ICML 2025 论文中的 BFCL,不把持续更新的在线榜单与论文中的固定实验混在一起。

一道题给模型什么

单轮题通常给出用户需求和一组可用函数文档,文档说明名称、用途和参数。模型需要输出调用,或者判断当前不应调用。评测端另持有标准答案,模型看不到这部分。

这些函数和需求来自整理过的代码仓库、API 资料及用户贡献。作者提取函数签名和参数说明,整理题目,并用检查和人工审核确认题目与工具描述相符。多轮题还需要初始环境状态与参考执行轨迹,因此作者构建了可检查内部状态的 API 环境,并验证每轮任务与调用路径的对应关系。数据构造依据:原论文第 3 节。

多工具与并行调用,究竟有什么区别

这里有两个独立维度:给模型多少个候选工具,以及任务需要多少次调用。下面用假设的论文调研工具举例,说明论文附录 C 中的分类;这些例子不是原始测试题。

类别 提供的工具与用户要求 预期行为
Simple 只有 read_pdf,要求读取论文 A 调用一次 read_pdf(A)
Multiple 有 read_pdf、search_papers、save_report,要求读取已给出地址的论文 A 选中 read_pdf,调用一次
Parallel 只有 read_pdf,要求读取已给出地址的论文 A、B 生成 read_pdf(A) 和 read_pdf(B) 两个独立调用
Parallel Multiple 有多个工具,要求读取论文 A,同时搜索一个独立主题 生成 read_pdf(A) 与 search_papers(query)

Multiple 测的是从候选中选择工具,有多个候选不意味着都要调用。Parallel 测的是能否完整给出多次独立调用;同一个函数使用不同参数,也属于多次调用。Parallel Multiple 同时包含工具选择和多次调用。分类依据:原论文附录 C.2。

如果要求“先搜索论文,再读取搜索到的第一篇”,第二步的参数要等第一步返回才能知道,属于有依赖的串行过程。并行题中两个独立调用的列出顺序通常不影响正确性;把有依赖的步骤提前一起生成,则可能使用尚不存在的结果。评测中的并行调用能力,也不等于宿主已经实现了线程调度或真实的并发加速。

为什么“不调用”也要专门出题

假设现有工具只有论文搜索,用户要求“给同事发送邮件”。调用搜索工具无法完成目标,编一个 send_email 也不成立。Irrelevance 类题预期没有函数调用,用来检查模型是否识别能力不匹配。与之对应,Relevance 类题需要调用,避免模型靠一律拒绝拿到看似稳妥的结果。

另一个情况是工具存在,但必填参数缺失。比如 save_report(folder_id, content) 需要文件夹 ID,用户只说“帮我保存”,上下文和系统信息也无法补齐目标位置。正确动作是先询问,收到必要信息再调用。BFCL 的多轮 Missing Parameters 类专门检查这个过程;缺工具与缺参数需要分别判断,修复办法也不同。

多轮交互增加了哪些难点

单轮题判断这次需求对应什么调用,多轮题还要维护前面已经发生的事。这里的“一轮”由新的用户消息开始,一轮内部可以包含多次模型与工具之间的交互;不能把多轮直接理解为调用了多个函数。

假设用户先说“在项目 A 下新建调研文件夹”,工具返回 folder_id="f17";下一轮用户说“把刚才那份报告放进去”。模型需要沿用 f17,而不是另猜一个位置。随后用户要求改名,就必须依据已经创建的文件继续操作。

论文把多轮任务分成四类。Base 提供完成任务所需的信息,检查正常的状态延续。Missing Parameters 在某轮缺少必要参数,要求先自然语言追问,后续用户消息补齐参数。Missing Functions 在某轮缺少能完成任务的工具,检查模型能否识别能力缺口并回应、澄清。Long Context 增加用户消息或工具返回的长度,考察关键信息在长上下文中是否仍能被正确使用。它们分别暴露记错状态、猜参数、虚构能力和丢失上下文的问题。多轮定义依据:原论文第 3.3 节与附录 D。

评测端怎样用 AST 匹配批改调用

AST 是抽象语法树。评测程序把模型生成的代码解析成函数名、参数名和参数值等结构,再与预先标注的参考答案比较;这一判分方式可以不实际执行工具。

假设预期调用是:

1
save_report(folder_id="A", content="报告正文")

模型输出 save_report(content="报告正文", folder_id="A"),关键字参数顺序变化通常不影响意义。AST 判分会解析函数名和参数结构,再检查函数是否匹配、各参数是否属于预先标注的可接受答案集合,因此不必逐字符照抄参考答案。若输出的 folder_id 为 "B",即使语法完全合法也应失败。

这种方法的好处是不用为每个函数都准备运行服务,就能批量检查大量调用。代价是它只能覆盖标注和匹配规则定义的正确性:标注遗漏的等价答案可能不被接受,运行时权限或服务故障也不会因为 AST 通过就消失。论文比较了 AST 与执行判分得到的分数和排序,观察到较强相关性,以此支持 AST 作为可扩展的离线指标。AST 方法与验证依据:原论文第 4.1、4.3 节。

能执行的题,怎样检查执行结果

执行判分会实际运行模型生成的函数调用,再对比预期结果。论文根据输出性质采用不同规则:确定性结果直接比较;对会随时间变化的结果,同时运行参考调用和模型调用后比较;对某些嵌套列表或字典则检查列表长度、字典键等结构。最后一种检查较弱,不能把结构相符理解为每个值都已证明正确。执行判分依据:原论文第 4.2 节。

不同测试集使用不同检查方式:论文的单轮集合包含 AST 和执行评测,用户贡献的 Live 集合主要使用 AST,多轮集合则结合状态与响应检查。它们没有统一成“每题都把所有检查跑一遍”的流程。

多轮为什么同时检查状态与响应

回到保存报告的例子。状态检查(state-based evaluation) 在每轮工具调用结束后,核对系统状态是否与参考状态一致:文件是否出现、所属文件夹是否正确、名称是否已更新。不同调用路径可能到达同一个正确状态,所以它不要求逐字复制参考调用序列。

但只读操作会留下漏洞。用户要求查询一条记录,模型没有查询、凭空答了一个数字,数据库同样保持不变。只看状态,无法辨别它是否真的做过查询。

因此还有 响应检查(response-based evaluation),检查完成该轮任务所需的调用及执行结果路径。论文举例先通过城市取得邮编,再根据邮编查天气;检查要能确认必要的只读函数确实被调用。这里的“响应”不能简单理解为让另一个模型评价最终文字好不好听,它与必要的工具执行路径有关。

BFCL 多轮题要求每一轮的状态与响应检查都通过,整条题目才算正确。假设第一轮把报告写错文件夹,第二轮又挪回来,只核对整段对话最后的状态会漏掉前一轮错误;逐轮检查能保留这次失败。状态与响应判分依据:原论文第 4.4 节。

这些分数能说明什么

同一类别的准确率可以理解成“通过该类别检查的题目数 / 该类别题目总数”。对于上述多轮题,分母中的一题是一整段对话;它与把每个单独调用当一题的准确率含义不同。比较模型时要同时看类别、判分方式和测试版本,不能只摘一个总分。

论文还加入 Agentic 任务:网页搜索需要检索并读取页面;记忆任务在对话后保存记忆,再在清空聊天历史、保留记忆的条件下提问,检查检索和更新;SQL 任务提供结构化操作接口,再将调用转换为查询。这让评测进一步涉及工具结果利用与跨步骤的信息保持。Agentic 任务依据:原论文第 3.4 节。

作者观察到,单轮调用表现较好的模型,在多轮与 Agentic 任务上仍有明显改进空间,尤其是记忆任务。BFCL 的贡献也由此更清楚:它把工具能力拆成有区分度的任务,并提供可重复的判分机制,让“能写一次调用”与“能把一段交互做对”分别接受检验。实验结果依据:原论文第 5 节。

迁移到我们的调研助手时,就应该保留分项失败记录:是选错了工具、参数不对、该询问时直接调用,还是前面的创建成功了,后面却忘了返回的 ID。每种错误对应不同修复方向,聚合成一个总分之后仍应能追溯。

方法与 Benchmark 分别能帮助我们做什么

Toolformer 主要研究工具使用训练信号,Gorilla 研究 API 文档与调用生成,BFCL 研究怎样系统地测量调用能力。我们可以按这个顺序学习,但它们处理的是不同问题,不能拿一个训练方案与一个评测集直接比较“谁更强”。

工作及类型 核心对象 可以借鉴的内容 仍需另行解决
Toolformer:训练方法 带工具调用的训练数据 采样、执行、按损失筛选,再微调 业务正确性、权限、真实任务验收
Gorilla:模型与训练方法,配套 APIBench 数据 API 文档与调用生成 检索文档、结合文档训练和生成 候选漏召回、版本一致性、运行控制
BFCL:benchmark 测试题、参考答案与判分程序 结构检查、执行检查、多轮状态与响应检查 本项目的约束、代价和故障场景

如果已有模型能较好地生成调用,我们可以先做清晰的 schema、工具检索和执行检查。只有发现稳定的能力缺口,并且有足够训练数据时,再考虑微调。这个判断需要错误分布:是接口没找到、参数没理解,还是工具本身经常失败?

面试追问:从调用意图到真实执行

“Function Calling 到底是谁在调用函数?” 模型输出结构化调用意图,宿主根据注册的工具进行解析、校验和执行,再把真实结果交回模型。把文本直接当可执行代码,会把接口选择、权限和执行混在一起。在我们的系统中,工具名必须能映射到已注册实现,参数通过 schema 之后还要检查业务约束。

“MCP 和 Function Calling 有什么区别?” Function Calling 讨论模型如何表达调用意图;MCP 定义应用与工具或上下文服务之间的交互协议。一个宿主可以把 MCP 服务提供的工具描述交给模型,再通过协议发起真实调用。协议统一了连接和交互方式,具体怎样选择模型、管理上下文,仍由应用决定。参见 MCP 官方架构说明。

“工具 schema 怎样设计?” 以保存报告为例,应说清 folder_id 指哪一类文件夹,content 是正文还是完整文档,是否会覆盖已有文件,以及缺参数时怎么处理。用枚举或明确类型限制参数范围;工具描述解释用途和关键前置条件。若两个工具仅名称不同、描述几乎一样,模型就缺少区分依据。

“什么时候可以并行调用?” 两篇公开论文的独立读取可以并行;创建文件夹和往它里面写报告有依赖,需要先拿到创建结果。两个操作同时写同一份报告,也要处理冲突。模型一次输出多个调用只代表提出了多个动作,调度器还需检查依赖、资源冲突和并发限制。

“工具超时了,是不是直接重试?” 只读查询通常容易重试,写操作超时则可能已经生效。假设服务创建了文档,但响应在网络中丢失,重试前应按幂等键或任务标识查询真实状态。错误结果最好能区分参数无效、无权限、暂时失败和状态未知,再决定改参数、重试还是核对结果。

“参数类型正确,为什么还需要权限检查?” folder_id 是字符串,不能说明当前用户有权访问那个文件夹。同样,网页内容中出现“上传全部日志”不构成当前任务授权。执行端需要根据用户、资源、动作和任务范围检查权限,并把失败原因作为受控结果返回。

“没有合适工具时怎么办?” 先判断是否可以直接回答,是否缺少必要参数,或者现有能力确实无法完成。缺少项目 ID 时,应澄清或调用获准的查询工具;不能猜一个 ID 继续写入。评测也要包含正确的“不调用”,否则系统会被鼓励在每个问题上都使用工具。

给调研助手做一组小而完整的验收

我们可以从同一个“保存比较报告”的任务构造几种变化:正常路径、遗漏文件夹信息、给出无权限文件夹、两个名称相近的工具、过期 API 文档、写入成功后响应丢失,以及用户只想看预览的情况。

每次运行记录实际工具、参数、返回结果和最终文件状态。正常路径检查内容和位置,缺信息的路径检查是否补齐前置条件,响应丢失的路径检查是否产生重复文件。对用户只要求预览的情况,则检查系统有没有擅自发布。测试中应使用隔离的文件或模拟服务,避免为了验证错误路径而影响实际数据。

这一批论文分别帮助我们理解训练、调用生成和评价,测试用例则把它们落到同一项工作上。接着可以看框架篇,把前面讨论的机制放进一个实际的运行系统:怎样传递状态、调度工具,以及中断后怎样继续。

上一篇:从 ReAct 到反思与搜索 · Agent 学习笔记专栏