用2B小模型给Coding Agent的工具输出"挤水分":砍掉92%的token,召回率反而更高
你用 Coding Agent 写代码的时候,有没有注意过一个特别浪费的事情?
Agent 调用一次 grep,返回了800行结果。它真正需要的可能就那么两三行。调用 read_file 读一个源文件,1600多个token灌进上下文,但跟当前任务相关的就80来个token。更夸张的是 type_check 的输出,平均3400多个token,有用的就39个。
这些"垃圾信息"不仅浪费上下文窗口的宝贵空间,还会干扰模型的注意力——你塞进去越多无关内容,模型越容易走神。
这篇来自 KR Labs 的论文,提出了一个干净利落的方案:训练一个2B参数的小模型,专门干一件事——在工具输出送进 Agent 大脑之前,把没用的部分精准裁掉。
核心摘要
Squeez 解决的是 Coding Agent 工作流中一个非常具体但非常痛的问题:工具调用返回的观测信息太长,绝大部分内容跟当前推理步骤无关。作者构建了一个 11,477 条样本的 benchmark(覆盖27种工具类型),在 Qwen 3.5 2B 上用 LoRA 微调,做到了 0.86 recall、0.80 F1,同时砍掉92%的输入token。更有意思的是,这个 2B 的小模型在 recall 上比 18 倍大的 Qwen 35B 零样本高出 11 个百分点。这不是一个"大模型做不好所以换小模型"的故事——这是一个"窄任务 + 精准数据 + LoRA微调"能打败通用大模型的工程案例。
论文信息
- 标题:Squeez: Task-Conditioned Tool-Output Pruning for Coding Agents
- 作者:Adam Kovacs
- 机构:KR Labs
- 链接:https://arxiv.org/abs/2604.04979
- 代码:https://github.com/KRLabsOrg/squeez
- 日期:2026年4月4日

