Agent Mentor Learn
Agent 的记忆和状态 · 第 1 / 6 节

第 1 课:上下文窗口就是 Agent 的全部记忆

学习目标:

  • 解释"Agent 记得住之前的对话"这句话为什么是一种幻觉
  • 说出一次请求的上下文窗口里,哪些内容算数、哪些不算数
  • 解释「上下文腐化」是什么现象,为什么窗口更大不等于更好用
  • 判断一次 API 调用是不是无状态的,以及这对记忆意味着什么

前置要求:读过前 4 门课,理解工具调用的往返协议 | 下一课 第 2 课 >>

一个看起来"记得住"的对话

你在跟一个客服 Agent 聊天:

text
用户: 我叫王芳,订单号是 ORD-2026-8842Agent: 好的王芳,我看一下 ORD-2026-8842 的情况……正在配送中,预计明天送达。用户: 那我上次买的那个呢?Agent: 你是指 ORD-2026-8842 这一单吗?它明天就到——如果你是问另一笔订单,能告诉我订单号吗?

第二轮里 Agent 显然"记得"你叫王芳、记得你问的是 ORD-2026-8842。这看起来就像它把这些信息存进了某个地方,下次见面还能想起来。

真相要朴素得多:每一次请求,你的代码都会把从对话开始到现在的全部消息重新打包,原样发给模型1。模型看到的不是"我记得你叫王芳",而是每次都重新读到一遍"用户说过:我叫王芳"。上一轮的每一句话、每一次工具调用,都得靠你的代码亲手塞进这一轮的请求里——模型自己什么都不存。

API 调用是无状态的

这句话说得再直白一点:模型这一次调用和下一次调用之间,没有任何东西被服务器悄悄留下来。这一次请求处理完,那些 token 就从模型"眼前"消失了。下一次请求如果什么都不带,模型看到的就是一张白纸,连你叫什么都不知道。

之所以感觉"记得住",是因为宿主应用替你做了这件苦力活:把完整的历史消息数组重新发一遍。这不是模型的能力,是你代码里那个数组在增长。这个特性有个名字,叫无状态——官方文档的原话是:Messages API 是无状态的,这意味着你每次都要把完整的对话历史发给 API1。服务器不为你的会话保留任何跨请求的私有状态,一切"记忆"都得由客户端自己携带、自己重发。

上下文窗口里,到底装了什么

模型能看到的东西,全部装在一个叫上下文窗口的容器里。官方文档说得很明确:一次请求里的所有东西都算数——系统提示词messages 数组里的每一条消息(包括工具结果、图片、文档)、你传进去的工具定义,连模型这一轮自己生成的输出(包括扩展思考)也算在内。2

这份请求里,systemtoolsmessages 数组里的三条消息,全部计入上下文窗口。模型的回答生成出来之后,那部分输出 token 同样要算进当前这一轮的窗口用量。响应里会带一个 usage 字段,告诉你这一轮实际消耗了多少输入和输出 token。2

没被塞进这份请求的东西——比如数据库里那笔还没被工具查询过的订单记录——不属于上下文窗口的一部分。模型看不到它,也没法凭空知道它存在。这就是为什么"记忆"必须靠某种机制主动搬进这份请求,而不是自动可及的。

窗口会腐化,不是越大越好

窗口能装多少东西是有上限的——不同模型的容量不同,官方文档给出的口径是最大可以到 100 万 token。2 但这不意味着往里面塞得越满越好。文档里点名了一个现象,叫上下文腐化:随着 token 数越堆越多,准确率和召回率会下降。2 也就是说,窗口里装的内容越多、越杂,模型从中提取出正确答案的能力反而会打折扣——这让"往上下文里放什么"本身,变得和"窗口还剩多少空间"一样重要。2

这一点对"记忆"的设计影响很直接:把整段历史无脑塞进上下文,既不是免费的(会占满窗口容量),也不是没有代价的(会让模型在一堆陈旧信息里更难找到当前真正相关的那句话)。第 2 课会讲具体怎么应对这种累积——历史该怎么截断、怎么摘要,而不是放任它无限增长。

"记忆"这个词,其实是个比喻

回到开头那个客服对话。当我们说"Agent 记得你叫王芳"时,准确的说法应该是:你的代码把包含"我叫王芳"这句话的完整历史,又一次原样发给了模型,模型在这次请求里重新读到了它。没有存储,没有回忆,只有重发

这不是在抠字眼。理解"记忆是重发出来的幻觉",直接决定了你接下来怎么设计一个真正"记得住"的 Agent:

  • 如果历史消息数组会无限增长,窗口迟早会被填满,还会撞上上下文腐化——这是第 2 课要解决的问题。
  • 如果有些信息需要在多个会话之间留存(不只是这一次对话内),就不能指望塞进 messages 数组里,得写到外部才行——这是第 3 课要讲的外部记忆。
  • 如果 Agent 需要在任务中途知道"自己做到哪一步了",这份进度同样得是上下文窗口里能看到的结构化状态,而不是模型凭直觉猜——这是第 4 课的内容。

这三条线索,其实都是同一个事实的不同推论:窗口里有什么,模型才知道什么;窗口里没有的,对模型来说就是不存在的。

小结

  • "Agent 记得住之前的对话"是一种幻觉:真相是宿主应用每次把完整历史重新塞进请求,模型自己不存储任何东西1
  • API 调用是无状态的,服务器不为会话保留跨请求的私有状态,一切"记忆"都得由客户端主动携带1
  • 上下文窗口装的是这次请求实际携带的一切:系统提示词、messages 里的每条消息(含工具结果、图片、文档)、工具定义,以及模型本轮的输出2
  • 窗口大小是有上限的,而且更大不等于更好用——上下文腐化会让 token 越堆越多、准确率和召回率越下降2
  • 理解"窗口里有什么模型才知道什么",直接引出后面三课要解决的问题:历史怎么管理、记忆怎么外部化、状态怎么结构化

>> 第 2 课:对话历史管理:追加、截断、摘要

Footnotes

  1. Using the Messages API — https://platform.claude.com/docs/en/build-with-claude/working-with-messages 2 3 4

  2. Context windows — https://platform.claude.com/docs/en/build-with-claude/context-windows 2 3 4 5 6 7

练习

01

下面是一次实际发给模型的请求(简化版),以及三样"额外存在"但没有出现在这份请求里的东西:

Level 1:标出哪些内容算入上下文窗口

额外存在的三样东西:

  1. 数据库里 ORD-2026-8842 这条订单记录的实际内容(还没被任何工具查询过)
  2. 上周同一个用户和另一个 Agent 会话时提到的收货地址
  3. 这次请求处理完之后,模型生成的回复文本

请分别判断:这四类内容(system / tools / messages 里的用户消息 / 上面额外列出的三样东西)哪些算入这次请求的上下文窗口,哪些不算,并各写一句理由。

完成标准 · 本地勾选
02

一个同事这样描述他正在设计的 Agent:"我们的对话已经进行到第 20 轮了,数据库里存着完整的历史记录,所以就算这一轮请求只发送用户最新说的一句话,模型也能接着往下聊,因为服务器那边知道这是同一个会话。"

Level 2:找出这段描述里的错误假设

指出这段描述里的错误假设是什么,并说明如果真的按这个思路实现,会在什么场景下出问题。

完成标准 · 本地勾选