智能代理体验:代理错误日志是你命令行界面的蓝图

我有一个代码代理——Claude Code,每月与我的问题跟踪工具Linear交互大约800次:列出任务、创建问题、更改状态、发表评论。我检查了其中165次会话,并统计出了超过500个错误和超过370次重试。 这些错误没有一个是Linear的API故障引起的,全部都是接口错误:代理与命令行通信,但命令行无法理解。 保守估计,每月大约浪费了70万个token仅仅是与工具“斗争”:重试、读取错误信息、纠正再尝试。这种无形的成本不会出现在任何账单中,但却存在于每次会话中。 背景:代理现在是主要用户 命令行工具(CLI)——从历史上看,是为人类设计的。而人类用户是非常灵活的。如果一个命令失败了,他们会通过 --help 查看帮助。如果错误信息很难理解,他们会去查阅文档。如果该工具有某些特性,他们会记住并避免再犯同样的错误。 而AI代理基本不会这么做。它在不同会话之间不会积累经验,也不会投入太多精力去阅读文档。当某些操作失败时,它也不会深入分析问题所在,而是根据认为最有可能的方式尝试再次执行。 这改变了你的工具客户是谁的定义。如果代理每月调用该工具800次,而你手动使用它只有三次,那么这个接口的主要使用者就是代理。为了人类用户设计工具并期待代理能适应,这是在为次要用户优化。 为这个主要用户专门设计有一个名字:智能代理体验(agentic experience)。它之于代理,正如用户体验(UX)之于人类用户,开发者体验(DX)之于API的开发者。更重要的是,一种最有效的测量工具早已存在——代理的错误日志。 错误有其规律 我将所有以非零退出代码结束的CLI调用定义为错误。根据这一标准,这500多个错误并非随机发生:几乎全部集中在三个模式中。 错误模式 代理行为 设计暴露的问题 虚构的标志 输入 --status 而非 --state;--priority urgent 而非 --priority 1 真正的标志不是人们会优先猜到的名称 缺少的操作过程 尝试搜索文本、按项目筛选或在创建时分配项目:CLI不具备这些功能 工具未涉及实际的工作流程 忘记必要的标志 忽略 --sort,--no-pager,--no-interactive 要求人工决策,而工具本可以自动处理 当代理输入 --status 时,它并非胡乱猜测,而是在推测出最可能的接口名称。--status 和 --state 都非常合理。而我设计的CLI并未与这种直觉一致。 还有一个问题没有反映在统计数据中,因为它不会导致命令失败:冗长的输出。CLI以JSON格式返回列表,每个问题大约50个token。如果命令成功返回(退出代码为零),这类问题就不会被统计为错误;但长列表和每月800次调用叠加起来,这就是那70万个token成本的另一半。这种成本往往被忽视,因为不会妨碍任务完成。 重新审视错误 容易的解释是直接了当的:代理在错误使用工具。这是错误的解释,应逐一反驳。 虚构的标志表明真正的标志名称不够直观。忘记的必要标志表明这些标志完全不应该是必要项:如果工具可以推测出合理的默认值,那么强制要求填写这些标志只会额外增加使用者的工作量。一段没有用的输出或者产生过多token意味着选错了应对的输出格式。 代理的错误日志不是错误列表,而是一个规范: 每个错误都“否定性”地描述了你原本该如何设计接口。而这个规范是你最真实的反馈:无需代价,无数的真实数据,且没有人为善意隐藏工具缺陷。一个人类用户遇到糟糕的CLI时,可能会保持沉默并自动适应它。但代理不会适应:它会一遍又一遍地犯同样的错误,并记录下每一次。 这与我在另一篇文章中提出的一个原则有关:不应允许错误路径存在,而非仅仅禁止其使用。禁止只是一种文档说明——“不要使用--status“——而文档取决于是否有人认真阅读。让错误变得不可能,是设计的职责。 因此,解决方案并非更好的文档,而是更好的工具。 重设计 我围绕这个原则重写了CLI,工具叫做lql。以下四个设计决策贯穿整个改进过程。 宽容优于拒绝。 如果代理输入了 --status,工具会将其接受为 --state 的别名并继续运行。如果输入了 --priority urgent,将其解析为 --priority 1 并说明假定的结果。最常见的“错误”路径直接被转变为正确路径。工具不会因为合理的猜测而惩罚用户,而是直接适应它。 通过错误信息指导正确方向。 当某个功能确实不存在时,错误信息不会简单地说 unknown flag,而是给出具体指导:--filter 不存在。要按状态筛选,请使用:--state <状态>。要搜索,请使用:lql search "文本"。 错误消息本身变成了文档,并且恰好呈现在用户最专注的时间点:他们刚刚失败时。 取消所有强制标志。 lql list 无需提供任何参数即可运行:默认按优先级排序,筛选出活跃状态,并根据工作目录自动检测团队。因为不存在 --sort 参数,自然也就没有遗漏它的可能性。一个代理无法忘记不存在的标志。 ...

2026年5月22日 · Fernando

Cloudflare "Ask AI" 创建了一个拥有我整个账户访问权限的 API 令牌

上周,在审计我 Cloudflare 账户的 API 令牌时,我发现了一个令牌,而我从未创建过它:“Cloudflare Agent Token - 2026-04-28”,这是由 Cloudflare 控制台的 AI 助理(“Ask AI”)创建的。\n\nCloudflare 自己的提示框表明该令牌存在是为了让 AI 能够_“理解你的环境并代表你行动”_。\n\n根据令牌摘要页面,它实际授予的权限是读取所有账户、所有区域和所有用户的权限——超过 160 个权限。所有权限的操作都以:Read结尾:它无法_修改_任何内容。但“只读”这个词显得过于简单化。权限列表包括 Secrets Store:Read、Access: Keys:Read、Access: Service Tokens:Read、Zero Trust: PII:Read、Logs:Read、Account Audit Logs:Read、Billing:Read、API Tokens:Read,以及你拥有的所有 DNS、Access 和身份供应商配置。\n\n此外,这些权限永不过期。\n\n如果这样的令牌泄露,这不只是意味着“攻击者可能重新配置你的基础设施”,它还代表了全面的数据识别和泄露:你的安全态势、日志、个人身份信息(PII)、你的组织和用户的结构——所有数据都可以一次性轻松读取。对于大多数团队来说,这本身就构成了需要报告的安全漏洞。\n\n我使用了“Ask AI”功能。这创建了一个具有全面账户权限并且永久有效的凭据,它在我的账户里存在了三周,直到我偶然发现它——由于没设有效期,它将永远保持有效。没有人以显著的方式通知我“向 AI 提问”会导致这种情况发生,而“Ask AI”也并没有建议“创建一个永久的代理,可以读取你所有的数据”。\n\n一个回答问题的 AI 助理只需要在对话期间针对问题的局部读取权限。而一个永久的凭据能够读取你整个数字资产,这是完全不同的概念,且必须是一个知情的、可见的、经过慎重考虑的决定。\n\n检查你的账户:dash.cloudflare.com/profile/api-tokens。如果你看到 “Cloudflare Agent Token” 而且你不使用该代理,请立即撤销它。\n\n是的,它是只读的、第一方的和可撤销的。但它永不过期,且从未明示给我——因此“可撤销”毫无意义,因为你根本不知道它甚至存在。没有任何理由证明,赋予一个永久读取你所有秘密、日志和个人身份信息权限的令牌能符合“向聊天机器人询问问题的需求”。 本文原文为西班牙语,借助AI翻译。

2026年5月21日 · Fernando

TurboQuant一月后:实现方案、争议与实用效果

本文原文为西班牙语,借助AI翻译。

2026年4月5日 · Fernando

三个智能模型进入酒吧:我的编程节省实验

在每个使用编程智能助手的开发者生命中,总有一个时刻,你盯着月结账单看,心想:“这些服务确实很好用,可我的订阅怎么比小区健身房的会员费还多?” 那个时刻就这样悄然降临了。当时我同时订阅了Claude Max 5、Codex Plus,还在考虑加上Z.AI配合OpenCode处理廉价的机械性工作。理论看上去很美好。问题出在这样搞下去,就像一个管理乱糟糟工地的包工头,工人各忙各的,却没人看图纸。 我的初步想法很简单:在自动化分流之前,最好先手动做个小实验,持续两周。规则简单,但明确。 问题不在于成本,而在于协调 分开来看,支付23欧元、90欧元或者10美元一点也不算多。 问题在于,当每个工具开始互相干扰时,就开始麻烦了。你让一个助手思考,另一个实现,再让第一个检查,最后还要麻烦第三个处理一些琐事。二十分钟后,你已经搞不清楚是在优化成本、提升质量,还是单纯地折腾自己不停切换窗口了。 用大白话说:真正的成本不仅仅是订阅费,还有你的上下文切换成本。 就像一个厨房里同时有一把日本刀、一台智能厨师机、和一个空气炸锅。它们各有用处,各有千秋。但如果你用厨师机来炸土豆,那肯定不行。 假设:思考、执行、清理 我想要试验的规则说起来不过三句话: 用Claude进行思考 用Codex完成主要实现 用GLM/Z.AI处理基础任务 这不是学术讨论,而是一条实用的工作坊规则。 当一项任务模糊不清、涉及架构、存在风险或者需要判断时,把它交给擅长推理的代理是最合逻辑的选择。在这个实验中,就是Claude。 而当一项任务已经很明确,需要进入代码库、修改文件、运行测试并在错误间反复调试时,就轮到Codex登场。 至于那些没有人愿意干但必须有人去完成的工作,比如简单的测试代码、文档编写、小脚本处理、文件重命名或机械化的代码重构,那么就让GLM+OpenCode来试试。 暂不考虑的事情 目前还不考虑设置一个自动化分流器。 我不打算弄一个能自动读取提示词、分类任务、选择代理、按时间切换模型,还能输出漂亮图表证明其复杂性的魔法中介层。 这听起来或许是个超级有趣的项目,但也可能事倍功半。 这里最常见的错误是在真正需要航管之前就急着建塔台。先观察好了,再决定是否需要设置交通信号灯。 手动操作的两周实验 我的实验相当朴实,够不上“炫技”,但大概率更有用。 1. 用Claude处理复杂任务 当以下情况之一发生时,我会优先用Claude: 我不确定怎么入手 需要设计决策 存在破坏性风险 需要审查逻辑或论证,而不仅仅是代码 翻译成人话:如果任务需要准确判断,我不会吝啬。 2. 用Codex主导代码库工作 当任务已经清晰时,我会用Codex处理: 实现真实的功能修改 修复测试 带验证的重构 反复迭代,直到项目重新回到稳定状态 在这个阶段,一个好的执行代理最能展现价值。这不是模型本身的问题,而是流程的问题。 3. 用Z.AI负责简单工作 对于Z.AI,规则很简单: 模板代码 初稿 文档 简单的测试 小型脚本 文件重命名 容易检查的机械性操作 即使失败了,也没关系。可以随时丢弃,然后换成别的工具重做。 这才是关键。我不会向GLM要求飞针走线般的精细活,我的目标只是拧几颗螺丝。 最重要的规则:失败两次便升级 这一点是最重要的,但也最容易被忽视。 当一个便宜的工具失败了,你可能会倾向于继续试,“再试一次就好”,“这次应该可以”,“我改下提示词试试”,“再加点上下文”。半个小时过去了,你还在像与“抓狂的打印机”讨价还价一样调整模型。 我的规则是: 如果Z.AI1到2次尝试后仍失败,那我就提升它 如果是执行问题,转交Codex 如果是理解或设计问题,转给Claude 低成本工具一旦浪费掉太多时间,就不再低成本。 我要真正关注的是什么 我对“表演式性能对比”没兴趣。 不会去费劲列出诸如每秒处理字节数、平均延迟这类看起来“很专业”的表格,毕竟我的主要问题还是怎么快速重命名40个符号,而不是花一下午来折腾。 但我要在这两周内统计以下内容: 指标 意义 Z.AI吸收的简单任务数量 它是不是真的帮我减少负担 我有多少次需要提升任务 节省的时间和金钱是否值得 减少的Codex使用量 整体思路是否划算 切换工具的总耗时和难度 系统是否够流畅,易于长期使用 如果实验成功,那再好不过。 ...

2026年3月30日 · Fernando

早上用Claude,下午用Codex:原来我需要的这个双AI助手工作流

TL;DR: 在165次Claude Code会话和27次Codex CLI会话后,我发现了一个清晰的模式:早上使用Claude来处理需要头脑风暴的互动型任务,下午让Codex完成可自动执行的具体任务。这不是哪个更好的问题,而是何时使用哪一个的问题。数据显示,两者结合远胜于单独使用任何一个。 早上九点,手握一杯咖啡,我脑中只有一个模糊的点子,准备重新构建一个模块。我不知道确切该怎么做,只是确信当前的模式并不理想。 于是我启动了Claude Code。 并不是因为它是“最好的”选择——而是因为我需要一种“自言自语”的方式,一个能理解上下文的助手。我告诉Claude:“这个服务的职责太多了,帮我拆分它。”随即,一场对话开始了。它提出一种分离方案,我讨论修改它,我让它探索另一种可能性,它在动手之前还会展示diff。这是一种协作的过程。 三个小时后,模块被成功拆分,测试通过,设计也有所改进。但这是我的Claude配额的40%。而我还列了一堆机械性任务,比如更新集成测试、清理无用的import、重整目录结构。 然后我打开了Codex。 我给它任务,设置为全自动模式,然后悠闲地去吃午饭。 数据揭示的模式 几个月来,我使用一款菜单栏应用监控自己的使用情况,这款工具记录了会话、token用量及耗时。以下是统计数据: Claude Code: 165次会话,160,893条消息,28,052次工具调用。使用模型:Opus 4.5和Opus 4.6。从缓存中读取了56亿token。 Codex CLI: 27次会话。使用模型:GPT-5.4。模式:danger-full-access,审批策略:never。 令我意外的首先是Claude Code的使用时间分布: 09:00 1 ▏ 10:00 5 ██ 11:00 10 ████▌ 12:00 14 ██████▎ 13:00 7 ███ 14:00 16 ███████▏ 15:00 21 █████████▍ 16:00 15 ██████▋ 17:00 18 ████████ 18:00 13 █████▊ 19:00 14 ██████▎ 20:00 9 ████ 21:00 10 ████▌ 22:00 8 ███▌ 我70%的Claude Code会话发生在下午,仅22%在上午。 初看这些数据,我还以为自己“清晨用Claude”的理论行不通。但深入分析每个时间段的任务类型后,一切都渐渐清晰。 上午用于探索,下午用于执行 Claude Code的上午会话少但时长较长。这些会话属于设计阶段:“这部分功能应该如何运作?”、“帮我审阅这个计划”、“能不能提供一些其他选项”。这些是一次又一次的讨论,需要反复修改观点,最终达成决定。 ...

2026年3月26日 · Fernando

疯狂驱动设计:堂吉诃德、桑乔潘萨和你的AI副驾驶

