news 2026/10/8 6:21:30

手搓 CodingPlan 照妖镜:用 Next.js 把 TOKEN 燃烧器接上 TaoToken

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手搓 CodingPlan 照妖镜:用 Next.js 把 TOKEN 燃烧器接上 TaoToken

1. 为什么你的 CodingPlan 总在偷偷烧 TOKEN

如果你同时订了两三个 CodingPlan 套餐,大概率遇到过这种场景:月初信心满满,觉得额度够用,结果不到两周就提示余额不足。你打开各家控制台,看到的只有一个冷冰冰的剩余百分比,至于这些 TOKEN 到底被谁吃掉了、是哪个模型、哪次请求、输入多还是输出多,一概不知。

这就是 CodingPlan 场景下最典型的痛点——TOKEN 消耗不可见。你花钱买的是「额度」,但额度被消耗的过程是个黑盒。批量测试跑一轮、Agent 自动补全跑一天、群聊接力聊嗨了,TOKEN 就像开了闸的水龙头,你只能事后看着账单心疼。

我试过最笨的办法:每调一次接口就手动记一笔。结果当然是坚持不了三天。后来想明白了,这事必须交给代码——在请求出口做一层拦截,把每次调用的 usage 字段落盘,再用一个本地看板把数据可视化出来。这就是今天要手搓的「CodingPlan 照妖镜」:一个基于 Next.js 的本地 TOKEN 用量看板。

它能做什么?简单说三件事。第一,统一走一个 API 通道,不管你后面接的是哪家模型,请求都从同一个出口出去;第二,每次请求结束后,把 prompt_tokens、completion_tokens、total_tokens 连同模型名、时间戳一起写进本地 JSON;第三,提供一个仪表盘页面,按天、按模型、按平台聚合展示,让你一眼看出谁在烧钱。

适合谁?适合手上握着多个 CodingPlan、想搞清楚额度去向的开发者;适合在跑批量评测、Agent 任务、需要核算成本的团队;也适合刚接触 Next.js 全栈、想拿一个真实小项目练手的朋友。整篇教程从零开始,命令可复制,配置可照抄,跑完你就能看到自己第一笔真实的 TOKEN 账单。

需要说明的是,本文的看板只做「记录与展示」,不碰任何账号密码,也不做额度代充之类的操作。所有请求都通过你自己配置的 API 通道发出,数据全部落在你本地的 JSON 文件里,安全边界清晰。

2. 用 TaoToken 做统一出口,先把 Key 和通道理清楚

要让看板能统计到 TOKEN,前提是所有模型调用都走同一个出口。如果每个平台各调各的,你就得在每个 SDK 里都埋一遍统计逻辑,维护成本极高。所以第一步,我们用一个统一的 API 通道把请求收口。

这里用 TaoToken 作为统一出口。它的价值在于:你只需要维护一套 Base URL 和一把 Key,就能在同一个通道里切换不同模型,而每次响应里都会带回标准的 usage 字段,正好喂给我们的统计逻辑。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加任何查询参数。

先注册并登录,然后进控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建好之后把 Key 复制出来,形如sk-xxxxxxxx,后面写进.env.local。

如果你只是想先验证模型通不通,可以打开模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 手动发一条消息,看看返回是否正常。这一步能帮你排除掉「Key 本身有问题」这类低级错误,省得后面在代码里瞎找。

对于长期跑编码任务、Agent 自动化的场景,建议直接上 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。它的定位就是给高频调用准备的,配合我们的看板,你能清楚看到每个任务的 TOKEN 消耗曲线,判断套餐是否划算。

接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面写明了 OpenAI 兼容协议和 Anthropic 协议的调用方式。我们的看板默认走 OpenAI 兼容协议,因为它的响应结构里 usage 字段最规整,解析起来最省事。

这里要强调一个关键点:统计的准确性取决于响应里有没有 usage。有些流式响应默认不返回 usage,需要显式开启。TaoToken 的 OpenAI 兼容接口在请求体里带上stream_options: { include_usage: true }就能在流式结束时拿到 usage。这个参数后面在代码里会体现,先记住。

配置阶段还有个小坑:环境变量名不要用NEXT_PUBLIC_前缀。因为我们的 Key 只在服务端路由里使用,加了NEXT_PUBLIC_反而会把它暴露到浏览器端,既不安全也没必要。用普通的TAOTOKEN_API_KEY就行。

