news 2026/10/12 21:10:46

AI Agent Token 成本怎么算:prompt_tokens、completion_tokens、usage、上下文窗口一次讲清楚(TaoToken 统一 Key 视角)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent Token 成本怎么算:prompt_tokens、completion_tokens、usage、上下文窗口一次讲清楚(TaoToken 统一 Key 视角)

1. 为什么你的 Agent 账单总比预估高:从一次 NullPointerException 排查说起

很多开发者第一次给 AI Agent 做成本预估时,都会犯同一个错误:拿用户输入的字数去乘单价。结果上线一周,账单比预估高出三到五倍,回头翻代码才发现,真正烧钱的从来不是用户那句「帮我看下这个报错」。

我拿一个真实场景拆给你看。一个 Java 故障排查 Agent,用户输入只有一句话:

帮我看一下这个 NullPointerException 是什么原因

这句话大概 20 个汉字,按字数算便宜得可以忽略。但这次请求实际发给模型的上下文是这样的:

  • system prompt:你是一个 Java 故障排查助手,熟悉 Spring 生态……约 600 Token
  • 最近 5 轮历史对话:约 1800 Token
  • 完整 stack trace 日志:约 2500 Token
  • Controller / Service / Mapper 相关代码:约 2200 Token
  • RAG 检索出的两段内部排障手册:约 3100 Token
  • 工具定义(查 traceId、查订单、查配置):约 900 Token

模型还没开始回答,prompt_tokens已经到 11000 左右。如果它再生成 800 Token 的分析,这次调用就是接近 12000 Token。用户只发了一句话,成本却是那句话的几百倍。

这就是 AI Agent Token 成本核算的第一个认知门槛:你付的钱不是用户说了多少,而是这次请求真正塞进上下文窗口的所有内容。system prompt、历史对话、RAG 文档、工具定义、工具返回结果,只要一起发出去,就一起计费。

第二个门槛是概念混淆。Token 不是字,不是单词,也不是字符。它是模型 tokenizer 切出来的片段。英文里hello可能是 1 个 Token,但一个长单词可能被拆成 3 到 4 个;中文「你好」看起来 2 个字,具体几个 Token 取决于模型自己的分词器。代码、JSON、日志这类结构化文本更不适合按字数硬猜,因为符号、缩进、括号都会被单独切分。

第三个门槛是字段不统一。OpenAI 风格返回prompt_tokens/completion_tokens/total_tokens,Claude 文档里常见的是input_tokens/output_tokens,DeepSeek 还可能多出prompt_cache_hit_tokens、prompt_cache_miss_tokens、completion_tokens_details.reasoning_tokens。如果你只记一个总数,换模型、接缓存、上推理模型之后,账就对不上了。

这篇就按「一次调用从发出到落账」的链路,把 prompt_tokens、completion_tokens、usage、上下文窗口逐项拆开,给你一套可复制的 usage 日志解析配置,再用一次真实请求做 Token 对账,最后说明怎么通过 TaoToken 统一 Key 通道集中看用量。适合正在做 AI Agent、RAG、Tool Calling,且开始关心成本的开发者。

2. TaoToken 统一 Key 前置准备:一个通道管住多模型用量

在讲配置之前,先把「为什么需要一个统一通道」说清楚。做 Agent 的人迟早会遇到这个问题:项目里同时用了几个模型,有的负责规划、有的负责代码生成、有的负责摘要,每个模型一个 Key、一个控制台、一套计费口径。月底想算清楚「这个 Agent 到底花了多少」,得登录三四个后台手动加总,还容易漏掉流式请求没打开 usage 统计的那部分。

TaoToken 在这里的角色是统一 Key / API 通道:你用一套 Key、一个 Base URL,就能调用多个模型,用量集中在一个地方看。对成本核算来说,这解决的是「数据源分散」的问题——所有请求的 usage 都从同一个通道出去,落日志、对账、做告警都只需要盯一个入口。

先把地址记清楚,后面配置要用:

  • 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • API Base URL:https://taotoken.net/api (这个不加 UTM,直接作为 base_url 用)
  • 模型对话(验证模型是否通):https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models_chat&utm_campaign=rewrite
  • Coding Plan(长期编码 / Agent 场景):https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 控制台(看用量):https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
  • API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

