大模型应用做得越久,越会发现一个残酷事实:真正拖慢系统、拉高成本、制造幻觉的,往往不是模型不够强,而是上下文塞得太乱。
历史会话摘要不是“把前面聊过的内容压缩成一段话”这么简单。生产级上下文工程要做的是:把会话沉淀成一份结构化、可验证、可增量更新、可检索的状态。模型每次回答时,不需要重读全部历史,只需要看到当前任务最相关的状态。
一、问题根源:长会话不是“记忆变强”,而是上下文变脏
全量历史塞入上下文会带来成本、注意力、冲突和遗忘问题
很多项目一开始会这样做:用户每问一句,就把前面所有 user、assistant、tool 消息原封不动拼进 prompt。刚开始没问题,十几轮之后就开始出事:输入 token 暴涨、工具日志占满窗口、旧方案和新方案互相打架,模型还会突然忘记用户刚确认过的约束。
更隐蔽的问题是:上下文越长,模型不一定越聪明。过期信息、重复寒暄、长日志、废弃决策,都会抢走模型的注意力。最后模型不是在“理解历史”,而是在噪声里捞答案。
LangGraph 官方文档把短期记忆超出上下文窗口时的常见处理拆成几类:裁剪消息、删除消息、把早期历史总结成摘要、管理 checkpoint 和自定义过滤策略。这里的关键不是“删掉多少”,而是“留下什么”。
二、结构化摘要的本质:它不是总结,是会话状态
一句话版总结:结构化摘要 = 当前会话的状态表。
普通摘要通常长这样:“用户想安装 Qwen Code,遇到 npm 报错,后来 npm 正常了,建议选择 qwen3.7-max。”这种写法能给人看,但不适合系统长期使用。因为程序不知道里面哪些是事实、哪些是决定、哪些是风险、哪些是待办,也不知道哪些信息来自哪一轮消息。
结构化摘要要像一个小型状态数据库。它应该拆出目标、事实、决策、约束、待办、风险、用户偏好、证据来源、更新时间、置信度。这样模型下一轮回答时,不需要翻完整聊天记录,也能知道“现在任务做到哪了、哪些话不能忘、哪些事情还没做”。
三、推荐摘要 Schema:这 10 类字段最实用
生产环境里,不建议只用一个 summary 字符串。推荐至少拆成下面这些字段。
字段 | 作用 | 示例 | 注意点 |
session_goal | 当前会话要完成什么 | “配置 Qwen Code 接入百炼并跑通项目” | 目标变更时要覆盖旧目标 |
user_preferences | 用户表达过的稳定偏好 | “中文、步骤细、不要无关内容” | 不要把一次性要求误当长期偏好 |
facts/entities | 硬事实、路径、版本、ID、订单号 | Node v24.14.1,项目路径 E:\xm\... | 数字、路径、Key 名必须原样保留 |
decisions | 已经确认的选择 | “AI Coding 模型选 qwen3.7-max” | 每条最好带 evidence_msg_id |
constraints | 环境限制、业务边界 | Windows PowerShell、不能暴露 API Key | 优先级高于一般建议 |
todo | 下一步要做什么 | 进入项目目录运行 qwen /init | 完成后要移动到 done |
open_questions | 仍需确认的问题 | 是否需要配置 MCP、是否要接 Git | 不要乱猜 |
risks | 潜在问题 | 不要在 home 目录运行;npm 源可能慢 | 用于主动提醒 |
source_range | 摘要覆盖的消息范围 | msg_31-msg_47 | 方便回放和审计 |
confidence | 低置信信息标记 | rumor、user_claim、verified | 防止传闻变成事实 |
字段不一定越多越好,真正要看业务。客服场景更重视订单号、工单状态、用户承诺;AI Coding 场景更重视文件路径、错误日志、已修改文件、测试结果;内容创作场景更重视选题、风格、禁用词和结构。
四、三层记忆架构:原始日志、结构化摘要、长期记忆
结构化摘要不是替代数据库,也不是替代向量库。更合理的架构是三层:
原始会话日志:完整保存每条消息、工具调用、返回结果、token 数、时间戳,用于审计、回放、重建摘要。
结构化摘要:每个 session 维护一份最新摘要,作为短期工作记忆,通常每轮或每个阶段进入 prompt。
长期记忆:把跨会话可复用的信息抽取成事实或规则,比如用户偏好、项目约定、业务流程,通过检索按需拉回。
MemGPT 的思路很有启发:不要幻想把一切都塞进有限上下文,而是像操作系统管理内存一样,在不同记忆层之间搬运信息。上下文窗口像 RAM,只能放当前最需要的东西;原始历史和长期记忆像磁盘,保留但按需读取。
五、完整流程:历史会话结构化摘要 10 步走
监控 token:每轮请求前估算 messages、tools、retrieval、summary 的总 token。
判断触发:达到阈值、轮次过多、阶段切换、工具返回巨大结果、用户显式要求总结时触发。
准备输入:取旧 summary、最近新增消息、关键工具结果摘要,不要把全量原始历史都扔给摘要模型。
信息分类:把内容分到目标、事实、决策、约束、待办、风险、冲突、用户偏好等槽位。
去重合并:重复事实只保留一份;过期决策降权;同一字段出现新版本则标记覆盖。
冲突检测:旧摘要和新消息矛盾时,不要混在一起,要写清谁覆盖谁、证据来自哪一轮。
生成 JSON:用固定 schema 输出,限制最大 token,禁止模型自由发挥。
程序校验:检查 JSON 是否可解析、字段是否齐全、是否包含不存在的事实、是否丢失关键 ID。
落库版本化:写入 summary_vN,保留 source_msg_range、hash、token_count,方便回滚。
上下文拼装:下一轮只拼系统指令、最新摘要、检索到的相关记忆、最近 N 轮和当前问题。
六、什么时候触发摘要:别等到爆窗,也别每轮都压缩
推荐用 token 阈值、轮次、事件和人工触发组合控制摘要刷新
一个实用策略是:60% 上下文占用时开始预警,70%-75% 做轻量摘要,85% 以上强制摘要。对于 AI Coding、RAG 问答、客服工单这类长链路任务,还要结合事件触发。
工具返回很大:比如搜索结果、日志、SQL 查询、测试输出,只保留结论和关键证据。
任务阶段切换:从排错进入修复、从讨论进入执行、从方案 A 切到方案 B。
用户确认决策:比如“就用 qwen3.7-max”“按这个方案部署”,必须写入 decisions。
用户显式要求:比如“总结一下当前进度”“重新开窗口交接一下”。
不要每轮都总结。每轮总结会多花一次模型调用,延迟和成本上升,而且摘要一旦写错,错误会不断被新摘要继承,形成“摘要漂移”。
七、上下文拼装:摘要不是单独用,要和检索、最近消息配合
上下文拼装要给不同信息设置预算和优先级
很多系统做了摘要仍然效果差,是因为拼装上下文没有配额。正确做法是把上下文当成有限空间,每类信息都有预算。
一个通用比例可以这样起步:系统指令 10%,结构化摘要 20%,相关长期记忆 20%,最近消息 35%,当前问题和输出要求 15%。实际项目中,这个比例要根据任务调。客服类最近消息可以少一些,代码类最近文件和测试结果要多一些,RAG 类检索证据要更重。
Anthropic 在上下文工程文章中提到,压缩时要保留架构决策、未解决 bug、实现细节,同时丢弃冗余工具输出。这一点对 AI Coding 尤其重要:代码 Agent 不需要每次看到完整日志,但必须知道“哪个测试失败、失败原因是什么、改了哪些文件、下一步要验证什么”。
八、冲突合并:新旧摘要打架时,不能让模型自己猜
结构化摘要要有冲突检测和合并规则
会话越长,冲突越常见。用户可能先说用 A 方案,后面又说改用 B;模型可能先建议一个路径,后来发现路径不对;工具返回结果可能推翻之前判断。
冲突字段要用明确规则处理:
时间优先:同一字段的后续确认通常覆盖前面信息。
证据优先:工具返回的可验证结果优先于模型猜测。
用户确认优先:用户明确拍板的选择优先于助手建议。
无法判断就追问:写入 open_questions,不要在 summary 里编一个答案。
低置信打标:传闻、推测、未验证数据不要写成事实。
九、落库表设计:不要只把摘要塞到 Redis 里就完事
原始日志、摘要版本、长期记忆三类数据要分表管理
生产系统建议至少三张表:chat_messages、session_summaries、memory_facts。
CREATE TABLE chat_messages (
id BIGINT PRIMARY KEY,
session_id VARCHAR(64),
role VARCHAR(32), -- user / assistant / tool
content TEXT,
tool_name VARCHAR(128),
token_count INT,
created_at TIMESTAMP
);
CREATE TABLE session_summaries (
id BIGINT PRIMARY KEY,
session_id VARCHAR(64),
version INT,
summary_json JSON,
source_msg_start BIGINT,
source_msg_end BIGINT,
summary_hash VARCHAR(64),
token_count INT,
created_at TIMESTAMP
);
CREATE TABLE memory_facts (
id BIGINT PRIMARY KEY,
user_id VARCHAR(64),
fact_type VARCHAR(64), -- preference / project_rule / business_fact
fact_value TEXT,
evidence_msg_id BIGINT,
confidence DECIMAL(4,3),
embedding_id VARCHAR(128),
updated_at TIMESTAMP
);
原始消息用于审计和重建;摘要版本用于给模型续上下文;长期记忆用于跨会话复用。三者不要混在一张表里,否则后面很难做权限控制、过期清理和质量评估。
十、Prompt 模板:让模型按状态机方式总结
摘要生成、校验、检索三类 Prompt 要分开
摘要 Prompt 不要写成“请总结前面的聊天”。要明确角色、输入、输出 schema、禁止项和校验规则。
你是一个“会话状态整理器”,只负责根据输入内容更新会话摘要。
输入:
1. old_summary_json:上一版结构化摘要
2. new_messages:自上一版摘要之后新增的 user / assistant / tool 消息
3. schema:必须遵守的 JSON 字段定义
任务:
- 合并 old_summary_json 与 new_messages,输出 new_summary_json。
- 保留用户目标、硬事实、路径、版本号、订单号、已经确认的决策、未完成待办。
- 删除寒暄、重复解释、完整工具日志和已经废弃的临时方案。
- 如果新旧信息冲突,写入 conflicts,并说明采用哪个版本和证据消息 ID。
- 如果无法判断,写入 open_questions,不要猜。
- 不允许添加输入中不存在的事实。
输出:
只输出合法 JSON,不要输出 Markdown。
还要有一个独立的校验 Prompt 或程序校验逻辑,专门检查摘要是否漏字段、是否编造、是否丢失关键 ID。严肃业务里,摘要生成和摘要校验最好用两个不同模型或两套不同提示词,降低同类错误。
十一、Java + Python 项目里怎么落地
如果是 Java 做业务系统、Python 做 AI 服务,可以这样分工:
Java 层负责 session、用户、权限、消息落库、token 统计、接口编排、风控和监控。
Python AI 服务负责摘要生成、向量检索、summary 校验、上下文拼装和模型调用。
两边用 gRPC / HTTP 调用,所有请求都带 session_id、summary_version、trace_id。
上线前必须保留 summary_prompt、summary_output、最终 prompt 的日志,方便回放问题。
def build_context(session_id, user_query):
summary = load_latest_summary(session_id)
recent_messages = load_recent_messages(session_id, max_tokens=4000)
memories = retrieve_memory(user_query, top_k=5)
context = [
system_prompt(),
{"role": "assistant", "content": "当前会话状态:" + json.dumps(summary, ensure_ascii=False)},
{"role": "assistant", "content": "相关长期记忆:" + format_memories(memories)},
*recent_messages,
{"role": "user", "content": user_query}
]
return trim_by_budget(context)
def maybe_update_summary(session_id):
stats = estimate_context_usage(session_id)
if stats.input_ratio < 0.72 and not stats.has_stage_change:
return None
old_summary = load_latest_summary(session_id)
new_messages = load_messages_after(old_summary.source_msg_end)
candidate = llm_generate_summary(old_summary, new_messages)
validate_summary(candidate, old_summary, new_messages)
return save_summary_version(session_id, candidate)
十二、质量评估:结构化摘要不是省钱工具,首先是可靠性工具
结构化摘要上线前要评估成本、连续性、事实一致性和错误传播
结构化摘要上线前,至少准备一批长会话测试集:多轮客服、复杂代码排错、长篇内容创作、需求反复变更、工具调用失败重试。每条测试都跑两版:全量历史版和结构化摘要版。
看四个指标:
成本:输入 token 是否明显下降,摘要刷新带来的额外调用是否可接受。
连续性:用户偏好、关键 ID、已确认决策、未完成任务是否还能被正确召回。
一致性:摘要里的每个事实是否能追溯到原始消息或工具结果。
抗污染:错误信息进入摘要后,系统是否能发现、回滚、重建。
最终目标不是把摘要写得漂亮,而是让模型在第 50 轮、第 100 轮仍然像“刚刚认真读过前文”一样回答,同时成本更低、延迟更稳、错误更可追踪。
十三、最容易踩的 8 个坑
只做自然语言摘要,不做字段化,后续无法检索、无法校验。
摘要替代原始消息,导致出了问题无法回放。
把工具返回原文全部塞进摘要,压缩失败,还污染上下文。
不记录 source_msg_range 和 evidence_id,摘要里的事实无法追溯。
不处理冲突,新旧目标同时存在,模型回答开始摇摆。
每轮都总结,成本和延迟上升,错误摘要还会自我强化。
把敏感信息写入长期记忆,没有权限、过期和脱敏机制。
没有评估集,只凭体感判断“好像更聪明了”。
十四、一句话总结
大模型上下文工程的核心,不是把上下文窗口塞满,而是把正确的信息放到正确的位置。历史会话结构化摘要的价值,就是把杂乱的聊天记录变成清晰的会话状态:模型看得少了,但记得更准;系统花得少了,但回答更稳。
真正可落地的方案应该满足四个条件:原始日志可回放,摘要字段可校验,记忆内容可检索,上下文拼装可评估。做到这四点,长会话应用才不会越聊越贵、越聊越乱、越聊越失忆。