3. 可复制的 Next.js 路由配置与环境变量写法

这一节是全文的核心,所有代码都可以直接复制。我们创建一个 Next.js 项目,用 App Router 结构,写一个 API 路由作为统一出口,请求发出后把 usage 落盘。

先初始化项目。打开终端,执行:

npx create-next-app@latest codingplan-burner --typescript --app --no-tailwind --no-eslint --src-dir --import-alias "@/*" cd codingplan-burner npm install

创建完成后,在项目根目录新建.env.local,写入:

TAOTOKEN_API_KEY=sk-你的Key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL=claude-sonnet-4-5

注意TAOTOKEN_BASE_URL结尾不要带斜杠,代码里会自己拼/v1/chat/completions。模型 ID 按你实际要用的填,这里只是示例。

接着建数据目录和统计文件。在项目根目录执行:

mkdir -p data echo "[]" > data/usage.json

然后写核心路由。新建文件src/app/api/chat/route.ts:

import { NextRequest, NextResponse } from "next/server"; import fs from "fs"; import path from "path"; const USAGE_FILE = path.join(process.cwd(), "data", "usage.json"); type UsageRecord = { ts: string; model: string; prompt_tokens: number; completion_tokens: number; total_tokens: number; latency_ms: number; }; function appendUsage(record: UsageRecord) { const raw = fs.readFileSync(USAGE_FILE, "utf-8"); const list: UsageRecord[] = JSON.parse(raw || "[]"); list.push(record); fs.writeFileSync(USAGE_FILE, JSON.stringify(list, null, 2), "utf-8"); } export async function POST(req: NextRequest) { const { messages, model } = await req.json(); const useModel = model || process.env.TAOTOKEN_MODEL!; const started = Date.now(); const resp = await fetch( `${process.env.TAOTOKEN_BASE_URL}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.TAOTOKEN_API_KEY}`, }, body: JSON.stringify({ model: useModel, messages, stream: false, }), } ); if (!resp.ok) { const errText = await resp.text(); return NextResponse.json( { error: errText, status: resp.status }, { status: resp.status } ); } const data = await resp.json(); const usage = data.usage || {}; appendUsage({ ts: new Date().toISOString(), model: useModel, prompt_tokens: usage.prompt_tokens ?? 0, completion_tokens: usage.completion_tokens ?? 0, total_tokens: usage.total_tokens ?? 0, latency_ms: Date.now() - started, }); return NextResponse.json({ content: data.choices?.[0]?.message?.content ?? "", usage, }); }

这段代码做了四件事:接收前端传来的 messages 和 model;用环境变量里的 Key 和 Base URL 向 TaoToken 发请求;从响应里取出 usage;把 usage 追加写入data/usage.json。注意这里用的是同步文件读写,本地单机看板够用,如果你要并发压测,建议换成追加写或 SQLite。

再写一个查询路由,给仪表盘用。新建src/app/api/usage/route.ts:

import { NextResponse } from "next/server"; import fs from "fs"; import path from "path"; const USAGE_FILE = path.join(process.cwd(), "data", "usage.json"); export async function GET() { const raw = fs.readFileSync(USAGE_FILE, "utf-8"); const list = JSON.parse(raw || "[]"); const byModel: Record<string, number> = {}; let total = 0; for (const item of list) { byModel[item.model] = (byModel[item.model] || 0) + item.total_tokens; total += item.total_tokens; } return NextResponse.json({ total, byModel, records: list }); }

如果你更习惯用 TOML 或 JSON 配置文件管理模型清单,可以在根目录建config/models.json:

{ "models": [ { "id": "claude-sonnet-4-5", "label": "Sonnet 4.5" }, { "id": "claude-opus-4-6", "label": "Opus 4.6" }, { "id": "glm-4-plus", "label": "GLM-4-Plus" } ] }

然后在路由里读取这个文件做模型下拉。这样切换模型不用改代码,改配置就行。

最后写一个最简仪表盘页面src/app/page.tsx,用 fetch 拉/api/usage展示总量和分模型统计。页面代码不复杂,核心就是useEffect里请求一次,把total和byModel渲染成表格。到这一步,配置部分就齐了。

4. 启动项目并验证一次真实请求的 TOKEN 账单

配置写完,启动项目:

npm run dev

