PostHog Signals 分组管线离线评估解析:从 2026-03-16 报告读懂 ARI、匹配失败模式与裁判指标
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
导读
本文以 products/signals/eval/reports/2026-03-16.md 这份评估报告为骨架,深入解读 PostHog Signals 产品中**信号分组管线(signal grouping pipeline)**的一次离线(offline)端到端评估结果。报告中记录了 91 个合成信号、41 个 ground-truth 分组、54 份产出报告的聚类质量(ARI、同质性、完整性、纯度、组召回)、匹配失败模式分布、按来源划分的 pre-emit 可操作性检查,以及 Safety/Actionability 两个报告级裁判的准确率。读完本文,你将理解报告中每个指标的业务含义与计算口径、失败模式(尤其是 SPECIFICITY_SPLIT)背后对应的管线阶段,以及如何在本地复现这次评估并用 HogQL 查询同样的指标。
一、这份报告是什么:Signals 分组管线的离线评估
PostHog Signals 是一个将 Zendesk、GitHub、Linear 等外部工单/Issue 信号聚合为"报告(report)"的功能:多个信号如果描述同一个问题,应被聚类到同一份报告中,从而让用户一次看到问题的全貌。为了保证聚类质量,仓库在 products/signals/eval/ 下维护了一套端到端评估(end-to-end eval),其目标定义在 AGENTS.md 中:
将合成信号送入真实管线(LLM 查询生成 → 嵌入检索 → LLM 匹配 → 特异性校验 → 摘要 → 安全裁判 → 可操作性裁判),与人工标注的 ground-truth 分组对比,衡量管线把信号聚成报告的效果。
关键设计是"管线真实、基础设施打桩":用内存版EmbeddingStore替代 ClickHouse + Kafka、用ReportStore替代 Postgres(实现见 mock.py),从而在纯 Python 测试环境中跑完整链路。2026-03-16.md正是某一次离线评估产出的结果快照,包含聚合指标、匹配质量、裁判准确率等若干层面,供开发者在迭代分组逻辑时对比基线。
从文件命名看,reports/目录下还保存着 2026-03-17.md、2026-08-18.md、2026-09-03.md 等后续报告,说明这套评估是持续迭代、逐次对比的机制,本文的 03-16 报告是其中的基线记录之一。
二、本次运行的测试集与执行规模
报告开头给出了本次评估的规模信息:
- 91 个信号(signals):由合成数据生成,来自 Zendesk、GitHub、Linear、error tracking 等来源,信号内容格式在 data_spec.py 中定义(如 Zendesk 使用
subject/description字段、GitHub 使用title/body、error tracking 按 cymbal 格式渲染描述)。 - 41 个 ground-truth 分组(groups):人工标注的"正确答案"——这些信号真实归属于哪 41 个主题分组。
- 54 份报告(reports produced):管线最终产出 54 份报告,数量多于 ground-truth 的 41 组,说明存在过度拆分现象(下文失败模式会量化这一点)。
信号到达顺序并非按组排列,而是由 common.py 中的get_signals_stream()用固定随机种子(RNG_SEED = 1337)将各组信号交错打乱,模拟真实世界的乱序到达,同时保持组内顺序。这意味着管线必须在信号"不按主题排队"的情况下完成在线聚类,难度更接近生产环境。
每个信号经历的管线阶段
从 AGENTS.md 与 eval_grouping_e2e.py 的编排逻辑看,本次评估中每个信号依次经过以下阶段:
- Pre-emit:摘要长描述、做可操作性检查(actionability check,经由内部 LLM 网关调用 Claude Haiku)。不满足可操作性的信号在此被丢弃。
- Match:LLM 生成检索查询 → OpenAI 嵌入(
text-embedding-3-small)→ 对已存信号做余弦相似度检索 → LLM 判断"并入已有报告"还是"新建报告" → 特异性校验(specificity judge)确认匹配是否过于宽泛。 - Persist:存储信号与嵌入,更新报告元数据。
- Judge:全部信号处理完后,对每份报告做摘要、安全裁判(提示注入检测)与可操作性裁判。
所有 LLM 调用统一走内部 LLM 网关(LLM_GATEWAY_URL),信号并发执行,但 match + persist 阶段由asyncio.Lock串行化,以保证嵌入库与报告库在做"并入/新建"决策时看到一致视图(并发上限MAX_CONCURRENT_RUNS = 70,定义在 common.py)。
三、聚合指标(Aggregate metrics):聚类的四个"体检项"
报告的第一张核心表是聚合指标:
| Metric | Score |
|---|---|
| ARI | 0.7059 |
| Homogeneity | 0.9923 |
| Completeness | 0.9065 |
| Mean purity | 0.9954 |
| Mean group recall | 0.6790 |
| Malicious leaked rate | 0.2083 (5/24) |
这些指标由 sklearn 的adjusted_rand_score、homogeneity_completeness_v_measure等函数计算(见 eval_grouping_e2e.py 的导入),并结合报告里的定义逐项解读:
- ARI(调整兰德指数,Adjusted Rand Index):衡量"管线聚类结果"与"ground-truth 分组"的相似度,经随机机会校正,取值 -1~1,1 为完全一致。本次 0.7059,说明聚类与人工标注的一致性处于中上水平。
- Homogeneity(同质性)= 0.9923:每份报告里只包含来自同一个真实组的信号。越接近 1.0,说明越没有过度聚合(overgrouping)。本次表现优秀。
- Completeness(完整性)= 0.9065:某个真实组的所有信号是否都落在同一份报告里。越接近 1.0,说明越没有欠聚合(undergrouping)。本次略低于同质性,提示存在一定程度的组被拆分。
- Mean purity(平均纯度)= 0.9954:每份报告中主导组信号所占的平均比例,即"报告内容有多纯粹"。
- Mean group recall(平均组召回)= 0.6790:一个真实组的信号被其最佳匹配报告捕获的比例的平均值。0.679 意味着平均约 32% 的信号没有与同组信号聚在一起——这是本次运行最薄弱的环节。
- Malicious leaked rate(恶意信号泄漏率)= 0.2083 (5/24):24 个不安全的(恶意/提示注入)信号中,有 5 个未被安全裁判拦截而通过了管线,泄漏率 20.83%。这是安全维度的关键红线指标,后续 03-17 报告将其降到 0/24,见下文的演进对比。
从源码角度,这些指标在 AGENTS.md 中有同样的定义清单,并被作为$ai_evaluation事件中的grouping-aggregate实验捕获,用于在 PostHog 内做长期趋势监控。
四、匹配质量:失败模式的分布与 SPECIFICITY_SPLIT 的来源
报告的第二张核心表关注每个信号的匹配决策质量,本次共覆盖 104 个匹配决策:
| Failure mode | Count | % of total |
|---|---|---|
| CORRECT | 79 | 76.0% |
| SPECIFICITY_SPLIT | 13 | 12.5% |
| UNDERGROUP | 6 | 5.8% |
| OVERGROUP | 6 | 5.8% |
失败模式的定义
在 common.py 中定义了三个基础匹配结果枚举:
NONE:匹配正确(对应报告中的 CORRECT);UNDERGROUP:应该并入已有报告却新建了一份(欠聚合);OVERGROUP:加入了属于其他 ground-truth 组的报告(过度聚合)。
而报告中的SPECIFICITY_SPLIT是特异性校验阶段(specificity judge)引入的第四种状态:matcher 原本给出的匹配被特异性判断器判定为"过于宽泛"而否决/拆分,导致本应属于同一报告的信号被拆开。报告用一行专门给出了它的体量:
Total undergrouping (UNDERGROUP + SPECIFICITY_SPLIT): 19/104 (18.3%). Specificity split share of undergrouping: 13/19 (68.4%).
也就是说:本次 18.3% 的欠聚合错误中,68.4%(13/19)是由特异性判断器"过度拆分"造成的,而不是 matcher 本身没有识别出关联。这直接点出了本次运行的一个优化方向——特异性判断器的阈值/行为需要校准。
与后续报告的印证
这种"特异性判断器引入拆分"的现象并非孤例,2026-03-17.md 明确记录:特异性判断器把 14 个 overgroup 转为正确匹配,但同时引入了 5 个新的 undergroup(净 +9);2026-08-18.md 则在模型对比中发现"在 sonnet-5 上特异性判断器是净损失(84.9% → 83.7%)",因为该模型本身已很少过度聚合,判断器剩余的作用只是拆分本应合并的组。这解释了 03-16 报告中 SPECIFICITY_SPLIT 占比高、而后续迭代不断调整该阶段的原因。
检索多样性(供理解匹配质量辅助信息)
虽然 03-16 报告未列出检索多样性表,但match-quality实验本身就包含query_diversity(查询间平均余弦距离)与candidate_diversity(1 − Jaccard)两个数值指标,用于诊断"检索召回是否足够多样化"。后续报告(如 03-17 的 query_diversity 均值 0.465、candidate_diversity 均值 0.445)展示了它们的典型量级:查询中等程度多样化,不同查询召回的候选集部分重叠。
五、Pre-emit 可操作性检查:按来源的过滤质量
报告第三张表评估信号进入分组之前的可操作性过滤器({source}-actionability-check实验):
| Source | Total | Correct | Failure rate | FP | FN |
|---|---|---|---|---|---|
| Zendesk | 88 | 64 | 27.3% | 12 | 0 |
| GitHub | 58 | 38 | 34.5% | 10 | 0 |
| Linear | 36 | 24 | 33.3% | 6 | 0 |
解读要点:
- 全部失败都是假阳性(FP):即"本应被过滤掉的不可操作信号被放行了",三个来源的 FN 均为 0——没有把真正可操作的信号误杀,这是产品体验上最要紧的底线。
- 三个来源的失败率集中在 27%~35% 区间,说明 pre-emit 的可操作性判断对来源不敏感,瓶颈在 LLM 判断本身而非数据格式。Zendesk(工单描述)相对表现最好,GitHub Issue(34.5%)最差。
- 从实现看,这一步对应 eval_grouping_e2e.py 中导入的
check_actionability(来自 products/signals/backend/emission/pipeline.py),属于 emission 阶段的预处理,未通过即被丢弃,不会进入后续匹配。
该实验按来源拆分命名(zendesk-actionability-check、github-actionability-check、linear-actionability-check),指标为二值correct_classification,与 ground-truth 的可操作性标签对比。
六、报告级裁判:Safety 与 Actionability
所有信号分组完成后,管线对每份报告运行两个裁判(对应report-safety-check与report-actionability-check实验):
| Judge | Total | Correct | Accuracy | FP | FN |
|---|---|---|---|---|---|
| Safety | 54 | 49 | 90.7% | 5 | 0 |
| Actionability | 54 | 35 | 64.8% | 19 | 0 |
- Safety(90.7%):54 份报告中有 49 份被正确分类,5 个假阳性——即 5 份安全报告被误判为不安全。结合聚合指标中的恶意泄漏率 5/24,本次运行的安全护栏"有漏也有误报",后续 03-17 通过调整达到 Safety 100.0%。
- Actionability(64.8%):54 份报告中仅 35 份被正确判定可操作性,19 个假阳性。这是本次运行准确率最低的环节,也是"报告该不该推送/展示给用户"的核心决策点,是明显的迭代目标。
- 两个裁判的 FN 均为 0,说明没有不安全/不可操作的报告被漏判放行——错误都集中在"过于保守"方向,与 pre-emit 阶段的 FP-only 特征一致。
从实现看,报告级裁判分别对应 products/signals/backend/temporal/report_safety_judge.py(judge_report_safety,提示注入检测)与 actionability 判断,二者在 eval_grouping_e2e.py 中被逐一调用。
七、逐报告分组质量(Per-report grouping quality)
最后一张表从"单份报告"粒度给出分组质量的分布:
| Metric | n | Mean | Min | Max |
|---|---|---|---|---|
| Purity | 54 | 0.995 | 0.750 | 1.000 |
| Is pure | 54 | 53/54 (98.1%) | - | - |
| Group recall | 54 | 0.679 | 0.333 | 1.000 |
- Purity 均值 0.995、最小值 0.750:绝大多数报告内容非常纯粹,但存在纯度低至 0.75 的报告(混入了 25% 的其他组信号),对应上文 OVERGROUP 错误。
- Is pure = 53/54(98.1%):仅 1 份报告混入了不同组的信号。
- Group recall 均值 0.679、最小值 0.333:与聚合指标中的 mean group recall 一致,是本次运行的主要短板;最差情况下一个组的信号只有 1/3 被召回,说明存在明显的欠聚合(UNDERGROUP + SPECIFICITY_SPLIT)。
综合看,本次评估的结论画像可以概括为:报告内容足够"纯"(同质性/纯度都很高),但"全"不够(完整性/组召回偏低),根因主要是特异性判断器过度拆分,其次是 matcher 自身的欠聚合。
八、如何复现与继续观测:运行评估与查询指标
复现本次评估
评估入口是 eval_grouping_e2e.py,通过 pytest 运行:
# 完整运行——评估结果上报到 PostHog pytest products/signals/eval/eval_grouping_e2e.py -xvs # 快速试跑——只处理前 10 个信号,不上报 pytest products/signals/eval/eval_grouping_e2e.py -xvs --limit 10 --no-capture # 在线评估模式(结果标记为 online 而非 offline) pytest products/signals/eval/eval_grouping_e2e.py -xvs --online需要的环境变量(配置在仓库根目录.env,自动加载)见 AGENTS.md:
| Variable | Purpose |
|---|---|
OPENAI_API_KEY | 嵌入(text-embedding-3-small) |
LLM_GATEWAY_URL | 内部 LLM 网关地址(匹配/特异性/摘要/可操作性/安全等全部 LLM 调用) |
LLM_GATEWAY_PERSONAL_API_KEY | LLM 网关的 Bearer token(PostHog 个人 API key) |
SIGNALS_EVAL_TEAM_ID | LLM 成本归属头使用的 team id(默认 1) |
POSTHOG_PROJECT_API_KEY | 上报评估结果用(可用--no-capture跳过) |
POSTHOG_HOST | PostHog 实例地址(默认http://localhost:8010) |
支持的命令行选项:
| Flag | Effect |
|---|---|
--limit N | 只处理信号流中的前 N 个信号 |
--no-capture | 不向 PostHog 发送$ai_evaluation事件 |
--online | 将上报结果标记为 online 评估(默认 offline) |
运行过程中 stderr 会输出两条 tqdm 进度条(Matching 与 Judging),随后是聚合结果汇总表,与 03-16 报告的数据结构一一对应。
用 HogQL 复现报告中的指标
评估结果以$ai_evaluation事件($ai_eval_source = 'signals-grouping'、$ai_evaluation_type = 'offline')落库,AGENTS.md 提供了多段可直接在 PostHog SQL 编辑器中执行的查询。例如聚合指标:
SELECT properties.$ai_metric_name AS metric, properties.$ai_score AS score, properties.$ai_metric_description AS description, properties.$ai_reasoning AS reasoning, properties.$ai_input AS input, properties.$ai_output AS output, properties.$ai_expected AS expected FROM events WHERE event = '$ai_evaluation' AND properties.$ai_eval_source = 'signals-grouping' AND properties.$ai_evaluation_type = 'offline' AND properties.$ai_experiment_name = 'signals-grouping/grouping-aggregate' ORDER BY metric匹配失败模式分布:
SELECT multiIf( properties.$ai_score = 1.0, 'CORRECT', properties.$ai_reasoning LIKE '%UNDERGROUP%', 'UNDERGROUP', properties.$ai_reasoning LIKE '%OVERGROUP%', 'OVERGROUP', 'UNKNOWN' ) AS failure_mode, count() AS cnt, round(count() * 100.0 / (SELECT count() FROM events WHERE event = '$ai_evaluation' AND properties.$ai_eval_source = 'signals-grouping' AND properties.$ai_evaluation_type = 'offline' AND properties.$ai_experiment_name = 'signals-grouping/match-quality'), 1) AS pct FROM events WHERE event = '$ai_evaluation' AND properties.$ai_eval_source = 'signals-grouping' AND properties.$ai_evaluation_type = 'offline' AND properties.$ai_experiment_name = 'signals-grouping/match-quality' AND properties.$ai_metric_name = 'correct_match' GROUP BY failure_mode ORDER BY cnt DESC按来源统计 pre-emit 可操作性(对应报告第五张表):
SELECT replaceOne(properties.$ai_experiment_name, 'signals-grouping/', '') AS check_name, count() AS total, countIf(properties.$ai_score = 1.0) AS correct, countIf(properties.$ai_score != 1.0) AS failures, round(countIf(properties.$ai_score != 1.0) * 100.0 / count(), 1) AS failure_pct, countIf(properties.$ai_score != 1.0 AND properties.$ai_output = 'ACTIONABLE') AS false_positives, countIf(properties.$ai_score != 1.0 AND properties.$ai_output = 'NOT_ACTIONABLE') AS false_negatives FROM events WHERE event = '$ai_evaluation' AND properties.$ai_eval_source = 'signals-grouping' AND properties.$ai_evaluation_type = 'offline' AND properties.$ai_experiment_name IN ( 'signals-grouping/zendesk-actionability-check', 'signals-grouping/github-actionability-check', 'signals-grouping/linear-actionability-check' ) AND properties.$ai_metric_name = 'correct_classification' GROUP BY check_name ORDER BY check_name此外还有报告级裁判、逐报告分组质量、特异性判断器影响(对比correct_match_pre_specificity与correct_match)以及详细失败样本等查询,完整清单见 AGENTS.md 的 "HogQL queries" 一节——这正是把 03-16 这种快照报告转化为可持续监控手段的桥梁。
九、基线定位:本次运行在迭代脉络中的位置
将 03-16 报告与仓库中后续报告对比,可以更准确地定位这次运行的水平(以下数字均来自对应报告文件本身):
| 维度 | 2026-03-16 | 2026-03-17 | 说明 |
|---|---|---|---|
| ARI | 0.706 | 0.754 | +0.048 |
| Completeness | 0.907 | 0.932 | +0.025 |
| Mean group recall | 0.679 | 0.775 | +0.096 |
| Malicious leaked | 5/24 | 0/24 | 泄漏清零 |
| Match accuracy | 76.0% | 82.1% | +6.1pp |
| Safety accuracy | 90.7% | 100% | +9.3pp |
| Actionability acc | 64.8% | 81.1% | +16.3pp |
其中 03-17 报告特别注明"Safety judge 从 90.7% 提升到 100.0%(对照 03-16)",且其match-quality表中不再出现 SPECIFICITY_SPLIT 单独列(合并进了 UNDERGROUP),说明特异性判断器行为在两次运行之间发生了调整。这正好印证了 03-16 报告最大的诊断价值:它通过 SPECIFICITY_SPLIT 的 13/19 占比,把"特异性校验过度拆分"这一根因显式暴露出来,为后续迭代提供了明确靶点。
十、总结
2026-03-16 报告是 PostHog Signals 分组管线一次信息密度很高的离线评估快照,其价值体现在三个层面:
- 量化了管线的当前水位:ARI 0.7059、同质性 0.9923、完整性 0.9065、恶意泄漏率 5/24,以及仅 64.8% 的 Actionability 裁判准确率,构成了清晰的改进基线。
- 定位了主要失败模式:18.3% 的欠聚合中 68.4% 来自特异性判断器拆分(SPECIFICITY_SPLIT),而非 matcher 本身;全部可操作性错误均为假阳性、无假阴性。
- 沉淀了可复现、可持续的观测方式:通过 eval_grouping_e2e.py 可复跑,通过 AGENTS.md 中的 HogQL 查询可持续监控每一轮迭代。
对于任何从事"LLM 驱动的在线聚类/工单归并"类系统的工程师,这份报告连同其评估框架(真实管线 + mock 基础设施 + 固定种子信号流 + 分级指标体系)都是一个值得参考的离线评估范式。
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考