“这次很慢”需要一棵证据树
本地 graph.invoke() 返回结果时,打印一行成功日志也许够用。设想开发者在 Phoenix 里看到一条运行耗时 28 秒:他需要继续定位,究竟是检索节点、模型请求还是重试退避占用了时间;检索为空是上游没有数据,还是过滤条件变化;恢复之后的发布是否真的只发生了一次。
单条日志适合描述一个事实,Trace 适合串起一次运行的父子调用、耗时和属性。对于 LangGraph,还需要把节点、模型、检索器、工具和外部 HTTP 请求放进同一条关联链,否则只能看到许多孤立的“成功 / 失败”。
Phoenix 处在执行器之外
Phoenix1 是 Trace 的接收、存储、查询和可视化后端。它不执行 LangGraph,不保存图的恢复点,也不负责决定用户能否继续审核。数据链路可以画成:
查看 Mermaid 源码
flowchart LR
A[LangGraph 图] --> B[LangChain / OpenInference 语义]
B --> C[OpenTelemetry SDK]
C -->|OTLP| D[Phoenix]
D --> E[Trace 查询]
D --> F[评估与指标]
A --> G[Checkpointer]Trace 经 OpenInference 和 OpenTelemetry 送入 Phoenix;Checkpointer 仍由应用运行时负责,不能把 Trace 当作恢复状态。
OpenTelemetry(OTel)2 提供 Trace、Span、导出器和传输的厂商无关能力;OpenInference3 在其上补充 LLM、Agent、Tool 和 Retriever 的语义属性。一次调研运行在 Trace 中应类似这样:research.run 是根 Span,下面有 split_questions、collect_sources、llm.generate 和 publish_article;每个子 Span 带耗时、错误和外部请求 ID。Phoenix 消费这些标准化数据并提供 AI 调用的查询界面。将来更换支持 OTLP 的后端时,应用侧的观测边界不必整体重写。
先在本地启动一个可验证的 Phoenix
开发环境可以用 Docker 快速确认数据链路。latest 只适合实验;生产镜像应锁定实际验证过的版本,并单独配置认证、持久化和网络入口:
docker pull arizephoenix/phoenix:latest
docker run --rm \
-p 6006:6006 \
-p 4317:4317 \
arizephoenix/phoenix:latest打开 http://localhost:6006,确认页面能访问后再启动应用。6006 是常用的 Web / collector HTTP 入口,4317 是 OTLP gRPC 入口;真实部署要以所用 Phoenix 版本的端口和协议配置为准。
如果团队希望把生命周期写入项目,可以使用 Compose:
services:
phoenix:
image: arizephoenix/phoenix:latest
ports:
- "6006:6006"
- "4317:4317"
environment:
PHOENIX_SQL_DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
volumes:
- phoenix-db:/var/lib/postgresql/data
volumes:
phoenix-db:docker compose up -dCompose 示例里的数据库凭据只适合本地开发。线上至少要替换凭据、限制 6006 的暴露范围、使用持久卷和备份,并设定 Trace 保留期。提示词、用户问题和检索片段可能含个人信息,Phoenix 的访问控制不能被当成“调试工具例外”。
用 Phoenix OTel 接入应用入口
按 2026-08-24 Phoenix 文档的 Python 示例,先安装 OTel 封装和需要的 instrumentor:
pip install "arize-phoenix-otel>=0.16.0"
pip install openinference-instrumentation-langchain
pip install openinference-instrumentation-openai本地默认可以连接 http://localhost:6006;远程实例再注入 endpoint 和 API key:
export PHOENIX_COLLECTOR_ENDPOINT="http://localhost:6006"
# 远程实例按部署文档设置:
# export PHOENIX_API_KEY="..."初始化集中放在应用入口,一次完成,不要在每个节点里重复初始化:
# observability.py
from phoenix.otel import register
def setup_tracing():
return register(
project_name="langgraph-research",
auto_instrument=True,
batch=True,
)auto_instrument=True 会根据当前环境启用受支持的集成。安装 LangChain 和 OpenAI 的 instrumentor 后,LangGraph 中由 LangChain 驱动的链和模型调用有机会出现在同一条 Trace 中。接入后不要只确认“列表里出现了一条 Trace”,而要展开一次运行检查:根 Span 是否包含 run_id,模型 Span 是否挂在正确的节点下,超时是否记录了错误类型和供应商请求 ID。
自动埋点之后,还要补业务语义
自动埋点通常能告诉你某个库调用了多久,却不一定知道这次调用属于哪个租户、哪个业务运行或哪个草稿版本。应用入口应把这些关联信息放进 Trace context 或受控属性:
from opentelemetry import trace
tracer = trace.get_tracer("research-workflow")
def run_research(graph, state, config, run_id: str):
thread_id = config["configurable"]["thread_id"]
with tracer.start_as_current_span("research.run") as span:
span.set_attribute("app.run_id", run_id)
span.set_attribute("app.thread_id", thread_id)
span.set_attribute("app.workflow", "research")
return graph.invoke(state, config=config)属性名称是项目契约的一部分;不要把整段 prompt、用户原文和工具参数无差别写入 Span。建议记录模板版本、输入哈希、租户级匿名 ID、草稿 revision、错误分类和外部请求 ID。这样开发者点开某个 llm.generate Span 时,能知道它属于哪次运行和哪个版本,却不会在 Trace 页面直接暴露用户原文。原始内容如确实需要复现,应放在受控存储,并在 Trace 中只保留引用。
节点级手工 Span 也要有边界:只有当自动埋点不能解释业务步骤时才补,不要为每一行 Python 代码创建 Span。一个“检索资料” Span 应包含开始 / 结束时间、来源数量、重试次数和结果分类,而不是完整网页正文。
一次 LangGraph 运行至少观察什么
可以按四个问题组织字段:
| 问题 | 关键字段 | 用途 |
|---|---|---|
| 运行走到哪里 | run_id、thread_id、节点名、状态 | 还原控制流和恢复关系 |
| 为什么变慢 | 节点 / 模型耗时、排队时间、重试次数 | 定位延迟来源 |
| 为什么失败 | 错误类型、供应商请求 ID、是否可重试 | 判断处理路径 |
| 为什么变贵或变差 | token、模型版本、工具次数、反馈标签 | 成本和质量回归 |
从 Trace 聚合出的指标可以包括:按节点和模型的 p50 / p95 延迟、失败率、重试率、空检索率、每次运行 token 和费用估算、人工暂停时长。比如 p95 只在 collect_sources 升高,说明先查检索上游;如果所有节点都升高,才需要检查整体排队或 Phoenix exporter。指标只保留聚合事实;不要为了方便统计而把敏感原文复制到指标标签,基数过高的 thread_id 也不适合作为 Prometheus label。
两条 Phoenix 接入失败路径
页面能打开,但 Trace 列表为空
先看应用的 PHOENIX_COLLECTOR_ENDPOINT、协议配置和 exporter 错误,再用一个确定性节点发起最小运行。如果本地页面正常而 exporter 没有数据,通常是 endpoint、端口或批处理进程退出时没有 flush;处理方式是按照目标版本确认 HTTP / gRPC 入口,并在短脚本结束前关闭 tracer provider。验证时应能在 Phoenix 中按项目名看到一条运行和至少一个模型 / 节点 Span。
Trace 有模型 Span,却看不到业务节点
先检查自动 instrumentor 是否覆盖了 LangGraph 当前版本,以及应用是否只把模型调用包进了 Span。处理方式是在应用入口补一个 research.run Span,必要时为检索和发布节点增加少量手工 Span,并用 run_id / thread_id 关联。验证时展开 Trace,节点、模型和外部请求应形成可解释的父子关系,而不是一组孤立调用。
Phoenix 不能替代什么
Phoenix 能解释“发生了什么”,但它不能替代:
- Checkpointer:保存图 State 和恢复点;
- 业务数据库:保存发布、审核、权限和幂等事实;
- 用户事件协议:决定浏览器应该显示哪些进度;
- 评估集:判断改动是否改善了答案质量;
- 告警系统:把失败率、延迟或成本异常通知到值班流程。
把这些职责都塞进 Trace,会导致 Trace 既无法作为可靠业务记录,也无法作为轻量用户事件。它只负责留下可查询的运行证据,业务事实和调优结论要在各自的系统中保存。
最小上线检查清单
- Phoenix 版本、端口、存储和认证方式已固定并验证。
- OTel / OpenInference 初始化只发生在应用入口。
-
run_id、thread_id、Trace ID 的关联关系已写入接口约定。 - LangGraph、模型、检索和工具的 Span 层级能在页面中解释。
- 节点耗时、错误分类、重试、token 和外部请求 ID 可查询。
- prompt、用户内容、工具参数和检索片段有脱敏与访问控制。
- Trace 数据有保留期、删除和备份策略。
- 自动埋点失败时,应用仍能完成核心业务或进入明确降级路径。
参考资料
- Phoenix:使用 Phoenix OTel 进行追踪(官方文档,访问日期:2026-08-24)
- Phoenix:OpenTelemetry 与 OpenInference(官方文档,访问日期:2026-08-24)
- Phoenix:LangGraph 工作流示例(官方文档,访问日期:2026-08-24)
- OpenTelemetry Python Tracing(官方文档,访问日期:2026-08-24)
Footnotes
-
Phoenix 官方文档:用于接收、查询和可视化 LLM、Agent 与工具调用的 Trace。 ↩
-
OpenTelemetry 官方文档:厂商无关的观测数据采集与传输标准。 ↩
-
OpenInference 官方文档:为 AI 调用补充统一语义属性的规范与工具。 ↩