首页
/ Headroom Rust 核心 signals 模块详解:行级重要性检测 Trait 与 Tiered 组合式检测架构

Headroom Rust 核心 signals 模块详解:行级重要性检测 Trait 与 Tiered 组合式检测架构

2026-09-05 20:18:52作者:柏廷章Berta

本文基于 Headroom 仓库 crates/headroom-core 中的 signals 模块文档展开,讲清楚"压缩前如何判断一行文本值不值得保留"这一核心设计:按粒度划分的检测 Trait 家族、Tiered 组合器如何以置信度驱动短路/降级、KeywordDetector 的 Aho-Corasick 自动机实现细节,以及如何按项目约定把未来的 ML 检测器(如 BGE 分类头)无侵入地接入现有检测栈。读完后可掌握一套"关键词 → 结构化解析 → 小模型"的渐进式检测器扩展方法论。

为什么 signals 是 crate 顶层模块,而不是 transforms 的子模块

Rust 侧的压缩逻辑位于 transforms 目录,而检测分类逻辑独立放在 signals 目录模块文档注释解释了分层理由:

  • transforms 负责"变",signals 负责"判":transforms 里的压缩器负责变更数据,signals 里的检测器负责对数据做分类(打分、归类)。
  • 同一个分类器喂给多个消费方:行级重要性打分同时被 text_compressorsearch_compressordiff_compressorlog_compressor 使用。如果把它嵌套在某个 transform 之下,会暗示"归某一个消费方所有",造成错误的归属。
  • 无静默回退(No silent fallbacks)是项目级约定:模块注释明确写了"没有 NoOpDetector、没有返回全零的桩 ML 实现、没有悄悄降级的 fallback 分类器"。某一级检测器要么真正干活,要么把"我没有把握"这件事通过 confidence 字段如实表达,而不是被强行扭转为阳性答案。

模块注释还给出了检测能力的成熟路径,这决定了 traits 的演进方向:

  1. 模式回退(Pattern fallback)——关键词/正则扫描,便宜但脆弱,是每个检测器的起点,即当前的 KeywordDetector
  2. 结构化解析(Structured parser)——输入有语法(diff、JSON、代码)时直接解析,unidifftree-sitter 已在项目其他部分落地;
  3. ML 模型——对模糊类别(行重要性、锚点单元格、HTML 抽取),在小规模标注流量上训练的小分类器优于关键词,规范扩展路径是在已有的 bge-small-en-v1.5 embedder 上加分类头。

三类实现都挂在同一个"按粒度"的 trait 之下,分层通过 Tiered 组合完成,从不使用继承

Trait 家族:按粒度拆分,而不是按领域拆分

signals/README.md 中的 trait 家族表如下:

Trait 粒度 状态
LineImportanceDetector 逐行 已交付(Phase 3e.1)
ContentTypeDetector 整个文本块 未来对 transforms::detection 的泛化
ItemImportanceDetector<I> &[I] 排序 未来,面向 SmartCrusher 单元格 / 搜索命中

设计要点是"按粒度、不按领域":把所有能力塞进一个 Detector<Any> 会迫使每个调用点都去 match 输入形态;拆成三个 trait 后,每个调用点都能被类型系统约束住。模块公开了 keyword_detectorline_importancetiered 三个子模块,并在 mod.rs 中重导出 KeywordDetectorKeywordRegistryImportanceCategoryImportanceContextImportanceSignalLineImportanceDetectorTiered

ImportanceContext:一行文本"从哪里来"决定哪套模式生效

ImportanceContext 有四个变体,它决定了哪一组模式会触发:

  • Text——自由文本(text_compressor),markdown 结构有意义;
  • Search——grep/ripgrep 输出(search_compressor),error/warn 关键词占主导;
  • Diff——git diff(diff_compressor),error + security + importance 关键词生效;
  • Log——日志输出(log_compressor),error/warn 关键词 + 级别前缀生效。