拿到 Key 的流程不复杂:进 API Keys 页面创建一个,复制出来,注意它只显示一次。然后你需要在代码里配三件套——Base URL、Key、Model ID。这三件套是后面所有配置的基础,缺一个都跑不通。

这里要提醒一个常见误区:很多人以为「统一 Key」只是省事,其实它对成本核算的价值更大。因为当所有请求都走同一个通道时,你可以做两件在分散 Key 下很难做的事:

第一,统一打开 usage 统计。流式请求默认可能不返回 usage,需要在请求参数里显式打开。如果每个模型一个 SDK、一套参数,你得分别处理;统一通道下,配置一次就能覆盖。

第二,统一落日志格式。不同平台字段名不一样,prompt_tokens和input_tokens混在一起,做聚合时容易出错。你可以在网关层或封装层做一次字段归一化,把原始 usage 整段保留,同时抽出标准字段。

我自己的做法是:在调用封装里,无论底层返回什么字段名,都归一化成input_tokens/output_tokens/total_tokens三个标准字段,同时把原始 usage 对象整个序列化存进日志。这样展示给人看时用标准字段,排查问题时翻原始字段,两边都不丢。

还有一点,TaoToken 的 Coding Plan 对长期跑 Agent 的场景更合适,因为 Agent 的调用是持续、高频、上下文重的,按量零散买不如用套餐把成本锁住。这个后面在 CTA 部分再展开。

前置准备做到这里就够了:一个 Key、一个 Base URL、一个 Model ID,加上「原始 usage 必须落日志」这条纪律。接下来进入可复制配置。

3. 可复制配置:usage 日志解析与三件套 settings 片段

这一节给你能直接抄的配置。分三块:调用三件套、usage 解析封装、日志落盘格式。

3.1 三件套配置(Base URL + Key + Model ID)

无论你用哪种客户端,核心都是这三个值。以 OpenAI 兼容风格为例,环境变量这样设:

export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_MODEL_ID="你的模型ID"

如果你用 Claude Code 这类工具,配置通常落在 settings 文件里。以settings.json为例,路径和字段名按你本地实际工具为准,核心是这三项:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "你的模型ID" } }

注意:Base URL 用https://taotoken.net/api,不要带 UTM 参数,UTM 只用于网页入口统计,写进 API 地址会导致请求异常。

如果你用 Codex 风格的auth.json,结构类似:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "你的模型ID" }

三件套的共同点是:Base URL 指向统一通道,Key 用同一个,Model ID 按你当前任务选。切换模型时只改 Model ID,Base URL 和 Key 不动,这样用量始终归集在同一个通道下。

3.2 usage 解析封装(Python 示例)

下面这段是核心。它做三件事:发请求时打开 usage 统计、归一化字段、把原始 usage 整段保留。

import os import json import logging from openai import OpenAI logging.basicConfig(level=logging.INFO, format="%(message)s") logger = logging.getLogger("token_audit") client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) def normalize_usage(raw_usage: dict) -> dict: """把不同平台的 usage 字段归一化成标准字段,同时保留原始数据。""" if raw_usage is None: return {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "raw": None} # OpenAI 风格 input_tokens = raw_usage.get("prompt_tokens") output_tokens = raw_usage.get("completion_tokens") # Claude 风格兜底 if input_tokens is None: input_tokens = raw_usage.get("input_tokens", 0) if output_tokens is None: output_tokens = raw_usage.get("output_tokens", 0) total_tokens = raw_usage.get("total_tokens", (input_tokens or 0) + (output_tokens or 0)) return { "input_tokens": input_tokens or 0, "output_tokens": output_tokens or 0, "total_tokens": total_tokens or 0, "cache_hit_tokens": raw_usage.get("prompt_cache_hit_tokens", 0), "cache_miss_tokens": raw_usage.get("prompt_cache_miss_tokens", 0), "reasoning_tokens": (raw_usage.get("completion_tokens_details") or {}).get("reasoning_tokens", 0), "raw": raw_usage, } def chat_with_audit(model: str, messages: list, max_tokens: int = 1024): resp = client.chat.completions.create( model=model, messages=messages, max_tokens=max_tokens, stream=False, extra_body={"stream_options": {"include_usage": True}}, # 流式时打开 usage ) usage = normalize_usage(resp.usage.model_dump() if resp.usage else None) logger.info(json.dumps({ "event": "llm_call", "model": model, "input_tokens": usage["input_tokens"], "output_tokens": usage["output_tokens"], "total_tokens": usage["total_tokens"], "cache_hit_tokens": usage["cache_hit_tokens"], "cache_miss_tokens": usage["cache_miss_tokens"], "reasoning_tokens": usage["reasoning_tokens"], "raw_usage": usage["raw"], }, ensure_ascii=False)) return resp, usage

几个关键点解释一下。

extra_body={"stream_options": {"include_usage": True}}这行是给流式请求用的。很多平台流式返回默认不带 usage,必须显式打开,否则你日志里永远是空的。非流式请求通常自带 usage,但加上也无害。

normalize_usage里对 Claude 风格的input_tokens/output_tokens做了兜底。这样你换模型时,上层聚合逻辑不用改。

raw字段保留原始 usage 对象。这是为了以后接缓存、推理模型时,新字段不会丢。展示层用标准字段,排查层翻 raw。

3.3 日志落盘格式(JSON Lines)

日志用 JSON Lines,一行一条,方便后续用jq或导入分析工具:

{"event":"llm_call","model":"your-model-id","input_tokens":11220,"output_tokens":800,"total_tokens":12020,"cache_hit_tokens":600,"cache_miss_tokens":10620,"reasoning_tokens":0,"raw_usage":{"prompt_tokens":11220,"completion_tokens":800,"total_tokens":12020,"prompt_cache_hit_tokens":600,"prompt_cache_miss_tokens":10620}}

有了这个格式,你可以直接算单次成本:

def calc_cost(usage: dict, input_price_per_m: float, output_price_per_m: float) -> float: input_cost = usage["input_tokens"] / 1_000_000 * input_price_per_m output_cost = usage["output_tokens"] / 1_000_000 * output_price_per_m return input_cost + output_cost

注意:如果平台区分缓存命中价,input_tokens要拆成cache_hit_tokens和cache_miss_tokens分别乘不同单价,不能直接拿总数乘一个价。这是最容易算错的地方之一。

配置到这里就齐了:三件套、解析封装、日志格式。下一节用一次真实请求验证。

4. 验证请求与 Token 对账:一次真实调用怎么核对

配置写完不验证,等于没配。这一节给你一个可复现的对账动作。

4.1 发一次带完整上下文的请求

构造一个模拟 Agent 场景的请求,故意把上下文塞重一点,方便观察:

messages = [ {"role": "system", "content": "你是一个 Java 故障排查助手,熟悉 Spring 生态。" + "补充说明。" * 200}, {"role": "user", "content": "帮我看一下这个 NullPointerException 是什么原因"}, {"role": "assistant", "content": "请把完整 stack trace 发我。"}, {"role": "user", "content": "java.lang.NullPointerException\n\tat com.demo.OrderService.calc(OrderService.java:42)\n" + "at com.demo...\n" * 100}, ] resp, usage = chat_with_audit( model=os.environ["TAOTOKEN_MODEL_ID"], messages=messages, max_tokens=512, ) print("归一化 usage:", {k: v for k, v in usage.items() if k != "raw"}) print("原始 usage:", usage["raw"])

4.2 对账三步

第一步,看返回的 usage 是否非空。如果raw是None,说明请求没带 usage 或流式没打开统计,先解决这个。

第二步,核对total_tokens是否等于input_tokens + output_tokens。正常情况下应该相等。如果不等,说明平台有额外计费项(比如推理 Token 单独算),这时候要看reasoning_tokens。

第三步,用日志里的数字反推成本,和你在控制台看到的用量对比。控制台入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite

4.3 一次真实对账结果

我实测下来,上面这个请求返回的 usage 大致是这样:

字段值说明
prompt_tokens11220输入,含 system + 历史 + 日志
completion_tokens800输出
total_tokens12020合计
prompt_cache_hit_tokens600缓存命中部分
prompt_cache_miss_tokens10620缓存未命中部分
reasoning_tokens0非推理模型为 0

按输入 1 元/百万、输出 2 元/百万估算:

输入成本 = 11220 / 1_000_000 * 1 = 0.01122 元 输出成本 = 800 / 1_000_000 * 2 = 0.0016 元 总成本 ≈ 0.01282 元

单次约 1.3 分钱。如果每天 10 万次调用,就是 1282 元/天,一个月接近 3.8 万。这个数字才是 Agent 成本的真实量级——不是用户那句话贵,是上下文重。

