让 Agent 记住事的代价:每次对话都得重新带上笔记
你点开任何一款 Agent 产品,让它替你查资料、改代码、订行程。它看起来像个能干的助理在后台忙活,但把盖子掀开,你会看到一个朴素得多的事实:它只是一个循环,不断重复「想一下、做一步、看一眼结果、再想」。这个循环里没有硬盘,没有抽屉。Agent 记住的一切、会用的每项技能、干到一半的进度,本质上都是写进对话窗口的文字。窗口是硬的,每轮对话的预算就那么多,而每次对话都得重新带上笔记。
这篇专题沿着一条线往下走:Agent 看起来会自己干活,这份自主性到底是用什么换来的?答案是上下文预算。理解了这条线,你既能看懂任何一家 SDK 的文档,也能判断任何一篇新论文到底改进了什么。
1. Agent 到底是什么:一个「想→动→看→再想」的循环
循环就是全部
「Agent」(智能体)这个说法容易让人联想到有自己意志的实体。先把它还原成最朴素的定义:一个循环。循环的每一步是四件事,先想一下,做一步,看一眼结果,再想。规范叫法来自 ReAct 这篇奠基论文:Thought / Action / Observation 三段交织。Thought 是模型写给自己的推理文字,Action 是对外发起的一步动作,Observation 是环境对这一步动作的回应,然后带着 Observation 进入下一轮 Thought。
这里有一个入门读者最容易错过、却贯穿全篇的点:Thought 不直接作用于环境,它只更新上下文。模型在心里写下一句「我刚才查到的数字可能有误,需要二次核对」,这句话不会自己触发任何动作,它只是写给「未来的自己」看的笔记,让下一步的推理有依据。换句话说,循环里的一切思考、纠错、计划,都是往对话窗口里追加文字的过程。
ReAct 论文把这条轨迹画在了论文第一张图里,值得亲眼看一次:

图注:上面是 HotpotQA 同一道题用四种提示方式跑出的轨迹对比,下面是 ALFWorld 同一关卡两种方式的对比。注意 ReAct 那一行里 Thought 与 Action 交织出现,而 Act-only 那一行直接从动作开始、没有思考,容易因为猜错环境而失败。ReAct
看图时不必纠结具体句子,那只是示例任务的查询。要紧的是结构:Thought 在哪里出现、是否每一步都有。ReAct 在 ALFWorld 上的成绩很能说明问题。ALFWorld 是一个模拟家居操作的游戏环境,ReAct 平均成功率 71%,只做动作不思考的 Act-only 是 45%,传统的模仿学习基线 BUTLER 只有 37%。多出来的那部分,正是「想」对「动」的增益。
循环是提示词给的,不是模型练出来的
另一个容易误解的地方:这套循环骨架来自提示词,不是来自模型训练。ReAct 是在一个冻结的大模型上做 few-shot prompting(少样本提示),也就是给几个示范例子再问,模型参数一个都没动。所以同一个模型可以被塞进完全不同的循环,你给它 ReAct 模板它就「想→动→看」,你给它别的模板它就按别的方式走。这也解释了后面所有进展为什么都绕着「往上下文里写什么」做文章:既然参数不能动,改进只能发生在对话窗口里。
2023 年有一篇综述把同一个循环拆成四个模块:Profiling(画像)、Memory(记忆)、Planning(规划)、Action(行动),解释 agent 是谁、记得什么、怎么打算、怎么动手。名字不同,骨架一致,认识一下即可,后面我们主要用 ReAct 的叫法。AgentSurvey
章末再挂一张心智图,把它当后续所有章节的收纳架:

图注:以 Agent 为中心节点的结构图,上接记忆(分短期、长期两类),左接工具(计算器、日历、搜索等示例),右接规划(反思、自我批评、思维链、子目标分解),下接行动。循环是骨架,记忆、工具、规划都是插上骨架的模块。Lilian Weng
所以 Agent 与普通聊天框的分界,就是这条循环:聊天框答完就结束,Agent 带着观察结果继续转下去。而循环里每一步思考,都是一段写进上下文的文字。现在问题来了:循环里「行动」这一步,到底具体怎么发生?
2. 工具调用为什么自己跑起来:模型出主意,你的代码动手
模型只出主意,动手的是你的代码
上一章说「做一步」,这个「做」发生在哪?答案可能反直觉:模型自己永远不动手。它只负责输出一段结构化的建议,说「我建议调用 get_weather,参数是 Paris」,真正的查天气动作在你的代码里执行。这个机制叫工具调用(tool calling),是今天主流 Agent 产品的标准接口。
OpenAI 官方文档把整件事规范成五步:先带着工具清单请求模型;模型返回一个 tool_call(工具调用提议);你的代码执行这个提议;把执行结果拼回去再请求一次;拿到最终回复。第五步得到的可能不是最终答复,而是又一个 tool_call,那就回到第二步再转一圈。这五步流程正是上一章 ReAct 循环的工程版:模型给意图,运行环境给动作。Function Calling 看官方的示意图最直观:

图注:OpenAI 官方绘制的工具调用五步流程:请求模型 → 模型返回 tool_call → 应用代码执行工具 → 结果回填 → 拿到最终回复。第 2 到第 5 步之间的回环箭头表示这一圈可以无限重复,模型自身从不真正执行任何工具。Function Calling
工具选择 = 查字典
你可能会凭直觉觉得工具选择「像查字典一样逐个比对」。这个直觉完全正确,而且就是 OpenAI 文档描述的底层机制。每一轮请求里,整张工具清单会被拼进 system message(系统提示),用模型在训练时见过的一种特殊语法排好。模型拿到用户问题后,逐条看每个工具的 name 和 description,跟问题比对,挑最像的那个,输出对应的 tool_call。
下面这张示意图把「查字典」落到画面上:

图注:左边的四张工具卡片就是模型拿到的「字典条目」,中间标注 description matching 的对角箭头表示逐条比对,右侧的箭头说明真正的执行(tool_call + arguments)发生在你自己的代码里。被高亮的那张不是模型「思考」出来的,是按照注入的语法比对出来的。
这里有两个机制细节值得记住。其一,description 是写给模型看的说明书,不是写给人看的注释,它的措辞直接决定选型准确率。其二,官方给了一个软性建议:同一时刻可用的工具最好少于约 20 个。字典条数越多,比对的准确率越容易掉。更重要的是,整张工具清单会被注入进对话,按输入 token 计费。这是全篇第一张「能力账单」:每加一个工具,就多占一份上下文预算。
工具怎么定义:schema 五件套
清单里每个工具都要按固定格式写,一共五个字段:type(固定是 function)、name(函数名)、description(何时用、怎么用)、parameters(JSON Schema 描述输入参数)、strict(是否启用严格模式)。Function Calling
strict mode(严格模式)有两条硬性要求,不满足会被直接拒绝请求:所有对象的 additionalProperties 必须设为 false,且 properties 里的每个字段都必须标记为 required。可选字段怎么表达?用 ["string", "null"] 这种类型联合来写,同时照样放进 required。
模型什么时候必须调工具、能调几个,由 tool_choice 控制,分四档:
| 档位 | 行为 |
|---|---|
auto(默认) | 模型自己决定调 0、1 或多个工具 |
required | 至少调用一个工具 |
| 强制指定某函数 | 必须调用指定的那一个 |
allowed_tools | 只能从给定的子集里挑 |
外加以 none 模拟「不传任何工具」。而 allowed_tools 这一档在需要保留大工具清单、又只允许模型用其中一小部分时很有用(利于提升 prompt caching 命中率)。
这一章的核心可以收成两句话:Agent 的「手」是你的代码,模型只做决策;而它决策所依据的工具清单本身也要占对话空间。能力越加越挤,下一步就是怎么在有限的窗口里塞进更多能力。
3. 技能与长任务:怎么把更多能力塞进有限的对话
Skills:按需加载,给账单还款
承接上一章的账单:工具太多,字典越来越厚,上下文越来越挤。Anthropic 在 2025 年给出的答案是 Agent Skills(技能)。一个技能就是一个文件夹,里面有一个必须存在的 SKILL.md 文件,用 YAML frontmatter(文件头部的元数据区)写必填的 name 和 description,正文是操作说明,还可以捆绑参考文件、脚本、模板。它就像给新员工的一本 onboarding 手册:平时锁在抽屉里,上岗时才翻开。
关键设计叫渐进披露(progressive disclosure):整个技能不一次性塞进对话。system prompt 里只常驻每项技能的 name 和 description 短描述,约 100 tokens;模型判断某个技能跟当前任务相关时,才把 SKILL.md 正文读进上下文,正文上限约 5k tokens;正文里引用到的捆绑文件更是按需再读,理论上不设上限。这跟第二章的查字典是同一思路的升级:字典正文太长,就把目录留在手边,条目按需翻开。AgentSkills 看成本账就清楚了:

图注:三级渐进披露的成本账。第一级 YAML 元数据常驻 system prompt,约 100 tokens;第二级 SKILL.md 正文在技能被触发时才加载,约 5k tokens 以内;第三级捆绑文件由模型按需读取,标注为无上限。AgentSkills
技能在运行时怎么被触发?看下面这张图的链路:

图注:Context Window 里技能被触发的完整链路:顶部 system prompt 挂着所有技能的短描述片段,用户发来带 PDF 附件的请求后,Claude 判断该用 PDF 技能,于是用 Bash 工具的两次调用依次 cat 出 SKILL.md 和相关文件,把说明读进上下文再干活。AgentSkills
注意一个容易混淆的点:技能不是被「调用」的硬函数,而是被「读进上下文」的文档。它依赖的仍然是第二章的 tool_use 通道,只是把「打开文件」这个动作当作普通工具来用。两层机制是层叠关系,不是替代关系。
Harness:长任务把记忆搬出窗口
Skills 解决的是「说明书太长」,但长任务还有一个更狠的敌人:记忆会丢。每个新会话都从空白上下文起步,Agent 一旦需要跨多个会话干同一个活,比如把一个网页应用从零做到能上线,它就会失忆。Anthropic 工程团队把这个问题描述得很形象:就像换班工程师没有任何交接就来上班。LongAgentHarness
他们的解法是一套两段式 harness(外壳):initializer agent 和 coding agent。两者共享同一套系统提示、工具和外壳,唯一的区别是初始的用户 prompt 不同。initializer 在第一次会话里把环境搭好,写出一键启动脚本 init.sh、跨会话日志文件、结构化的功能验收清单,并完成首次 git commit。之后每个会话由 coding agent 接手,每轮只做一件事。LongAgentHarness
这套接力里有两个反直觉的设计。第一,进度不记在对话里,记在文件里:coding agent 开工先读 progress 文件和 git log,看上次干到哪,再挑一个还没完成的功能。第二,feature list 里所有功能初始都标成失败(failing),模型只被允许把真正通过端到端测试的功能改为完成。这是反制「提前宣布胜利」的关键约束,模型不太会去覆盖改写 JSON 文件,所以用 JSON 而不是 Markdown 存清单。
这笔交易的代价要算清楚:你把记忆搬出了窗口,搬进了文件系统,换来的是你成了项目经理。progress 文件、feature list、启动脚本都得有人维护,漏写一行,下一个会话就多猜一次。后面第六章会看到,这些文件没维护好,坑就从哪里冒出来。
4. 2025–2026 巡礼:自我反思、多智能体与弹性记忆
先拿一张地图
进入近两年的进展之前,先给研究对象挂一张坐标图。2025 年 3 月的综述把 LLM Agent 生态画成四个象限:Agent Methodology(方法学)、Evaluation and Tools(评测与工具)、Real-World Issues(真实世界问题)、Applications(应用)。任何一篇新工作,都能在四个象限里找到位置:AgentMethods

