RTK技术原理揭秘:一次会话节省80%上下文

2026-06-20阅读 0热度 0
技术原理

AI 帮手修前端代码的时候,不会光靠嘴说完成。它会反复调用真实工具:git status 看文件状态、rg Button src 搜组件、pnpm test 跑测试、tsceslint 做静态检查、next build 验证构建——一轮又一轮,执行完根据输出再接着改。

这些命令本身是为人类终端设计的。程序员瞟一眼就能跳过进度条、忽略通过的用例、跳过样板提示,直接定位到错误发生的地方。LLM 没有这个眼睛。AI 助手里的 Bash / shell 执行工具,会把完整的 stdout / stderr 当作 tool result 一股脑塞回上下文。模型被迫同时接收有用信号和大量噪声。

RTK(Rust Token Killer)就是来管这段工具调用开销的。它是一个 CLI / 袋里层工具,卡在 AI 助手和真实开发命令之间。通过 hook 或者 plugin,把 AI 助手准备执行的 Bash 命令改写成 RTK 袋里命令。真实工具照常运行,文件系统副作用和退出码不能变,但返回给模型的是压缩后的输出。

README 里有个数据很说明问题:一次 30 分钟的 Claude Code 会话,原始命令输出大约占了 118,000 token,经过 RTK 压缩后降到约 23,900 token,省了差不多 80%。当然,这个数字不是死承诺,它揭示的是高频命令输出在典型 AI 编程会话里的真实成本。

命令输出为什么会吃掉上下文

AI 助手修一个问题时,会连续跑好几类命令:先看工作区状态,再搜相关代码,然后跑测试或构建。每一条命令的输出都会回到模型上下文里,跟任务描述、已读代码、错误线索挤在一起。

终端输出本来就是按人的阅读方式设计的。git status 带着 Git 的说明文本;vitestjest 会打印通过的用例、进度条和失败栈;tsceslintnext build 把编译信息、规则提示、构建摘要和错误全混在一起;搜索命令可能带回几十个无关命中。

这些东西一股脑儿进入上下文,消耗的是同一块空间。噪声多一行,模型能保留的代码、约束和推理就少一行。上下文接近上限时,模型可能丢掉刚读过的关键文件,也可能忘了上一轮定位到的失败原因。

模型真正需要的信息其实很窄:

命令类型模型需要的信息容易吃掉上下文的内容
Git当前分支、变更文件、diff 摘要模板说明、长 diff 头、重复元信息
测试失败用例、断言差异、错误栈通过用例、进度条、重复日志
TypeScript / Lint / 构建错误文件、行号、规则、失败原因banner、构建进度、长 warning 列表
搜索 / 文件读取命中文件、关键行、必要上下文无关命中、过长文件内容

RTK 就是来解决这个浪费的。它不等模型读完完整的终端日志再自己总结,而是抢在命令输出进入上下文之前,先把适合机器消费的部分提炼出来。

RTK 只处理输出边界

RTK 不修 bug,也不替模型判断代码对不对。它只处理命令输出进入模型之前的那一段。

AI 助手跑 vitest 时,RTK 仍然调用测试框架;助手跑 next build 时,RTK 仍然调用 Next.js;助手跑 git diff 时,RTK 仍然调用 Git。RTK 只压缩真实工具的输出。

它有三大职责:

传话:接住 AI 助手要运行的命令,把它交给真实工具。

整理:过滤真实工具返回的 stdout / stderr,保留模型下一步需要的信息。

记账:记录原始输出和压缩输出的差异,让用户看到省了多少 token。

一句话,RTK 的目标就是减少 LLM 上下文里的无效 token。

Hook 接入:用户写原命令,RTK 自动接管

用户不用每次手写袋里命令。装好 hook 或 plugin 后,AI 助手仍然发起普通 Bash 命令,改写发生在命令执行之前。

hook 接在 AI 助手调用 Bash 工具之前。助手提交原始命令后,hook 可以把这段命令文本替换成另一条命令,再交给执行器去跑。

在 Claude Code 里,RTK 把自己注册成 PreToolUse hook,settings.json 配置大致是这样的:

{"hooks": {"PreToolUse": [{"matcher": "Bash","hooks": [{"type": "command","command": "rtk hook claude"}]}]}}}

模型不需要记住“我应该主动用 RTK”。用户也不用改变和 AI 助手协作的方式。可优化命令在执行前被路由到 RTK,模型收到的是过滤后的结果。

注意,只有 Bash / terminal tool call 会触发这种透明改写。Claude Code 内置的读文件、搜索、Glob 能力不一定经过 Bash hook;这些场景要么显式用 shell 命令,要么直接调用 rtk readrtk greprtk find

RTK 也保留了直接调用方式。用户可以显式跑 rtk greprtk readrtk find,或者用 rtk proxy 跑暂不支持的命令并记录用量。

袋里执行:真实工具照常运行

命令被改写后,RTK 进入袋里执行阶段。这个“袋里”不要求模型换写法,也不依赖用户的 shell alias。

命令改写:PreToolUse 返回新的 Bash 输入

