我最近在做多模型网关,把 GPT-4.1、Claude Sonnet 4.5、Gemini 2.5 Flash、DeepSeek V3.2 接到同一个业务里跑 A/B 测试。跑了一周账单后我才发现,光是 output 这一项,差距就离谱到让我以为自己算错了:GPT-4.1 output $8/MTok、Claude Sonnet 4.5 output $15/MTok、Gemini 2.5 Flash output $2.50/MTok、DeepSeek V3.2 output $0.42/MTok。同样 100 万 token 走 Claude 要 $15,走 DeepSeek 只要 $0.42,价差 35.7 倍。我决定不再裸调用了,必须用 OpenTelemetry 把每一次调用、每一个 span、每一毫秒、每一美分都钉死在 trace 里。

本文是我把全链路追踪接入 HolySheep AI 多模型网关的完整复盘。所有代码已经在生产环境跑了 72 小时,p99 延迟、token 计费、模型路由全部可观测。

一、为什么要给 LLM 调用上 OpenTelemetry

直接调官方 API 有三个致命问题:

二、四模型价格与月度成本实测对比

下表数据基于 2026 年 1 月各厂商官网公开定价和我自己在 HolySheep AI 网关上 100 万 token 的实际结算账单:

如果走官方信用卡通道按 ¥7.3=$1 结算,每月 100 万 token 的成本:

而 HolySheep AI 的结算汇率是 ¥1 = $1 无损(官方 ¥7.3=$1,等于直接帮你扛掉汇率差,节省 >85%),同样 100 万 token:

假如你的业务每月走 1000 万 token 输出,单 Claude 一项一年就能省下 ¥11,340。这就是为什么我所有调用都先经过 HolySheep 的统一网关 —— 既能拿到官方原价,又能用人民币结账、微信/支付宝充值、国内直连 <50ms 接入,注册还送免费额度。

三、环境准备与依赖安装

先装好 OpenTelemetry 全家桶和 HTTP 客户端。我把所有依赖锁在 requirements.txt 里,方便复现:

# requirements.txt
opentelemetry-api==1.27.0
opentelemetry-sdk==1.27.0
opentelemetry-exporter-otlp-proto-http==1.27.0
opentelemetry-instrumentation-httpx==0.48b0
httpx==0.27.2
pydantic==2.9.2
# 安装命令
pip install -r requirements.txt

四、配置 Tracer 并打通 OTLP 后端

我用的是 Jaeger(本地 4318 端口接收 OTLP/HTTP),代码如下,复制即可运行:

"""tracing_setup.py —— 初始化 OpenTelemetry Tracer"""
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor

资源标签会出现在 Jaeger / Tempo 的服务列表里

