ARTICLE DETAIL

资讯详情

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

模型调用统计里,PR Author 经 TaoToken 的埋点方案

模型调用统计里,PR Author 经 TaoToken 的埋点方案 1. 从 Cosmos 的四环节说起为什么埋点必须放在客户端Cosmos 软件工厂把 PR Author 铺到了需求、工单、PR、生产四个环节最先暴露给工程效能团队的问题不是它能不能跑而是这四个环节的 Token 到底花在哪。后台的用量报表通常只按 Key、按模型、按天聚合你看到的是一条平滑的曲线但这条曲线回答不了任何决策问题需求拆分阶段是不是在反复重试工单补全是不是把整仓库的上下文都塞进了 promptPR 审查意见是不是被同一个文件反复触发生产告警摘要是不是成了长尾里最贵的一块要把这些问题变成可回答的问题只有一个办法在客户端侧把每一次出站请求打上环节标签再把响应的 usage 与标签一起落到本地。这一步做完才有资格谈优化。我这次的做法是先统一出口——把所有客户端的 Base URL 收到 TaoToken 后面官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_stage_trace Base URL 固定为https://taotoken.net/api然后在调用链路里做环节级埋点。整条链路上有三个客户端需要改造Claude Code、Codex CLI、以及团队自研的调度脚本。它们各自读不同的配置项混用配置项是最常见的翻车点后文会逐个给到可复制片段。这篇内容面向的是和我一样维护工具链的人不追热点结论只关心配完能跑、跑完能统计、统计完能定位。下面是完整的落地路径。2. 统一出站把 Base URL 收到一个域名后面多客户端并存时最先失效的是统计口径。Claude Code 走一套配置、Codex 走另一套、自研脚本又直接硬编码了一个地址那么同一个需求环节消耗了多少这件事就永远对不齐。所以第一步不是写埋点而是收敛出口。创建一个统一的环境文件所有工具都从这里取变量# ~/.taotoken.env export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYYOUR_API_KEY # 环节标签供埋点脚本读取枚举见第 5 节 export PR_AUTHOR_STAGErequirement在~/.bashrc或~/.zshrc里加载[ -f $HOME/.taotoken.env ] source $HOME/.taotoken.envKey 的创建入口在控制台不要在代码里写死也不要把 Key 提交进仓库控制台创建 Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_env_key几个容易踩的细节先讲清楚再看配置TAOTOKEN_BASE_URL只填到https://taotoken.net/api不要在客户端配置里再拼/v1也不要在末尾加斜杠。多数客户端的 SDK 会自己补路径重复拼路径会直接拿到 404。Key 通过环境变量注入CI 环境用 Secret 管理器下发本地用chmod 600 ~/.taotoken.env收一下权限。环节标签是给埋点用的不是给模型用的。不要把PR_AUTHOR_STAGE拼进 prompt那只会白白增加输入 Token。出口统一之后你会得到两个额外好处一是换成任意一个模型或供应商只需要改一处环境变量二是所有请求都会带着同一套鉴权与配额口径报表不会出现两个后台各自算一半的情况。3. CC Switch 三件套Claude Code、Codex、自研 CLI 的可复制配置CC Switch 这类切换器解决的是多供应商、多客户端的管理问题但它本身不产生配置语义。三件套必须分别对待最忌讳的做法是把ANTHROPIC_*那套变量套到 Codex 上——Codex 根本不读那两个变量你会得到一个看起来改了配置、实际还在走默认端点的假象。3.1 Claude Codesettings.json ANTHROPIC_*Claude Code 的配置走~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY } }也可以用环境变量临时覆盖适合做单次验证ANTHROPIC_BASE_URLhttps://taotoken.net/api \ ANTHROPIC_AUTH_TOKEN$TAOTOKEN_API_KEY \ claude -p 把工单 #1234 拆成可执行任务列表输出 JSONANTHROPIC_AUTH_TOKEN与ANTHROPIC_API_KEY二选一即可不要两个都写、值还不一样——排查这类问题时你会怀疑人生。Claude Code 的完整接入说明在文档站https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_cc_doc 。3.2 Codexconfig.tomlCodex 读的是~/.codex/config.toml配置语义完全不同model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat配套的环境变量export TAOTOKEN_API_KEYYOUR_API_KEYenv_key写的是变量名不是变量的值这是 Codex 配置里最常见的误填。wire_api要与网关实际支持的协议一致如果请求一直报协议相关的错误把chat与responses两种取值分别试一次定位很快。3.3 自研 CLI / 任意 OpenAI 兼容客户端团队的调度脚本、批量任务、评测工具通常走 OpenAI 兼容协议用标准变量即可export OPENAI_BASE_URLhttps://taotoken.net/api export OPENAI_API_KEYYOUR_API_KEY3.4 CC Switch 面板填写对照面板字段填什么Claude CodeBase URLhttps://taotoken.net/apiClaude CodeTokenYOUR_API_KEYCodexbase_urlhttps://taotoken.net/apiCodexenv_keyTAOTOKEN_API_KEY自研 CLIOPENAI_BASE_URLhttps://taotoken.net/api自研 CLIOPENAI_API_KEYYOUR_API_KEY切换器本身的信息可以从官网入口进入查看https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_cc_switch 。4. 环节级埋点脚本请求侧打标 响应侧取 usage配置跑通之后真正的埋点只需要两件事请求前生成一个 trace请求后把 usage 写进本地日志。先看单次调用curl -sS https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -H x-pr-stage: ticket \ -H x-trace-id: $(uuidgen) \ -d { model: YOUR_MODEL_ID, messages: [{role: user, content: 补全该工单的验收标准}], stream: false }自定义头x-pr-stage是否被网关透传到后端并不重要——统计的权威来源是本地日志请求头只是给链路排查留一条线索。真正要抓的是响应体里的usage字段。下面是一段可直接使用的 Python 采集函数按环节打标并落 jsonlimport json import os import time import uuid import requests BASE_URL os.environ[TAOTOKEN_BASE_URL] # https://taotoken.net/api API_KEY os.environ[TAOTOKEN_API_KEY] # YOUR_API_KEY LOG_PATH os.environ.get(PR_AUTHOR_LOG, token_usage.jsonl) STAGES {requirement, ticket, pr, prod} def call_pr_author(stage: str, prompt: str, model: str, client: str claude-code, retry: int 0, stream: bool False) - dict: if stage not in STAGES: raise ValueError(funknown stage: {stage}) trace_id str(uuid.uuid4()) started time.time() payload { model: model, messages: [{role: user, content: prompt}], stream: stream, } # 流式请求若不显式声明最后一块可能拿不到 usage if stream: payload[stream_options] {include_usage: True} resp requests.post( f{BASE_URL}/v1/chat/completions, headers{ Authorization: fBearer {API_KEY}, Content-Type: application/json, x-pr-stage: stage, x-trace-id: trace_id, }, jsonpayload, timeout180, ) body resp.json() if resp.headers.get(content-type, ).startswith(application/json) else {} usage body.get(usage, {}) if isinstance(body, dict) else {} record { ts: time.strftime(%Y-%m-%dT%H:%M:%S%z, time.localtime(started)), trace_id: trace_id, stage: stage, client: client, model: model, retry: retry, status: resp.status_code, prompt_tokens: usage.get(prompt_tokens, 0), completion_tokens: usage.get(completion_tokens, 0), total_tokens: usage.get(total_tokens, 0), latency_ms: int((time.time() - started) * 1000), } with open(LOG_PATH, a, encodingutf-8) as f: f.write(json.dumps(record, ensure_asciiFalse) \n) resp.raise_for_status() return body调用侧按环节传参即可call_pr_author(requirement, 把这段需求整理成验收标准, modelYOUR_MODEL_ID) call_pr_author(ticket, 根据标题补全工单描述, modelYOUR_MODEL_ID) call_pr_author(pr, 审查这份 diff 并给出风险点, modelYOUR_MODEL_ID) call_pr_author(prod, 把这段脱敏告警摘要映射到可能的改动点, modelYOUR_MODEL_ID)关于重试把retry计数写进记录而不是在重试时新建一条干净的日志。否则你会看到某个环节的调用量莫名其妙偏高最后发现是网关超时重试。关于生产环节的边界这一环只处理脱敏后的告警文本和日志片段摘要不要让任何 Agent 或工具直接连生产库。需要查数据时由值班同学在本地执行 SQL再把结论贴回工单。这既是安全边界也是审计要求。5. 四环节 Token 对照表字段口径与归因规则四环节的埋点字段是同一套差异只在触发时机与关注的指标。下表是团队内部对齐用的口径模板观测重点一列按你自己的实际数据填写环节stage 取值典型触发时机观测重点归因注意需求requirement原始需求进入系统、整理验收标准单条需求的输入 Token 是否被历史上下文撑大同一需求的多次澄清算同一条链路用 trace_id 串起来工单ticket工单创建、补全描述、拆分任务批量任务的调用次数与失败重试比批量补全要区分批量与单条两种 client 标识PRpr打开 PR、推送新 commit、审查意见生成同一 PR 的重复触发次数、diff 上下文长度新 commit 触发的增量审查与全量审查分开标记生产prod告警触发、故障复盘摘要长尾请求占比、单次超长输入只统计脱敏文本原始日志不入库四条归因规则建议在接入第一天就定下来一条链路一个 trace。需求从澄清到拆单可能跨多个环节用同一个trace_id串起来环节之间不重复计数。重试单独标记。retry 0的记录在报表里单独一列评估供应商稳定性时用得上。失败请求不进业务成本。status 400的记录保留原始数据但在环节成本汇总里排除。模型维度必须留下。同一个环节可能用不同模型做不同子任务没有 model 字段就没法做替换评估。6. 汇总与看板从 jsonl 到环节成本排行日志落盘之后先做最小可用的汇总。用 Pythonimport json from collections import defaultdict agg defaultdict(lambda: {calls: 0, prompt: 0, completion: 0, total: 0, failed: 0}) with open(token_usage.jsonl, encodingutf-8) as f: for line in f: line line.strip() if not line: continue r json.loads(line) a agg[r[stage]] a[calls] 1 a[prompt] r.get(prompt_tokens, 0) a[completion] r.get(completion_tokens, 0) a[total] r.get(total_tokens, 0) if r.get(status, 200) 400: a[failed] 1 for stage, a in sorted(agg.items(), keylambda kv: -kv[1][total]): print(f{stage:12s} calls{a[calls]:6d} failed{a[failed]:5d} fprompt{a[prompt]:9d} completion{a[completion]:9d} total{a[total]:9d})如果本地装了 jq也可以一条命令出结果jq -s map(select(.status 400)) | group_by(.stage) | map({ stage: .[0].stage, calls: length, prompt: (map(.prompt_tokens) | add), completion: (map(.completion_tokens) | add), total: (map(.total_tokens) | add) }) | sort_by(-.total) token_usage.jsonl把这两个脚本接进每日定时任务输出写进一张看板表。看板上只放四列环节、调用次数、总 Token、失败率。不要一上来就堆十几个指标先让团队能回答哪个环节最贵这一个问题。7. 报错排查清单401 / 404 / 429 / 流式无 usage配置阶段九成的问题集中在这几类按顺序排查基本能自愈现象常见原因处理401 / invalid api keyKey 未创建、复制带空格、Authorization 缺 Bearer 前缀在控制台重新创建 Keyecho $TAOTOKEN_API_KEY检查首尾空白404 路径不存在Base URL 里多写了/v1、末尾多了斜杠、客户端又补了一次路径统一只填https://taotoken.net/api404 model not found模型 ID 与站点列表不一致、大小写或版本号写错用模型列表页显示的完整 ID 覆盖配置429并发过高或短时间批量任务堆积在埋点脚本里加指数退避retry字段记录次数流式响应无 usage未声明返回用量请求体加stream_options: {include_usage: true}并处理最后一块Claude Code 配置不生效多层配置叠加shell 里已有 export 覆盖了 settings.jsonenvCodex 报协议错误wire_api与网关支持协议不匹配在chat与responses之间切换验证请求偶发超时长上下文任务超过客户端默认超时把客户端超时提到 180s 以上并区分超时重试排查时有一条经验先降级到 curl 验证再回到客户端。用第 4 节的 curl 命令拿到 200说明 Key、Base URL、模型 ID 这一层没问题接下来所有问题都在客户端配置的覆盖顺序上。8. 把统计变成动作四环节各自的优化抓手有了环节粒度的数据之后优化动作才能落到具体位置而不是笼统地省点 Token需求环节重点看输入 Token 的分布。多数时候最贵的是把整篇历史讨论都塞进上下文。做法是按需检索、只带最近 N 条结论把完整讨论留在线下。工单环节批量任务的调用次数是主要成本项。把逐条调用改成分批调用 结构化输出同时给批量任务单独打 client 标识避免和交互式调用混在一起统计。PR 环节重复触发是最大的隐性开销。新 commit 推送到同一个 PR 时只发增量 diff 而不是全量并且给增量与全量打不同标签用数据证明优化是否有效。生产环节长尾请求最容易失控。给单次输入设长度上限超限时先做摘要再送模型原始日志不进链路。优化的顺序建议从调用次数入手再处理单次输入长度。次数是乘法因子长度是加法因子前者的收益通常更直接。9. 上手路径如果你的团队也在维护类似的软件工厂工具链建议按这条顺序走一遍先跑通单次调用再补齐三个客户端的配置最后加埋点。打开模型对话页先确认你能正常发起一次请求https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_step_chat查看 Coding Plan确认额度与并发符合你们的批量任务规模https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_step_plan创建 API Key写入~/.taotoken.envhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_step_key按 Claude Code 接入文档配好settings.json再回来加环节埋点https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentpr_author_step_cc整条链路的核心只有两句话Base URL 统一为https://taotoken.net/apiKey 用环境变量注入环节标签由客户端打成本口径由本地日志定。把这两件事做完四环节的 Token 统计就不再是靠猜的一笔账。
返回列表