Tool Calling 与 Acting:让模型学会做事


一、从”说话”到”做事”

1.1 语言模型的本质限制

语言模型的能力:

给定输入 ,生成文本输出 :

限制:

  1. 无法访问实时信息:不知道当前时间、天气、股价
  2. 无法执行操作:不能搜索网页、发送邮件、执行代码
  3. 无法与环境交互:只能处理文本,无法改变外部世界

1.2 工具调用的核心思想

核心洞察:

让语言模型不仅能”说话”,还能”调用工具”——通过函数/API 调用执行操作,获取外部信息或改变外部状态。

形式化:

将语言模型扩展为:

其中 是可用工具的集合。

工具调用示例:

用户:今天北京天气如何?

模型决定调用工具:
[TOOL_CALL] get_weather(location="北京")
[TOOL_RESULT] 晴,25°C

模型基于结果生成回答:
"今天北京天气晴朗,气温25摄氏度。"

二、Toolformer:模型自主学习工具调用

2.1 核心思想

Toolformer(Schick et al., 2023)的核心是:让模型自己决定何时调用哪些工具,以及如何调用。

关键创新:

  1. 工具选择:模型自己决定是否需要调用工具
  2. 工具调用:模型生成符合 API 格式的调用
  3. 结果融合:将工具返回结果融入下一步生成

2.2 Toolformer 的工具定义

定义(工具):

每个工具 由以下部分组成:

示例:计算器工具

name: "calculator"
description: "计算数学表达式的值"
parameters: {
    "expression": "str"  # 要计算的表达式
}
return_type: "float"  # 返回计算结果

2.3 Toolformer 的数据生成

核心优势:自监督自动构建

Toolformer 的关键创新在于无标注自监督自动构造工具调用样本——并非简单人工插入调用语句,而是让模型自行判断何地插入工具调用并验证结果,形成低成本大规模数据集。

Algorithm: Toolformer Data Generation

输入:语言模型 ,工具集 ,工具调用数据集

步骤 1:API 相关采样

for each sample (x, y) in D:
    # 判断是否适合插入工具调用
    if should_insert_tool(x, y):
        y' = insert_tool_calls(x, y, T)  # 在适当位置插入工具调用

步骤 2:工具调用格式

在自然语言中插入特殊格式的调用:

这导致 45 + 23 = [TOOL_CALL] calculator("45 + 23") [TOOL_RESULT] 68 [TOOL_CALL_END]
所以结果是 68。

2.4 工具调用的训练目标

Toolformer 的损失函数:

在每个 token 位置 ,计算:

但只对以下 token 计算梯度:

  • 自然语言 token
  • 工具调用 token(API 名、参数)
  • 不包含工具结果的 token

梯度阻断的三大 Token 类别:

Token 类型示例梯度计算
自然语言 Token普通文本生成✅ 回传梯度
工具调用格式 Token[TOOL_CALL], calculator, "45+23"✅ 回传梯度(模型自主生成)
外部工具返回结果 Token[TOOL_RESULT] 68❌ 冻结梯度(外部系统生成)

梯度阻断的核心原因:工具返回结果由外部系统(如计算器、搜索API)生成,不属于模型自主生成内容,若对其回传梯度会导致模型学习外部系统的输出分布,破坏训练目标。

原因:工具结果是外部提供的,不应该从中学习。

2.5 工具选择的决策

何时调用工具?

模型通过 prompt 学习何时调用工具:

当你需要执行计算、查询信息或访问外部数据时,
使用以下格式调用工具:

[TOOL_CALL] tool_name("param1", "param2")
[TOOL_RESULT] result
[TOOL_CALL_END]

工业主流 JSON Function Calling 格式(OpenAI 生态标准):

{
  "name": "get_weather",
  "arguments": {
    "location": "北京",
    "unit": "celsius"
  }
}

优势:

  • 结构化程度高,参数类型安全校验
  • 企业级落地首选方案
  • 规避自定义标签解析歧义

零样本/少样本工具调用提示工程:

无需 SFT 微调,仅靠 Prompt 即可实现工具调用:

可用工具:
{
  "name": "calculator",
  "description": "计算数学表达式",
  "parameters": {
    "expression": {"type": "string", "description": "要计算的表达式"}
  }
}

示例调用:
{
  "name": "calculator",
  "arguments": {"expression": "45 + 23"}
}

请遵循上述格式调用工具。

决策的数学表示:

其中 是最后一层的隐藏状态。


三、ReAct:推理与行动的交织

3.1 核心思想

ReAct(Yao et al., 2023)的核心是:将**推理(Reasoning)和行动(Acting)**交错起来。

ReAct 的核心洞察:

人类智能是推理和行动的交织:边想边做,边做边想。

与 Toolformer 的区别:

方法工具调用推理方式
Toolformer模型自主决定工具调用穿插在生成中
ReAct每个 step 都显式Thought → Action → Observation 循环

3.2 ReAct 的轨迹格式

ReAct 的轨迹由多个步骤组成:

输入:x

Step 1: Thought: 模型思考下一步该做什么
        Action: 执行的操作(如 search, calculate)
        Observation: 操作返回的结果

Step 2: Thought: 基于观察继续思考
        Action: 继续操作
        Observation: 继续观察

... (重复直到得到答案)

关键特性:双向交互与动态纠错

ReAct 支持环境反馈反向修正推理逻辑——可推翻之前错误思路重新规划,而非一条道走到黑。

Step 1: Thought: 我认为应该先搜索"北京天气"
        Action: search("北京天气")
        Observation: 结果显示"晴,25°C"

Step 2: Thought: 等等,用户问的是"明天"北京的天气,
           而搜索结果显示的是"今天"的。
           需要重新搜索"北京明天天气"
        Action: search("北京明天天气")
        Observation: 结果显示"多云,22°C"

形式化表示:

设 是第 步的状态, 是行动, 是观察。

3.3 ReAct 的数学框架

定义(状态):

状态 包含:

  • 原始输入
  • 之前的思考
  • 当前任务描述

定义(行动空间):

其中:

  • :检索类行动(如搜索网页、查询数据库)
  • :计算类行动(如执行数学运算、代码运行)
  • :终止行动(给出最终答案)
  • :自定义业务行动

定义(策略):

3.4 ReAct 的推理循环

Algorithm: ReAct

输入:问题 x,可用工具集 T
输出:最终答案 y

初始化:s_0 = (x, empty_context)

for step t = 1 to MAX_STEPS:
    # 1. 思考(Thought)
    thought_t = pi_thought(s_{t-1})
    # thought_t 是对当前状态的推理

    # 2. 行动(Action)
    action_t = pi_action(s_{t-1}, thought_t)
    # action_t 可以是 search/calculate/finish

    # 3. 执行并观察(Observation)
    if action_t.type == "search":
        obs_t = search(action_t.query)
    elif action_t.type == "calculate":
        obs_t = calculate(action_t.expression)
    elif action_t.type == "finish":
        return action_t.answer

    # 4. 更新状态
    s_t = \text{Concat}\big(s_{t-1},\; \text{Thought}_t,\; \text{Action}_t,\; \text{Observation}_t\big)

3.5 ReAct vs CoT vs Toolformer

对比:

方法推理结构工具调用反馈利用
CoT线性推理链无无
Toolformer工具穿插在文本中自动插入通过工具结果
ReActThought→Action→Obs 循环显式决策每步都有观察

各自适用场景:

  • CoT:数学推理、逻辑推导(不需要外部信息)
  • Toolformer:需要调用工具但不希望干扰生成流程
  • ReAct:需要与环境交互、多步信息获取

四、工具调用的训练方法

4.1 监督微调方法

步骤:

  1. 数据收集:收集包含工具调用的轨迹数据
  2. 格式化为对话:将轨迹格式化为对话格式
  3. SFT 训练:在格式化数据上做监督微调

数据格式:

[USER] x
[ASSISTANT] thought: ...
         action: search("query")
