← 返回资讯

上下文越长越贵:长上下文成本详解与控制方法

2026-07-10

很多开发者第一次看到超长对话的账单时会吓一跳——明明只是加了几轮历史,成本却翻了好几倍。我见过一个真实的坑:某个客服机器人上线两周后账单暴涨 4 倍,团队排查了半天以为是调用量涨了,翻日志才发现调用次数其实没怎么变,是因为产品经理要求”记住整个会话”,于是每轮请求都把从第一句到当前的全部历史原样塞进 messages 数组。用户平均聊 15-20 轮才结束一次会话,到第 20 轮时输入 token 已经是第 1 轮的十几倍——这就是典型的”上下文债务”,欠着欠着就爆了。

原因很简单:大模型 API 每次调用都要把完整上下文作为输入计费,历史轮次不会”免费”积累。这跟你自己脑子里聊天不一样——人脑记忆是压缩存储的,但大模型每次推理都是”无状态”的,它不会记得上一次调用发生了什么,你必须把所有它需要”记住”的内容重新喂一遍,而这些重新喂进去的内容,一个字都不会少算钱。

上下文成本的计费逻辑

每次调用 API,你支付的输入 token 包括:

输入成本 = (System Prompt + 所有历史对话 + 当前用户输入) × 输入单价

随着对话轮数增加,历史对话部分线性增长,而整体成本也随之线性上涨:

对话轮数输入 token 结构(示意)输入 token 总量(示意)
第 1 轮500(sys) + 100(用户)600
第 3 轮500(sys) + 600(历史) + 100(当前)1,200
第 5 轮500(sys) + 1,200(历史) + 100(当前)1,800
第 10 轮500(sys) + 2,700(历史) + 100(当前)3,300

第 10 轮的输入 token 是第 1 轮的 5.5 倍,而每轮产生的用户价值并未线性增长。

这里有个容易被忽略的细节:表里”历史对话”这一栏其实是累加的,不是”当前这一轮新增的字数”。第 3 轮的历史是第 1、2 轮的用户输入 + 模型回复全部加起来,第 5 轮的历史又把第 3、4 轮也算进去了——也就是说,你每多聊一轮,之前每一轮的内容都要再被计费一次。用等差数列的思路估算:如果每轮平均产生 300 token(用户输入 + 模型回复合计),聊到第 N 轮时,累计输入 token 总量大致是 N × 系统提示 + 300 × N(N-1)/2。这是个二次增长的量级,不是线性的——很多人以为”多聊几轮顶多成本翻倍”,实际上聊到 30 轮、50 轮时,光是历史部分的计费总和就可能比你想的多出一个数量级。这也是为什么”不限制历史轮数”的对话产品,只要用户粘性够高,账单曲线会陡得很难看。

长上下文窗口:能力扩展但成本也扩展

近两年主流模型的上下文窗口从 4k → 8k → 32k → 128k → 1M token 不断扩大,这带来了强大的能力,但也带来了成本陷阱:

声明:截至 2026-06,以官方为准。

场景上下文规模单次输入 token 量级
简单问答< 1k token极低
多轮对话(10轮)5k–20k token中等
长文档 QA(整书喂入)50k–200k token
超长上下文推理(1M)100k–1M token极高

部分厂商对超过一定阈值的上下文会分级定价,超出部分单价更高,使用前务必查阅各厂商的阶梯价格政策。

有个认知误区必须先纠正:“支持 1M 上下文”不等于”你应该用满 1M 上下文”。窗口大小是模型的能力上限,不是你的用量目标。我见过团队图省事,把整份产品手册(几十万 token)不做任何裁剪,每次提问都原样塞进 context,理由是”反正模型支持这么长”。结果是:单次问答成本高得离谱,而且大部分时候用户问的只是手册里的一小段内容,剩下 95% 的 token 纯粹是陪跑,既费钱又拖慢首字延迟(TTFB)。窗口能装多少,跟这次调用该装多少,是两件事——前者看模型规格,后者看你的产品实际需要什么信息。

