返回 2026-07-06
🤖 AI / ML

sqlite-utils 4.0rc2 发布:主要由 Claude Fable 编写(花费约 149.25 美元)sqlite-utils 4.0rc2, mostly written by Claude Fable (for about $149.25)

simonwillison.net·2026-07-05

开发者分享了利用 AI 模型 Claude Fable 辅助完成 sqlite-utils 4.0rc2 版本开发的实践经验。为了在订阅到期前充分利用该模型,作者测试了其能否帮助平稳推进 4.0 稳定版的发布。整个过程中严格遵循语义化版本控制原则,以最大程度减少不兼容的主版本更新。这展示了大语言模型在实际开源项目工程化迭代与版本管理中的具体应用。

Simon Willison

2026年7月5日

几周前我写过关于 sqlite-utils 4.0rc1 发布的文章。由于我们的 Max 订阅只能再用几天 Claude Fable 了,我决定看看它能否帮我推出一个让我真正放心的 4.0 稳定版,因为我尽量遵循语义化版本(SemVer),并且希望尽可能减少不兼容的主版本发布。

我在 iPhone 上的网页版 Claude Code 中用了这个提示词作为开始:

发布稳定的 4.0 版本前的最终审查——非常重要的是,要发现任何如果以后修复就会造成破坏性变更的遗留问题

这是它为我生成的初始报告。其中有一些我自己还未遇到过的重要问题——Fable 将其中 5 个归类为“发布阻塞”(release blockers)。这是其中最严重的一个:

1. delete_where() 从不提交并污染连接(数据丢失) Table.delete_where() (sqlite_utils/db.py:2948) 通过纯粹的 self.db.execute() 执行 DELETE 操作,没有 atomic() 包装器——相比之下,位于 db.py:2944 的 Table.delete() 则进行了正确的包装。连接状态保持为 in_transaction=True,因此随后的每次 atomic() 调用都会进入保存点(savepoint)分支(db.py:430-440)并且同样不会提交。 端到端复现: db = sqlite_utils.Database("dw.db") db["t"].insert_all([{"id": i} for i in range(3)], pk="id") db["t"].delete_where("id = ?", [0]) # conn.in_transaction 现在为 True db["t"].insert({"id": 50}) db["u"].insert({"a": 1}) db.close() # 重新打开:行变回 [0, 1, 2] —— 删除操作、行 50 以及表 u 全都丢失了。

这确实是个极其糟糕的 Bug!很高兴我还没把它发布出去,不过至少这只是个我能在 4.0.1 小版本中修复的 Bug,而不是那种会迫使我发布 5.0 版本的设计缺陷。

经过 37 次提示、34 次提交,以及对 30 个独立文件进行了 +1,321 行新增和 -190 行删除的代码更改,我们依次处理了所有的反馈,并在此过程中进行了几项其他的设计改进。

关于编程智能体(coding agents)的一件奇妙的事情是,像这样更难的任务实际上提供了更多同时去做其他事情的机会,因为智能体有时需要 10 到 15 分钟来处理新任务。我出门去观看了半月湾(Half Moon Bay)的 7 月 4 日独立日游行,期间偶尔查看一下,并在手机上为 Fable 提示下一步操作。

完整详情请见 PR 和这份共享的记录。我在最终审查时切换到了笔记本电脑,并通过 GitHub 的 PR 界面进行了审查。

最重大的更改与事务处理有关,这是早期 RC 版本中的标志性新功能。新的 RC 版本现在包含了关于新事务模型的详尽文档,我将在这里完整引用其介绍部分:

本库中每个写入数据库的方法——insert()、upsert()、update()、delete()、delete_where()、transform()、create_table()、create_index()、enable_fts() 等——都在各自的事务中运行,并在返回前提交。方法调用一旦完成,您的更改就会保存到磁盘:db = Database("data.db") db.table("news").insert({"headline": "Dog wins award"}) # 新行已经保存 - 不需要 commit()。使用 db.execute() 执行原始 SQL 也是如此——写入语句一旦执行完毕就会立即提交。您永远不需要调用 commit(),也不需要为了持久化更改而关闭数据库。只有以下两种情况需要考虑事务:您希望将多个写入操作组合在一起,使它们要么全部成功要么全部失败——请使用 db.atomic()。您正在使用 db.begin() 自行管理事务,在这种情况下,在您提交之前什么都不会提交——该库永远不会提交由您打开的事务。

在审查 Fable 的文档时——我发现先审查文档的编辑记录是初步了解已发生更改的极佳方式——我注意到了这个细节:

db.atomic() 和每方法自动事务是为 Python 默认事务处理模式下的连接而设计的。不支持使用 Python 3.12+ 的 sqlite3.connect(..., autocommit=True) 或 autocommit=False 选项创建的连接,因为 commit() 和 rollback() 在这些连接上的行为有所不同。

我承认,我之前没有想过 sqlite-utils 会对 Python 3.12 中新增的较新的 autocommit 设置作何反应。事实证明,“在这些连接上的行为有所不同”相当于几乎整个测试套件都失败了,因此我与模型进行了合作,以确保这种差异不会破坏该库的运行方式。

最后由 GPT-5.5 进行了审查

我过去常认为,让一个模型审查另一个模型工作的想法有些荒谬——感觉有一种奇怪的迷信。但问题在于它确实有效——我已经开始习惯性地让 Anthropic 的最佳模型审查 OpenAI 的工作,反之亦然,因为这种情况已经足够频繁地产生出有趣的结果,非常有价值。

我用以下内容提示了 Codex Desktop 和 GPT-5.5 xhigh:

审查自上一个 RC 以来的更改。同时确认更新日志是最新的。

这就足以发现两个值得调查的问题:

发现结果 [P1] sqlite_utils/db.py:663:db.query() 现在仅在调用 db.execute() 之后才会拒绝非行返回语句,而 sqlite_utils/db.py:705 会首先自动提交这些写入操作。因此,db.query("update ...") 会引发 ValueError,但更新已经被提交了。对于一个被文档描述为“只能用于返回行的 SQL”的方法来说,这是一个令人惊讶的副作用。 [P1] sqlite_utils/db.py:672:通过 db.query() 执行的 INSERT ... RETURNING 仅在返回的生成器被完全耗尽后才会提交。如果不进行迭代,或者在常见的 next(db.query(...)) 用法中,db.query("insert ... returning ...") 会保持事务打开状态,并且在关闭时写入操作可能会被回滚。这与 docs/changelog.rst:15 和 docs/python-api.rst:232 中的说明相矛盾,文档称其无需迭代即可生效。

我将这些内容粘贴到了一个新的 Fable 会话中,该会话运行了一些实验来确认这个问题:

这两个发现都得到了证实。db.query() 会先调用 self.execute()(这会自动提交写操作),然后才检查 cursor.description —— 因此 db.query("update ...") 会在抛出 ValueError 之前提交更新。而且 INSERT ... RETURNING 的提交动作位于返回的生成器的末尾,所以除非你耗尽迭代器,否则它永远不会触发 —— 执行 next(db.query(...)) 或者未进行迭代的调用会使事务保持打开状态,这与 changelog 和文档的承诺相矛盾。

这是包含修复程序的 PR,以及完整的 Claude Code 记录。审查这段代码帮助我更好地建立起了关于 SQLite 事务语义边界情况的心智模型!

预计(无补贴)成本为 149.25 美元

我升级到了每月 200 美元的 Claude Max 计划(之前是每月 100 美元),以便在 7 月 7 日“Fablepocalypse”到来之前的剩余时间里增加我的 Fable 额度,届时即使是 Claude Max 订阅者也必须为该模型支付全额 API 费用。

我很好奇如果我直接支付这些费用,到底会花多少钱。起初我以为自己看不到这些数据,因为我是使用 Claude Code for web 远程运行这些工作的,后来我意识到,我可以在那个现有会话中运行 AgentsView 来获取成本估算!

运行 "uvx agentsview --help" 然后使用该工具计算本次会话的成本

Claude 弄清楚了如何使用 session list --include-children 命令,并得出了以下结果:

我非常庆幸自己订阅了这个套餐!我真该听从自己的建议,更多地依赖使用更便宜模型的子智能体。

以下是 claude.ai/settings/usage 目前向我显示的内容:

我目前还在推进其他几个由 Fable 驱动的主要项目,目标是在涨价之前刚好把那个 Fable 进度条刷到 100%。

sqlite-utils 4.0rc2 的完整发行说明

这是该 RC 版本的完整发行说明。在每次提交变更时,我都让 Fable 将它们添加到 changelog 的“Unreleased”部分,并随之进行审查。这带来了一个极好的副作用,即 changelog 的提交历史可以作为该版本中每项变更的简明摘要。

过去我习惯于手动编写发行说明,但老实说,这些说明比我自己写的还要好。发行说明是一个我很乐意外包给智能体的绝佳写作案例,因为它们需要的是平铺直叙、可预测且准确无误。

破坏性更新:

需要完整排版与评论请前往来源站点阅读。