git-cliff: 自动生成的变更日志(几乎不费力)

107 个提交。从第一天开始就是完美的约定式提交。Feat、fix、refactor、chore — 所有内容都完美标记。那么 CHANGELOG 呢?空的。不存在。一个"明天再写"的文件,已经拖了两个月。 如果这听起来很熟悉,你不是一个人。手动编写变更日志是奥林匹克级别的苦差事。不是说它难 — 而是它乏味、重复,总有更紧急的事情要做。这就是为什么 git-cliff 存在的原因。 什么是 git-cliff(30 秒版本) 这是一个用 Rust 编写的变更日志生成器,它读取你的 git 提交,根据约定式提交解析它们,然后输出按版本和类型分组的 CHANGELOG.md。没有奇怪的依赖,没有插件,没有黑魔法。一个二进制文件,一个配置文件,就完了。 简单来说:你给它你的提交,它返回你拖延了几个月的文件。 brew install git-cliff git cliff --output CHANGELOG.md 这两行字面上就是开始所需的全部。如果你的提交遵循 类型: 描述 约定,git-cliff 无需额外配置就能理解它们。 真实案例:8 个版本的回溯性变更日志 在 Tokamak(我的监控 Claude 配额的菜单栏应用)中,我正好遇到了这个问题:107 个完美提交和一个空白的 CHANGELOG。应用已经是 v1.3.0 版本,但只有一个开始时的 v0.1 标签。 计划很简单: 步骤 1:在每个版本的提交上创建回溯性标签。 git tag v0.2.0 32950f4 # Dashboard, biblioteca, achievements v1 git tag v0.3.0 9c56985 # Rename a Tokamak, 6 idiomas git tag v1.0.0 a283490 # App Store: sandbox, privacy manifest git tag v1.3.0 6248bac # HEAD: multi-provider, fetch pipeline # ... 以此类推 步骤 2:运行 git-cliff。 ...

2026年2月22日 · Fernando

如何在 Anthropic 断供时估算你的 Claude 配额

我正在构建 Tokamak,一个监控 Claude Max 配额的 macOS 菜单栏应用。几周前,Anthropic 在他们的服务条款中发布了这样的条文: “您不得使用 OAuth 或类似的授权机制来允许第三方应用程序代表用户访问 Claude。” 而我,正在使用浏览器 cookies 调用一个未公开的端点来读取 Claude Max 配额,盯着屏幕想:“现在怎么办?” 今天的工作方式(有用的权宜之计) Tokamak 需要知道你的配额百分比。就是当你在 claude.ai 上用了一段时间后看到的那个 42%。问题是Anthropic 没有公开的配额 API。没有一个带 API key 的文档化的 GET /api/quota。 但确实存在一个 claude.ai 网站自己使用的内部端点: GET /api/organizations/{org_id}/usage 返回类似这样的内容: { "five_hour": { "utilization": 42, "resets_at": "2026-02-22T18:00:00Z" }, "seven_day": { "utilization": 19, "resets_at": "2026-02-28T14:59:59Z" } } 要调用这个端点,你需要已登录用户的会话 cookies。Tokamak 用一个隐藏的 WKWebView 解决这个问题:用户在应用内的 claude.ai 中登录,cookies 保留在 WebView 中,应用每 30 秒使用这些 cookies 进行轮询。 这样做有效。已经运行了好几个月。但这是一个优雅的权宜之计,而不是稳健的解决方案。我们在使用一个内部 API,Anthropic 可以随时更改、破坏或阻止它,而无需预先通知。 法律灰色地带 OAuth 的禁令是明确的。但这适用于 cookies 吗?技术上我们没有使用 OAuth 或"类似的授权机制"。用户直接在标准 WebView 中登录 claude.ai。就像在应用内打开 Safari。 ...

2026年2月22日 · Fernando

macOS公证:苹果为你的应用设置的夜店门卫