长上下文成本的三大隐性来源

1. 系统提示膨胀
随着功能迭代,system prompt 越来越长——文档、规则、few-shot 示例不断堆积,很多团队的 system prompt 已达 2k–5k token,每次调用都是固定成本。

2. 无限累积的历史对话
没有设置历史窗口上限的对话系统,随着用户会话时间增长,每次调用的输入 token 都在增加。

3. RAG 检索注入
RAG 系统每次把检索到的文档片段注入 context,如果检索结果过多或过长,会大幅推高输入 token。

上下文超限时你会遇到什么报错

如果上面三个隐性来源同时叠加,迟早会撞到硬上限。这不是危言耸听,是真实会遇到的故障现象,排查思路记一下:

  • 400 context_length_exceeded / invalid_request_error:请求体里的 token 总量(历史 + 系统提示 + 当前输入 + 预留的 max_tokens)超过了模型上限。很多人第一反应是”我这一轮没说多少字啊”,忘了历史是一起算的。修法:调用前先用官方 tokenizer(或对应 SDK 的计数方法)预估总量,超过阈值就先触发压缩/截断逻辑,不要等 API 报错才处理。
  • 报错里提示的 token 数比你预估的高很多:多半是因为你只统计了”文本字数”,没统计到系统提示、few-shot 示例、工具调用的 function schema 定义——这些也占 token,而且中文场景下工具定义的 JSON schema 经常被低估。
  • 截断策略”暴力砍头”导致对话前言不搭后语:如果你简单粗暴地把历史数组砍掉前面几条,容易砍掉关键的系统设定或用户在早期声明的重要信息(比如”我叫张三,账号是 xxx”),后面模型会”失忆”。更稳妥的做法是把 system prompt 和最近几轮固定保留,只对中间的历史做压缩或删减,而不是无差别砍头。
  • max_tokens(预留给输出的空间)设置过大,间接挤占了输入可用空间:有的开发者习惯把 max_tokens 拉满到模型允许的最大值”以防万一”,结果输入侧留给历史的空间反而变小,看似同样的窗口反倒更容易触发超限。按实际期望的回复长度设置 max_tokens,不要无脑拉满。

排查这类问题的顺序建议:先确认总 token 量(用 SDK 自带的计数工具,别自己按”字数除以2”估算,中英文 token 比例差异很大),再看是历史堆积、系统提示膨胀还是 RAG 注入过量,最后再决定用滑窗、摘要压缩还是精准检索来解决——对症下药,别一上来就无脑截断。

工程上如何控制上下文成本

策略一:滑动窗口——只保留最近 N 轮

MAX_HISTORY = 6  # 保留最近 3 轮问答
history = history[-MAX_HISTORY:]

这行代码看着简单,但有两个坑要注意。第一,history[-MAX_HISTORY:] 是按”条目数”切片,不是按”token 数”切片——如果某一轮用户贴了一大段日志或代码,这一条本身可能就有上千 token,按条数保留反而控制不住总量,更稳妥的做法是维护一个 token 计数器,每加入一条历史就往回缩,直到总量落回预算内。第二,切片时容易把 system prompt 也当成普通历史一起砍掉,正确的做法是 system prompt 单独存放、永远保留,只对 user/assistant 的历史消息做滑窗。滑动窗口的代价很直白:早期轮次说过的关键信息(比如用户在第 1 轮提到的账号、需求背景)一旦滑出窗口就彻底”失忆”,所以它更适合客服闲聊、一次性问答这类”上下文关联弱”的场景,不适合长周期、强依赖历史事实的对话。

策略二:摘要压缩——用轻量模型总结旧历史

