跳转至

第3章 Token 与上下文窗口

学习目标

  • 理解 Token 的概念与 BPE 分词原理
  • 掌握上下文窗口的含义及其对 Agent 的影响
  • 区分 Chat Completions API 与 Responses API
  • 学会使用 tiktoken 估算 Token 数量与成本
  • 掌握长上下文管理的工程化策略

问题导入

"为什么我的 Prompt 明明只有 1000 字,API 却报错说超出上下文?""为什么模型会忘记我前面说过的话?"——这两个高频问题背后,都指向同一个核心概念:Token 与上下文窗口。LLM 不以"字"为单位处理文本,而是以 Token 为最小单元;模型的记忆容量也由上下文窗口大小决定。对于 Agent 开发者而言,Token 直接影响成本、延迟、能力上限:一次调用可能因为上下文膨胀而消耗数倍预算,也可能因为关键信息被截断而导致任务失败。本章将揭开 Token 的底层机制,并给出实用的上下文管理策略。

核心概念

1. Token 与 BPE 分词

Token 是 LLM 处理文本的最小单元,介于"字"与"词"之间。OpenAI 模型使用 Byte Pair Encoding(BPE) 分词:先初始化为单字节,再统计高频字节对合并为新 token,迭代直到词表规模达到上限(如 GPT-4 的 cl100k_base 词表约 10 万 token)。 - 英文:约 1 token ≈ 4 个字符 ≈ 0.75 个单词。 - 中文:一个汉字通常占 1-3 个 token(视模型与字频而定)。 - 代码、emoji 的 token 消耗显著更高。

2. 上下文窗口(Context Window)

上下文窗口是模型在单次推理中能处理的最大 token 数,包含输入与输出。常见模型上下文长度: - GPT-4o / GPT-4.1:128K - Claude 3.5 Sonnet:200K - Gemini 1.5 Pro:1M-2M - Qwen2.5-Turbo:1M

注意:上下文长度 ≠ 有效记忆长度。研究表明,当关键信息位于超长上下文中间时,模型可能出现"中间迷失(Lost in the Middle)"现象。

3. Chat Completions API vs Responses API

  • Chat Completions API:OpenAI 经典对话接口,输入为 messages 列表,无状态,每次需传入完整历史。结构稳定,兼容性最广。
  • Responses API:OpenAI 2025 年推出的新一代接口,原生支持工具调用、文件搜索、计算机使用等内置能力,并可在服务端维护会话状态,减少重复传输。适合构建 Agent。

4. Token 计费机制

LLM 按 token 计费,通常分输入 token 与输出 token 两档(输出价格往往是输入的 3-5 倍)。还需注意: - 缓存输入(Cached Input):复用前缀可享大幅折扣(OpenAI 缓存命中可降 50%-90%)。 - 工具调用开销:工具定义、返回结果均计入输入 token。 - 系统提示:长 system prompt 在多轮对话中会重复计费。

5. 上下文管理策略

  • 摘要压缩:将早期对话总结为简短摘要,节省 token。
  • 滑动窗口:仅保留最近 N 轮对话。
  • 向量检索:把历史存入向量库,按需取回相关片段(RAG 思路)。
  • 状态外置:用 Responses API 的服务端会话或外部存储维护状态,避免每次重传。

6. 长上下文失效模式

  • 截断:超出窗口时,最早的内容被丢弃。
  • 稀释:信息过多导致模型"抓不住重点"。
  • 遗忘:关键指令被中间大量文本淹没。

原理图

flowchart LR
    Text[原始文本\n我爱AI Agent] --> BPE1[BPE分词]
    BPE1 --> Tokens[Token序列\n我/爱/AI/ Agent]
    Tokens --> EmbedTok[Token Embedding]
    EmbedTok --> Model[LLM推理]
    Model --> OutTokens[输出Token]
    OutTokens --> Detok[解码]
    Detok --> Result[最终文本]

    subgraph 上下文窗口
        SysTok[系统提示]
        HistTok[历史对话]
        UserTok[当前输入]
        OutTok2[输出预留]
    end
    SysTok --> Window[窗口容量上限]
    HistTok --> Window
    UserTok --> Window
    OutTok2 --> Window
图:BPE 分词流程与上下文窗口构成

示例代码

使用 tiktoken 估算文本的 token 数量,并计算调用成本。

import tiktoken


def count_tokens(text: str, model: str = "gpt-4o") -> int:
    """统计文本在指定模型下的 token 数。

    Args:
        text: 待统计的文本。
        model: 模型名称,用于选择分词器。
    """
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))


def estimate_cost(
    input_tokens: int,
    output_tokens: int,
    input_price: float = 2.5,
    output_price: float = 10.0,
) -> float:
    """估算单次调用的费用(美元)。

    Args:
        input_tokens: 输入 token 数。
        output_tokens: 输出 token 数。
        input_price: 每 1M 输入 token 价格(美元)。
        output_price: 每 1M 输出 token 价格(美元)。
    """
    return input_tokens / 1_000_000 * input_price + output_tokens / 1_000_000 * output_price


if __name__ == "__main__":
    sample = "人工智能正在重塑软件开发的方式,Agent 将成为下一代应用的核心范式。"
    tokens = count_tokens(sample)
    print(f"文本长度: {len(sample)} 字")
    print(f"Token 数: {tokens}")
    print(f"预估单次调用成本: ${estimate_cost(tokens, 100):.6f}")

    # 演示不同文本的 token 差异
    for text in ["hello world", "你好世界", "import numpy as np"]:
        print(f"{text:30s} -> {count_tokens(text)} tokens")

运行结果说明:会输出中文与英文文本的 token 数对比,直观展示"你好世界"通常比"hello world"消耗更多 token,从而帮助开发者预估成本。

企业案例

案例 1:长文档处理的上下文优化

某律所使用 LLM 审查 200 页合同,单份文档约 8 万 token。直接喂入 GPT-4o(128K)虽可行,但单次成本超 1 美元且响应慢。优化方案:先按章节分块,仅对相关章节做精读,整体成本降低 80%,准确率反而提升(避免长上下文稀释)。

案例 2:客服对话的状态外置

某 SaaS 公司将客服对话历史存入 Redis,每次只向模型发送"摘要 + 最近 3 轮 + 当前问题",单次输入 token 从 6000 降至 1200,月度 API 成本从 5 万降至 1.2 万元,且响应延迟从 4 秒降至 1.5 秒。

案例 3:缓存优化节省成本

某 AI 编程助手利用 OpenAI 的 Prompt Cache,将系统提示与项目上下文作为稳定前缀缓存,缓存命中率 85%,输入 token 成本下降 70%,首 token 延迟下降 40%。

常见问题

Q1:1 个汉字到底等于几个 token? A:取决于模型与字频。常见汉字多为 1-2 个 token,生僻字可能 3 个。tiktoken 可精确计算。

Q2:上下文窗口越大越好吗? A:不一定。大窗口带来更高成本与延迟,且存在"中间迷失"问题。应结合任务选择,并配合分块与检索策略。

Q3:Chat Completions 与 Responses API 该用哪个? A:构建 Agent 优先 Responses API(内置工具、服务端状态);简单对话或兼容旧系统用 Chat Completions。

Q4:为什么工具调用的 token 消耗比想象中高? A:因为工具的 JSON Schema 定义、历史工具调用记录与返回结果都会计入输入 token,且每轮重复发送。

面试题

1. 解释 BPE 分词的原理。 参考答案:BPE 从字节级初始化,统计高频相邻 token 对并合并,迭代扩展词表,最终得到一个平衡覆盖率与词表大小的分词方案。

2. 什么是"Lost in the Middle"现象?如何缓解? 答题要点:模型对位于超长上下文中部的信息召回率下降。缓解方法:关键信息放在开头或结尾、分块检索、压缩无关上下文、使用重排序。

3. 设计一个多轮对话系统,如何控制 token 成本? 答题要点:滑动窗口 + 摘要、向量检索历史、缓存稳定前缀、按需调用工具、小模型预筛 + 大模型精答。

4. 输入 token 与输出 token 价格为何不同? 参考答案:输出 token 需要逐个自回归生成,计算量远大于输入的并行前向传播,且推理延迟更高,因此定价更高。

练习题

  1. Token 实验:用 tiktoken 统计一段 500 字中文与等长英文的 token 数差异,并计算 100 万次调用的成本差。
  2. 上下文设计:为一个法律咨询 Agent 设计上下文管理方案,要求支持 100 轮以上多轮对话且单次输入不超过 4K token。
  3. 思考题:如果模型上下文窗口无限大,是否还需要 RAG?为什么?

小结

Token 是 LLM 的计量与处理单元,BPE 决定了文本到 token 的映射;上下文窗口限制了模型单次能"看见"的信息量。合理管理上下文——通过摘要、滑动窗口、检索与缓存——是控制成本与提升效果的关键工程能力。延伸阅读:OpenAI Tokenizer 在线工具、tiktoken GitHub、论文《Lost in the Middle: How Language Models Use Long Contexts》。