mcpField
P
phoenix-error-analysis

phoenix-error-analysis

github.com/arize-ai测试与质量12.0k97 次安装今天更新
代码仓库

关于 phoenix-error-analysis

这是用于分析 Phoenix 中抽样 trace、span 或 session 的技能,先进行开放编码,再进行轴心编码。它先为有问题的实体记录具体笔记,再将观察归为标签并统计,供评估设计和问题优先级排序使用。

主要功能

  1. 01分析 trace、span 或 session
  2. 02在服务端和本地记录笔记
  3. 03依据观察归纳注释
  4. 04统计标签以规划评估

适用场景

  1. 01查找抽样 trace 中的反复故障
  2. 02分析跨 session 的上下文丢失
  3. 03将观察到的问题转为评估目标

MCP 相关

该技能说明如何使用 Phoenix CLI(`px`)或 GraphQL 读取 trace 并写入笔记和注释。它本身不是 MCP 服务。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/arize-ai/phoenix --skill phoenix-error-analysis

安装后在 Claude Code 中输入/phoenix-error-analysis确认已加载。

详细介绍

错误分析

分两个阶段分析采样的追踪记录、跨度或会话。开放式编码会逐一阅读采样实体,并简短、具体地记录出了什么问题。主轴编码会将这些记录归纳为少数几个范围明确的注释,每条注释都使用一组精简标签,评判实体的一个维度;再统计各标签的数量,为评估设计和修复优先级排序提供依据。开放式编码始终先进行:如果维度和标签没有以记录为依据,就是自上而下凭空设定,而本工作流正是为了避免这种问题。主轴编码也可以从任意一组已有的开放式观察开始。

记录下来的笔记才是交付成果。 开放式编码结束时,应为每个有问题的实体编写一条笔记并写入服务端,同时镜像保存到本地 sidecar;只在聊天中总结并不算完成。阅读追踪记录并汇报观察结果,却不记录任何内容,只是只读诊断,正是本工作流旨在取代的自上而下捷径。像“找出所有问题”“哪里出了问题”或“这里有错误吗”这样针对整个项目的问题,意味着要执行第 1 阶段:选择标识符,选择分析单位,发现问题时逐一记录笔记,然后再总结。项目目前还没有任何注释名称,是正常的起始状态,不应成为暂缓记录的理由。

选择分析单位

记录之前,有意选择 追踪记录、跨度或会话 —— 这个选择决定了下面每条笔记和注释针对的对象,默认选错会导致中途更改代价高昂。分析单位应是你正在调查的故障模式实际存在的层级:

  • 追踪记录 — 一次输入 → 一次输出。分类器、单轮摘要器、无状态的工具调用 Agent、单次查询 RAG。
  • 跨度 — 追踪记录中的一项操作。可独立识别的机械故障(异常、工具错误响应、格式错误的输出),或一眼就能归因于某个组件的故障。
  • 会话 — 共享 session.id 的一系列追踪记录。适用于多轮 Agent,其中故障是一种轨迹——跨轮次丢失上下文、目标偏移、忘记偏好——只有在多个追踪记录之间才会显现。

需要查看三个信号:

  1. 用户的描述方式。 “对话”“Agent 忘记了”“偏移”“跨多个轮次” → 会话。“这条追踪记录”“输出错误” → 追踪记录。“异常”“格式错误”“检索失败” → 跨度。

  2. 会话关联情况。 会话 ID 位于根跨度的 session.id 属性中(而非追踪记录的顶层字段);"" 表示缺失。列出最近约 200 条追踪记录,计算其中有多少条带有非空会话 ID、出现了多少个不同的会话 ID,以及每个会话包含的追踪记录数量的中位数。没有会话 ID 或中位数为 1 → 追踪记录或跨度级别;中位数为 2+ → 会话级别可能是合适的选择。

  3. 轮次结构。 打开一条近期追踪记录的根跨度 input.value(内容可能很大——请筛选,而不是从头到尾阅读)。一条用户消息 → 单轮交互;消息数组([{role: user}, {role: assistant}, ...])→ 属于会话级别的对话轮次。