凌晨两点。你的应用编译通过。签名完成。打包成DMG。执行notarytool submit。苹果说"In Progress"。你等了5分钟。10分钟。20分钟。一个小时。两个小时。提交仍然是"In Progress"。你睡觉去了。第二天早上:Invalid。 除了"The signature of the binary is invalid"之外没有更多解释。对两种架构都是如此。谢谢苹果。非常有用。 公证是那种完美运行的流程…直到它不工作为止。当它失败时,会给你留下一个Gatekeeper不会打开的.dmg文件和一个什么都不告诉你的错误。在为Tokamak(我的用于监控Claude配额的菜单栏应用)与此斗争几天后,我决定记录所学到的一切并编写一个检查器以避免再次经历这种痛苦。 什么是公证(简单来说) 想象Mac App Store是一个有保安的购物中心。但你不想在购物中心销售——你想直接分发你的应用,用你自己的DMG。就像街边摊位。 苹果说:“好的,你可以。但首先要通过门卫。” 那个门卫就是公证。这是苹果的一个自动化服务,扫描你的已签名应用,验证它不包含已知的恶意软件,如果一切正常,就给你一个票据。你把这个票据钉在你的DMG上(stapler staple),从那时起,当用户下载并尝试打开它时,Gatekeeper看到票据就说"请进"。 没有那个票据,用户会看到这个: “Tokamak.app"无法打开,因为苹果无法检查它是否不含恶意软件。 你的应用就被丢在路边了。 苹果为什么这样做 有两个原因。一个是合理的。另一个…嗯。 合理的原因:保护用户。在公证之前(2019年在macOS 10.14.5中引入),任何人都可以用Developer ID分发已签名的.app,macOS会毫无怨言地打开它。代码签名验证开发者的身份,但不扫描内容。如果你的已签名应用包含键盘记录器,签名完整的情况下也会执行。 公证增加了一层:苹果在到达用户之前扫描二进制文件寻找已知的恶意软件和可疑行为。这不是像App Store那样的人工审查——这是一个自动化系统。但至少是个保障。 另一个原因:控制。苹果希望你通过App Store Connect处理所有事情。使用Developer ID的直接分发一直是二等公民。公证是在"你可以在App Store之外分发,但我们会让你感到不便"这条道路上的又一步。 话虽如此,从macOS 10.15开始,对于所有在App Store之外分发的应用,公证是强制性的。这不是可选的。你的应用要么经过公证,要么不会打开。没有商量余地。 会让你浪费数小时的7个错误 在收集了自己的错误和开发者论坛的错误后,这些是最痛苦的: 1. 缺少--timestamp 这是经典错误。你的codesign在本地完美工作,Gatekeeper不抱怨,但公证返回"The signature of the binary is invalid.” # 错误 — 本地签名有效,苹果拒绝 codesign --force --options runtime --sign "Developer ID Application: ..." MiApp.app # 正确 — 使用苹果服务器的时间戳 codesign --force --options runtime --timestamp --sign "Developer ID Application: ..." MiApp.app 安全时间戳证明签名是在证书有效期内完成的。没有它,苹果不会信任。就像签署没有日期的合同——技术上有效,但没人会接受。 ...

2026年2月22日 · Fernando

一行命令创建 macOS 虚拟机

我正在构建一个 macOS 的菜单栏应用程序。在我的 Mac 上运行完美。现在我需要知道它是否能在干净的 macOS 环境中正常工作:没有我的配置、没有我的权限、没有我的数据。一个全新安装的用户环境。 如何测试这种情况?你需要一个虚拟机。 “简单”,我想。“我安装了 UTM。打开向导,创建一个 macOS 虚拟机,然后运行。” 事情并没有那么简单。 UTM:漂亮但难以驯服 UTM 是一个很棒的应用程序。精心设计的界面,支持在 Apple Silicon 上运行 macOS 客户机,全屏显示,共享剪贴板。手动使用确实很棒。 当你试图自动化时问题就出现了。 UTM 有一个叫做 utmctl 的命令行界面。可以列出虚拟机、启动它们、停止它们、克隆它们。它不能做的是创建虚拟机。对于 macOS 客户机,甚至 UTM 的 AppleScript 也不允许创建它们——操作系统字段被硬编码为 Linux。 简单来说:如果你想在 UTM 中创建 macOS 虚拟机,你必须通过向导手动创建。每次都是。需要点击、下载 IPSW(Apple Silicon 的 macOS 安装映像——相当于传统的 ISO,但由 Apple 打包)、等待安装。 对于需要在质量保证流程中频繁创建和销毁虚拟机的开发者来说,这真是个麻烦事。 Tart:为开发者设计的 macOS 虚拟机 Tart 是当有人在设计虚拟化工具时考虑开发者而不是最终用户的结果。 它使用与 UTM 完全相同的 Apple Virtualization.framework。相同的技术,相同的功能,相同的原生速度。区别在于界面:Tart 是命令行优先的。 brew install cirruslabs/cli/tart 就这样。没有图形界面配置,没有向导。只是在你的 PATH 中的一个二进制文件。 一个命令统治一切 创建一个使用最新可用版本的 macOS 虚拟机: tart create mi-vm --from-ipsw latest 就是这样。latest 告诉 Tart “给我这台 Mac 支持的最新 macOS 版本”。Tart 查询 Apple 的 API,下载 IPSW(约 15 GB——是的,macOS 很大),创建虚拟磁盘,安装操作系统,并为你准备好可以启动的虚拟机。去喝杯咖啡吧,因为需要一段时间——但你不需要动手做任何事情。 ...

