上下文窗口与 Token 预算怎么算:一份实用分配指南

128K 上下文不等于能塞 128K 的内容,输出、系统提示、工具返回都要从同一份额度里扣。这篇讲清上下文窗口的真实计算方式、中英文 token 换算、四段式预算分配法,以及超限时该压缩哪一部分。

很多人第一次看到"128K 上下文"就以为能贴进去十万字资料,结果第一轮对话就被截断,或者账单比预期高好几倍。问题出在两个误解:一是把输入和输出当成两个独立额度,二是用字符数估算 token。

上下文窗口是一份共享总额度。你的系统提示、历史对话、检索到的文档、工具返回的结果,以及模型即将生成的每一个 token,全都从这同一个数字里扣。理解这一点,后面的预算分配才有意义。

先说结论

  • 上下文窗口 = 输入 token + 输出 token,共享同一份额度,不是各自独立
  • 中文 1 个汉字大约 0.6–1 个 token,英文 1 个单词约 1.3 个 token,用字符数估 token 会偏差 2–3 倍
  • 实用分配比例:系统提示与工具定义 10%、检索资料 40%、对话历史 30%、输出预留 20%
  • 上下文越长,单位 token 成本越高,别把"能塞进去"当成"应该塞进去"
  • 超限时优先压缩历史对话,其次是检索资料,绝不要压缩系统提示里的约束条件

一、上下文窗口到底怎么算

一次 API 调用的真实占用是这样的:

总占用 = 系统提示 + 工具/函数定义 + 历史消息 + 本次用户输入 + 检索资料 + 模型输出

如果总占用超过模型的上下文上限,接口就会直接报错,或者(取决于实现)在你不注意的地方被静默截断。所以关键不是"我的输入有多少",而是"输入加上预期输出,离上限还有多少余量"。

举个具体的账:假设模型上限是 128K token,你希望模型每次输出 2000 token 的分析报告,那么输入最多能用到 126K token。听起来很宽松,但如果你的检索流程每次召回 20 篇文档、每篇 3000 字,光资料就接近 40K token,再加历史对话和工具返回,几轮之后就会顶到天花板。

二、Token 换算:别用字符数猜

最常被低估的就是中英文混排的 token 数。粗略的换算经验:

内容类型大致换算举例
中文汉字1 字 ≈ 0.6–1 token1000 字 ≈ 600–1000 token
英文单词1 词 ≈ 1.3 token1000 词 ≈ 1300 token
代码1 行 ≈ 10–20 token200 行 ≈ 2000–4000 token
JSON 结构化数据1 个字段 ≈ 5–15 token复杂嵌套会更高
数字与标点每 3 位 ≈ 1 token长数字串消耗偏多

这里的差异来自分词器(tokenizer)的切分方式。中文常用词会被合并成一到两个 token,生僻字和繁体字往往按字切甚至按字节切;代码里的符号、缩进、驼峰命名也会被拆得比较碎。

实际工程中不要靠估算,用接口返回的 usage 字段核对一次真实值,再按比例推整体量级。一次真实测量比十次猜测可靠。

三、四段式预算分配法

把上下文上限当成一个固定预算来切分,比"能塞就塞"稳得多。一个在多轮对话、RAG、Agent 三类场景都能用的比例:

分区建议占比作用可压缩性
系统提示与工具定义10%角色、规则、输出格式约束不可压缩
检索资料40%本轮问答依据可压缩
对话历史30%保持上下文连贯优先压缩
输出预留20%模型生成空间不可压缩

这个比例的核心逻辑是:约束和输出是硬需求,资料和历史是软需求。系统提示被截掉,模型就不知道自己要遵守什么格式;输出预留不够,回答会在半句话处被切断。

具体到代码里,预算检查应该放在请求发出之前:

def build_request(system, history, docs, user_input, ctx_limit=128000):
    # 按优先级从低到高淘汰
    reserve_out = int(ctx_limit * 0.20)
    budget = ctx_limit - reserve_out

    sys_tokens = count_tokens(system)
    remain = budget - sys_tokens

    # 历史对话:保留最近 N 轮,超出则丢最早的
    history = trim_history(history, max_tokens=int(remain * 0.45))
    remain -= count_tokens(history)

    # 检索资料:按相关度排序,够用即止
    docs = trim_docs(docs, max_tokens=min(int(budget * 0.40), remain))
    remain -= count_tokens(docs)

    return {
        "messages": [{"role": "system", "content": system}]
                    + history
                    + [{"role": "user", "content": docs + user_input}],
        "max_tokens": reserve_out,
    }

注意 max_tokens 一定要显式传。不传的话,服务端会按模型默认值预留输出空间,实际可用输入就比你以为的少一截。