终端会输出http://localhost:3000。先别急着开页面,我们用 curl 直接打一次 API 路由,验证统计链路是否通。新开一个终端,执行:

curl -X POST http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "user", "content": "用一句话解释什么是 TOKEN"} ] }'

如果一切正常,你会看到类似这样的返回:

{ "content": "TOKEN 是模型处理文本的最小单位,可以粗略理解为字或词的片段。", "usage": { "prompt_tokens": 18, "completion_tokens": 27, "total_tokens": 45 } }

记下这个total_tokens: 45。然后打开data/usage.json,应该能看到刚写入的一条记录:

[ { "ts": "2025-01-15T08:30:12.345Z", "model": "claude-sonnet-4-5", "prompt_tokens": 18, "completion_tokens": 27, "total_tokens": 45, "latency_ms": 1832 } ]

现在打开浏览器访问http://localhost:3000,仪表盘应该显示总消耗 45 TOKEN,分模型统计里 Sonnet 4.5 占 45。到这里,一次完整的「请求—统计—展示」闭环就跑通了。

接下来做核对。回到 TaoToken 控制台的用量页面,找到刚才那次调用的记录,对比它的 total_tokens 是不是 45。如果一致,说明你的燃烧器统计准确;如果有偏差,先检查是不是有别的请求混进来了,或者模型 ID 对不上导致计费口径不同。

为了验证多模型场景,再打两次请求,分别指定不同模型:

curl -X POST http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{"model":"glm-4-plus","messages":[{"role":"user","content":"写一个二分查找"}]}' curl -X POST http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{"model":"claude-opus-4-6","messages":[{"role":"user","content":"解释一下快速排序"}]}'

刷新仪表盘,你会看到三个模型各自的 TOKEN 消耗。这时候「照妖镜」的效果就出来了:同样一个问题,不同模型的输入输出 TOKEN 差异可能很大,谁更费钱一目了然。实测下来,输出型任务里 Opus 的 completion_tokens 通常明显高于轻量模型,这就是账单差异的主要来源。

如果你要跑流式请求,记得在请求体里加stream_options: { include_usage: true },否则流式响应可能不带 usage,统计就会漏记。这是最容易踩的坑之一。

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

跑起来之后,报错基本集中在几个地方。这一节按真实报错逐个拆。

401 Unauthorized。返回体里通常是{"error":{"message":"Invalid API key"}}。原因有三个:Key 没填、Key 填错、或者.env.local改了之后没重启 dev server。Next.js 的环境变量是在启动时加载的,改完必须Ctrl+C停掉再npm run dev。另外检查 Key 有没有多余空格,复制的时候很容易带上换行。

local proxy failed / fetch failed。这个报错说明请求根本没发出去,通常是 Base URL 写错了。检查TAOTOKEN_BASE_URL是不是https://taotoken.net/api,结尾不要带斜杠,也不要在代码里重复拼/api。如果你在代码里写的是${BASE_URL}/v1/chat/completions,那 BASE_URL 就应该是https://taotoken.net/api,拼出来正好是https://taotoken.net/api/v1/chat/completions。多一层少一层都会 404 或连接失败。

Cannot read properties of undefined (reading 'choices')。这个报错说明data.choices是 undefined,也就是响应结构和你预期的不一样。最常见的原因是请求失败但没检查resp.ok,直接把错误响应当成功解析了。解决办法是在resp.json()之前先判断resp.ok,不 ok 就把resp.text()打出来看。另一个原因是模型 ID 写错,接口返回了错误对象而不是正常的 chat completion 结构。

usage 全是 0。请求成功但统计为 0,八成是流式响应没开include_usage,或者你用的模型/协议本身不返回 usage。先确认请求体里stream: false,非流式一般都会带 usage。如果确实要用流式,加上stream_options。

OAuth / 认证相关报错。如果你在 Claude Code 或 Codex 这类工具里配置,报 OAuth 错误通常是因为认证方式选错了。这类工具要的是 Base URL + API Key + Model ID 三件套,不是网页登录的 OAuth 流程。以 Claude Code 为例,配置时把 Base URL 指向https://taotoken.net/api,Key 填sk-开头的 API Key,Model ID 填具体模型名,三者缺一不可。Codex 的auth.json里同理,OPENAI_BASE_URL和OPENAI_API_KEY要对应上,模型 ID 单独在配置里指定。

CC Switch / Cline MCP 配置。如果你用 CC Switch 管理多个通道,或者用 Cline 的 MCP 接模型,同样记住三件套:Base URL、Key、Model ID。MCP 配置里不要直连生产数据库,只做模型调用。CC Switch 里新增通道时,协议选 OpenAI 兼容,地址填https://taotoken.net/api,Key 填你的,模型 ID 按需填。

排查顺序建议固定下来:先看 HTTP 状态码,再看响应体原文,最后看本地data/usage.json有没有写入。三步走完,90% 的问题都能定位。

6. 把看板用起来:从记账到优化你的 CodingPlan

看板跑通只是开始,真正有价值的是用它做决策。这里分享几个我实际用下来的思路。

第一,按任务类型分组统计。你可以在请求体里加一个自定义字段,比如task: "batch-test"或task: "agent-run",然后在路由里把它一起写进 usage 记录。这样你就能看出「批量测试」和「日常补全」各占多少额度。很多人额度不够用,其实是被某个自动化任务悄悄吃掉了,分组之后一目了然。

第二,关注 completion_tokens 而不是 total。输入 TOKEN 你基本控制不了,但输出 TOKEN 可以通过提示词约束。比如在系统提示里加一句「回答控制在 200 字以内」,输出量能降一大截。看板里把 completion_tokens 单独列一栏,你就能评估提示词优化的效果。

第三,给看板加一个按天聚合的视图。现在的byModel只统计了总量,你可以再写一个按ts的日期部分分组的逻辑,画出每天的消耗曲线。如果某天突然飙升,回去翻那天的记录,就能找到是哪次调用异常。

第四,定期导出 JSON 做归档。data/usage.json会一直增长,建议每周导出一次,清空文件重新记。导出后可以用 Excel 或脚本做更复杂的分析,比如算每个模型的「每千 TOKEN 平均延迟」,找出又慢又费钱的组合。

如果你想让看板更实用,可以加一个「预算告警」:在查询路由里判断当日 total 是否超过阈值,超过就在页面上标红。这个逻辑很简单,几行代码的事,但能帮你避免月底才发现额度见底。

最后说下扩展方向。当前看板是单机 JSON 存储,适合个人用。如果你要团队共享,把存储换成 SQLite 或 Postgres,路由逻辑基本不用改。如果你要接更多平台,只要它们兼容 OpenAI 协议,改一下 Base URL 和 Key 就能纳入统计。核心思路始终是:统一出口、拦截 usage、本地落盘、可视化展示。

这套东西不复杂,但解决的是真问题。当你第一次看到自己所有 CodingPlan 的 TOKEN 消耗被摊在一张表上,那种「终于看清了」的感觉,比任何账单提醒都管用。

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

工业互联网不会取代DCS?解析传统工控与数据智能的融合之道

上个月我去一家石化厂做设备数据采集试点&#xff0c;仪表车间主任指着中控室那套和利时DCS&#xff0c;半开玩笑地问&#xff1a;“你们搞工业互联网的&#xff0c;是不是早晚把这玩意儿换掉&#xff1f;”我说&#xff1a;“不敢换&#xff0c;也换不了。”他有点意外&#x…

作者头像 李华
网站建设 2026/10/8 6:21:26

网规案例分析

IPV6采用128位动态分配方式包括有状态分配和无状态分配和无状态分配出口链路配置要点&#xff1a;一是出口防火墙配置NAT&#xff0c;实现内&#xff0c;外网地址转换访问互联网&#xff1b;二是出口防火墙配置安全策略&#xff0c;防止非法访问和网络攻击&#xff1b;三是出口…

作者头像 李华
网站建设 2026/10/8 6:19:39

端侧LLM部署实战:llama.cpp、GGUF与量化选型指南

端侧 Agent 这两年从概念走向落地&#xff0c;最大的拦路虎其实不是 Agent 的编排逻辑&#xff0c;而是"模型到底能不能塞进设备里、跑不跑得动"。我前后在几台不同配置的机器上折腾过端侧 LLM 部署&#xff0c;从最早的 llama.cpp 编译踩坑&#xff0c;到 GGUF 量化…

作者头像 李华
网站建设 2026/10/8 6:19:00

Claude Code 实战:工程实践里的常见坑与 TaoToken 统一接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华