记录任何笔记之前,明确写出所选分析单位。如果数据表明需要调整,单位可以改变——如果追踪记录级笔记反复出现“Agent 总是记不住之前的轮次”,那么下一批应转为会话级分析。分析单位是一个起始假设,而非硬性约定。

编码标注标识符(先选择这个)

此工作流生成的每个产物——开放编码笔记、轴心编码标签和本地附属文件——都会标记同一个编码标注标识符,以便将本次运行作为一个整体进行查询和撤销。记录前,请先选择一个描述性且唯一的值,例如 coding-run:chatbot-context-loss-2026-05-06。(coding-run: 前缀是一种命名约定;该值不是 Phoenix 会话 ID。)

每次向服务端写入时,都要显式传递此标识符。为方便阅读,可以使用 shell 变量,但不要依赖 shell 继承——许多 Agent 执行环境会在全新的子 shell 中启动每条命令。

本地附属文件位于 .px/coding/<sanitized-identifier>.jsonl(开放编码笔记)和 .px/coding/<sanitized-identifier>-axial.jsonl(轴心标签),路径相对于当前工作目录(CWD);清理时,会将 [a-zA-Z0-9_-] 以外的每个字符替换为 -。唯一性检查应当在本地检查文件,而不是查询服务端:

CODING_ANNOTATION_IDENTIFIER="coding-run:chatbot-context-loss-2026-05-06"
SLUG=$(echo -n "$CODING_ANNOTATION_IDENTIFIER" | sed 's/[^a-zA-Z0-9_-]/-/g')
NOTES_SIDECAR=".px/coding/${SLUG}.jsonl"
AXIAL_SIDECAR=".px/coding/${SLUG}-axial.jsonl"
test ! -f "$NOTES_SIDECAR" || { echo "Sidecar already exists at $NOTES_SIDECAR — pick a new identifier or delete the file"; exit 1; }
mkdir -p .px/coding

如果 $NOTES_SIDECAR 已存在,请添加一个消歧后缀(例如 -v2、-dustin 等),然后重新检查。

