昨天我用 Claude Code 写了一条 commit message。diff 是一行修改:一个注释里的拼写错误。Claude Opus 读取了 diff,思考了两秒钟,生成了 fix: correct typo in auth comment。为此,它用了大约 800 个输入 token 和 30 个输出 token,分别花费 $15 和 $75 每百万 token。总成本:不到一分钱。但是,把这个过程放大到一天 40 次 commits,250 个工作日,一家公司有 200 名开发者使用 coding agents,那么这些看似微不足道的花费会积累成数千美元的开销,付出的只是等同于贴创可贴的智力劳动。
问题并不是 Opus 太贵。问题在于 coding agents 不会区分 $0.001 的任务和 $0.10 的任务。一切都通过同样的大模型执行。生成 commit 信息、分类一个 issue、验证格式 —— 全部使用高成本的模型,就像雇用一位外科医生来贴创可贴。
数据与成本
我们用 Claude Opus 4(上一代,目前大部分生产环境还在用的版本)的价格来算一个账:
| 任务 | 输入 Token 数 | 输出 Token 数 | 成本 |
|---|---|---|---|
| 生成 commit 信息(小型 diff) | ~800 | ~30 | $0.014 |
| 分类某个 issue | ~500 | ~50 | $0.011 |
| 验证 commit 格式 | ~300 | ~20 | $0.006 |
| 生成 standup 报告 | ~2000 | ~200 | $0.045 |
这些任务都不需要一个拥有 2 万亿参数和多步推理能力的大模型。这些仅仅是带有强约束的分类和生成任务。本质上就像是根据颜色对卡片进行分类。
而使用苹果 Apple Intelligence 的 on-device 模型(3B 参数,内置于 macOS 26):成本 $0.00,延迟约 300ms,无需网络,无需 API 密钥。
foundation-hooks
foundation-hooks 是一套 Swift 编写的四个二进制文件,利用苹果的 Foundation Models 框架自动化开发过程中不需要耗费云端模型资源的任务:
| 二进制 | 功能 | Git Hook |
|---|---|---|
fm-commit-msg | 根据 diff 自动生成常规 commit 信息 | prepare-commit-msg |
fm-validate-msg | 验证 commit 信息格式,提供修正建议 | commit-msg |
fm-lql-create | 分类并通过 lql 在 Linear 创建 issue | CLI |
fm-lql-standup | 根据 git log + issues 生成 standup 总结 | CLI |
这四个工具都遵循同一个模式:定义一个带有 @Generable 注解的 Swift struct,传递最低限度的上下文给模型,只需几毫秒就能生成结构化输出。
安装步骤:
git clone https://github.com/frr149/foundation-hooks
cd foundation-hooks
make build && make install-hooks REPO=/path/to/your/repo
从这之后,每一次 git commit 都会自动生成一个符合规范的信息。这些 hooks 已经在 11 个生产仓库中安装,并测试了两周。
原理:@Generable 与 constrained decoding
下面是技术亮点所在。@Generable 并不是「在 prompt 中要求模型返回 JSON 然后祈祷它正常工作」。它是 constrained decoding —— 模型从字面上无法生成违反 schema 的 tokens。
工作机制
@Generable是一个 Swift 宏,能在编译时从 struct 自动生成 JSON Schema。- 框架将 schema 注入到 prompt 中,指定响应的格式。
- 在推理过程中,每一步解码都应用 token masking:根据 schema,将所有无效的 tokens 从词汇表中屏蔽(在 softmax 中概率设为 0)。
- 模型只能选择有效的 tokens。
苹果在 WWDC25 文档 中将其描述为「guided generation」。这是 OpenAI 的 response_format: json_schema 和 Anthropic 用于工具使用的方式相同的技术。不同之处在于:苹果将其集成至 Swift 的类型系统中。定义 struct,编译器生成 schema,运行时在推理中应用。这是端到端的类型安全。
三种约束级别
@Generable
struct CommitMessage {
// 第一级:严格限制 —— 实际上是枚举
// 激活 token masking:只有 "fix", "feat", "refactor" 等有效。
// 构成 "bug" 或 "update" 的 token 概率为 0。
@Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore", "style"]))
var type: String
// 第二级:软限制 —— 类似于 system prompt
// 模型倾向于遵循但不强制。
@Guide(description: "修改的范围,例如 auth, ui, db。输入单词,使用小写。")
var scope: String
// 第三级:无约束 —— 自由文本,由模型决定
var subject: String
}
类比:anyOf 就像一个下拉菜单,description 像带有占位符的输入框,一个没有 Guide 的字段则像一个空白的多行文本框。三者的区别不在于程度,而在于机制。第一种在 token 水平上操作(模型无法脱离限制),第二种在 prompt 水平上操作(模型倾向于遵循),第三种则完全自由。
这在 hooks 的使用场景中尤为重要,因为严格限制的用途非常明确。commit type 必须是 7 个值之一。没有歧义、创新或推理,仅是纯粹的分类任务。3B 参数的模型配合 constrained decoding 的表现与 200B 参数的模型一样好。区别在于前者执行需要 300ms 并且免费,而后者需要两秒且花费金钱。
一个完整 hook 的代码
以下是 fm-commit-msg 的 prepare-commit-msg hook。它只有 106 行 Swift 代码,无任何外部依赖:
import Foundation
import FoundationModels
@Generable
struct CommitMessage {
@Guide(description: "Type of change")
@Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore", "style"]))
var type: String
@Guide(description: "Scope of the change, e.g. auth, ui, db, api. One word, lowercase.")
var scope: String
@Guide(description: "Imperative summary of the change, max 50 chars, lowercase, no period")
var subject: String
}
guard SystemLanguageModel.default.isAvailable else {
exit(0) // 无 Apple Intelligence:静默退出,用户直接手写
}
安装
# 前置条件:macOS 26,Xcode 26,启用 Apple Intelligence
git clone https://github.com/frr149/foundation-hooks
cd foundation-hooks
make build
# 给指定 repo 安装 hooks
make install-hooks REPO=/path/to/your/repo
# 将 CLI 可执行文件安装在 ~/.local/bin
make install-lql