1. 一次线上请求,为什么 Trace、Cost、质量总是对不上
线上 AI 应用跑起来之后,最让人头疼的不是模型答得不好,而是出了问题你根本不知道从哪查。用户投诉"回答变差了",你打开日志看到的是几百行 JSON;财务问"这个月为什么多花了三千块",你翻遍账单只能看到按天汇总的 token 数;老板问"我们的 AI 到底靠不靠谱",你只能拿几个 demo 案例硬撑。
这三个问题背后其实是同一件事:AI 可观测性没有把 Trace 链路、Token 成本、输出质量对齐到同一个请求 ID 上。传统 APM 能告诉你 HTTP 接口 P99 是多少,但它看不懂gen_ai.usage.input_tokens这种字段,也不知道一次 RAG 检索里 embedding 花了多少钱、rerank 拖了多少延迟、最终答案的 faithfulness 是 0.9 还是 0.6。
我试过最笨的办法:在业务代码里手动打点,把 trace_id、token 数、评分分别写进三张表,然后靠时间戳去 join。结果就是时间戳对不齐、采样率不一致、异步 judge 的评分回来时 trace 已经归档了。真正能落地的方案,是让一次请求从入口到出口只产生一个 trace_id,所有 span 都挂在这棵树上,成本和质量作为 span 属性或关联事件写入,这样回查时一个 ID 就能拉出完整链路。
这篇要解决的就是这个场景:你有一个已经上线的 AI 应用(不管是 Spring AI、LangChain4j 还是自己写的 Agent),现在要给它加上可观测性,让 Trace、Cost、质量三合一。我会给出可复制的采集配置、看板字段设计,以及用统一 Key/API 通道接入后,怎么用一次请求 ID 回查三段数据、验证成本归因和质量评分是否一致。
适合谁看:正在做 AI 应用上线后运维的工程师、需要给老板解释"钱花哪了"的技术负责人、以及被"回答质量下降"工单折磨的 on-call。不需要你之前用过 Langfuse 或 Phoenix,但需要你能看懂基本的 OpenTelemetry 概念和一段 YAML 配置。
核心检索词先明确:AI 可观测性是把 LLM 应用的分布式追踪、成本账本、质量评分统一到一套 telemetry 契约里;Trace回答"哪一步慢/失败",Cost回答"谁花了多少钱",质量回答"输出好不好"。三合一的意思就是这三个问题的答案挂在同一个请求 ID 下,5 分钟内能定位到根因。
下面从原问题拆解开始,一步步搭出可运行的链路。
2. TaoToken 统一通道:让 Trace 和 Cost 有同一个源头
2.1 为什么需要统一 API 通道
做可观测性最怕的就是数据源分散。如果你的应用同时调 OpenAI、Anthropic、通义千问,每个 SDK 的 usage 字段格式不一样,有的返回prompt_tokens,有的返回input_tokens,有的流式响应里根本不带 usage。你写三套解析逻辑,成本账本永远对不平。
统一通道的价值在于:所有模型调用都经过同一个网关,网关层统一注入gen_ai.*语义属性,统一记录 token 用量和费用。这样你的 trace 里每个 chat span 都有一致的字段,成本归因不需要在业务代码里做。
TaoToken 在这里扮演的就是这个统一入口。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。它的作用是把不同厂商的模型调用收敛到一个 OpenAI 兼容的接口上,你只需要配一个 Base URL 和一个 Key,就能在 trace 里拿到统一的 usage 数据。
2.2 前置准备:拿到 Key 和确认模型 ID
在控制台创建 API Key,路径是 https://taotoken.net/console/api-keys 。创建后你会得到一个sk-开头的字符串,这个就是后面所有配置里的api_key。
模型 ID 需要确认一下你实际要用的。在模型对话页面 https://taotoken.net/models 可以看到当前支持的模型列表,常见的比如gpt-4o-mini、claude-3-5-sonnet这类。记下你要用的 Model ID,后面配置里会用到。
这里有个坑要注意:不同模型的价格不一样,成本归因必须按 model 维度分开算。如果你在 trace 里只记了总 token 数没记 model,后面算钱的时候只能拍脑袋。所以配置里gen_ai.request.model这个属性一定要写对。
2.3 三件套:Base URL + Key + Model ID
不管你用什么框架,接入统一通道都是这三件套:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | OpenAI 兼容端点,注意不要加 UTM |
| API Key | sk-xxxxxx | 控制台创建,权限按需最小化 |
| Model ID | gpt-4o-mini等 | 按实际使用的模型填 |
如果你用的是 Claude Code 这类工具,配置方式略有不同,需要设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY,具体可以参考接入文档 https://taotoken.net/doc 。但核心逻辑一样:所有请求走同一个通道,usage 数据在网关层统一。
2.4 在 trace 里注入租户和功能标签
光有 model 和 token 还不够,成本归因要能回答"哪个租户、哪个功能花了钱"。所以业务代码里要往 span 上打两个标签:app.tenant_id和app.feature。
这两个字段是低基数还是高基数取决于你的业务。租户 ID 如果是几百个,算低基数,可以放 span attribute;如果是几万个,建议放高基数属性或者单独的事件。功能标签一般就几十个,放 attribute 没问题。
配置好之后,一次请求的 trace 树大概长这样:
session_trace (root) ├── guardrail_span (12ms) ├── retrieve_span (85ms, top_k=8) │ ├── embed_span (22ms) │ └── vector_search_span (41ms) ├── rerank_span (120ms) ├── chat_span (TTFT 620ms, TPOT 38ms, tokens in/out) │ ├── tool_call get_order (45ms) │ └── tool_call get_logistics (38ms) └── async_judge_span (faithfulness 0.89)每个 span 上都带着gen_ai.system、gen_ai.request.model、gen_ai.usage.input_tokens、gen_ai.usage.output_tokens这些属性。成本计算就是把这些 token 数乘以对应 model 的单价,质量评分挂在async_judge_span上。
2.5 成本公式和看板字段
成本公式很简单,但要把所有环节都算进去:
cost_usd = in_tok/1e6 * price_in + out_tok/1e6 * price_out + embed_tok/1e6 * price_embed + gpu_sec * gpu_rate_per_sec如果你用的是托管 API,最后一项 GPU 成本已经包含在 token 单价里了,不用单独算。但如果你自己部署了 vLLM 做 embedding 或 rerank,那 GPU 时间要单独归因。
三合一看看板的核心字段(按 feature × 天聚合):
| feature | sessions | $/task P50 | TTFT P99 | faith P50 | 拒答率 | 采纳率 |
|---|---|---|---|---|---|---|
| cs_bot | 1.2M | $0.0167 | 720ms | 0.90 | 8% | 61% |
| checkout_assist | 80k | $0.0429 | 980ms | 0.86 | 3% | 54% |
这张表就是 on-call 的入口:哪一列异常就下钻哪一列。TTFT 涨了查 chat span,faith 掉了查 retrieve 和 judge,$/task 涨了查 model 分布和 token 用量。
2.6 质量评分怎么挂进 trace
质量评分分在线和离线两种。在线用规则或小模型做实时低价判断,离线用 LLM-as-judge 做秒级评分。推荐的做法是5% 会话异步 judge,faithfulness < 0.8 自动打标quality_incident。
异步 judge 的结果回来时,通过 trace_id 关联到原始请求。这里要注意:judge 本身也是一次 LLM 调用,也会产生 token 成本,这部分成本要单独归因到quality_judge这个 feature 下,不能混进业务成本里。
配置好之后,你就能用一次请求 ID 回查三段数据了。下一节给出具体的可复制配置。
3. 可复制配置:OpenTelemetry + 统一通道接入
3.1 环境变量配置
先把三件套写进环境变量,避免硬编码:
# .env TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=sk-your-key-here TAOTOKEN_MODEL_ID=gpt-4o-mini # OpenTelemetry 导出配置 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_SERVICE_NAME=ai-app OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.1采样率设 0.1 是头部采样,10% 的请求全量记录。错误请求要 100% 采集,这个在 Collector 层做 tail sampling。
3.2 Spring AI 埋点配置
如果你用 Spring AI,通过ObservationRegistry注入:
@Configuration public class ObservabilityConfig { @Bean public ChatClient chatClient(ChatClient.Builder builder, ObservationRegistry registry) { return builder .defaultAdvisors(new SimpleLoggerAdvisor()) .build(); } @Bean public ObservationRegistry observationRegistry() { return ObservationRegistry.create(); } }调用时手动加租户和功能标签:
@Autowired private ObservationRegistry registry; public String ask(String question, String tenantId, String feature) { return Observation.createNotStarted("chat.completion", registry) .lowCardinalityKeyValue("gen_ai.request.model", "gpt-4o-mini") .lowCardinalityKeyValue("app.feature", feature) .highCardinalityKeyValue("app.tenant_id", tenantId) .observe(() -> chatClient.prompt() .user(question) .call() .content()); }3.3 OpenTelemetry Collector 配置
Collector 负责接收、处理、导出。关键配置:
# otel-collector-config.yaml receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 5s send_batch_size: 512 # PII 脱敏:手机号、身份证打码 attributes: actions: - key: gen_ai.prompt action: delete - key: app.user_phone action: hash # 尾部采样:错误全采,正常 10% tail_sampling: decision_wait: 10s policies: - name: errors type: status_code status_code: status_codes: [ERROR] - name: slow type: latency latency: threshold_ms: 5000 - name: random type: probabilistic probabilistic: sampling_percentage: 10 exporters: otlphttp/langfuse: endpoint: http://langfuse:3000/api/public/otel headers: Authorization: "Basic ${LANGFUSE_AUTH}" prometheus: endpoint: 0.0.0.0:8889 service: pipelines: traces: receivers: [otlp] processors: [attributes, tail_sampling, batch] exporters: [otlphttp/langfuse] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus]3.4 成本归因的 JSON 配置
成本单价表单独维护,方便更新:
{ "models": { "gpt-4o-mini": { "price_in_per_1m": 0.15, "price_out_per_1m": 0.60, "price_embed_per_1m": 0.02 }, "claude-3-5-sonnet": { "price_in_per_1m": 3.00, "price_out_per_1m": 15.00, "price_embed_per_1m": null } }, "gpu_rate_per_sec": 0.00012, "quality_judge_model": "gpt-4o-mini" }这个表放在配置中心,Collector 的 transform processor 读取它来算cost_usd属性。
3.5 看板字段映射
Grafana 看板的字段映射:
{ "panels": [ { "title": "三合一总览", "targets": [ { "expr": "sum(rate(gen_ai_cost_usd_total[5m])) by (feature)", "legendFormat": "{{feature}} $/s" }, { "expr": "histogram_quantile(0.99, sum(rate(gen_ai_ttft_ms_bucket[5m])) by (le, feature))", "legendFormat": "{{feature}} TTFT P99" }, { "expr": "avg(gen_ai_faithfulness) by (feature)", "legendFormat": "{{feature}} faith" } ] } ] }3.6 Claude Code 接入配置
如果你用 Claude Code 做开发辅助,配置方式:
export ANTHROPIC_BASE_URL=https://taotoken.net/api export ANTHROPIC_API_KEY=sk-your-key-here export ANTHROPIC_MODEL=claude-3-5-sonnet这样 Claude Code 的调用也会走统一通道,usage 数据能被采集到。具体可以参考 https://taotoken.net/doc 里的接入说明。
配置完成后,启动应用发一次请求,就能在 Langfuse 或 Phoenix 里看到完整的 trace 树了。下一节验证请求和成功结果。
4. 验证请求:用一次请求 ID 回查三段数据
4.1 发一次带 trace 的请求
用 curl 模拟一次请求,带上 traceparent 头:
TRACE_ID=$(openssl rand -hex 16) SPAN_ID=$(openssl rand -hex 8) curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -H "traceparent: 00-${TRACE_ID}-${SPAN_ID}-01" \ -d '{ "model": "gpt-4o-mini", "messages": [ {"role": "user", "content": "帮我查一下订单 882 的物流状态"} ], "stream": false }'响应里会带 usage:
{ "id": "chatcmpl-xxx", "choices": [{"message": {"content": "订单 882 已发货..."}}], "usage": { "prompt_tokens": 2400, "completion_tokens": 180, "total_tokens": 2580 } }4.2 在 trace 里回查
打开 Langfuse,用TRACE_ID搜索,能看到完整的 span 树。每个 span 上挂着属性:
chat_span gen_ai.system: openai gen_ai.request.model: gpt-4o-mini gen_ai.usage.input_tokens: 2400 gen_ai.usage.output_tokens: 180 gen_ai.response.finish_reasons: stop app.tenant_id: shop_882 app.feature: checkout_assist cost_usd: 0.000468成本计算:2400/1e6 * 0.15 + 180/1e6 * 0.60 = 0.00036 + 0.000108 = 0.000468。和 trace 里记录的一致。
4.3 质量评分关联
异步 judge 完成后,会在同一个 trace 下挂一个async_judge_span:
async_judge_span gen_ai.request.model: gpt-4o-mini app.trace.quality.faithfulness: 0.89 app.trace.quality.relevance: 0.92 judge_cost_usd: 0.000021这样一次请求 ID 就能拉出三段数据:Trace 链路(哪些 span、各耗时多少)、Cost(业务成本 + judge 成本)、质量(faithfulness、relevance)。
4.4 验证成本归因一致性
把 trace 里的 cost_usd 按 feature 聚合,和网关侧的 spend_logs 对比:
-- 从 trace 聚合 SELECT feature, SUM(cost_usd) as trace_cost FROM traces WHERE date = '2025-07-14' GROUP BY feature; -- 从网关 spend_logs 聚合 SELECT feature, SUM(cost_usd) as gateway_cost FROM spend_logs WHERE date = '2025-07-14' GROUP BY feature;两边差异应该小于 1%。如果差异大,检查是不是有请求没走统一通道,或者采样率导致 trace 侧少算了。
4.5 验证质量评分一致性
在线 judge 和离线 judge 的评分要定期校准。每周抽 200 条人工标注,对比 judge 分数:
| 指标 | 在线 judge | 人工标注 | 差异 |
|---|---|---|---|
| faithfulness P50 | 0.89 | 0.87 | +0.02 |
| relevance P50 | 0.92 | 0.90 | +0.02 |
差异超过 5% 就要检查 rubric 或换 judge 模型。
4.6 成功结果长什么样
配置正确的话,你会看到:
- Langfuse 里每次请求都有完整 span 树,属性齐全
- Grafana 三合一看看板实时更新,$/task、TTFT、faith 三列并排
- 用 trace_id 能在 trace、spend_logs、quality_scores 三处查到同一请求
- 成本归因误差 < 1%,质量评分和人工校准差异 < 5%
到这一步,可观测性链路就通了。下一节讲常见报错排查。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
5.1 401 Unauthorized
最常见的报错,通常是 Key 配错或没生效。
{"error": {"message": "Invalid API key", "type": "invalid_request_error"}}排查步骤:
第一,确认TAOTOKEN_API_KEY环境变量真的被读到了。在代码里打印一下前 8 位,看是不是sk-开头。
第二,确认 Base URL 没写错。正确的是https://taotoken.net/api,不要加 UTM 参数,不要多写/v1(有些 SDK 会自动加)。
第三,确认 Key 没过期或被禁用。去控制台 https://taotoken.net/console/api-keys 看一下状态。
第四,如果用的是 Claude Code,确认ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL都设了,且没有和其他环境变量冲突。
5.2 local proxy failed
这个报错通常出现在你本地起了代理但配置不对的时候:
Error: local proxy failed: dial tcp 127.0.0.1:7890: connect: connection refused排查:检查你的 HTTP_PROXY / HTTPS_PROXY 环境变量是不是指向了一个没启动的本地端口。如果不需要代理,直接 unset 掉:
unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy然后重新发请求。注意:统一通道本身不需要额外代理配置,直接访问即可。
5.3 reading choices 报错
流式响应解析时常见:
Error: reading choices: unexpected end of JSON input这个通常是流式响应的 chunk 解析问题。排查:
第一,确认stream: true时你的解析逻辑能处理data: [DONE]结束标记。
第二,确认没有在流式响应中途关闭连接。有些 HTTP 客户端默认超时太短,长响应会被截断。
第三,如果用的是 OpenAI SDK,确认版本兼容。老版本 SDK 对某些字段的解析可能有问题,升级到最新版。
第四,检查 trace 里gen_ai.response.finish_reasons是不是stop。如果是length,说明输出被 max_tokens 截断了,不是解析问题。
5.4 OAuth 相关报错
如果你用 Claude Code 或类似工具,可能遇到:
OAuth error: invalid_grant这个通常是 token 过期或配置冲突。排查:
第一,确认你用的是 API Key 模式而不是 OAuth 模式。统一通道走的是 API Key,不需要 OAuth 流程。
第二,检查~/.claude/settings.json或对应的配置文件,确认没有残留的 OAuth 配置。
第三,如果同时设了ANTHROPIC_API_KEY和 OAuth token,可能会冲突。清掉 OAuth 相关的环境变量。
5.5 Codex auth.json 配置
如果你用 Codex 类工具,配置文件在~/.codex/auth.json:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-your-key-here", "model": "gpt-4o-mini" }三件套要写全:Base URL、Key、Model ID。少任何一个都会报错。改完配置后重启工具生效。
5.6 Cline MCP 配置
Cline 通过 MCP 接入时,配置在cline_mcp_settings.json:
{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-your-key-here", "TAOTOKEN_MODEL_ID": "gpt-4o-mini" } } } }同样三件套要全。注意 MCP 直连生产库是禁止的,这里只是模型调用通道。
5.7 CC Switch 配置
CC Switch 用于切换不同通道,配置里要写全三件套:
{ "providers": { "taotoken": { "base_url": "https://taotoken.net/api", "api_key": "sk-your-key-here", "model_id": "gpt-4o-mini" } } }切换后确认 trace 里的gen_ai.system和gen_ai.request.model正确。
5.8 trace 断链排查
如果发现 trace 树不完整,某些 span 没挂上:
第一,确认 W3C traceparent 头在全链路透传。入口生成 trace_id 后写进 MDC,跨服务调用时带上。
第二,确认异步任务(比如 judge)也传了 context。Java Reactor 用 context 传播,Python asyncio 用 contextvars。
第三,确认采样率一致。如果入口采了但下游没采,trace 会断。
第四,检查 Collector 的 tail_sampling 配置,确认没有误丢。
5.9 成本对不平排查
trace 侧成本和网关侧 spend_logs 差异大:
第一,检查是不是有请求没走统一通道。直连厂商的调用不会进 trace。
第二,检查采样率。如果 trace 只采了 10%,聚合时要乘以 10 才是全量。
第三,检查流式响应的 usage 是否被记录。有些流式响应最后一个 chunk 才带 usage,如果没解析到就会漏算。
第四,检查 judge 成本是否被混进业务成本。judge 应该单独归因到quality_judgefeature。
5.10 质量评分异常排查
faithfulness 突然掉:
第一,看 retrieve span 的cosine_top1是不是也掉了。如果掉了,是检索问题。
第二,看 embedding 模型版本有没有变。embed 模型升级但索引没重建,会导致检索质量下降。
第三,看 judge 模型有没有换。换 judge 模型后评分标准会变,要重新校准。
第四,看输入分布有没有变。用户问的问题类型变了,faithfulness 自然会波动。
排查完这些,基本能覆盖 90% 的线上问题。最后一节给出 CTA 分流。
6. 接入与排障入口
如果你在配置过程中遇到问题,或者想先验证模型调用是否正常,可以按下面的路径走。
排障和接入配置:先看接入文档 https://taotoken.net/doc ,里面有各框架的配置示例。如果报 401 或 local proxy failed,去 API Keys 页面 https://taotoken.net/console/api-keys 确认 Key 状态和权限。
验证模型调用:在模型对话页面 https://taotoken.net/models 直接发一条消息,确认通道正常、usage 数据能返回。这一步能排除大部分配置问题。
长期编码和 Agent 场景:如果你要把这套可观测性用在日常开发或 Agent 工作流里,Coding Plan 页面 https://taotoken.net/coding-plan 有更详细的接入说明和配额管理。
API 端点:https://taotoken.net/api ,所有模型调用走这个地址,OpenAI 兼容格式。
配置的核心就三件套:Base URL 用https://taotoken.net/api,Key 从控制台创建,Model ID 按实际用的填。三样写全,trace 里的gen_ai.*属性就能正常采集,成本和质量数据也能对齐到同一个请求 ID 上。
最后给一个实用技巧:先在 staging 环境把采样率设成 100%,跑一周确认数据完整,再降到 10%。这样能避免上线后发现 trace 断链却查不到原因。错误请求的 tail sampling 策略一定要配,不然出问题时刚好没采到就尴尬了。