四、上下文变长,成本不是线性增长

这里有个容易被忽略的点:长上下文请求的单位成本通常高于短请求。原因在于注意力机制的计算量与序列长度的平方相关,上游资源的消耗确实更大。

所以在工程上,判断标准不该是"这个资料能不能塞进去",而是"塞进去能不能换来更好的答案"。一份 3000 字的文档,如果只有两段和问题相关,把两段摘出来可能比整篇塞进去效果更好,还更便宜。

三个减少无效上下文的具体做法:

  • 检索先排序再截断:按相关度打分,取前 k 篇,而不是全量塞入
  • 历史做摘要而不是全留:超过 6 轮的对话压成一段摘要,保留结论和约定
  • 结构化输出代替长文本:让上一步返回 JSON 而不是散文,下一步解析成本低得多

配合模型列表里不同模型的上下文规格选型,短任务用上下文小但便宜的模型,长文档分析才上万级上下文的型号,成本结构会合理很多。站内价格对比里也有按输入输出分别计价的口径,上下文越长差距越明显。

五、超限了该压哪一部分

真的顶到上限时,按这个顺序处理:

  1. 历史对话降级:把最早的几轮替换成一句摘要,例如"前面已确定技术栈为 Go + PostgreSQL"
  2. 检索资料减量:从 20 篇降到 5 篇,或对每篇做段落级筛选
  3. 工具返回瘦身:只保留必要字段,去掉冗余的元数据和调试信息
  4. 换更大的上下文模型:前三条都做过还不够,才考虑升级模型
  5. 任务拆解:把一次大分析拆成多次小调用,中间结果落盘

顺序不能颠倒。直接换大模型是最贵的解法,而大部分超限情况其实是历史对话里堆了太多无用的寒暄和重试记录。

另一个常见误区是"分块处理会导致模型看不到全局"。实际上,先分块提取要点、再基于要点做汇总,通常比一次性塞进全部原文效果更稳定——后者容易在中段丢失细节。

六、几个常见的量级参考

  • 一份 10 页的 PDF 报告,纯文本约 8000–12000 字,中文折合约 6000–10000 token
  • 一份 1000 行的源代码文件,约 10000–20000 token
  • 一次带 20 条检索结果的 RAG 问答,输入常在 8000–30000 token
  • 一个跑了 10 轮的客服对话,历史累积约 3000–6000 token

用这些量级去反推你的场景需要多大的上下文,比按"最大规格"选型务实得多。多数应用的日常请求其实在 8K–32K 区间,真正需要 128K 以上的场景集中在长文档分析与代码库级理解。

FAQ

上下文窗口是不是越大越好?不是。大窗口意味着更高的单次成本,而且超长输入下模型对中段信息的利用效率会下降。选够用的规格,配合良好的检索和摘要策略,效果通常更稳。

输入和输出共用一份额度吗?共用。上限同时约束两者,所以设 max_tokens 时要留出足够空间,否则回答可能被硬切在半句。

中文真的比英文更费 token 吗?同样信息量下,中文的 token 数通常比英文略少(汉字信息密度高),但比按字符数估算的结果多不少。不要用"字数除以 2"这类经验公式,实测一次最准。

流式输出会改变 token 占用吗?不会。流式只改变返回方式,不改变总量。但它能让你更早发现输出被截断的情况。

多轮对话必须带上全部历史吗?不需要。保留最近若干轮加一段摘要,通常就能维持连贯性。全量历史是成本增长最快的一项。

延伸阅读

想把预算分配跑通,先从一个能实际计量的调用开始。在这里创建 API Key,用 usage 字段核对一次真实 token 数,比任何估算公式都管用。

#上下文窗口怎么算#token 预算分配#128k 上下文实际能用多少#中文 token 换算#上下文超限怎么办
免费创建 API Key

相关阅读

Claude API 和 OpenAI API 有什么区别

Claude 的 messages 和 OpenAI 的 chat/completions 看着像,实际有 6 处硬性差异:system 位置、max_tokens 必填、content 块数组、工具调用结构、多模态格式、流式事件名。这篇逐项对照并给出同一需求的两套写法,附迁移检查表。

Function Calling 工具调用入门指南

让大模型查天气、下单、读数据库,靠的是 Function Calling 而不是更长的提示词。这篇讲清工具调用的完整循环:定义 tools、模型输出 tool_calls、本地执行、结果回传,附可直接运行的 Python 代码与常见坑。

大模型 API 429 报错:原因、重试与并发调优

429 不一定是「你请求太多了」。这篇拆开 RPM、TPM、并发数和免费档限流四种成因,给出带抖动的退避重试实现、并发池写法与降级链路,附监控指标清单。