
可观测性AI 评测LLMOpsAI 应用人工智能【免费下载链接】phoenixAI Observability Evaluation项目地址https://gitcode.com/gh_mirrors/phoenix13/phoenix点击查看免费下载Phoenix 的 GraphQL API 以 Project项目、Span跨度、Trace追踪三类核心对象为骨架支撑从追踪数据检索到聚合指标统计的全部分析场景。这篇指南以官方参考文档 project-spans-traces.md 为骨架结合 Project.py、Span.py、Trace.py 等源码实现完整讲解三个对象的字段、参数与语义陷阱并给出可直接运行的 GraphQL 查询。读完你就能自己写出按时间窗口检索根 Span按 trace 级条件过滤 span 列表批量获取 p50/p99 延迟分位数等高质量查询。先建立整体认知三个对象分别是什么对象本质在查询中的角色Project一组 span/trace 的逻辑集合如一个应用、一个环境查询的入口拿到它之后才能遍历 span、读取聚合指标Span一次操作的执行片段一次 LLM 调用、一次工具调用、一个检索步骤数据检索的最小粒度Trace一条完整调用链由多个 span 组成端到端的单位通常一个 trace 对应一次请求/一轮对话三者之间的关键导航关系是Project.spans(...)返回 span 连接connectionSpan.trace { traceId }从 span 反查它所属的 traceProject.trace(traceId: ID!)按 OTel 十六进制 trace id 直接定位 traceTrace.rootSpan拿到该 trace 的代表性根 span。在 Phoenix 的 GraphQL schema 中这三类对象全部实现自 Relay 的Node接口都有全局 id也都可以通过顶层node(id: ID!)查询见 SKILL.md。入口如何定位到一个 Project对任意实体Phoenix 都提供顶层node(id: ID!)全局查找配合内联片段inline fragment解析类型query GetProject($id: ID!) { node(id: $id) { ... on Project { name } } }如果你只有项目名而没有 id则使用顶层getProjectByName(name: String!)帮助函数。需要注意getSpanByOtelId(spanId: String!)、getTraceByOtelId(traceId: String!)是仅有的几个 by-id 帮助函数之一而不存在getDatasetByName之类的对应物——遇到这种情况一律回到node(id:)或连接上的filter输入。在继续之前明确两类 id 的区别这是最容易混淆的点Relay 全局 id任何 node 上的id字段是TypeName:rowId的 base64 编码用于node(id:)OpenTelemetry 十六进制 id来自Span.spanId与Trace.traceId用于对外部 OTel 生态的对照。两者绝不能混用。Project 上的 span 连接理解没有 traces 连接的设计Project.spans是检索 span 数据的主通道其完整签名为Project.spans(first, after, timeRange, sort: SpanSort, filterCondition: String, traceFilterCondition: String) → SpanConnection参数说明如下参数类型作用注意事项firstInt单页条数建议 10–50配合after分页afterCursorString分页游标取pageInfo.endCursor回传timeRangeTimeRange时间窗口过滤{ start: DateTime, end: DateTime }ISO 8601 字符串end为开区间不含两个字段都可省略sortSpanSort排序{ col: SpanColumn, dir: SortDir }常用SpanColumn取值见下文filterConditionStringspan 过滤表达式只保留匹配的单个 spantraceFilterConditionStringtrace 过滤表达式保留匹配 trace 的每一个 span关键设计Project 上没有 traces 连接参考文档明确指出Project没有traces连接也没有 root-span 参数。要列出 trace正确做法是通过 span 连接加根 span 子句。这背后是两个语义不同的子句务必分清filterCondition: parent_span is None保留所有顶层 span包括那些父 span 从未被采集到的孤儿 span。这正是 UI 的 traces 表实际执行的查询通常每个 trace 一条参考文档原文what the UIs traces table runs。filterCondition: parent_id is None只保留parent_id为空的 span即显式无父的 span不含孤儿。一个 trace 通常对应一个根 span但当 trace 因采样、采集丢失等原因发生碎片化时同一个 trace 可能出现多个根 span。如果需要匹配 trace 的根将 root-span 子句与traceFilterCondition配对使用即可spans(filterCondition: parent_span is None, traceFilterCondition: ...)会保留每个匹配 trace 的根 span每个 trace 一行。源码视角spans 连接底层如何工作在 Project.py 的spans解析器中可以看到完整的实现链路从models.Span出发join(models.Trace)用project_rowid圈定项目范围timeRange直接作用到Span.start_time上start start_time end若给了traceFilterCondition先通过get_filtered_trace_rowids_subquery(...)来自 trace_filters 模块计算出匹配 trace 的 rowid 集合再用Span.trace_rowid.in_(...)圈定这些 trace 下的所有 span若给了filterCondition则构造SpanFilter(conditionfilter_condition)并作用于语句该过滤器来自 phoenix.trace.dsl 中的root_span_scope等模块排序通过SpanSortConfig翻译成 ORM 表达式游标分页基于(sort_column, id)元组比较实现每次查询first 1条overfetch by one借此判断是否存在下一页。也就是说filterCondition与traceFilterCondition在 SQL 层面是先圈 trace、再筛 span的两段式组合匹配 trace 内匹配 span。这也解释了文档中两者可以组合使用的语义。常用 SpanColumn 取值SpanSort输入形如{ col: latencyMs, dir: desc }。参考 SKILL.md 中的约定常用的SpanColumn有startTime、latencyMs、tokenCountTotal、cumulativeTokenCountTotal、tokenCostTotal。Project 聚合字段不看明细也能回答整体怎么样Project上暴露了一组聚合字段多数同时接受timeRange与filterCondition部分还接受sessionFilterCondition字段返回值含义traceCountInttrace 数量recordCountIntspan 数量tokenCountTotalFloat总 token 数tokenCountPromptFloatprompt输入token 数tokenCountCompletionFloatcompletion输出token 数costSummarySpanCostSummary成本汇总含prompt/completion/total三组CostBreakdown每组有tokens、costlatencyMsQuantile(probability: Float!)Floattrace延迟分位数如 p50/p99spanLatencyMsQuantile(probability: Float!)Floatspan延迟分位数源码佐证Project.pyrecord_count与trace_count走同一个record_countsdataloaderkey 上区分span/traceL414-L459latency_ms_quantile与span_latency_ms_quantile也走同一个 dataloader用trace/span区分统计粒度L533-L587cost_summary返回的SpanCostSummary把 prompt/completion/total 拆成三个CostBreakdownL494-L531。两个使用要点第一filterCondition与sessionFilterCondition互斥。源码中record_count、trace_count、cost_summary、两个分位数字段在两者同时给出时会直接抛出BadRequest例如 Project.py。一次查询只能选一种过滤口径。第二聚合可以批量别名化。独立聚合彼此无依赖应在一次往返中用别名aliases拿全例如query Overview($name: String!, $timeRange: TimeRange) { getProjectByName(name: $name) { traceCount(timeRange: $timeRange) recordCount(timeRange: $timeRange) p50: latencyMsQuantile(probability: 0.5, timeRange: $timeRange) p99: latencyMsQuantile(probability: 0.99, timeRange: $timeRange) errSpanCount: recordCount(timeRange: $timeRange, filterCondition: status_code ERROR) cost: costSummary(timeRange: $timeRange) { total { tokens cost } } } }发现类字段与校验查询之前先确认有什么聚合和过滤都依赖项目里实际存在的数据因此Project提供了四类发现字段参考文档明确建议查询某个 eval/annotation 之前先用它们确认其存在spanAnnotationNames、traceAnnotationNames列出该项目中已存在的 span/trace 级 annotation 名称源码中分别对SpanAnnotation、TraceAnnotation表按 name 做 distinct 聚合见 Project.pyspanAnnotationSummaryspan annotation 汇总documentEvaluationNames文档级 eval 名称。此外还有校验类字段validateSpanFilterCondition(condition: String!)→{ isValid errorMessage }在不执行查询的情况下校验一个 span 过滤串的合法性对应地validateTraceFilterCondition、validateSessionFilterCondition校验另外两种语言的表达式traceFilterVocabulary、sessionFilterVocabulary列出可绑定的过滤词表{ name type category description iterableName }。一个重要的排错提醒来自 filter-expressions.mdspan 过滤串中未知名称不会报错而是被当作属性路径attribute path解析最终匹配不到任何行。一个返回空结果的过滤条件很可能是拼写错误而非真的没有数据——先用validateSpanFilterCondition或对照词表检查拼写再下无数据的结论。Span 对象关键字段与两个易踩的坑参考文档列出的 Span 关键字段字段类型/语义说明spanIdIDOTel 十六进制 span idnameStringspan 名称spanKindenumCHAIN/LLM/RETRIEVER/EMBEDDING/TOOL/AGENT/RERANKER/GUARDRAIL/EVALUATOR/PROMPT/UNKNOWNstatusCodeenumOK/ERROR/UNSETstartTimeDateTime开始时间latencyMsFloat延迟毫秒cumulativeTokenCountTotalFloat本 span 及其所有后代的累计 token 数源码中对应cumulative_llm_token_count_total见 Span.pyinput/output{ truncatedValue value }输入/输出载荷spanAnnotations列表{ name label score }trace { traceId }Trace所属 trace坑一Span 没有 traceId 字段Span上不存在traceId字段。要读 OTel trace id必须通过嵌套的trace { traceId }。实现上Span.trace是一个 lazy 字段通过trace_rowid加载对应Trace见 Span.py。这也是 SKILL.md 中Never mix global IDs with OTel IDs警告的一部分。坑二input/output 载荷可能巨大span 的输入输出可以是完整的 prompt 文本、工具返回等体积可观。参考文档与 SKILL 一致地建议普查阶段只请求input { truncatedValue }返回截断后的短文本SKILL 中说明约为前 100 字符只有当你要精读某个 span时才请求input { value }拿完整载荷。从源码看input/output字段的解析器在无db_record时只读取 DB 中的input_value_first_101_chars列并调用truncate_value(...)构造truncatedValue完整值value则在需要时才按行读取见 Span.py——服务端本就为截断路径做了优化客户端配合使用能显著降低响应体积。Trace 对象一条调用链的完整视图参考文档列出的 Trace 关键字段字段类型/语义说明traceIdIDOTel 十六进制 trace idlatencyMsFloat整条 trace 的延迟numSpansIntspan 总数源码走num_spans_per_tracedataloader见 Trace.pyrootSpanSpan代表性根 spanspans(first, after, filterCondition)SpanConnectiontrace 内 span 列表支持 span 过滤projectSessionIdGlobalID / null所属会话的全局 id无会话时为 null见 Trace.pyrootSpan 到底是什么参考文档的定义是最早出现的、没有父 span 的 span如果它的父 span 从未被采集到也算在内——一句话总结就是用于生成一行式 turn/trace 摘要的代表性根 span。源码实现印证了这个定义。trace_aggregates.py 中的representative_root_span_by_trace函数根谓词为parent_id is None当orphan_span_as_root_spanTrue默认时额外并入父 span 不存在的孤儿 span对每个 span 检查同 trace 内是否有parent_id指向它、且该父 span 未被采集对每个 trace 按(start_time asc, id desc)排序后取row_number() 1——即最早的那个根 span。因此Trace.rootSpan与Project.spans(filterCondition: parent_span is None)的语义是自洽的两者选出的根是同一套规则。Trace.spans 的默认顺序Trace.spans连接默认按 span 的数据库 id降序最新插入在前源码注释解释了原因root span tends to show up later in the ingestion process根 span 往往在采集流程后期才写入见 Trace.py。它同样接受first/after分页参数与filterConditionspan 过滤表达式但不接受traceFilterCondition——因为它本身已经限定在单个 trace 内。组合实战三个可直接运行的查询参考文档提供了三个典型的可运行级示例全部使用node(id:) 内联片段进入 Project值得原样保留并逐条拆解。1. 最近根 span通常每个 trace 一条最慢优先query RecentTraces($id: ID!, $first: Int 20) { node(id: $id) { ... on Project { spans(first: $first, filterCondition: parent_span is None, sort: { col: latencyMs, dir: desc }) { edges { node { spanId name latencyMs statusCode startTime cumulativeTokenCountTotal trace { traceId numSpans } } } pageInfo { hasNextPage endCursor } } } } }要点拆解parent_span is None保留顶层 span含孤儿通常一 trace 一行sort: { col: latencyMs, dir: desc }让最慢的排最前是定位慢 trace的典型姿势trace { traceId numSpans }通过嵌套字段同时拿到 trace id 与 span 数——注意这里不能在node上直接请求traceIdSpan 没有该字段带上pageInfo { hasNextPage endCursor }为翻页留好游标。2. 时间窗口内、出错 trace 的根 spanfilterCondition 与 traceFilterCondition 组合query ErroredTraces($id: ID!, $timeRange: TimeRange) { node(id: $id) { ... on Project { spans( first: 20 timeRange: $timeRange filterCondition: parent_span is None traceFilterCondition: error_count 0 ) { edges { node { name latencyMs input { truncatedValue } trace { traceId } } } } } } }这个查询把三种能力叠加在同一个连接上语义精确到出错的 trace 的根 spantimeRange圈定时间窗口end开区间traceFilterCondition: error_count 0先筛出含错误的 traceerror_count是trace 过滤语言的词汇trace 内status_code ERROR的 span 计数filterCondition: parent_span is None再从这些 trace 中只取根 span保证每 trace 一行。3. 过滤后的 error LLM spanquery ErrorSpans($id: ID!) { node(id: $id) { ... on Project { spans(first: 20, filterCondition: span_kind LLM and status_code ERROR) { edges { node { spanId name statusCode trace { traceId } } } } } } }这是纯 span 过滤的示例span_kind LLM与status_code ERROR都是span 过滤语言的词汇。字符串字面量必须用引号包裹LLM状态码字面量会为你自动转大写——写成status_code ERROR即可。分页循环的通用写法三例都返回 Relay 连接分页模式统一读pageInfo.hasNextPage为真则把endCursor作为下一次请求的after传入直到hasNextPage false。游标是不透明字符串不要解析其内容。过滤表达式span 与 trace 两种语言的分工参考文档将两种过滤语言指向了专门的参考文件 filter-expressions.md这里提炼与其直接相关的要点参数名决定语言词汇表互不混用语言参数保留范围接受位置Span 过滤filterCondition单个 spanProject.spans、Trace.spans、项目聚合recordCount/tokenCountTotal/costSummary/latencyMsQuantile等Trace 过滤traceFilterCondition匹配 trace 的每个 spanProject.spansSession 过滤sessionFilterCondition会话Project.sessions、部分聚合语法三种语言共享同一套 Python 布尔表达式语法Phoenix 将其编译为 SQL包括!、is None/is not None、in/not in、and/or/not与括号、float(x)/int(x)/str(x)强转、对声明集合的any/all/len/sum/max/min推导。字面量方面字符串必须加引号、布尔是True/False、缺失值是None、datetime 必须是带时区偏移的 ISO 8601。几个与 Python 直觉不同的规则都是过滤表达式文档明确列出的缺失值会让一切比较失败包括!。没有metadata[tier]的 span 既不匹配 premium也不匹配! premium必须显式写出缺失分支metadata[tier] ! premium or metadata[tier] is Nonetext in field是大小写不敏感的子串判断与列表成员判断是精确匹配整个表达式必须是条件裸的True或裸字段名会被拒绝常见误写会编译通过但匹配为空span_kind LLM未加引号会被当作属性路径LLMerror_count 0是 trace 语言词汇用在 span 过滤里会被当作属性——span 级应写status_code ERROR。根 span 子句是 span 过滤中最高频的组件参考文档给出了明确的取舍表子句保留适用场景parent_id is None无 parent_id 的 span默认选择parent_span is None无 parent_id 的 span加上父 span 从未被采集的孤儿想要每一个顶层 span包括被丢弃父级的如何在实际代码/脚本中调用这些查询参考文档聚焦 schema 本身但结合 SKILL.md 的外部调用约定这些查询可以直接用于你自己的脚本与集成端点POST phoenix-endpoint/graphqlbody 为{ query: ..., variables: { ... } }其中phoenix-endpoint是PHOENIX_ENDPOINT环境变量指向的 Phoenix 基础 URL同路径 GET 可打开 GraphiQL IDE认证携带 Phoenix API key 作为 Bearer tokenAuthorization: Bearer API_KEYAPI key 在 Phoenix 设置中创建约定值一律通过查询变量query variables传入绝不字符串插值分页用pageInfo { hasNextPage endCursor }→ 将endCursor作为after回传注意 GraphQL schema 主要为 Phoenix UI 服务、版本间可能变动追求稳定程序化访问时优先考虑 REST API/v1/...与arize-phoenix-clientPython/arizeai/phoenix-clientTypeScript客户端。小结围绕Project 是入口、Span 是最小检索粒度、Trace 是端到端单位这条主线可以总结出几条写查询时的决策规则要整条 trace 的汇总用Trace的rootSpan、numSpans、latencyMs、projectSessionId直接Project.trace(traceId: ID!)按 OTel id 定位要按 trace 级条件筛 span在Project.spans上组合filterConditionspan 语言与traceFilterConditiontrace 语言前者保留匹配的 span后者保留匹配 trace 的每个 span要每 trace 一行的列表filterCondition: parent_span is None含孤儿或parent_id is None仅显式根要整体指标优先用 Project 聚合字段并批量别名化一次往返拿 p50/p99、token、cost动手查询前用validateSpanFilterCondition校验过滤串用spanAnnotationNames/traceAnnotationNames确认 annotation 是否存在。以上全部内容均可对照仓库源码验证schema 字段实现在 Project.py、Span.py、Trace.py根 span 语义实现在 trace_aggregates.py过滤语言完整规范在 filter-expressions.md。赞分享可观测性AI 评测LLMOpsAI 应用人工智能【免费下载链接】phoenixAI Observability Evaluation项目地址https://gitcode.com/gh_mirrors/phoenix13/phoenix点击查看免费下载相关推荐Phoenix TypeScript Session 追踪实战用 session.id 与 withSpan 聚合多轮对话 TracesPhoenix TypeScript Session 追踪实战用 session.id 与 withSpan 聚合多轮对话 Traces 多轮对话、Agent可观测性AI 评测LLMOpsAI 应用人工智能PostHog APM Span 聚合查询apm-spans-aggregate完全指南按服务与操作聚合 Trace Span 统计PostHog APM Span 聚合查询apm spans aggregate完全指南按服务与操作聚合 Trace Span 统计 apm spans数据分析后端前端数据可视化大数据Phoenix TypeScript 客户端arizeai/phoenix-client完全指南配置、Prompt 管理、Trace/Span 查询、数据集与实验评估Phoenix TypeScript 客户端arizeai/phoenix client完全指南配置、Prompt 管理、Trace/Span 查询、数可观测性AI 评测LLMOpsAI 应用人工智能上一篇PaddleOCR 与 PaddleX 协同使用指南产线配置导出、加载与版本对应关系下一篇3大核心功能实现OneNote笔记跨平台自由迁移开源笔记迁移工具技术解析与实践指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考