1. 从“调用黑盒”到“成本可见”:LLM 应用可观测的真实痛点
做 LangChain 或 Agent 应用的朋友大概率遇到过这种场景:本地跑一个多轮对话 Agent,日志里只看到 HTTP 200,但用户反馈“回答变慢了”“这个月账单怎么涨了”,你打开代码一看,ChatOpenAI、ChatAnthropic、某个国产模型混着调,每个请求的 Token 消耗、首 Token 延迟、重试次数全散落在不同 SDK 的日志里。这就是 LLM 应用可观测要解决的核心问题——不是“能不能调通”,而是“每一次调用花了多少 Token、慢在哪一步、哪个 Prompt 在偷偷烧钱”。
AI 可观测(AI Observability)这个词这两年从 APM 圈延伸过来,落到 LLM 场景,至少要覆盖三层:调用链追踪(哪次 Agent 决策触发了哪次模型请求)、Token 与成本归因(按模型、按会话、按 Prompt 模板拆)、质量与延迟指标(首 Token 延迟、端到端耗时、错误率)。传统 Prometheus + Grafana 能监控 QPS 和 P99,但拿不到 prompt_tokens 和 completion_tokens 这种业务级指标,因为模型网关那层你没接进去。
我试过直接在 LangChain 里挂 CallbackHandler 打日志,问题是每个模型供应商的返回结构不一样,OpenAI 的 usage 字段和 Anthropic 的 usage.input_tokens 对不上,写一堆 if-else 之后代码比业务逻辑还长。更麻烦的是多模型混用——你不可能给每个供应商单独维护一套 Key 和监控。所以这篇要讲的落地路径是:用 TaoToken 做统一 Key 入口,把模型调用收敛到一个 Base URL,再通过 LangChain 回调把 Token 和延迟指标接出来,形成一条端到端可验证的可观测链路。
适合谁看:正在用 LangChain / LangGraph / 自研 Agent 框架,已经过了“能跑通”阶段、开始关心成本和稳定性的开发者。如果你还在纠结选哪个模型,这篇也能帮你把“选型”和“观测”解耦——模型可以换,观测链路不用重写。
2. TaoToken 统一 Key 前置:把多模型调用收敛到一个入口
在讲配置之前,先说清楚 TaoToken 在这个链路里的角色。它提供的是兼容 OpenAI 接口规范的 API 入口,你可以把它理解成一个“模型调用的统一网关”:Base URL 固定为 https://taotoken.net/api,用同一个 API Key 就能调用不同厂商的模型,模型 ID 通过请求参数区分。对 LangChain 来说,这意味着你不需要为每个供应商装不同的 langchain-xxx 包,统一用 ChatOpenAI 指向 TaoToken 的 Base URL 即可。
这一步的价值在可观测上很直接:所有模型请求都经过同一个出口,Token 用量、延迟、错误码的采集点就统一了。否则你在 LangChain 里挂三个 Callback,每个供应商的字段名还不一样,归因逻辑会写得很痛苦。
前置准备分三件事:
第一,拿到 API Key。访问 https://taotoken.net/api-keys(带 UTM:?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=),在控制台创建一个 Key。建议按环境分 Key,比如 dev-langchain、prod-agent,这样后面做成本归因时能直接按 Key 维度拆。
第二,确认模型 ID。TaoToken 的模型列表在文档里能查到,常见的有 gpt-4o、claude-3-5-sonnet、deepseek-chat 这类命名。注意模型 ID 是请求参数,不是 Base URL 的一部分,所以切换模型不用改配置结构。
第三,环境变量约定。我习惯用 TAOTOKEN_API_KEY 和 TAOTOKEN_BASE_URL 两个变量,避免 Key 硬编码进代码。.env 文件这样写:
TAOTOKEN_API_KEY=sk-你的实际Key
TAOTOKEN_BASE_URL=https://taotoken.net/api
这里有个容易踩的坑:Base URL 末尾不要带 /v1。LangChain 的 ChatOpenAI 内部会拼 /chat/completions,如果你写成 https://taotoken.net/api/v1,最终请求路径会变成 /api/v1/chat/completions,部分模型会返回 404。实测下来 https://taotoken.net/api 是正确写法。
另外,如果你用的是 Claude Code 这类工具,配置方式不一样,需要走 Anthropic 兼容入口,文档里有单独说明(https://taotoken.net/doc)。但 LangChain 场景统一用 OpenAI 兼容接口就行,不用折腾两套。
前置做完后,你的调用链应该是:LangChain Agent → ChatOpenAI(Base URL=TaoToken) → TaoToken 网关 → 实际模型。可观测的采集点就落在 ChatOpenAI 的回调和 TaoToken 的用量返回上。
3. 可复制配置:LangChain + TaoToken 的 settings 与回调接入
这一节给可直接复制的配置片段。先装依赖:
pip install langchain langchain-openai python-dotenv
如果你用 LangGraph 或 AgentExecutor,额外装 langchain-community 即可,核心的 ChatOpenAI 在 langchain-openai 里。
3.1 基础模型配置(Python)
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
load_dotenv()
llm = ChatOpenAI(
model="gpt-4o", # 换成 claude-3-5-sonnet / deepseek-chat 都行
api_key=os.getenv("TAOTOKEN_API_KEY"),
base_url=os.getenv("TAOTOKEN_BASE_URL"),
temperature=0.3,
max_tokens=1024,
timeout=60,
max_retries=2,
)
这段配置的关键参数说明:
| 参数 | 作用 | 可观测相关 |
|---|---|---|
model | 模型 ID,决定路由到哪个厂商 | 成本归因的第一维度 |
base_url | 固定为 TaoToken 入口 | 统一采集点 |
max_retries | 失败重试次数 | 重试会放大 Token 消耗,需监控 |
timeout | 单次请求超时 | 配合延迟指标定位卡顿 |
3.2 自定义 Callback 采集 Token 与延迟
LangChain 的 BaseCallbackHandler 提供了 on_llm_end 和 on_llm_error 钩子,response 对象里能拿到 token_usage。下面是一个最小可用的采集器:
import time
from langchain_core.callbacks import BaseCallbackHandler
class ObservabilityCallback(BaseCallbackHandler):
def __init__(self):
self.start_time = {}
def on_llm_start(self, serialized, prompts, run_id=None, **kwargs):
self.start_time[run_id] = time.time()
def on_llm_end(self, response, run_id=None, **kwargs):
latency = time.time() - self.start_time.pop(run_id, time.time())
usage = response.llm_output.get("token_usage", {})
prompt_tokens = usage.get("prompt_tokens", 0)
completion_tokens = usage.get("completion_tokens", 0)
total = usage.get("total_tokens", 0)
model = response.llm_output.get("model_name", "unknown")
print(f"[LLM] model={model} latency={latency:.2f}s "
f"prompt={prompt_tokens} completion={completion_tokens} total={total}")
def on_llm_error(self, error, run_id=None, **kwargs):
print(f"[LLM ERROR] run_id={run_id} error={error}")
挂到调用上:
callback = ObservabilityCallback()
result = llm.invoke("用一句话解释什么是 AI 可观测", config={"callbacks": [callback]})
print(result.content)
跑一次你会看到类似输出:
[LLM] model=gpt-4o latency=1.83s prompt=18 completion=42 total=60
这就是可观测链路的最小闭环:模型名、延迟、Token 三个核心指标都出来了。生产环境里把 print 换成打点到 Prometheus 或写入 ClickHouse 即可。
3.3 Agent 场景的配置差异
如果你用 AgentExecutor,Callback 要挂在 executor.invoke 的 config 上,而不是单个 llm 上,否则拿不到工具调用和多次 LLM 请求的完整链路:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个助手,可以调用工具。"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_openai_tools_agent(llm, tools=[], prompt=prompt)
executor = AgentExecutor(agent=agent, tools=[], verbose=False)
result = executor.invoke(
{"input": "帮我查一下今天的天气"},
config={"callbacks": [callback]}
)
Agent 场景下一次 invoke 可能触发多次 LLM 调用,Callback 的 on_llm_start / on_llm_end 会成对出现,用 run_id 区分。这样你就能看到“Agent 决策花了 2 次模型调用,第一次 800ms,第二次 1.2s”这种粒度。
4. 验证请求:用 Token 用量与延迟指标跑通端到端检查
配置写完不算完,得验证链路真的通了。这一节给一个可执行的检查清单,按顺序跑一遍,每步都有预期结果。
4.1 第一步:裸请求验证 Key 和 Base URL
先不挂 LangChain,用 curl 直接打 TaoToken,确认 Key 有效、模型可调:
curl -s https://taotoken.net/api/chat/completions \
-H "Authorization: Bearer $TAOTOKEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "ping"}],
"max_tokens": 10
}'
预期返回里包含 choices 和 usage 字段,usage.total_tokens 应该是个小数字(10 以内)。如果返回 401,说明 Key 有问题;返回 404,检查 Base URL 是不是多写了 /v1。
4.2 第二步:LangChain 单次调用验证
跑 3.2 节的代码,预期看到 [LLM] 日志,latency 在 1-3 秒之间(取决于模型),total 等于 prompt + completion。如果 token_usage 是空字典,说明该模型没返回 usage 字段,需要换模型或检查 TaoToken 的响应格式。
4.3 第三步:多模型切换验证
把 model 改成 claude-3-5-sonnet 再跑一次,预期日志里 model 字段变化,Token 计数逻辑不变。这一步验证的是“统一 Key 下多模型可观测的一致性”——不用改采集代码,换模型后指标照常出来。
4.4 第四步:Agent 多轮调用验证
跑 3.3 节的 Agent 代码,预期看到多条 [LLM] 日志,每条有独立 run_id。统计一下总 Token 和总延迟,这就是一次 Agent 交互的真实成本。
4.5 检查清单汇总
| 检查项 | 预期结果 | 不通过时的排查方向 |
|---|---|---|
| curl 裸请求 | 返回 choices + usage | 401 查 Key,404 查 Base URL |
| LangChain 单次调用 | 日志有 model/latency/token | token_usage 空则换模型 |
| 多模型切换 | model 字段变化,指标正常 | 模型 ID 拼写 |
| Agent 多轮 | 多条日志,run_id 不同 | Callback 挂载位置 |
| 延迟基线 | 首 Token 1-3s 可接受 | 超时参数、网络 |
| 成本归因 | 按 model 聚合 total_tokens | 采集器字段映射 |
跑完这六项,你的可观测链路就算端到端验证过了。后面接 Grafana 或自建看板,数据源就是 Callback 里那几个字段。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
这一节按真实报错来。以下四个是我在接入过程中实际遇到过的,每个都给现象、原因、修复。
5.1 401 Unauthorized
现象:curl 或 LangChain 调用返回 {"error": {"message": "Invalid API key", "type": "invalid_request_error"}}。
原因通常有三个:Key 复制时带了空格或换行;.env 文件没被 load_dotenv() 加载;Key 被删除或过期。
修复:先 echo $TAOTOKEN_API_KEY 确认变量有值且无空格。如果用的是 IDE 的 Run 配置,检查环境变量有没有覆盖 .env。Key 本身的问题去 https://taotoken.net/api-keys 重新生成一个。
5.2 local proxy failed
现象:LangChain 报 APIConnectionError: Connection error 或 local proxy failed。
这个报错关键词是“proxy”,但原因不一定是代理。常见情况是 base_url 写错导致请求打到了本地某个端口,或者系统环境变量里有 HTTP_PROXY / HTTPS_PROXY 指向了一个不可用的地址。检查方式:
env | grep -i proxy
如果有输出且不是你预期的,临时清掉再跑:
unset HTTP_PROXY HTTPS_PROXY
另外确认 base_url 是 https://taotoken.net/api,不是 http://localhost:xxxx。
5.3 reading choices 相关报错
现象:KeyError: 'choices' 或 reading 'choices' 报错,通常出现在自定义解析代码里。
原因是响应结构和你预期的不一致。比如模型返回了错误对象而不是正常 completion,你的代码直接取 response["choices"][0] 就会炸。修复方式是先判断 response 里有没有 error 字段:
data = response.json()
if "error" in data:
raise RuntimeError(f"API error: {data['error']}")
choices = data.get("choices", [])
if not choices:
raise RuntimeError(f"Empty choices: {data}")
LangChain 内部已经做了这层判断,所以如果你用 ChatOpenAI 还遇到这个报错,大概率是自己写了 requests 调用。
5.4 OAuth 相关报错
现象:报 OAuth token expired 或 invalid_grant。
TaoToken 的 API Key 是静态 Bearer Token,不走 OAuth 流程。如果你看到 OAuth 报错,说明请求打到了别的服务,或者代码里混入了其他 SDK 的认证逻辑。检查 base_url 和 api_key 参数有没有被其他配置覆盖。Claude Code 场景下走的是 Anthropic 兼容入口,配置方式不同,参考 https://taotoken.net/doc 里的说明。
5.5 三件套检查法
遇到任何接入问题,先核对三件套:
- Base URL:
https://taotoken.net/api(不带/v1) - API Key:
sk-开头,无空格 - Model ID:从文档模型列表里复制,别手打
这三项对了,90% 的报错能排除。
6. 从可观测到可行动:把指标接进你的日常流程
链路跑通之后,指标怎么用才是关键。我自己的做法是分三层:
第一层,实时日志。Callback 里的 print 换成结构化日志(JSON 格式),打到 stdout,容器环境里直接被日志系统收走。这样出问题时能快速 grep 某个 run_id 的完整调用链。
第二层,聚合看板。把 total_tokens 按 model 和 session_id 聚合,每天看一次趋势。异常点通常是某个 Prompt 模板改坏了导致 Token 暴涨,或者某个模型延迟劣化。这一步用 Prometheus 的 Counter 和 Histogram 就能做,不需要上重型平台。
第三层,成本告警。给 total_tokens 设日环比阈值,超过 30% 触发告警。Agent 场景下特别有用,因为多轮调用容易失控。
如果你还在选型阶段,建议先用 TaoToken 的统一 Key 把调用收敛,再挂一个最小 Callback 采集器,跑一周拿到真实数据后再决定要不要上完整的可观测平台。这样避免一开始就陷入工具对比,而是先有数据、再有决策。
需要长期跑 Agent 或编码任务的,可以看下 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=),按用量计费比按次调用更适合高频场景。验证模型效果直接去模型对话页(https://taotoken.net/chat?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=,Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。
最后留一个实用技巧:Callback 里的 run_id 建议和你的业务 session_id 做关联,比如在 on_llm_start 时从 metadata 里取。这样排查问题时能从“用户反馈慢”直接定位到“哪次模型调用慢”,而不是翻一堆日志。这个关联字段设计好了,后面接任何可观测后端都不用改采集逻辑。



被折叠的 条评论
为什么被折叠?



