YAOTU INSIGHTS

Langfuse Score Analytics 架构指南:Provider + Hook + Smart Cards 模式与 ClickHouse 查询优化实战

Langfuse Score Analytics 架构指南:Provider + Hook + Smart Cards 模式与 ClickHouse 查询优化实战
Langfuse Score Analytics 架构指南Provider Hook Smart Cards 模式与 ClickHouse 查询优化实战【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuseScore Analytics 是 Langfuse 项目中用于分析 LLM 评分数据Score的专项分析看板支持单选单个分数与双选对比两种模式。本文以web/src/features/score-analytics/README.md为骨架结合仓库源码完整讲解其 Provider Hook Smart Cards 前端架构、tRPC 服务端查询链路、ClickHouse 自适应优化策略以及如何在此基础上扩展新卡片、新图表与新数据转换。读完本文你将掌握该模块从页面入口到 ClickHouse 聚合查询的完整调用链并能在自己的 Langfuse 二次开发中熟练运用其分层模式。架构设计原则Score Analytics 的前端采用经典的Provider Hook Smart Cards分层模式核心目标是在保证关注点分离的前提下实现最大程度的代码复用。其设计原则可归纳为五条Transform Once一次转换所有数据转换都收敛在useScoreAnalyticsQueryhook 内完成组件不做任何转换逻辑Single Source of Truth单一数据源ScoreAnalyticsProvider通过 React Context 对外暴露全部数据No Prop Drilling杜绝属性透传Smart Cards 直接通过useScoreAnalytics()hook 消费 Context避免逐层传 propsType-Safe类型安全全链路使用显式 TypeScript 接口Presentation vs Logic表现与逻辑分离图表组件是纯展示组件业务逻辑由卡片组件承担。这一设计与该模块的演进历史直接相关从源码注释可见转换函数最早内嵌在analytics.tsx、SingleScoreAnalytics、TwoScoreAnalytics中后来被抽取到独立的纯函数库lib/score-analytics-transformers.ts以消除重复并支持单元测试参见 score-analytics-transformers.ts 顶部注释。文件夹结构与职责划分模块根目录为web/src/features/score-analytics/各子目录职责如下web/src/features/score-analytics/ ├── server/ # tRPC 路由与 ClickHouse 查询构建 │ ├── scoreAnalyticsRouter.ts # tRPC router3 个 procedure │ ├── buildEstimateQuery.ts # 预评估查询1% 采样估算 │ ├── buildScoreComparisonQuery.ts # 主分析查询UNION ALL 聚合 │ └── queryHelpers.ts # 查询辅助函数 ├── components/ │ ├── cards/ # Smart Cards消费 Context │ │ ├── StatisticsCard.tsx │ │ ├── TimelineChartCard.tsx │ │ ├── DistributionNumericCard.tsx │ │ ├── DistributionCategoricalCard.tsx │ │ ├── DistributionBooleanCard.tsx │ │ └── HeatmapCard.tsx │ ├── charts/ # 纯展示图表组件 │ │ ├── ScoreDistribution*.tsx # 分布图Numeric/Boolean/Categorical │ │ ├── ScoreTimeSeries*.tsx # 时间序列图Numeric/Boolean/Categorical │ │ ├── Heatmap*.tsx # 热力图Heatmap/Cell/Legend/Skeleton │ │ ├── MetricCard.tsx # 指标展示组件 │ │ ├── ScoreCombobox.tsx # 分数选择器 │ │ └── ObjectTypeFilter.tsx # 对象类型过滤 │ ├── ScoreAnalyticsHeader.tsx # 头部控件选择器/过滤/日期 │ ├── ScoreAnalyticsDashboard.tsx # 2x2 响应式网格布局 │ ├── ScoreAnalyticsProvider.tsx # Context Provider │ ├── ScoreAnalyticsNoticeBanner.tsx # 加载/采样提示横幅 │ └── SamplingDetailsHoverCard.tsx # 采样详情悬浮卡片 ├── hooks/ │ └── useScoreAnalyticsQuery.ts # 数据获取 转换 hook ├── lib/ # 纯工具函数 │ ├── score-analytics-transformers.ts │ ├── analytics-url-state.ts │ ├── clickhouse-time-utils.ts │ ├── color-scales.ts │ ├── heatmap-utils.ts │ ├── statistics-utils.ts │ ├── chart-formatters.ts │ └── ScoreChartTooltip.tsx └── README.md # 本文档其中lib/目录下的statistics-utils.ts还承担着相关性、Cohens Kappa、F1、MAE、RMSE 等统计量的计算与强弱程度解释返回强度、颜色、描述三元组用于前端 UI 着色展示。数据流全景整条数据流从页面入口开始依次经过 Provider、hook、Context最终到达图表组件可用下图概括页面 (web/src/pages/project/[projectId]/scores/analytics.tsx) ↓ ScoreAnalyticsProvider包裹 Dashboard ↓ ① 先执行预评估查询 api.scoreAnalytics.estimateScoreComparisonSize ↓ ② 预评估成功后执行主查询 api.scoreAnalytics.getScoreComparisonAnalytics ↓ useScoreAnalyticsQuery hook通过 tRPC 拉取数据 ↓ ③ 使用 lib/ 中的纯函数完成全部转换 ↓ ④ 返回结构化 ScoreAnalyticsData ↓ React Context暴露 data / isLoading / params / colors ↓ Smart Cards通过 useScoreAnalytics 消费 ↓ Chart Components仅接收 props 渲染这里有一个值得注意的两阶段查询设计Provider 会先执行一次预评估查询estimateScoreComparisonSize只有在其成功后主查询才被启用enabled: !canEstimate || estimateQuery.isSuccess见 ScoreAnalyticsProvider.tsx。预评估结果会作为estimateResults传给主查询从而避免在主查询中重复执行一次预检。同时主查询钩子通过trpc: { abortOnUnmount: true }保证组件卸载时中止未完成的请求。页面入口 analytics.tsx 还负责通过api.scoreAnalytics.getScoreIdentifiers拉取项目内可用分数列表并按照 BOOLEAN → CATEGORICAL → NUMERIC 的顺序排序后填入选择器通过useAnalyticsUrlState把选择条件同步到 URL 查询参数支持分享与刷新恢复。关键组件逐层拆解1. ScoreAnalyticsProvider上下文中枢ScoreAnalyticsProviderScoreAnalyticsProvider.tsx是整个架构的中枢职责包括调用useScoreAnalyticsQuery并传入查询参数先执行预评估查询estimateScoreComparisonSize单分数与双分数模式都会执行用于加载提示与采样透明度展示并对结果做 30 秒的staleTime缓存根据模式single / two决定配色方案通过 Context 暴露数据、加载状态、参数与颜色。其典型用法如下score2为可选缺省即单分数模式ScoreAnalyticsProvider params{{ projectId: ..., score1: { name: ..., source: ..., dataType: NUMERIC }, score2: undefined, // 可选提供即为双分数对比模式 fromTimestamp: new Date(), toTimestamp: new Date(), interval: { count: 1, unit: day }, objectType: all, // all | trace | session | observation | dataset_run }} ScoreAnalyticsDashboard / /ScoreAnalyticsProviderProvider 还暴露了两个颜色相关能力colorMappings为所有类别/取值建立稳定的十六进制色映射供分布图、时间序列图使用与getColorForScore返回该分数的基准色。颜色方案由lib/color-scales.ts中的getScoreColors依据后端返回的metadata.mode决定单分数用score单色双分数用score1/score2双色。2. useScoreAnalyticsQuery一次转换全站复用useScoreAnalyticsQueryuseScoreAnalyticsQuery.ts负责拉数据 只转换一次。它通过 tRPC 调用api.scoreAnalytics.getScoreComparisonAnalytics获取单次聚合查询的完整响应然后在useMemo内使用纯函数完成全部转换extractCategories提取类别仅 categorical/booleanfillDistributionBins补齐缺失的分布分箱缺失箱补 0 计数generateBinLabels为数值型分布生成箱标签transformHeatmapData将 API 数据转换为热力图/混淆矩阵所需格式calculateModeMetrics计算众数mode与占比仅 categorical/booleanfillTimeSeriesGaps/fillCategoricalTimeSeriesGaps填补时间序列空洞类别命名空间化将两个分数的类别时间序列合并为all/allMatched视图时为类别添加score名: 类别前缀防止同名类别互相冲突。关键类型定义如下interface ScoreAnalyticsQueryParams { projectId: string; score1: ParsedScore; // { name, dataType, source } score2?: ParsedScore; fromTimestamp: Date; toTimestamp: Date; interval: IntervalConfig; // { count, unit } objectType?: all | trace | session | observation | dataset_run; nBins?: number; // 默认 10 estimateResults?: { // 传入预评估结果避免重复预检 score1Count: number; score2Count: number; estimatedMatchedCount: number; }; } interface ScoreAnalyticsData { statistics: { score1, score2?, comparison? }; distribution: { score1, score2?, binLabels?, categories?, ... }; timeSeries: { numeric, categorical }; heatmap: {...}; metadata: { mode: single | two, dataType, isSameScore }; samplingMetadata: SamplingMetadata; // 采样透明性元数据 }值得注意的细节是数值型分数存在三套分箱边界score1 独立分箱用 min1/max1、score2 独立分箱用 min2/max2、全局分箱用 global_min/global_max分别对应score1/score2标签页与all/matched标签页。useScoreAnalyticsQuery会据此生成三套 bin 标签binLabelsIndividual1/binLabelsIndividual2/binLabelsGlobal。当匹配数为 0无配对观测导致热力图为空时代码会回退到用mean ± 3 * std估算边界对应正态分布约 99.7% 覆盖范围。此外同一分数选择两次isSameScore时score2 的数据会用 score1 填充保证两个标签页都能展示。useScoreAnalytics()消费 hook 在 Provider 外部调用时会抛出 useScoreAnalytics must be used within a ScoreAnalyticsProvider 错误这正是 Troubleshooting 中卡片不更新问题的根源。3. Smart Cards自包含的业务卡片components/cards/下的卡片是自包含组件统一遵循从 Context 取数、自行处理加载与空态、向图表传 props的模式export function ExampleCard() { const { data, isLoading, params, colors } useScoreAnalytics(); if (isLoading) return LoadingState /; if (!data) return EmptyState /; const { statistics, metadata } data; // 渲染图表传入转换好的数据 return ChartComponent data{...} /; }实际部署的卡片包括StatisticsCard汇总指标、TimelineChartCard时间趋势、三个按数据类型路由的分布卡DistributionNumericCard/DistributionCategoricalCard/DistributionBooleanCard以及HeatmapCard对比热力图/混淆矩阵。卡片编排在 ScoreAnalyticsDashboard.tsx 中完成——该组件只做布局与路由不承载任何取数逻辑移动端/平板 xl单列垂直堆叠桌面端 xlgrid-cols-2的 2×2 网格分布卡依据data.metadata.dataType在三种类型卡片间切换数据未就绪时默认渲染数值卡。Dashboard 还通过useScoreAnalyticsNotice实现了加载/采样提示横幅逻辑当预评估或查询耗时超过 1.5 秒、或预估匹配数超过 10 万时显示 loading 横幅当查询完成且samplingMetadata.isSampled为 true 时显示采样提示横幅让用户清楚看到当前视图是基于采样数据的。4. Chart Components纯展示层components/charts/下的图表组件只通过 props 接收数据不触发任何请求、不消费 Context、不做转换interface ChartProps { data: SomeDataType; dataType: NUMERIC | BOOLEAN | CATEGORICAL; score1Name: string; score2Name?: string; } export function ExampleChart({ data, dataType, score1Name, score2Name }: ChartProps) { // 纯渲染逻辑例如 Recharts... / }实际组件包括ScoreDistributionNumericChart、ScoreDistributionCategoricalChart、ScoreDistributionBooleanChart、ScoreTimeSeriesNumericChart、ScoreTimeSeriesBooleanChart、ScoreTimeSeriesCategoricalChart、Heatmap含HeatmapCell、HeatmapLegend、MetricCard、ScoreCombobox、ObjectTypeFilter等。5. Transformers 与 Utilities纯函数工具库Transformersscore-analytics-transformers.ts提供五个核心纯函数全部满足同输入必同输出、无副作用、完整类型、JSDoc 注释函数作用extractCategories从混淆矩阵/堆叠分布中提取去重排序后的类别NUMERIC 返回 undefinedBOOLEAN始终返回[False, True]保证即使数据只有单一类别也能展示两个类别fillDistributionBins用 0 计数补齐缺失分箱保证每个类别都有条目calculateModeMetrics计算众数类别与占比count / total × 100transformHeatmapDataNUMERIC →generateNumericHeatmapData10×10 分箱否则 →generateConfusionMatrixDatagenerateBinLabels根据 min/max/nBins 生成格式化的箱区间标签精度随区间量级自适应≥1 用 1 位小数≥0.1 用 2 位其余用 3 位Utilities按领域分工analytics-url-state.ts管理过滤器与选择的 URL 查询参数clickhouse-time-utils.tsClickHouse 时间间隔归一化与时间分桶支持 ISO 8601 周、日历月等对齐color-scales.ts为图表生成统一配色。核心逻辑基于chroma-js在OKLAB 色彩空间内做感知均匀的混合每个分数拥有各自的基准色score1 --chart-3蓝、score2 --chart-2黄同一分数的所有类别/取值使用该基准色的单色渐变深→浅热力图零值格子渲染为transparent从而保证跨图表视觉一致且类别颜色稳定类别先按字母序排序再分配颜色避免隐藏/显示类别时颜色漂移heatmap-utils.ts热力图数据预处理。generateNumericHeatmapData依据 min1/max1/min2/max2 计算 bin 宽度并生成网格单元、行/列标签generateConfusionMatrixData将行列类别交叉展开为 n×m 网格并标记对角线isDiagonal用于一致性可视化另有fillMissingBins用于为 0 计数的 bin 补全数据score-formatter.ts源码中为chart-formatters.ts分数值显示格式化statistics-utils.ts统计计算与解释。包括calculateCohensKappaκ (Po − Pe)/(1 − Pe)、calculateWeightedF1Score按 support 加权的多分类 F1、calculateOverallAgreement对角线占比以及interpretPearsonCorrelation/interpretSpearmanCorrelation/interpretCohensKappa/interpretF1Score/interpretOverallAgreement/interpretMAE/interpretRMSE等解释函数——每个都返回{ strength, color, description }三元组例如相关系数按 ≥0.9 / ≥0.7 / ≥0.5 / ≥0.3 分为 Very Strong / Strong / Moderate / Weak / Very WeakMAE/RMSE 支持传入{ min, max }量程做相对误差分级。6. Server RoutertRPC 服务端与 ClickHouse 优化服务端入口是 scoreAnalyticsRouter.ts注册为api.scoreAnalytics.*根注册见web/src/server/api/root.ts包含三个 proceduregetScoreIdentifiers通过getScoresGroupedByNameSourceType从 ClickHouse 查询项目内所有分数的 name / dataType / source返回值为name-dataType-source复合标识供下拉选择器使用estimateScoreComparisonSize运行buildEstimateQuery基于约 1% 采样返回score1Count、score2Count、estimatedMatchedCount、willSample、willSkipFinal、estimatedQueryTime用于加载状态与采样徽标展示。查询耗时按匹配数分级100 万 → 30-60s50 万 → 15-30s10 万 → 10-20s否则 10sgetScoreComparisonAnalytics主分析查询通过buildScoreComparisonQuery构建单个 UNION ALL 聚合查询一次返回 counts、heatmap、confusion matrix、statistics、time series、distributions含 individual/matched 多套视图与 stacked distribution。响应中按result_type标记区分 19 类结果块例如counts、heatmap、confusion、stats、timeseries、distribution1/2、stacked、timeseries_categorical1/2、distribution1/2_individual、distribution1/2_matched等。自适应 FINAL 优化代码中定义了ADAPTIVE_FINAL_THRESHOLD 100_000。当两张分数表任一规模 ≥ 10 万时跳过 ClickHouse 的FINAL修饰符FINAL 会触发昂贵的合并去重以保证查询性能小数据集则使用 FINAL 保证准确性评分数可被更新近期数据准确性重要。该决策会写入响应的samplingMetadata.adaptiveFinal包含usedFinal与reason方便测试与透明化排查。Hash 采样SAMPLING_THRESHOLD 100_000任一分数字表超过该阈值即触发采样TARGET_SAMPLE_SIZE 100_000采样率按较大表计算samplingRate min(1.0, TARGET_SAMPLE_SIZE / maxCount)。采样表达式使用cityHash64对复合键trace_id、observation_id、session_id、dataset_run_id做确定性哈希后取模cityHash64( coalesce(trace_id, ), coalesce(observation_id, ), coalesce(session_id, ), coalesce(dataset_run_id, ) ) % 100 samplingPercent这种设计的关键在于对同一复合键的配对分数采样结果一致从而在采样后仍然保留 score1 与 score2 的配对关系保证混淆矩阵与相关性计算不被破坏。其他查询细节查询执行时设置short_circuit_function_evaluation: enable确保if()条件先于函数调用求值避免相关性计算在空集上抛错isIdenticalScoresname/source/dataType 全同时跳过 Spearman 等对恒等数据集无意义的计算跨类型对比一方非 NUMERIC按 categorical 处理。支持的数据类型与展示形态NUMERIC连续数值评分分布带分箱的直方图默认 10 箱nBins允许 5–50时间线平均值折线图对比10×10 热力图 Pearson/Spearman 相关 MAE RMSE。BOOLEAN真/假评分分布双类别柱状图始终包含 False/True时间线堆叠面积图对比混淆矩阵 Cohens Kappa F1 一致性Agreement。CATEGORICAL离散类别评分分布N 类别柱状图时间线堆叠面积图对比混淆矩阵 Cohens Kappa F1 一致性。三种工作模式单分数模式Single Score仅分析一个分数展示 Statistics、Timeline、Distribution 三卡隐藏对比指标与热力图双分数对比模式Two Scores四卡全展示每张卡内部提供score1/score2/all/matched标签页对比指标含相关性、一致性、误差指标热力图呈现相关性/混淆矩阵同一分数选择两次Same Score Twice作为双分数模式处理使用 source 区分同名分数例如accuracy (EVAL)与accuracy (ANNOTATION)后端识别为isSameScore后做数据复用与计算裁剪。模式判断的逻辑是score2 undefined即 single否则为 two后端将mode原样回显并以metadata.isSameScore作为权威判定。常见模式与二次开发指南单/双分数模式分支const { data, params } useScoreAnalytics(); const { metadata } data; const { mode } metadata; if (mode single) { // 单分数 UI } else { // 双分数 UI带标签页 }按数据类型分支const { metadata } data; const { dataType } metadata; if (dataType NUMERIC) { return NumericChart /; } else if (dataType BOOLEAN) { return BooleanChart /; } else { return CategoricalChart /; }使用 Provider 提供的颜色const { colors } useScoreAnalytics(); if (score in colors) { // 单分数colors.score } else { // 双分数colors.score1 / colors.score2 }扩展新功能的标准路径新增卡片在components/cards/创建NewCard.tsx消费useScoreAnalytics()、处理 loading/empty、渲染图表然后在ScoreAnalyticsDashboard.tsx中挂载新增图表类型在components/charts/创建纯展示组件仅 props、不消费 Context供卡片使用新增转换在lib/score-analytics-transformers.ts增加纯函数在useScoreAnalyticsQuery的useMemo中调用并扩展返回对象接口修改数据结构同步更新useScoreAnalyticsQuery.ts中的接口、useMemo转换逻辑以及消费卡片。性能考量一次转换全部转换发生在 hook 内而非每次渲染记忆化所有转换均使用useMemo且依赖数组正确Context 隔离避免 prop drilling防止无关组件因父级重渲染而重渲时间序列补洞一次完成fillTimeSeriesGaps在 hook 内只执行一次而不是每个图表各做一遍服务端两层优化自适应 FINAL 与 cityHash64 哈希采样见上文第 6 节。测试与验证后端集成测试位于 score-comparison-analytics.servertest.ts覆盖全部数据类型NUMERIC / BOOLEAN / CATEGORICAL全部模式单分数、双分数、同分数两次边界情况空数据、缺失分箱、时区处理统计计算相关系数、Cohens Kappa、F1 等自适应 FINAL 优化小/大数据集差异哈希采样大数据集采样一致性对象类型过滤trace / observation / session / dataset_run。前端侧另有useScoreAnalyticsQuery.clienttest.tsx对 hook 转换逻辑做单元测试。运行后端测试pnpm --filterweb test score-comparison-analytics故障排查速查表现象排查方向卡片不更新确认useScoreAnalytics()在ScoreAnalyticsProvider内部调用Context 外调用会直接抛错数据转换异常检查useScoreAnalyticsQuery.ts——所有转换都应集中在此 hook 中完成类型错误核对useScoreAnalyticsQuery.ts与ScoreAnalyticsProvider.tsx中的接口定义颜色显示异常检查lib/color-scales.ts与ScoreAnalyticsProvider.tsx中的颜色赋值时间线类别碰撞检查useScoreAnalyticsQuery.ts中的 categorical 时间序列命名空间逻辑score前缀: 类别数值分箱标签错位确认使用了与标签页匹配的分箱标签individual1 / individual2 / global 三套关键文件索引页面入口web/src/pages/project/[projectId]/scores/analytics.tsx功能目录web/src/features/score-analytics/tRPC 路由web/src/features/score-analytics/server/scoreAnalyticsRouter.ts注册为api.scoreAnalytics.*根注册见web/src/server/api/root.ts查询构建web/src/features/score-analytics/server/buildEstimateQuery.ts、buildScoreComparisonQuery.ts转换纯函数web/src/features/score-analytics/lib/score-analytics-transformers.ts统计工具web/src/features/score-analytics/lib/statistics-utils.ts热力图工具web/src/features/score-analytics/lib/heatmap-utils.ts配色方案web/src/features/score-analytics/lib/color-scales.ts时间序列补洞web/src/utils/fill-time-series-gaps.ts后端数据访问仓库packages/shared/src/server/repositories/score-analytics.ts后端集成测试web/src/__tests__/server/score-comparison-analytics.servertest.ts这套 Provider Hook Smart Cards 架构在 Langfuse 中是可复用的分析看板范式数据流单向、转换收敛、组件职责单一配合 ClickHouse 端的自适应 FINAL 与哈希采样兼顾了功能完整性与大数据量下的查询性能。【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考