深色模式
调用链追踪:Langfuse 与 Phoenix
摘要:本文对比两大主流开源 LLM 追踪平台 Langfuse(MIT,SaaS 优先、UI 完善)与 Arize Phoenix(基于 OpenTelemetry / OpenInference,自托管优先),给出以 OpenTelemetry GenAI Semantic Conventions 为标准 schema 的接入方案。覆盖自动埋点、手动埋点、span 层级设计、敏感数据脱敏与回滚清理。适用模型:OpenAI
gpt-4o、Anthropicclaude-sonnet系列、gcp.vertex_ai/aws.bedrock等;[版本相关]:GenAI 语义约定截至 2026 年仍为 experimental,部分属性需用OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental开启最新命名。
核心概念
调用链追踪把一次 LLM 交互拆成树状 span:
- Trace:一次完整请求(如一个用户对话回合或一个 Agent 任务)。
- Span:一个工作单元(一次 chat 调用、一次检索、一次工具调用)。
- gen_ai. 属性*:OpenTelemetry GenAI 语义约定定义的标准属性(
gen_ai.system、gen_ai.operation.name、gen_ai.usage.input_tokens等)。
一个典型 Agent 循环的 span 层级:
关键设计:prompt / completion 内容不写进 span 属性,而是写进 span event。属性会被索引且有大小限制,且易导致 PII 泄漏;event 可以在 Collector 层被过滤/丢弃,无需改业务代码。
架构:统一走 OTel,平台可替换
OpenInference(Arize 提出的 OTel 扩展)与 OTel GenAI 约定使埋点一次、后端可选:
Langfuse vs Phoenix 怎么选
- 选 Langfuse:想要开箱即用的云托管、成熟 UI、prompt 版本管理、内置评估与 Spend 看板,团队不想运维。
- 选 Phoenix:已重度使用 OpenTelemetry、需要本地/自托管、想要内置 LLM-as-judge 评估与 embedding 聚类可视化,避免 vendor lock-in。
- 两者都兼容 OTLP,生产可并行接入做交叉验证。
方案 A:Phoenix + OpenInference(自托管优先)
1. 启动 Phoenix
bash
# 本地开发:单容器,UI 监听 http://localhost:6006
uvx arize-phoenix serve1
2
2
bash
# 生产自托管(docker compose 片段,[版本相关]:镜像版本随发布更新)
docker run -p 6006:6006 -p 4317:4317 -p 4318:4318 \
-e PHOENIX_WORKSPACE_DIR=/mnt/data \
arizephoenix/phoenix:latest1
2
3
4
2
3
4
2. 一行接入 OpenInference(Python)
python
# app/tracing.py
from phoenix.otel import register
tracer_provider = register(
project_name="ops-agent",
endpoint="http://phoenix:4317", # OTLP gRPC;云版用 Phoenix Cloud 端点
auto_instrument=True, # 自动扫描并激活已安装的 OpenInference instrumentor
)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
auto_instrument=True 会自动激活已安装的 instrumentor(如 openinference-instrumentation-openai、-langchain、-llamaindex)。此后每个 LLM 调用、工具调用、决策点都自动产出结构化 trace。
3. 手动埋点(自定义检索 / 业务逻辑)
python
from opentelemetry import trace
tracer = trace.get_tracer("rag-service")
def retrieve(query: str):
with tracer.start_as_current_span("retrieval vector_index_v3") as span:
span.set_attribute("gen_ai.operation.name", "retrieval")
span.set_attribute("gen_ai.data_source.id", "vector_index_v3")
span.set_attribute("retriever.top_k", 5)
# 敏感查询文本 hash 后再记录,见"安全与合规"
span.set_attribute("retriever.query.text", hash(query))
docs = vector_store.search(query, k=5)
return docs1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
方案 B:Langfuse(SaaS / 自托管)
1. 安装与初始化
bash
pip install langfuse1
python
# 实例会自动读取环境变量 LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_HOST
from langfuse import Langfuse
langfuse = Langfuse(
public_key="pk-lf-...", # [未实测]:密钥从控制台获取,勿硬编码进仓库
secret_key="sk-lf-...",
host="https://cloud.langfuse.com", # 自托管改为内网地址
)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
2. 手动 trace(Python)
python
from langfuse import Langfuse
langfuse = Langfuse()
# 一个全局 trace 聚合本次请求的所有 span
trace = langfuse.trace(name="support-agent", user_id="hashed_user_123",
metadata={"env": "production", "feature": "billing_qa"})
# 每个节点一个 generation span
generation = trace.generation(
name="chat",
model="gpt-4o",
input=[{"role": "user", "content": "我的订阅怎么取消?"}],
model_parameters={"temperature": 0.2, "max_tokens": 512},
)
# ... 调用模型 ...
generation.end(
output="在账单页面点击取消订阅即可。",
usage={"input": 128, "output": 47}, # 用于成本与 token 指标
)
langfuse.flush()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3. Langfuse 自托管(docker compose 片段)
yaml
# docker-compose.langfuse.yml —— [版本相关]:请参照官方 latest 配置,以下为结构示意
services:
langfuse-web:
image: langfuse/langfuse:latest
environment:
DATABASE_URL: postgresql://langfuse:secret@db:5432/langfuse
CLICKHOUSE_URL: http://clickhouse:8123
REDIS_URL: redis://redis:6379
ports: ["3000:3000"]
db:
image: postgres:16
clickhouse:
image: clickhouse/clickhouse-server:latest
redis:
image: redis:71
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Langfuse 架构要点(来自官方)
后端为 ClickHouse(OLAP,毫秒级查百万 trace)+ Redis 队列(异步 ingestion,不阻塞应用)+ S3/Blob(大 payload 存储)。异步是默认设计,追踪不应拖慢线上请求。
验证
bash
# Phoenix:访问 http://localhost:6006,确认 project 列表出现 ops-agent 且有 trace
# Langfuse:访问 HOST/traces,确认新 trace 出现且 token/成本字段已填充
# 用 OTLP 直连验证(无 SDK 时):向 4317 发一条 span,确认后端收到
# 见 OpenTelemetry Collector 的 otlp 接收器健康检查
curl -s http://localhost:8889/metrics | grep otelcol_receiver_accepted_spans1
2
3
4
5
6
2
3
4
5
6
python
# 单元级:确认 span 带齐 canonical 属性
with tracer.start_as_current_span("chat gpt-4o") as span:
span.set_attribute("gen_ai.system", "openai")
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.request.model", "gpt-4o")
span.set_attribute("gen_ai.usage.input_tokens", 128)
span.set_attribute("gen_ai.usage.output_tokens", 47)1
2
3
4
5
6
7
2
3
4
5
6
7
回滚与清理
切换 / 下线追踪后端
- 并行运行期:先 OTLP 双写(Phoenix + Langfuse),确认数据一致再切主。
- 回滚:把
register()的endpoint指回旧后端,或注释auto_instrument;SDK 调用本身失败不应影响主流程(OTel 默认非阻塞,异常被吞掉并记日志)。 - 清理:自托管 ClickHouse / Postgres 需配置保留期(retention),避免无限增长。Phoenix 单容器默认存本地卷,删除容器即丢数据——生产务必挂载持久卷。
故障排查
- 看不到 trace:检查 OTLP 端点可达性(4317 gRPC / 4318 HTTP)、Collector 是否接收(
otelcol_receiver_accepted_spans)、SDK 是否在客户端创建前instrument()。 - token 数为 0:确认 provider 返回
usage块;自托管模型(vLLM 等)一般返回,个别网关不返回需本地用对应 tokenizer 计数。 - 属性名不对:experimental 约定下旧客户端用
llm.*前缀,新客户端用gen_ai.*。统一设置OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental并固定版本,避免双前缀混用。
安全与合规
trace 含敏感 prompt —— 数据泄露头号风险
gen_ai.input.messages / gen_ai.output.messages 默认可能包含用户 PII、机密。生产必须:
- 默认不采集 message 内容;仅在可控数据驻留环境开启
capture_message_content=True。 - 或在 Collector 用 processor 做脱敏 / 哈希(如
retriever.query.text一律 hash)。 - 受监管行业(EU AI Act / 瑞士 FADP)强制自托管,且审计留痕 100% 覆盖。
成本 / 性能
- OTel 默认异步、非阻塞,埋点本身延迟可忽略;但 自动埋点的序列化开销 在延迟敏感场景需评估(OpenInference overhead 可能拖慢推理)。
- trace 存储成本随 ingest volume + retention 增长;ClickHouse 后端按量可控,商业 SaaS 按 span 计费可能成为大头。tail sampling(尾部采样)是控制成本的关键。
采样策略
错误与高延迟 trace 全采;正常 trace 尾部采样(如 10–20%)。结合成本告警,避免存储无界增长。
参考资料
- Langfuse 官方文档 - Observability & Tracing
- Arize Phoenix 官方文档 - What is Phoenix
- OpenTelemetry Semantic Conventions for GenAI
- uptrace - OpenTelemetry for AI Systems: LLM and Agent Observability
- arize.com - Add Observability to Your Open Agent Spec Agents with Phoenix
- futureagi - What Does a Good LLM Trace Look Like in 2026