多轮对话上下文太长怎么办:消息裁剪、摘要保留与 token 预算实验
来源:17golang原创
时间:2026-08-27 03:26:28 361浏览 收藏
聊天接口上线后,最先暴露的往往不是模型“不会回答”,而是对话一长就开始忘记前面的约束:用户明明说过“只返回 JSON”,第十几轮却混入了解释文字;工具调用的参数也越来越长,最后在请求阶段就被上下文窗口拒绝。解决这类问题,关键不是盲目把窗口换大,而是先给历史消息、摘要、当前问题和输出预留明确预算。
- 先为输出和工具调用预留空间,再决定历史消息最多保留多少。
- 最近几轮保留原文,较早内容压缩成带边界的摘要,不能把摘要当成绝对事实。
- 裁剪前后都要记录 token 估算、保留轮次和摘要版本,方便复现质量波动。
- 一旦命中窗口上限,应停止继续拼接,返回可恢复的状态,而不是无限重试。
先做一个能复现的上下文预算实验
先把一次请求拆成四块:系统约束、历史消息、当前用户问题、预留输出。设模型上下文上限为 W,工具定义和安全说明占用 T,输出预留 O,那么给历史消息的预算不是“窗口减去当前问题”这么简单,而是:
history_budget = W - T - O - current_input_tokens
例如本地测试把 W 设为 8192,系统与工具定义约 1100,输出预留 1200,当前问题约 260,那么历史预算只有 5632。这个数一开始就算清楚,后面每次裁剪才有稳定依据。
不要用字符串长度代替 token 计数。中文、代码、JSON 键名和英文标识符的切分方式不同;如果生产模型提供官方 tokenizer,应在服务端使用同一套规则。没有 tokenizer 时可以先用保守估算,但要给估算值留安全余量。

初始化消息结构:把可丢弃内容标出来
消息不要只保存 role 和 content。至少补上时间、会话轮次、重要级别和是否已经摘要,这些字段决定了裁剪时能不能做出可解释的选择。
{
"turn": 12,
"role": "user",
"content": "把订单查询改成只返回 JSON",
"importance": "constraint",
"summarized": false
}
建议把内容分成三档:系统约束和当前任务属于不可丢弃区;最近两到四轮属于原文保护区;更早的闲聊、已经完成的中间推理属于可摘要区。重要级别不是越多越好,生产里通常只需要 constraint、task、context 三种。
编写裁剪函数:先保约束,再保最近轮次
下面的伪代码使用 token 估算函数 count_tokens,重点是顺序而不是某一家 SDK 的调用方式。裁剪时先放入不可丢弃消息,再从后往前加入最近对话,最后把旧内容摘要放在历史区前面。
def fit_messages(system, old_summary, messages, current, budget):
kept = [system]
used = count_tokens(system) + count_tokens(current)
if old_summary and used + count_tokens(old_summary) budget:
continue
kept.insert(1, message)
used += size
return kept + [current], used
真实实现还要处理成对的 user/assistant 消息,以及工具调用和工具结果必须成组保留的问题。不能只保留一条工具调用而丢掉对应结果,否则模型看到的历史会像一条未完成的分支。
检查点很简单:输出最终消息列表后,再次计算总 token,确认总量小于 W - safety_margin。如果超出,宁可继续缩短摘要,也不要把超限请求交给下游。
摘要不是全文压缩:只留下能影响下一轮的事实
摘要最容易出错的地方,是把已经确认的事实、用户偏好、待办事项和模型猜测混成一句话。建议固定成四个字段:已确认事实、用户约束、未完成任务、需要追问的缺口。
已确认事实:订单状态接口返回字段为 order_id、status
用户约束:后续只输出 JSON,不添加 Markdown 说明
未完成任务:补充超时后的重试策略
待确认缺口:重试是否允许改变查询时间范围
每次摘要都带一个版本号,例如 summary_v3,并把它与被压缩的消息轮次记录在日志里。这样当用户反馈“模型好像忘了上一轮”时,可以判断是摘要丢字段,还是最近轮次根本没有进入请求。

运行检查:用三类指标判断裁剪是否过度
不要只看请求是否成功。至少记录四个字段:input_tokens、output_tokens、history_turns_kept、summary_version。如果输入 token 下降了,但“违反输出格式”的比例上升,说明摘要保留了背景却丢了约束。
可以做一组固定回放:同一批 20 个问题,分别使用完整历史、只保留最近四轮、最近四轮加摘要三种策略。对比格式遵循率、工具参数正确率和平均输入 token。实验数据不需要伪装成线上结论,关键是让每次改动都能在同一批问题上复核。
- 格式遵循率下降:优先检查系统约束是否被重复摘要或放到了可丢弃区。
- 工具参数错误增加:检查工具调用和工具结果是否被拆开裁剪。
- 输入 token 仍持续增长:检查摘要是否被追加而不是替换旧摘要。
相关问题与恢复边界
把上下文窗口调大就能解决吗?
不能保证。窗口变大只扩大容量,不能修复摘要错误、工具结果失配或无界追加。仍然应该保留硬预算和超限保护。
摘要应该每一轮都重新生成吗?
不必。只有当可摘要区新增内容达到阈值,或任务状态发生变化时再更新;否则频繁摘要会增加延迟,也会让事实不断被改写。
裁剪后用户还能继续对话吗?
可以,但要把摘要版本和当前任务状态作为可恢复信息保存。若关键约束无法压缩,应提示用户重新确认,而不是假装模型仍记得全部细节。
把策略落成发布前检查清单
上线前逐项核对:系统约束是否永远保留;当前问题和输出预算是否先扣除;最近消息是否按完整轮次保留;工具调用与结果是否成组;摘要是否区分事实、约束和待确认项;超限时是否停止请求并返回可恢复状态;日志是否记录裁剪数量、估算 token 和摘要版本。
多轮对话的稳定性,本质上是一个预算管理问题。把历史消息当成有生命周期的数据,给它标注重要性、摘要版本和回放指标,模型窗口变长或变短都不会让系统失去边界。
-
475 收藏
-
478 收藏
-
484 收藏
-
151 收藏
-
396 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习