caveman-shrink:压缩 MCP/OpenAI 工具目录的 Token 工程——caveman 项目中 fail-open 与可逆压缩的设计拆解
2026/9/7 18:55:38 网站建设 项目流程

caveman-shrink:压缩 MCP/OpenAI 工具目录的 Token 工程——caveman 项目中 fail-open 与可逆压缩的设计拆解

【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman

caveman-shrink 是 caveman 仓库中专用于压缩 MCP/OpenAI 工具定义目录(tool catalog)的产品:它在工具清单撑满上下文窗口之前,删掉注解类元数据、把冗长描述缩减为"引导句 + 全部约束句",同时保证名称、参数、枚举、required以及default/const等参数构造值逐字节存活。读完本文,你将掌握它的 CLI 与 npm 启动器用法、基于 enginetoolschema压缩器的底层压缩规则,以及"结构选择面不变量 + 有界 CCR 可逆恢复"这两条正确性边界的实现与测试证据。

定位:toolschema 压缩器的专用产品面

caveman-shrink 本身不是一套独立的压缩算法。根据 shrink/CLAUDE.md,它是对 engine 中toolschema压缩器的一个薄 Go 封装(thin wrapper),结构压缩器本体位于 engine/compressors/toolschema.go,shrink 只复用、绝不 fork。这一分工在 shrink/CLAUDE.md 的 Conventions 一节中是明确约定:

  • 构建/测试:make product-build PRODUCT=shrink/make product-test PRODUCT=shrink
  • shrink 是toolschema压缩器的专用产品面。engine 的 API/CLI 调用方也可以本地强制走该压缩器(通过engine.Options.Type = "toolschema"),但在 caveman 的受管网关(managed-gateway)路径中,工具数组被保留在冻结的 prompt-cache 前缀里,从不交给它处理;网关上另一条 S2 的 tool-search/deferral 路径也不是压缩。本地缩减的统计口径一律记为inferred
  • 文档同时给出一条未来的商业化前提:如果将来要为这个变换开辟计费路由,必须先有 cache-vs-schema 的成本证明、稳定的逐字节前缀输出,以及一个 eval 门禁。

压缩行为本身分三层(均来自 shrink/CLAUDE.md 首段与 engine/compressors/toolschema.go 的类型注释):

  1. 丢弃注解元数据——examplesexampletitle$comment$schema这类对工具选择没有信息量、却最占字节的 JSON-Schema 注解键;
  2. 缩减自由文本描述——只保留引导句和所有携带约束的句子(规则见后文"两条正确性边界");
  3. 逐字节保留两类 token——选择 token(工具名、参数名、类型、enumrequired)和参数构造值defaultconst、内部$ref目标)。

它在 engine 的安全分级里属于 S4:lossy、模型可见的字节会被改变。这一点可以从 engine/safety/safety.go#L48 的注册表看到:S4: {Class: S4, ByteSafe: false, RequiresCCR: true, Reversible: false}——S4 类要求依赖 CCR(上下文压缩恢复存储)作为安全网。

目录布局与三种入口形态

shrink/CLAUDE.md 的 Layout 一节列出了 shrink 的三块组成,对应仓库中的实际路径:

组成路径职责
shrink/shrink.goShrink(目录 → 压缩,fail-open 的 S4 变换,持久化 CCR 支撑)、Recover(handle → 原始字节)、Lint(逐工具的 inferred token 缩减报告)、SelectionProfile(结构选择面——即"必须存活的东西")
CLIshrink/cmd/caveman-shrink/main.gocaveman-shrink/shrink(stdin→stdout)、lint <file>recover <handle>
npm 启动器shrink/bin/caveman-shrink.mjs + shrink/package.jsonnpx caveman-shrink的 MIT 启动器,围绕预构建二进制工作

