快速结论:当 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、显卡、节点或依赖版本信息,无需在这些方向补写排查项。
解决步骤
- 定位 Bug 1 的代码路径
createHistogramData,将分桶改为基于原始值:使用Math.floor((value - min) / binSize),其中 min/binSize 由未舍入的原始 min/max 派生。 - 仅为显示标签做舍入,并保留足够精度使相邻边界不塌缩、不重复、不出现零宽度桶。
- 定位 Bug 2 的代码路径
applyAggregation/calculateSubtotals,在"avg"分支改为按组计数加权;calculateSubtotals调用时需传入组计数。 - 按 Issue 建议补充复现测试(差分测试对照
numpy.histogram与加权平均参考实现),覆盖 uniform/narrow/likert 三类数据集及不等样本量分组。 - 若暂无法立即改代码,可先通过仪表盘查看直方图桶标签是否为窄区间塌缩、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 #13331
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