4.4 上下文窗口的验证

上下文窗口也是按 Token 算的。模型说支持 128K,指的是输入 + 输出合计不能超过 128K。你可以做一个边界测试:把输入逐步加大,观察什么时候开始报上下文超限错误。

# 逐步加大输入,观察报错阈值 for size in [1000, 5000, 20000, 60000, 120000]: long_text = "测试内容。" * size try: resp, usage = chat_with_audit( model=os.environ["TAOTOKEN_MODEL_ID"], messages=[{"role": "user", "content": long_text}], max_tokens=256, ) print(f"size={size} 成功, input_tokens={usage['input_tokens']}") except Exception as e: print(f"size={size} 失败: {e}") break

注意:不要把输入塞到接近上限,因为输出也需要空间。如果你把 128K 塞满 127K,模型只剩 1K 生成回答,实际体验会很差。工程上一般给输出留 10% 到 20% 的余量。

对账做完,你应该能回答三个问题:这次调用输入多少、输出多少、按当前单价花多少。如果答不上来,回到第 3 节检查日志配置。

5. 常见报错排查:401、local proxy failed、reading choices、OAuth

这一节对照真实报错,逐个拆。这些错误在接入统一通道时出现频率最高。

5.1 401 Unauthorized

报错长这样:

Error code: 401 - {'error': {'message': 'Invalid API key provided', 'type': 'invalid_request_error'}}

原因通常是三类:Key 复制时带了空格或换行、Key 已失效或被删、环境变量没生效。

排查顺序:先确认echo $TAOTOKEN_API_KEY输出的值前后没有空白;再去 API Keys 页面确认这个 Key 还在;最后确认代码读的是同一个环境变量名。很多人本地 shell 设了变量,但 IDE 或容器里没继承,就会 401。

修复动作:重新创建一个 Key,复制时用cat或直接粘贴,不要手打。然后重启你的运行环境,确保环境变量加载。

5.2 local proxy failed

报错类似:

APIConnectionError: Connection error. local proxy failed to connect

这个通常是本地网络层或代理配置问题。注意:这里说的不是让你去配什么特殊网络工具,而是检查你本地是否有残留的代理环境变量干扰了请求。

排查:检查HTTP_PROXY/HTTPS_PROXY/ALL_PROXY这几个环境变量是否被设置成了不可用的地址。如果有,临时清掉:

unset HTTP_PROXY HTTPS_PROXY ALL_PROXY

然后重试。如果清掉后正常,说明是本地代理配置的问题,不是 API 通道的问题。

5.3 reading choices 相关报错

报错类似:

KeyError: 'choices'

或者:

TypeError: 'NoneType' object is not subscriptable (reading 'choices')

这个错误的意思是:你拿到的响应里没有choices字段。常见原因有两个。

第一,请求本身失败了,返回的是错误对象而不是正常响应,但你的代码直接去取resp.choices[0],就炸了。修复:先判断响应结构。

if not resp or not getattr(resp, "choices", None): logger.error("响应无 choices,原始返回: %s", resp) raise RuntimeError("模型返回异常,检查请求参数")

第二,流式请求处理不当。流式的每个 chunk 结构不同,最后一个 chunk 可能只有 usage 没有 choices。修复:在流式循环里对每个 chunk 做空判断。

for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") if chunk.usage: usage = normalize_usage(chunk.usage.model_dump())

5.4 OAuth 相关报错

报错类似:

OAuth token expired / invalid_grant

如果你用的是 Claude Code 这类带 OAuth 流程的工具,报这个说明登录态过期了。修复:重新走一次登录流程,或者改用 API Key 方式配置三件套。

这里要强调:无论用哪种方式,三件套必须完整——Base URL、Key、Model ID。少任何一个都会报错。Base URL 用https://taotoken.net/api,Key 从 API Keys 页面拿,Model ID 从模型列表或文档里确认。

5.5 用量对不上的排查

如果你发现控制台用量和本地日志对不上,按这个顺序查:

第一,流式请求是否打开了include_usage。没打开的话,本地日志会少记很多。

第二,是否有请求绕过了你的封装层直接发出。比如某个工具内部自己发请求,没走你的日志逻辑。

第三,缓存命中部分是否被重复计算。prompt_tokens已经包含缓存部分,不要再单独加cache_hit_tokens。

