✦ 本文目录

  1. 上下文就是一个 messages 数组
  2. 模型是无状态的
  3. 多轮对话的本质:重发全部历史
  4. Token:模型处理文本的最小单位
  5. 输入 Token 与输出 Token
  6. Prompt Cache:省钱的缓存机制
  7. 缓存写入也要花钱
  8. Token 计费实例
  9. 总结

本文基于 labuladong 的文章 《LLM 的上下文是什么?token 怎么计费?》 整理。原文用 curl 命令直接调用大模型 API,在终端里一步步演示了 LLM 交互的最原始形态。

上下文就是一个 messages 数组

所有 AI 工具——ChatGPT、Claude、Cursor——底层都在围绕一个东西雕花:messages 数组。

核心概念

所谓「上下文」(Context),说穿了就是一个 JSON 数组。你发给大模型 API 的请求体长这样:

// 一次最简单的 API 调用 { "model": "deepseek-v4-flash", "messages": [ { "role": "user", "content": "你好" } ] }

发给 LLM 的所有输入统称为 Prompt(提示词),整个 messages 数组就是 Prompt 的载体。数组中 roleuser 的消息叫 User Prompt(用户提示词)rolesystem 的叫 System Prompt(系统提示词)

模型返回的回答在 choices[0].message.content 里,其中 roleassistant,表示这是模型的回复。一来一回,就是一次对话。

你的输入
messages 数组
模型推理
预测下一个 Token
返回回答
choices[0].message

模型是无状态的

你以为它「记住」了你说的话?并没有。每次 API 调用都是独立的。

关键原理

一个实验就能验证:先告诉模型「我叫小明」,下一次请求直接问「我叫什么」——模型答不上来,因为它根本不知道「上一次请求」的存在。

❌ 误解:模型有记忆

  • 模型记住了之前的对话内容
  • 像人类一样有短期记忆
  • 所以能接住上下文聊天

✅ 实际:完全无状态

  • 每次 API 调用都是全新的
  • 上一次请求与这一次毫无关系
  • 「记忆」全靠你把历史塞进 messages
💡 关键认知:你用 ChatGPT 聊天时感觉它「记得」你说过的话,并不是模型真的有记忆,而是 ChatGPT 的客户端在背后帮你维护了 messages 数组——每次你发一句话,客户端把你的消息和模型之前的回答一起追加进去,然后把整个数组发给 API。所有 AI 产品都是这样,没有例外。

多轮对话的本质:重发全部历史

要让模型「记住」之前的对话,唯一的办法就是把完整的对话历史全部塞进 messages 数组。

上下文工程

同样的「我叫什么?」,不带历史时模型答不上来,带上完整历史就能答出来。区别只在于 messages 数组里有没有之前的对话记录。

// 带完整历史的 messages 数组 { "messages": [ { "role": "user", "content": "你好" }, { "role": "assistant", "content": "你好!很高兴见到你!" }, { "role": "user", "content": "我叫小明,我是一个程序员" }, { "role": "assistant", "content": "你好小明!很高兴认识你" }, { "role": "user", "content": "我叫什么?" } ] } // 模型回答:"你叫小明呀,刚才你告诉我的~"

messages 数组像三明治一样交替排列:每一轮对话就是一条 user 消息加一条 assistant 消息,最后追加一条新的 user 消息就是当前的提问。模型读到这个数组,就好像经历了整段对话一样。

⚠️ 重要推论:所谓「多轮对话」,本质上就是你每次发请求时把所有聊天记录重新发一遍。对话越长,messages 数组越大,消耗的 Token 越多,费用也越高。

Token:模型处理文本的最小单位

Token 不等于字符,也不等于单词。它是模型把文本切成的「碎片」。

基础概念

LLM 的工作方式是根据已有的 Token 预测下一个 Token。你发给 API 的 messages 数组,在送进模型之前,会先被一个叫 tokenizer 的工具拆成 Token 序列。

🔤

英文 Token

一个英文常见词通常是 1 个 Token(如 hello)。罕见的词可能被拆成多个 Token。

🀄

中文 Token

一个汉字大约 1-2 个 Token。中文的 Token 化效率通常比英文低,同样内容用中文写会消耗更多 Token。

💻

代码 Token

代码和标点的切法各有不同,空格、括号、关键字都有各自的 Token 规则。

⚗️

模型差异

不同模型用不同的 tokenizer,同样的文本在不同模型上 Token 数会有差异。

messages 数组
JSON 文本
Tokenizer
拆成 Token 序列
模型读取
从头开始预测
逐个生成
直到结束符

输入 Token 与输出 Token

每次 API 调用的返回值里都有一个 usage 字段,里面的数字跟你的钱包直接相关。

计费基础
// API 返回的 usage 字段 "usage": { "prompt_tokens": 5, // 输入 Token 数 "completion_tokens": 32, // 输出 Token 数 "total_tokens": 37 // 两者之和 }
字段 含义 价格特点
prompt_tokens 你发送的 messages 数组被拆成的 Token 数(输入) 可以并行处理,较便宜
completion_tokens 模型生成的回答包含的 Token 数(输出) 串行逐个生成,更贵
total_tokens 输入 + 输出的总和
💡 为什么输出比输入贵?输入 Token 可以并行处理(一次性全部读取),而输出 Token 只能一个一个串行生成,每个都需要模型单独做一次前向预测计算,GPU 利用效率更低,所以输出单价更高。

对话越长,Token 越多

请求内容 messages 条数 prompt_tokens
只发「你好」 1 条 5
只发「我叫什么?」 1 条 7
带完整历史问「我叫什么?」 5 条 63

同样问「我叫什么?」,不带历史只花 7 个输入 Token,带上完整对话历史就要 63 个。因为每次请求都要把所有历史消息重新发一遍

Prompt Cache:省钱的缓存机制

多轮对话每次都发全量历史,但前缀都是一样的——把前缀缓存起来,只算新增的部分就行。

成本优化

每一轮请求的 messages 前半部分(之前的对话历史)都是一样的,只有最后一条新消息不同。前缀都一样,把前缀的计算结果缓存起来,下一次请求只计算新增的尾巴——这就是 Prompt Cache(提示缓存)

第 1 轮请求
完整计算 + 缓存前缀
第 2 轮请求
前缀命中缓存
只算新增消息
第 N 轮请求
缓存命中率更高
成本接近线性增长

缓存命中的 Token 价格通常便宜得多(大部分是正常输入价格的 10%),响应也更快。所以多轮对话虽然每次发送全量历史,但前缀命中缓存后实际成本低得多。

自动缓存 vs 手动缓存

自动缓存(如 DeepSeek)

  • 服务商自动完成,不需要额外操作
  • 两次请求的 messages 前缀相同(且达到触发条件)即自动复用
  • 开发者无感知,开箱即用
  • 但短 prompt 可能不触发缓存

手动标记(如 Claude)

  • 需要调用方在 messages 中添加 cache_control 字段
  • 手动指定缓存断点位置
  • 更灵活但更繁琐
  • 带标记的消息及其之前的内容会被缓存
💡 各服务商字段名差异:
OpenAIcached_tokens
DeepSeekprompt_cache_hit_tokens + cached_tokens(兼容)
Anthropic:另一套字段,且需手动 cache_control 标记

缓存写入也要花钱

命中缓存能省钱,但第一次把前缀存进缓存,有的服务商会单独收一笔钱——而且比普通输入还贵。

进阶知识

严格来说,输入 Token 的价格有三档:

类型 说明 相对价格
普通输入 既没命中缓存、也没打算缓存的部分 1 倍(基准)
缓存写入 第一次把前缀存进缓存 1.25 ~ 2 倍(最贵)
缓存命中 复用已存好的前缀 0.1 倍(最便宜)
⚠️ 缓存写入为什么比普通输入还贵?因为缓存要实打实占用服务器的存储资源。保留时间越长,创建时收费越高。以 Claude 为例:保留 5 分钟 = 1.25 倍,保留 1 小时 = 2 倍。

各服务商策略对比

服务商 缓存方式 缓存写入收费
DeepSeek 自动缓存 免费(不单独收费)
Claude 手动标记 cache_control 5 分钟 1.25 倍 / 1 小时 2 倍
GPT 5.6+ 自动缓存 1.25 倍
GPT 5.6 以前 自动缓存 免费
💡 缓存到底划不划算?关键看这段前缀之后会被命中多少次。写入时多花的那点钱,靠后续每次命中省下的钱慢慢摊平。对 Agent 场景来说,消息前缀基本不变、只是不断往最后追加消息,缓存命中率很高,即便对写入额外收费也非常划算。

反过来,如果一个 Agent 每一步都改系统提示或往 messages 中间插消息,破坏前缀一致性,缓存全部失效,费用会翻好几倍。

Token 计费实例

用一个具体例子算一次账,看看调用一次 LLM 到底花多少钱。

实战计算

deepseek-v4-flash 为例(2026 年 7 月价格,具体以官网为准):

项目 单价(元 / 百万 Token)
输入(缓存未命中) 1
输入(缓存命中) 0.02(仅 1/50,性价比极高)
输出 2

假设某次请求返回的 usage 如下(一段系统提示加几十轮对话,上万 Token 很常见):

"usage": { "prompt_tokens": 12000, "completion_tokens": 800, "prompt_cache_hit_tokens": 11500, // 命中缓存 "prompt_cache_miss_tokens": 500 // 未命中 }

有 Prompt Cache 时的费用

项目 计算 费用(元)
缓存命中的输入 11,500 × 0.02 / 1,000,000 0.00023
缓存未命中的输入 500 × 1 / 1,000,000 0.0005
输出 800 × 2 / 1,000,000 0.0016
合计 0.00233 元

无 Prompt Cache 时的费用(对比)

项目 计算 费用(元)
输入(全部按未命中) 12,000 × 1 / 1,000,000 0.012
输出 800 × 2 / 1,000,000 0.0016
合计 0.0136 元

有没有缓存差了近 6 倍。这就是为什么 Agent 设计中反复强调「保持 messages 前缀稳定」——只要缓存命中率高,输入 Token 就算堆到几万也不会让费用失控。

全文总结

messages 数组 — 上下文的唯一载体,一切 AI 工具围绕它雕花
模型无状态 — 每次 API 调用独立,"记忆"全靠拼接历史
多轮对话 = 重发全量历史 — 对话越长,Token 越多
Token 计费 — 输入便宜(并行),输出贵(串行)
Prompt Cache — 缓存公共前缀,命中后便宜 10 倍以上
Agent 工程 — 组织好 messages 数组,保持前缀稳定,控制成本
🎯 一句话:LLM 是基座,Agent 是围绕 LLM 的一套工程体系。和 LLM 交互就是这么朴实无华——全靠一个 messages 数组,而 Agent 的核心任务就是组织好这个数组,让它在有限的上下文窗口里尽可能聪明地工作

ChatGPT、Claude、各种 AI 编程工具,底层都是这个结构。它们做的事情无非是帮你维护 messages 数组,再加上一些上下文管理策略(什么时候压缩历史、什么信息要保留、什么可以丢掉),让 LLM 在有限窗口里尽可能聪明地工作。