AI Agent 的核心推理逻辑详解
收录于 AI 浏览量
上一篇文章介绍了 OpenCode 这类 AI 编码代理的用法,这篇往深处走一层:Agent 到底是怎么“思考”的。剥开各家产品的界面差异,所有 Agent 的核心都是同一套推理循环。理解了它,你就明白为什么有的 Agent 表现得有条不紊,有的却像无头苍蝇。
一、Agent 与聊天机器人的本质区别
普通对话式 AI 是“一问一答”:你给输入,它给输出,结束。而 Agent 是一个会自主多步行动的系统,它具备三个聊天机器人没有的东西:
- 工具——可以读写文件、执行命令、搜索网页,而不只是生成文本;
- 循环——一个动作的结果会喂回给模型,驱动下一步决策,直到任务完成;
- 目标——它对着一个需要多步才能完成的目标工作,而不是回答单个问题。
一句话概括:Agent = LLM + 工具 + 循环。剩下的所有复杂性,都是围绕这个循环做工程。
二、核心组成:感知、大脑、规划、记忆、工具
在拆循环之前,先回答“一个 Agent 由什么构成”。把经典 Agent 定义(感知 → 决策 → 行动)与 LLM 时代的拆解(Lilian Weng 综述的四大件)合起来,一个完整的 Agent 由五大件构成:
Agent = 感知 + 大模型 + 规划 + 记忆 + 工具
flowchart LR
S[感知 Perception] --> L[大模型 LLM]
P[规划 Planning] --> L
M[记忆 Memory] --> L
L --> T[工具 Tools]
T --> E[真实环境]
E --> S
- 感知(Perception)——眼睛和耳朵:把目标与环境的最新状态变成模型能读的输入。纯文本场景下它很薄,基本就是“把对话和工具结果整理进上下文”;但在多模态与具身场景(看屏幕、读图纸、听语音、采样环境状态)它是一等公民,需要专门的模型与管线;
- 大模型(LLM)——大脑:唯一的决策器。理解目标、解读观察、决定下一步调用什么工具。它的推理质量直接封顶整个系统的上限;
- 规划(Planning)——执行策略:把“帮我重构这个模块”这类大目标拆成可执行的子任务序列,并在现实与计划冲突时动态重规划。详见第四节;
- 记忆(Memory)——经验存储:短期记忆是上下文窗口里的对话历史,长期记忆在窗口之外(文件、数据库、向量库),靠检索按需取回。详见第五节;
- 工具(Tools)——双手:突破纯文本的边界,让模型能真正读写文件、执行命令、访问网络。详见第六节。
注意这五大件是静态的构成,而 Agent 真正运行起来,靠的是把它们串起来的动态循环——下一节你会看到,循环的四个拍点(感知 → 推理 → 行动 → 观察)正是五大件在时间轴上的运转方式。一个只有大模型没有工具的系统是聊天机器人;加上感知、工具与循环,它才成为 Agent。
三、核心循环:感知 → 推理 → 行动 → 观察
把 Agent 拆到最简,是一个不断旋转的四拍循环:
flowchart LR
A[感知 Perception] --> B[推理 Reasoning]
B --> C[行动 Action]
C --> D[观察 Observation]
D --> B
B --> E[任务完成 输出答案]
- 感知:收集当前状态——用户的目标、对话历史、上一步工具返回的结果;
- 推理:模型思考“接下来该做什么”,决定调用哪个工具、传什么参数,或者判断任务已完成;
- 行动:执行工具调用——真正地读文件、跑命令、发请求;
- 观察:把执行结果追加进上下文,回到第 1 步。
这个循环最经典的学术表述叫 ReAct(Reasoning + Acting,2022 年提出):模型交替输出“思考”和“行动”,行动产生的观察结果再触发下一轮思考。你在 Claude、ChatGPT 的 Agent 模式里看到的“正在搜索…”“正在读取文件…”背后,就是一轮又一轮的 ReAct。
一个真实的推理轨迹长这样(以“修复一个测试失败”为例):
思考: 测试报错在 user_test.py 第 42 行,先读这个文件。
行动: read_file("user_test.py")
观察: 第 42 行断言 get_user(1) 返回的名字是 "Alice",实际返回 "alice"。
思考: 大小写不匹配。可能出在数据层,查一下 get_user 的实现。
行动: grep("def get_user")
观察: 在 db.py 第 88 行找到定义,直接返回了数据库原始字段。
思考: 数据库里存的是小写。修复位置应该是展示层而非数据层……
但断言期望 "Alice",说明接口约定是首字母大写。
在 db.py 的返回处做 capitalize 处理最合适。
行动: edit_file("db.py", 88, "return row[0].capitalize()")
观察: 文件已修改。
思考: 改完了,重跑测试验证。
行动: run_command("pytest user_test.py")
观察: 1 passed。
思考: 测试通过,任务完成。
最终回答: 已修复。原因是数据库返回的名字没有做大小写规范化……
注意这里的推理质量完全取决于观察结果如何被消化——每一步思考都在引用上一步观察到的具体细节。这就是为什么“给模型的上下文质量”比“模型多聪明”往往更影响 Agent 的表现。
四、推理的三种典型模式
1. 思维链(Chain-of-Thought)
最基础的推理增强:让模型把中间步骤写出来,而不是直接跳到答案。“让我一步步想”不是客套话——自回归模型逐 token 生成,把推理过程显式写出来,等于给后面的 token 提供了可依据的上下文,显著降低复杂任务的错误率。
2. 先规划后执行(Plan-and-Execute)
先让模型产出一份完整的任务清单(todo list),再逐项执行。好处是全局结构稳定、不易跑偏;代价是计划可能基于错误假设。成熟的做法是动态重规划:执行中发现现实与计划冲突时,回头修订清单。OpenCode 的 plan Agent 就是这个模式的产物——先在只读模式下把方案想清楚并写成计划文件,再切到 build 模式执行。
3. 反思与自我修正(Reflexion)
行动失败后,把失败原因显式地写进上下文再重试:“上一次编译失败,报错是空指针,因为我没有检查返回值。”没有反思机制的 Agent 会在同一个坑里反复摔倒;有反思机制的 Agent 会越挫越准。这也是为什么好的 Agent 产品都把“错误信息完整回传给模型”当作铁律——报错被截断的瞬间,自我修正能力就瘸了。
五、上下文与记忆:循环的资源约束
循环不是无限的——所有上下文都装在一个有限的上下文窗口里(几万到几十万 token)。Agent 工程的大量巧思都花在“往窗口里放什么、留什么、丢什么”:
- 短期记忆就是当前对话的完整历史,包括每一轮工具调用和观察;
- 长期记忆放在窗口外:文件、数据库、向量库,需要时通过工具检索回来(RAG 的本质就是“用搜索给模型装外挂记忆”);
- 上下文压缩:会话太长时,用一次模型调用把前面的历史压缩成摘要,腾出空间继续干活。压缩是有损的——被压掉的细节可能正是后面需要的,所以何时压缩、保留什么,是各家 Agent 的核心竞争力之一。
一个实用的推论:给 Agent 的指令要假设它可能“忘”。重要约束(编码规范、项目结构约定)不要只在对话开头说一遍,而应该写进项目里每次都能被读到的文件(如 AGENTS.md),让循环中的每一轮都能重新看到。
六、工具调用:结构化输出是关键
模型本身只会生成文本,所谓“调用工具”是一个约定:让模型按特定格式输出结构化数据,由外部程序解析并真正执行。现代方案叫 function calling / tool use,本质是让模型输出 JSON:
{
"tool": "read_file",
"arguments": { "path": "src/db.py", "line_start": 80, "line_end": 100 }
}
外部程序执行后,把结果以“工具消息”的身份追加进对话,模型下一轮就能看到。三个决定工具调用质量的细节:
- 工具描述是给模型看的文档。描述写得含糊,模型就会传错参数或选错工具——这和给人写 API 文档是同一门手艺;
- 工具粒度要适中。太细(每次读一行)会让循环空转几十轮;太粗(一次改整个仓库)则错误代价巨大。好的工具集让常见操作一步到位;
- 并行调用。相互独立的调用(比如同时读三个文件)可以并行发出,这是 Agent 提速的重要来源。
七、子代理:循环之上的分形结构
当任务大到一定程度,单线程循环会遇到两个瓶颈:上下文装不下,以及主线被探索性工作的中间垃圾填满。解法是子代理(subagent):主 Agent 派生一个子会话,给它一段独立的上下文和明确的任务,子代理自己跑自己的循环,只把最终结论交回来。
这正是“分形”结构——每个子代理内部仍然是完整的“感知-推理-行动-观察”循环,但主上下文只看到一行结论。代价是子代理看不到主线的全部语境,所以派发时的任务描述必须自包含。OpenCode 里的 explore、general 子代理,以及自定义的 mode: subagent Agent,都是这个机制。
八、权限与安全:循环的刹车
Agent 能执行命令、改文件,就必须有刹车。常见设计是权限门控:把“允许 / 询问 / 禁止”做成有序规则,作用于每一类动作。比如:读文件放行、写项目文件放行、执行 git push 必须人工确认、删除操作一律禁止。刹车不是可有可无的装饰——一个没有权限约束的编码 Agent,等于把 shell 交给了一个过于自信的实习生。
九、60 行伪代码:亲手实现一个最小 Agent
把前面所有概念合在一起,一个最小可用的 Agent 其实出奇地短:
def agent_loop(goal: str, max_steps: int = 20) -> str:
messages = [{"role": "user", "content": goal}]
for step in range(max_steps):
# 推理:模型看到全部历史,决定下一步
reply = llm_call(messages, tools=TOOL_SCHEMAS)
if reply.tool_calls is None:
return reply.content # 模型认为任务完成
for call in reply.tool_calls: # 行动:可能多个调用并行
result = execute_tool(call.name, call.arguments)
# 观察:结果以工具消息身份回到上下文
messages.append({"role": "tool",
"tool_call_id": call.id,
"content": result})
# 简易反思:接近步数上限时提醒模型收尾
if step == max_steps - 3:
messages.append({"role": "user",
"content": "步数预算快用完了,请总结当前进展并收尾。"})
return "达到最大步数,任务未完成。"
麻雀虽小五脏俱全:llm_call 承担推理(含思维链),tool_calls 是行动,messages.append 是观察与记忆,max_steps 是防失控的预算,收尾提醒是最低配的反思。生产级 Agent 在此之上叠加的,主要是更好的工具设计、上下文压缩、子代理调度和权限体系——骨架从来都是这个循环。
十、小结
| 概念 | 一句话 |
|---|---|
| 核心组成 | 感知 + 大模型 + 规划 + 记忆 + 工具,五大件 |
| Agent 循环 | 感知 → 推理 → 行动 → 观察,转到达成为止 |
| ReAct | 思考与行动交替,观察驱动下一步思考 |
| 思维链 | 把中间推理写出来,给后续 token 当依据 |
| 规划 | 先列清单再执行,发现冲突就重规划 |
| 反思 | 失败原因显式回炉,同一个坑不摔第二次 |
| 记忆 | 窗口内是短期,窗口外靠检索,压缩有损要谨慎 |
| 工具调用 | 模型出结构化 JSON,外部执行后回填结果 |
| 子代理 | 分形循环,主上下文只收结论 |
| 权限 | 循环的刹车,allow / ask / deny 三挡 |
理解了这套循环,再回头看 OpenCode、Claude Code 这类工具的每一个功能——plan 模式、上下文压缩、子代理、权限规则——都会看到它落在循环的某一个拍点上。产品可以千变万化,循环只有一个。