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 包含:

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

支持的插桩库:

💻 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/MilvusLangChain/LlamaIndex/Haystack
**OpenLIT**25+ChromaDB/Pinecone/Qdrant/MilvusLangChain/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