Context Window 终极指南:为什么 Agent 聊得越久,反而越容易出错?
做 Agent 时,我遇到过一个很反直觉的问题:
明明给模型的历史信息更多了,回答却不一定更好。
有一次我让 Agent 根据前面的需求继续生成一份项目方案。刚开始还能跟上,但聊到后面,它开始出现:
- 把早期已经否定的方案重新拿出来;
- 忘记用户刚刚确认过的格式;
- 工具调用结果被后续内容淹没;
- 同一个问题重复解释;
- Token 消耗越来越高,响应越来越慢。
后来我才意识到,问题不只是“模型记性不好”,而是 Context Window(上下文窗口)管理得不好。
上下文窗口不是简单的“能放多少字”,而是直接影响 Agent 的理解、规划、工具调用和任务完成率。
一、Context Window 到底是什么?
可以把 Context Window 理解成:
模型一次能够看到并处理的全部上下文空间。
它通常包括:
System Prompt
+
用户当前问题
+
历史对话
+
工具调用记录
+
工具返回结果
+
检索到的知识
比如一次 Agent 请求可能是:
系统规则:2000 tokens
历史对话:6000 tokens
工具结果:5000 tokens
知识库内容:4000 tokens
当前问题:500 tokens
总共就是 17500 tokens。
这意味着,窗口越大,不代表一定越好。真正重要的是:
相关信息占比
+
信息顺序
+
噪声多少
+
成本与延迟
二、为什么上下文太长反而会出问题?
1. 关键信息被淹没
前面有 30 页聊天记录,真正有用的可能只有其中 3 段。
模型虽然“看到了”,但未必能准确抓住重点。
2. 旧信息与新信息冲突
用户前面说:
默认使用 Python
后面又说:
现在项目改用 Java
如果没有明确更新机制,模型可能把两条都当成有效信息。
3. 成本和延迟上升
每轮都把完整对话重新发送,Token 消耗会持续增加。
4. 工具结果占据大量空间
SQL 查询结果、网页内容、日志文件往往比用户问题长得多,如果全部塞进上下文,很容易挤掉真正重要的信息。
三、上下文管理的第一步:不要什么都放
我现在会把上下文拆成几类:
必须保留:
– 系统规则
– 当前任务目标
– 最近几轮对话
– 当前步骤的工具结果
可以压缩:
– 很早的历史对话
– 已完成的中间过程
– 重复说明
应该外置:
– 大型文档
– 长日志
– 全量数据库结果
– 不相关历史
原则很简单:
模型需要的是“当前决策所需的信息”,不是所有历史原文。
四、四种常见的上下文管理策略
1. 滑动窗口
只保留最近 N 轮对话:
messages = messages[–20:]
优点是简单、成本可控。
缺点是旧信息可能直接丢失。
2. 历史摘要
把很早的对话压缩成一段摘要:
用户是 Java 后端开发,正在准备秋招;
偏好中文、Markdown、详细解释;
当前项目是企业级多 Agent 工作台。
这比保留 100 条原始消息更有效。
3. RAG 检索
需要旧资料时,不把全文一直放在上下文里,而是临时检索最相关的几段。
当前问题
↓
检索相关文档
↓
只注入 Top-K 内容
4. Memory 外置
把稳定的用户信息、长期目标和任务状态放到独立存储中,需要时再取出来。
五、如何构造一个干净的 Agent Context?
我比较推荐把上下文分成四层:
context = {
"system_rules": system_prompt,
"task_state": current_task_state,
"recent_messages": recent_messages,
"retrieved_memory": relevant_memory
}
最终再按固定顺序组装:
1. 系统规则
2. 当前任务目标
3. 必要的历史摘要
4. 相关记忆
5. 当前问题
6. 当前步骤的工具结果
这个顺序很重要。
如果把大量工具日志放在最前面,模型可能会把日志当成主要任务;如果把当前目标埋在最后,也容易出现方向漂移。
六、一个简单的上下文压缩实现
def build_context(system_prompt, summary, recent_messages, tool_results, question):
context = [
{"role": "system", "content": system_prompt},
{
"role": "system",
"content": f"历史摘要:
{summary}"
}
]
context.extend(recent_messages)
if tool_results:
context.append({
"role": "system",
"content": f"当前工具结果:
{tool_results}"
})
context.append({
"role": "user",
"content": question
})
return context
如果历史太长,可以先摘要:
def summarize_history(messages):
text = "
".join(
f"{m['role']}: {m['content']}"
for m in messages
)
return llm_generate(
f"请提取任务目标、用户偏好、已完成步骤和未解决问题:
{text}"
)
注意摘要不要只总结“聊了什么”,还要保留:
已确认决策
待办事项
失败记录
用户偏好
关键约束
七、Context Window 和 Memory 的区别
这两个概念很容易混在一起。
Context Window:
模型这一轮能看到什么
Memory:
系统长期保存什么
举个例子:
Memory:
用户偏好 Markdown
Context:
本轮任务需要生成项目方案
Memory 是外部存储,Context 是当前送给模型的工作集。
所以真正的 Agent 通常是:
长期 Memory
↓
检索相关信息
↓
组装当前 Context
↓
交给 LLM
八、如何验证上下文管理是否有效?
建议至少比较三种方案:
方案 A:完整历史直接拼接
方案 B:滑动窗口 + 摘要
方案 C:滑动窗口 + 摘要 + RAG + Memory
重点测:
- 任务完成率;
- 关键约束保留率;
- 工具调用正确率;
- 平均 Token 消耗;
- P95 响应时间;
- 用户纠正次数。
例如:
完整历史:
完成率 62%,Token 2800,平均 12.3s
窗口 + 摘要:
完成率 74%,Token 2100,平均 10.8s
窗口 + 摘要 + RAG + Memory:
完成率 93%,Token 1600,平均 8.5s
这类对比更能说明上下文治理的价值。
九、最常见的坑
1. 把所有内容都塞给模型
上下文不是越多越好,重点是相关性。
2. 只压缩历史,不保存决策
如果摘要没有保留“用户已经确认的方案”,后面还是会重复讨论。
3. 工具结果不做裁剪
大型 JSON、日志和网页正文应该先过滤、截断、结构化。
4. 不区分不同任务
代码调试、文档写作、知识问答需要的上下文完全不同,不能用同一套模板硬套。
5. 没有成本监控
建议记录:
输入 Token
输出 Token
总 Token
单轮耗时
摘要耗时
检索耗时
最后
我现在越来越觉得,Context Window 不是大模型的一个参数,而是 Agent 的“工作记忆空间”。
如果管理得好:
Agent 更稳定
任务更连续
成本更可控
工具调用更准确
如果管理得不好:
信息越多,噪声越大
历史越长,错误越难定位
模型越强,系统越难控制
真正成熟的 Agent,不是把所有历史都交给模型,而是能够判断:
这一轮,哪些信息必须让模型看到?哪些信息应该摘要?哪些信息应该去知识库或 Memory 里临时取?
这才是上下文工程真正要解决的问题。
标签
#Agent #ContextWindow #上下文工程 #RAG #Memory #大模型应用 #后端开发
网硕互联帮助中心



评论前必须登录!
注册