前言¶
在 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