聊天机器人每轮重发全部历史:实测上下文成本与压缩策略

聊天API无状态导致历史token反复计费,摘要加滑动窗口可降本76%并保留关键事实。
聊天 API 无状态,每轮都要重发全部历史,实测 99.3% 的处理 token 都是输入 token,账单大头是历史而非回答。作者用客服机器人做 40 轮对照实验:全量历史每条消息 $0.001599,最近 6 轮滑动窗口降到 $0.000413(−74%),摘要加最近 4 轮降到 $0.000388(−76%)。但纯滑动窗口会在 3/3 次测试中忘掉开头给出的账户 ID 和条款,而摘要方案几乎同价却全部记住。生产方案是保留最近 8 轮原文、超过 12 轮时用便宜模型压缩更早历史,并把摘要持久化只压缩一次。系统提示、RAG 片段和工具 schema 也是常见输入泄漏点,应精简或配合 prompt caching。
聊天 API 是无状态的。为了回答第 41 条消息,你的应用要把前 40 条消息重新发一遍,而这些历史消息全部按输入 token 计费——每一条消息都要重付一次。
[图片]
这不是个小数目。据一项基于真实 API 流量的分析,在 7 天约 411,000 次请求中,99.3% 的已处理 token 都是输入 token;中位数请求输入 3,782 token,只返回 147 token。大多数 LLM 账单的真正大头不是回答太长,而是历史太长。
一次真实对照实验:三种上下文策略
为了量化“保留多少历史”的代价,作者设计了一个客服机器人场景:经过 40 轮对话后,再问最后一个问题,并对比三种策略:
- 全量历史:大多数教程的做法
- 最近 6 轮:滑动窗口
- 摘要 + 最近 4 轮:用便宜模型把更早的对话压缩成一段 60 词的笔记
测试脚本 trim_context.py 使用 claude-sonnet-5 模型,输入价格 $0.3835/1M、输出价格 $1.5339/1M。三种策略发送的消息不同,但模型与输出长度保持一致。
实测结果如下:
| 策略 | 输入 token | 每条消息成本 | 相对全量 |
|---|---|---|---|
| 全量历史(40 轮) | 3,690 | $0.001599 | — |
| 最近 6 轮 | 596 | $0.000413 | −74% |
| 摘要 + 最近 4 轮 | 531 | $0.000388 | −76% |
同样的模型、同样的输出长度,账单只剩大约四分之一。摘要步骤本身有额外开销:调用一次 GLM 5.3 Flash 约 $0.00002,而且只在历史超过你设定的限制时才需要支付,并非每条消息都付。
无状态(stateless) 指 API 本身不保存任何会话记忆:每次请求都是独立的,服务端不会“记住”你上一次说了什么。因此要实现多轮对话,客户端必须把此前的 user 和 assistant 消息一并放进本次请求的 messages 数组中。这也是历史 token 被反复计费的根源——同一段历史每轮都作为输入重新发送。部分平台提供“会话状态”托管能力(如 OpenAI 的 Responses API 或 Assistants 的状态存储),由服务端保存历史,但计费逻辑通常仍按实际处理的输入 token 计算,理解这一机制有助于判断成本优化的着力点。
便宜有代价:滑动窗口会“失忆”
成本只是一半,另一半是质量。作者在对话开头埋了一个事实:“我的客户是 Northwind,账户 ID 7731,net-45 条款。”再经过 40 轮后,让机器人复述这个 ID 和条款,每种策略跑 3 次:
| 策略 | 输入 token | 是否记住“7731, net-45” |
|---|---|---|
| 全量历史 | 3,751 | ✅ 3/3 |
| 最近 6 轮 | 600 | ❌ 0/3(“我没有收到过任何账户 ID”) |
| 摘要 + 最近 4 轮 | ~530 | ✅ 3/3 |
纯滑动窗口最便宜,但它会忘掉窗口之外的一切,包括用户只说过一次的关键事实。摘要方案几乎以同样的价格把这些事实保留下来。要提高可靠性,需要在摘要提示里明确要求:"Keep names, IDs and terms."(保留姓名、ID 和条款)。
15 行生产级代码:摘要 + 窗口
作者给出了一个可以直接落地的模式:保留 MAX_TURNS = 8 轮原文,当历史超过 SUMMARY_AT = 12 轮时,把更早的部分交给便宜模型压缩成摘要,然后把摘要和最近轮次一起发送。
核心函数 build_messages(system, turns, summary, new_msg) 的逻辑是:
- 当
len(turns) > SUMMARY_AT时,调用summarize()把turns[:-MAX_TURNS]并入已有摘要,并只保留最近MAX_TURNS轮原文; - 消息数组以 system 提示开头,如果存在摘要就追加一条 system 消息
Earlier: {summary}; - 最后拼上保留的轮次和新消息。
关键工程细节:把 summary 存在对话旁边,这样同一段历史只会被压缩一次,不会反复付费。
MAX_TURNS = 8 # keep this many recent turns verbatim
SUMMARY_AT = 12 # compress once history passes this
def build_messages(system, turns, summary, new_msg):
if len(turns) > SUMMARY_AT:
summary = summarize(summary, turns[:-MAX_TURNS]) # cheap model
turns = turns[-MAX_TURNS:]
msgs = [{"role": "system", "content": system}]
if summary:
msgs.append({"role": "system", "content": f"Earlier: {summary}"})
return msgs + turns + [new_msg], turns, summary
滑动窗口(sliding window) 是最简单的历史裁剪策略:只保留最近 N 轮对话,更早的内容直接丢弃。它的优点是实现简单、token 可控,缺点是窗口之外的信息彻底消失,模型无法再引用。与之相对的滚动摘要(rolling summary) 则把超出窗口的旧内容交给一个便宜模型压缩成一段简短笔记,随对话不断更新并持久化。由于摘要只在新历史溢出时生成,压缩成本被摊薄,而关键事实(姓名、ID、条款)可通过在摘要提示中显式要求保留。二者常组合使用:近期原文保证细节准确,远期摘要保证长期记忆。
其他值得排查的输入 token 泄漏点
除了对话历史,还有几处常见的“隐形输入浪费”:
- 膨胀到 2,000 token 的系统提示:每次调用都会发送,应该精简,或使用支持 prompt caching 的服务商。
- RAG 塞入 10 个片段,实际 3 个就够:先重排(rerank),再裁剪。
- 工具 schema:每个函数定义在每次调用中都是输入 token,只发送当前步骤真正需要的工具。
作者的流量数据来自 Qubax——一个兼容 OpenAI 接口、聚合 400+ 模型的 API 服务;文中裁剪代码可配合任何 OpenAI 兼容端点使用。
Prompt caching(提示缓存) 是服务商提供的优化机制:当多次请求共享相同的前缀(如固定的 system 提示或历史对话的初始部分)时,这段前缀只需在首次请求时完整计费和处理,后续请求可命中缓存,从而降低输入 token 的价格并减少延迟。OpenAI、Anthropic 等均提供该能力,通常需要显式标记缓存边界,缓存有生存时间(如 5 分钟或 1 小时),过期后需重新写入。注意缓存只能让重复前缀更便宜,并不能缩短总上下文长度,因此长历史仍会占用上下文窗口并抬高延迟,最佳实践是把裁剪与缓存结合使用。
Rerank(重排) 是 RAG 流程中的一步:初步检索通常按向量相似度召回较多片段(如 10-20 个),但这些片段未必都与问题最相关。重排模型(cross-encoder 或专用 reranker)会同时阅读问题和候选片段,输出更精确的相关性排序,再截取分数最高的少数片段送入 LLM。由于重排发生在调用 LLM 之前,能显著减少塞入上下文的 token 量,同时提升答案质量。常见做法是召回 10-20 个、重排后只保留 3-5 个。
常见问题
滑动窗口够用吗?
对短生命周期对话够用。但在测试中,它 3/3 次忘掉了对话开头给出的账户 ID。如果用户陈述过一次事实并期望被记住,就需要对更早轮次做摘要。
摘要该用哪个模型?
便宜、快速的模型。本测试中摘要调用约 $0.00002,且只在历史超过限制时付费。
Prompt caching 能解决这个问题吗?
部分能。缓存让重复前缀更便宜,但长历史仍会增加延迟,并可能把你推过上下文上限。裁剪和缓存配合使用效果最好。
参考资料
- OpenAI 管理对话状态:https://platform.openai.com/docs/guides/conversation-state
- Anthropic prompt caching:https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- OpenRouter 模型定价:https://openrouter.ai/models
相关推荐

Product Hunt 推出可视化发布编辑器,告别繁琐填写
Product Hunt 推出可视化发布编辑器,支持点击原位编辑名称、标语、画廊和 maker 信息,自动保存草稿,并用检查清单引导创作者完成发布。本文解析其编辑逻辑与对发布体验的影响。

泽连斯基亲述:俄军2000万人压境,我们如何守住基辅
泽连斯基访谈回顾俄乌战争初期:俄军200,000兵力直扑基辅,乌军主力却在东部。他讲述动员如何将军队从200,000扩张到800,000,以及基辅州、切尔尼戈夫州如何被收复。

Ovren Live Preview:用自然语言实时预览产品改动
Ovren Live Preview 是一款用自然语言描述改动、在真实页面上实时预览的 AI 开发工具。了解其核心功能、工作流、市场定位与潜在挑战。