2026年2月21日 · Fernando

为什么你发送给Claude的99%内容都已经被缓存了

我正在构建一个应用来监控我在Claude Code中的token消费。几天前,查看原始数据时,我遇到了这样的情况: cacheReadInputTokens: 4.241.579.174 inputTokens: 1.293.019 从缓存中读取的四十二亿个tokens。一百三十万个"新鲜"tokens。这是**99.97%**的缓存命中率。 我的第一反应是认为出了什么问题。没人能达到99%的缓存率。Redis不行。Cloudflare不行。你妈妈说她已经知道你要吃什么的时候也不行。 但事实证明它没有坏。就是这样工作的。而原因既优雅又反直觉。 缓存的不是文本 这里是大多数解释都不够深入的地方。当你看到"提示词缓存"时,你会想到类似Redis的东西:保存问题,保存答案,如果有人问同样的问题就返回同样的答案。 完全不是这样。 缓存的是KV张量——transformer在预填充阶段计算的Key和Value矩阵。用通俗的话说:当LLM收到你的提示词时,它首先要做的是将所有这些文本转换为内部数字表示(embeddings),然后与权重矩阵相乘,得到注意力机制生成响应所需的"键"(K)和"值"(V)。 这种计算是极其昂贵的。在一个200,000个tokens的提示词中(在Claude Code中很常见,对话历史会累积),我们谈论的是数十亿次矩阵乘法运算。这是最消耗GPU的部分,最耗时的部分,成本最高的部分。 这就是巧妙之处:在你的一条消息和下一条消息之间,99%的提示词不会改变。系统提示词是相同的。之前的对话历史是相同的。它读取的文件是相同的。唯一新的是你的最后一条消息。 为什么要重新计算你30秒前已经计算过的东西呢? 匹配机制的工作原理 仅仅缓存是不够的。你必须知道缓存什么时候有用。这里Anthropic使用了一个优雅的技巧:按前缀的累积哈希。 提示词的每个块(system、tools、消息)生成一个哈希。但不是单独的哈希:是累积哈希。第3块的哈希包括第1、2、3块的内容。如果前面任何块中的任何东西发生变化,后面所有块的哈希也会改变。 当新请求到达时,系统从标记有cache_control的点开始向后搜索,逐块比较哈希,直到找到匹配的最长前缀。所有匹配的→从缓存读取。只有新的→重新计算。 这就像一部你已经看了40遍的电影。你不需要看完整部电影就知道会发生什么。你只需要从与你记忆中不同的点开始看。 注意这个数据:系统只向后检查最多20个块。超过这个范围,它就停止搜索。这是一个实用的决定,避免花在搜索缓存上的时间超过直接计算张量的时间。 为什么Claude Code有99%的缓存命中率 现在你知道匹配是如何工作的了,99%就不再神秘了。看看Claude Code中典型会话发生的情况: 消息1(会话中的第一条): 系统提示词 (8K tokens) + 工具 (2K tokens) + 你的消息 (500 tokens) = 10,500 tokens → 全部计算,全部写入缓存 消息2: 系统提示词 (8K) + 工具 (2K) + 消息1 (500) + 响应1 (3K) + 你的消息2 (500) = 14,000 tokens → 前面的10,500个 → 缓存命中(我们之前已经计算过) → 新的3,500个 → 计算并添加到缓存 缓存命中率:75% 消息10: 系统提示词 + 工具 + 9条消息 + 9个响应 + 你的消息10 = ~150,000 tokens → 前面的~149,500个 → 缓存命中 → 新的~500个 → 计算 缓存命中率:99.7% 看到了吗?对话历史只是增长。每条新消息都是累积总数的微小部分。缓存比率以自然对数的确定性收敛到99%。 ...

2026年2月19日 · Fernando

召唤智者:如何使用LLM与任何专家进行导师对话

我妻子召唤Charlie Munger来规划家庭预算。在ChatGPT里。不是开玩笑。 她会说"像Charlie Munger审查我们家庭财务一样行动",然后把本月的开支告诉它。这家伙会回复类似"你在教育支出项目上把投资和花费搞混了"或"那个基金有你没有计算的隐性成本"这样的话。这些是Munger会说的话。用Munger会用的语调。 我也做了同样的事。但我没有召唤投资者,而是召唤了一个不同的专家:Edward Tufte。 谁是Edward Tufte(以及为什么你应该关心) Edward Tufte可能是对我们如何可视化数据影响最大的人。他的书《定量信息的视觉显示》(1983年)是那种永远改变你看图表方式的罕见文本之一。40多年来,它一直是全世界大学、新闻编辑部和设计工作室的绝对参考。 他的原则非常简单: 最大化数据-墨水比。 你图表的每个像素都必须传达数据。如果不传达数据,就删掉它。 不要装饰。 网格、3D渐变、阴影、装饰性边框……所有这些都是Tufte称之为图表垃圾的东西。分散数据注意力的视觉垃圾。 让数据自己说话。 如果你需要用200字的图例来解释你的图表,那图表就设计错了。 Tufte还发明了迷你图——那些能放在一行文字内的微型图表。图表不需要标题、坐标轴或图例来交流的想法。只需要数据。 我的图表很糟糕 我正在构建一个macOS菜单栏应用,用来监控我的Claude Max配额。应用的一部分显示一个迷你图——一个小小的线图——显示我消费配额的速度。 我的第一个版本有这些问题: Y轴固定在0到100% — 如果你的使用量在8%,图表就是一条贴在底部的平线。85%的空间什么都不显示。 在80%处有一条阈值线 — 任意的,没人要求,不传达任何有用信息。 显示当前%的浮动标签 — 冗余的:下面的卡片已经显示了确切数字。 “pp/min"作为单位 — 每分钟百分点?连我都不知道这意味着什么。 线下方的渐变填充 — 纯装饰。图表垃圾。 简单说:我在一个56像素高的图表中违反了Tufte的每一个原则。 技术:循环召唤专家 我没有试图自己修复它(显然我对此没有判断力),而是做了不同的事。我让Claude成为Tufte。 提示词: 像Edward Tufte审查这个图表一样行动。 分析VelocityCardView的SwiftUI代码。 识别你数据可视化原则的所有违规之处。 对于每个违规: 1. 违反了什么原则 2. 为什么这是个问题 3. 具体的处方(代码,不是哲学) 要无情。如果某些东西是图表垃圾,就说出来。 用PASS或FAIL评估。 关键是最后部分:PASS或FAIL。因为没有这个,LLM会给你温和的建议,告诉你"总体上还可以”。有了二元判决,它必须承诺。不能躲在"有改进空间"后面。 然后是循环: 应用Tufte的处方。 实施每个改变。 不要停止迭代,直到他给出认可。 四轮直到PASS 我第一轮没通过。第二轮也没有。第三轮也没有。 第1轮 — FAIL(6个处方): 自适应Y轴而不是固定的0-100 消除80%的阈值线(图表垃圾) 消除浮动标签(与下面的卡片冗余) 用简单词语替换"pp/min"(上升、下降、稳定) 将摘要行折叠为仅时间窗口+趋势 将迷你图高度从44提高到56点 我实施了6个。第二轮。 第2轮 — FAIL(1个处方): ...

2026年2月18日 · Fernando

Beads已死,Linear CLI长存