TL;DR:一个LLM就像堂吉诃德——你无法修复它,它天生就是随机的。解决方案不是修复疯子,而是给他配一个确定性的桑乔潘萨。MDD有两个层次:首先研究其可能犯的错误类型,从而设计出能够吸收这些错误的工具;然后把它放入工具中验证是否还有漏洞。为疯狂设计,而不是对抗疯狂。 我已经在审查日志好几周了。165次AI代理与CLI交互的会话,总共产生了500多个错误和370次重试。一些模式一次次重复出现:代理使用了--status,而正确的参数名应该是--state。它写了Todo,而API期望的是unstarted。它传递urgent作为优先级,而系统只接受数字。 而有趣的是,每个错误都“看起来有道理”。这些并不是随机的错误。它们是合理的错误。就像是你对一个领域有些许了解,但从来没有细读文档时所可能犯下的错误。 某个时刻,当我盯着第十次出现的--status Done(实际上应该是--state completed)时,我意识到这些错误在文学上来说有一个对应的模式。一个有着400年历史的模式。 堂吉诃德就是一个LLM 想象一下。堂吉诃德看到风车,说“是巨人”。他不是笨——他很有文化、书读得多,对骑士小说了如指掌。问题在于,他的世界模型被带有虚假数据的训练集污染了。他读了太多的骑士小说,所以当他看到某些模糊的东西时,就根据他的训练数据来进行解读。风车→巨人。羊群→军队。客栈→城堡。 一个LLM(大语言模型)做的事情和堂吉诃德一模一样。它在训练过程中见了成千上万的API。当你要求它使用一个它不太熟悉的API时,它不会说“我不知道”。它会猜测。而且常常猜得很接近足以让你信任,但偶尔猜错时,它的错误也是合理的。 --status而不是--state。因为在它见过的60%的CLI中,参数名确实是--status。 Todo而不是unstarted。因为在工具的图形界面中,有一列名称就是“Todo”。LLM从文档中看了截图、读了博客,因此它推断如果UI写了“Todo”,那么API一定接受这个值。这听起来合情合理,但却是错的。 urgent而不是1。因为在多数优先级系统中,urgent确实是一个常见的有效值。谁会设计一个优先级必须用1到4的数字,而不能用标签表示的API呢? 每一次“幻觉”都是基于不完整数据的合理推断。堂吉诃德并不笨。他只是疯了。而一个疯子是无法被治愈的。 塞万提斯早就明白了 塞万提斯没有试图“治愈”堂吉诃德。他做的,是让桑乔潘萨陪着他。 桑乔并不聪明。他没读过书。他没有伟大的幻想。但他是确定性的。当堂吉诃德说道“瞧那些巨人”,桑乔会回答:“老爷,那是风车。”堂吉诃德不一定会听他的,但信息已经传递了。这个系统有两个层次:一个随机的层次生成假设(堂吉诃德),另一个确定的层次与现实进行对比(桑乔)。 当你和一个LLM一起工作时,这就是你需要的架构。你无法让它停止做梦,因为这就是它的天性。但你可以嵌入确定性的层次来捕捉这些幻觉,以便防止灾难发生。 这就是MDD方法大显身手的地方。 MDD: 疯狂驱动设计 MDD有两层架构,而顺序很重要。 第一层:先验考古学 在写第一行代码之前,需要研究“疯癫”。不要凭空猜测——要观察。你需要收集LLM与现有工具交互过程中的数据,并为这些错误分类整理。 以我的项目为例,我分析了165次会话,过程中AI代理使用了一个CLI来管理开发团队的任务。以下是数据统计: 错误类别 出现次数 触发的重试次数 虚构或无效的参数 275 ~150 JSON/GraphQL转义失败 25 80+ 名称混淆 40+ 50+ CLI无法完成的操作 60+ 90+ 浪费token的冗余输出 N/A N/A 基于这些数据,你设计一个能够“吸收”错误而不是拒绝它们的新工具。用通俗易懂的话来说:理性的人适应疯狂的人,而不是反过来。 下面是一些具体的吸收设计示例: LLM错误 → 工具设计 ───────────────────────────────────────── --status Done → --status是--state的别名, “Done”将被标准化为“completed” --priority urgent → “urgent”被标准化为1, “high”→2,“medium”→3,“low”→4 --no-pager → 安静地忽略此参数 (工具从不使用pager) 描述中引号转义失败 → 通过文件或stdin传递输入 永远不会内联,交给Serde处理 表中的每项设计决策都来源于真实观察到的错误,而不是关于“可能会出错什么”的猜测。 这种设计方法与传统方法有微妙但重要的区别。传统设计定义出一个正确的接口,然后拒绝一切不符合它的输入。而MDD则是定义出一个正确的接口,并_额外_定义所有用户可能尝试的错误用法,然后吸收这些错误。 就像设计一扇门,既能推也能拉着开。正确的门只会往一个方向开。好用的门却能够双向操作,因为你观察到40%的人在看到门时会向相反方向推/拉。 第二层:后验验证 在完成了第一层后,你需要将其投入使用,交给LLM试用,并观察它在有新工具时会产生哪些新错误。 ...

2026年3月26日 · Fernando

删除了150行道歉

TL;DR:我的AI代理有一个246行的指令文件用于管理Linear中的问题。其中150行是变通方法:硬编码的UUID、对curl的回退、“CLI不支持X"的注释。我没有重写它们——而是构建了一个让它们变得不必要的工具。现在那150行变成了零行。 你是否曾经写过一份指令文档,它的长度本身就证明了有什么地方不对劲? 我指的不是合理的文档。我指的是那些开头说"使用工具X”,然后花80%的篇幅解释工具X什么时候不工作以及应该如何替代的文件。那些实际上是为本应构建的工具道歉清单的指令。 我有一个这样的文件。而且很令人尴尬。 150行垃圾的解剖 背景:我与一个AI代理(Claude Code)合作,它管理我在Linear中的问题。为了让代理知道如何操作,我有一个技能文件——一个代理在需要创建、列表或更新问题时会读取的指令文件。 这个文件有246行。其中约100行是合理的文档:存在哪些命令、有哪些团队、使用哪些标签。这是合理的。 其他150行是防御性垃圾。三个类别: 约30行硬编码的UUID。 我使用的CLI不支持--project。所以技能文件在XML表格中包含了17个UUID(5个团队+12个项目)。代理必须找到正确的UUID并手动构建GraphQL突变来分配项目。一个应该是--project Tokamak的操作需要记住一个36字符的UUID。 约25行对curl的回退。 CLI没有搜索功能。没有按项目过滤。创建时没有项目分配。三个基本操作,三个嵌入GraphQL查询的curl块,引号转义,以及认证头。每一个都是等待代理吃掉引号的定时炸弹。 约15行"不支持X"。 五个"CLI不支持"的警告和两个"必需"(每次列表时的–sort和–no-pager)。注意这点:我在工具使用指令中记录工具的缺陷。这就像汽车手册花三页解释雨刷只有在先敲击仪表板后才能工作。 约80行防御性上下文。 整整一节标题为"何时使用API而不是CLI"。目录→UUID映射表。选择标签的启发式方法。当CLI挂起时该怎么办的规则。这些材料存在的唯一原因是工具无能为力。 “小心台阶"的标志 当一个工具有不舒适的界面时,自然的反应是记录变通方法。你写指令。你放警告。你创建一个"常见错误"部分。文档越详细,你就越相信问题已经解决了。 但实际上没有。你放了一个"小心台阶"的标志,而不是修复台阶。 当那些指令的用户是LLM时,问题就成倍增加了。人类读到"不支持–project"会记住(多多少少)。LLM读到它,处理它,三轮对话后还是会使用--project。这不是因为它笨——而是因为它优化完成任务,而--project是分配项目的逻辑路径。禁令在信号的海洋中是噪音。 我在另一篇文章中写过这个问题:对LLM的冗长指令完全等同于放置标志。LLM忽略它们不是因为叛逆。它忽略它们是因为它的功能是找到最直接的路径,而"不要使用–project,而是在这个表格中查找UUID,然后用这个GraphQL查询做curl"不是直接路径——这是一个粗糙的修补。 解决方案不是更好的技能文件 我本可以用更好的指令重写技能文件。更清晰的。有例子的。有图表的。我本可以从246行增加到400行并覆盖每个边缘情况。 这就像扩大标志。 我所做的是构建lql——一个用Rust编写的CLI,专门设计让AI代理(或人类,但主要是代理)可以与Linear交互而不需要生存手册。 设计理念是一句话:错误的路径不应该被禁止,应该是不可能的。 换句话说:你不在文档中禁止--status——你让它工作。你不记录--project在create中不存在——你让它存在。你不维护UUID表格——你自动解析名称。你不提供curl回退——工具没有做不到的事情。你不写"必需:–sort”——你设置合理的默认值。 消失的东西 这是我删除的清单: 防御性垃圾 删除的行数 删除原因 硬编码的UUID(17个ID) ~30 lql自动解析名称 对curl + GraphQL的回退 ~25 lql原生支持search、project、relate “不支持X"的注释(5个) ~15 代理期望的一切都存在 “必需"标志(2个) ~5 合理的默认值,没有必需标志 “何时使用API vs CLI"部分 ~15 没有"vs”——lql什么都能做 上下文→UUID映射表(XML) ~20 从TOML配置自动检测 启发式和防御性规则 ~40 工具是容错的,多余了 总计 ~150 剩下的是合理的文档:存在哪些命令,有哪些团队,使用哪些标签。零变通方法。零道歉。 为什么有效(有趣的部分) 行数的减少很引人注目,但这不是重点。重点是_为什么_那些行是多余的。 旧技能文件中的每行变通方法都存在是因为底层工具是脆弱和不宽容的。脆弱是因为它在合理输入面前失败(--status而不是--state)。不宽容是因为它拒绝而不提供替代(--project不存在,自己想办法)。 当你用容错工具替换脆弱工具时,指令_自动_简化。你不必重写手册——手册自己重写,因为不再有什么需要警告的。 这是解释为什么iPhone手册有10页而打印机手册有200页的同一原理。不是苹果写更好的文档。而是iPhone不需要你解释如何装纸、对齐打印头或清洁滚筒。 容错工具生成简短文档。脆弱工具生成生存手册。 当用户是LLM时,这更加重要。每行指令都是可能被误解、遗忘或矛盾的一行。有150行变通方法的技能文件给它150个错误遵循变通方法的机会。没有变通方法的技能文件给它…零个出错的机会。 ...

2026年3月26日 · Fernando

对抗式编程:当你的AI助手凭空编造出API