库入口 shrink/shrink.go 的核心 API 值得逐一说明:

  • Shrink(input []byte, opts ...Option) (Result, error)——以Type: "toolschema"强制路由到工具的 schema 压缩器(shrink/shrink.go#L117-L124)。它是 fail-open 的:任何解析问题、或结果不更小,都原样返回输入,Ratio为 0、无 handle。
  • Result——OutputTokensBeforeTokensAfterRatioBasis(恒为inferred)、ContentTypeRecoveryHandle(仅当真正压缩时非空)。
  • Recover(handle, opts...)——从与Shrink相同的持久化 store 读取原始字节;未知 handle 返回ccr.ErrNotFound(engine/ccr/store.go#L27-L29),恢复过程从不猜测
  • WithStore(s *ccr.Store)/WithStorePath(path string)——两个 Option 用于指定恢复存储:前者由调用方持有生命周期,后者打开指定路径(":memory:"仅在一次调用内有意义)。不提供 Option 时使用默认的共享持久化 store:CAVEMAN_CCR_DB环境变量,否则~/.caveman/ccr.db
  • Lint(input) (Report, error)——只测量、不提交:对每个工具用 engine 的默认离线 counter 计数,并刻意取"压缩前后较小者"(若压缩结果不更小就保留原值),保证 Lint 的数字不会虚报。
  • SelectionProfile(input) (map[string]ToolProfile, error)——提取每个工具的选择面:参数名列表、每参数的enum值、required列表;description被刻意排除,因为那正是 shrink 要压缩的对象。它支持三种目录形态(shrink/shrink.go#L240-L284 的extractTools):MCP 的{"tools":[…]}、OpenAI 的{"functions":[…]}或扁平数组(含{function: …}嵌套),以及单个裸工具对象;schema 字段则兼容 MCP 的inputSchema与 OpenAI 的parameters

默认存储路径的解析在 shrink/shrink.go#L79-L95:依次尝试CAVEMAN_CCR_DBCAVEMAN_HOME/ccr.db~/.caveman/ccr.db,并会自动创建父目录(0o700),使一台机器上的第一次 shrink 就能成功。

CLI 实操:shrink、lint、recover 三个子命令

CLI 入口在 shrink/cmd/caveman-shrink/main.go。子命令分派逻辑是:显式shrinklintrecoverhelp/-h打印用法,不带子命令时把整个调用当作对 stdin 的 shrink——这使得它可以直接插进管道。

基本用法

# 压缩目录(stdin → stdout);inferred 的 ratio 报告写到 stderr cat tools.json | caveman-shrink > tools.min.json # 从 shrink stderr 报告里打印的 handle 恢复精确的原始字节 caveman-shrink recover ccr_... > tools.original.json # 不承诺压缩、只查看逐工具的缩减量 caveman-shrink lint tools.json

以上三条即 shrink/README.md 的 Use 一节。仓库自带一个可直接拿来试手的示例目录 shrink/testdata/catalog.json:两个工具(search_filesrun_command),带title$schemaexamplesenumdefault等注解,正好覆盖压缩器会处理的所有注解类型:

caveman-shrink lint shrink/testdata/catalog.json cat shrink/testdata/catalog.json | caveman-shrink

有界输入:32 MiB 上限

CLI 对 stdin 有硬性上限。shrink/cmd/caveman-shrink/main.go#L23 定义maxStdinBytes int64 = 32 << 20readBoundedInputio.LimitReader(r, maxBytes+1)读取,超限即返回cave_input_too_large错误——更大的目录直接失败,而不是无界缓冲(shrink/README.md 将其列为 Bounded input 保证之一)。

fail-open 的字节安全语义

shrink/cmd/caveman-shrink/main.go#L46-L63 的runShrink体现了"失败也不改字节"的原则:Shrink返回错误时,CLI把原始输入原样写到 stdout,并在 stderr 输出{"ratio":0,"basis":"informed"…,"note":"passed through: …"};成功时 stdout 是压缩后的目录,stderr 是Result的 JSON(其中recovery_handle字段是后续recover的凭据)。测试侧 shrink/shrink_test.go#L174-L186 的TestShrinkByteSafeOnMalformed锁死了库层行为:喂入{not a valid catalog这样的坏 JSON,Shrink不返回 error,输出与输入逐字节相等,且ratio=0、无 handle。

npm 启动器:无需 Go 工具链

shrink/package.json 声明包名caveman-shrink"bin": {"caveman-shrink": "bin/caveman-shrink.mjs"}、Node>=18、MIT 许可。shrink/bin/caveman-shrink.mjs 是一个 shim:通过ensureBinary定位预构建的 Go 二进制(支持CAVEMAN_SHRINK_BIN环境变量覆盖),stdio: "inherit"地 exec 它并透传退出码;找不到二进制时以退出码 127 失败。按 shrink/README.md,MIT 启动器首次运行会下载匹配的 BSL-1.1 许可二进制,校验密钥签名的 checksum 清单与工件 SHA-256,并缓存在~/.caveman/bin——因此不需要 Go 工具链,也不需要全局安装 Caveman

npx -y caveman-shrink lint tools.json

许可是拆开的:npm 启动器本身是 MIT(shrink/LICENSE.launcher 对应LICENSE.launcher文件),下载到的二进制受 shrink/BINARY_LICENSE.md 中注明的 BSL-1.1 条款约束。shrink/CLAUDE.md 标题中的 "commercial Go core + MIT launcher" 指的就是这个结构。

引擎侧压缩规则:哪些字节会掉,哪些字节必活

shrink 的全部压缩决策发生在 engine/compressors/toolschema.go 的toolSchemaCompressor中。理解四个键集合和描述缩减算法,就理解了 shrink 的完整行为面。

元数据丢弃集与必保留集

engine/compressors/toolschema.go#L28-L45 定义了两张核心表:

// 直接丢弃:schema 注解元数据(可经 CCR 恢复) var schemaMetaDrop = map[string]bool{ "examples": true, "example": true, "$comment": true, "title": true, "$schema": true, } // 逐字节保留:承载"参数构造"语义的键(不递归、不截断) var keepSchemaKeys = map[string]bool{ "enum": true, "required": true, "default": true, "const": true, }

源码注释里有一个值得注意的设计动机:default被刻意排除在丢弃集之外,因为它是"agent 省略参数时所依赖的值"——丢掉default会静默改变调用行为;const同理(它钉死了唯一合法值)。

第三张表 userDefinedKeys(properties$defsdefinitionspatternPropertiesdependentSchemasdependentRequireddependencies)标记了键名本身由用户控制的位置:一个叫title的属性名是用户命名,不是 schema 元数据,必须逐字存活;压缩器只递归压缩这些键下面的 schema 值。compressSchema(engine/compressors/toolschema.go#L191-L230)用一个inUserKeys布尔位区分这两种语境。

描述缩减:小额预算整体保留 + 约束句全保留

compressDescription(engine/compressors/toolschema.go#L269-L289)的规则是:

  1. 小额预算整体保留。常量maxDescLen = 80(L18),smallDescBudget = maxDescLen * 2 = 160字节(L260)。描述若 ≤160 字节,整句保留、一个句子都不剪——源码注释解释了为什么:短描述是常见情形,为一个省不了多少字节的小描述冒"漏掉无标记约束"的风险不值得。
  2. 大描述 = 引导句 + 全部约束句。对超过预算的描述,先按句号切句(splitSentences),然后遍历:带约束标记的句子永不丢弃、永不截断;第一个非约束句作为引导句保留,按 rune 边界截到 80 字节(capRunes保证不拆 UTF-8 字符、尽量退回词边界);其余非约束句丢弃(可经 CCR 找回)。

约束标记是一个刻意写得的正则 constraintRe,匹配整词/短语,涵盖四类语义:

  • 义务/禁止:mustmust notcannotcan'tshallrequire*reject*disallow*forbidden
  • 合法性判断:invalidnot allowednot permitted
  • 数量/互斥/边界:exactly oneonly one ofone ofat leastat mostmutually exclusiveonlyuniquecase-sensitivemaxminmaximumminimumrangebetweengreater/less/more/fewer thanno more/less thanoverabovebelowunderbeyondexceed*
  • 格式锚点:format*iso[- ]?数字*rfc[- ]?数字*absolute(后两者允许尾随数字串,以便RFC3339ISO8601命中)。

源码注释明确给出了取舍原则:匹配宁多勿少(over-keep)——多留一句只损失一点 ratio,而丢掉一个约束句会造出非法工具调用和重试循环,代价远超 shrink 省下的字节。

句子切分splitSentences(engine/compressors/toolschema.go#L296-L330)专门处理了英文缩写陷阱:只有当句号后跟空白+大写字母、且句号不是某个缩写(e.gi.eetcvs等,见 abbrevSet)的结尾时才切句。这保证 "e.g. /srv/x" 不会被截成 "e."——shrink/CLAUDE.md 里把这一反例写得很直白:把 "Target path, e.g. /srv…" 截成 "Target path, e." 会产生非法调用,而结构 profile 根本看不见这类错误。

信封与 schema 的分流

压缩器还区分"provider 工具信封"与裸 JSON Schema:compressDocument(engine/compressors/toolschema.go#L112-L133)识别tools数组、function嵌套、单工具对象;compressToolEnvelope对工具级description走描述缩减,对声明为 schema 值的字段才进入compressSchema。MCP 注解里模型可见的title等信封字段不受 schema 元数据规则影响。

两条正确性边界:结构选择面与参数有效性

shrink/CLAUDE.md 用"Two correctness boundaries"一节划定了这个产品能证明什么、不能证明什么,这是全文最核心的诚实性声明。

边界一:结构选择面不变量

不变量是:

SelectionProfile(input)==SelectionProfile(Shrink(input).Output)恒成立。

这证明了工具名、参数名、enum值、required列表逐字节存活。但它不能证明模型会选中同一个工具——因为描述是模型可见的,而长描述的缩减是有损的。行为等价需要模型 eval 固件,当前结果因此保持inferred

库级测试TestStructuralSelectionSurfaceInvariant(shrink/shrink_test.go#L105-L127)就是这条不变量的执行体:对压缩前后分别取SelectionProfilereflect.DeepEqual,同时验证mode参数的 3 个 enum 值一个不少。

边界二:参数有效性(偏向 over-keep)

Agent 是从描述里构造工具参数的,所以"选择面存活"并不等于"参数合法"。shrink 的保证(偏保守地多留)是两条:

  1. 一个已经装得下小额预算(≤maxDescLen * 2字节)的描述整体保留——不剪任何句子,因此即便约束句没有使用任何被识别的标记词,它也活下来;
  2. 对真正大的描述,保留引导句 + 每个带约束标记的句子(完整保留,见上一节的标记清单)。

default、RFC3339 或边界规则会产生结构 profile 看不见的非法调用;为此 engine 侧有 call-validity 一致性测试(golden fixture + 复现的 under-keep 用例,位于engine/compressors测试中),把已识别的约束 token 钉死在行为里。shrink 产品层的对应测试是TestShrinkPreservesArgumentConstraints(shrink/shrink_test.go#L134-L172),断言压缩输出必须仍然包含:

  • "must be an absolute path"(绝对路径规则)
  • "exactly one of body"(互斥约束)
  • "format must be RFC3339"(时间戳格式)
  • "e.g. /srv/x"(缩写不被截成 "e.")

并且长描述里的填充散文(This trailing clause is filler…)应当被丢弃。shrink/CLAUDE.md 的总结语是产品的行为准则:"Over-keep is the rule — never knowingly under-keep."

Reversible for real:持久化 CCR 恢复存储

shrink/CLAUDE.md 的 Gotchas 第三条("reversible for real")描述了一次真实的缺陷修复:早先的实现在返回时关闭的内存store 里铸造 handle,导致"Shrink 一返回,handle 就永远无法解析"——可逆性声明在每次调用上都是假的。现在的设计是:

  • 先提交、后发布。一次真正压缩的 shrink 会把精确的原始字节写入持久化store——engine 的Compress在发布变换后的字节之前提交恢复行(shrink/shrink.go#L112-L116 的注释)。因此任何非空的RecoveryHandle都能稍后、跨进程、经Recover/caveman-shrink recover解析。默认 store 是共享的CAVEMAN_CCR_DB/~/.caveman/ccr.db,与 engine CLI、MCP server、网关使用同一个。
  • store 打不开则不承诺可逆。若持久 store 无法打开,库的Shrink返回 error,CLI 捕获后转发原始字节:没有 handle,也就没有可逆性声明。

两条测试锁死这个语义。TestShrinkRoundTripsThroughAFreshStore(shrink/shrink_test.go#L76-L99)是"可逆性门禁":第一次ShrinkWithStorePath(dbPath),然后用同一路径全新打开一个 store 实例(模拟一个recover进程看到的状态)取回字节,与原始 catalog 逐字节比较——注释特别指出,旧实现下handle != ""的自证检查恰好掩盖了这个 bug。TestShrinkReturnsPassThroughResultWhenCCRIsUnavailable(shrink/shrink_test.go#L188-L203)则验证:store 已关闭时,Shrink返回 error,且附带一个"完整记账的原始字节直通结果"(Ratio=0、无 handle、TokensAfter == TokensBefore),让库调用方既能安全转发又能感知操作失败。

恢复端同样有边界测试:TestRecoverUnknownHandleFails(shrink/shrink_test.go#L205-L209)断言未知 handle 必须失败——"recovery never guesses"。

诚实性不变量与统计口径

shrink/CLAUDE.md 最后三条 Gotchas 汇总成产品的诚实性不变量,值得逐条对照源码验证:

  • S4 lossy、fail-open:成功压缩会改变模型可见的字节(S4 在 engine/safety/safety.go 中ByteSafe: false);畸形/不可压缩的目录原样直通(ratio:0、无 handle)。直通语义在库层由TestShrinkByteSafeOnMalformed、CLI 层由runShrink的 error 分支共同保证。
  • inferred-only:token 数来自 engine 的离线 counter,是估计值;永远是inferred,从不verified,也从不二次投影。LintReport.Basis硬编码为engine.BasisInferred(shrink/shrink.go#L183),TestLintReportsInferredReductions(shrink/shrink_test.go#L211-L230)验证两个工具各自有名、总量确有下降。
  • reversible for real:如上节所述,持久 store、先提交后发布、跨进程可解析;store 不可用时宁可报 error 也不发 handle。

ratio 的计算口径也值得说明:ratio(before, after) = (before-after)/before,且当after >= before时直接记 0(shrink/shrink.go#L326-L331)——ratio 永远不会是负数,"压不动"与"没压"在报告里是同一个信号(0)。

小结

caveman-shrink 用很小的表面(一个库文件 + 一个 CLI + 一个 npm shim)实现了工具目录压缩的完整产品语义:压缩决策全部委托给 engine 的toolschema压缩器,产品层负责存储寻址(CAVEMAN_CCR_DB/~/.caveman/ccr.db)、有界输入(32 MiB)、fail-open 直通和跨进程可逆恢复。它的两条正确性边界——结构选择面不变量与偏向 over-keep 的参数构造保真——分别由SelectionProfile对比测试和约束句保留测试钉死,而"全部数字都是 inferred"的口径则避免了结构检查被误读为行为等价。相关深入阅读路径:shrink/CLAUDE.md、shrink/README.md、engine/CLAUDE.md、engine/compressors/toolschema.go、shrink/shrink_test.go。

【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询