Squeez的吉祥物设计很直观——一个可爱的柠檬榨汁机,把混杂着各种代码片段的冗长工具输出"榨"成几滴精华。名字取得好,Squeeze(挤压)去掉一个e,既点题又好记。
问题动机:Agent的"信息过载"困境
现在的 Coding Agent(不管是 SWE-bench 上跑的研究原型,还是 Claude Code、Cursor 这类产品),工作模式都差不多:调工具 -> 拿到观测 -> 推理下一步 -> 再调工具。这个循环里,每一步工具返回的内容都会被完整塞进上下文。
问题在哪?我拿论文里的数据给你感受一下:
| 工具类型 | 样本数 | 平均输入token | 平均有用token | "水分"比例 |
|---|---|---|---|---|
read_file | 3,768 | 1,677 | 84 | 95% |
grep | 1,330 | 779 | 19 | 97.6% |
git_log | 720 | 161 | 11 | 93.2% |
type_check | 317 | 3,418 | 39 | 98.9% |
tsc | 229 | 1,444 | 56 | 96.1% |
pip_install | 441 | 438 | 79 | 82% |
看到 type_check 那行了吗?3418个token进去,39个token有用。98.9%都是噪声。
你可能会想:现在上下文窗口不是越来越大了吗,128K、200K都有了,塞点垃圾信息又怎样?
两个原因。一是成本,每个token都要钱,Agent跑一个SWE-bench任务动辄几十轮工具调用,累积起来非常可观。二是——也是更关键的——注意力稀释。已经有不少研究发现,上下文越长,模型在中间位置的信息上表现越差("lost in the middle"问题)。你把无关信息一股脑塞进去,模型可能反而更难找到真正关键的那几行。
方法核心:问题界定得很窄,但很精准
Squeez 做的事情可以用一句话说清楚:
给定一个聚焦查询(focused query)和一段工具输出(tool output),返回最小的、逐字保留的证据块(verbatim evidence block)。
注意几个关键约束:
- 逐字保留(verbatim):不做改写、不做摘要,直接从原始输出中抠出连续片段。这很重要,因为代码和日志的格式信息本身就有语义,改写可能丢失关键细节。
- 任务条件化(task-conditioned):不是无差别压缩,而是根据当前推理步骤的具体需求来裁剪。同一个
grep输出,找bug和理解架构可能需要保留完全不同的片段。 - 单次工具输出:每次只处理一个工具调用的返回值,不做跨轮次的上下文管理。
这个问题界定很克制。它不试图替代整个上下文管理系统,只做一件事:在工具输出进入Agent上下文之前,把明确无关的部分去掉。
和 LLMLingua 这类通用压缩方案的区别
说到 prompt 压缩,很多人会想到微软的 LLMLingua 系列。LLMLingua 的思路是用一个小语言模型来评估每个 token 的困惑度,低困惑度的token被视为冗余可以删除,能做到 20 倍压缩。
Squeez 走的是完全不同的路:
| 维度 | LLMLingua | Squeez |
|---|---|---|
| 压缩粒度 | token级别 | 行/块级别 |
| 是否保持原文 | 删除token后文本可能不连贯 | 严格逐字保留连续片段 |
| 是否任务相关 | 通用压缩 | 条件化于当前查询 |
| 适用场景 | 通用prompt | 结构化工具输出(代码/日志/命令行) |
| 输出可读性 | 压缩后的token序列 | 人类可读的原始代码/日志片段 |
对于 Coding Agent 的场景,Squeez 的做法更合理。你想想,一个 TypeScript 的类型检查报告里,行级别的完整性比 token 级别的统计压缩更重要。删掉几个 token 可能让一行错误信息变得不可解析,但删掉整段不相关的文件检查结果则不会丢失任何有用信息。
数据集构建:这块做得扎实
数据来源
数据集由三部分组成:
| 来源 | 原始输入 | 最终发布 | 说明 |
|---|---|---|---|
| SWE-bench 派生 | 10,713 | 9,205 | 来自真实仓库的工具交互 |
| 合成正例 | 2,039 | 1,697 | TypeScript/Go/Rust/Java/Docker/Terraform/K8s |
| 合成负例 | -- | 575 | 故意错配query和tool output |
| 合计 | 12,752 | 11,477 |
SWE-bench 提供了真实场景的丰富性,但只覆盖 Python 生态。作者通过合成数据补充了 TypeScript、Go、Rust、Java 这些生态的工具输出(比如 tsc 类型检查、cargo build、Docker 日志等),让模型能泛化到多语言场景。
合成负例这个设计值得说一下:575条样本是故意把查询和工具输出搞错配的。模型在这些样本上应该输出空——"这段输出里没有你要找的东西"。这对实际部署很重要,不然模型每次都硬挤出点东西来,反而引入噪声。
标注流程
用 openai/gpt-oss-120b 做两阶段标注:
- 给定任务背景和原始工具输出,生成一个聚焦的、面向具体调试步骤的提取查询(extraction query)
- 在带行号的工具输出中,选出回答该查询的最小连续片段
标注结果会映射回原始文本(确保逐字对齐),正例中无法被工具输出支撑的查询直接丢弃。
测试集的手工审核
618条测试样本是手动审核过的——从729条候选中剔除了111条(15.2%),包括近似重复、输出过短(1-2行)、标注的span过宽、或者标注错误的样本。
坦率讲,对于一个自动标注的数据集来说,这个质控流程做得比较认真。很多论文的自动标注数据集直接拿来用,根本不做人工审核。
工具覆盖面
数据集覆盖了 27 种工具类型,分布如下(部分展示):
| 工具 | 样本数 | 工具 | 样本数 | 工具 | 样本数 |
|---|---|---|---|---|---|
| read_file | 3,768 | ls | 347 | build_output | 168 |
| grep | 1,330 | type_check | 317 | docker_build | 151 |
| git_log | 720 | git_blame | 291 | make_cmake | 133 |
| python | 698 | npm_build | 230 | kubectl | 123 |
| test_output | 546 | tsc | 229 | cargo_build | 122 |
| curl | 493 | npm_install | 227 | go_build | 120 |
| pip_install | 441 | coverage | 198 | mvn_gradle | 114 |
| lint_output | 184 | docker_logs | 198 | terraform | 103 |
27种工具,从文件读取到包管理,从CI/CD到容器编排,覆盖面相当广。这个分布也很能说明实际 Agent 的使用模式——read_file 占了最大头,毕竟 Agent 最常做的事就是读代码文件。
模型与训练
技术选型很直接:
- 基座模型:Qwen 3.5 2B
- 微调方式:LoRA
- 序列长度:20,000 token
- 训练配置:batch size 8, gradient accumulation 4, lr \(2 \times 10^{-4}\), warmup 0.05, weight decay 0.01
- 硬件:单张 A100 80GB
- 训练轮次:3 epoch
推理时把 LoRA adapter 合并进基座,用 vLLM 部署。同时提供了 CLI 工具,可以直接接收管道输入的工具输出。
2B 参数量这个选择很务实。作为 Agent 工作流里的前置过滤器,推理延迟和资源占用必须足够低。如果用一个 70B 的模型来做裁剪,省下的 token 成本可能还不够覆盖裁剪模型本身的推理开销。
实验结果
主实验
在618条手动审核的测试样本上:
| 模型 | Precision | Recall | Strict F1 | Exact Match | F1 | Compression |
|---|---|---|---|---|---|---|
| Squeez-2B | 0.80 | 0.86 | 0.79 | 0.49 | 0.80 | 0.92 |
| Qwen 3.5 35B (zero-shot) | 0.74 | 0.75 | 0.70 | 0.39 | 0.73 | 0.92 |
| Kimi K2 (zero-shot) | 0.61 | 0.53 | 0.53 | 0.30 | 0.68 | 0.94 |
| Qwen 3.5 2B base (zero-shot) | 0.42 | 0.53 | 0.41 | 0.19 | 0.55 | 0.82 |
| BM25 (10%) | 0.13 | 0.22 | 0.13 | 0.01 | 0.23 | 0.90 |
| First-N (10%) | 0.07 | 0.14 | 0.08 | 0.02 | 0.16 | 0.91 |
| Random (10%) | 0.07 | 0.10 | 0.07 | 0.01 | 0.20 | 0.91 |
| Last-N (10%) | 0.05 | 0.05 | 0.04 | 0.01 | 0.14 | 0.91 |
几个值得关注的点:
2B打赢35B。Squeez-2B 的 recall 0.86,比 Qwen 35B 零样本的 0.75 高了 11 个百分点。参数量差了 18 倍,但在这个窄任务上,精准的监督信号比模型规模更管用。这其实是个老道理了——task-specific fine-tuning 在窄领域往往能打败通用大模型的零样本能力,但在 Agent 系统的工程实践中,很多团队还是倾向于"用更大的模型硬扛"。
启发式方法全面溃败。BM25 是所有启发式方法里最好的,但 recall 也只有 0.22。First-N(取前10%行)、Random、Last-N 的 recall 都在 0.14 以下。这说明工具输出中有用信息的位置分布是不可预测的——不像很多人直觉以为的"错误信息在最后几行",实际上关键证据可能在开头、中间或结尾的任何位置。
Kimi K2 的表现有点意外。作为一个大模型,K2 的 recall 才 0.53,比 Squeez-2B 差了整整 33 个百分点。不过 K2 的 compression 最高(0.94),说明它倾向于裁剪得更激进,但也因此漏掉了更多关键信息。
关于 Exact Match 只有 0.49。说实话,我觉得这个数不算低。考虑到任务是在可能有几千 token 的输出中精确定位几十 token 的片段,做到接近一半完全匹配已经相当不错了。而且论文的评估指标设计比较合理——用的是行级别的模糊匹配(substring similarity > 0.5 阈值),不是严格的字符级精确匹配。
负例处理能力
这个点很关键,但容易被忽略。当工具输出中确实不包含查询所需的信息时,模型应该返回空输出。
Squeez-2B 在负例上的准确率是 80%,而 Qwen 35B 只有 7%。
7%是什么概念?大模型在93%的负例上都"编"了一些输出。这在实际 Agent 系统中是很危险的——如果裁剪模块没法说"这里没你要的东西",它就会从不相关的文本中抠出看起来像是答案的片段,往下游注入噪声。
定性分析
论文给了几个典型case:
| 模式 | 场景 | Squeez表现 | 基线错误 |
|---|---|---|---|
| 结构化输出精准选择 | git_log 21行中定位特定commit | 精确命中3行commit信息 | 35B选了相邻但无关的commit |
| 噪声日志中找关键块 | 长篇测试输出中找failure | 正确定位失败块 | 返回整段输出或随机片段 |
| 负例正确拒绝 | 查询内容不在输出中 | 返回空 | 强行抠出不相关文本 |
失败案例主要是"语义相邻"的错选——比如在文件列表中选了同目录下的另一个文件,或者在 git log 中选了同模块的相邻 commit。这类错误的特点是"选对了区域,但选错了具体条目",说明模型已经有了不错的语义理解能力,只是在细粒度区分上还有提升空间。
我的判断
亮点
问题定义得干净。不试图做一个万能的上下文管理器,就只做"单次工具输出 + 聚焦查询 -> 最小证据块"。这种窄定义让整个系统从数据构建到模型训练到部署都非常清晰。
数据集构建有诚意。27种工具类型、多语言生态覆盖、手动审核的测试集、负例设计,这些都不是论文为了凑contribution加上去的,而是实际部署需要的。
工程上可插拔。2B模型 + vLLM部署 + CLI接口,现有的 Agent 系统可以很低成本地把 Squeez 加到工具调用和推理步骤之间,不需要改核心逻辑。
问题和局限
缺少端到端评估。这是论文自己也承认的最大问题。Squeez 在"裁剪准确度"这个proxy metric上表现很好,但裁剪后的内容是否真的让 Agent 更好地完成了下游任务(比如在 SWE-bench 上的解题率)?没有这个数据。
这不是小事。有可能存在这样的情况:Squeez 裁掉了一些看起来不相关但实际上对模型推理有帮助的上下文信息。比如一段代码文件的import部分,从查询的角度看可能不需要,但它能帮助模型理解项目结构。没有端到端实验,这些可能性无法排除。
教师模型的天花板。标注用的是 GPT-OSS-120B,标注质量受限于这个教师模型。如果教师模型本身在某些工具类型上理解有偏差(比如对 Kubernetes 日志的理解不如对 Python 错误的理解),那训练数据里就会有系统性的偏差。
span overlap 作为评估指标有局限。两段选中的文本,overlap 可能一样,但信息价值完全不同。比如一段 grep 输出里,命中了正确的文件名那一行和命中了文件内容那一行,overlap 算下来可能差不多,但对下游推理的价值天差地别。
没有和同期的上下文管理方案对比。最近 Agent 社区有不少上下文管理的实践方案(比如 OpenClaw 的 context pruning、Claude Code 的上下文压缩策略),论文没有和这些工程方案做对比,有点遗憾。当然这些方案很多没有正式论文,做对比有难度,但至少可以讨论一下差异。
工程启发
如果你在做 Coding Agent,这篇论文有几个值得借鉴的点:
-
工具输出裁剪是低垂果实。数据很清楚——平均95%+的工具输出是噪声。即使不用 Squeez,在你的 Agent 系统里加一层工具输出的过滤/裁剪,性价比都很高。
-
小模型 + 窄任务 + 精准数据,能打败大模型的零样本。2B vs 35B,差18倍参数量,但在这个任务上2B赢了。做 Agent 系统时,不是每个子模块都需要用最大的模型,关键是想清楚每个环节的任务边界,然后用最经济的方案解决。
-
负例很重要。如果你在训练一个信息提取/过滤模型,一定要加入"什么都不该提取"的负例。不然模型会学成"总是输出点什么"的模式。
收尾
Squeez 不是什么颠覆性的工作,它解决的是一个非常具体的工程问题。但好的工程研究就是这样——把问题定义清楚,用合适的方案解决,然后把数据和代码全开源出来。
在 Coding Agent 越来越火的今天,大家的注意力都放在推理能力、规划能力、多Agent协作这些"高级"话题上。但实际跑起来,卡住你的往往是上下文管理、token成本、推理延迟这些"脏活"。Squeez 做的就是这种脏活——把工具输出的水分挤干净,让Agent的每一个token都花在刀刃上。
有个更深层的问题这篇论文没有回答,但值得思考:如果 Agent 的基座模型本身变得足够强(比如能处理无限长上下文且注意力不衰减),还需要这种前置裁剪吗?我的判断是——短期内肯定需要,因为成本和延迟的约束不会消失;长期来看,这类方案可能会内化到模型本身的注意力机制中,但那是另一个故事了。
觉得有启发的话,欢迎点赞、在看、转发。跟进最新AI前沿,关注我