1. 一次对话到底花了多少钱,为什么你算不明白
很多人第一次用 DeepSeek Harness 跑通对话后,盯着控制台那行回复就以为完事了。等到月底看账单,发现费用比预期高出一截,却完全说不清钱花在哪。问题不在模型,而在于你从没认真看过返回结果里的usage字段。
usage是每次 API 调用后服务端回传的用量凭证,它告诉你这次请求消耗了多少输入 Token、多少输出 Token、有多少命中了缓存。DeepSeek Harness 作为第三方协议适配层,会把这部分信息整理成结构化对象交给你。但小白常见的做法是只打印message.content,把usage直接丢掉,等于每次调用都在“盲付”。
这篇面向刚上手 Harness 的读者,用一个真实对话演示:从返回结果里提取prompt_tokens、completion_tokens和缓存命中信息,算清单次成本,最后落成一张可聚合的 JSONL 账单。全程用 TaoToken 统一 Key 接入,方便你在一个控制台里核对用量、验证缓存是否真的生效。适合谁:已经能跑通 Harness 最小对话、但还没建立成本观测习惯的开发者。
2. 用 TaoToken 统一 Key 接入,先把用量口径对齐
在算钱之前,得先保证你看到的usage是可信的。如果你同时用多个 Key、多个端点,账单口径就会打架。TaoToken 的做法是给你一个统一 Key,模型对话、Coding Plan、API 调用都走同一个入口,用量在控制台里集中呈现,核对起来不用来回切换。
接入动作很简单:打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后进入控制台,在 API Keys 页面生成一个 Key。这个 Key 就是你后面所有请求的凭证。注意,Key 只在生成时完整显示一次,复制后立刻存进环境变量,别写进代码。
拿到 Key 后,把 Harness 的 Base URL 指向 TaoToken 的 API 地址 https://taotoken.net/api。这样你的请求会经过统一网关,返回的usage字段和 TaoToken 控制台里的用量统计是同一套口径。后面算出来的成本,才能和控制台对得上。
注意:TaoToken 是统一接入与用量管理入口,不是让你绕过任何合规流程。Key 的权限范围、余额、调用记录都在控制台可查,出问题先看那里。
如果你还没生成 Key,直接去 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各语言的 Base URL 配置示例。
3. 可复制的 usage 解析脚本与账单模板
3.1 环境准备
先建一个干净的测试目录,装好依赖。Python 用 3.10 以上,Harness 用你当前核验过的版本。Key 通过环境变量注入,别硬编码。
mkdir -p ~/harness-billing && cd ~/harness-billing python3 -m venv .venv && source .venv/bin/activate pip install deepseek-harness export TAOTOKEN_API_KEY="你的Key"3.2 最小调用与 usage 提取
下面这段脚本做三件事:发一次短对话、把usage完整打印出来、把不含正文和密钥的账单追加进 JSONL 文件。你可以直接复制运行。
import os import json import time from deepseek_harness import DeepSeekHarness client = DeepSeekHarness( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api", disable_thinking_by_default=True, ) out = client.chat( model="deepseek-v4-flash", messages=[{"role": "user", "content": "用不超过 80 字解释什么是缓存命中"}], max_tokens=256, extra_body={"thinking": {"type": "disabled"}}, ) usage = out.get("usage") or {} message = out.get("message") or {} record = { "ts": int(time.time()), "model": out.get("model"), "finish_reason": out.get("finish_reason"), "prompt_tokens": usage.get("prompt_tokens"), "completion_tokens": usage.get("completion_tokens"), "total_tokens": usage.get("total_tokens"), "prompt_cache_hit_tokens": usage.get("prompt_cache_hit_tokens"), "prompt_cache_miss_tokens": usage.get("prompt_cache_miss_tokens"), "estimated_cost_usd": usage.get("estimated_cost_usd"), } print("回复:", message.get("content")) print("用量:", json.dumps(record, ensure_ascii=False)) with open("billing.jsonl", "a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n")跑完后你会看到类似这样的输出:
回复: 缓存命中指请求的内容与之前已缓存的前缀一致,服务端直接复用,无需重新计算,从而降低延迟和费用。 用量: {"ts": 1755..., "model": "deepseek-v4-flash", "finish_reason": "stop", "prompt_tokens": 42, "completion_tokens": 38, "total_tokens": 80, "prompt_cache_hit_tokens": 0, "prompt_cache_miss_tokens": 42, "estimated_cost_usd": 0.000021}3.3 字段含义对照表
| 字段 | 含义 | 小白怎么理解 |
|---|---|---|
| prompt_tokens | 输入 Token 总数 | 你发出去的内容占多少 |
| completion_tokens | 输出 Token 总数 | 模型回给你的内容占多少 |
| total_tokens | 总 Token | 上面两项相加 |
| prompt_cache_hit_tokens | 命中缓存的输入 Token | 这部分通常更便宜 |
| prompt_cache_miss_tokens | 未命中缓存的输入 Token | 这部分按正常价算 |
| estimated_cost_usd | 估算费用 | 单次成本,用于聚合 |
3.4 账单表格模板
JSONL 适合程序聚合,但人看还是表格直观。你可以用下面这个模板把多次调用汇总成一张账单。把billing.jsonl里的记录读出来,按模型和日期分组即可。
import json from collections import defaultdict agg = defaultdict(lambda: {"calls": 0, "prompt": 0, "completion": 0, "hit": 0, "cost": 0.0}) with open("billing.jsonl", encoding="utf-8") as f: for line in f: r = json.loads(line) key = r["model"] agg[key]["calls"] += 1 agg[key]["prompt"] += r["prompt_tokens"] or 0 agg[key]["completion"] += r["completion_tokens"] or 0 agg[key]["hit"] += r["prompt_cache_hit_tokens"] or 0 agg[key]["cost"] += r["estimated_cost_usd"] or 0.0 print(f"{'模型':<20}{'调用':<6}{'输入':<8}{'输出':<8}{'缓存命中':<10}{'费用USD':<10}") for model, v in agg.items(): print(f"{model:<20}{v['calls']:<6}{v['prompt']:<8}{v['completion']:<8}{v['hit']:<10}{v['cost']:<10.6f}")这张表就是你自己的成本账单。每次实验后跑一遍,费用变化一目了然。
4. 验证请求:缓存命中到底有没有生效
4.1 设计一个能触发缓存的实验
缓存命中的前提是请求前缀稳定。DeepSeek 的缓存机制对相同前缀的输入会复用计算结果。所以你要做的是:第一次发一个带长系统提示的请求,第二次发同样的系统提示、只改用户问题,观察prompt_cache_hit_tokens是否从 0 变成正数。
system_prompt = "你是一个严谨的技术助手,回答必须简洁,不超过 100 字。" * 20 def ask(question): out = client.chat( model="deepseek-v4-flash", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": question}, ], max_tokens=128, extra_body={"thinking": {"type": "disabled"}}, ) u = out.get("usage") or {} return { "q": question, "hit": u.get("prompt_cache_hit_tokens"), "miss": u.get("prompt_cache_miss_tokens"), "cost": u.get("estimated_cost_usd"), } print(ask("什么是 Token?")) print(ask("什么是缓存?"))4.2 成功结果长什么样
第一次调用,hit应该是 0,miss等于prompt_tokens。第二次调用,因为系统提示前缀完全一致,hit应该变成正数,miss明显下降,estimated_cost_usd也随之降低。如果你看到第二次的hit大于 0,说明缓存生效了。
实测下来,稳定前缀越长,第二次的命中比例越高,单次成本下降越明显。这也是为什么工程上建议把系统提示、工具 Schema 这些不变内容放在前面,把用户问题放在后面。
4.3 用 TaoToken 控制台交叉核对
脚本跑完后,去 TaoToken 控制台看用量记录。找到对应时间段的调用,核对prompt_tokens和completion_tokens是否和你的 JSONL 账单一致。如果一致,说明你的解析逻辑没问题;如果不一致,先检查是不是有别的请求混进来了。控制台入口在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
5. 本篇常见错排查
5.1 usage 是空的
最常见的原因是中间层把usage字段丢了。有些封装只返回message,不返回完整响应对象。解决方法是确认你用的 Harness 版本会透传usage,并且你没有在代码里手动裁剪返回结果。另外,流式模式下usage可能只在最后一个 chunk 出现,需要单独收集。
5.2 缓存命中一直是 0
先检查前缀是否真的稳定。系统提示里如果混入了时间戳、随机 ID、动态拼接的用户名,前缀每次都变,缓存自然不命中。把动态内容移到用户消息里,系统提示保持纯静态。其次确认模型和端点一致,换模型或换 Base URL 都会导致缓存失效。
5.3 费用对不上
estimated_cost_usd是估算值,实际计费以控制台为准。如果你发现脚本算的和控制台差很多,先确认是不是有并发请求没写进 JSONL,或者 Key 被别的地方用了。另外,缓存命中的 Token 单价和未命中不同,如果你的估算公式没区分这两部分,结果会偏高。
5.4 401 或 403
Key 没注入成功,或者环境变量名写错了。检查TAOTOKEN_API_KEY是否在当前终端可见,别把 Key 写进代码后提交到 Git。如果确认 Key 没问题,去控制台看余额和权限范围。
5.5 finish_reason 是 length
输出被max_tokens截断了。这时候completion_tokens等于你设的上限,但内容不完整。算成本时要注意,截断的请求照样计费。要么提高上限,要么把任务拆小。
6. 把成本观测变成习惯
到这里你已经有了三样东西:一个能提取usage的脚本、一张能聚合的账单表、一套验证缓存命中的方法。接下来要做的不是继续加功能,而是把这三样固定成每次实验的收尾动作。
我的建议是:每次跑完 Harness 实验,先看finish_reason是不是stop,再看usage有没有写进 JSONL,最后跑一遍聚合脚本看当天总费用。如果缓存命中率低于预期,回头检查前缀是否稳定。这套动作花不了两分钟,但能让你在费用失控之前就发现问题。
如果你还没生成统一 Key,现在去 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 建一个,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。想先验证模型返回的usage结构,可以直接在模型对话页试一次:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果你打算长期跑编码类 Agent,Coding Plan 页面有更集中的用量视图:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
下一篇会接着讲怎么用 pytest 锁住消息、缓存和流式这三条底线,让成本观测从手动变成自动。