我最早用 ChatGPT 的插件功能时,有一个场景让我困惑了很久:我让它“帮我查一下今天的天气,然后根据温度换算成华氏度,同时去数据库里查一下我上次记录的体感温度偏好”。它确实能同时用天气插件、计算器和数据库,但有时候它会先算华氏度,才发现还没拿到温度数据;有时候它并行调用三个工具,结果数据库返回失败,它却直接基于错误数据继续推理。我那时候想,让一个模型同时操作多个工具,难道不就是把工具说明塞进 prompt 里吗?后来我才发现,事情远没有那么简单。

这个问题的本质是:大语言模型是个“文字接龙”引擎,它并不是天生就会规划任务、调度资源、处理异步结果的。当我们要求它同时调用搜索、计算器、数据库,实际上是在要求它完成一个多步骤的、有依赖关系的、可能并行的任务规划与执行。这就需要一套协调机制。
现在业内最主流的思路,来自于一篇叫 ReAct 的论文。它把“推理”(Reasoning)和“行动”(Acting)交织在一起,让模型在每一步都输出一个“思考-行动-观察”的循环。模型会先吐一段思考文字:“我需要知道现在的温度,所以先调用天气工具”,然后生成一个动作指令(比如 get_weather(city)),系统执行后把结果作为“观察”喂回模型,模型再决定下一步该干什么。这套框架直接催生了后来 LangChain、AutoGPT 等 Agent 框架的核心逻辑。
但 ReAct 本身只定义了单步串行模式:一次只调用一个工具,等结果回来再继续。如果你想同时调用搜索和计算器,ReAct 原生并不支持。这就引出了第二个关键问题:并行调用到底怎么实现?
答案藏在 OpenAI 的 Function Calling 机制里,也广为其他模型厂商借鉴。当你把多个工具(函数)的定义发给模型,模型可以一次性返回多个 tool_calls,每个调用对应一个独立的函数。比如上面的例子,模型可以在一次响应里同时发起 get_weather 和 query_database 两个调用,因为它们之间没有相互依赖。系统拿到这两个调用后,可以并行执行,把结果一起打包返回给模型,模型再做最终推理。
但这里有一个巨大的坑:模型怎么知道哪些调用可以并行,哪些必须串行?模型并没有真正的“理解”依赖关系,它只是根据训练过程中学到的模式来猜测。如果你把换算式华氏度的工具和天气工具一起发给模型,它可能会以为“换算华氏度”不需要温度数值,结果并行调用,然后换算出一个基于空输入的垃圾结果。更常见的错误是:模型在所有调用返回后,才意识到某些结果需要作为另一个调用的参数,但已经晚了,因为它已经在同一轮里把那个调用发出去了。
解决这个问题的思路,目前分成两派。
一派是“强制串行,安全第一”。比如 LangChain 的 AgentExecutor 默认采用 ReAct 模式,每次只让模型生成一个工具调用,执行完再继续,绝不允许并行。这种做法的好处是绝对不会出现依赖错误,但问题是效率低,用户等待时间长。
另一派是“模型自主判断 + 系统兜底”。也就是让模型在生成 tool_calls 时,自己决定哪些可以并行(通过返回一个调用列表),系统负责执行。但系统会额外做一个“依赖检查”:如果某个调用的参数引用了另一个调用的输出占位符,说明有依赖,系统会强制串行执行。这个占位符机制在 GPT-4 的并行工具调用里其实已经隐含了——模型可以在一次响应里写出两个调用,但如果第二个调用的参数是一个符号,代表它预期从第一个调用的结果里取值,那系统就得按顺序执行。不过,目前模型并没有显式地使用占位符,它只是凭“感觉”写出调用,所以这种依赖检查在实际产品中并不完全可靠。
另一个关键问题是:当多个工具返回结果后,模型怎么把结果拼在一起,形成最终答案?这就是“结果融合”阶段。模型需要理解每个工具返回的原始数据,提取关键信息,纠正可能的矛盾,再生成最终回答。这个过程其实非常考验模型的推理能力。比如搜索返回了“今天北京气温18°C”,数据库返回了“用户偏好体感温度用华氏度”,计算器工具等着换算。如果模型把搜索结果的数字直接拿去换算,却忘了加上“用户偏好华氏度”这个上下文,就会错。所以,多工具协作的 Agent 往往会在 prompt 里加入一个“规划”阶段,先让模型输出一个任务分解方案,再执行。这个方案可以是一段自然语言,也可以是结构化的 JSON。
我见过一个很聪明的设计,来自 Anthropic 的 Tool Use 文档:在系统提示里明确告诉模型,“你可以同时调用多个工具,但请确保它们之间没有依赖关系。如果有依赖,请分步进行。” 同时,他们提供了一个 tool_choice 参数,允许开发者强制模型在特定轮次调用特定工具,这就把一部分控制权从模型手里拿了回来,减少了不确定性。
那在实际工程中,协调多个工具还会遇到哪些现实问题?
| 问题 | 典型表现 | 常见解决方案 |
|---|---|---|
| 工具调用失败 | 搜索超时、数据库连接断开、计算器输入格式错误 | 模型生成错误处理分支:重试、降级、请求用户澄清 |
| 结果格式不一致 | 搜索返回 HTML,计算器期望纯数字,数据库返回 JSON 嵌套 | 在工具定义中明确输出 schema,模型学会提取字段 |
| 上下文长度爆炸 | 多轮工具调用和结果塞满上下文窗口 | 对返回结果做摘要、压缩,或只保留关键字段 |
| 工具选择错误 | 模型把简单计算发给搜索,把复杂查询发给计算器 | 在工具描述中写清楚能力边界,甚至给 few-shot 示例 |
| 并行调用导致的幻觉 | 模型在看到结果前就臆测结果,或忽略实际返回 | 强制模型在每次工具返回后重新推理,而不是一次性生成所有调用 |
说到这里,你可能会问:那有没有一种方法,能让模型真的学会“调用工具的时机”,而不是靠 prompt 硬来?有,而且这条路已经有人在走了。Meta 的 Toolformer 论文就是通过在训练数据中插入特殊的 API 调用标记,让模型在预训练阶段就学会“在需要的时候自动插入工具调用”。也就是说,模型在生成文本时,如果遇到自己不知道的事实,它会自动生成一个 [搜索(“XXX”)] 标记,然后由外部系统执行,结果再插回文本。这种方式更底层,但需要专门训练,普适性不如 prompt 工程。
还有一个更前沿的趋势:多工具协作的“编排”正在从模型内部走向外部调度器。比如微软的 TaskMatrix.AI 和最近的 JARVIS 项目,都试图用一个中央控制器(通常是另一个语言模型)来管理多个工具模型,进行任务规划、资源分配和错误恢复。这就像给 Agent 配了一个“项目经理”,专门负责协调,而不是让执行者自己分心。这种做法在工具数量多、依赖关系复杂时,可靠性会明显提升。
最后说一点我自己的判断:多工具协作的瓶颈,短期看是 prompt 和推理能力,长期看是模型对“工具调用”这一行为的根本理解。现在的模型就像是一个刚学会用遥控器的孩子,知道按哪个按钮能换台,但不知道遥控器没电的时候该怎么办,也不知道为什么有时候按了没反应。真正的协调能力,需要模型内化一个“世界模型”——知道工具何时可用、何时不可用,知道结果可能不完美,知道如何组合多个不可靠的信息源得出可靠结论。这个目标,目前还差得远。
FAQ
模型能同时调用搜索和计算器,为什么有时候还是会出错?
因为模型在生成 tool_calls 时,并没有真正执行工具,它只是“猜测”应该调用哪些。如果两个工具之间有数据依赖,而模型没有正确识别,就会发出错误的并行调用,导致计算器拿到空输入或错误输入。
怎么让模型判断哪些调用可以并行?
目前最实用的方法是:在工具描述中明确标注“该工具不需要其他工具的输出即可独立运行”,同时在系统 prompt 里给模型一个规则:“如果你确定两个调用之间没有数据依赖,可以同时发起;否则请分步执行。” 但即使这样,也仍然有出错概率,所以工程上最好做一层后置校验。
ReAct 和 Function Calling 有什么区别?
ReAct 是一种推理-行动循环的 prompt 策略,强调每一步都显式地“思考”再行动,是串行的。Function Calling 是一个更底层的接口能力,允许模型在单次响应中返回多个函数调用,可以并行执行。两者可以结合:在循环中每次允许模型发起多个并行调用。
为什么 LangChain 的 Agent 默认不允许并行调用?
因为 LangChain 的默认 Agent 基于 ReAct 模式,追求稳健性,一次只调用一个工具,可以有效避免依赖错误。但你可以通过 MultiActionAgent 或自定义 executor 来支持并行调用。