每当历史超过阈值,用轻量/便宜模型将旧历史压缩成摘要,替换掉原始记录。摘要的 token 量通常是原历史的 10%–20%。实现思路很简单:设定一个触发线(比如历史超过 8 轮或超过 2000 token),触发时把最早的几轮打包成一段文本,丢给一个便宜模型,让它输出”用户已确认的信息、当前进展、待办事项”三段式摘要,再把这段摘要塞回历史数组的最前面,替换掉原始的那几轮对话。这样做的好处是历史不会无限膨胀,代价是压缩必然有信息损耗——摘要模型可能会漏掉它认为”不重要”但用户后面会用到的细节。所以摘要压缩通常不单独用,而是跟滑动窗口搭配:最近几轮保留原文(滑动窗口),更早的历史做摘要(摘要压缩),两者结合既保住了细节,又控制了总量。

策略三:Prompt Caching——固定前缀只算一次

如果 system prompt 和文档是固定的,启用支持 Prompt Caching 的 API(如 Anthropic、OpenAI),重复前缀命中缓存后成本可降至原价的 10%–25%。详见 Prompt Caching 省钱原理与实践。这里补一句实操经验:缓存命中是有条件的,前缀必须逐字节一致,哪怕只改动了系统提示里的一个空格、一个标点,缓存就会失效退回原价。如果你的 system prompt 里拼了动态时间戳(比如”当前时间:2026-07-05 14:32”),那这个前缀每次都不同,永远无法命中缓存——想用好这个策略,就得把”会变的部分”(时间、用户名、动态参数)挪到 system prompt 之后、作为独立的用户消息传入,让 system prompt 本身保持完全固定。另外缓存通常有 TTL(比如几分钟到一小时不等,具体以各家文档为准),低频调用的场景可能缓存还没等到下次调用就过期了,收益会打折扣,高并发、高频次的场景才是它的甜区。

策略四:RAG 精准检索——少取多精

不要把前 20 个检索结果都注入,选择相关性最高的 3–5 个,并对每个片段做截断(如最多 500 token/片段)。进阶一点的做法是引入 rerank(重排序)环节:先用向量检索粗筛出 Top 20-50 个候选片段(这一步召回率优先,宁可多召回),再用专门的 rerank 模型对这些候选按”真实相关性”重新打分排序,最后只把排名最靠前的 3-5 个片段送进 context。这样比单纯依赖向量相似度排序更准,因为向量检索容易出现”语义相近但答非所问”的片段排在前面,而 rerank 是针对”问题-片段”这一对做精细打分,准确率明显更高,代价是多了一次调用和一点延迟,但换来的输入 token 大幅减少往往是划算的。

四种策略不是二选一,实际项目里通常是组合拳。简单对比一下适用场景:

策略优点代价适合场景
滑动窗口实现最简单、零额外调用早期信息彻底丢失弱历史依赖的短对话(客服闲聊)
摘要压缩保留关键信息、总量可控有信息损耗、多一次调用成本长周期客服/助手类对话
Prompt Caching命中后成本大降,不损失信息前缀必须完全固定,有 TTL 限制固定 system prompt + 高频调用
RAG 精准检索从源头减少注入量需要额外的检索/rerank 基础设施大知识库问答

我自己在实际项目里的组合是:system prompt 走 Prompt Caching(因为它固定不变),最近几轮历史走滑动窗口,更早的历史走摘要压缩,涉及知识库问答的部分再叠加 RAG 精准检索——四层叠加下来,同样的对话深度,实际账单能压到”无脑塞全部历史”方案的两三成。

常见问题

长上下文模型(如 1M context)贵很多吗?
通常贵,原因有二:一是支持超长上下文的模型本身单价较高;二是实际填充的 token 量巨大,即使单价相同,总费用也成倍增加。建议只在真正需要长上下文的场景使用。

可以把上下文存到数据库,需要时再取吗?
这正是 RAG 的核心思路。代价是检索时需要额外的 embedding 调用和延迟,但对于超大知识库场景,比全文注入 context 划算得多。

上下文长度和回答质量有关系吗?
有一定关系,但并非线性。过长的上下文反而会导致”遗忘中间”(Lost in the Middle)问题——模型对位于上下文中段的信息注意力下降。精简且相关的上下文往往比堆砌信息效果更好。


延伸阅读: