bug: silent wrong results in two score-analytics aggregations (histogram binning & pivot-table averages)

当你在 Langfuse 仪表盘上查看分数分析时,如果使用的是带小数的分数(0–1 置信度、LLM-judge 评分、cost、latency 等),数值分数直方图会把值分到错误的桶里,透视表的 subtotal/grand total 行也会给出偏大或偏小的平均值。这两个错误都不会报错,只会静默返

快速结论:当你在 Langfuse 仪表盘上查看分数分析时,如果使用的是带小数的分数(0–1 置信度、LLM-judge 评分、cost、latency 等),数值分数直方图会把值分到错误的桶里,透视表的 subtotal/grand total 行也会给出偏大或偏小的平均值。这两个错误都不会报错,只会静默返回错误数字,所以优先排查 createHistogramData 的取整逻辑和 calculateSubtotals 的平均值算法。

适用环境:Langfuse 仪表盘(dashboard)前端分数分析路径,涉及文件 web/src/features/dashboard/lib/score-analytics-utils.tsweb/src/features/widgets/utils/pivot-table-utils.ts。Issue 未确认操作系统、Python、CUDA、显卡或依赖版本,此处不补写。

最快修复方案:暂无确认的一步修复方案。Issue 中提出的方向是:直方图改为在原始值上分桶(Math.floor((value - min) / binSize)),桶边界由原始 min/max 推导,仅对显示标签取整;透视表平均值在可用时按组内计数加权。这两项均为 Issue 中讨论的修复建议,尚未确认为已合并版本。

注意事项:整数(Likert)分数不受影响,因此该问题可能长期未被发现;一旦改成小数分数或窄范围分数就会显现。修复后需注意窄范围的桶标签精度,否则可能出现零宽桶或重复边界。方案尚未验证是否覆盖所有聚合路径。

问题场景

用户在 Langfuse 仪表盘上读取评估结果时触发问题,具体涉及两条前端统计路径:数值分数直方图(由 NumericScoreHistogram 渲染,数据来自 dashboard-router.ts 中的 createHistogramData),以及透视表组件(pivot-table widgets)的 subtotal / grand total 行(由 applyAggregation / calculateSubtotals 计算)。用户看到的是分布图形与合计行数字,但两者都不报错,只是数值不对。整数 Likert 分数不受 Bug 1 影响,因此只有使用 0–1 置信度、LLM-judge 评分、cost、latency 等小数分数时才会暴露。

报错原文

bug: silent wrong results in two score-analytics aggregations (histogram binning & pivot-table averages)

该 Issue 没有传统异常堆栈,属于静默错误:不抛错、不报错,只返回错误数字。Issue 中给出的典型症状是桶标签区间不包含被计入该桶的值,例如 0.857 被计入 [0.86, 1],而实际应落在 [0.71, 0.86];以及窄范围下桶标签塌缩为 ["[0.85, 0.85]", "[0.85, 0.86]"] 这类零宽桶。

原因分析

可能原因是两个函数在计算前对数值做了不恰当的取整或聚合方式错误。

Bug 1 位于 createHistogramData:代码在计算桶索引之前先用 round(value)(内部通过 toFixed(2) 截断到两位小数)把值取整,而桶边界仍由原始范围推导,导致分桶使用的是取整后的值。例如 round(0.857) = 0.86floor(0.86 / (1/7)) = 6,于是被放进最上面的桶,尽管 0.857 < 0.86。同一取整还导致窄范围内两位小数的标签塌缩,出现零宽桶与重复边界。相比之下,整数 Likert 分数不受影响,这也是问题长期未被发现的原因。

Bug 2 位于 applyAggregation / calculateSubtotals"avg" 分支使用 values.reduce((sum, val) => sum + val, 0) / values.length,即对各组平均值的简单算术平均,没有按计数加权。calculateSubtotals 调用时也没有传入组计数,因此当各组大小不等时,subtotal / grand total 行必然给出偏大或偏小的 avg cost / latency 等指标。Issue 也提到相关的历史问题 #13331 涉及类似的直方图边界计算问题。

环境排查

  • 确认你查看的是 Langfuse 仪表盘上的数值分数直方图或透视表 subtotal / grand total 行,而非其他统计视图。
  • 确认分数类型:整数 Likert 分数不会触发 Bug 1,只有小数分数(0–1 置信度、LLM-judge 评分、cost、latency)会触发。
  • 确认分组数量是否不相等:各组大小相同时 Bug 2 的偏差不明显,大小差异越大越明显。
  • 确认前端代码版本中 score-analytics-utils.tscreateHistogramData 是否仍存在计算桶索引前调用 round(value) 的写法。
  • 确认 pivot-table-utils.tsapplyAggregation"avg" 分支是否仍为未加权的 sum / values.length,且 calculateSubtotals 是否仍未传入组计数。
  • Issue 未确认操作系统、Python、CUDA、PyTorch、显卡或依赖版本,这些项目无需排查。

解决步骤

  1. 复现 Bug 1:用固定输入 [0, 0.857, 0.86, 1] 和 7 个等宽桶([0, 1])调用当前 createHistogramData,确认 0.857 是否落在最上面的桶。
  2. 修改 createHistogramData:分桶改为在原始值上计算,即 Math.floor((value - min) / binSize);桶边界由原始 min/max 推导,不再对参与计算的值取整。
  3. 仅对显示标签做取整,并提高标签精度以保证窄范围内边缘不重复、不出现零宽桶。
  4. 复现 Bug 2:构造各组数量不相等的数据,让透视表计算 subtotal / grand total 行的 "avg",对比按组内计数加权的平均值。
  5. 修改 applyAggregation / calculateSubtotals:在组计数可用时,按组内计数对平均值做加权,而不是求各组平均值的简单平均。
  6. 按 Issue 建议补充差异测试:直方图移植版与 numpy.histogram 在固定桶数下对比,覆盖均匀分布、窄范围、Likert 分布等数据集,确认不匹配数为 0。

验证方法

对 Bug 1,用 1,200 组随机数据集将移植后的 createHistogramDatanumpy.histogram(相同桶数)对比,修复前 916 / 1200 组与 numpy 不一致,修复后应为 0 / 1200 不匹配;同时检查最小确定性用例中 0.857 是否落在 [0.71, 0.86] 而非顶部桶,窄范围输入不再产生零宽桶或重复边界。对 Bug 2,构造组大小明显不等的透视表数据,确认 subtotal / grand total 行的平均值等于按计数加权的平均值,而不是各组平均值的简单平均。Issue 提到可同时对照 calculateCohensKappacalculateWeightedF1ScorecalculateOverallAgreement 这些已验证正确的相邻统计函数,确认它们仍为 0 不匹配。

参考来源

langfuse/langfuse #15208

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 25274

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注