不到一个月前,我写了篇完整文章介绍如何在Claude Code中使用三层记忆系统:Linear负责战略、Beads负责战术、Tasks负责执行。构建了一个优雅的金字塔模型。 然而现实很骨感。 今天我正式退役Beads。这不是心血来潮,而是因为这个工具制造的麻烦已经超过了它解决的问题——它不再是工具,而是累赘。 Beads的初衷 对于没读过前文的读者,Beads是一个基于Git的issue跟踪器。作为Claude Code的插件,它将issues存储为代码库中的JSONL文件。理论上设计很精妙: Git持久化:issues保存在.beads/目录并与代码一起提交 依赖管理:支持issue间阻塞关系 离线工作:无需网络连接 LLM原生支持:直接读取文件,无需API配置 其核心价值是作为"本周计划"(Linear)和"当前任务"(Tasks)之间的战术衔接层。 故障始末 起初一切顺利,直到各种创意故障接踵而至。 恶魔守护进程 Beads依赖后台守护进程管理SQLite数据库并与Git同步。听起来合理?实际状况: 检测到数据库不匹配! 当前数据库属于其他代码库: 数据库记录库ID:d1f9ca0c 当前代码库ID:01eac8ea ⚠️ 严重警告:此错误可能导致同步时误删issues! 这个错误会在每次会话启动时出现。守护进程崩溃、同步失败,导致issues陷入量子态——既存在于本地SQLite又不存在于Git,反之亦然。 幽灵同步 bd sync本应同步Git远程仓库的issues,但经常失效: → 从远程拉取中... 错误:git pull执行失败:退出状态1 remote: 仓库不存在 fatal: 无法访问'https://git.frr.dev/frr/wuwei.git/' 当代码库配置多个remote时(这很常见),Beads可能选错远程仓库。若该仓库不存在或已更名,每次操作都会静默报错。最终issues停止同步,直到下次会话时数据全部消失才后知后觉。 认知损耗 每次Claude Code会话都这样开始: Claude读取Beads提示(通过hooks注入) 尝试启动守护进程 因数据库不匹配失败 Claude尝试bd sync 因远程仓库错误失败 你手动输入"忽略该错误" 终于可以开始工作 六个摩擦步骤消耗着上下文、时间和耐心。 局势转变 两件事让Beads从"带bug的实用工具"沦为"不必要的负担": 1. Tasks的成熟 当初设计三层架构时,Tasks功能简陋。现在已具备: 支持描述和元数据的TaskCreate 带依赖关系的TaskUpdate 查询功能TaskList/TaskGet 通过CLAUDE_CODE_TASK_LIST_ID实现跨会话持久化 简言之:Tasks现已实现Beads的所有会话内功能,且无需守护进程、SQLite或Git同步。 2. Linear CLI问世 原生的Linear管理控制台(MCP)往好了说也很糟糕——延迟高、稳定性差,总在关键时刻掉链子。 直接调用GraphQL API?理论上可行,直到你需要在issue描述中使用特殊字符: # 尝试1:使用bash字符串插值 # 结果:括号和箭头破坏JSON结构 # 尝试2:Python urllib方案 # 结果:因op read无法在Python环境执行报401错误 # 尝试3:默默流泪 # 结果:情绪宣泄但无实际产出 直到发现linear命令行工具: ...

2026年2月18日 · Fernando

Git工作树:如何让多个AI编程代理并行工作互不干扰

