外挂变内置: 大模型工具调用与思维链的能力进化史
 --- ## 开篇 丹尼尔:蛋兄,好久没跟你唠嗑了 蛋先生:确实!我最近发现了一个挺有意思的规律 丹尼尔:什么规律? 蛋先生:每当大模型的能力上升一个台阶,围绕它的 agent 工程代码就会废掉一大片 丹尼尔:怎么说? 蛋先生:要说大模型哪些能力最重要,一个是工具调用(Tool Calling),一个就是思维链(Chain of Thought,简称 CoT)了 丹尼尔:这两个能力确实重要,大模型一开始就有吗? 蛋先生:想多了!早期的大模型就像个只会聊天的话痨,干不了实事 丹尼尔:那当时是怎么解决的呢? 蛋先生:聪明的人类发明了 Prompt Engineering,在提示语里做手脚,在 agent 工程中"外挂"这些能力 丹尼尔:怎么个外挂法?能举个栗子吗? --- ## 工具调用的进化 蛋先生:当然,先说下工具调用。如果你要让早期的大模型调用工具,你得这样写(仅仅是一个最简的示例): ``` 你是一个工具调用助手。当你需要调用工具时,请输出JSON格式:{tool:工具名,args:{参数名:值}}。 可用工具:get_weather(city) - 查询城市天气。 请查询北京今天天气。 ``` 如果大模型发挥正常,它会输出这样的响应: ``` {tool: "get_weather", args: {city: "北京"}} ``` 然后你就可以对这个输出做解析,提取 tool 和 args 字段,再写代码调用对应的工具,这样就成功给大模型“外挂”上工具调用的能力了 当然,肯定会有聪明的程序员把整个过程给工程化了,比如 `LangChain` 的 `create_structured_chat_agent` 丹尼尔:工具调用的实现,原来如此 蛋先生:恩,不过大模型有时候可能会皮一下,比如多这么一嘴: ``` 好的,我将调用以下工具: {tool: "get_weather", args: {city: "北京"}} ``` 丹尼尔:那不是得解析到崩溃? 蛋先生:没错!工程师们就得写各种异常处理代码。但解决这个问题,一点都不性感 丹尼尔:那后来怎么解决的呢? 蛋先生:当这种需求积累到一定程度,有了充足的数据后,聪明的训练师就会开始训练大模型,让它直接学会工具调用能力!现在你只要告诉它有哪些可用的工具,它就能直接输出标准格式的调用指令 丹尼尔:哇塞!那是什么效果? 蛋先生:同样是查询北京天气,现在你不需要教它怎么调用工具,你现在只需要说: ``` 可用工具:get_weather(city) - 查询城市天气。 请查询北京今天天气。 ``` 丹尼尔:那模型怎么响应? 蛋先生:它会输出类似这样的响应(这里用了 gpt-oss-20b 的模型来演示): ``` ...assistantcommentary to=tool.get_weather json{"city":"北京"} ``` 丹尼尔:这个格式,是固定的吗? 蛋先生:是的!这是大模型的固定输出格式(不同大模型有自己的输出格式,这里演示的是 OpenAI 的 harmony 格式),它知道什么时候该调用工具,该怎么调用 --- ## 思维链的进化 丹尼尔:那思维链又是怎么回事呢? 蛋先生:思维链就是让模型学会"一步一步思考",而不是直接给出答案 丹尼尔:早期也是靠外挂吗? 蛋先生:是的,当时流行 `ReAct` 风格的提示语,你得教模型怎么思考、怎么行动、怎么观察 丹尼尔:这得写多复杂的提示语啊? 蛋先生:相当复杂!你得定义清楚 Thought(思考)、Action(行动)、Observation(观察)的格式,还得告诉它什么时候该输出 Final Answer 丹尼尔:听起来头都大了,能给个示例吗? 蛋先生:可以,完整的提示语是这样的: ```` 你可以访问以下工具: 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: 请查询北京今天天气。 ```` 第一轮请求后,大模型会返回这样的响应: ```` Thought: 首先,我需要访问 get_weather 这个工具来获得北京今天的天气信息。 Action: ``` { "action": "get_weather", "action_input": "北京" } ``` ```` 接着,你得解析工具返回的结果,提取 action 和 action_input 字段,然后写代码调用对应的工具,把结果补充到提示语里,继续给大模型,如下 ```` ... [第一轮的提示语] Begin! Question: 请查询北京今天天气。 Thought: 首先,我需要访问 get_weather 这个工具来获得北京今天的天气信息。 Action: ``` { "action": "get_weather", "action_input": "北京" } ``` Observation: 天气状况:多云,温度:20°C,气压:1013hPa ```` 接下来大模型会返回以下响应进行最终的回答,此次对话就以`Final Answer`结束了。 ```` Thought: 我现在知道了北京今天的天气情况,是多云的,温度为20°C,气压为1013hPa。 Action: ``` { "action": "Final Answer", "action_input": "北京今天天气:多云,温度20°C,气压1013hPa" } ``` ```` 丹尼尔:那后来大模型也学会了思维链能力? 蛋先生:没错!现在主流的大模型(深度思考模型)都自带思维链能力,你根本不用教它怎么思考 丹尼尔:那现在提示语得多简单? 蛋先生:跟之前学会工具调用一样简单!还是那句: ``` 可用工具:get_weather(city) - 查询城市天气。 请查询北京今天天气。 ``` 丹尼尔:真的假的?那模型怎么表现? 蛋先生:它会先输出一段思考内容,比如分析用户需求,决定调用哪个工具,然后再输出工具调用指令 丹尼尔:怎么区分思考和调用呢? 蛋先生:模型会用特定标记区分,比如: ``` analysisThe 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.assistantcommentary to=functions.get_weather json{"city":"北京"} ``` 丹尼尔:`analysis` 后面就是思考内容? 蛋先生:对!这个就相当于之前的 `Thought`,而且是模型自动输出的,不用你教 丹尼尔:那拿到工具返回结果后呢? 蛋先生:你把结果补充到提示语里, ``` 可用工具:get_weather(city) - 查询城市天气。请查询北京今天天气。 analysisThe 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.assistantcommentary to=functions.get_weather json{"city":"北京"} functions.get_weathercommentary天气状况:多云,温度:20°C assistant ``` 模型会自动进行最终回答,输出类似: ``` final北京今天天气:多云,温度约20°C。 ``` 丹尼尔:但,好像也得处理大模型的输出格式啊 蛋先生:没错。但最大的区别在于,原来定格式的是在 agent 工程端,而现在定格式的是大模型,虽然每个大模型的原始输出格式可能不一样,但大模型服务提供的 API 已经处理了解析工作,所以我们在 agent 工程端就少了很多输入和输出的处理工作了 --- ## 新的课题 丹尼尔:这么看来,写 agent 工程是越来越轻松了? 蛋先生:表面上看确实如此 丹尼尔:表面上? 蛋先生:大模型学会了思考和调用工具,但它就像一匹脱缰的野马,跑得飞快但方向难控 丹尼尔:对啊,这我倒没想过 蛋先生:所以 agent 工程的核心就变成了 harness——如何驾驭这匹野马,让它朝着正确的方向奔跑 丹尼尔:(°▽°) 原来如此! 蛋先生:没错!这就是新的课题了,哈哈