例如 markdown 标题在 Text 语境里算优先级信号,在 diff hunk 里就不算。

ImportanceSignal:绝不返回裸 bool

单个检测器对单行文本的输出是 ImportanceSignal,包含三个字段:

字段 含义
category: Option<ImportanceCategory> 命中的类别(Error / Warning / Importance / Security / Markdown),未命中为 None
priority: f32 压缩器排序依据:0.0 = 最先丢弃,1.0 = 无论如何保留
confidence: f32 Tiered 组合器用来决定是否继续问下一层:0.0 = 无信息,1.0 = 检测器确信

它提供两个构造器:ImportanceSignal::neutral()(category 为 None、priority 与 confidence 均为 0.0,表示"我对这行没有意见")和 ImportanceSignal::matched(category, priority, confidence)(命中检测)。is_match() 仅以 category.is_some() 判定是否命中。

trait 定义本身很薄:

pub trait LineImportanceDetector: Send + Sync {
    fn score(&self, line: &str, ctx: ImportanceContext) -> ImportanceSignal;
}

注释明确要求实现必须 Send + Sync,因为压缩器会在 tokio 工作线程之间共享检测器实例;同时实现应当"便宜"(关键词自动机、词法特征)或"可摊销"(embedding + 分类头,配合批量推理)。

Tiered 组合器:以置信度为驱动的短路栈

Tiered<dyn Trait> 把一个有序检测器栈串起来,规则只有一条:ESCALATE_THRESHOLD0.7。核心打分逻辑在 score 实现

impl LineImportanceDetector for Tiered<dyn LineImportanceDetector> {
    fn score(&self, line: &str, ctx: ImportanceContext) -> ImportanceSignal {
        let mut best = ImportanceSignal::neutral();
        for tier in &self.tiers {
            let signal = tier.score(line, ctx);
            if signal.confidence >= ESCALATE_THRESHOLD {
                return signal;
            }
            if signal.confidence > best.confidence {
                best = signal;
            }
        }
        best
    }
}

语义可以拆成三条:

  1. 短路:第一个 confidence >= 0.7 的 tier 立即胜出,后续 tier 不再被咨询;
  2. 落空:低置信度 tier 会被跳过,继续问下一层;
  3. 兜底:如果没有任何 tier 越过阈值,返回"见过的最高置信度信号",让调用方至少拿到一个最佳猜测,且 confidence 分数如实反映了整栈的不确定度。

栈是组合而非继承KeywordDetector 不知道未来 ML 检测器的存在,ML 检测器也不知道关键词检测器的存在——双方各自实现 trait,Tiered 只负责排序。with 构建器要求"最精确的层放最前",with_detector 便捷方法则帮调用点省掉 as Box<dyn …> 的样板。

tiered.rs 的单元测试用两个合成检测器把上述语义逐条钉死:AlwaysFiresHigh(confidence 0.95)放在关键词层之前时,"ERROR: connection refused" 被判为 Security 而不是 Error,证明高置信层先短路;AlwaysFiresLow(confidence 0.5)则必须落到关键词层,结果变为 Error;没有任何层命中阈值时,返回 best-seen(0.5 的 Importance);空栈返回 neutral。

KeywordDetector:aho-corasick 自动机 + 词边界后过滤

当前唯一注册的检测层是 KeywordDetector,它是 Tier-3 模式检测器,用 aho-corasick 一次确定有限自动机扫描就能在一行里找出所有关键词,复杂度 O(n + m),替代了 Python 侧 error_detection.py 的独立正则逐个搜索——关键词集只有一份真源,不会出现"表"与"编译后的模式"漂移。

置信度与优先级常量

