用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吉祥物:一个柠檬榨汁机,把冗长的代码输出"榨"成精华

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 做两阶段标注:

  1. 给定任务背景和原始工具输出,生成一个聚焦的、面向具体调试步骤的提取查询(extraction query)
  2. 在带行号的工具输出中,选出回答该查询的最小连续片段

标注结果会映射回原始文本(确保逐字对齐),正例中无法被工具输出支撑的查询直接丢弃。

测试集的手工审核

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,这篇论文有几个值得借鉴的点:

  1. 工具输出裁剪是低垂果实。数据很清楚——平均95%+的工具输出是噪声。即使不用 Squeez,在你的 Agent 系统里加一层工具输出的过滤/裁剪,性价比都很高。

  2. 小模型 + 窄任务 + 精准数据,能打败大模型的零样本。2B vs 35B,差18倍参数量,但在这个任务上2B赢了。做 Agent 系统时,不是每个子模块都需要用最大的模型,关键是想清楚每个环节的任务边界,然后用最经济的方案解决。

  3. 负例很重要。如果你在训练一个信息提取/过滤模型,一定要加入"什么都不该提取"的负例。不然模型会学成"总是输出点什么"的模式。


收尾

Squeez 不是什么颠覆性的工作,它解决的是一个非常具体的工程问题。但好的工程研究就是这样——把问题定义清楚,用合适的方案解决,然后把数据和代码全开源出来。

在 Coding Agent 越来越火的今天,大家的注意力都放在推理能力、规划能力、多Agent协作这些"高级"话题上。但实际跑起来,卡住你的往往是上下文管理、token成本、推理延迟这些"脏活"。Squeez 做的就是这种脏活——把工具输出的水分挤干净,让Agent的每一个token都花在刀刃上。

有个更深层的问题这篇论文没有回答,但值得思考:如果 Agent 的基座模型本身变得足够强(比如能处理无限长上下文且注意力不衰减),还需要这种前置裁剪吗?我的判断是——短期内肯定需要,因为成本和延迟的约束不会消失;长期来看,这类方案可能会内化到模型本身的注意力机制中,但那是另一个故事了。


觉得有启发的话,欢迎点赞、在看、转发。跟进最新AI前沿,关注我