上一篇我们讲了智能体的最小定义是「模型 + 循环 + 工具」,也看到了 ReAct 循环的宏观图景。但你可能还有一个疑问:模型到底是怎么「调用」一个工具的? 它不就是一个只会输出文本的接口吗?
这一篇我们就打开这个黑箱,把一次 ReAct 迭代的每一步展开来看——从用户消息输入,到模型推理、工具调用、返回结果、再推理,直到最终答案。
一次迭代的完整骨架
任何工具调用系统(无论你用的是 OpenAI、Claude 还是 Gemini),都遵循同一个循环:
- 你把用户消息 + 工具描述(tool schemas)一起发给模型 API
- 模型决定:是直接回答,还是需要调用某个工具
- 如果是调用工具,模型输出一个结构化 JSON 块——里面包含 工具名 和 参数
- 你的代码(runtime)解析这个 JSON,真正去执行那个函数(查天气、搜索网页、读数据库)
- 你把工具返回的结果作为一条新消息发回给模型
- 模型读到结果,要么继续调用下一个工具,要么给出最终答案
步骤 2–6 可以重复多次,直到模型认为任务完成1。
这就是智能体循环的实质:LLM 只负责输出指令,你的代码负责执行,然后把真实世界的反馈喂回给 LLM,让它据此决定下一步。
从用户消息到最终答案:一个完整跟踪
假设我们有一个智能体,它有两个工具:search_web(搜索)和 get_weather(查天气)。用户说:
"北京明天适合跑步吗?"
让我们逐帧播放这个循环的内部过程。
第 1 轮
你的代码发送给模型 API:
- 系统提示(包含工具描述)
- 用户消息:"北京明天适合跑步吗?"
模型 API 返回:
- 文本内容(Thought):"我需要先查一下北京明天的天气,才能判断是否适合跑步。"
- 工具调用块(tool_call):
1{ 2 "tool": "get_weather", 3 "args": { "city": "北京", "date": "2026-07-08" } 4}
注意:模型 没有 去查天气。它只是输出了一个结构化的"我想调用 get_weather 这个工具,参数是北京和日期"的指令——它仍然在生成文本,只是这段文本恰好是 JSON 格式且有约定含义2。
步骤 1.5:执行工具
你的代码收到这个 tool_call 之后:
- 解析 JSON,取出工具名
get_weather和参数{city: "北京", date: "2026-07-08"} - 调用真实的天气 API(或查询数据库)
- 拿到结果:
"气温 25°C,湿度 80%,有雨" - 构造一条新消息,角色为
tool,内容是上述结果
第 2 轮
你的代码再次发送给模型 API:
- 系统提示(不变)
- 用户消息
- 上一条助手回复(含 tool_call)
- 工具消息(含天气结果)
模型 API 返回:
- 文本内容:"北京明天有雨,湿度 80%,不太适合户外跑步。建议改去室内健身房。"
此时模型决定不再调用工具,直接输出最终答案。循环结束。
为什么 Observation 必须来自真实世界
上面这个例子中有一个关键细节:观察结果(Observation)不是模型生成的,而是工具执行后由你的代码插入的。
假设我们允许模型自己生成观察结果,会出现什么情况?模型可能说:
1Action: get_weather("北京", "2026-07-08")
2Observation: 天气晴朗,温度 25°C,非常适合跑步但真实天气其实是有雨的。模型可以编造一个"看起来合理"的观察结果——这就是智能体版本的幻觉。因此,Observation 必须来自工具的真实执行,不能由模型生成。这是 ReAct 循环设计中最重要的一条安全边界3。
模型是"失忆"的,Transcript 才是状态
还有一个不细看就容易误解的地方:模型在两次 API 调用之间什么都不记得。
每次你调用模型 API,它看到的只是你这次发给它的全部消息。它没有"记忆",不会记得上一次调用时它想了什么、做了什么。所谓"记忆",是你 在代码里把每次调用前后的消息都追加到同一个对话历史中,下次调用时再把整个历史发过去。
换句话说,智能体的状态不在模型里,而在你的代码维护的那份 transcript(对话记录)里。你可以在第 1 轮用 Claude,第 2 轮用 GPT,第 3 轮换一个本地模型——只要 transcript 完整,模型就能接上之前的上下文继续推理4。
所以循环到底在哪里?
很多人第一次理解 ReAct 时,会以为循环在模型内部——模型自己在"思考→行动→观察→思考"。
不是。循环在你的代码里。 模型每次只做一件事:接收一段文本,输出下一段文本。是你的代码在检查输出——如果是 tool_call 就执行工具、把结果追加回去、再次调用模型——直到输出是最终答案为止。
把这段逻辑写成伪代码,大概是这样:
1while not finished:
2 response = call_llm(messages + tools) # 模型输出
3 if response.has_tool_call():
4 tool_name = response.tool_call.name
5 args = response.tool_call.arguments
6 result = execute_tool(tool_name, args) # 你的代码执行
7 messages.append(tool_result(result)) # 追加结果
8 # 继续循环
9 else:
10 final_answer = response.text
11 finished = True循环不在模型里。循环是你的代码里那个 while 语句4。
小结
把这一篇和上一篇连起来,完整的画面就清晰了:
- 上一篇:智能体 = 模型 + 循环 + 工具。ReAct 循环让模型从"一次回答完就停"变成"持续执行直到完成"。
- 这一篇:循环的具体机制——模型输出 tool_call 指令,你的代码执行工具,把真实结果喂回去,模型据此决定下一步。模型每步都"失忆",但 transcript 让一切连贯。
下一期我们会更进一步:当你注册多个工具时,模型怎么决定"该用哪个"?工具描述(tool schema)的写法会如何影响模型的选择?