A02第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

Token 与上下文

Token 与上下文 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

模型没有记忆。读完本课你能说清「记忆」是怎么伪造出来的,能估算一次请求要占用多少上下文,并写出长对话的裁剪策略。

问题场景

你先告诉助手「接下来所有代码都用 TypeScript」,聊了五十轮之后它开始输出裸 JS。你质问「我开头说过什么」,它答不上来。是模型变笨了吗?不——它根本没「听到」那句话:要么被你的裁剪策略裁掉了,要么上下文窗口已经被塞满了。

核心解释

模型是无状态的:每次调用都是一次全新请求,服务端不记得上次聊了什么。所谓「多轮对话」,是应用把历史消息数组每次都重新完整发送:

const messages = [
  { role: "system", content: "你是学习助手,回答用中文。" },
  { role: "user", content: "接下来所有代码都用 TypeScript" },
  { role: "assistant", content: "好的,之后示例都带类型标注。" },
  { role: "user", content: "写一个防抖函数" }, // 本轮新输入
];
// 这个数组随每次请求重新发给模型

发送的一切都按 token(词元) 计量——模型处理文本的最小单位,一个汉字通常对应一到两个 token,具体切分因分词器而异。三项共享同一个上限:

  • 输入侧:system + 全部历史 + 本轮输入
  • 输出侧:模型本次生成的回答
  • 上限:上下文窗口,即单次请求允许的 token 总量(随模型不同而不同)

两个容易混淆的口径要分清:总额度是账户在计费、限速维度上累计消耗的 token,按周期结算;单次容量是上下文窗口,超了这条请求直接报错,跟余额无关。省前者靠少聊、压缩历史;省后者靠裁剪、摘要。报错时先分清是哪一类。

示例拆解

长对话的裁剪策略:system 永远保留,历史只留最近 N 条:

function buildContext(
  system: string,
  history: Array<{ role: "user" | "assistant"; content: string }>,
  keep = 20
) {
  return [
    { role: "system" as const, content: system },
    ...history.slice(-keep), // 从尾部取最近 keep 条
  ];
}

关键点:

  • 从尾部取是因为最近的对话相关性最高;最早的约定最容易被裁掉——这正是开头场景的原因。重要约定要么写进 system,要么定期在新消息里复述。
  • 裁剪只影响发给模型的内容;数据库里仍应存全量历史(A13 课讲持久化),两个层面别混为一谈。
  • 本站的真实实现(app/api/ai/chat/route.ts)就是查最近 20 条再反转顺序,与本例一致。
  • 更完整的策略还会给旧对话做摘要、把超长正文替换成摘要链接,本课先掌握「留头留尾」。

验证别靠肉眼估:各提供方都有 token 计数工具,响应里也有 usage 用量字段,具体以所用提供方文档为准。本站把每次调用的 prompt_tokens / completion_tokens 记进调用记录,可以从记录反推每轮开销。

动手任务

  1. 估算一笔账:system 约 200 token、历史 20 条平均每条 300 token、预计输出 800 token——这次请求大约占多少?如果窗口上限是 8000,够不够?
  2. 把 buildContext 的 keep 改成 4,用你自己的话描述用户会在什么场景下察觉「模型失忆」。
  3. 判断题:把历史裁到只剩最近 3 条,能降低计费成本吗?能降低单次超窗风险吗?分别回答并说明理由。

常见错误

  • 以为模型记得开头的约定:它只记得这次发过去的。重要约定进 system 或定期复述,别指望它「记性」。
  • 混淆两个口径:余额充足但窗口超限照样报错;窗口再大额度用完也照样失败。
  • 裁剪把 system 一起裁掉:模型立刻丢失固定要求,回答风格漂移。system 永远置顶保留。
  • 按字符数硬算 token:中英文、代码、标点的换算比例都不同,粗估可以,精确计数以提供方工具为准。

检查与复盘

自查清单:

  • 能解释「多轮对话」的假记忆是怎么实现的
  • 能区分总额度与单次容量,并说出各自的开销省法
  • 能写出保留 system、截取最近 N 条的裁剪函数

参考资料

  • OpenAI 文档:Text generation —— 上下文窗口与消息数组的官方说明
  • openai/tiktoken —— OpenAI 官方分词器,直观感受一段文本被切成多少 token
  • 下一课 A03 把这个消息数组真正发出去:在服务端完成一次受控的真实调用。
检查题共 3 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

用户在对话开头说「接下来所有代码都用 TypeScript」,聊了五十轮后模型开始输出裸 JS,问它开头说过什么也答不上来。从实现角度,最可能的原因是?

Q2读代码中等

阅读长对话的上下文组装代码。history 里有 50 条 user/assistant 消息,调用 buildContext(system, history) 后,发给模型的 messages 是什么形态?

function buildContext(system, history, keep = 20) {
  return [
    { role: "system", content: system },
    ...history.slice(-keep),
  ];
}
Q3多选中等(多选,全部选对才算正确)

关于 token 计量的「总额度」与「单次容量(上下文窗口)」两个口径,下列说法正确的有?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...