TL;DR:你的AI可能会凭空臆造出合理但不存在的API字段。解决办法不是祈祷它正确,而是:在编写代码之前下载真实的_schema_,使用API的实际返回值作为_fixtures_,并将_fetch_与数据处理分离,以便无需网络测试代码。这就是对抗式编程:假设你的AI助手会撒谎。 你是否有过这样的经历:写了针对某个API的代码,一切都编译通过,测试也没问题,逻辑看起来合理……但连接真实API时,完全行不通? 如果你独自开发,这往往是因为你读错了文档。而如果你和AI一起编程,会是因为AI编造了那份文档。 不像“幻觉”的幻觉 我当时正在用Rust构建一个命令行工具(CLI),用于与某个GraphQL API交互。我让我的AI助手为实现一个按优先级排序的过滤器编写代码,它返回了这样一段: query { issues(orderBy: { priority: ASC }) { nodes { id title priority } } } --- 整洁、合理、完全符合预期。唯一的问题是:这个API的`orderBy`字段并不接受`priority`作为值。实际的枚举类型叫做`PaginatedOrder`,合法值包括`createdAt`和`updatedAt`,而不是`priority`。 我是如何发现的?当API返回一个毫无意义的400错误时,我花了20分钟才弄明白问题不在我的代码,而是因为我使用了一个**根本不存在的字段**。 ## 总是相似的模式 这并不是个例。在几周的开发过程中,AI一再出现类似的“幻觉”: - **不存在的过滤字段**——比如建议`state.id.or`代替实际的`state.type.in`。听起来合乎逻辑,但API使用了完全不同的模式。 - **凭空捏造的枚举**——提供的值名称看起来合理,但实际API中从未定义过。 - **完全错误的生态系统方法**——在Rust项目中,AI建议使用`fcntl.flock`来处理文件锁定。这是Python的做法,而在Rust中应该用`fs2::FileExt`。 这些错误具有一个共同点——**它们都看上去非常可信**。没有一个错误显而易见。一个新手程序员在粗读文档后可能会犯下完全相同的错误。这正是危险所在:它们不像“幻觉”,更像是对领域知识“一知半解”的代码。 ## 为什么LLM会编造API? 简单来说:LLM不知道你的API具体有哪些字段。在训练中它可能见过成千上万的GraphQL APIs,而当你要求它使用其中一个时,它就像一个有着良好直觉但没有文档的开发者:**猜测**。 而且它猜得挺好,足以让你信服。然而偏偏这个“差不多”就足以让你的项目进度完全崩盘。 这就像和一个从不看文档但总是自信满满的聪明同事一起工作。对方会无比肯定地告诉你:“是的,这个端点接受名为`priority`的字段。”你完全信了,结果运行时发现它是编造的。 ## 解决方案:对抗式编程 在经历了一周内三次类似的“幻觉”后,我采取了一种不同的策略。从“先信任,后验证”,改为**“先怀疑,先验证”**。我称之为**对抗式编程**:假设你的AI助手总是会编造一些东西。 这不是敌意,而是一种开发安全手段。 ### 1. 编码前进行Schema自省 如果你在使用GraphQL API,请在向AI提出任何需求之前,下载真实的Schema: ```bash # 下载API的完整Schema curl -s https://api.example.com/graphql \ -H "Authorization: token-here" \ -H "Content-Type: application/json" \ -d '{"query":"{ __schema { types { name fields { name type { name kind ofType { name } } } } } }"}' \ > schema.json 现在你有了真实的内容。当AI告诉你“使用orderBy: { priority: ASC }”时,你可以在Schema里查找priority字段并验证。然后将相关部分的Schema片段交给AI,明确告诉它“只使用这些字段”。猜测就此终结。 ...

2026年3月26日 · Fernando

Linear Agent 不是你需要的。你的代理早就在终端里

TL;DR: Linear 推出了一个集成的人工智能代理。听起来不错,但它并没有解决开发者在终端操作 coding agents 时的痛点。我们需要的不是另一个代理,而是一个可靠的 CLI,我们现有的代理可以直接调用。如果要重写,那就用 Rust——这就是 lql 的由来,一款专为 Linear 设计、面向代理的 CLI。 昨天,Linear 宣布了他们的人工智能代理。这是一个集成到应用中的聊天机器人,能够理解你的 roadmap,你的 issue 和你的代码。你可以在 Slack 上与它对话,在评论中@提到它,它会综合上下文,建议行动方案,甚至直接为你创建 issue。 听起来很棒。真的,很棒。 尽管如此,当我读到这个公告时,我的第一反应是:“这不是我需要的东西。” Linear 的大冒险 为了让你明白我的意思,我需要先讲讲背景。我和 Linear 的关系就像一部委内瑞拉肥皂剧一样,是一段充满了爱恨交织的故事。 第一幕:MCP 服务器。 Linear 曾经有一个 MCP 服务器,供人工智能代理与其交互使用。它的表现就像是在飓风里点打火机:技术上是能点着火,但火焰从来维持不到两秒。断断续续、缓慢,偏偏总是在关键时刻掉链子。最终我直接把它卸载了。 第二幕:GraphQL API。 于是唯有通过 GraphQL 直接和 Linear 沟通。没错,它确实能用,直到你需要在某个 issue 的描述中加入特殊字符,结果这些字符的转义问题会让你重新思考自己的整个人生。某一次,我花的时间转义一个括号比写 issue 所描述的代码还要长。 第三幕:Linear CLI。 然后 linear CLI 出现了,这是一个由社区开发的项目。brew install schpet/tap/linear 然后直接运行。一个第三方工具,朴素、不显眼,但正是我需要的工具:能够直接在终端创建、列出并更新 issue,而不需要与 GraphQL 或那个让人发疯的 MCP 作斗争,也没有弹窗干扰。 我甚至专门写了一篇文章 讲述自己如何用这个 CLI 解放了工作流。一个简单的 bash 脚本帮我在不到一分钟内创建了 49 个 issue。如果用 MCP,我可能会花上一个半小时。 进入代理 现在 Linear 推出了他们的代理。这款产品承诺:一个可以理解你的工作空间、与你的代码连接并自动化你的工作流程的集成助手。 ...

2026年3月25日 · Fernando

转化即制胜:Google如何通过改变坐标将LLMs压缩至6倍

乘法很难,加法很简单。 任何小学生都知道这一点。但他们不知道的是,对数的存在正是为了利用这种不对称性:通过将乘法转换为加法,在简单的世界中操作,然后逆转这种变换。结果正确,而努力则大大减少。 这种模式——把问题转化到一个易于解决的空间,解决问题,然后再转换回来——是整个工程学中最强大的工具之一。快速傅里叶变换(FFT)用它来处理信号,对数用它来处理乘积。而现在,Google发表了一篇论文,提出用它来压缩语言模型。 这个方案叫做 TurboQuant。它的理念优雅得值得我们详细探讨一番。 问题:压缩而不牺牲质量 现代的大语言模型(LLMs)有一个主要瓶颈,而它并不是模型本身,而是它的工作内存。每当一个模型生成文本时,它需要在内存中维护一种叫做键值缓存(KV缓存)的结构——基本上就是模型用来生成下一个词所需的“上下文”记忆。 对于一个具有长上下文的大型模型来说,其KV缓存可以占用几十GB的显存。这是一个严重的问题:GPU的显存非常昂贵、有限,而且需要用来处理其他很多事情。 一个显而易见的解决方案是量化:减少每个数字的精度。比如,代替用32位浮点数(float32)存储每个值,可以用4位甚至3位。这意味着你从一个连续的值域转化为离散的定标值。通常,这种方法效果不错。 直到你遇见了一个陷阱。 规范化常数的陷阱 要对一组数字进行量化,你需要知道它们的范围:它们的最小值和最大值。这些被称为规范化常数,如果需要还原原始值,就必须保存这些完整的常数(16位)。 而问题就在这里:,如果你用3位对每个数字进行量化,但每8个数字你就需要保存一个16位的常量,这些常量会为每个数字额外占用2位。你的“3位”压缩实际上需要5位才能实现。你几乎失去了一半的压缩效果。 这就像是搬家到一套更小的公寓,却发现搬家的纸箱占了新公寓的一半空间。 而你也不能随意增加数据块的大小以分摊这些常数,因为更大的数据块意味着近似度变差——范围加大,你会失去精度。你被困在了两个矛盾的力之间。 这个问题困扰了大家好几年。每个人都试图通过发明更好的压缩算法来解决它,比如自适应块、非均匀量化、混合方案之类的方案。但所有这些方法只是在增加复杂度,带来的效果却非常有限。 然后Google做了一件不同的事情。他们没有发明一个更好的压缩器,他们改变了坐标。 改变坐标系:从直角坐标变成极坐标 TurboQuant的核心思想是,在量化之前先对向量进行随机旋转,然后将结果转换为极坐标。 分步骤来解释。 第一步:随机旋转 想象一下,你在一个高维空间中有一个向量。它的各个分量分布得并不均匀——某些维度的值非常大,而另一些则接近于零。这种不平衡恰恰是为什么你需要为每个数据块保存规范化常数:每个块都有一个不同的范围。 如果你把这个向量乘以一个随机的旋转矩阵会怎样?从几何上看,你是在将向量旋转到一个任意的方向。向量本身保持不变(长度相同,与其他向量的关系也依然保持),但它的各个分量会重新分布。 这时,会发生一个看似魔法的数学现象:在高维空间中,随机旋转会使向量的分量变得几乎均匀。这被称为测地集中现象——在高维空间中,几乎所有分布的质量都会集中在其平均值附近。随机旋转会使这一切变得平滑。 用通俗的语言来说:旋转消除了峰值和谷底。在旋转之后,所有的数据块范围变得相似。如果所有块的范围都类似了……就不需要为每个块单独保存范围了。 第二步:转换为极坐标 经过旋转之后,TurboQuant将向量从直角坐标系(x, y, z…)转换为极坐标(一个半径和多个角度)。 为什么要这样做?因为随机旋转使得角度变得高度可预测——它们被集中在狭窄的范围内且是已知的。这些角度的量化阈值是固定的,不受数据的影响。 这意味着:角度不需要保存任何规范化常数。 之前每个数字要额外占用的1-2位开销现在完全消失。你只需要保存半径(一个数值对应整个向量)和用固定界限量化的角度。 flowchart LR subgraph antes["&nbsp;&nbsp;&nbsp;传统量化&nbsp;&nbsp;&nbsp;"] direction LR V1["&nbsp;原始向量&nbsp;"] --> B1["&nbsp;划分为数据块&nbsp;"] B1 --> N1["&nbsp;计算每块的<br/>最小值/最大值&nbsp;"] N1 --> Q1["&nbsp;量化&nbsp;<br/>3位+2位开销&nbsp;"] end subgraph despues["&nbsp;&nbsp;&nbsp;TurboQuant&nbsp;&nbsp;&nbsp;"] direction LR V2["&nbsp;原始向量&nbsp;"] --> R2["&nbsp;随机旋转&nbsp;"] R2 --> P2["&nbsp;转换到极坐标&nbsp;"] P2 --> Q2["&nbsp;量化&nbsp;<br/>3位,开销≈0&nbsp;"] end antes ~~~ despues --- 精彩之处在于,旋转是可逆的。要恢复原始向量,只需反量化,回到直角坐标系,然后应用逆旋转。结果是,KV缓存的压缩比达到了**6倍**,且没有可测量的精度损失。 ## 为什么能奏效:随机性减少不确定性 这是论文中最违背直觉的地方。**添加噪声(随机旋转)实际上减少了不确定性。** 这似乎是个悖论,但其背后有着优雅的解释。 没有旋转时,每个向量都有自己内部的分布特性。某些维度占主导地位,其他维度是噪声。你无法提前知道每个块的范围,因此必须测量并存储这些范围。 有了旋转,**你强制所有向量都服从相同的统计分布**(测地集中现象的作用)。你无需测量任何东西,因为你提前知道值的分布特性。随机性破坏了每个向量的特定信息(这些信息对你毫无用处),并用可预测的规则性取而代之(这对你非常有用)。 这就像洗牌一副牌。在洗牌前,你不知道牌的顺序——必须逐张查看。而洗好后,你能确定:它们的顺序是均匀随机的。看似无序的洗牌后,在统计层面你对这副牌了解更多了。 ## TurboQuant的成果 Google使用Gemma和Mistral模型在多个基准中测试了TurboQuant,结果如下: | 指标 | 值 | |------|----| | KV缓存压缩 | **6倍**(从32位减少到大约5位,其中量化为3位) | | 注意力计算加速(H100) | **最高达8倍**的*attention logits*计算速度提升 | | 精度 | 在LongBench、RULER、ZeroSCROLLS上无可测精度损失 | | 是否需要重新训练 | **不需要**——无需微调或校准即可运行 | | 数据依赖性 | **无**——与数据无关 | 最后一点尤为关键。大多数量化技术依赖于校准数据集来调整参数。而TurboQuant完全不需要——随机旋转直接起效,因为测地集中现象是空间的属性,而不是数据的特性。 ## 每个程序员都该知道的模式 TurboQuant是一个更大范围内模式的特例,这个模式值得一个名字:**转化即制胜**。 核心思想是:当一个问题很难解决时,先问问自己,你是否站在正确的空间中。有时候,答案并不是更聪明的算法,而是问题的另外一种表达形式。 你早已熟悉的一些例子: | 问题 | 原始空间 | 转换 | 简化空间 | |------|---------|------|--------| | 大数乘法 | 算术 | 对数 | 加法 | | 过滤信号 | 时间 | FFT | 频率 | | 解微分方程 | 时间 | 拉普拉斯变换 | 代数 | | 压缩KV缓存 | 直角坐标 | 旋转+极坐标 | 规整角度 | | 找文本模式 | 字符 | 正则表达式 → 自动机 | 状态转移 | 在每种情况下,困难并不在于问题本身,而在于**表达形式**。改变坐标,难题变得简单。 ## 你今天能应用的 即使你不在压缩LLMs,也可以采用这个模式。下次遇到一个“看似简单但难以解决”的问题时,问问自己: **1. 我在正确的空间中吗?** 如果比较两件事很难,也许应该先规范化后再比较。如果搜索效率低,也许应该建立一个索引(这是为搜索优化的替代表达方式)。 **2. 我的领域中是否有已知的变换?** FFT自1965年以来就存在了。小波变换从80年代就被引入。向量嵌入技术自2013年来到大舞台。许多“困难”的问题已经有标准的变换方案,解决它们。别急着发明新东西,先查查是否有人已经找到了正确的坐标。 **3. 我能通过添加随机性简化问题吗?** 哈希、随机投影、概率性摘要——它们之所以有效,是因为添加受控随机性可破坏不必要的复杂性。如果你的问题有着让你头疼的不规则结构,有时最好的策略是有目的地摧毁这种结构。 ## 下次碰到问题解决不了时 TurboQuant的教训并不仅仅适用于模型压缩。它告诉我们,在发明更聪明的解决方案前要三思,也许你真正需要的只是**改变视角**。 近年来,机器学习领域一直试图通过更复杂的量化算法来解决规范化常数负担的问题。更大的数据块,更多的量化级别,更复杂的启发式方法——一切都停留在直角坐标的框架内。然而改进却十分有限。 Google却另辟蹊径。他们旋转数据,改变了坐标系,问题随之消失。他们不是解决了这个问题。而是**消弭**了它。 下次,当你花费数小时试图用一堆补丁修复一个无法解决的问题时,停下来问问自己:我是和问题本身作斗争,还是和表达形式做斗争?因为如果站在错误的坐标系上,世界上最聪明的算法也救不了你。 --- **参考来源:** [TurboQuant: Redefining AI Efficiency with Extreme Compression](https://research.google/blog/turboquant-redefining-ai-efficiency-with-extreme-compression/) — Google Research Blog.

2026年3月25日 · Fernando