先看 PreToolUse 这类 hook 的通用原理。AI 助手准备调用 Bash 工具时,宿主先启动配置好的 hook 命令,并把这次工具调用的信息通过 stdin 传给 hook。输入里包含工具名和工具参数,比如工具名是 Bash,参数里有 command: "git status"

hook 如果不想介入,可以什么都不返回,让宿主按原样执行。如果要改写 Bash 命令,就往 stdout 返回一段结构化结果,告诉宿主替换这次工具输入。在 Claude Code 这类格式里,关键字段是 hookSpecificOutput.updatedInput.command,它会覆盖原来的 command 字段:

{"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "ask","permissionDecisionReason": "rewrite command before Bash runs","updatedInput": {"command": "rtk git status"}}}}

宿主收到这段返回后,把 updatedInput.command 里的新命令交给 Bash。袋里发生在工具调用参数提交给 Bash 之前。

RTK 用这层机制改写命令文本。改写后的 rtk ... 命令启动后,RTK 进程调用真实的 Git、测试框架、构建工具,捕获 stdout / stderr,过滤后把结果打印回 Bash tool result。

RTK 先保证真实命令照常运行,再处理输出。

RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的

这条链路里有几个必须遵守的底线:

  1. 真实工具仍然执行——Git 还是 Git,测试框架还是测试框架。
  2. 退出码要保留——测试失败、构建失败、Git 命令失败,都不能被压缩过程吞掉。
  3. 不认识的命令要透传——RTK 没有可靠规则时,不强行改写输出。
  4. 过滤失败要可回退——压缩工具不能破坏原始排障路径。

这些底线把 RTK 牢牢限定在开发工具链袋里的位置,而不是一个日志总结脚本。

RTK 如何识别命令类型

RTK 分两层识别命令。

第一层发生在 hook 或 plugin 里。Claude Code 准备执行 Bash 命令时,RTK hook 会拿到原始命令文本。它先判断这条命令有没有已知改写规则:git status 可以改写到 rtk git statusrg Button src 可以改写到 RTK 的搜索过滤链路,vitesttsceslintnext build 这类前端命令也会进入对应袋里路径。

比如 AI 助手想跑 rg Button src。hook 先判断它是搜索命令,再把它交给 RTK 的搜索链路;RTK 运行真实搜索工具后,按文件分组命中行,并截断过长上下文,最后把更短的搜索结果返回给模型。

第二层发生在 RTK 进程内部。命令已经带上 rtk 前缀后,RTK 会根据子命令和工具名路由到具体模块:

原始命令形态RTK 识别到的类型后续过滤重点
git status / git diffGit分支、变更文件、diff 摘要
rg ... / grep ...搜索命中文件、关键行、截断上下文
vitest / jest前端测试失败用例、断言差异、错误栈
tscTypeScript文件、行号、类型错误
eslint / biomeLint文件、规则、错误数量
next build / vite build构建构建错误、关键 warning、摘要

hook 找不到可靠规则时,不改写命令。RTK 内部没有对应过滤器时,走原样透传或通用处理。识别类型只服务于一件事:在有把握压缩时介入。

过滤阶段:按命令类型提取信号

统一的截断解决不了命令输出问题。

测试失败的关键内容可能在末尾;Git diff 的关键内容在变更行;eslint 输出要按规则和文件聚合;tsc 输出要按文件和错误位置整理;搜索输出要按文件分组并截断长行。RTK 因此必须按命令类型选择过滤器。

不同命令的噪声长得完全不一样,过滤器不能只做统一截断。

git status 为例。普通输出适合人看,有分支说明、未跟踪文件提示和 git add 建议。模型需要更短的状态信号:当前分支、文件状态,以及仓库是否处在 rebase、merge、cherry-pick、detached HEAD 这类危险状态里。

RTK 默认用更适合机器解析的状态输出生成短结果,同时从普通 git status 头部补回危险状态。这样既能删掉 Git 的提示语,也不会因为压缩把关键仓库状态藏起来。

RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的

RTK 过滤器先判断哪些信息会影响模型下一步行动,再删掉解释性、重复性和过长的内容。

常见过滤策略包括:

  1. Git 输出压缩:保留分支、变更文件、提交摘要、diff 统计,删掉长说明和冗余头部。
  2. 前端测试失败聚焦:隐藏通过用例,保留 vitestjestplaywright test 的失败用例、断言差异和相关栈信息。
  3. TypeScript / Lint 分组:按文件、规则或错误类型聚合 tsceslintbiome 输出,让模型先看到错误分布。
  4. 构建输出整理:从 next buildvite build 里保留路由摘要、构建错误和关键 warning。
  5. 搜索结果整理:按文件分组命中,截断过长行,避免把整个代码库塞进上下文。

不同命令的“有用信息”不同,RTK 的过滤器必须理解这些差异,而不是用一把尺子量所有输出。

共享 runner:把执行链收在一起

RTK 支持很多开发常用命令:Git、GitHub CLI、pnpmnpmvitestjesttsceslintnext buildplaywright test、文件读取和搜索等。命令种类多,但袋里执行的骨架是相似的。

过滤器负责“某类输出怎样压缩”。runner 负责把真实命令运行、输出捕获、过滤调用、结果打印、统计记录这些通用步骤串起来。

runner 大致支持四类处理方式:

处理方式适合场景结果
一次性过滤输出不长,结构清楚收完整输出后再压缩
看退出码过滤成功和失败需要不同摘要成功极短,失败保留证据
流式过滤输出较长边读边处理
原样通过没有可靠规则保持原始输出

共享 runner 让命令模块可以复用执行逻辑,过滤器只关心一件事:“怎样把这个命令的输出压缩好”。

失败路径:不能只返回一句“失败了”

成功输出可以很短。vitest 通过、tsc 无类型错误、next build 完成、Git add 成功,模型往往只需要一个状态信号。

但失败输出不能压成一句“失败了”。模型下一步要修问题,至少需要知道:

  1. 哪个文件出错。
  2. 哪个测试或构建步骤失败。
  3. 错误位置和错误类型。
  4. 断言差异或相关栈信息。
  5. 必要时怎样回看原始输出。

RTK 必须在压缩率和证据保留之间做取舍。它默认给模型更短的结果,也保留了回看能力:需要时可以查看原始输出,用 verbose 模式拿到更多细节,或者用 tee 保存原始日志。一句话,压缩不能关掉后续排障入口。

TOML 规则:给简单命令做轻量扩展

专门模块适合复杂命令,小脚本不一定需要。

很多团队内部脚本只需要简单规则:删除进度行、保留错误行、去掉 ANSI 颜色码、限制最大行数、输出为空时返回默认摘要。

RTK 用 TOML 规则承载这类轻量扩展。它是一层声明式文本过滤,适合处理输出格式稳定、规则简单、团队经常运行的命令。

比如团队里有一个 api pull 命令,用来从接口平台拉取请求类型和发起请求的代码。原始输出可能长这样:

[auth] refreshing token[download] GET https://api.example.com/openapi.json[cache] schema unchanged: shared/common.json[type] UserProfile -> src/api/types/user.ts[type] OrderDetail -> src/api/types/order.ts[client] GET /api/users/:id -> src/api/user.ts fetchUser[client] POST /api/orders -> src/api/order.ts createOrder[progress] generated 2 type files, 2 client files[done] api pull completed in 4.8s

模型需要知道哪些类型文件变了、哪些请求函数变了、有没有失败或 warning。用 TOML 规则做轻量过滤后,返回可以压成:

[type] UserProfile -> src/api/types/user.ts[type] OrderDetail -> src/api/types/order.ts[client] GET /api/users/:id -> src/api/user.ts fetchUser[client] POST /api/orders -> src/api/order.ts createOrder[done] api pull completed in 4.8s

api pull 适合 TOML,因为输出按行稳定,保留 [type][client][warn][error][done] 这几类行就够了。如果团队以后要解析 OpenAPI JSON、按模块重新分组、合并重复类型、判断 breaking change,那就应该写专用过滤器。

对应的项目级 TOML 规则可以放在 .rtk/filters.toml

[filters.api-pull]description = "Keep generated API types, client functions, and failures"match_command = "^api\s+pull\b"strip_ansi = truekeep_lines_matching = ["^\[type\]","^\[client\]","^\[warn\]","^\[error\]","^\[done\]",]truncate_lines_at = 160max_lines = 80on_empty = "api pull: no changed API output"

这段配置不理解接口结构,只在稳定日志里保留关键行。仓库里的 .rtk/filters.toml 需要用户信任后才会生效,因为它会影响 AI 助手看到哪些命令输出。

TOML 规则降低了扩展成本。团队可以把常见噪声沉淀成配置,不必为了每个小脚本都改 Rust 代码。RTK 仍然保留了专用过滤器来处理 Git、前端测试、TypeScript、构建、Lint 这类复杂输出。

简单命令可以先用 TOML 规则试试。需要理解结构、分组错误、解析 JSON、处理失败路径或流式输出的命令,还是应该写专用过滤器。Git、tscvitestnext buildeslint 这些内置命令走对应的专用过滤器,而不是让项目 TOML 规则直接覆盖。

项目级规则还需要信任机制。仓库里的规则可能来自别人提交,它虽然不是 shell 脚本,但会影响 AI 助手看到什么。RTK 要求用户显式信任项目级规则后再启用。

统计系统:节省量怎样被看见

RTK 会记录原始输出和过滤输出的 token 估算。用户可以通过 rtk gain 看累计节省,也可以用 rtk discover 找还没有接入 RTK 的高浪费命令。

总结

AI 助手写代码时,命令输出很容易变成另一种上下文债务。一次 git status 不算多,几轮搜索、测试、构建叠在一起,模型要读的噪声就会超过真正的线索。

RTK 的做法很克制:命令还是原来的命令,Git、测试框架、构建工具照常运行;进入模型之前,输出先被整理成更适合下一步行动的信息。成功时给状态,失败时保留证据,不认识的命令就透传。

你可以把 RTK 当成 AI 编程会话里的输出闸门。它不替模型写代码,也不替工具做判断;它只让模型少读一些没用的终端文本,多保留一些代码、错误和上下文。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策