上下文工程与记忆
记忆分层设计、checkpointer vs store、上下文压缩策略、Lost in the middle、工具超大结果处理。
上下文工程与记忆
Agent 的「记忆」不是把历史消息无限堆进 prompt,而是一套分层存储 + 按需召回 + 主动压缩的工程。上下文工程是 2026 拉开差距的核心能力之一。
记忆分层设计
| 层 | 存什么 | 实现 |
|---|---|---|
| 工作记忆 | 当前任务轨迹 + 中间结论 | LangGraph 的 State / checkpoint |
| 会话记忆 | 最近几轮原文 + 较早历史 rolling summary(滚动摘要) | 内存 + 摘要 |
| 长期记忆 | 用户偏好、任务事实、历史结论 | 向量库/结构化库,相似度 + 时间衰减 + 重要性召回 |
写入质量(高分点):区分「事实 vs 推断」、附时间戳和来源、支持更新与撤销、矛盾检测、相似合并。
长期记忆不每轮全量注入——只注入与当前 query 相关的召回结果,否则上下文快速爆掉且引入噪声。
checkpointer vs store(分水岭题,别混)
| 维度 | Checkpointer | Store |
|---|---|---|
| 作用域 | 单线程(per thread_id) | 跨线程(cross-thread) |
| 存什么 | 图的完整状态快照(每步 State) | 语义化的长期记忆条目(KV + 向量检索) |
| 主要用途 | 中断恢复、时间旅行、重放、HITL | 用户偏好、跨会话事实积累 |
| 典型实现 | MemorySaver / AsyncPostgresSaver / MysqlCheckpointSaver | InMemoryStore / AsyncPostgresStore(支持 store.search()) |
| 类比 | 数据库的事务日志 | 数据库的业务表 |
面试话术:「短期记忆靠 checkpointer 按 thread_id 存状态快照,核心价值是让图能中断后恢复;跨会话的长期记忆用 store(BaseStore),支持按 namespace 检索。两者职责不同,不能互相替代。」
记忆方案对比:旧方案
ConversationBufferMemory本质是「把历史消息拼接进 prompt」,无状态管理、上下文线性膨胀;新方案 checkpointer 本质是「持久化整张图的状态」,天然支持中断恢复、时间旅行、HITL,并可配合摘要节点做上下文压缩。
上下文窗口满了怎么办
接近上限时按价值排序:
- 保留:系统指令、当前任务、关键约束、工具调用结果(事实锚点)
- 压缩:低价值历史对话(先做摘要,摘要也满再做层级摘要:会话级 → 日级 → 周级)
- 兜底:滑动窗口(最近 N 轮原文)、摘要压缩(rolling summary)、关键信息抽取存入长期记忆、Compaction(历史太长老裁不动时的重写/浓缩)
Lost in the middle
LLM 对上下文中间位置信息注意力显著低于首尾(长上下文精度下降的学术解释,Liu et al. 2023《Lost in the Middle》验证了定性规律)。对策:关键指令放首尾,中间塞低价值内容。
⚠️ 别背「遵循率 60%/85%」这类具体百分比——答不出出处会尴尬。稳妥说法是引用定性结论:「模型对上下文首尾信息利用最好,中间明显衰减,具体幅度随模型与任务变化」。给出处 > 背数字。
短期记忆存 Redis?TTL 是坑
记忆数据 ≠ 可被自动 evict 的缓存。Redis TTL 是为高并发线上业务设计的,用户记忆是最宝贵资产,过期应该 archive(归档)而不是 delete。
Redis 在 Agent 项目里的正确用途:模型响应缓存、检索结果缓存、分布式锁(多 Agent 并发写同一份记忆)、会话状态隔离、限流、临时 checkpoint。
长期记忆落持久化存储:向量库 / 图库 / 关系库(pgvector、Chroma、Neo4j、MySQL/PG 都行)。
工具返回超大结果(如 50KB 代码搜索结果)
直接塞上下文 → 爆窗口 + 注意力稀释 + token 烧钱。处理链:
- 源头截断:工具层做结果上限(top-K 限制、字段裁剪)
- 摘要压缩:大结果先过一次摘要/提炼再进上下文
- 分段按需:先回元信息(文件名列表),LLM 需要再取具体内容
- 结构化错误:超限时返回「结果过大已截断为前 N 条 + 计数」,让模型知道信息不完整