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

当 Langfuse 仪表盘上的评分分析(Numeric Score Histogram、pivot-table 小计/总计)在存在小数分数或各组样本量不一致时,会静默返回错误数字: bug: silent wrong results in two score-analytics aggregati

快速结论:当 Langfuse 仪表盘上的评分分析(Numeric Score Histogram、pivot-table 小计/总计)在存在小数分数或各组样本量不一致时,会静默返回错误数字:bug: silent wrong results in two score-analytics aggregations (histogram binning & pivot-table averages)。优先排查前端统计代码中的直方图分桶逻辑与平均值加权方式。

适用环境:Issue 已确认的工具为 Langfuse Web 前端(TypeScript 代码路径);涉及文件 web/src/features/dashboard/lib/score-analytics-utils.ts 与 web/src/features/widgets/utils/pivot-table-utils.ts。Issue 未提供操作系统、Python、CUDA、显卡或后端依赖版本信息,因此不作补充。

最快修复方案:Issue 中提出的修复方向已被维护者确认为正确(可优先尝试):Bug 1 改为基于原始值分桶(Math.floor((value - min) / binSize)),边界由原始 min/max 派生,仅在显示标签时做足够精度的舍入;Bug 2 在可用时按组计数对平均值做加权,而非直接对各组平均值取算术平均。Issue 关闭时未明确说明已合入的具体补丁编号,应用前请以仓库当前代码为准。

注意事项:以上修复方案来自 Issue 讨论中的建议与维护者认可,并非 Issue 中已验证的官方补丁;整数(如 Likert 1–5)分数不受 Bug 1 影响,因此该问题此前不易被发现,仅在小数分数(0–1 置信度、LLM-judge 分数、cost、latency)或各组样本量不等时暴露。修复后需重点回归窄区间范围与不等样本量分组两类场景。

问题场景

用户在 Langfuse 仪表盘查看评分分析结果时触发:

  • 运行 Numeric Score Histogram(数值评分直方图),数据源为 createHistogramData,由 dashboard 的 NumericScoreHistogram 渲染、在 dashboard-router.ts 中产出。
  • 查看 dashboard pivot-table 组件的小计(subtotal)与总计(grand total)行,数据源为 applyAggregation / calculateSubtotals。

问题在“对照参考库做差分测试”时被发现:作者用 numpy、scikit-learn 对照 Langfuse 客户端统计代码,发现两处静默正确性缺陷——不报错,但给用户返回错误数值。相邻的 inter-rater 统计函数(calculateCohensKappa、calculateWeightedF1Score、calculateOverallAgreement)经 1,200 个随机用例验证与 scikit-learn 一致(0 不一致),说明这两处是异常点。

报错原文

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

说明:这是一个“静默错误结果”类缺陷,不会抛出异常堆栈;用户看到的是渲染出来的错误数字或错误桶标签。Issue 中给出的差分测试证据如下:

uniform 0..1 (n=50)          | value-in-wrong-bucket:185/200  numpy-mismatch:117/200
uniform 0..1 (n=200)         | value-in-wrong-bucket:200/200  numpy-mismatch:199/200
narrow 0.80..0.85 (n=40)     | value-in-wrong-bucket:200/200  numpy-mismatch:200/200
1..5 likert (n=60)           | value-in-wrong-bucket:  0/200  numpy-mismatch:  0/200
...
runs differing from numpy.histogram: 916 / 1200

原因分析

Bug 1(直方图分桶):createHistogramData 在计算桶索引之前先把数值舍入到 2 位小数,而桶边界并未做同样处理:

const min = round(Math.min(...numericScoreValues));            // rounded
const binSize = range / bins || 1;
// ...
const shiftedValue = round(value) - min;                       // value rounded to 2dp
const binIndex = Math.min(Math.floor(shiftedValue / binSize), bins - 1);

维护者确认:该函数确实在计算 shiftedValue / binSize 前调用了 round(value)(经 toFixed(2) 截断到 2 位小数),且桶边界也由舍入后的 min/max 派生,导致靠近桶边界的小数分数被放进错误桶。最小确定性示例([0, 1] 上 7 个等宽桶,输入 [0, 0.857, 0.86, 1]):round(0.857) = 0.86,于是 floor(0.86 / (1/7)) = 6,落入最顶部桶,尽管 0.857 < 0.86。同一舍入还会引发第二个症状:窄区间下 2 位小数标签塌缩,例如 createHistogramData([0.855, 0.857, 0.858, 0.861]) 产出标签为 ["[0.85, 0.85]", "[0.85, 0.86]"] 的桶——出现零宽度桶和重复边界。

Bug 2(pivot-table 小计/总计平均值):calculateSubtotals 把平均值算成了“各组平均值的平均”,而不是按计数加权的平均值:

case "avg":
  return values.reduce((sum, val) => sum + val, 0) / values.length;   // mean of per-row means

维护者确认:applyAggregation 对 "avg" 分支计算的是各组平均值的简单算术平均,没有任何计数加权;而 calculateSubtotals 调用它时未传入组计数,因此各组样本量不等时不可能得到正确加权结果。这会让小计/总计行的 avg cost、latency 等指标被高估或低估。

此外 Issue 提到一个相关的既有问题 #13331,关于评分对比视图中的直方图桶溢出,根因是类似的桶边界计算问题,可作为排查时可能原因的旁证。

环境排查

  • 确认 Langfuse 前端代码版本,定位 web/src/features/dashboard/lib/score-analytics-utils.ts 与 web/src/features/widgets/utils/pivot-table-utils.ts 的实际内容。
  • 确认评分数据类型:是否包含非整数小数分数(0–1 置信度、LLM-judge 分数、cost、latency)。整数 Likert 分数不会触发 Bug 1。
  • 确认 pivot-table 中各分组样本量是否不等;样本量相等时 Bug 2 的表象可能不明显。
  • Issue 未提供 Python、CUDA、PyTorch、显卡、节点或依赖版本信息,无需在这些方向补写排查项。

解决步骤

  1. 定位 Bug 1 的代码路径 createHistogramData,将分桶改为基于原始值:使用 Math.floor((value - min) / binSize),其中 min/binSize 由未舍入的原始 min/max 派生。
  2. 仅为显示标签做舍入,并保留足够精度使相邻边界不塌缩、不重复、不出现零宽度桶。
  3. 定位 Bug 2 的代码路径 applyAggregation / calculateSubtotals,在 "avg" 分支改为按组计数加权;calculateSubtotals 调用时需传入组计数。
  4. 按 Issue 建议补充复现测试(差分测试对照 numpy.histogram 与加权平均参考实现),覆盖 uniform/narrow/likert 三类数据集及不等样本量分组。
  5. 若暂无法立即改代码,可先通过仪表盘查看直方图桶标签是否为窄区间塌缩、pivot-table 各组样本量是否差异明显,以判断当前视图是否受影响。

验证方法

修复后,将改动后的实现重新对照 numpy.histogram(相同桶数量)跑 1,200 个随机数据集,Issue 中给出的修复后预期结果为 0 / 1200 mismatches;同时构造最小确定性用例([0, 1] 上 7 个等宽桶、输入 [0, 0.857, 0.86, 1]),确认 0.857 被计入 [0.71, 0.86] 而不再落入顶部桶。Bug 2 则用各组样本量不等的用例,验证小计/总计行等于按计数加权的平均值,而非各组平均值的算术平均。

参考来源

langfuse/langfuse #15208

相关既有问题:langfuse/langfuse #13331

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 25356

发表回复

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