OpenTelemetry + Langfuse 实战指南:LLM 可观测性的标准协议
> 来源: https://langfuse.com/integrations/native/opentelemetry
> OTel 官网: https://opentelemetry.io/
> GenAI 语义约定: https://opentelemetry.io/docs/specs/semconv/attributes-registry/gen-ai/
> 研究时间: 2026-03-17
📌 一句话总结
OpenTelemetry(OTel)是 CNCF 制定的可观测性数据传输标准协议。Langfuse 作为 OTel 后端,可以接收任何语言(Python/Go/Java/Rust)通过标准 OTLP 协议发送的 Trace 数据。这意味着零厂商锁定——换可观测性平台只需改一个 URL。
🎯 一句话版本
Span = "我在某个时间段干了某件事,这是相关数据"
开始 ──────────── 结束
│ │
│ 这段时间里: │
│ • 调了 GPT-4 │
│ • 花了 150 token│
│ • 耗时 200ms │
│ │
└─── 打包发给 Server
就三步:
1. 开始计时(Start)
2. 贴标签(SetAttributes:模型名、token 数、输入输出...)
3. 结束计时(End → 自动发送给 Langfuse)
多个 Span 串起来就是一条 Trace(完整链路)。Langfuse 收到后帮你画图、算钱、做统计。
跟打日志差不多,只不过是结构化的、有父子关系的、按标准格式发的日志。
🤔 OTel 是什么?
类比
| 领域 | 标准协议 | 作用 |
|---|---|---|
| Web 通信 | HTTP | 浏览器和服务器之间的通信标准 |
| 数据库查询 | SQL | 不管是 MySQL 还是 PostgreSQL,查询语言统一 |
| **可观测性** | **OpenTelemetry** | 不管后端是 Langfuse/Datadog/Jaeger,数据格式统一 |
核心概念
Trace(链路)= 一次完整请求的生命周期
└─ Span(跨度)= 链路中的一个步骤
├─ Span: LLM 调用(model=gpt-4, tokens=1500)
├─ Span: 工具调用(tool=web_search)
└─ Span: 向量检索(db=pinecone)
每个 Span 包含:
- 名称:这一步在干什么
- 时间:开始时间 + 持续时长
- 属性(Attributes):键值对,描述这一步的细节
- 父子关系:哪个 Span 是哪个的子步骤
GenAI 语义约定
OTel 社区为 LLM/GenAI 场景定义了标准属性前缀 gen_ai.*:
| 属性 | 含义 | 示例 |
|---|---|---|
| `gen_ai.system` | 模型提供商 | `openai`, `anthropic` |
| `gen_ai.request.model` | 请求的模型名 | `gpt-4`, `claude-3` |
| `gen_ai.usage.input_tokens` | 输入 token 数 | `150` |
| `gen_ai.usage.output_tokens` | 输出 token 数 | `320` |
| `gen_ai.prompt.0.role` | 第一条消息的角色 | `user` |
| `gen_ai.prompt.0.content` | 第一条消息的内容 | `什么是 OTel?` |
| `gen_ai.completion.0.role` | 回复的角色 | `assistant` |
| `gen_ai.completion.0.content` | 回复的内容 | `OTel 是...` |
Langfuse 靠这些属性自动识别 LLM 调用——有 gen_ai.* 的 Span 显示为 "Generation",没有的显示为普通 "Span"。
🔌 在 Langfuse 里使用 OTel 的三种方式
方式一:Python SDK v3(最简单)
from langfuse import Langfuse
lf = Langfuse()
@lf.observe()
def my_agent(question):
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": question}]
)
return response.choices[0].message.content
my_agent("什么是 OTel?")
# → 自动出现在 Langfuse 仪表盘
SDK v3 底层就是 OTel——@lf.observe() 装饰器自动创建 Span 并发送。
方式二:纯 OTel 协议(任何语言)
设置环境变量,把 OTel 数据发到 Langfuse:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n 'pk-lf-xxx:sk-lf-xxx' | base64)"
然后用任何语言的 OTel SDK 发 Span 即可。
方式三:OTel 插桩库(零代码改动)
pip install openllmetry
from openllmetry import init
init() # 自动追踪所有 OpenAI/Anthropic/Cohere 调用 → 发到 Langfuse
支持的插桩库:
- OpenLLMetry:覆盖 OpenAI/Anthropic/Cohere/Bedrock/Vertex 等 20+ 提供商
- OpenLIT:额外支持 DeepSeek/Groq/vLLM/xAI 等
- Arize:支持 OpenAI/Anthropic/Bedrock/Groq/Mistral
💻 Go 语言完整示例
安装
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
完整代码
package main
import (
"context"
"encoding/base64"
"fmt"
"log"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
func initTracer() (*sdktrace.TracerProvider, error) {
// Langfuse 认证:pk:sk 做 base64
auth := base64.StdEncoding.EncodeToString(
[]byte("pk-lf-你的公钥:sk-lf-你的私钥"),
)
exporter, err := otlptracehttp.New(
context.Background(),
otlptracehttp.WithEndpointURL(
"https://cloud.langfuse.com/api/public/otel/v1/traces",
),
otlptracehttp.WithHeaders(map[string]string{
"Authorization": "Basic " + auth,
}),
)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-go-agent"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}
func main() {
tp, err := initTracer()
if err != nil {
log.Fatal(err)
}
defer tp.Shutdown(context.Background())
tracer := otel.Tracer("my-go-agent")
ctx := context.Background()
// ===== 创建根 Trace =====
ctx, rootSpan := tracer.Start(ctx, "deep-research-task")
rootSpan.SetAttributes(
attribute.String("langfuse.trace.name", "用户提问:什么是OTel"),
attribute.String("langfuse.trace.user_id", "young"),
)
// ===== 子 Span 1: LLM 调用 =====
_, llmSpan := tracer.Start(ctx, "llm-call")
llmSpan.SetAttributes(
attribute.String("gen_ai.system", "openai"),
attribute.String("gen_ai.request.model", "gpt-4"),
attribute.Int("gen_ai.usage.input_tokens", 150),
attribute.Int("gen_ai.usage.output_tokens", 320),
attribute.String("gen_ai.prompt.0.role", "user"),
attribute.String("gen_ai.prompt.0.content", "什么是OTel?"),
attribute.String("gen_ai.completion.0.role", "assistant"),
attribute.String("gen_ai.completion.0.content", "OTel是..."),
)
time.Sleep(100 * time.Millisecond) // 模拟调用耗时
llmSpan.End()
// ===== 子 Span 2: 工具调用 =====
_, toolSpan := tracer.Start(ctx, "tool-web-search")
toolSpan.SetAttributes(
attribute.String("langfuse.span.name", "web_search"),
attribute.String("search.query", "OpenTelemetry 介绍"),
)
time.Sleep(50 * time.Millisecond)
toolSpan.End()
// ===== 结束根 Span =====
rootSpan.End()
fmt.Println("✅ Spans 已发送到 Langfuse")
}
在 Langfuse 中的显示
Trace: "deep-research-task"
├─ Generation: "llm-call" ← 自动识别为 LLM 调用
│ Provider: openai
│ Model: gpt-4
│ Input: 150 tokens ($0.0045)
│ Output: 320 tokens ($0.0192)
│ Duration: 100ms
│
└─ Span: "tool-web-search" ← 显示为普通 Span
Name: web_search
Duration: 50ms
代码核心三步
// 1. Start — 开始一个 Span
ctx, span := tracer.Start(ctx, "span-name")
// 2. SetAttributes — 设置属性(gen_ai.* 会被 Langfuse 识别为 LLM 调用)
span.SetAttributes(
attribute.String("gen_ai.system", "openai"),
attribute.String("gen_ai.request.model", "gpt-4"),
)
// 3. End — 结束 Span(自动计算耗时并发送)
span.End()
🔑 为什么 OTel 重要?
零锁定
| 场景 | 没有 OTel | 有 OTel |
|---|---|---|
| 用 Langfuse | 用 Langfuse SDK | 用 OTel SDK |
| 想换 Phoenix | **重写所有埋点代码** | **改一个 URL** |
| 想换 Datadog | **又重写一遍** | **改一个 URL** |
| 多语言支持 | Python/JS 只 | **Go/Java/Rust/C#/...** |
Langfuse 的 OTel 生态
Langfuse 支持三大 OTel 插桩库的数据:
| 库 | 覆盖 LLM 提供商 | 覆盖向量数据库 | 覆盖框架 |
|---|---|---|---|
| **OpenLLMetry** | 20+ | Chroma/Pinecone/Qdrant/Milvus | LangChain/LlamaIndex/Haystack |
| **OpenLIT** | 25+ | ChromaDB/Pinecone/Qdrant/Milvus | LangChain/LlamaIndex/CrewAI |
| **Arize** | 8+ | - | LangChain/LlamaIndex/DSPy |
协议细节
| 项目 | 值 |
|---|---|
| 传输协议 | OTLP over HTTP(JSON 或 Protobuf) |
| gRPC | ❌ 暂不支持 |
| 认证 | HTTP Basic Auth(pk:sk base64 编码) |
| Endpoint | `/api/public/otel`(通用)或 `/api/public/otel/v1/traces`(trace 专用) |
| 数据区域 | EU: `cloud.langfuse.com`,US: `us.cloud.langfuse.com` |
💡 与我们的关联
1. 如果用 Go 写 Agent
直接用上面的代码模板,所有 LLM 调用和工具调用都能进 Langfuse 仪表盘。
2. OpenClaw 已有集成路径
OpenClaw 可以通过 OpenRouter Broadcast 零代码接入 Langfuse。如果需要更细粒度的追踪(比如 Skill 执行、Cron 任务),可以在 OpenClaw 的 Node.js 代码中加 OTel 埋点。
3. 未来标准
OTel GenAI 语义约定还在演进中(CNCF 正在制定),但已经是事实标准。现在用 gen_ai.* 属性埋点,未来不管换什么可观测性平台都能识别。
📊 评分
| 维度 | 评分(/10) |
|---|---|
| 标准化程度 | 9.5 — CNCF 官方标准,所有主流平台都支持 |
| 易用性 | 8.0 — 概念简单(Start→SetAttributes→End),但初始化代码略长 |
| 语言覆盖 | 9.5 — Go/Java/Python/JS/Rust/C#/... 全覆盖 |
| LLM 生态 | 8.0 — GenAI 语义约定还在演进,但已可用 |
| 与我们的关联 | 7.5 — Go Agent 直接可用,OpenClaw 有集成路径 |
| **综合** | **8.5** |
报告由深度研究助手自动生成 | 2026-03-17
来源: https://opentelemetry.io/ + https://langfuse.com/integrations/native/opentelemetry