单一checkout的困境 我正在开发一个macOS菜单栏应用,有三个并行需求:消费趋势的走势图、原生通知和桌面小组件。这三个功能独立开发,都计划使用Claude Code实现。 问题在于:Claude Code需要在指定目录工作。而每个Git目录同一时间只能检出(checkout)一个分支。标准的git checkout就如同单车道环岛——每次只能通过一辆车。 如果想让三个功能并行开发,传统方案只有以下几种: 暂存乒乓:反复执行git stash保存更改→切换分支→git stash pop恢复→祈祷不要出现冲突…如此循环直到崩溃或退休,视哪个先到来。 克隆多个仓库:可行,但会产生三个完整的.git/目录副本、三个独立历史记录,以及三倍git fetch操作。资源浪费严重。 顺序开发:老老实实一个接一个开发。稳定可靠,但效率低得像手动执行归并排序。 这些方案都不理想。但Git其实自2015年起就内置了第四种解决方案——只是鲜为人知。 工作树(Worktree):现成的解决方案 Git工作树允许为同一仓库创建多个工作目录,无需克隆完整仓库。没有文件复制,没有魔法技巧。 类比说明:将Git仓库视作图书馆。原先你只有一张阅览桌,每次只能打开一本书。而工作树相当于增添多张阅览桌,每张桌子可打开不同的书,但所有书都来自同一个书库。 ~/code/miapp/ ← 主工作区(main分支) .git/ ← 核心书库(唯一存在) ~/code/miapp-sparkline/ ← 工作树1(feature/sparkline分支) .git ← 符号链接文件(指向主书库) ~/code/miapp-notificaciones/ ← 工作树2(feature/notifications分支) .git ← 另一符号链接 每个目录都是完整的检出副本,包含所有文件。你可以在一个目录编译代码,在另一个目录运行测试,同时让AI编程代理在第三个目录工作——真正并行不悖。 一行命令创建工作树 在项目主目录执行: git worktree add ../miapp-sparkline -b feature/sparkline git worktree add ../miapp-notificaciones -b feature/notifications 就这么简单。两个新目录立即创建完成,各自对应独立分支,共享同一Git数据库。无需克隆操作,无需配置远程仓库,无需复制历史记录。 共享与隔离机制 关键在于理解:所有工作树完全共享Git仓库数据——包括提交历史、分支、标签、远程配置和钩子脚本。当你在sparkline工作树提交代码,notifications工作树可即时看到此次提交,无需执行fetch操作。 但以下内容是独立的: 工作目录文件(每个工作树有自己的文件副本) 暂存区(各自独立的git add状态) HEAD指针(指向不同分支) 简言之:每个工作树的"当前工作状态"是私有的,其他所有仓库数据都是共享的。 与AI编程代理的协作流程 这才是工作树的精髓所在。通过工作树,你可以让多个AI编程代理真正并行工作: # 终端1:在sparkline分支运行Claude Code cd ~/code/miapp-sparkline claude # 终端2:在notifications分支运行Claude Code cd ~/code/miapp-notificaciones claude # 终端3:保持主分支运行 cd ~/code/miapp make run 每个Claude实例都在独立目录、独立分支下操作,拥有独立的.build/缓存目录。彼此互不干扰,无需竞争暂存区,更不需要反复执行stash操作。 ...

2026年2月16日 · Fernando

5种对抗代码幻觉的防御措施(但只有3种真正有效)

