资讯详情 上下文是什么?token 怎么计费?
📅 2026/10/20 5:21:00
LLM 的本质是概率预测一切 Agent 相关的概念都是围绕上下文Context的工程实现。那么到底什么是「上下文」其实很简单就是一个messages数组一切 AI 工具都在围绕这个东西雕花。这篇我们直接在终端里用 curl 命令发 HTTP 请求调用大模型的 API。动手和 LLM 交互几次就能彻底理解什么是上下文之后再动手开发 Agent 就游刃有余了。准备工作你需要一个DeepSeek API Key如果还没有就先去创建一个。curl 是命令行里发 HTTP 请求的工具macOS 和 Linux 自带Windows 用户在 Git Bash 或 CMD 里也能直接用。第一次调用拿到 API Key 后在终端执行这条命令curl -s https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer 你的API_KEY \ -d { model: deepseek-flash, thinking: {type: disabled}, messages: [ {role: user, content: 你好} ] }curl 的几个参数-s让 curl 不显示进度条-H设置请求头设置 API key 鉴权-d后面跟 JSON 格式的请求体。看请求体里的内容model指定用哪个模型messages是一个数组里面放着发给模型的消息。我们把发给 LLM 的所有输入统称为Prompt提示词整个messages数组就是 Prompt 的载体。数组里role为user表示这是用户说的话content是具体内容单独这条消息叫User Prompt用户提示词。后面还会遇到role为system的消息叫System Prompt系统提示词到时候再细讲。关于思考模式deepseek-flash默认开启思考模式模型会先写出思维链再给最终答案。本文所有 curl 示例都会带一个thinking: {type: disabled}字段把思考模式关掉让响应保持简洁方便讲解。在后面动手实现一个 Coding Agent 中处理复杂任务时会开启思考模式。返回的 JSON 长这样省略了不重要的字段就这么简单你发一个messages数组模型给你返回一条message一来一回就是一次对话。choices是个数组但默认只返回一条取第一个就行模型的回答就在choices[0].message.content里。注意message里的role字段Request 中我们用user标记用户说的话Response 中用assistant标记这是模型的回答。后面你还会碰到system和tool等其他角色具体的作用我们遇到了再说。finish_reason告诉你模型为什么停下来这里是stop说明正常说完了。我们之后还会遇到其他的结束原因比如因为回答太长被截断这个字段会变成length或者模型需要调用工具这个字段会变成tool_calls。usage记录了这次调用消耗了多少 token跟费用直接相关具体的参数含义后面会讲。文档里给出的是某次实测的数字你跟着练时拿到的数会有小幅波动看清相对趋势就行。模拟多轮对话一次调用不过瘾我们来模拟一段连续对话。先跟模型做个自我介绍curl -s https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer 你的API_KEY \ -d { model: deepseek-flash, thinking: {type: disabled}, messages: [ {role: user, content: 我叫小明我是一个程序员} ] }返回的 JSON{ choices: [ { index: 0, message: { role: assistant, content: 你好小明 作为同行很高兴认识你。程序员的世界里我们经常遇到各种挑战和乐趣。你现在在做什么项目是前端、后端、全栈还是对某个特定领域比如AI、游戏开发、云计算特别感兴趣 }, 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 -s https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer 你的API_KEY \ -d { model: deepseek-flash, thinking: {type: disabled}, messages: [ {role: user, content: 我叫什么} ] }返回的 JSON{ 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数组。比如刚才的对话我们把所有对话历史都塞进messages数组再问一遍「我叫什么」curl -s https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer 你的API_KEY \ -d { model: deepseek-flash, thinking: {type: disabled}, messages: [ {role: user, content: 你好}, {role: assistant, content: 你好很高兴见到你 我是DeepSeek由深度求索公司创造的AI助手。}, {role: user, content: 我叫小明我是一个程序员}, {role: assistant, content: 你好小明很高兴认识你作为程序员你一定经常和代码、逻辑打交道吧}, {role: user, content: 我叫什么} ] }这次返回的 JSON{ 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数组里有没有之前的对话记录。顺便注意一下usage里的prompt_tokens: 63前面只发一句「我叫什么」时才 7 个 token现在带上完整历史变成了 63 个。看一下这个messages数组的结构每一轮对话就是一条user消息加一条assistant消息像三明治一样交替排列。最后追加一条新的user消息就是当前的提问。模型读到这个数组就好像经历了整段对话一样。所以所谓的「多轮对话」本质上就是你每次发请求时把所有聊天记录重新发一遍。模型是无状态的所有「记忆」都在你手动拼的messages数组里。你用 ChatGPT 聊天时感觉它「记得」你说过的话并不是模型真的有记忆而是 ChatGPT 的客户端在背后帮你维护了这个数组每次你发一句话客户端把你的消息和模型之前的回答一起追加进去然后把整个数组发给 API。所有 AI 产品都是这样没有例外。对 LLM 来说一切都是 token前面每次调用的返回值里都有一个usage字段里面的数字跟你的钱包直接相关。LLM 和 Agent 的核心术语和原理 讲过LLM 的工作方式是根据已有的 token 预测下一个 token。token 是模型处理文本的最小单位你可以理解为模型把文本切成的「碎片」。但 token 不等于字符也不等于单词不同语言、不同模型的切法不一样。大致的规律是一个英文常见词通常是 1 个 token如hello一个汉字大约 1-2 个 token代码和标点的切法各有不同。你发给 API 的messages数组在送进模型之前会先被一个叫tokenizer的工具拆成 token 序列。模型从头读取所有 token然后开始预测后续内容直到它认为该停了或者达到了长度上限。这个过程从用户的视角就是发给模型一句话模型根据整个对话历史的信息给出了一句回复。¶输入 token 和输出 token看一个具体的例子usage: { prompt_tokens: 5, completion_tokens: 32, total_tokens: 37 }prompt_tokens是你发送的messages数组被拆成了多少个 token也就是输入消耗。completion_tokens是模型生成的回答包含多少个 token也就是输出消耗。total_tokens是两者之和。模型服务商按 token 数量收费通常以「每百万 token」为单位标价而且输入和输出的单价不同输出一般更贵。因为输入 token 可以并行处理而输出 token 只能一个一个串行生成每个都需要模型单独做一次预测计算GPU 利用效率更低所以更贵一些。提示关于 LLM 推理的具体流程会在LLM 怎么预测下一个 token 中具体展开。对话越长token 越多回看前面几次调用的prompt_tokens请求内容messages 条数prompt_tokens只发「你好」1 条5只发「我叫什么」1 条7带完整历史问「我叫什么」5 条63同样问「我叫什么」不带历史只花 7 个输入 token带上完整对话历史就要 63 个。因为每次请求都要把所有历史消息重新发一遍所以对话历史越长messages数组越长prompt_tokens就越大费用也越高。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。为什么需要这个机制想一下多轮对话的场景每一轮请求的messages前半部分之前的对话历史都是一样的只有最后一条新消息不同。前缀都一样那把前缀的计算结果缓存起来下一次请求只计算新增的部分就行了。业界把这个机制叫Prompt Cache提示缓存模型服务商会自动做这件事第一次请求时把输入的前缀算一遍并缓存后续请求如果前缀和缓存里某段一致就直接复用只计算新增的尾巴。缓存命中的 token 价格通常便宜得多依模型提供商而定大部分是正常输入价格的 10%响应也更快。所以多轮对话虽然每次都会发送全量messages对话历史但前缀命中缓存后实际成本低得多费用没有想象中那么夸张。不同服务商的缓存策略不太一样常见的几个差异点触发条件有的服务商对短 prompt 不缓存需要 prompt 达到一定长度才会启动缓存。我们上面的示例中对话内容非常少没有达到缓存的触发条件所以你会发现即便把相同的请求重发多次都不会命中缓存。过期时间从几分钟到几小时不等超过过期时间未被使用的缓存会被清理。字段名OpenAI 用cached_tokensDeepSeek 同时提供prompt_cache_hit_tokens和cached_tokensAnthropic 又是另一套字段。自动缓存 vs 手动缓存本文用的 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标记的消息及其之前的内容会被缓存后续请求如果前缀相同就能命中没有标记的部分不会被缓存。我们会在给 Claude Code 发一句 Hi会发生什么 里分析一个实际请求。不过不管哪种方式底层原理都一样缓存messages的公共前缀命中缓存的话 token 价格更便宜。缓存写入也要花钱前面说命中缓存能省钱但缓存不是白存的。第一次把前缀存进缓存服务器要腾一块地方把计算结果存下来这个「写入」动作有的服务商会单独收一笔钱而且比普通输入还贵。所以严格来说输入 token 的价格有三档普通输入既没命中缓存、也没打算缓存的部分按原价算。缓存写入缓存创建第一次把前缀存进缓存最贵。缓存命中复用已经存好的前缀最便宜。要不要对这一步单独收费各家策略不一样有的免费有的要单收一笔创建费。本文一直用的 DeepSeek 就属于免费那类并未单独对创建缓存收费所以你只需要计算命中缓存和未命中缓存的输入 token 费用而 Claude 和最新的 GPT 5.6 则把缓存写入单拎出来收费。以 Claude 为例以未命中缓存的输入价格为基准类型相对基础输入价未命中缓存的输入1 倍缓存命中0.1 倍缓存写入保留 5 分钟1.25 倍缓存写入保留 1 小时2 倍GPT 从 5.6 这代开始也对缓存写入按 1.25 倍收费更早的模型写入缓存不额外收钱。写入缓存为什么比普通输入还贵因为缓存要实打实占用服务器的存储资源。而且同一份缓存你要求保留的时间越长创建时收的费用越高所以 Claude 里保留 1 小时2 倍就比保留 5 分钟1.25 倍贵。这笔写入费也会记在usage里。DeepSeek 的返回里只有命中和未命中其他对缓存写入收费的模型你可能会看到类似cache_creation_input_tokens或者cache_write_tokens的字段记录缓存写入的 token 数量。那缓存到底还划不划算关键看这段前缀之后会被命中多少次。写入时多花的那点钱要靠后续每次命中省下的钱慢慢摊平。一段前缀如果存进去只用一次就过期了写入费就白花反而不如不缓存如果能被反复命中几十次写入成本早就摊得可以忽略了。对于 Agent 场景来说由于消息前缀基本不变只是不断往最后追加消息所以缓存命中率一般都很高即便对缓存写入额外收费也是非常划算的。token 如何计费到底调用一次 LLM 花多少钱我们造个例子算一下。先看价格表这里以我们一直在用的deepseek-flash为例使用 2026 年 9 月 10 日的空闲时段价格具体以 DeepSeek 官网 为准。高峰时段为北京时间周一至周五 9:00–12:00、14:00–18:00价格是下表的两倍其余时间为空闲时段项目单价元 / 百万 token输入缓存未命中1输入缓存命中0.02输出4注意缓存命中的输入单价大部分其他模型的缓存命中价格是未命中缓存价格的 1/10而 DeepSeek 只有未命中的 1/50所以说 DeepSeek 模型性价比非常高。前面 curl 示例里的messages都只有几十 token都没有触发缓存命中。真实场景中一次请求上万 token 很常见一段系统提示加上几十轮对话历史就能到这个量级假设某次请求返回的usage长这样usage: { 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 }来手动算一下这个请求的计费缓存命中的输入prompt_cache_hit_tokens11500 × 0.02 / 1,000,000 0.00023 元缓存未命中的输入prompt_cache_miss_tokens500 × 1 / 1,000,000 0.0005 元输出completion_tokens800 × 4 / 1,000,000 0.0032 元累加起来这次调用总共花了 0.00393 元。再看一下没有 Prompt Cache 时的对比同样的 12000 输入 token 全部按 miss 计价输入12000 × 1 / 1,000,000 0.012 元输出800 × 4 / 1,000,000 0.0032 元累加起来这次调用总共花了 0.0152 元。有没有缓存差了近 4 倍。所以后面讲 Agent 设计时你会反复看到「保持messages前缀稳定」的思路只要缓存命中率高输入 token 就算堆到几万也不会让费用失控。反过来如果一个 Agent 每一步都去改系统提示或者往messages中间插消息破坏了前缀一致性缓存全部失效费用会翻好几倍。总结本文展示了和 LLM 对话最原始的样子一个 HTTP 接口一个messagesJSON 数组一来一回。ChatGPT、Claude以及各种 AI 编程工具底层都是这个结构。它们做的事情无非是帮你维护messages数组再加上一些上下文管理策略什么时候该压缩历史、什么信息要保留、什么可以丢掉让 LLM 在有限的上下文窗口里尽可能聪明地工作。现在你应该更直观的理解LLM 和 Agent 的核心术语和原理 中为什么说LLM 是基座Agent 是围绕 LLM 的一套工程体系了。和 LLM 交互就是这么朴实无华全靠一个messages数组而 Agent 的核心任务就是组织好这个messages数组让它在有限的上下文窗口里尽可能聪明地工作。是不是觉得很神奇基于一个普通的messages数组就能构造出能帮我们解决复杂任务的 Agent 工具让我们逐步深入学习下一篇使用 AI 工具的实用技巧 会基于本文的原理聊聊日常使用 AI 工具时的几个实用技巧。