TECH NOTE
4582 字 23 分钟 系列 1 / 1Phoenix 核心功能与实现流程
ON THIS PAGE / 目录11 节
一句话讲透:Phoenix 是 Arize 开源的 LLM 应用可观测性平台——用 OpenTelemetry 的 OTLP 协议把 LLM 应用里每一次检索、工具调用、模型推理采成 Trace,再在这份 Trace 上做评估、实验和 Prompt 迭代。
LLM 应用的故障大多不抛异常,只表现为”答得不对”。要定位这种问题,你得看到这次回答用了哪几段检索文档、真正发给模型的 prompt 长什么样、token 花在哪、哪一步慢——这些恰好是 Trace 天然能表达的东西。Phoenix 的全部能力,都长在这份 Trace 上。
下面按”是什么 → 整体架构 → 核心流程 → 底层原理 → 值得借鉴”的顺序拆,重点在第 4 章。
1. 项目简介
Phoenix 是面向 LLM 应用的可观测性(Observability)平台,解决”LLM 应用是黑盒”的问题:一次请求会在检索、工具调用、模型推理之间反复跳转,出问题时既不知道错在哪一步,也不知道 prompt 实际长什么样。
围绕一次请求的生命周期,它提供三段能力:记录(追踪)、判断(评估)、比较(数据集与实验)。
典型场景:
| 场景 | 用它做什么 |
|---|---|
| 开发期调试 Agent / RAG | 看一次回答到底检索了什么、prompt 拼成什么样 |
| 生产监控 | 盯 p95 延迟、token 成本、错误率 |
| Prompt 回归 | 把线上 bad case 变成 Dataset,改完 Prompt 跑实验对比 |
| 质量评估 | LLM-as-judge 批量打分,按分数筛出问题 case |
与同类方案相比,Phoenix 最明显的两个特点是:完全自托管、开源免费(本地一条命令或 Docker 起一个实例,数据不出内网),以及基于 OpenTelemetry 开放标准——埋点代码不被锁定,同一套 instrumentation 可以导出到任何支持 OTLP 的后端。
2. 核心功能概览
| 核心功能 | 功能说明 | 典型使用场景 |
|---|---|---|
| Tracing(链路追踪) | 通过 OTLP 接收并存储 Span / Trace,可视化 LLM 调用链 | 调试 RAG 检索链路、定位慢调用与异常 |
| Evaluation(评估) | LLM-as-judge 对相关性、幻觉、毒性等维度打分 | 上线前质量基线、线上持续评估 |
| Datasets & Experiments | 版本化数据集 + 对比实验,跟踪 Prompt / 模型变更效果 | Prompt 回归测试、模型 A/B 对比 |
| Playground & Prompt Management | Prompt 模板版本管理、参数调优、重放真实 Trace | 基于线上 bad case 迭代 Prompt |
| Metrics Dashboard | 自动聚合延迟、token、成本、错误率 | 生产环境监控与告警 |
| PXI / Remote MCP | 内置 AI Agent 调查 Trace、驱动产品操作;/mcp 端点供 Claude Code / Cursor 查询 | 自动化根因分析、编码 Agent 集成 |
这六项里,Tracing 是地基:后五项的数据全部来自 Span,区别只是”读出来做什么”——评估是给 Span 打分,实验是拿 Dataset 跑出新 Span,仪表盘是聚合 Span,PXI 是让 Agent 用自然语言查 Span。
Playground 与 Prompt 管理本质上是”读 Trace / 跑 Dataset”的组合封装,本文不单独展开,下面聚焦五条最能体现架构取舍的链路。
3. 整体架构
flowchart TB UserApp["用户 LLM 应用<br/>OTel SDK / OpenInference 探针"] GRPC["OTLP gRPC Server<br/>端口 4317"] HTTP["FastAPI HTTP Server<br/>端口 6006"] Bulk["BulkInserter<br/>异步批量写入"] DB[("数据库<br/>SQLite / PostgreSQL")] API["GraphQL + REST v1 API"] UI["React SPA 前端"] Agent["PXI Agent / MCP"]
UserApp -->|"OTLP spans(gRPC)"| GRPC UserApp -->|"OTLP spans(HTTP)"| HTTP GRPC --> Bulk HTTP -->|"OTLP/HTTP 摄入"| Bulk Bulk -->|"批量写入"| DB DB -->|"查询读取"| API API -->|"GraphQL"| UI API -->|"内部工具"| Agent服务端同时跑两个入口:gRPC 服务器(4317)专门接收 OTLP Trace,生产环境用它压缩率高、开销低;FastAPI(6006)一手承担 OTLP/HTTP 摄入(本地开发、受限网络场景更方便),一手提供 REST / GraphQL API,并托管前端静态资源。两条摄入通道的终点是同一个 BulkInserter:它把 Span 放进内存队列,异步批量落库,避免高并发下每条 Span 一个事务。
| 组件 | 职责 |
|---|---|
| OTLP gRPC Server(4317) | 接收生产环境的 OTLP Trace |
| FastAPI HTTP Server(6006) | OTLP/HTTP 摄入、REST v1 / GraphQL API、静态前端托管 |
| BulkInserter | 队列缓冲 + 批量写入,解耦摄入与落库 |
| 数据库(SQLite / PostgreSQL) | Span / Trace、Dataset、Experiment、Annotation |
| GraphQL + REST API | 前端与客户端 SDK 的读写入口 |
| React SPA | Trace 树、仪表盘、Playground、PXI Chat |
| PXI Agent / Remote MCP | 服务端 Agent 与外部 MCP 客户端入口 |
存储层默认 SQLite(本地零依赖),生产可切 PostgreSQL。查询侧不做预聚合:Span 的属性在写入时就已规范化,读的时候按需聚合——这一点在第 4.4 节还会展开。
4. 核心功能实现流程
4.1 Tracing(OTLP 链路追踪摄入)
① 功能作用
记录请求在 LLM 应用内部的传播路径(检索 → 工具调用 → 生成),形成一棵可追溯的 Trace 树。这是 Phoenix 最基础的能力,其他所有功能都建立在 Trace 数据之上。
② 核心执行流程
- 用户应用调用
phoenix.otel.register()创建 TracerProvider,并用openinference-instrumentation-*自动探针化 OpenAI / LangChain 等 SDK,或手动用 decorator 创建 Span。 - 应用执行 LLM 调用时,OpenTelemetry SDK 生成 Span,并写入 OpenInference 语义属性(span kind、token 计数、prompt 模板、检索文档与分数)。
- SpanProcessor(推荐 BatchSpanProcessor)把 Span 攒成批次,经 OTLP 导出——gRPC 到 4317,或 HTTP 到 6006。
- 服务端解析 OTLP payload,把 Span 归一化成内部结构并入队
BulkInserter。 BulkInserter异步批量写入数据库(SQLAlchemy ORM,SQLite / PostgreSQL 皆可),事务边界按批而不是按条。- React 前端通过 GraphQL 查出某个项目下的 Trace 树,渲染时间线、属性、异常与 token 统计。
③ 执行流程
sequenceDiagram participant App as 用户应用(OTel SDK) participant Col as Phoenix OTLP 接入层 participant BI as BulkInserter 队列 participant DB as 数据库 participant UI as React UI(GraphQL)
App->>App: 执行 LLM 调用,生成 Span Note over App: Span 携带 OpenInference 语义属性 App->>Col: BatchSpanProcessor 导出 OTLP batch(4317 / 6006) Col->>Col: 解析 payload,归一化 Span 结构 Col->>BI: 入队 BI->>DB: 批量 INSERT Span / Trace UI->>DB: GraphQL 查询 Trace 树 DB-->>UI: Span 列表 + 属性④ 核心实现原理
- OTLP 双通道摄入:gRPC(4317)与 HTTP(6006)共用同一套解析与落库逻辑,只在前端接入层分叉。前者适合生产(长连接、二进制编码、压缩率更高),后者适合本地与受限网络(防火墙放行一个 HTTP 端口即可)。
- OpenInference 语义约定:Span 的属性不是随手写的日志字段,而是一套约定——span kind 区分 LLM / Tool / Retriever / Chain,
llm.token_count、llm.prompts、retrieval.documents等属性让摄入端可以结构化解析。这正是”能按模型聚合 token 成本""能展开某条检索的文档与分数”的前提:存的是结构化字段,不是原始文本。 - 异步批量写入:
BulkInserter用队列把”接收”和”落库”解耦。摄入延迟敏感、落库吞吐导向,两者用队列隔开后,写入峰值被吸收成批量事务,单条 Span 写失败也不会阻塞接收链路。
⑤ 实际例子
一个 LangChain RAG 应用收到提问”这份合同里违约金怎么算”:探针自动创建 Retriever Span(记录召回的 4 段文档与各自相似度分数)和 LLM Span(记录完整 prompt、temperature、输入输出 token 数),外层再挂一个 Chain Span 表示整次请求。导出后 Phoenix 入库,前端展示成一棵三层调用树。点开 LLM Span,你能看到实际发给模型的 prompt 里拼进了哪几段文档——答案错了,问题出在检索还是生成,一眼就能分辨。
4.2 Evaluation(LLM-as-judge 评估)
① 功能作用
用 LLM 或程序化 evaluator 给 Span / Trace 打分(相关性、faithfulness、幻觉、毒性),结果作为 Annotation 附回 Trace。它回答的是”这条回答到底好不好”,把主观判断变成可筛选、可聚合的数值。
② 核心执行流程
- 通过
arize-phoenix-evals选择内置或自定义 evaluator(模板 prompt + LLM provider adapter)。 - 从 Phoenix 拉取一批 Span(REST 接口或 DataFrame 导出)。
input_mapping把嵌套的 Span 数据绑定到 evaluator 期望的字段(比如把召回文档映射成context)。- 并发调用 judge LLM 批量评分,返回 label / score / explanation。
- 评估结果作为 Span Annotation 写回 Phoenix,在 UI 与指标里呈现。
③ 执行流程
flowchart TB Spans["Phoenix 中的 Span"] -->|"导出"| Eval["phoenix-evals 评估器"] Map["input_mapping 字段绑定"] --> Eval Eval -->|"并发调用"| Judge["Judge LLM"] Judge -->|"原始输出"| Score["评估结果<br/>label / score / explanation"] Score -->|"作为 Annotation 写回"| Server["Phoenix Server"] Server --> UI["UI 与指标展示"]④ 核心实现原理
核心是 Annotation 数据模型:评估结果不与 Trace 混存,而是作为独立实体标注在 Span / Trace / Session 上。同一条 Span 可以同时挂人审结论、LLM 评分、用户点赞,互不覆盖,也能按来源区分。另一个机制是 adapter 层:evaluator 只描述”评什么、怎么评”,provider 差异(OpenAI / Anthropic / 本地模型)与并发批处理由 adapter 屏蔽,换 judge 模型不用改写评估逻辑。
⑤ 实际例子
对客服 RAG 应用当天 500 条 Trace 跑 hallucination evaluator:每条响应连同召回文档一起送进 judge prompt,产出 faithful / hallucinated 标签写回 Phoenix。然后在 UI 里按标签过滤,把被判为幻觉的 case 逐条排查——其中不少是检索召回了过期的旧版条款,问题不在模型,而在检索库。
4.3 Datasets & Experiments
① 功能作用
把样例(input / 期望输出)存成版本化 Dataset,在其上运行 Experiment(换 Prompt、换模型、换代码),对比输出与评估分数。它解决的是”改完之后有没有变好、有没有回归”。
② 核心执行流程
- 用 phoenix-client 从 DataFrame / CSV / 线上 Trace 创建 Dataset,每个 example 含 input、reference output、metadata。
- Dataset 的修改产生新版本,实现内容版本控制。
- 定义 task 函数(你的应用逻辑)与 evaluators,启动 Experiment。
- 框架对每个 example 并发执行 task,执行过程自动打 Trace 到 Phoenix。
- 结果、评估分数、Trace 关联存入 Experiment 记录,UI 并排对比多次实验。
③ 执行流程
sequenceDiagram participant Dev as 开发者 participant Task as task 函数 participant API as REST API participant DB as 数据库
Dev->>API: 上传 Dataset(examples) API->>DB: 保存 Dataset 版本 Dev->>Task: 启动 Experiment(dataset、task、evaluators) loop 每个 example 并发执行 Task->>Task: 执行应用逻辑(自动打 Trace) Task->>API: Trace 上报 Task-->>Dev: 返回 output end Dev->>API: 提交结果与评估分数 API->>DB: 保存 Experiment Run + Annotation④ 核心实现原理
两个机制值得记住:Dataset 版本化让实验可复现——Experiment 绑定的是某个确定的 Dataset 版本,而不是”当时那份数据”,否则一周后回看实验结论就站不住脚。**实验自追踪(tracing the experiment itself)**则让 Experiment 跑在自家 OTel 管线上:每条 example 的执行本身就是一条 Trace,某条输出错了,可以直接点进去看那次执行内部的检索与 prompt。
⑤ 实际例子
把线上 50 条 bad case 导出成 Dataset,改完 Prompt 跑 v2 实验:50 条并发执行,每条都产生一条 Trace,judge 打分随结果一起入库。UI 并排对比 v1 / v2,看到 v2 修好了 38 条且没有新增回归;剩下 12 条点进 Trace,发现都是检索没召回到对应条款——说明下一轮该调的是切分策略,而不是继续改 Prompt。
4.4 Metrics Dashboard
① 功能作用
对摄入的 Trace 自动聚合指标:延迟分位、token 用量、成本估算、错误率。不需要手工定义指标或写查询,打开就有。
② 核心执行流程
Span 摄入时携带 token 计数、模型名、延迟、错误状态等 OpenInference 属性 → 规范化入库 → 查询时对项目内 Trace 做聚合 → GraphQL 返回时间序列与分布 → React 仪表盘渲染预置面板(trace 量、延迟分位、USD 成本、按模型的 token 排行、Annotation 均分等)。
③ 执行流程
flowchart LR Span["Span 摄入<br/>token / model / latency / error"] --> Store[("Span 表")] Store -->|"查询侧按项目聚合"| GQL["GraphQL 聚合查询"] GQL --> Dash["仪表盘预置面板"]④ 核心实现原理
指标不在摄入时预聚合,而是查询侧实时聚合。好处是不用维护物化视图或流式计算任务,新增一个维度只是改查询;代价是要求写入时的属性必须规范——token 计数、模型名、错误标记必须是结构化字段且口径统一,否则聚合会算错或算空。这也是 OpenInference 语义约定在数据链路上真正兑现价值的地方。
⑤ 实际例子
某个周一仪表盘显示 p95 延迟从 1.8s 涨到 4.2s,同时某模型的 token 成本翻倍。按模型过滤 Span 后发现:新上的 Prompt 把 few-shot 示例从 2 个加到 6 个,输出没变长、输入长了一倍——延迟和成本其实是同一个原因。
4.5 PXI / Remote MCP(AI Agent 集成)
① 功能作用
PXI 是内置在 Phoenix 里的 AI 工程 Agent,用工具调用调查 Trace、迭代 Prompt、操作页面;/mcp 端点则让 Claude Code、Cursor 等 MCP 客户端直接查询 Phoenix 里的 Trace、Dataset 与 Experiment。
② 核心执行流程
- 用户在浏览器 Chat UI 提问(比如”这条调用为什么失败”)。
- 服务端 Agent 组装 system prompt、skills 与当前页面可用的工具集,调用用户自己配置的 LLM(用户自带 API Key)。
- LLM 返回 tool call:查数据类走服务端沙箱 bash,通过
phoenix-gql执行 GraphQL 查询;页面操作类则下发到浏览器执行。 - 工具结果回填给 LLM,循环直到得出结论,最终流式返回 UI。
③ 执行流程
flowchart LR User(("用户")) <--> ChatUI["浏览器 Chat UI"] ChatUI <--> Agent["PXI Agent(服务端)"] Agent -->|"LLM 调用"| LLM["LLM Provider<br/>用户自带 API Key"] LLM -->|"tool call"| Agent Agent --> Bash["沙箱 bash<br/>phoenix-gql"] Bash --> GQL["GraphQL API"] Agent -->|"页面操作指令"| ChatUI④ 核心实现原理
能力按上下文门控:Agent 只暴露当前页面真正可执行的工具——在 Trace 详情页它能查这条 Trace,在 Dataset 页它才拿到 Dataset 相关工具。权限复用:服务端沙箱 bash 里的 phoenix-gql 用的是与登录用户相同的权限面,不为 Agent 单开一条高权限通道,沙箱本身也没有对外网络出口。两条合起来,既避免 Agent”什么都能干”带来的安全面扩张,也让模型不必在几十个工具里做选择。
⑤ 实际例子
在一条失败的 Trace 页面问 PXI”为什么这条调用失败”:Agent 加载 trace-debugging skill,用 GraphQL 查出该 Trace 的 Span 树,发现 LLM Span 状态是 error、异常信息是限流(429),且重试在 3 次后放弃。回答按可能性排序给出根因假设与下一步动作,而不是把原始 Span 一股脑倒给你。
5. 值得借鉴的设计
- 站在开放标准上做差异化。 摄入层完全采用 OTLP + OpenTelemetry,自定义价值只放在 OpenInference 语义约定(LLM 特有属性)上。结果是用户的 instrumentation 代码不被锁定,随时可以换后端。自建类似系统时,协议层优先复用行业标准,把创新压在”这个领域特有的语义”上。
- 摄入与落库解耦。 双协议接收统一汇入异步
BulkInserter批量写入。“队列 + 批量插入”是遥测系统吸收写入峰值的通用解,代价是要接受秒级可见性延迟和少量丢失风险。 - Annotation 作为一等公民。 人审、LLM 评分、用户反馈统一建模为可附在 Span / Trace / Session 上的标注实体,而不是去改 Span 本身。扩展元数据时”追加标注”比”修改事实数据”干净得多:数据不可变、来源可追溯、多来源不打架。
- 元可观测(self-tracing)。 Experiment 的运行本身走自家 OTel 管线,实验输出能直接回放成 Trace。这个设计让”工具自己也在被工具观测”,调试实验平台本身不需要另建一套日志。
- Agent 能力上下文门控 + 权限复用。 PXI 的工具集随页面上下文动态收窄,沙箱查询复用登录用户的权限面。做产品内 Agent 时,这是兼顾安全与体验的实用范式:不给 Agent 更多权限,只给它更少但更准的工具。
6. 总结
| 核心功能 | 核心执行流程 | 关键实现机制 |
|---|---|---|
| Tracing | OTel 探针 → OTLP(gRPC 4317 / HTTP 6006)→ BulkInserter → DB → GraphQL / UI | OTLP 双通道、OpenInference 语义、异步批量写入 |
| Evaluation | 拉取 Span → input_mapping 绑定字段 → judge LLM 并发评分 → 写回 Annotation | evals adapter 层、Annotation 数据模型 |
| Datasets & Experiments | Dataset 版本化 → 并发跑 task → 自追踪 → 结果与分数入库对比 | 数据版本控制、元可观测 |
| Metrics | Span 属性规范化 → 查询侧实时聚合 → 仪表盘 | 实时聚合、预置面板 |
| PXI / MCP | Chat → Agent 编排 → tool call(服务端 GraphQL 沙箱 / 浏览器页面操作)→ 流式返回 | 上下文门控、权限复用、沙箱执行 |
一句话收尾:Phoenix 没有发明新的可观测性范式,它做的是把 LLM 应用特有的语义(prompt、召回文档、token)钉进 OpenTelemetry 这套成熟标准里,然后在 Trace 之上长出一个评估与实验的工作台。这也是它最值得抄的地方——先用标准解决”数据怎么进来”,再把精力花在”这个领域特有的数据长什么样”。
口径说明:Tracing 与 PXI / MCP 两条链路的组件划分与执行顺序,来自官方文档与公开的架构说明;Evaluation、Datasets & Experiments 的服务端实现细节(如 online evals 的调度策略、Playground 的流式执行)可查资料覆盖较少,文中按官方文档描述的功能行为整理,未逐文件核对源码,粒度上做了保守处理。
这套梳理是怎么写出来的:用的是《基于DeepWiki梳理开源框架核心流程》这份提示词——固定六个章节,强制把”功能怎么运转”讲到从输入到输出的每一步。想拿它拆别的开源项目,直接复制那一篇。