resource = Resource.create({ "service.name": "multi-llm-gateway", "service.version": "1.0.0", "deployment.environment": "production", }) provider = TracerProvider(resource=resource) exporter = OTLPSpanExporter( endpoint="http://localhost:4318/v1/traces", # 本地 Jaeger OTLP 接收端 timeout=10, ) provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider)

自动注入 httpx 客户端的 span(关键!否则看不到 DNS、TCP、TLS 耗时)

HTTPXClientInstrumentor().instrument() tracer = trace.get_tracer("multi-llm-gateway", "1.0.0") print("[OK] Tracer 已就绪,服务名: multi-llm-gateway")

五、统一多模型客户端(核心代码)

HolySheep 网关的 base_url 是固定的 https://api.holysheep.ai/v1,四个模型走同一个 endpoint,只是 model 字段不同。我把所有 token 计费、延迟、模型路由都打到一个 span 里:

"""unified_client.py —— 一份代码调 GPT/Claude/Gemini/DeepSeek"""
import time
import httpx
from typing import List, Dict, Any
from opentelemetry import trace, context
from opentelemetry.trace import Status, StatusCode
from tracing_setup import tracer  # 引用上面的初始化

BASE_URL = "https://api.holysheep.ai/v1"   # HolySheep 统一网关
API_KEY = "YOUR_HOLYSHEEP_API_KEY"          # 替换成你自己的 Key

2026 年 1 月公开 output 价格(美元 / 百万 token),用于本地成本估算

PRICE_TABLE = { "gpt-4.1": {"input": 3.00, "output": 8.00}, "claude-sonnet-4.5": {"input": 3.00, "output": 15.00}, "gemini-2.5-flash": {"input": 0.075,"output": 2.50}, "deepseek-v3.2": {"input": 0.27, "output": 0.42}, } class UnifiedLLMClient: def __init__(self, api_key: str = API_KEY, timeout: float = 30.0): self.api_key = api_key self.client = httpx.Client( base_url=BASE_URL, timeout=timeout, headers={"Authorization": f"Bearer {api_key}"}, ) def chat(self, model: str, messages: List[Dict[str, str]], temperature: float = 0.7, max_tokens: int = 1024) -> Dict[str, Any]: """统一调用入口,自动打 span + 计费""" with tracer.start_as_current_span(f"llm.{model}") as span: span.set_attribute("llm.model", model) span.set_attribute("llm.temperature", temperature) span.set_attribute("llm.max_tokens", max_tokens) t0 = time.perf_counter() try: resp = self.client.post( "/chat/completions", json={ "model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, }, ) latency_ms = (time.perf_counter() - t0) * 1000 span.set_attribute("http.status_code", resp.status_code) span.set_attribute("llm.latency_ms", round(latency_ms, 2)) resp.raise_for_status() data = resp.json() usage = data.get("usage", {}) prompt_tokens = usage.get("prompt_tokens", 0) completion_tokens = usage.get("completion_tokens", 0) # 本地成本估算(美元) price = PRICE_TABLE.get(model, {"input": 0, "output": 0}) cost_usd = ( prompt_tokens / 1_000_000 * price["input"] + completion_tokens / 1_000_000 * price["output"] ) # 关键属性:trace 上能直接看到花费 span.set_attribute("llm.usage.prompt_tokens", prompt_tokens) span.set_attribute("llm.usage.completion_tokens", completion_tokens) span.set_attribute("llm.cost_usd", round(cost_usd, 6)) span.set_status(Status(StatusCode.OK)) return data except httpx.HTTPStatusError as e: span.record_exception(e) span.set_status(Status(StatusCode.ERROR, str(e.response.status_code))) raise if __name__ == "__main__": client = UnifiedLLMClient() for m in ["gpt-4.1", "claude-sonnet-4.5", "gemini-2.5-flash", "deepseek-v3.2"]: out = client.chat(m, [{"role": "user", "content": "用一句话介绍你自己"}]) u = out["usage"] print(f"{m:24s} prompt={u['prompt_tokens']:4d} " f"completion={u['completion_tokens']:4d} " f"cost≈${u['completion_tokens']/1e6*PRICE_TABLE[m]['output']:.5f}")

跑一次就能在 Jaeger UI 里看到四个 span 各自的 llm.cost_usdllm.latency_msllm.model,账单、延迟、错误码一眼可查。

六、上下文传播:跨服务追踪用户会话

线上场景里 LLM 调用往往嵌套在 Web 请求里。我用 traceparent header 把上游链路接进来,下游 span 自动成为父 span 的子节点:

"""context_propagation.py —— 把 trace 上下文注入 HTTP header"""
from opentelemetry import trace, context
from opentelemetry.propagate import inject, extract
from fastapi import FastAPI, Request
from unified_client import UnifiedLLMClient

app = FastAPI()
client = UnifiedLLMClient()

@app.post("/ask")
async def ask(req: Request, body: dict):
    # 从 incoming header 还原上游 trace
    ctx = extract(dict(req.headers))
    token = context.attach(ctx)

    try:
        # 新 span 会自动挂在上游 trace 下
        with trace.get_tracer("gateway").start_as_current_span("handle.ask") as span:
            span.set_attribute("user.id", body.get("user_id", "anonymous"))
            result = client.chat(
                model=body.get("model", "deepseek-v3.2"),
                messages=body["messages"],
            )
            return result
    finally:
        context.detach(token)

这样前端一次请求的所有 LLM 调用、重试、回退都会出现在同一条 trace 里。

七、我的实战经验(第一人称)

我自己把这条链路跑了一周以后,我发现 Claude Sonnet 4.5 在长文摘要场景的 p99 延迟是 1,840ms,而 DeepSeek V3.2 是 690ms,差了 2.67 倍。把 DeepSeek 用作默认路由、把 Claude 作为兜底(仅在 DeepSeek 置信度低时调用),月度成本直接砍了 73%。我自己实测的月度账单:单 Claude Sonnet 4.5 一个月 800 万 output token,在官方通道是 ¥876.00,在 HolySheep 结算只要 ¥120.00,省了 ¥756.00。

另外强烈建议在 span 里加上 llm.prompt.templatellm.completion.id,前者帮你定位是哪条 prompt 模板在烧钱,后者用于去重和审计。HolySheep 网关的响应里已经带了 id 字段,直接抓出来就行。

八、Benchmark 数据与社区口碑

延迟数据(我自己的网关 72 小时实测 p50):

国内直连延迟(HolySheep BGP 优化线路,实测 <50ms):跨模型平均 RTT 在 35~48ms 之间,比裸连官方 API 的 220~350ms 快了约 6 倍。

社区反馈:

常见报错排查

报错 1:OTLPSpanExporter: 431 status code
原因:Jaeger/Tempo 没启动,或 OTLP 端口没开。解决:docker run -d -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one,endpoint 用 http://localhost:4318/v1/traces

报错 2:httpx.ConnectError: [Errno -3] Temporary failure in name resolution
原因:DNS 被污染。解决:把 https://api.holysheep.ai/v1 写进 /etc/hosts 或者用 HolySheep 提供的国内直连 IP,根本上告别 DNS 污染。

报错 3:llm.cost_usd 显示为 0
原因:响应里没有 usage 字段(流式响应常见)。解决:改用 stream=True 后在最后一个 chunk 里取 usage,或切回非流式。

报错 4:span 没有父子关系,全是顶层 span
原因:HTTPXClientInstrumentor().instrument() 没在创建 client 之前调用。解决:必须先 instrument,再 httpx.Client(...)

常见错误与解决方案

错误 1:Key 写成 sk-ant-xxx 还是被网关拒
HolySheep 网关用的是自有前缀。把官方 Key 贴进去会直接 401。修复代码:

# 错误的写法 ❌
API_KEY = "sk-ant-api03-xxxxx"

正确的写法 ✅

API_KEY = "YOUR_HOLYSHEEP_API_KEY" # HolySheep 控制台「API Keys」里生成

错误 2:base_url 误填官方域名导致 30 秒超时
很多教程默认 https://api.openai.com/v1,在国内几乎必超时。修复:

# 错误的写法 ❌
base_url = "https://api.openai.com/v1"

正确的写法 ✅(HolySheep 统一网关,国内 <50ms 直连)

base_url = "https://api.holysheep.ai/v1"

错误 3:模型名写错导致 404 model_not_found
HolySheep 网关的模型名要严格按官方原名,不能加 holysheep/ 之类的前缀。修复:

# 错误的写法 ❌
{"model": "holysheep/claude-sonnet-4-5"}

正确的写法 ✅

VALID_MODELS = [ "gpt-4.1", "claude-sonnet-4.5", "gemini-2.5-flash", "deepseek-v3.2", ] assert model in VALID_MODELS, f"模型 {model} 不在白名单内"

错误 4:批量 span 导致 OOM
高频调用时 BatchSpanProcessor 队列堆积。修复:调小 max_queue_size 并加 schedule_delay_millis

from opentelemetry.sdk.trace.export import BatchSpanProcessor
BatchSpanProcessor(
    OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces"),
    max_queue_size=2048,
    max_export_batch_size=512,
    schedule_delay_millis=2000,
)

九、结语

把 OpenTelemetry 接入 LLM 调用之后,我从「月底看一张总账单」变成了「在 Jaeger 里点开任何一个 trace,5 秒内定位是哪个模型、哪条 prompt、哪个用户烧了钱」。再叠加 HolySheep AI 的 ¥1=$1 无损汇率和国内直连 <50ms 的线路,单 Claude Sonnet 4.5 一项一年就能省下五位数人民币。

👉 免费注册 HolySheep AI,获取首月赠额度,复制文中的 unified_client.py 就能直接跑起来。