前言¶
在 DeepSeek Harness(DSH)里,工具调用返回的大段文本会进入模型上下文。Harness 默认按体积截断:保留开头和结尾,中间丢弃。Codex、pi 等 Agent 宿主也常见类似做法。
这种截断不看内容形状。测试输出里 5,000 行通过、中间 3 条失败时,头尾截断往往只留下通过记录,模型会误判测试全部通过。unclecode 的 toolshrink 针对这类问题:先识别输出形态,再保留携带信息的部分,而不是单纯按位置切分。
这是什么¶
toolshrink 是一个 DSH 插件,也可作为 Node.js 库单独使用。维护者为 unclecode,仓库地址:github.com/unclecode/toolshrink。项目在 SkillHub 社区目录的分类为「模型推理」,当前 GitHub 星标约 10。
一句话定位:Cut large agent tool output by what it means, not by where it was cut.(按语义压缩,而非按截断位置。)插件内置 13 种内容感知 reducer;无匹配时回退到按尺寸的截断策略,保证结果始终落在预算内。
与默认截断的差异¶
README 给出一个 vitest 运行示例:输入 31,958 字符、805 行,预算 2,000 字符。
| 方式 | 输出大小 | 模型能读到什么 |
|---|---|---|
| head+tail 截断 | 1,904 字符 | 主要是摘要 |
| toolshrink | 255 字符 | 哪条测试失败、原因、行号,以及摘要 |
被移除的内容会在输出末尾标注数量,例如 ... 15,903 characters, 401 lines omitted ...。若配置了 spill 存储,完整原文会写入磁盘并附带 locator,不会静默丢失。
核心功能:13 种 cut¶
每个 cut 识别一种文本形态,按顺序尝试;第一个匹配的 cut 执行。都不匹配时使用 size 回退。各 cut 遵循四条规则:不返回半行、不拆分 UTF-16 代理对、明确标注移除量、二次调用不再改变结果。
| Cut | 识别对象 | 保留 | 丢弃 |
|---|---|---|---|
diff |
git diff、patch | 变更行、文件与 hunk 头、两侧各 1 行上下文 | 未变更上下文 |
json |
单个 JSON 值 | 结构、长数组每数组 3 条样本、宽对象每对象 5 个键、计数 | 重复记录 |
tests |
vitest、jest、pytest、cargo test、go test | 失败项及说明、摘要 | 通过的测试 |
build |
tsc、cargo、gcc、webpack、esbuild | 错误与警告及代码帧、摘要 | 构建进度 |
stacktrace |
Node、Python、Java、Ruby 堆栈 | 消息与用户代码帧 | 依赖库帧(计数标注) |
log |
带时间戳的日志 | 错误与警告及前序行、结尾 | 常规行 |
tree |
find、ls -R、文件列表 | 目录结构、每目录 8 条、计数 | 拥挤目录的其余条目 |
repeat |
重试风暴、进度刷屏 | 每种模式 2 条样本 + 省略说明 | 连续近重复行 |
lint |
eslint、ruff、clippy | 每条规则的数量与示例位置、最差文件 | 同规则重复出现 |
install |
npm、pip、pnpm、cargo 安装 | 摘要、版本、弃用、漏洞、错误 | 下载进度 |
csv |
CSV、TSV、管道表格 | 表头、开头 5 行、末尾 2 行、行列计数 | 中间行 |
gitlog |
git log(两种格式) | 最新 15 条提交、总数、作者及计数 | 更早提交 |
size |
兜底 | bash:末尾;grep/read:开头;未知:头尾 | 其余部分(计数标注) |
新增 cut 只需一个实现共享接口的文件,无需 fork 整个项目。
安装与启用¶
DSH 采用「一切皆插件」的架构;SkillHub(skillhub.cn)是面向中国用户的 Skills 社区目录,与 DeepSeek / 幻方无官方从属关系。安装前建议浏览仓库源码并确认 MIT 许可证(见 package.json)。插件以当前 dsh 进程权限运行。
一条命令安装到 web profile:
dsh plugin --profile web add github:unclecode/toolshrink
包内带有 dsh.bundle manifest,下次启动时自动挂载,默认字符预算为 50,000。可在用户层 ~/.dsh/cordis.patch.yml 调整:
- id: toolshrink
config:
maxChars: 20000
log: /tmp/toolshrink.log
本地开发时,可 clone 后执行 npm install && npm run build,再通过 insert 挂载适配器文件:
- insert:
- id: toolshrink
name: /path/to/toolshrink/adapters/harness/toolshrink.mjs
config:
maxChars: 50000 # 超过此字符数触发压缩(默认 50000)
maxLines: 2000 # 或超过此行数(默认 2000)
maxLineChars: 0 # 单行长度上限,0 表示关闭(默认 0)
disable: [json] # 跳过指定 cut(默认无)
spillDir: ~/.dsh-toolshrink # 完整原文存放目录
log: /tmp/toolshrink.log # 每次 cut 一行日志,省略则静默
日志行格式示例:bash 64151 -> 2942 via tree+size。
典型用法¶
作为库调用¶
当前版本为 0.1.0(ESM,main 指向 ./dist/index.js)。
import { shrink, FileSpillStore } from 'toolshrink'
const out = shrink(bigText, { tool: 'bash', command: 'npm test' }, {
budget: { maxChars: 20_000 },
spill: new FileSpillStore({ dir: '/tmp/spills' }), // 可选
})
out.content // 交给模型的文本
out.reduced // 输入未超预算时为 false
out.strategy // 如 "tests"、"diff+size"、"size:tail"、"none"
out.note // 人类可读的一行说明
out.stats // inputChars、outputChars、keptLines、droppedLines 等
第二个参数 hint 可选:tool 影响 size 截断方向,command 帮助识别测试与 diff,path 帮助识别 JSON 与日志。
自定义 cut¶
一个 cut 文件默认导出三个成员,文件名即 cut 名:
// mycut.mjs
export default {
name: 'mycut',
detect(text, hint) {
return hint.command?.startsWith('kubectl') ?? false
},
reduce(text, hint, budget) {
const content = text.slice(0, budget.maxChars)
return {
content,
reduced: true,
strategy: 'mycut',
note: 'kept the part I know matters',
stats: {
inputChars: text.length, inputLines: 0,
outputChars: content.length, outputLines: 0,
},
}
},
}
加载并使用:
import { shrink, loadReducers } from 'toolshrink'
const mine = await loadReducers('/path/to/my-cuts')
shrink(text, hint, { extra: mine }) // 优先于内置 cut 尝试
shrink(text, hint, { only: ['tests', 'diff'] }) // 限制并排序
shrink(text, hint, { disable: ['json'] }) // 跳过指定 cut
Spill:完整原文可恢复¶
启用 spill store 后,压缩前会将完整原文落盘,压缩文本末尾附带 locator,例如:
[full output saved as spill:bash-d63d2aebb643: directories sampled to 8 entries each]
store.load('spill:bash-d63d2aebb643') 可字节级还原原文。默认文件存储会在 24 小时后清理;存储后端可替换。
适用场景与注意¶
适合谁
- 在 DSH 或自建 Agent 中频繁调用 shell、测试、构建、lint 等工具,输出常超上下文预算的开发者。
- 需要可审计截断(明确省略量 + 可选 spill)而非静默丢数据的场景。
使用时注意
- 无 cut 匹配时仍走
size回退,行为与「按位置截断」接近;可通过hint与自定义 cut 提高识别率。 - README 提到:在 3,000 字符预算下,60,000 字符的
find结果被头截断后,模型看到省略标记会主动改用聚合查询——这是设计预期,依赖模型读懂 marker。 - 安装与运行权限等同于
dsh进程;spillDir会写入本地磁盘,注意路径与磁盘占用。 - 仓库 TODO 列出计划中的 cut(如
semantic、sql),尚未实现,勿当作现有能力。
结尾¶
toolshrink 把 Agent 工具输出的截断从「按字节切」改成「按形态留」:测试留失败、diff 留变更、日志留错误。对 DSH 用户来说,一行 dsh plugin add 即可挂载;需要更细控制时,改 YAML 或写自定义 cut 即可。
- SkillHub 目录页:https://www.skillhub.cn/plugins/unclecode/toolshrink
- GitHub 仓库:https://github.com/unclecode/toolshrink