首页
/ rtk TOML 过滤器体系详解:编写 Filter 文件、构建期嵌入、运行时管线与信任机制

rtk TOML 过滤器体系详解:编写 Filter 文件、构建期嵌入、运行时管线与信任机制

2026-09-06 15:08:37作者:舒璇辛Bertina

本文围绕 rtk(Rust Token Killer)仓库中的 src/filters/README.md 展开,系统讲解内置 TOML 过滤器的工作方式:哪些命令适合用 TOML 过滤器、Filter 文件的完整字段与编写步骤、build.rs 如何把 src/filters/*.toml 拼接并嵌入二进制、运行时 8 阶段过滤管线与查找优先级,以及自定义过滤器(项目级/用户级)的信任(trust)门控机制。读完本文,你可以直接为新的 CLI 命令写一个可构建、可测试、可信用的 TOML 过滤器,并理解它在 rtk 内部从构建到执行的完整链路。

一、内置过滤器是什么:一个 .toml 文件 = 一个过滤器 + 内联测试

rtk 是一个面向 LLM/Agent 的 CLI 代理:它在命令输出进入模型上下文之前,对 bash 输出做过滤和压缩,官方描述中可削减常见开发命令 60%–90% 的输出体积(见 README)。内置过滤器的核心约定非常简洁,来自 src/filters/README.md

  • 每个 .toml 文件定义一个过滤器及其内联测试[[tests.*]] 段落与过滤器放在同一文件里);
  • 构建时由 build.rs 将所有文件按字母序拼接成一个 TOML 大块(blob),整体嵌入二进制

二、何时使用 TOML 过滤器(以及何时不该用)

原文档给出了清晰的适用边界,这里完整继承并结合仓库佐证:

TOML 过滤器只做噪声行剔除(strip noise lines),不重排输出。过滤后的结果必须仍然"看起来像真实命令的输出"(设计原则见 CONTRIBUTING.md — Design Philosophy)。

TOML 适合逐行可预测的文本输出、且正则过滤能达到 60% 以上节省率的命令,典型场景:

类别 例子 过滤策略
安装/更新日志 brew、composer、poetry 剔除 Using ... / Already installed
系统监控 df、ps、systemctl 保留关键行,去掉表头/装饰
简单 Linter shellcheck、yamllint、hadolint 去掉上下文,保留 findings
基础设施工具 terraform plan、helm、rsync 去掉进度信息,保留 summary

TOML 与 Rust 模块的取舍标准在 CONTRIBUTING.md — TOML vs Rust: Which One? 中有完整对照表:输出是纯文本且行结构可预测、正则能砍掉 60%+ 字节、不需要注入 CLI 参数(如 --format json)、不需要跨命令路由时,选 TOML;反之(JSON 结构化输出、需要状态机解析、需要注入参数、需要路由到其他命令)应写成 Rust 模块(如 vitest、pytest、golangci-lint)。

"看起来像真实命令输出"这一点在源码中有硬性执行者:CONTRIBUTING.md 提到的 guard::never_worse(raw, filtered) 保证 rtk 永远不发出比原始输出更多的 token。此外还有一个容易踩的坑:被 Clap 路由的保留命令不能被 TOML 过滤器接管src/core/toml_filter.rs 定义了 RUST_HANDLED_COMMANDSlsgitghcargonpmtscvitest 等 50 余个),这些命令在到达 TOML 匹配(run_fallback())之前就被专用 Rust 模块处理。如果某个过滤器的 match_command 命中了保留命令,加载时会打印警告:

[rtk] warning: filter 'x' match_command matches 'git' which is already handled by a Rust module — this filter will never activate for that command

三、文件格式与全部字段

一个 Filter 文件的完整格式如下(继承自原文档,可直接复制使用;注意真实文件中 schema_version = 1 是必填的,build.rs 在拼接内置过滤器时会自动写入该行,见 build.rs):

[filters.my-tool]
description = "Short description of what this filter does"
match_command = "^my-tool\\b"          # 正则,匹配完整命令字符串
strip_ansi = true                       # 可选:先剥离 ANSI 转义序列
strip_lines_matching = [                # 可选:丢弃匹配任一正则的行
  "^\\s*$",
  "^noise pattern",
]
max_lines = 40                          # 可选:过滤后只保留前 N 行
on_empty = "my-tool: ok"               # 可选:过滤后输出为空时的兜底消息

[[tests.my-tool]]
name = "descriptive test name"
input = "raw command output here"
expected = "expected filtered output"

可用字段一览

字段 类型 说明
description string 人类可读描述
match_command regex 匹配命令字符串(如 "^docker\\s+inspect"
strip_ansi bool 处理前先剥离 ANSI 转义码
filter_stderr bool 捕获 stderr 并合并进 stdout 再过滤(适用于把横幅打到 stderr 的工具,如 liquibase)
strip_lines_matching regex[] 丢弃匹配任一正则的行
keep_lines_matching regex[] 只保留匹配至少一个正则的行
replace array 逐行正则替换({ pattern, replacement }
match_output array 短路规则({ pattern, message }
truncate_lines_at int 截断超过 N 字符的行
max_lines int 只保留前 N 行
tail_lines int 只保留最后 N 行(在其他过滤之后应用)
on_empty string 过滤后输出为空时的兜底消息

源码补充:表格里没有、但实际存在的字段与约束

结合 src/core/toml_filter.rsTomlFilterDef 反序列化定义,有几个文档表格未列出、实操时需要注意的事实:

  1. head_lines:源码同时支持 head_lines(保留前 N 行)与 tail_lines(保留后 N 行),两者可同时配置——超出部分会以 ... (N lines omitted) 提示行衔接,这符合"截断必须附带恢复提示"的设计原则。
  2. match_output 支持 unless 字段:若 unless 正则也命中整个输出,该短路规则被跳过——用于防止"输出里出现错误信息时仍然被替换成成功短语"(见 src/core/toml_filter.rsapply 实现)。
  3. strip_lines_matchingkeep_lines_matching 互斥,同时设置会导致该过滤器编译失败并被跳过(compile_filter)。
  4. schema_version = 1 必填parse_and_compile 对非 1 的 schema 版本直接报错(src/core/toml_filter.rs)。
  5. deny_unknown_fields:过滤器定义开启未知字段拒绝(src/core/toml_filter.rs),意味着字段名拼错会让整个文件的 TOML 解析失败,运行时只会打一条 warning 然后静默忽略该文件——写自定义过滤器时务必跑一遍 cargo test / rtk verify 验证。

真实过滤器示例

仓库内 60 余个内置过滤器都遵循同一套模式,举四个有代表性的:

  • src/filters/make.tomlmatch_command = "^make\\b",剔除 make[N]: Entering/Leaving directoryNothing to be done 和空行,max_lines = 50on_empty = "make: ok"
  • src/filters/terraform-plan.tomlstrip_ansi = true,剔除 Refreshing state...Acquiring/Releasing state lock、"unchanged" 资源行,max_lines = 80,空结果输出 terraform plan: no changes detected
  • src/filters/df.tomlmatch_command = "^df(\\s|$)"(锚定单词边界,避免误伤 dfx 之类命令),truncate_lines_at = 80 + max_lines = 20,是"截断型"过滤器的典型;
  • src/filters/liquibase.tomlfilter_stderr = true 的教科书案例——Liquibase 把 ASCII 横幅与 INFO [liquibase.core] Reading resource 等日志打到 stderr,合并后再用 10 余条 strip_lines_matching 规则清洗,最终只留版本行与真正的变更集执行结果。

make.toml 的输入/输出为例,可以看到"透明度"约束:

输入:                                   输出:
make[1]: Entering directory '/home/user'   gcc -O2 foo.c
gcc -O2 foo.c
make[1]: Leaving directory '/home/user'

输出仍是一条像样的 make 编译输出,而非 rtk 自创的格式。

四、内联测试与构建期校验

[[tests.<filter-name>]] 段落是 TOML 过滤器的内联测试:每个测试给 input(原始输出)与 expected(过滤后期望),由测试框架驱动 apply_filter 逐条比对。构建与测试链路(原文档 mermaid 图的核心,完整继承如下):

flowchart TD
    A[["src/filters/my-tool.toml\n(new file)"]] --> B

    subgraph BUILD ["cargo build"]
        B["build.rs\n1. ls src/filters/*.toml\n2. sort alphabetically\n3. concat → BUILTIN_TOML"] --> C
        C{"TOML valid?\nDuplicate names?"} -->|"fail"| D[["Build fails\nerror points to bad file"]]
        C -->|"ok"| E[["OUT_DIR/builtin_filters.toml\n(generated)"]]
        E --> F["rustc embeds via include_str!"]
        F --> G[["rtk binary\nBUILTIN_TOML embedded"]]
    end

    subgraph TESTS ["cargo test"]
        H["test_builtin_filter_count\nassert_eq!(filters.len(), N)"] -->|"wrong count"| I[["FAIL"]]
        J["test_builtin_all_filters_present\nassert!(names.contains('my-tool'))"] -->|"name missing"| K[["FAIL"]]
        L["test_builtin_all_filters_have_inline_tests\nassert!(tested.contains(name))"] -->|"no tests"| M[["FAIL"]]
    end

    subgraph RUNTIME ["rtk my-tool args"]
        R["TomlFilterRegistry::load()\n1. .rtk/filters.toml\n2. ~/.config/rtk/filters.toml\n3. BUILTIN_TOML\n4. passthrough"] --> S
        S{"match_command\nmatches?"} -->|"no match"| T[["exec raw (passthrough)"]]
        S -->|"match"| U["exec command\ncapture stdout"]
        U --> V["8-stage pipeline\nstrip_ansi → replace → match_output\n→ strip/keep_lines → truncate\n→ tail_lines → max_lines → on_empty"]
        V --> W[["print filtered output + exit code"]]
    end

    G --> H & J & L & R

build.rs:拼接、校验、嵌定的三步

build.rs 的实现与上图完全对应:

  1. 读取 src/filters/ 下全部 .toml按文件名排序保证确定性顺序(build.rs);
  2. schema_version = 1 为头,逐文件拼接(每个文件带 # --- <文件名> --- 注释分隔)(build.rs);
  3. 整体解析一次 TOML,语法错误直接 panic 让构建失败(build.rs);
  4. 跨文件重复的过滤器名也会让构建失败build.rs);
  5. 结果写入 OUT_DIR/builtin_filters.toml,再由 src/core/toml_filter.rsinclude_str! 在编译期嵌入二进制:
const BUILTIN_TOML: &str = include_str!(concat!(env!("OUT_DIR"), "/builtin_filters.toml"));

cargo test 中的三道"内置过滤器"守门测试

src/core/toml_filter.rs 的测试模块用硬断言防止内置过滤器集合被悄悄改动:

  • test_builtin_filter_count:锁定当前内置过滤器恰好为 63 个——增删 src/filters/ 文件后必须同步更新该数字,否则 cargo test 失败;
  • test_builtin_all_filters_present:断言预期过滤器名全部存在,文件被误删会报 "was its .toml file deleted from src/filters/?";
  • test_builtin_all_filters_have_inline_tests:断言每个内置过滤器至少有一条内联测试,禁止零测试覆盖率出厂;
  • 另有 test_new_filter_discoverable_after_concat:模拟"新增一个 my-new-tool 过滤器后拼接",验证新过滤器 63+1=64 个且可被 find_filter_in 发现——即你提交一个新 .toml 后,构建+测试通过就意味着它在运行时可被发现。

cargo test 外,源码中的 run_filter_testssrc/core/toml_filter.rs)还是 rtk verify 命令的执行引擎,可以按过滤器名单独运行内联测试并报告"没有内联测试的过滤器"。

五、命名规范

原文档约定:

  • 以命令名作为文件名terraform-plan.tomldocker-inspect.tomlmix-compile.toml
  • 有子命令的,优先 <cmd>-<subcommand>.toml,而不是把多个过滤器塞进一个文件。

这个约定配合 build.rs 的"重名即构建失败"机制,使得"一个文件一个过滤器"在物理上不可违背。

六、运行时:注册、匹配与 8 阶段管线

查找优先级(First Match Wins)

运行时按以下顺序查找过滤器(完整继承原文档):

flowchart LR
    CMD["rtk my-tool args"] --> P1
    P1{"1. .rtk/filters.toml\n(project-local)"}
    P1 -->|"match"| WIN["apply filter"]
    P1 -->|"no match"| P2
    P2{"2. ~/.config/rtk/filters.toml\n(user-global)"}
    P2 -->|"match"| WIN
    P2 -->|"no match"| P3
    P3{"3. BUILTIN_TOML\n(binary)"}
    P3 -->|"match"| WIN
    P3 -->|"no match"| P4[["exec raw (passthrough)"]]

项目过滤器与内置过滤器同名时会遮蔽(shadow)内置版本并触发警告:

[rtk] warning: filter 'make' is shadowing a built-in filter

源码中的加载顺序见 TomlFilterRegistry::load:先加载两个磁盘上的受信任自定义文件(gated_filter_paths(),见下一节),再追加内置 BUILTIN_TOML;匹配用 find_filter_in 线性扫描,先注册者先命中src/core/toml_filter.rs)。两个环境变量可用于调试/旁路(src/core/toml_filter.rs):

  • RTK_NO_TOML=1 —— 完全绕过 TOML 引擎;
  • RTK_TOML_DEBUG=1 —— 在 stderr 打印加载了多少过滤器、哪个过滤器命中。

8 阶段过滤管线

apply_filter_with_infosrc/core/toml_filter.rs)按固定顺序应用各阶段,与上表字段一一对应:

  1. strip_ansi — 剥离 ANSI 转义码;
  2. replace — 逐行正则替换,规则顺序串联(支持 $1 回引);
  3. match_output — 全量输出 blob 匹配即短路返回 message(首条命中规则生效,unless 命中则跳过该规则);
  4. strip/keep_lines — 基于 RegexSet 的行过滤(二者互斥);
  5. truncate_lines_at — 每行截断到 N 字符(unicode-safe);
  6. head/tail_lines — 保留前/后 N 行,被丢弃处以 ... (N lines omitted) 提示行标注;
  7. max_lines — 绝对行数上限,超量行以 ... (N lines truncated) 收尾;
  8. on_empty — 结果为空时输出兜底消息。

值得注意的设计细节:管线同时返回一个 Lossiness 分类(src/core/toml_filter.rs),区分"无损剔除噪声"、"尾部可经 tee 恢复"与"整体有损",供上层决定是否附带恢复提示(tee hint)——这正是 CONTRIBUTING.md 中"截断必须附带恢复提示"原则在管线层的落地。

七、自定义过滤器与信任(trust)机制

两个自定义位置

过滤器可以加在磁盘上的两个位置(格式与内置完全相同,均需 schema_version = 1):

  • 项目级.rtk/filters.toml(随仓库提交,仅对该项目生效);
  • 用户级~/.config/rtk/filters.toml(Linux 下即此路径;源码通过 dirs::config_dir() 定位,见 src/hooks/trust.rs,跨平台时对应各系统的用户配置目录)。

信任门控:未信任的过滤器被静默跳过

由于过滤器可以改写 Agent 看到的命令输出,自定义过滤器文件必须先被信任才会生效。未信任(或内容被修改过)的文件在命令路径上被静默跳过——rtk 从不围绕一条被改写的命令打印警告。发现并启用未信任过滤器的入口是你主动执行的命令:

rtk trust       # 列出每个检测到的过滤器(标注 project/global)+ 风险摘要,然后确认([y/N],或 --yes)
rtk untrust     # 撤销信任

信任的存储与失效规则:

  • 信任记录为文件内容的 SHA-256,因此修改一个已信任的文件会使信任失效,必须重新执行 rtk trust(源码中对应的 ContentChanged 状态在 TomlFilterRegistry::extend_with_trusted 中与 Untrusted 一样被跳过);
  • rtk init 会检测已存在的自定义过滤器并交互式确认是否启用([y/N],脚本场景可用 --trust-filters / --no-trust-filters);对只有注释的空模板保持静默,非交互运行时保持过滤器禁用;
  • 内置过滤器编译进二进制,始终受信任,只有磁盘上的项目级/用户级文件受门控。

原文档同时给出了诚实的边界说明(完整继承):

Honest limitation: this is consent + tamper-evidence, not a sandbox. An attacker who can write your filter file can usually also write the trust store (~/.local/share/rtk/trusted_filters.json) and bypass the gate. It defends the common case — a filter dropped in by a script, a dotfile sync, or an untrusted repo — not a same-user attacker who specifically targets RTK.

信任存储文件名在 src/core/constants.rs 中定义为 trusted_filters.json,信任校验与撤销逻辑集中在 src/hooks/trust.rs

八、新增一个过滤器的完整步骤

综合原文档与源码校验机制,实操清单如下(原文档 4 步 + 源码侧约束):

  1. 复制任一现有 .toml 文件并重命名,如 my-tool.toml(文件名即过滤器名,遵循第五节命名规范);
  2. 修改三个必备字段descriptionmatch_command,以及至少一个动作字段(strip_lines_matching / keep_lines_matching / replace / max_lines / on_empty 等);文件需带 schema_version = 1,且不得同时设置 strip_lines_matchingkeep_lines_matching
  3. 添加 [[tests.my-tool]] 内联测试,覆盖"典型输出"与"全部行被剔除后触发 on_empty"两类场景(参考 make.toml 的三条测试写法);
  4. 运行 cargo test:构建步骤会校验 TOML 语法、重名与内联测试存在性。此时还必须同步更新 src/core/toml_filter.rstest_builtin_filter_count 的过滤器总数断言(当前锁定为 63)。

额外检查项(源自源码行为):

  • match_command 不要命中 RUST_HANDLED_COMMANDS 中的保留命令,否则过滤器永远不会生效(加载时会有警告);
  • 字段名拼写错误会让整个文件解析失败并被静默忽略,务必依赖 cargo testrtk verify 验证;
  • 若输出里可能包含错误信息而你想用 match_output 做"成功短语替换",记得配 unless 防止误吞错误。

对于需要注册 hook 重写规则的新命令(例如让 Agent 直接把 my-tool ... 写成 rtk my-tool ...),完整贡献清单见 src/cmds/README.md — Adding a New Command Filter,整体架构与端到端流程可进一步参阅 docs/contributing/TECHNICAL.md

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

项目优选

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