[SYSTEM] observation: ...
[ASSISTANT] thought: ...
         action: finish("answer")

4.2 工具选择的训练

方法 1:分类头(判别式)

在语言模型上添加一个二分类头,判断是否调用工具:

方法 2:工具 embedding(生成式)

为每个工具学习一个 embedding,在生成时通过 attention 选择工具。

判别式 vs 生成式工具选择:

方式核心思想适用场景
判别式(分类头)二分类头判断是否调用工具简单二选一场景
生成式(直接生成)直接生成工具名与参数(主流)复杂多工具选择

4.3 API 调用的格式学习

学习 API 调用格式:

模型需要学会:

  1. 生成正确的函数名
  2. 生成正确的参数(JSON 格式)
  3. 解析返回结果

格式约束:

使用特殊的 token 标记来约束格式:

[TOOL_CALL] calculator("45 + 23") [TOOL_RESULT] 68 [TOOL_CALL_END]

4.4 训练中的问题

问题 1:工具描述的利用

模型需要理解工具的 description 才能正确调用。

解决方案:

将工具描述作为输入的一部分:

可用工具:
- calculator: 计算数学表达式
- search: 搜索网页

请在需要时调用工具。

问题 2:工具结果的幻觉

模型可能伪造工具结果(没有调用工具但生成假结果)。

解决方案:

严格使用真实工具结果替换模型输出中的 [TOOL_RESULT] 部分。

工具调用三类典型幻觉:

幻觉类型描述治理方案
函数名幻觉写错工具名称(如 get_whether 而非 get_weather)Schema 校验 + 相似度匹配限制
参数幻觉入参格式/内容错误(如字符串未加引号)严格 JSON Schema 验证
结果伪造幻觉未调用工具,凭空编造返回结果强制真实结果替换 + 调用日志审计

高阶工具调用模式:

  1. 并行工具调用:同时发起多个独立工具请求(如同时搜索多个关键词)
  2. 嵌套工具调用:一个工具的输出作为另一个工具的输入
  3. 条件分支调用:根据中间结果选择不同工具分支

示例:并行调用

# 同时查询天气和股价
[TOOL_CALL] get_weather(location="北京")
[TOOL_CALL] get_stock(symbol="AAPL")

示例:嵌套调用

# search 结果作为 calculate 输入
result = search("北京的人口")
calculate(f"population * 2 = {result}")

五、多工具协作与规划

5.1 多工具调用序列

问题:一个任务可能需要多个工具。

示例:

任务:查找今天北京演唱会票价

Step 1: search("北京演唱会今天")
         → 获取演出名称和场馆

Step 2: calculate("场馆到最近地铁站的距离")
         → 需要先 search 地铁站信息

Step 3: 整合信息给出回答

多步依赖:

后一步的工具可能依赖于前一步的结果。

5.2 工具调用图

定义(工具依赖图):

工具集合 的输入依赖于的输出

有向无环图(DAG):工具调度使用 DAG 结构规避循环调用死锁。若存在环路,拓扑排序失效,需检测并终止。

拓扑排序:

按照依赖关系排序工具调用: 满足依赖顺序

5.3 规划与重规划

静态规划:事先规划好完整调用序列

动态规划:根据每步结果调整后续调用

Algorithm: Dynamic Replanning

if last_action.failed:
    # 根据失败原因调整计划
    new_plan = replan(current_state, failure_reason)
else if last_observation.is_satisfactory:
    # 继续原计划
    pass

六、实际应用中的挑战

6.1 工具调用的可靠性

问题:模型可能调用错误的工具或参数。

解决方案:

  1. 参数验证:对工具参数做 schema 验证
  2. 错误处理:工具调用失败时重新尝试或换工具
  3. 置信度阈值:低于置信度阈值时不调用

6.2 工具数量与选择

问题:当工具数量很大时(如 100+),如何选择?

解决方案:

  1. 分层选择:先选类别,再选具体工具
  2. 语义匹配:用文本相似度匹配工具描述
  3. 工具 embedding:学习工具的向量表示

6.3 循环与终止判断

问题:如何知道任务完成了?

终止条件:

  1. 显式终止:调用了 finish 类型的工具
  2. 隐式终止:连续几步没有新信息
  3. 步数限制:达到最大步数
  4. 自反思终止:模型自我判断当前信息已足够回答,主动停止交互

自反思终止策略示例:

Step t: Thought: 我已经获取了足够的信息来回答用户的问题。
               北京今天天气晴朗,气温25°C,与用户询问相符。
               不需要进一步搜索。
        Action: finish("北京今天天气晴朗,气温25摄氏度。")

6.4 工具结果的处理

长结果处理:

工具可能返回很长的结果(如搜索返回 10 条结果)。

解决方案:

  1. 截断:限制结果长度
  2. 摘要:对结果做摘要
  3. 选择性使用:只使用最相关的部分

七、数学公式速查

7.1 工具调用公式

扩展语言模型:

工具选择:

7.2 ReAct 公式

状态更新:

策略:


八、总结

Toolformer vs ReAct:

方面ToolformerReAct
工具调用方式自动插入在文本中显式 Thought→Action→Obs 循环
推理方式工具调用穿插在生成推理与行动交替
反馈利用通过工具结果每步都有观察
适用场景需要工具辅助生成需要交互探索

核心洞察:

Tool Calling 将语言模型从”纯文本生成器”扩展为”能够操纵外部世界的智能体”。ReAct 则进一步将推理和行动交织,使得模型能够在与环境交互中不断调整策略。

关键挑战:

  1. 工具选择的准确性
  2. 工具调用的可靠性
  3. 多步规划的效率
  4. 循环检测与终止

8.1 主流智能体框架适配

LangChain 工具调用封装:

from langchain.agents import AgentExecutor, Tool
from langchain.llms import OpenAI
 
tools = [
    Tool(name="calculator", func=calculate, description="计算数学表达式"),
    Tool(name="search", func=search_web, description="搜索网页")
]
 
agent = initialize_agent(tools, llm, agent_type="react")
agent.run("北京今天天气如何?")

LlamaIndex 工具调用封装:

from llama_index import download_loader
from llama_index.agent import ReActAgent
 
agent = ReActAgent.from_tools(tools, verbose=True)
response = agent.query("计算 45 + 23 的结果")

AgentScope 工具调用封装:

import agentscope
from agentscope.agents import ReActAgent
 
agent = ReActAgent(config={
    "tools": [get_weather, calculator],
    "model": "gpt-4"
})

8.2 工程实战落地方案

调用异常处理:

异常类型处理策略
超时重试3次,间隔2s/4s/8s指数退避
参数错误自动纠错(如类型转换)+ 回退到自然语言回答
工具不可用降级方案:直接基于已有信息回答,或提示用户

多轮交互上下文优化:

问题:用户进行20轮对话后,Context 溢出风险

解决方案:
1. 摘要压缩:每隔5轮用 small LLM 压缩历史
2. 滑动窗口:只保留最近 N 轮对话
3. 重要性筛选:删除与当前任务无关的历史

微调实战配比:

数据类型推荐比例作用
工具调用轨迹数据30%专精工具调用能力
自然对话数据70%保持通用对话能力

冷热方案选型:

场景推荐方案说明
快速上线验证少样本Prompt无需训练,快速迭代
深度业务适配SFT微调专属工具调用范式,效果更优
复杂多工具场景ReAct + DAG规划推理+行动+环境观察闭环

延伸阅读:

  1. Schick et al., “Toolformer: Language Models Can Teach Themselves to Use Tools” (ICLR 2023)
  2. Yao et al., “ReAct: Synergizing Reasoning and Acting in Language Models” (ICLR 2023)
  3. Liu et al., “Tool Learning with Foundation Models” (2023)
  4. Zhou et al., “InternLM: A Multilingual Language Model with Progressively Enhanced Capabilities” (2023)
  5. Patil et al., “Gorilla: Large Language Models Connect with Tools” (2023)