图注:LLM Agent 生态的四象限总览:中央是 Agent,向外展开方法学(构建、协作、进化)、评测与工具、真实世界问题、应用四个维度。这张图只当坐标系用,不必逐字读每个小条目。AgentMethods
同年 8 月另一篇综述换了个分类法,从「核心方法、能力增强、领域应用、评测与安全、人机交互」五块来切,跟四象限互为另一种挂法,知道即可。ActionReview
自我反思:Reflexion 与「不动参数」的改进
第一站是自我反思,源头是 2023 年的 Reflexion。它的做法一句话就能讲完:让 agent 先跑一轮任务,失败后把自己失败的过程和原因用自然语言写下来,存进记忆,下一轮把这段反思作为额外上下文再试。论文把这套方法命名为 verbal reinforcement learning,意思是「改进用文字来传递,而不是改模型参数」。Reflexion
再用论文原图对照一遍这套闭环:

图注:Reflexion 的闭环示意图。左侧是高层流程:Actor 生成轨迹 → Evaluator 打分 → Self-Reflection 写反思 → 存入记忆 → 进入下一轮试验;右侧是配套的算法伪代码。注意这一圈里没有任何一步在改模型权重,改进全部发生在上下文里。Reflexion
它为什么能成立?反思的文字比一个「对/错」的分数信息量大得多,而且不动参数意味着同一个基座模型可以反复复用。实证锚是编程评测 HumanEval:Reflexion 达到 91% 的 pass@1,当时 GPT-4 是 80%。Reflexion
但注意它的软肋:反思被写进一个只追加、不压缩的 episodic memory buffer(情景记忆缓冲)。trial 一多,这段记忆迟早撑爆上下文窗口。「自我改进」和「窗口上限」的正面冲突,在这里第一次论文级亮相,稍后 2026 的工作专门处理它。
多智能体:把循环拆给多个角色
单 Agent 是「一个人干到底」,多智能体路线换个思路:把循环拆开,分给多个各司其职的 actor(角色),靠消息协作。微软 2025 年 1 月发布的 AutoGen v0.4 是这条线的官方实践代表。它的底层是 actor-model:actor 是可以交换消息、同时干活的计算单元;框架分三层,Core 提供异步消息机制,AgentChat 提供高层对话 API,Extensions 放各家的集成。AutoGen

图注:AutoGen v0.4 的生态图。右侧是框架本体,从下往上依次是 Core、AgentChat、Extensions 三层;上方是应用区(Magentic-One 等),左侧是开发者工具。图里没有箭头,别去推断谁调用谁,只需记住「actor 之间靠消息协作」这个心智。AutoGen
回到第一章那张心智图:同一个循环骨架,你既可以自己一个人转,也可以让多个角色各转一段、互相递消息。规划者负责拆任务,执行者负责动手,审核者负责挑毛病,消息传到哪里,工作就推进到哪里。
2026 双锚:流式评测与弹性记忆
时间线拉到 2026 年年中,两个方向值得认识。第一个是评测角度。AgentStream 指出,主流评测都是把每个 benchmark 单独拿来测,这跟真实使用差太远。它提出流式评测:让 agent 在一个连续的任务流里干活,并设计三种场景,Isolated(每个任务孤立、状态不共享)、Sequential(一个状态依次跨过多个任务,带域迁移标记)、Interleaved(所有任务混成一条流,共享状态)。结果发现结论会随场景翻盘,孤立测出来的单点数字,换到混合流里不见得成立。AgentStream

