Context Windows Are Not Memory: What AI Agent Developers Need to Understand
TL;DR · AI 摘要
上下文窗口不等于记忆,AI代理开发者需理解其区别及如何通过检索、压缩和摘要实现真正的记忆持久性。
核心要点
- 上下文窗口是无状态的,每次API调用都从零开始。
- 大上下文窗口不等于持久记忆,类似使用大办公桌代替文件柜。
- AI代理应作为数据库管理员,而非数据库本身,以实现记忆持久性。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 上下文窗口不等于记忆
- 上下文窗口的特性
- 无状态
- 每次调用从零开始
- 记忆持久性的实现
- 检索
- 压缩
- 摘要
金句 / Highlights
值得收藏与分享的关键句。
上下文窗口是无状态的,每次API调用都从零开始。
大上下文窗口不等于持久记忆,类似使用大办公桌代替文件柜。
AI代理应作为数据库管理员,而非数据库本身,以实现记忆持久性。
上下文窗口不是内存:AI代理开发人员需要了解的内容
上下文窗口不是内存:AI代理开发人员需要了解的内容
By
on
June 24, 2026
in
Artificial Intelligence
2
Share
Post
在本文中,你将了解为什么大上下文窗口并不等同于代理内存,以及检索、压缩和摘要等技术如何在代理的认知堆栈中协同工作。
我们将涵盖的主题包括:
- 为什么上下文窗口的行为更像是一个无状态的草稿纸,而不是持久内存。
- 检索增强生成、压缩和摘要各自在管理进入该草稿纸的内容方面扮演着不同的角色。
- 代理如何通过充当数据库管理员而非数据库本身,实现真正的内存持久性。
引言
上下文窗口是现代AI模型,特别是语言模型的一个关键方面,这些模型可以关注并利用有限数量的输入和先前对话——通常以标记数量来衡量——在生成响应时一次性处理。
当一个AI实验室发布一个具有200万标记上下文窗口的模型时,一些开发人员本能地这样想:“让我们把整个代码库塞进提示中!内存问题解决了!”然而,这里有一个例外。将一个巨大的上下文窗口视为“内存”,从架构角度来看,类似于因为不愿意购买文件柜而购买一个25英尺宽的办公桌。当然,你可以将所有文件摊开在你面前,但一旦工作会话结束,桌面上的所有文件都会被清理人员清除!
为了澄清这一区别并解开其他相关概念的神秘面纱,本文将对AI代理认知堆栈中的多个层次进行概念性分析。我们将使用几个与办公室相关的比喻,以帮助更好地理解这些概念。
上下文窗口
在AI模型中,特别是基于代理且使用底层语言模型的模型中,上下文窗口就像一个桌面或无状态的草稿纸。需要指出的是,模型本质上是完全无状态的。无论如何,每次对模型的API调用都从“步骤零”开始。
当向代理传递一个超过200,000个标记(大上下文窗口)的对话历史时,它并不是在记住之前某个时间点发生的事情。相反,它会迅速从头开始重新阅读“它的宇宙”,这只需要几毫秒。从长远来看,在基于代理的环境中依赖这种策略可能会引入一些危险(甚至致命)的陷阱:
- AI模型就像一个懒惰的学生,对一个巨大提示(文本)的开头和结尾部分非常关注,但对深埋在中间部分的观念和事实则完全忽略。
- 有一种滚雪球效应:随着对话的增长,代理必须在每一步重新发送和重新阅读整个历史,包括最早、通常不相关的对话。
- 在延迟方面,有一种“大脑冻结”效应,因此在面对大量文本时,模型需要一些时间才能开始生成响应中的第一个字。
为了使这一点更具体,考虑一下单个API调用在内部实际上看起来是什么样子。由于模型在调用之间不保留任何记忆,因此必须完整地重新发送所有先前的对话,只是为了提出一个新问题:
model.generate( messages=[ {"role": "user", "content": "步骤 1:我们把这个变量叫做 session_id。"}, {"role": "assistant", "content": "明白了,我之后会使用 session_id。"}, # ... 每一次中间的对话都必须重新发送 ... {"role": "user", "content": "步骤 47:我们在步骤 1 中同意使用哪个变量名?"} ] )
1
3
4
5
6
7
8
model
.
generate
(
messages
=
[
{
"role"
:
"user"
,
"content"
"步骤 1:我们把这个变量叫做 session_id。"
}
"assistant"
"明白了,我之后会使用 session_id。"
... 每一次中间的对话都必须重新发送 ...
"步骤 47:我们在步骤 1 中同意使用哪个变量名?"
]
)
仅步骤 47 就会迫使整个桌面——所有之前的 46 步——重新放回桌面,仅仅是为了回答关于步骤 1 的一个问题。这就是上面描述的雪球效应,具体化之后的表现。
检索
检索增强生成(RAG)系统就像办公室里的一排大书架,能够以“按需”的方式帮助获取与当前步骤相关且静态的现有数据。RAG 系统在用户提出某个问题时,会将最相关的 K 个文档片段拉取到草稿板(上下文窗口)中:当然,这些被检索到的文档是那些被判定为与用户的问题或提示语义上最相关的文档。
然而,当代理参与其中时,情况并不那么简单,因为向量相似性(RAG 系统中使用的相似性度量和数据表示类型)在某些情况下并不一定等同于语义上的真实性。例如,假设用户告诉他们的日程安排代理将会议移到星期五,之后又说“取消星期四的会议,因为 Alice 生病了。”一个向量搜索引擎可能会从文档库中检索到这两个声明,即使它们彼此矛盾。代理及其相关的语言模型必须能够充当能够判断哪条声明更符合当前现实情况的会计师。
一个简单的 RAG 流程只是将检索到的内容简单地连接起来,并让模型自行猜测哪条指令仍然有效。更可靠的方法是在生成之前就解决冲突,例如优先考虑最近记录的声明:
retrieved_chunks = [ {"text": "Move meeting to Friday", "timestamp": "2025-01-10T09:00:00"}, {"text": "Cancel Thursday, Alice is sick", "timestamp": "2025-01-12T14:30:00"} ] # 在它们到达提示之前就解决矛盾的片段 latest_relevant = max(retrieved_chunks, key=lambda chunk: chunk["timestamp"])
retrieved_chunks
"text"
"Move meeting to Friday"
"timestamp"
"2025-01-10T09:00:00"
"Cancel Thursday, Alice is sick"
"2025-01-12T14:30:00"
在它们到达提示之前就解决矛盾的片段
latest_relevant
max
key
lambda
chunk
这一行协调逻辑的差异在于,一个代理会自信地重复一条过时的指令,而另一个代理则能正确知道会议已被取消。
压缩
这在你熟悉将文件压缩为 ZIP 文件时就很容易理解。在代理和语言模型的语境中,这意味着一些算法层面的 token 减少:保持关键基础数据不变,同时在某个步骤中减少其在提示中的物理占用空间。实现这一点的技术包括去除停用词、将原始文本传递给特定的压缩模型(如 LLMLingua)或使用提示缓存。本质上,这是一种带宽优化策略,用于在诸如将一个 15K token 的 JSON 负载压缩到 5K token 的情况下使用,从而为模型的主任务腾出足够的临时空间。
实际上,这可能看起来简单得就像在负载到达主提示之前,将其通过一个压缩模型进行路由:
raw_payload = json.dumps(large_api_response) # 约 15,000 tokens compressed_payload = compress_with_llmlingua( raw_payload, target_token_count=5000 ) prompt = f"Given this data: {compressed_payload}\n\nAnswer the user's question."
raw_payload
json
dumps
large_api_response
约 15,000 tokens
compressed_payload
compress_with_llmlingua
target_token_count
5000
prompt
f
"Given this data: {compressed_payload}\n\nAnswer the user's question."
原始的事实在传输过程中保持不变;只是它们在桌面上的占用空间减少了。
摘要
与压缩不同,摘要会移除原始数据,并用抽象内容代替。必须将其视为一种单向旅程,本质上是不可逆的。因此,当应用上下文摘要时,一个良好的、几乎是强制性的做法是使用分支存储:将原始转录文本存入便宜的存储,如 S3 存储桶或基本的 SQL 表,然后仅将合成的摘要传递到活动提示中。
这种分支存储模式可以简单地表示为一个两步写入过程,一个写入冷存储,另一个写入活动提示:
def summarize_turn(raw_transcript, session_id, turn_id): # 1. 将原始、未删减的转录文本持久化到冷存储 s3_client.put_object( Bucket="agent-transcripts", Key=f"{session_id}/turn_{turn_id}.json", Body=raw_transcript ) # 2. 为活动提示生成一个简洁的摘要 summary = summarizer_model.generate(raw_transcript) # 3. 仅摘要重新进入上下文窗口 return summary
9
10
11
12
13
def
summarize_turn
raw_transcript
session_id
turn_id
1. 将原始、未删减的转录文本持久化到冷存储
s3_client
put_object
Bucket
"agent-transcripts"
"{session_id}/turn_{turn_id}.json"
Body
raw
_
transcript
2. 为活动提示生成一个简洁的摘要
summary
summarizer_model
3. 仅摘要重新进入上下文窗口
return
如果后续步骤需要原始细节,它总是可以从 S3 中检索到。与压缩不同,摘要从不需要从活动提示本身内部进行重建。
作为状态机的记忆持久化
代理中的记忆持久化经常被默认,尤其是对初级开发者而言。但要赋予代理真正的记忆,它不应充当数据库,而应充当数据库管理员。假设用户说:“我的狗的名字是 Goofy,但我们可能会给他改名为 Pluto”。然后代理应能够明确触发如下工具调用:
{ "tool": "update_entity_graph", "params": { "subject": "User_Dog", "attribute": "Name", "value": "Goofy", "notes": "Considering Pluto" } }
"tool"
"update_entity_graph"
"params"
"subject"
"User_Dog"
"属性"
"名称"
"值"
"Goofy"
"备注"
"考虑冥王星"
无论它是否由标准 SQL 表、知识图谱或 Redis 支持,无论如何,代理应在每一轮开始时查询状态机,并在该轮结束时提交更新。作为循环,这种“先查询后提交”的纪律看起来如下:
def agent_turn(user_message, entity_graph): # 在每一轮开始时查询现有状态 current_state = entity_graph.query(subject="User_Dog") response = model.generate( messages=[{"role": "user", "content": user_message}], context=current_state ) # 在每一轮结束时提交任何更新 for call in response.tool_calls: entity_graph.update(**call.params) return response
14
agent_turn
user_message
entity_graph
在每一轮开始时查询现有状态
current_state
query
subject
response
context
current
state
在每一轮结束时提交任何更新
for
call
tool_calls
update
*
params
总结
通过这些概念,你现在应该对基于语言模型的代理在上下文管理中起作用的元素有了更清晰的认识。这个教训很简单:不要试图购买一张巨大的、有 1000 万个标记的桌子。相反,只需买一张普通的桌子,给你的代理一支锋利的铅笔,并教它如何打开文件柜并有效地利用其内容来完成工作。
关于此主题的更多信息
- 超越短期记忆:长期记忆的三种类型
- 关于 Python 如何管理内存你需要知道的一切
- 为开发者准备的机器学习课程
- 为开发者准备的快速 R 语言入门课程
- 为机器学习开发者准备的 Python 快速入门课程
- Python 开发者关于 Pydantic 的完整指南
/.entry