---
url: /basics/token-context-window.md
description: 从 token 的切分原理讲到上下文窗口的额度分配，说清中文为什么更费 token、模型为什么会「忘事」，并给前端工程师一套可落地的长度估算与超窗排查思路。
---

# Token 与上下文窗口：模型为什么会「忘事」

接大模型 API 的前端，最先撞上的两个词就是 token 和上下文窗口：账单按它算，报错也因它起。这篇把它们讲清楚——token 到底怎么切、中文为什么更贵、窗口是谁和谁共用的、以及模型「忘事」时你能查什么、改什么。

## Token 是子词，不是字也不是词

模型不认识字符串，只认识整数 ID。分词器（tokenizer）负责把文本切成 token 再映射成 ID，常见算法是 BPE（字节对编码）。

* 切法不按语法：`unhappiness` 可能被切成 `un` + `happi` + `ness`。
* 空格、标点、代码缩进都会被算进去，一个都跑不掉。
* 一句话：token 是模型眼里的最小拼装块，比字大、比词小，全凭训练时统计出来的词表决定。

## 换一家厂商，token 数就变了

同一个字符串，不同分词器切出的数量并不一样。

* GPT 系列用 tiktoken：`cl100k_base` 词表约 10 万，`o200k_base` 约 20 万，对非英文更友好。
* Gemini 用 SentencePiece；Claude 的分词器官方未公开。
* 所以别拿一家的 token 数去套另一家的账，要准就调各家的计数接口。

```ts
// 示意代码：前端本地数 token
import { getEncoding } from "js-tiktoken";
const enc = getEncoding("o200k_base");
const n = enc.encode("你好，前端").length; // token 数
```

## 中文为什么更费 token

词表大多在英文语料上练成，常用汉字和词往往被拆得更碎。

* 经验值：英文 1 token ≈ 4 个字符 ≈ 0.75 个单词；中文 1 token ≈ 1 到 2 个汉字。
* 同样一段话，中文通常花掉比英文更多的 token，账单和窗口占用都更高。
* 代码和 JSON 也别忽略：缩进、引号、重复的字段名都会被逐块计入。

## 上下文窗口：输入和输出共用一个额度

* 上下文窗口是模型单次请求能同时「看到」的 token 上限，包含你的输入加模型生成的输出。
* 它不是「输入一份再加输出一份」，而是一个总池子：输入占得多，留给输出的就少。
* 请求里设的输出上限（max tokens）同样吃这份预算，加起来超了就报错。

## 上下文被谁吃掉了

窗口没满却已经开始「忘事」，通常是这些在抢位置：

* 系统提示词（system prompt）和工具、函数定义。
* 历史对话全文，每一轮都原样重发。
* 检索塞进来的文档（RAG）和长网页正文。
* 图片等多模态输入，以及你没注意的调试日志。

## 「忘事」是工程问题，不是玄学

模型没有记忆，也不会真「逐渐忘」；本质是应用层每次往 prompt 里拼了多少历史。

* 超出窗口时，要么请求直接失败，要么上游做了截断或摘要，把早期内容丢掉——你看到的就是「忘事」。
* 所以历史得在客户端管好：滚动窗口、对话摘要、把稳定内容交给缓存，都是常规手段。
* 和状态管理一个道理：别把整棵 store 每轮全量序列化再发出去。

## 怎么估算，以及超窗会怎样

* 粗估：英文可按「字符数 ÷ 4」；中文按「汉字数 × 1 到 2」。这只是量级，不是精确值。
* 要准就用厂商的计数接口，或本地 tiktoken；具体模型和参数以官方文档为准。
* 超窗两种表现：一是直接报错，例如 OpenAI 返回 400、`invalid_request_error`，提示 `context_length_exceeded` 这类语义；二是静默截断，内容悄悄丢了。
* 工程上留余量：估算值离上限留出一截，再用响应里的 usage 字段回头校准。

::: warning
别把「字符数 ÷ 4」当万能公式，那是英文经验值，中文明显偏离，一不留神就超窗、超预算。也别以为所有厂商超窗都会报错——不少产品是静默截断，你以为历史都在，其实前面早被丢了。分词方式、词表大小、免费额度这类信息会变，以官方文档为准。
:::
