外挂变内置: 大模型工具调用与思维链的能力进化史

开篇

丹尼尔:蛋兄,好久没跟你唠嗑了

蛋先生:确实!我最近发现了一个挺有意思的规律

丹尼尔:什么规律?

蛋先生:每当大模型的能力上升一个台阶,围绕它的 agent 工程代码就会废掉一大片

丹尼尔:怎么说?

蛋先生:要说大模型哪些能力最重要,一个是工具调用(Tool Calling),一个就是思维链(Chain of Thought,简称 CoT)了

丹尼尔:这两个能力确实重要,大模型一开始就有吗?

蛋先生:想多了!早期的大模型就像个只会聊天的话痨,干不了实事

丹尼尔:那当时是怎么解决的呢?

蛋先生:聪明的人类发明了 Prompt Engineering,在提示语里做手脚,在 agent 工程中"外挂"这些能力

丹尼尔:怎么个外挂法?能举个栗子吗?

工具调用的进化

蛋先生:当然,先说下工具调用。如果你要让早期的大模型调用工具,你得这样写(仅仅是一个最简的示例):

css 复制代码
你是一个工具调用助手。当你需要调用工具时,请输出JSON格式:{tool:工具名,args:{参数名:值}}。

可用工具:get_weather(city) - 查询城市天气。

请查询北京今天天气。

如果大模型发挥正常,它会输出这样的响应:

css 复制代码
{tool: "get_weather", args: {city: "北京"}}

然后你就可以对这个输出做解析,提取 tool 和 args 字段,再写代码调用对应的工具,这样就成功给大模型"外挂"上工具调用的能力了

当然,肯定会有聪明的程序员把整个过程给工程化了,比如 LangChaincreate_structured_chat_agent

丹尼尔:工具调用的实现,原来如此

蛋先生:恩,不过大模型有时候可能会皮一下,比如多这么一嘴:

css 复制代码
好的,我将调用以下工具:
{tool: "get_weather", args: {city: "北京"}}

丹尼尔:那不是得解析到崩溃?

蛋先生:没错!工程师们就得写各种异常处理代码。但解决这个问题,一点都不性感

丹尼尔:那后来怎么解决的呢?

蛋先生:当这种需求积累到一定程度,有了充足的数据后,聪明的训练师就会开始训练大模型,让它直接学会工具调用能力!现在你只要告诉它有哪些可用的工具,它就能直接输出标准格式的调用指令

丹尼尔:哇塞!那是什么效果?

蛋先生:同样是查询北京天气,现在你不需要教它怎么调用工具,你现在只需要说:

scss 复制代码
可用工具:get_weather(city) - 查询城市天气。

请查询北京今天天气。

丹尼尔:那模型怎么响应?

蛋先生:它会输出类似这样的响应(这里用了 gpt-oss-20b 的模型来演示):

ini 复制代码
<|channel|>...<|start|>assistant<|channel|>commentary to=tool.get_weather <|constrain|>json<|message|>{"city":"北京"}<|call|>

丹尼尔:这个格式,是固定的吗?

蛋先生:是的!这是大模型的固定输出格式(不同大模型有自己的输出格式,这里演示的是 OpenAI 的 harmony 格式),它知道什么时候该调用工具,该怎么调用

思维链的进化

丹尼尔:那思维链又是怎么回事呢?

蛋先生:思维链就是让模型学会"一步一步思考",而不是直接给出答案

丹尼尔:早期也是靠外挂吗?

蛋先生:是的,当时流行 ReAct 风格的提示语,你得教模型怎么思考、怎么行动、怎么观察

丹尼尔:这得写多复杂的提示语啊?

蛋先生:相当复杂!你得定义清楚 Thought(思考)、Action(行动)、Observation(观察)的格式,还得告诉它什么时候该输出 Final Answer

丹尼尔:听起来头都大了,能给个示例吗?

蛋先生:可以,完整的提示语是这样的:

makefile 复制代码
你可以访问以下工具:

get_weather(city) - 查询城市天气

使用 JSON 格式指定工具,提供 action(工具名)和 action_input(工具输入)。

有效 action 值:"Final Answer" 或 [get_weather]

每次只提供一个 action,格式如下:

Question: 需要回答的问题
Thought: 必须先思考,解释你的推理过程
Action:
```
{
    "action": "工具名",
    "action_input": "输入"
}
```
Observation: 行动结果
...(这个 Thought/Action/Observation 可以重复 N 次)
Thought: 我知道该如何回答了
Action:
```
{
    "action": "Final Answer",
    "action_input": "最终回复"
}
```

**重要**: 每次行动前必须先输出 Thought,不允许直接输出 Action。

Begin!

Question: 请查询北京今天天气。

第一轮请求后,大模型会返回这样的响应:

makefile 复制代码
Thought: 首先,我需要访问 get_weather 这个工具来获得北京今天的天气信息。
Action:
```
{
    "action": "get_weather",
    "action_input": "北京"
}
```

接着,你得解析工具返回的结果,提取 action 和 action_input 字段,然后写代码调用对应的工具,把结果补充到提示语里,继续给大模型,如下

makefile 复制代码
... [第一轮的提示语]
Begin!

Question: 请查询北京今天天气。
Thought: 首先,我需要访问 get_weather 这个工具来获得北京今天的天气信息。
Action:
```
{
    "action": "get_weather",
    "action_input": "北京"
}
```
Observation: 天气状况:多云,温度:20°C,气压:1013hPa

接下来大模型会返回以下响应进行最终的回答,此次对话就以Final Answer结束了。

makefile 复制代码
Thought: 我现在知道了北京今天的天气情况,是多云的,温度为20°C,气压为1013hPa。
Action:
```
{
    "action": "Final Answer",
    "action_input": "北京今天天气:多云,温度20°C,气压1013hPa"
}
```

丹尼尔:那后来大模型也学会了思维链能力?

蛋先生:没错!现在主流的大模型(深度思考模型)都自带思维链能力,你根本不用教它怎么思考

丹尼尔:那现在提示语得多简单?

蛋先生:跟之前学会工具调用一样简单!还是那句:

scss 复制代码
可用工具:get_weather(city) - 查询城市天气。

请查询北京今天天气。

丹尼尔:真的假的?那模型怎么表现?

蛋先生:它会先输出一段思考内容,比如分析用户需求,决定调用哪个工具,然后再输出工具调用指令

丹尼尔:怎么区分思考和调用呢?

蛋先生:模型会用特定标记区分,比如:

rust 复制代码
<|channel|>analysis<|message|>The user: "可用工具:get_weather(city) - 查询城市天气。请查询北京今天天气。" They want to use the get_weather tool to fetch today's weather for Beijing. We should call get_weather with "北京". The tool returns some data. Let's do that.<|end|><|start|>assistant<|channel|>commentary to=functions.get_weather <|constrain|>json<|message|>{"city":"北京"}<|call|>

丹尼尔:<|channel|>analysis 后面就是思考内容?

蛋先生:对!这个就相当于之前的 Thought,而且是模型自动输出的,不用你教

丹尼尔:那拿到工具返回结果后呢?

蛋先生:你把结果补充到提示语里,

sql 复制代码
可用工具:get_weather(city) - 查询城市天气。请查询北京今天天气。

<|channel|>analysis<|message|>The user: "可用工具:get_weather(city) - 查询城市天气。请查询北京今天天气。" They want to use the get_weather tool to fetch today's weather for Beijing. We should call get_weather with "北京". The tool returns some data. Let's do that.<|end|><|start|>assistant<|channel|>commentary to=functions.get_weather <|constrain|>json<|message|>{"city":"北京"}<|call|>

<|start|>functions.get_weather<|channel|>commentary<|message|>天气状况:多云,温度:20°C<|end|>
<|start|>assistant

模型会自动进行最终回答,输出类似:

kotlin 复制代码
<|channel|>final<|message|>北京今天天气:多云,温度约20°C。<|return|>

丹尼尔:但,好像也得处理大模型的输出格式啊

蛋先生:没错。但最大的区别在于,原来定格式的是在 agent 工程端,而现在定格式的是大模型,虽然每个大模型的原始输出格式可能不一样,但大模型服务提供的 API 已经处理了解析工作,所以我们在 agent 工程端就少了很多输入和输出的处理工作了

新的课题

丹尼尔:这么看来,写 agent 工程是越来越轻松了?

蛋先生:表面上看确实如此

丹尼尔:表面上?

蛋先生:大模型学会了思考和调用工具,但它就像一匹脱缰的野马,跑得飞快但方向难控

丹尼尔:对啊,这我倒没想过

蛋先生:所以 agent 工程的核心就变成了 harness------如何驾驭这匹野马,让它朝着正确的方向奔跑

丹尼尔:(°▽°) 原来如此!

蛋先生:没错!这就是新的课题了,哈哈

相关推荐
dong_junshuai2 小时前
每天一个开源项目#99 OpenResearch:2.2K星的本地研究Agent工作台
开源·github·agent
花椒技术2 小时前
把 AI 视频接进直播:提前生成、按次生成、持续生成怎么选?
agent·音视频开发
用户8082598666872 小时前
用 LangGraph 重写自己手写的 Agent:框架替你解决了什么,什么它不管
agent
能不能静下心来看2 小时前
从 RAG 到 Agent:跑通两个 Demo 后,我终于分清了这两个词
agent
ZGi.ai2 小时前
知识库权限变了,怎样避免答错?
agent·权限管理·知识库·工作流·zgi·客服运营
大模型真好玩3 小时前
DeepSeek Harness 入门很简单(四)——DeepSeek Harness接入插件
人工智能·agent·deepseek
掰头战士3 小时前
MCP、Skill、Plugin,都是给agent拓展能力,到底有何区别?
typescript·llm·agent
嘟嘟嘟95274 小时前
AI Agent 的边缘困境
人工智能·架构·agent
HYDtomako4 小时前
Agent checkpoint设计
ai·agent·checkpoint·claude code
梦在远山后5 小时前
从需求到架构:我的企业研发 Agent 整体设计
langchain·agent