上周我讲述了我的AI如何凭空编造完整JSON结构并将其封装进DTO、测试夹具和验证测试中的故事。90个测试全部通过,却是一场骗局。 那篇文章是诊断报告。而本文是治疗方案。 发现问题后,我的反应和任何自尊心受损的工程师一样:连续数天疯狂研究防止重蹈覆辙的方法。我阅读论文、测试工具、分析API真实数据,最终为应用构建了一套防御系统。 调查结果让我惊讶:在五类应对措施中,只有三种真正奏效。剩下两种充其量只是"善意表演"。 思维模型:你与AI的对决 在介绍具体措施前,需要先建立认知框架。最好的类比来自深度学习领域: 在**生成对抗网络(GAN)**中存在两个互相竞争的神经网络: 生成器负责产出内容(图像/文本等) 判别器负责鉴别真伪 系统通过两者的对抗持续进化。生成器变得更擅长欺骗,判别器变得更擅长识别。 当使用LLM编程时,你正身处一个非自愿的GAN环境: LLM是生成器。它产出代码、DTO、测试和夹具。 你是判别器。必须辨别真实与虚构的内容。 但存在一个残酷的不对称性:生成器永不疲倦,而你会。LLM可以不费吹灰之力生成50个文件。你检查到第10个就开始疲劳,第11个文件就可能被漏检。 这就是我在1Password每天47次TouchID验证中提到的"授权疲劳"。依赖人类时刻保持警觉的安全措施不过是纸糊的墙。 重点监控边界 你不需要(也不应该)逐行检查。需要重点盯防的是边界——代码与外部世界交互的环节: 边界 核心问题 外部API DTO字段是否真实存在于API? 依赖包 该依赖是否存在且名称正确? 数据库表 表结构是否包含这些字段? URL/端点 端点是否存在且响应合规? 铁律:LLM对外部世界的任何声明都需验证后再采信。 它说话时的信心程度不能作为判断依据。Anthropic在官方文档中也承认: “Claude有时会生成包含虚构信息的回答…这些回答往往以自信、权威的语气呈现。” LLM说"我确定"和说"我觉得"时,错误的概率其实完全相同。 自动化判别流程 终极目标是摆脱对人为主观能动性的依赖,将验证自动化: 改进前: LLM生成 → 人工抽查 → 合并 改进后: LLM生成 → CI用真实数据验证 → 人工检查差异 → 合并 下文将介绍的五种措施,都是实现这种自动化判别角色的方法。其中有些有效,有些则不尽如人意。 数据实证(致怀疑者) 如果你觉得"这事不会发生在我身上",请看实际研究数据: LLM推荐的21.7%开源包是虚构的。商业模型降至5.2%(仍意味着每20个就有1个虚构包) GPT-4o针对低频API的有效调用率仅38.58%。比抛硬币的胜率还低 当前最佳代码幻觉检测方法的准确率仅22-33%。换句话说:每四个虚构字段只能抓到一个 研究人员上传了LLM常虚构的空包名。3个月收获3万次下载。业界称其为"垃圾注册"(slopsquatting) AAAI 2025发表的研究CodeHalu将代码幻觉分为四类: 类型 特征 实例 字段映射 字段映射关系错误 混淆user_id和account_id 命名虚构 字段名不存在 使用response.quota.percentage而非真实字段response.utilization 资源虚构 虚构API资源 虚构的active_flags字段 逻辑虚构 看似合理实则错误的业务逻辑 基于虚构字段的判断逻辑isPaid = !activeFlags.isEmpty 我的遭遇正是资源虚构导致逻辑虚构。虚构字段不存在,而依赖它的业务逻辑却天衣无缝。堪称教科书级的"自洽虚构"。 ...

2026年2月16日 · Fernando

无声的失败:当你的AI虚构事实而测试却显示一切正常

昨天,我发现我的应用程序中有一半的模块是基于虚构的数据构建的。问题还不在于数据是虚构的,更糟糕的是,所有代码都能正常编译,而且90个测试全部通过。 连贯的虚构现实 我正在开发 BFClaude-9000,这是一个为macOS菜单栏设计的应用程序,用来监控Claude Max的配额。其中一个功能需要通过调用 claude.ai 的API来判断某个Claude账户是付费账户还是免费账户。 所以,我请求Claude Code来实现这个功能。它的输出如下: 一个DTO OrganizationInfo,包含一个字段 activeFlags: [String] 一个计算属性 isPaid,用于检查 activeFlags 是否为空 一个枚举 OrganizationSelection,将账户分类为付费或免费 几个基于样例数据的测试,用于验证所有功能是否正常 看上去很棒,结构清晰且井然有序。但这一切全是虚构的。 Claude的真实API中并不存在 active_flags 字段。即使这个字段存在,其作用也完全不符合代码中的假设。当我用自己的付费账户登录时,应用却提示我是免费账户用户。 纸上谈兵的模式 问题并不仅仅是它编造了API中的一个字段,更糟糕的是,它围绕这个谎言构建了一个逻辑完整的系统: // 含有虚构字段的DTO struct OrganizationInfo: Decodable { let uuid: String let name: String let activeFlags: [String] // ← 这个字段并不存在 var isPaid: Bool { !activeFlags.isEmpty } } // 基于虚构字段的逻辑 enum OrganizationSelection { case paid(id: String, name: String) case noPaidOrg // ← 这个状态不应存在 case noOrgs } // 验证虚构逻辑的测试 let paidOrg = """ {"uuid": "abc", "name": "Acme", "active_flags": ["pro"]} """ // 测试通过 ✅ — 但验证的实际上是虚构对虚构 看到了吗?这不仅是一个错误的API字段,而是一个纸上谈兵的构造:DTO定义了一个不存在的字段,业务逻辑依赖该字段,测试用虚构数据验证这个逻辑,也一致验证通过。每个部分彼此确认,从表面来看完全无懈可击,而实际上却毫无真实性可言。 ...

2026年2月13日 · Fernando