图注:AgentStream 的三种流式场景并排。左边每个 benchmark 各自维护独立状态;中间单一状态依次跨过多个 benchmark,边界处标记 Domain Shift;右边所有任务混合成一条流、共享同一个状态。图上不用读任何数字,只看「状态」是怎么被隔离、贯通、混合的。AgentStream
第二个是记忆方向。CrystalMem 把「上下文超限」往深处推了一层:外部记忆库本身也有字节预算,云平台负载高时会压缩预算,负载过去再恢复。论文发现一个反直觉的现象:预算恢复之后,agent 的能力往往回不到压缩前的位置。他们把这个现象命名为 memory hysteresis(记忆迟滞)。也就是说,记忆这条资源,压下去容易,涨回来难。CrystalMem
巡礼到这里可以收束:2025–2026 的主线,是在「不动参数」的前提下,把记忆、反思、技能做得更省、更可靠;同时流式评测提醒我们,论文里那些单点数字要打折听。下面动手把循环跑起来,你才能真正理解这些机制。
5. 亲手跑通最小 Agent:五十行代码把前四章串成一条循环
前四章讲的机制,用一个 while 循环就能串起来。下面的 Python 是 OpenAI 文档 get_horoscope 示例的改写版,三个工具换成了查本机文件、计算器和模拟网页搜索的 mock,除模型 API 调用本身外不访问外部网络服务、也不产生工具侧费用。Function Calling
from openai import OpenAI
import json
client = OpenAI() # 需要设置 OPENAI_API_KEY
MODEL_NAME = "gpt-5" # 换成你账户当前可用的模型名
# 工具 = JSON schema。description 是写给模型看的说明书,选型全靠它
tools = [
{
"type": "function",
"name": "read_local_file",
"description": "读取本机工作目录下的一个文本文件并返回内容。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string",
"description": "文件路径,如 notes.txt"},
},
"required": ["path"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "calculate",
"description": "执行四则运算表达式,如 (3+5)*2。",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string",
"description": "数学表达式"},
},
"required": ["expression"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "mock_web_search",
"description": "模拟网页搜索,返回一条缓存结果。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string",
"description": "搜索关键词"},
},
"required": ["query"],
"additionalProperties": False,
},
"strict": True,
},
]
def execute_tool(name: str, args: dict) -> str:
"""模型不执行,这里的代码才执行。"""
if name == "read_local_file":
with open(args["path"], encoding="utf-8") as f:
return f.read()[:2000]
if name == "calculate":
return str(eval(args["expression"])) # 仅演示,生产环境别用 eval
if name == "mock_web_search":
return "模拟结果:" + args["query"] + "(缓存)"
return "未知工具"
messages = [{"role": "user",
"content": "读一下 notes.txt,然后算出里面数字的总和。"}]
# 核心循环:模型说要用工具,就执行并回填,再问一次
while True:
response = client.responses.create(
model=MODEL_NAME,
tools=tools,
input=messages,
)
messages += response.output
calls = [it for it in response.output if it.type == "function_call"]
if not calls: # 没有 tool_call,拿到最终答复
print(response.output_text)
break
for call in calls: # 一次可能返回多个 tool_call,必须按多处理
result = execute_tool(call.name, json.loads(call.arguments))
messages.append({"type": "function_call_output",
"call_id": call.call_id,
"output": result})
回头对照第二章那幅五步流程示意图,你写的 while 循环把五步压成了图里这三块:请求、执行、回填:

图注:同一个最小 Agent 循环的三个阶段,从左到右:请求(把消息和工具定义发给模型,拿到一个 tool_call 卡片)、执行(应用代码把调用分发给本机工具)、回填(把结果拼回消息再请求,这次不再返回 tool_use)。第三块有一道弯箭头指回第一块,表示 while 循环;下方注释条把「思考」对应到请求阶段、「观察」对应到回填阶段。
跑通之后,对照着确认四个要点。第一,模型只产出 tool_call,循环里真正干活的 execute_tool 是你写的。第二,选型靠 description,它应该写成「只看这一条说明就能用对」的水平,就像递给一个实习生的任务说明。第三,strict 模式的两条硬性要求:additionalProperties 为 false、所有字段进 required,可选字段用 ["string", "null"] 表达,不满足请求会被直接拒绝。第四,一次可能返回多个 tool_call,代码里用 for 循环逐个执行、逐个回填;如果用的是推理型模型,中间产出的 reasoning 条目也要一并回填,丢了下一次上下文就断了。
现在你亲手跑通了循环,它出问题时怎么修?最后一章给一张检修表。
6. 踩坑检修表:工具选错、上下文超限、循环失控的根因与对策
自己写完循环之后,坏掉是常态。好消息是坑的种类不多,而且都能从「循环结构 + 上下文预算」两个根因推出来。Anthropic 在长任务 harness 那篇里总结过四类最常见的失败模式,直接拿来当检修表骨架:LongAgentHarness
| 失败模式 | 表现 | 对策 |
|---|---|---|
| 过早宣告胜利 | 看到项目有点进展就宣布整个完成 | initializer 建 feature list,全部标 failing,coding agent 每轮只做一个 |
| 环境留脏活 | 上轮把环境改坏、进度没记录 | progress 文件 + git log 固定读取顺序,每轮结束必提交 |
| 误标完成 | 只跑了单元测试就标完成 | 必须端到端测试(打开页面、点按钮、看到回复)通过才算数 |
| 不会启动 | 每个会话都花大量时间摸索怎么把服务跑起来 | initializer 写 init.sh 一键启动,coding agent 开工先读 |
「误标完成」那条要求端到端测试,被测对象长什么样,可以参考下面这张图:

图注:端到端测试的「被测对象外观」。这是 agent 被要求验证应用功能时看到的界面快照,左侧是会话列表,主区域是欢迎页和示例卡片。它只说明被测应用长什么样,并不展示自动化操作过程本身。LongAgentHarness
工具侧的坑
围绕第二章的机制,工具侧最常见的是这三类。
第一,工具选错,先改 description 而不是换模型。选型靠 description 与问题的比对,说明书写得含糊,再强的模型也会挑错。OpenAI 文档给了一个很好用的检验法,叫「实习生测试」:能不能让一个只看 schema 的实习生不犯错。能,你的 description 就合格了。Function Calling
第二,strict 不满足会被直接拒绝,报错会指出缺哪个约束。跑起来被拒,先检查 additionalProperties 和 required,别怀疑模型。
第三,一次返回多个 tool_call 时,同一个工具可能被连续调用多次。注意这类调用要幂等,或者干脆把并行关掉(parallel_tool_calls=false),确保一次只调一个。另外再次提醒:reasoning 模型的中间推理条目必须回填进下一次请求。
上下文侧的坑
上下文超限最隐蔽的形态不是报错,而是失忆。窗口塞满后,要么被截断,要么被压缩成摘要(compaction),摘要会丢掉细节,之后的所有推理都建立在一份残缺的记忆上。上下文窗口一刷新,相当于 agent 换了个新脑子。这也从反面印证了第三章的解法:重要的状态一律放文件,别放对话。LongAgentHarness
更深的层次来自 CrystalMem 命名的 memory hysteresis:即使你建了记忆库,预算被压再恢复,能力也回不到原位,被压掉的细节不会自动长回来。CrystalMem 所以凡是宣称「记忆被压缩了但我们没损失」的方案,都值得追问一句:恢复之后呢?
收口
三类坑归根到底两个根因。工具选错,是循环「决策」这一环的判断依据不充分、不清晰,解法是打磨 description 和工具边界;上下文超限与循环失控,是窗口预算的必然暴露,解法是把状态搬出窗口、把每一轮要做的事收窄。以后判断任何新框架,养成一个习惯:先问它的能力记在哪里,每轮要占多少窗口。答案越具体,这个框架就越可靠。能回答这两个问题的 Agent,才算真正被你握在了手里。
参考文献
本专题引用的核心资料如下,建议深入学习时阅读原文:
- ReAct: Synergizing Reasoning and Acting in Language Models:arXiv:2210.03629
- Reflexion: Language Agents with Verbal Reinforcement Learning:arXiv:2303.11366
- A Survey on Large Language Model based Autonomous Agents:arXiv:2308.11432
- Large Language Model Agent: A Survey on Methodology, Applications and Challenges:arXiv:2503.21460
- Equipping agents for the real world with Agent Skills(Anthropic Engineering):anthropic.com
- Effective harnesses for long-running agents(Anthropic Engineering):anthropic.com
- OpenAI Function Calling / Tools 官方文档:developers.openai.com
- LLM Powered Autonomous Agents(Lilian Weng):lilianweng.github.io
- AutoGen v0.4(Microsoft Research Blog):microsoft.com
- AgentStream: How Well Do Self-Evolving LLM Agents Perform Under Streaming Tasks?:arXiv:2608.00155
- CrystalMem: Elastic Memory for Self-Evolving LLM Agents via Knowledge Crystallization:arXiv:2608.00303
- From Language to Action: A Review of Large Language Models as Autonomous Agents and Tool Users:arXiv:2508.17281