AI 基础

Token 与 Context:模型究竟看见了什么?

通过分词实验和上下文预算,理解 Token 如何影响输入限制、输出空间、成本与提示缓存。

· 8 分钟

我们把文字发送给模型时,模型并不是直接按“一个汉字”或“一个单词”阅读。文本会先被编码成 Token;系统指令、历史消息、工具定义、文档和回答,也都要在有限的 Context 中共享空间。

理解这两个概念,能把很多模糊问题变成可以测量的工程问题:为什么请求变贵、为什么长对话开始丢信息、为什么输出空间突然不够,以及为什么只改提示词开头的一小段,缓存收益就下降了。

读完你能做什么

  • 解释 Token 为什么不等于字符数或单词数。
  • 对一次真实模型输入进行准确计数。
  • 为输入与输出分配 Context 预算。
  • 通过稳定前缀提高 Prompt Caching 的命中机会。

Token:文字进入模型前的编码单位

Tokenizer 会把文本切成词表中已有的片段,再把每个片段映射成整数。片段可能是一个字符、一个词的一部分、一个完整单词,或者包含前导空格的组合。

因此:

  • 同样的字符数,Token 数可能不同;
  • 同一句文本,在不同编码或模型上可能不同;
  • JSON、Markdown、代码和自然语言的切分方式也可能不同。

OpenAI 的tiktoken 指南 展示了不同编码对同一文本的切分差异,并提醒空格通常会和后面的词一起编码。

先建立一个最重要的判断:

Token 是编码结果,不是靠字符数就能稳定换算的自然语言单位。

停一下:1000 个汉字一定等于 1000 Token 吗?

答案:不一定。切分取决于目标模型使用的编码;标点、数字、英文和罕见字符也会改变结果。需要使用目标模型对应的计数方式。

实验一:数“实际请求”,不要只数字符串

对一段纯文本,本地 tokenizer 很方便;但真实请求还可能包含消息角色、内容边界、工具定义、图片或文件。OpenAI 的Token counting 指南 说明,服务端计数接口可以接收与 Responses API 相同的输入,并返回该输入的准确 Token 数。

TS
import OpenAI from 'openai'
 
const client = new OpenAI()
 
const result = await client.responses.inputTokens.count({
  model: 'gpt-5.6',
  input: [
    {
      role: 'developer',
      content: '只依据用户提供的内容回答;不知道时明确说明。'
    },
    {
      role: 'user',
      content: '请用三句话解释 Token 与 Context 的关系。'
    }
  ]
})
 
console.log(result.input_tokens)

这个实验的重点不是记住某次输出的数字,而是保留方法:构造与生产调用相同的输入,再计数。 如果生产请求包含工具 schema,就不要只对用户那句话做本地编码后当成总输入。

本地 tokenizer 仍然适合快速比较两个纯文本版本,例如检查删掉重复说明后减少了多少 Token。到了最终预算和账单分析,再用实际请求与 API usage 校准。

Context:一次推理可使用的工作区

Context window 可以理解为一次模型调用的总工作区。它通常要容纳:

这说明“模型支持很长上下文”不等于“可以把空间全部用来塞输入”。如果没有为输出预留空间,请求可能失败,或者无法生成你需要的完整答案。

可以在应用里显式计算预算:

TS
type ContextBudget = {
  limit: number
  input: number
  reserveOutput: number
}
 
function inspectBudget(budget: ContextBudget) {
  const remaining =
    budget.limit - budget.input - budget.reserveOutput
 
  return {
    remaining,
    fits: remaining >= 0
  }
}
 
const budget = inspectBudget({
  limit: 128_000,
  input: 92_000,
  reserveOutput: 8_000
})
 
console.log(budget)

这里的数字只是演示预算关系,不代表某个具体模型的限制。生产代码应从你实际使用的模型配置中读取 limit,并用实际计数替换估算值。

主动回忆:输入没有超过 Context 上限,就一定合理吗?

答案:不一定。还要为输出预留空间,而且大量无关或重复内容会稀释关键信息、增加延迟与成本。能装下只是最低条件。

实验二:给上下文做一份预算表

假设一个客服助手需要这些内容:

内容用途处理策略
稳定规则定义任务和边界保留,避免重复
工具定义允许查询订单只提供当前可用工具
最近对话理解当前问题保留必要轮次
历史摘要保留长期状态用结构化摘要代替全部原文
产品资料回答事实问题只选择与当前问题相关的片段
输出余量生成完整答复提前预留

当预算不足时,不要从头到尾机械截断。更合理的顺序通常是:

  1. 删除重复、无关内容;
  2. 只保留当前任务需要的工具;
  3. 把较早对话压缩成可检查的事实摘要;
  4. 选择相关资料,而不是附上整份知识库;
  5. 保留足够的输出余量。

这就是 Context Engineering 的基本问题:决定哪些信息值得进入这一次推理,而不只是追求把窗口填满。

读图时注意方向:预算不足后的动作不是随便从开头截断,而是回到 Context 入口,重新选择哪些信息有资格进入本次推理。

Prompt Caching:相同前缀为什么重要

许多生产请求都有很长的稳定开头:系统规则、工具定义和公共资料保持不变,只有末尾的用户问题发生变化。

OpenAI 的Prompt Caching 指南 说明,缓存依赖相同的提示前缀。为了提高命中机会,应把稳定内容放在前面,把每次变化的内容放在后面,并从 usage 中观察 cached tokens。

适合缓存的排列:

TEXT
[稳定规则]
[稳定工具定义]
[稳定公共资料]
[对话历史]
[本次用户问题]

不利于缓存的排列:

TEXT
[每次变化的请求 ID 和时间戳]
[稳定规则]
[稳定工具定义]
[稳定公共资料]
[本次用户问题]

第二种排列在开头就发生变化,后面即使有大量相同内容,也难以形成同一段连续前缀。不要为了缓存而改变任务语义,但在语义相同的前提下,稳定在前、动态在后通常更合理。

停一下:用户 ID 应该放在长篇稳定规则之前吗?

答案:如果它不影响规则解释,通常放在后面的动态区域更合适。真正的判断标准是语义和数据依赖,而不是为了缓存盲目移动字段。

三个容易混淆的问题

Token 少,就一定更好吗?

不是。删掉必要规则或证据会降低结果质量。目标是减少重复和无关内容,而不是追求最短输入。

Context 大,就不需要管理历史了吗?

不是。更大的窗口提高容量,却不会自动判断什么最相关,也不会替你为输出预留空间。

本地计数和 API usage 为什么可能不同?

你可能只编码了文本,却遗漏消息结构、工具 schema 或其他输入;也可能使用了与目标模型不一致的编码。最终应以目标模型的官方计数方式和实际 usage 为准。

上线前检查清单

  • 使用目标模型对应的方式计算真实请求,而不是按字符猜。
  • 输入预算包含指令、历史、工具、资料和输出余量。
  • 超预算时按相关性整理内容,不做无脑截断。
  • 稳定内容在前,动态内容在后。
  • 在生产 usage 中记录输入、输出和缓存 Token。
  • 模型或请求结构变化后,重新校准预算。

最后记住

Token 决定输入如何被编码,Context 决定一次推理能使用多少工作区。把二者放在一起,才得到可操作的工程视角:

先准确计数,再分配预算;只提供当前任务需要的信息,并为输出留出空间。

继续深入可参考: