ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

上下文是什么?Token 怎么计费?从 messages数组到 Prompt Cache

上下文是什么?Token 怎么计费?从 messages数组到 Prompt Cache 很多人第一次调用大模型 API 时会把上下文理解成一种模型内部的“记忆”。实际上API 并不会自动替你保存上一次请求。模型每次收到的都是当前请求里的输入。对聊天模型来说上下文最直接的形式就是一个messages数组。多轮对话、token 统计、Prompt Cache以及 Agent 的上下文管理都是围绕这个数组展开的。本文从一个最简单的 HTTP 请求开始逐步说明messages数组如何构成一次对话为什么模型默认不会记住上一次请求输入 token、输出 token 分别代表什么对话变长后为什么费用会增加Prompt Cache 如何降低重复计算和调用成本缓存命中、缓存写入分别如何计费准备工作本文使用 DeepSeek API 做示例。开始前需要准备一个 DeepSeek API Key。curl是命令行中发送 HTTP 请求的工具。macOS 和 Linux 通常自带Windows 用户也可以在 Git Bash 或 CMD 中使用。第一次调用一个messages数组拿到 API Key 后在终端执行下面的命令curl-shttps://api.deepseek.com/chat/completions\-HContent-Type: application/json\-HAuthorization: Bearer 你的API_KEY\-d{ model: deepseek-v4-flash, thinking: {type: disabled}, messages: [ {role: user, content: 你好} ] }这条命令里的参数并不复杂-s不显示进度条。-H设置 HTTP 请求头。这里分别设置内容类型和 API Key 鉴权信息。-d指定请求体内容是 JSON。model指定使用的模型。messages存放发送给模型的消息。我们把发送给 LLM 的输入统称为 Prompt而messages数组就是 Prompt 的载体。数组中的role为user表示这条消息来自用户content是消息正文。单独的一条用户消息可以称为 User Prompt。后续还会看到system、assistant和tool等角色。为什么关闭思考模式DeepSeek V4 默认开启思考模式。模型会先生成思维链再给出最终答案。为了让本文的响应更简洁所有curl示例都带有下面这个字段thinking:{type:disabled}在处理复杂任务时例如后续实现 Coding Agent可以打开思考模式。返回结果怎么看一次调用可能返回类似下面的 JSON。示例省略了部分不重要的字段{id:b7da8cc7-4487-4ffa-b802-3b250ab53229,object:chat.completion,created:1777257146,model:deepseek-v4-flash,choices:[{index:0,message:{role:assistant,content:你好很高兴见到你有什么我可以帮你的吗},finish_reason:stop}],usage:{prompt_tokens:5,completion_tokens:32,total_tokens:37,prompt_tokens_details:{cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:5}}模型的回答位于choices[0].message.contentchoices是一个数组默认通常只返回一条结果因此直接取第一项即可。message.role表示消息角色请求中的user表示用户消息。响应中的assistant表示模型消息。后续还会遇到system和tool等角色。finish_reason表示模型为什么停止生成stop正常生成结束。length达到长度限制回答被截断。tool_calls模型需要调用工具。usage记录这次调用消耗的 token后面会用它计算费用。示例中的 token 数来自一次实际调用自己练习时可能会有小幅波动重点是理解字段含义和变化趋势。到这里一次最小的对话就完成了客户端发送一个messages数组模型返回一条message。多轮对话模型为什么会“忘记”先发送一条自我介绍curl-shttps://api.deepseek.com/chat/completions\-HContent-Type: application/json\-HAuthorization: Bearer 你的API_KEY\-d{ model: deepseek-v4-flash, thinking: {type: disabled}, messages: [ {role: user, content: 我叫小明我是一个程序员} ] }模型可能返回{choices:[{index:0,message:{role:assistant,content:你好小明作为同行很高兴认识你。你现在在做什么项目},finish_reason:stop}],usage:{prompt_tokens:9,completion_tokens:105,total_tokens:114,prompt_tokens_details:{cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:9}}接着单独发送一句“我叫什么”curl-shttps://api.deepseek.com/chat/completions\-HContent-Type: application/json\-HAuthorization: Bearer 你的API_KEY\-d{ model: deepseek-v4-flash, thinking: {type: disabled}, messages: [ {role: user, content: 我叫什么} ] }这次模型可能回答{choices:[{index:0,message:{role:assistant,content:抱歉我无法知道您的名字因为我们没有之前的对话记录。},finish_reason:stop}],usage:{prompt_tokens:7,completion_tokens:34,total_tokens:41,prompt_tokens_details:{cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:7}}刚才明明介绍过自己模型为什么还是不知道名字原因是模型本身是无状态的。每次 API 调用都是独立的上一次请求和下一次请求之间没有自动关联。对模型来说上一次请求甚至不存在。messages数组就是对话记忆如果希望模型“记住”之前的对话需要在下一次请求中把完整的聊天历史重新放进messages数组。例如可以把前面的对话拼成这样curl-shttps://api.deepseek.com/chat/completions\-HContent-Type: application/json\-HAuthorization: Bearer 你的API_KEY\-d{ model: deepseek-v4-flash, thinking: {type: disabled}, messages: [ {role: user, content: 你好}, {role: assistant, content: 你好很高兴见到你我是 DeepSeek由深度求索公司创造的 AI 助手。}, {role: user, content: 我叫小明我是一个程序员}, {role: assistant, content: 你好小明很高兴认识你。}, {role: user, content: 我叫什么} ] }这次模型就可以根据历史回答{choices:[{index:0,message:{role:assistant,content:你叫小明刚才你告诉我的。},finish_reason:stop}],usage:{prompt_tokens:63,completion_tokens:29,total_tokens:92,prompt_tokens_details:{cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:63}}同样的问题“我叫什么”不带历史时模型答不上来带上完整历史后就能答出来。两次请求的区别只在于messages数组中是否包含之前的对话记录。这也解释了为什么前一次请求只有 7 个prompt_tokens带上完整历史后变成了 63 个。每一轮对话通常由一条user消息和一条assistant消息交替组成。最后再追加一条新的user消息就是当前问题。模型读取整个数组后会按照这段上下文生成回答。因此多轮对话的本质是每次请求都把聊天记录重新发送一遍。模型没有自动记忆所谓记忆都存在客户端维护的messages数组里。使用 ChatGPT、Claude 或其他 AI 产品时客户端会在后台完成这项工作把用户新发送的消息追加到历史数组中。把模型之前的回答也追加进去。将完整数组再次发送给 API。不同产品会在此基础上增加上下文压缩、历史裁剪等策略但底层仍然离不开消息数组。对 LLM 来说一切输入都是 token前面的响应里都有一个usage字段其中的数字会直接影响调用费用。LLM 的工作方式是根据已有 token 预测下一个 token。token 是模型处理文本时使用的基本单位可以把它理解为模型切分出来的文本片段。token 不等于字符也不等于单词。不同语言和不同模型的切分方式可能不同大致可以这样理解一个常见英文单词通常约为 1 个 token例如hello。一个汉字大约占 1 到 2 个 token。代码和标点符号的切分方式各不相同。messages数组发送给模型前会先经过 tokenizer被拆成 token 序列。模型从头读取这些 token然后不断预测后续内容直到它认为回答已经结束或者达到长度上限。从用户角度看这个过程就是发送一句话模型结合整个对话历史生成一段回复。输入 token、输出 token 和总 token先看一个简单的usage示例usage:{prompt_tokens:5,completion_tokens:32,total_tokens:37}三个字段分别表示prompt_tokens发送给模型的messages数组被拆成的 token 数量也就是输入消耗。completion_tokens模型生成的回答包含的 token 数量也就是输出消耗。total_tokens输入 token 和输出 token 的总和。模型服务商通常按每百万 token 计价而且输入和输出使用不同单价。输出一般更贵因为输入 token 可以并行处理而输出 token 需要一个接一个地生成每个 token 都需要模型进行一次预测GPU 利用效率更低。对话越长输入 token 越多前面几次请求的数据如下请求内容messages条数prompt_tokens只发送“你好”15只发送“我叫什么”17带完整历史询问“我叫什么”563问题没有改变但输入 token 从 7 增加到了 63。因为每次请求都会重新发送历史消息所以对话历史越长 → messages 数组越长 → prompt_tokens 越多 → 输入费用越高真实的 Agent 场景通常会包含系统提示词、工具描述、文件内容和几十轮对话历史一次请求达到上万输入 token 并不罕见。Prompt Cache 如何节省费用前面的usage中除了prompt_tokens还出现了缓存相关字段usage:{prompt_tokens:63,prompt_tokens_details:{cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:63}字段含义如下prompt_cache_hit_tokens命中缓存的 token 数量。prompt_cache_miss_tokens未命中缓存、需要重新计算的 token 数量。prompt_tokens_details.cached_tokens与prompt_cache_hit_tokens等价的字段主要用于兼容不同 SDK。多轮对话有一个特点每次请求的前半部分通常相同只有最后一条新消息发生变化。例如[系统提示词][历史对话][历史对话][历史对话][新的问题]前面的公共部分就是缓存前缀。第一次请求时服务端计算这个前缀并保存结果。下一次请求如果前缀仍然一致就可以复用之前的计算结果只处理新增加的内容。这套机制通常称为 Prompt Cache也就是提示缓存。缓存命中后token 价格通常会低很多响应也会更快。不同服务商的策略并不完全相同常见差异包括触发条件有些服务商不会缓存很短的 Prompt只有达到一定长度后才启用缓存。前面的示例内容很短所以重复发送也可能不会命中。过期时间缓存可能只保留几分钟也可能保留几小时。超过期限且没有再次使用缓存就会被清理。字段名称OpenAI 使用cached_tokensDeepSeek 同时提供prompt_cache_hit_tokens和cached_tokensAnthropic 使用另一套字段。自动缓存和手动缓存本文使用的 DeepSeek 模型会自动完成缓存。只要两次请求的messages前缀一致并且达到服务商的触发条件服务端就会自动检测和复用。并不是所有服务商都采用自动缓存。例如Anthropic 的 Claude 模型需要调用方主动设置缓存断点可以在messages中某条消息的content里添加cache_control{model:claude-sonnet-4-6,messages:[{role:user,content:[{type:text,text:这里是很长的对话历史或系统提示...,cache_control:{type:ephemeral}}]},{role:user,content:这是新的提问}]}带有cache_control标记的消息及其之前的内容会被缓存。后续请求的前缀相同就可以命中缓存没有标记的部分不会被缓存。无论服务商采用自动方式还是手动方式底层原理都相同缓存messages的公共前缀命中后减少重复计算。缓存写入也可能收费缓存命中可以省钱但把内容第一次写入缓存并不一定免费。服务器需要保存计算结果有些服务商会针对这次缓存创建单独收费而且价格可能比普通输入更高。严格来说输入 token 可能分成三类类型含义普通输入既没有命中缓存也没有创建缓存的部分按普通输入价格计算缓存写入第一次把公共前缀写入缓存通常是三者中最贵的缓存命中复用已经保存的前缀通常是最便宜的是否对缓存写入收费取决于服务商DeepSeek 不单独收取缓存创建费用因此只需要计算缓存命中和未命中的输入 token。Claude 会将缓存写入单独计费。最新的 GPT 5.6 也会将缓存写入单独计费。以下是 Claude 的相对价格示例以未命中缓存的输入价格为 1 倍类型相对基础输入价未命中缓存的输入1 倍缓存命中0.1 倍缓存写入保留 5 分钟1.25 倍缓存写入保留 1 小时2 倍GPT 从 5.6 这一代开始也会按 1.25 倍收取缓存写入费用。更早的模型写入缓存不额外收费。缓存写入比普通输入贵主要是因为它需要占用服务器存储资源。同一份缓存要求保留的时间越长创建时的费用也可能越高所以 Claude 保留 1 小时的缓存写入价格高于保留 5 分钟。这些费用也会记录在usage中。DeepSeek 的响应里主要能看到命中和未命中字段对缓存写入收费的模型可能会返回下面这类字段cache_creation_input_tokens cache_write_tokens是否值得写入缓存取决于后续能命中多少次如果前缀写入后只使用一次就过期写入费用可能抵消不了节省的费用。如果同一个前缀能被反复命中几十次写入成本就会被摊薄。Agent 通常会不断在消息末尾追加新内容而系统提示词和历史前缀变化较少因此缓存命中率通常较高。即使缓存写入需要额外收费稳定的前缀仍然可能带来明显收益。Token 计费示例下面用deepseek-v4-flash做一次完整计算。价格会调整以下是原文采用的 2026 年 7 月价格示例实际使用时应以 DeepSeek 官方文档 的最新价格为准。项目单价元 / 百万 token输入缓存未命中1输入缓存命中0.02输出2大多数模型的缓存命中价格约为未命中价格的 1/10DeepSeek 示例中的命中价格为未命中价格的 1/50。假设某次请求返回以下usageusage:{prompt_tokens:12000,completion_tokens:800,total_tokens:12800,prompt_tokens_details:{cached_tokens:11500},prompt_cache_hit_tokens:11500,prompt_cache_miss_tokens:500}有缓存时缓存命中的输入 token11500 × 0.02 ÷ 1,000,000 0.00023 元缓存未命中的输入 token500 × 1 ÷ 1,000,000 0.0005 元输出 token800 × 2 ÷ 1,000,000 0.0016 元总费用0.00023 0.0005 0.0016 0.00233 元没有缓存时如果同样的 12,000 个输入 token 全部按未命中价格计算输入12000 × 1 ÷ 1,000,000 0.012 元 输出800 × 2 ÷ 1,000,000 0.0016 元 总费用0.0136 元两种情况的费用相差接近 6 倍。这也是 Agent 设计中经常强调“保持messages前缀稳定”的原因。只要缓存命中率较高即使输入 token 增加到几万费用也不一定会失控。相反如果 Agent 每一步都修改系统提示词或者在messages中间插入消息就会破坏前缀一致性。缓存可能全部失效费用也可能增加数倍。总结和 LLM 对话的基本结构其实很直接HTTP 接口 messages JSON 数组 一次请求和一次响应ChatGPT、Claude 和各种 AI 编程工具底层都可以抽象成这个结构。它们主要替用户维护messages数组再加入上下文压缩、历史保留和信息裁剪等策略让模型在有限的上下文窗口内继续工作。从这个角度看LLM 是负责预测和生成内容的基础模型。Agent 是围绕 LLM 组织上下文、调用工具和完成任务的一套工程系统。messages数组是连接两者的核心载体。token 既影响模型的上下文容量也直接影响调用成本。稳定的消息前缀有利于 Prompt Cache 命中从而降低重复计算费用。理解messages和 token是继续学习 Agent、上下文压缩、工具调用和缓存策略的基础。参考资料原文上下文是什么token 怎么计费DeepSeek API 文档https://api-docs.deepseek.com/
返回列表