常量 作用
KEYWORD_CONFIDENCE 0.7 恰好达到 ESCALATE_THRESHOLD:无歧义的关键词命中不会被下一层"再议",同时给未来 ML 层留出在边缘样本上覆盖的空间(见 L41
ERROR_PRIORITY 0.95 Error 类命中行的保留优先级
SECURITY_PRIORITY 0.85 Security 类
WARNING_PRIORITY 0.75 Warning 类
IMPORTANCE_PRIORITY 0.6 Importance 类(important/todo/fixme/bug…)
MARKDOWN_PRIORITY 0.45 Markdown 结构类(仅 Text 语境)

关键词注册表 KeywordRegistry

KeywordRegistry::default_set() 定义了默认词表(这也是"配置"与"检测逻辑"的分界——静态词表属于检测器的配置,与消费它的检测器放在一起):

  • errorerror, exception, fail, failed, failure, fatal, critical, crash, panic, abort, timeout, denied, rejected
  • warningwarn, warning
  • importanceimportant, note, todo, fixme, hack, xxx, bug, fix
  • securitysecurity, auth, password, secret
  • markdown_prefixes"# ", "## ", "### ", "#### ", "**", "> "(按前缀匹配,不是整行关键词)
  • error_indicatorserror, fail, exception, traceback, fatal, panic, crash(供 contains_error_indicator 快速分诊用的子串集,独立于行打分集)

注册表还通过 as_map() 提供确定性快照(BTreeMap 保证迭代顺序稳定),供 PyO3 侧 Python shim 反射出旧的正则表,保持双语言行为一致。

语境相关的匹配规则

match_in_context 的查找顺序是:先查跨语境通用的 error/importance 自动机,再按语境分流——

  • Diff 语境:security 自动机生效;
  • Text / Search / Log 语境:warning 自动机生效(注意 Diff 不含 warning,这与 Python 的 PRIORITY_PATTERNS_DIFF 形状保持一致);
  • Text 语境:markdown 结构前缀生效。

自动机以 ascii_case_insensitive(true)MatchKind::LeftmostLongest 构建,命中的字节偏移再经过 is_word_boundary 做整词校验——因此 "panicker" 不会误触 panic。另有一个刻意的例外:contains_error_indicator无词边界的子串匹配,因为它服务的是"是否包含 error 形态内容"的快速分诊调用点(如消息签名分类),保留 Python 时代的宽松语义;它使用的词集也不同于 score()(含 traceback、不含 timeout 等四个后补词)。

相对 Python 版本修复的两个 bug(fixed_in_3e1)

文件头注释记录了 2026-04-29 修复、并由 parity 夹具锁定的两处行为差异:

  1. Python 的 ERROR_KEYWORDS 列了 {abort, timeout, denied, rejected},但 ERROR_PATTERN 正则遗漏了这四个词——"Connection timeout" 因此从未被标记为 error。Rust 版把四词纳入自动机消费的 error 集;
  2. Python 的 SECURITY_KEYWORDS 包含 token,会在 input_tokenstokens_saved 等 LLM 度量行上产生大量误报。Rust 版将其从 security 集剔除。

对应的回归测试见 keyword_detector.rs 测试模块timeout_now_classified_as_error_in_diffrejected_now_classified_as_errortoken_no_longer_flags_security_in_llm_proxy_contextauth_still_flags_security_in_diffwarning_fires_in_search_but_not_diffword_boundary_excludes_substring_matches 等,分歧行都带 // fixed_in_3e1 标记;Python 侧的 parity 夹具为 tests/test_signals_keyword_parity.py

检测器如何被 transforms 消费

信号最终以两种方式进入压缩决策:

  • 按类别加权search_compressor.rsboost_errors 开启时调用 importance.score(..., ImportanceContext::Search),把 Error 映射为 +0.5、Warning +0.4、Importance +0.3(对应 Python 的 PRIORITY_PATTERNS_SEARCH 顺序),而 SecurityMarkdown 不加权(bump 为 0.0)——这体现了"类别到业务语义的映射发生在调用点,而不是检测器内部"。该压缩器还提供 with_detector 构造器,允许换入自定义 LineImportanceDetector 实现;
  • 按行计数:日志 offload 路径(log_offload.rs)持有 Box<dyn LineImportanceDetector>(默认 KeywordDetector),对日志行逐行打分统计,再决定哪些行可被卸载。

两处调用点都是"在消费方把检测器接进 Tiered",检测器模块本身不感知其他层——这正是文档第 4 条接入约定的实例。

如何新增一个检测器(项目约定)

文档给出了五步清单,值得作为扩展规范逐条执行:

  1. 确认粒度。逐行分类器实现 LineImportanceDetector;整块分类器走新 trait。不要把跨粒度的工作硬塞进一个 trait;
  2. 实现 score(&self, ...) -> ImportanceSignal。如实设置 confidence:如果你是该输入的权威检测器,给 0.7+;如果希望分歧时让下一层覆盖,就给更低的值;
  3. 禁止静默回退。没有信息时返回 ImportanceSignal::neutral()——绝不用低置信度"编造一个阳性答案"来 fail open;
  4. 在消费方接入 Tiered,而不是在 signals 模块内。检测器自身不知道其他层的存在;
  5. 补齐 parity 夹具。如果新检测器替换或增强了既有实现,为分歧行打上 // fixed_in_<phase> 标记,保留审计线索。

规范的 ML 扩展路径:BGE 分类头

README 认为最可能成为下一层的,是在 relevance::EmbeddingScorer 已加载的 bge-small-en-v1.5 embedder 上加一个分类头。目标形态:

pub struct BgeClassifierDetector {
    embedder: Arc<dyn Embedder>,        // 与相关性打分共享
    classifier: LogisticRegression,     // 384 维 → 4 类 softmax
    threshold: f32,                     // 在验证集上校准
}

impl LineImportanceDetector for BgeClassifierDetector { ... }

这被定位为最省路径,理由有三(见 README):

  • 零新增资产:embedder 已经为 SmartCrusher 的相关性打分加载(relevance/embedding.rs 中默认模型即 BAAI/bge-small-en-v1.5,384 维,经 fastembed/ONNX Runtime 运行)。分类头仅约 1.5 KB 权重、每行约 1 ms 推理(可批处理),不需要新的 ONNX runtime、模型文件或下载;
  • 校准置信度天然契合 Tiered:高置信正样本可以短路 KeywordDetector,边缘样本则让位——而边缘样本恰恰是关键词自动机可靠的区域;
  • trait 形状零改动LineImportanceDetector 对三条路径都直接兼容。

同时保留两个备选,以防 BGE 头欠拟合:

  • 蒸馏 tinyBERT(ONNX)——精度更高,+10–20 MB 模型、+3 ms 延迟,引入新的 ort 依赖;
  • 词法特征上的逻辑回归——输入是比率、行长度、结构标记、栈帧启发式,约 5 KB 模型、三者中最快,适合做 A/B 基线。

这三条路对上层完全透明:它们都只是 Tiered 栈里的新成员。

什么不属于 signals 模块

边界约定同样写在文档里,扩展时不要越界:

  • 具体 transforms——归 crates/headroom-core/src/transforms/
  • 静态关键词数据表——是 KeywordDetector 的配置而非检测逻辑,随消费方放在 signals/keyword_detector.rsKeywordRegistry
  • 标签保护<headroom:keep> 标记)——属于用户意图,不属于分类。

小结

signals 模块用最小的类型面(一个 trait + 一个信号结构体 + 一个组合器)承载了 Headroom Rust 核心的"保留哪些行"决策:ImportanceSignal 的三元组让类别、保留优先级与不确定度彼此解耦,Tiered 的 0.7 阈值把"谁说了算"变成一条可测试、可替换的栈规则,KeywordDetector 则示范了如何在修复 Python 版遗留 bug 的同时用 parity 夹具固化行为。对想扩展检测能力(尤其是小模型层)的开发者,五步接入约定和 BGE 分类头方案给出了从当前 0.7 置信度关键词层平滑演进到 ML 层、且不动任何调用点的完整路线。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384