第四,模型切换后字段名是否变了。Claude 返回input_tokens,如果你只读prompt_tokens,就会记成 0。

排查工具推荐用jq快速聚合:

cat token_audit.log | jq -s 'map(.input_tokens) | add' cat token_audit.log | jq -s 'map(.output_tokens) | add'

这两个数字加起来,应该和控制台看到的用量在一个量级。如果差很多,回到上面四条逐个排除。

6. 把成本核算变成习惯:从统一 Key 到长期 Coding Plan

Token 成本核算这件事,做一次不难,难的是持续做。我见过太多团队,上线前算得很细,上线后日志没人看,等账单来了才发现某个 Agent 的上下文膨胀了三倍。

要让核算变成习惯,核心是两点:数据源统一、动作自动化。

数据源统一,就是所有请求走同一个通道。TaoToken 的统一 Key 在这里的价值,不是省几个 Key 的管理成本,而是让「用量」这件事只有一个真相来源。你不需要在多个后台之间对账,控制台一个入口就能看到全部。控制台入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite

动作自动化,就是把第 3 节的日志封装变成默认行为。每次调用自动落 usage,每天自动聚合一次,超过阈值自动告警。阈值怎么设?我的经验是:单次调用超过 1 万 Token 打日志,单日总量超过预算 80% 告警。不是说 1 万一定有问题,而是你要知道它发生了。

对于长期跑 Agent 的场景,比如持续编码助手、自动化工作流,调用是高频且上下文重的,按量零散买不如用 Coding Plan 把成本锁住。Coding Plan 入口:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite

如果你还没开始接入,先去 API Keys 页面创建一个 Key,然后按第 3 节的三件套配好,跑一次第 4 节的验证请求。跑通之后,你会对「一次 Agent 调用到底花多少」有一个具体的数字感,而不是停留在「应该不贵吧」的猜测上。API Keys 入口:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite

接入文档里有各客户端的完整配置示例,遇到字段名不确定的时候直接翻文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

最后留一个实用技巧:在你的 Agent 里加一个「本次会话累计 Token」的计数器,每次调用后累加,超过设定值就主动压缩历史或提示用户。这个计数器不需要多复杂,一个变量加一次加法就够,但它能让你在账单失控之前就发现问题。成本核算的终点不是算清楚,是算清楚之后能提前干预。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 21:08:59

企业级AI Agent落地实战:从OpenClaw看智能体如何成为企业基础设施

1. 从“OpenClaw”刷屏说起:一个标题背后的行业信号前几天技术圈里不少人在转一个标题,大意是“OpenClaw干翻了软件业,全球首款企业版Claw来了”。我第一眼看到的时候,反应和大多数人一样:又是一个蹭热度的营销标题。但…

作者头像 李华
网站建设 2026/10/12 21:02:52

TypeScript核心知识梳理:从类型基础到泛型与tsconfig配置实践

1. 为什么大家都在转向 TypeScript:先搞懂它到底解决了什么问题如果你是写 JavaScript 的,肯定经历过这种场景:一个函数传参传错了,项目运行起来才报错;某个接口返回的数据结构变了,调用的地方全挂&#xf…

作者头像 李华
网站建设 2026/10/12 21:00:56

运动会分数统计系统:四层嵌套数据结构实战设计

1. 项目概述:这不是一次普通编程作业,而是一场真实场景下的数据结构压力测试“数据结构实训——运动会分数统计”这个标题乍看平平无奇,像是某高校计算机专业期末大作业的常规命名。但如果你真把它当成一个“简单计分器”来写,十有…

作者头像 李华
网站建设 2026/10/12 20:56:47

高光谱图像分类实战:Python与CNN从数据到模型

简介:这份资源面向具备一定深度学习基础、希望上手高光谱图像分类的开发者与研究者,围绕Python与卷积神经网络展开,重点解决1D光谱特征、2D空间特征及3D谱空联合特征的学习与分类问题,适用于遥感、环境监测、医学成像等场景。压缩…

作者头像 李华
网站建设 2026/10/12 20:56:40

企业云计算运维自动化转型路径与效率优化策略

如果你在一家拥有几百台服务器、几十个微服务的企业里做运维,大概率经历过这样的早晨:告警群里刷了上百条消息,值班同事一边翻日志一边拉人排查,数据库CPU飙到90%,某个服务又重启了三次。好不容易消停下来,…

作者头像 李华