恢复运行——两个阶段可以在彼此独立的调用中运行。如果轴心编码在新的 shell 中开始,请将标识符设为开放编码期间选定的同一个值(可从收尾 UI 的 URL 中恢复,或通过列出 .px/coding/*.jsonl 找到),跳过唯一性检查,并据此重新推导附属文件路径。切勿在运行过程中创建新的标识符。

写入位置

以下步骤描述的是操作,而不是命令。可通过两种方式执行;请使用执行环境提供的方式。phoenix-cli 技能介绍 px 标志;phoenix-graphql 技能的参考文档(project-spans-traces.md、sessions.md、annotations.md)介绍 GraphQL 字段和 mutation 输入。此技能不附带自己的参考文件。

操作Phoenix CLI(px)GraphQL(phoenix-gql 或任何客户端)
采样、扩展、深入查看px trace list、px trace get <trace-id>、px span get <span-id>、px session get <session-id>Project.spans(filterCondition: "parent_span is None")、getTraceByOtelId、getSpanByOtelId、对会话使用 node(id:)
检查现有笔记在 get/list 中使用 --include-notesSpan.spanNotes;当 name == "note" 时,使用 Trace.traceAnnotations / ProjectSession.sessionAnnotations
写入笔记px trace add-note <trace-id> --text "..." --identifier "$CODING_ANNOTATION_IDENTIFIER"(也可使用 px span add-note、px session add-note)createTraceNotes、createSpanNotes、createProjectSessionNotes——每个都接受 { note, annotatorKind: LLM, source: API, identifier },以及通过 OTel ID({ otelId })或节点 ID({ id })指定的实体引用
写入轴心标注px trace annotate <trace-id> --name <annotation-name> --label <label> --identifier "$CODING_ANNOTATION_IDENTIFIER"(也可使用 span、session)createTraceAnnotations、createSpanAnnotations、createProjectSessionAnnotations——仅接受 Phoenix 节点 ID,另需 name、label、explanation、annotatorKind: LLM、source: API、metadata: {}、identifier
标注配置px annotation-config list、create、update <identifier>createAnnotationConfig、updateAnnotationConfig、addAnnotationConfigToProject
丢弃本次运行px trace-annotations delete --identifier "$CODING_ANNOTATION_IDENTIFIER" --all -y(也可使用 span-annotations、session-annotations)不支持按标识符筛选批量删除:收集本次运行的标注节点 ID,然后调用 deleteTraceAnnotations、deleteSpanAnnotations、deleteProjectSessionAnnotations

笔记和标注写入会同步执行,并按 (entity, name, identifier) 执行 upsert。如果执行环境要求先审批 mutation,请说明每条命令会进行的写入,并将每个 mutation 分别放在独立调用中。

阶段 1:开放编码

自由撰写笔记。写下你观察到的内容,而不是你认为它属于哪个类别——分类属于阶段 2。

流程
  1. 选择一个编码标注标识符,并确认辅助文件尚不存在
  2. 选择单位——trace、span 或 session
  3. 检查——获取所选单位的一个实体,并查看其输入、输出、异常、工具调用、检索到的上下文,以及(在 session 层级)跨子 trace 的轨迹
  4. 记录——用一个具体句子描述出了什么问题;如果结果正确,则跳过
  5. 写入——将备注写入服务,并追加一行辅助文件记录
  6. 迭代,直到样本耗尽或达到饱和
检查
  • Trace 单位——将一个 trace 的输入 → 工具调用 → 检索到的上下文 → 输出作为一个完整故事来阅读。
  • Span 单位——阅读一个操作的输入/输出及其周围的 spans,以了解上下文。
  • Session 单位——按顺序阅读 trace 序列;跨 trace 的轨迹才是数据,而不是任何单个 trace。

**不要按 span 状态 ERROR 采样。**只有当插桩器捕获到抛出的异常时,OTel 的 status_code 才会变为 ERROR。幻觉、语气不当、检索遗漏和工具选择错误都会正常完成,状态为 OK 或 UNSET——按错误状态筛选会排除本工作流旨在发现的问题样本。

关注点

这是一份检查清单,而非分类体系——类别稍后再整理。记录最先出错的地方;只有当下游症状有独立原因时,才为其单独记录备注。

  • 明确错误——工具、LLM 或检索器 spans 中的异常或错误消息
  • 成本和延迟——异常高的 token 数量或耗时较长的 spans
  • 检索质量——无关、缺失或评分较低的片段
  • 响应质量——幻觉、事实错误、语气不当、不恰当的拒绝
  • 工具使用——工具错误、调用格式不正确、结果处理不当
  • 轨迹——循环、绕路、任务未完成;在 session 中,还包括上下文丢失或目标偏移
  • 插桩缺口——缺失的 spans 或属性;盲点本身就是一个发现

将现有评估和标注作为众多输入之一。阅读内容,而非状态:成功状态可能掩盖属性中的错误,而异常也可能是预期行为。

无论使用什么工具,需要执行的获取操作都是:采样近期 traces(trace id、根 span 名称、状态、根 span 的 input.value / output.value);展开一个 trace,按开始时间排序查看其 spans;当单位为 span 时,按 id 深入查看单个 span;并检查你即将审阅的实体是否已有备注——备注以保留名称 note 作为标注存储在服务端。和往常一样,请注意数据可能很冗长,谨慎处理,避免撑爆上下文。

记录备注

对于你检查的每个 session、trace 或 span,都要向服务提交备注(参见写入位置),并保存一份本地副本。每次写入都应包含备注文本,并显式设置 identifier 为编码标注标识符。逐个实体边检查边记录——不要把备注汇总成最后的总结。

每次成功向服务写入备注后,都要在 $NOTES_SIDECAR 中追加一行 JSONL——轴向编码会读取辅助文件,不需要与服务端往返:

{"entity_kind":"trace","entity_id":"<trace-id>","note":"<text>","identifier":"<original identifier value, unsanitized>","ts":"<ISO-8601 UTC>"}
优质备注的特点
较弱的备注优质备注
“答案错误”“说商店下午 6 点关门,但政策规定是晚上 9 点”
“检索有问题”“问题问的是退货,却检索到了关于配送的文档”
饱和

当观察不再带来新发现时停止:最近 10–15 个实体重复出现你已经见过的问题、你发现自己在改述之前的备注,或跳过的数量超过备注数量。收集期间不要急于归类。无需为每个 trace 都添加标注——给正确的 trace 添加标注会稀释信号。

阶段之间:继续还是给出选择

达到饱和后,决定是否继续进行主轴编码:

  • 如果用户要求类别、分类体系、计数、评测目标或修复优先级,或将任务整体描述为错误分析——直接继续进入阶段 2。
  • 否则,在此停止:总结笔记中发现的内容,然后提出继续进行主轴编码,并等待用户回答,而不是直接开始。如果本次运行到此结束,请按照收尾执行。

阶段 2:主轴编码

将开放编码笔记整理为一组精简的标注,每个标注判断实体的一个维度:提出一个关于其行为的问题,并用几个标签作答。实体在笔记涉及的每个维度上各有一个标注,因此一条轨迹最终可能有两到三个标注。输出应由多个范围明确、各含两个至四个标签的标注组成,而不是一个宽泛的 <app>_failure_mode 标注,其标签涵盖互不相关的问题。宽泛的标注无法按维度聚合,无法用于只评判单一事项的评测,还会迫使同时以两种方式失败的实体只能选择一个标签。

流程
  1. 设置编码标注标识符,将其设为开放编码值,并推导附属文件路径(参见恢复运行)
  2. 收集——读取本次运行在 $NOTES_SIDECAR 中的笔记。文件不存在意味着当前工作目录中的此标识符尚未进行开放编码——停止并先运行阶段 1。每个实体以最新的 ts 为准;修复或删除格式错误的行,但不要修改其相邻行。若要纳入其他审阅者或早前运行的笔记,请从服务器获取(名称为保留名称 note 的标注)。
  3. 分组——将回答实体同一问题的笔记归为一组(“是否选对了工具?”“答案是否以检索到的内容为依据?”“是否完成了要求它执行的任务?”)。每组都是一个候选维度。根据笔记文本自下而上进行;不要从维度列表开始。
  4. 定义——为每个维度命名,体现其提出的问题(tool_selection、answer_faithfulness),并选择其标签:笔记描述的失败结果,以及一个通过时的取值,以便评测之后也能对通过的实体应用相同的标注。一个维度内的标签必须互斥;如果两个标签可能同时适用于同一实体,它们就属于不同维度。
  5. 归属——确定每个维度所属的层级(参见选择层级)
  6. 配置——为每个维度注册一个分类标注配置(参见标注配置)
  7. 记录——对每个有笔记的实体,为笔记涉及的每个维度写入一个标注,并为每个标注追加一行附属文件记录
  8. 量化——根据 $AXIAL_SIDECAR 按维度统计标签数量:按 (annotation_name, axial_label) 对当前记录分组,每个 (entity, annotation_name) 以最新的 ts 为准。无需查询服务器——附属文件恰好包含本次运行写入的标签。
划分维度
  • 每条注释只对应一个问题。 名称是问题(tool_selection),标签是答案(hallucinated_tool)。同时判断工具选择和运行环境稳定性,就意味着需要两条注释。
  • 标签集要精简。 通常有 2 到 4 个标签。标签超过 5 个左右的注释,几乎总是包含了多个维度;应将其拆分。
  • 标签表示结果,而非组成部分。 retrieval 或 tool_use 是维度名称,不是标签。如果某个标签读起来像系统某个部分的名词,就应将其提升为独立注释,并用标签描述其结果。
  • 一次运行会产生少数几个维度。 通常为 3 到 6 个。每条记录都应至少映射到一个维度中的某个标签;如果某条记录无处可归,就说明需要新增维度,或现有维度的定义过于狭窄。
  • 将 Agent 的判断与基础设施分开。 提供商错误、持久化失败和 UI 崩溃都是实际问题,但并非 Agent 的行为;应为它们设置单独的维度,而不是与模型错误并列。
选择层级

默认情况下,轴心编码沿用开放编码的单位,但注释所处的层级可以不同于为其提供依据的记录,且方向不限:当检索逐渐显现为持续的症结时,记录级的“用户询问退货,却回答了配送”记录可以生成检索跨度级的 retrieval_relevance 标签;当模式变成“无法跨轮次跟踪上下文”时,记录级的单轮混淆记录可以生成会话级的 context_tracking 标签;会话级的偏移记录也可以归因于某个特定轮次,并生成记录级标签。每条注释都应设在其维度实际涉及的层级——同一次运行中的不同维度可以处于不同层级。

对你记录过笔记的实体进行分类:$NOTES_SIDECAR 是候选项的来源,只有在阅读笔记文本和周围上下文后,才能写入标签。不要按错误状态选择实体——这只会捕获已引发的异常,排除大多数失败模式(幻觉、语气不当、检索未命中)。

注释配置

维度稳定后——在分组和定义之后,而非每次写入时——请在记录前为每个维度注册一个分类注释配置:

  1. 根据维度为每个配置命名。 配置名称与注释名称一致(tool_selection、answer_faithfulness);Phoenix 通过这种方式将两者关联起来。不要使用通用名称 category,也不要创建一个配置来捆绑所有维度。
  2. 检查项目中现有的注释配置。 如果已有配置用于判断相同维度,就沿用其名称和标签,仅当笔记中出现了配置缺少的结果时才扩展其取值。不要用新名称创建近似重复项。
  3. 创建或更新每个配置,将该维度的最终标签设为其取值,并将配置关联到项目。

没有配置也可以正常写入注释,但配置能让维度在 Phoenix UI 中成为一等对象:人工标注者可以从下拉菜单中选择标签,而不是输入自由文本;后续运行也会沿用统一的词汇,不会逐渐偏离。如果记录过程中出现新标签,请先将其添加到该维度的配置中,再使用它进行标注。

什么样的标注才算好

只有可筛选、可汇总、可审计的标注,日后才有用。因此:

  1. 命名维度;将结果放在标签中。 tool_selection = hallucinated_tool,而不是 hallucinated_tool = true,也不是 failure_mode = hallucinated_tool。
  2. 每条标注只对应一个维度。 同时评判相关性和忠实度,就需要两条标注;同时评判工具选择和任务完成情况也是如此。
  3. 标注负有责任的实体。 检索问题标注检索器片段,输出问题标注 LLM 片段;只有端到端评判才标注轨迹或会话。
  4. 标注第一个故障。 只有下游影响本身也是独立问题时,才单独标注。
  5. 优先使用标签,而非分数。 少量标签更容易一致地应用和汇总;仅当配置定义了评分尺度时才使用分数。
  6. 在说明中引用证据。 写“用户询问取消问题;检索到入门文档”,而不是“检索效果很差”。
  7. 不同运行之间保持名称和标签稳定。 不要添加 _v2 后缀,也不要因为名称合适就借用其他项目的配置。
记录标签

对于每个实体,以及其备注涉及的每个维度,写入一条标注,包含:

  • name:该维度的标注名称,例如 retrieval_relevance
  • label:结果,例如 off_topic
  • explanation(可选):例如“询问退货;检索到配送文档”
  • annotator kind:自己的判断使用 LLM,只有用户提供的判断才使用 HUMAN
  • identifier:编码标注标识符

服务端也接受可选分数。写入时会按 (entity_id, name, identifier) 执行 upsert,因此同一实体可以在同一个标识符下携带多条名称不同的标注。服务端的默认写入模式会异步排队;如果工具提供同步模式,优先使用,以便继续操作前完成写入。

每次写入后,向 $AXIAL_SIDECAR 追加一行 JSONL——每条标注占一行,因此在两个维度上标注的实体会对应两行:

{"entity_kind":"trace","entity_id":"<trace-id>","annotation_name":"<annotation-name>","axial_label":"<label>","explanation":"<optional explanation>","identifier":"<original identifier value, unsanitized>","ts":"<ISO-8601 UTC>"}

entity_kind("trace"、"span" 或 "session")对应写入标注的层级;identifier 是未经处理的原始值——只有文件名中才使用清理后的形式。要修订标签,请替换该行或追加更新的一行:对于 (entity_kind, entity_id, annotation_name),最新的 ts 即为当前值,与服务端的 upsert 行为一致。

维度示例

每个键都是一个标注名称;其值是该标注的标签。一次运行会生成其中几个,而不是一棵树。

tool_selection: [correct, wrong_tool, hallucinated_tool, unnecessary_call]
retrieval_relevance: [relevant, off_topic, nothing_retrieved]
answer_faithfulness: [grounded, invented_fact, invented_citation]
context_tracking: [kept, ignored_preference, goal_drift]
task_completion: [completed, partial, abandoned]
什么样的维度才算好
  • 只提出一个问题,且答案互斥——如果两个标签可以同时适用,那就是两个维度
  • 以所评判的行为命名,结果放在标签中(tool_selection = wrong_tool,而不是 bad_output)
  • 与修复措施相关——如果无法针对失败标签提出补救措施,该维度就过于含糊
  • 以数据为依据——从实际备注文本中归纳得出,而非预先假定

收尾

无论运行在阶段 1 还是阶段 2 后结束,都适用。

如果运行了轴向编码,请列出你编写的各个维度,并逐一说明你是复用了、扩展了还是新建了其标注配置——具体采用哪种评判标准由用户决定。将这些维度链接到项目的配置页面 <endpoint>/projects/<project-node-id>/config;该页面会在项目标注旁列出此项目的标注配置,而不是会混入其他所有项目配置的实例级标注设置页面。

向用户分享 Phoenix UI 链接:每个级别(span、trace、session)各一个,且链接所指内容确实包含本次运行的笔记或标注,并已筛选为本次运行的工作内容。没有内容的级别则跳过。每个标签页会从自己的搜索参数读取筛选条件;无法识别或拼写错误的参数会被静默丢弃,表格因此不带筛选条件。

按编码标注标识符筛选——标注访问器除了 .label、.score 和 .explanation,还会公开 .identifier——这样每个链接就只会显示本次运行的笔记和轴向标注。在该级别编写的每个维度都添加一个 <annotation-name> 子句,并用 or 连接;如果未运行轴向编码,则省略这些子句:

标注级别标签页和搜索参数筛选表达式
span/spans?spanFilterCondition=annotations['note'].identifier == '<id>' or annotations['<annotation-name>'].identifier == '<id>'
trace/traces?traceFilterCondition=trace_annotations['note'].identifier == '<id>' or trace_annotations['<annotation-name>'].identifier == '<id>'
session/sessions?sessionFilterCondition=session_annotations['note'].identifier == '<id>' or session_annotations['<annotation-name>'].identifier == '<id>'

将每个表达式进行 URL 编码后,填入其标签页对应的参数:

<endpoint>/projects/<project-node-id>/<tab>?<param>=<encoded-expression>

引用单条发现时,除分享经过筛选的表格外,还要直接链接到已标注实体。对于 trace,请使用 <endpoint>/projects/<project-node-id>/traces/<otel-trace-id>。对于 span,请使用该 trace 链接并添加 selectedSpanNodeId=<span-node-id>,以便选中并打开已标注的 span。请从获取的数据中解析 span 的 Relay 节点 ID 和其所属的 OpenTelemetry trace ID;选择参数不接受 OpenTelemetry span ID。保留现有搜索参数(例如 timeRangeKey=30d),并对新增值进行 URL 编码。

丢弃本次运行——只有获得用户明确确认后才能执行,因为此操作具有破坏性。对于 trace、span 和 session,分别删除按编码标注标识符筛选的项目标注;由于所有维度共用此标识符,这会一次性移除每个维度的所有标签。服务器要求提供明确的全量删除标记(或时间范围)以授权执行清理;标识符筛选条件只能缩小范围,本身不能授权删除。然后移除 $NOTES_SIDECAR 和 $AXIAL_SIDECAR。每种类型的删除都会一并移除笔记和轴向标注,因为它们共用底层标注表。

评论

支持 Markdown