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

我有一个代码代理——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

Sentry 前端被 Brave 浏览器屏蔽:如何通过隧道解决问题

一位学生从 Brave 浏览器中进行 KeepCoding 的心理测试时报告了一个问题,但该问题从未成功上传到 Sentry。团队技术支持人员查看了控制面板,却没有发现任何内容,便让他再次测试。同时,针对生产环境的 Playwright 烟雾测试显示所有情况正常:SDK加载,事件被处理,Sentry.flush()返回了true。一切看起来都没有问题。 依据不完整数据做出的产品决策比凭直觉合理性决策更糟,因为它们是基于虚假的数据安全感做出的。 背景:为什么 Brave 的情况特别重要 Brave 是一款内置广告屏蔽功能的浏览器,称为 Shields(保护盾)。它可以在默认情况下过滤广告和追踪器,而无需安装额外的插件。根据 Brave Software 2024年公布的公开数据,该浏览器的月活跃用户已经超过8000万。这一数字之所以重要,是因为用户分布并不均匀:Brave 的用户基础更倾向于技术人员、开发者和新潮用户。 对于面向普通大众的应用,这种偏向可以忽略不计;但对于以开发者为目标群体,例如针对开发者训练营申请者的技术评估应用来说,首批报告错误的用户极有可能来自使用 Brave 的人。 类似的情况也发生在使用 uBlock Origin 的 Chrome 或 Firefox 用户,以及启用了 **严格追踪保护(Strict Tracking Protection)**的 Firefox 配置用户,或通过企业网络使用 Pi-hole DNS阻止机制的用户。这些类别的交集——即启用了某种制止追踪器的积极拦截机制的用户——据相关阻止名单的覆盖研究显示(如 EasyPrivacy 用户超过400万订阅者),很容易占据技术流量的15-20%。 问题:保护盾会屏蔽两项内容,而不仅仅是一项 Sentry 的浏览器 SDK 以两个阶段工作。在第一阶段,浏览器从 Sentry 的 CDN 下载软件开发包的 JavaScript 包,通常位于 js-de.sentry-cdn.com 或 browser.sentry-cdn.com 下。在第二阶段,SDK 初始化后,将捕获到的事件发送到以 *.ingest.sentry.io 为主机的摄取端口(endpoint)。 Brave 的保护盾(Shields)会检查两个列表:一个是浏览器的全局列表,一个是针对具体网站启用的列表。关闭保护盾仅适用于网站域名,不影响全局列表。而全局列表中包括以下一些域名: *.sentry-cdn.com *.ingest.sentry.io *.ingest.de.sentry.io 此问题已在 Brave 社区的公共讨论线程中 “Brave blocks sentry endpoint even with shield down for website” 提及,但在 Sentry 官方文档中并未做出明确警告。实际后果是,使用 Brave 的用户会在控制台看到两个错误: ...

2026年5月18日 · Fernando

Agentic体验:1,324次调用我的CLI,15.9%的错误率

我的CLI最常见的用户并不是我自己 lql 是一个用 Rust 开发的CLI工具,用于管理Linear的问题任务。我开发这个工具是因为现有的替代方案并不能满足我的实际使用需求:让一个自主的AI代理来管理问题任务。 为什么我必须开发自己的CLI Linear的MCP服务是我的第一个尝试。这个想法很优雅:搭建一个MCP服务器,从而直接向代理公开Linear的API。但是在实际使用中,它的运行速度很慢且非常不稳定,每次调用时,代理都需要从头开始构建GraphQL查询。这增加了在每次调用中引发不存在字段的机会。用了一两周后,我把这个方案卸载了。 社区版CLI(schpet 的 linear)是我的第二个尝试。这个工具主要是为人类用户设计的,带有交互式菜单、箭头选择以及确认提示。但问题是,一个代理不能操作交互式菜单。也不适合。 Linear 的“智能代理”功能。 2026年3月,Linear 发布了一个集成了 AI 的智能代理。乍一看听起来很理想,直到你发现其局限性:它仅能在Linear的Web界面内使用,没有终端调用功能,没有API,也无法与外部工具集成。它就是一个嵌在自己用户界面中的聊天机器人。如果你的工作流包括“编程的代理也需要管理问题”,Linear 的代理功能无法满足需求。 基于数据的设计:初步分析 在开发 lql 之前,我解析了Claude Code与Linear交互时165次会话中的每一个错误。结果表明,共记录了500多个错误,370次重试,并估算出每月约有700,000个token被浪费。--sort忘记使用40次;写成了--state "Todo" 而非 --state unstarted共12次;--no-interactive未添加导致CLI挂起,等待键盘输入64次。(全套数据细节见原始文章。) 基于这些数据,我设计了 lql 的用户界面。我并没有猜测一个代理需要什么,而是使用测量得到的数据。这形成了一些基础设计决策:输出格式为精简化版本TOON(每个问题约25个token,而不是繁多的JSON格式),为LLM容易混淆的选项添加别名(例如--status → --state),对值进行标准化(比如Todo 转化为 unstarted,urgent 转化为 1),并提供建议正确命令的错误提示信息,而不是简单地显示“未知选项”。 当时我并不清楚,实际上自己是在应用Postel法则。这是后来才意识到的。 第二次分析:lql在生产环境中的一个月 lql上线已经一个月。Claude Code每天会调用它30到50次,用于创建问题、更新状态、链接依赖、查询细节等。我自己从来不手动使用它,因为对我而言,手动管理问题不是兴趣所在。我有代理来解决这些事情。如果哪天我必须亲自执行lql,那说明出了严重的问题。 lql唯一的用户是一个LLM。这让整个设计变得完全不同。 所以我再次解析了Claude Code调用lql的会话日志,搜寻错误。 指标 数值 分析的会话数 200 lql 的调用次数 1,324 错误数 (is_error: true) 210 错误率 15.9% 15.9%的错误率未包括因为并行调用失败且被Claude取消的调用。这里只计算CLI实际发生的错误。 错误分类 并非所有错误都是一样的。有些表明缺乏约定,而有些则表明需要添加操作。 错误 频率 实例 未找到Label 20 --label tokamak (在该团队中不存在) --title 用作 create 的选项 8 lql create --title "Epic: ..." --team PROD 将 show/get 写成 view 6 lql show PROD-911 relate 参数顺序错误 12 lql relate PROD-834 PROD-833 blocked-by 尝试 update --team (移动问题任务) 超过15次 lql update PRIV-32 --team PROD relates 写成 related 2 lql relate PROD-912 relates PROD-910 --body 用于 comment 1 lql comment PROD-926 --body "文本" --comments 用于 view 1 lql view PROD-824 --comments 其余是Linear的API错误、1Password认证问题、或shell错误(长heredocs中的引号破裂)。 ...

2026年4月29日 · Fernando

Apple Silicon中的双层模式:先执行廉价的确定性代码,再用CoreML处理重任务

在之前的两篇文章中(关于感知哈希在Chat Control背景下的用途和关于其对抗性碰撞问题),我们探索了如何仅用40行Python代码解决了一个被行业大规模部署的问题。感知哈希是一个廉价的确定性层的典型例子:无需模型、无需GPU、无需沉重的依赖。 哈希解决了重复数据和相似性检测的问题。但在实际图像处理管线中,有些任务无法用传统算法完成:检测水印的边界框,用持续上下文的方式填补空缺,分类人脸是否为成人,以及提取语义属性等。这些任务需要大型神经网络。 本文要解决的问题是:如何在Apple Silicon上部署这些神经网络,避免云API的依赖,不为每张图像支付费用,并充分利用Apple Neural Engine(ANE)? 简单回答 PyTorch → ONNX → CoreML,只需三条指令。 深入分析 真正的价值不在技术,而在架构。今年我在情感分析和图像过滤领域应用了这个模式。看似不相关,但实质是同样的核心方法。 模式:两层架构,一种哲学 几周前,我发布了SentimentKit,这是一种用于情感分析的库。我发现Apple的NLTagger会将“删除临时文件”评分为-0.8(非常消极)。这个问题并非Apple独有,而是一整类预训练ML模型中的系统性偏差。 解决方法不是“寻找更好的模型”,而是架构上的。SentimentKit的管线有四层: 确定性关键字检测器。 包含了经过人工选择的脏话、消极表达与正面词汇的字典,涵盖8种语言。容量约20 KB。作为最先运行的部分。 Apple的NLTagger(修正技术偏差)。 如果消息未触发第一层,则交给这部分处理。 特定领域模型(基于规则和技术领域的启发式算法)。 基础模型(Apple的本地LLM)。仅在前三层返回模糊结果时调用。 模式:先运行廉价的确定性层,仅在简单规则无法决策时才调用机器学习。 在第一层中有70-80%的流量被处理,无需涉及机器学习模型,带来了能耗、延迟和避免偏差的多方面好处。 几周后,当我开始设计一个规模化的图像清洗管线时,发现自己无意间又应用了同样的模式: 确定性验证(Pillow验证、magic bytes、尺寸限制)。剔除明显无效的数据。 感知哈希(aHash + dHash + pHash)。大约每张图像10-15毫秒,按相似性分组。 经典OCR配合已知Token的正则表达式检测水印。 神经网络(YOLOv11用于检测,LaMa用于图像修复)。仅应用于通过前几层过滤的图像,占比约10-20%。 以上并不足以一以贯之。这是相同哲学在不同领域的应用,其本质问题是一致的: 如何以最低成本实现深度学习模型的推理。 在Apple Silicon上,最优的方式是通过CoreML将计算任务分配至ANE。 ONNX:被低估的中间语言 ONNX(Open Neural Network Exchange)是神经网络模型交换的格式,由Microsoft、Meta、NVIDIA等组织维护。其规范描述了如何在便携的文件中序列化模型的计算图,包括操作、连接关系及权重。 作用:分离训练过程与部署过程。 在ONNX出现之前,如果你使用PyTorch训练模型,就必须在生产中使用PyTorch。如果要迁移到TensorFlow Serving、CoreML或嵌入式运行时,需要逐一手工实现操作。这种过程费时容易出错。 ONNX的流程重新定义了路径: PyTorch(训练) → ONNX(交换格式) → Runtime X(生产环境) 其中,Runtime X可以是:onnxruntime(支持多种后端)、TensorRT(NVIDIA)、OpenVINO(Intel)、CoreML(Apple)、TFLite(移动设备)、Triton(服务器)或WebAssembly(浏览器)等。 只需一次导出即可,由运行时选择针对硬件的最佳后端。在Apple Silicon上,我们需要的是CoreML Execution Provider,它基于CPU、Metal GPU和Apple Neural Engine规划实现。 实际限制 并非所有PyTorch的操作都能直接映射到ONNX。现代架构使用的某些自定义操作(例如flash attention或非传统卷积)可能无法被干净地导出。实际解决方案:如果你的模型是YOLO、ResNet、Transformer标准架构或U-Net,导出无难度。如果包含不常见的操作,在假设模型可导出前,请先检查兼容矩阵。 对本文涉及的YOLOv11和LaMa模型,两者都提供了高质量的ONNX导出支持。对于YOLOv11,ultralytics可一键完成导出;对于LaMa,Carve AI团队已在Hugging Face上发布了ONNX格式,无需重新导出。 ...

2026年4月18日 · Fernando

导致苹果CSAM系统崩溃的对抗性碰撞(及其对苹果以外的广泛影响)

这篇文章是“64比特决定你能上传什么到互联网”的技术后续。如果你还没读过它,请先阅读。这里我们假定你已经理解了什么是感知哈希以及为什么PhotoDNA、PDQ和NeuralHash是密切关联的。 2021年8月5日,苹果宣布推出NeuralHash。仅仅13天后——8月18日——匿名研究员Asuhariet Ygvar在GitHub上发布了从iOS二进制文件中提取的完整模型的逆向工程。数日后,两个独立的研究人员——Brad Dwyer及其合作者——公布了碰撞实例:两张视觉上完全不同的图像却生成了相同的哈希值。苹果承诺其系统的一年每账户误报率为“万亿分之一”,然而事实证明,这个系统可以用普通消费级硬件成功攻击。 公众舆论将焦点放在“苹果出了错”。但后续技术分析却表明事实相反:苹果并未犯错。对抗性脆弱性是感知哈希家族的一种结构性特性。如果有问题,NeuralHash可能是市场上最复杂的设计之一。 五年后,欧盟正在制定法律,强制在WhatsApp、Signal和Telegram中部署同样的技术。这项法规假定该算法是一种不变的基础技术,如同SHA-256一样。然而事实并非如此。 偶然碰撞与对抗性碰撞 熟悉密码学哈希的读者可能会有一个直觉模型:碰撞是一个罕见的、偶然的事件,其概率可以通过2^(-n)来计算,其中n为比特数。对于SHA-256来说,需要2^128次操作来找到一个碰撞——这在已知的计算基础设施下难以实现。 感知哈希属于另一类对象。这一区别非常重要。 **偶然碰撞。**两张合法图像因偶然因素生成了相同的哈希值。对于64比特的感知哈希,概率并非2^(-64),而远大于这个值,因为“合理”图像(人类生成的图像,而非随机噪声)具有统计规律性。实际概率取决于数据集分布,远高于理论上限。 对抗性碰撞。有意生成两张图像刻意使其产生相同的哈希值。这里的区别是质的。在感知哈希中: 哈希空间很小(64-96比特)。 哈希函数“平滑”——像素的小扰动会导致哈希值的小变化。这一特性是需要的(对重新编码的容忍度),但也带来了攻击性(容易被攻击)。 基于CNN的哈希(如NeuralHash)是可微的。攻击者可以使用梯度下降直接找到碰撞。 传统哈希(例如pHash、PDQ)在同样意义上并非可微,但可以通过局部搜索和标准机器学习启发式方法进行攻击。 实际而言:在消费级GPU和PyTorch工具支持下,一个研究生可以在一个下午解决感知哈希的对抗性碰撞问题。 三种攻击类型及其不同后果 2021-2023年的学术文献区分出三种攻击方式,每种都具有不同的操作影响。 任意碰撞 给定哈希算法H,找到两张图像x₁和x₂,它们视觉上完全不同,但满足H(x₁) = H(x₂)。这是最简单的攻击:如果哈希空间很小,它就会自然发生。Ygvar在2021年8月发布的碰撞就是这种类型:一只狗和一片灰色的风景,它们视觉上毫无关系,但生成了相同的NeuralHash。 **操作用途:**降低系统的可信度。如果能够公开展示碰撞,前提“哈希是可靠的标识符”便会瓦解。 定向预影 给定目标哈希值h₀(比如从NCMEC数据库提取或估计的值),生成一个视觉上无害的图像x,使得H(x) = h₀。这种攻击才是实际操作中最令人担忧的。 **操作用途:**发送图片给受害者。攻击者生成一张猫的照片,它具有与已知CSAM材料相同的哈希值,然后通过WhatsApp发送。在系统扫描触发警报后,即使人工审查确认图片为无害,过程依然耗费了时间、法律资源(在某些情况下)以及对受害者的污名化。如果规模化——同时针对数千名受害者——系统将变得不可操作。 苹果在2021年表示该攻击不太可能发生,因为NCMEC数据库是封闭的,攻击者无法知道目标哈希值。然而Prokos等人(USENIX Security 2023)证明该论点站不住脚:攻击者不需要每个具体哈希,只需要对目标集的参考图像拥有访问权限,而在现实世界中,拥有这种访问权限对高级黑客来说并不困难。 规避 已知一张图像x(例如NCMEC数据库中的CSAM材料),生成一个修改后的图像x’,视觉上与x几乎一致但生成不同的哈希值。这种攻击破坏了系统实现其声明用途的能力。 **操作用途:**真正的CSAM分发者对其材料应用该攻击。即使对人类来说修改后的图像几乎像素级相同,系统扫描无法检测到修改后的副本。新的材料加入NCMEC数据库的速度比对抗性变体的增长速度慢,覆盖率随着时间的推移而恶化。 NeuralHash案例详细分析 NeuralHash崩溃的时间线具有教育意义。 **2021年8月5日。**苹果宣布推出CSAM检测套件。发布的技术文档声明每年每账户误报率为1万亿分之一,基于对参考数据集的内部测试。 **2021年8月18日。**Asuhariet Ygvar在GitHub上发布了AppleNeuralHash2ONNX。该项目从iOS 14.7的二进制文件中提取了NeuralHash模型,并将其转换为ONNX格式,可在任何设备上运行。代码无需特权访问:任何拥有越狱过的iPhone或静态内核分析能力的用户都能提取CoreML模型。 **2021年8月19日。**该项目的用户首次公开碰撞示例:两张视觉上截然不同的图像(collision1.png和collision2.png)在NeuralHash下生成了相同的96比特哈希。该技术基于提取的模型进行梯度搜索,大约需要50行PyTorch代码。 **2021年8月20日至27日。**苹果确认示例碰撞风险确实存在,但坚称从操作角度看,这些问题并无问题,理由如下:(a)NCMEC数据库是机密的,(b)需达到30次匹配才能触发警报。批评者回应称:(a)数据库详细内容无需全部公开,只需要渠道获取目标集中的参考图像,(b)当攻击者可以随意创建N次碰撞时,30次门槛变得微不足道。 **2021年9月。**苹果宣布延期。幕后决策是由于无法在不公开承认架构设计缺陷的情况下技术性捍卫系统。 **2023年。**Prokos、Fendley、Green、Jois和Cao发表了论文《Squint Hard Enough: Attacking Perceptual Hashing with Adversarial Machine Learning》(USENIX Security)。这篇论文对NeuralHash、PhotoDNA(黑盒)和PDQ的三种攻击类型进行了形式化研究。实验结果表明使用标准的对抗性机器学习工具,攻击成功率超过80%。该研究结束了学术争论:脆弱性并非NeuralHash的漏洞,而是哈希家族的结构特性。 那么PhotoDNA和PDQ呢? 有人偶尔会提出这样一个论点:“NeuralHash基于CNN,因此是可微的;而PhotoDNA是传统方法,因此是安全的”。这是错误的。 PhotoDNA是微软的专有技术。我们无法审核其确切实现。然而,Prokos等人展示了黑盒攻击:攻击者无需访问算法,只需通过使用该算法的服务的输入/输出行为。在实验条件下,对PhotoDNA进行黑盒攻击的碰撞生成率为75-80%。 Meta的PDQ是开源的。任何人都可以研究代码并使用梯度进行攻击。Prokos等人用与NeuralHash相同的技术成功攻破了它,虽然成功率略有差异但大致相当。 其根本原因是:所有感知哈希必须是“平滑”的才能实用。如果不“平滑”,哈希无法容忍重新编码或小的改变,也就失去了目的。如果“平滑”,则可以被攻击。平滑性和对抗性防御之间在结构上不可兼得。 PhotoDNA、PDQ、pHash、dHash、aHash、NeuralHash:这些感知哈希都存在可被攻击的风险。过程中有难易程度与成功率的差异,但它们无一能够幸免。在学术文献中,没有一种感知哈希可以同时实现操作容忍度与对抗性鲁棒性。这个问题至今仍未解决,理论上可能在不放弃其中一种特性时无法解决。 对法规的影响 这应该是Chat Control辩论中最重要的问题。 欧洲委员会提出的CSA Regulation要求通信服务提供商通过客户端扫描检测加密消息中的CSAM。这项技术中立的提案提到了“感知哈希匹配”作为可接受的方法之一。关键问题在于:该法规将这一算法视为不变的技术基础,仿佛它是密码学上稳定的。 事实并非如此。由此产生的操作后果是可以预测的。 **复杂攻击者可规避。**具有一定技术知识的实际CSAM分发者利用学术文献中已发表的规避技术修改其材料。这些材料通过系统扫描时不会触发警报。法规所承诺的检测无法实现真正的目标。 **普通用户容易受到攻击。**攻击者生成带有已知CSAM哈希值的图像,并将这些图片大规模发送给受害者(政治对手、前伴侣、记者、持不同政见者)。警报触发后启动法律流程,证明无辜的责任落在受害者身上。该系统为法律骚扰打开了新的途径,难以被轻易排除。 **系统针对普通用户而非真正犯罪分子。**那些通过非常基础手段传播材料的用户——在很多情况下并没有明确的恶意——最终成为被捕的对象。而法规实际要追查的犯罪分子并没有受到真正影响。 这三种效应的结合导致了一个关键问题:系统在检测到真实犯罪情况所需的代价/实际案例随着部署规模增加而升高,同时系统的真实阳性与虚假阳性比率不断恶化。在某些规模上下,系统产生的噪音超过了信号。NCMEC关于PhotoDNA在异构场景中的实际效率研究表明我们可能已经接近这个极限,但公开证据由于供应商的利益而存在偏差。 ...

2026年4月18日 · Fernando

64位决定你能上传什么到互联网:Chat Control背后的算法

2021年8月,苹果宣布将在每张照片上传到iCloud之前扫描你的iPhone照片。同年9月,在两周的争议后撤回了提案。2022年12月,官方宣告计划终止。与此同时,2022年,欧盟委员会提出了一项规定,要求WhatsApp、Signal和Telegram执行与苹果撤销的提案完全相同的操作。这项法规,即Chat Control,目前仍在欧洲理事会中推进讨论。 这两项尝试都依赖同一个算法:一个实现于40行Python代码的64位感知哈希算法。这不是夸张,甚至不是比喻。确实只有40行,在阅读完这篇文章后,您可以在自己的机器上运行它的实现版本。 大量的公众讨论——无论是在西班牙还是其他地方——都发生在参与者对该机制的工作原理不了解的情况下。法规的讨论充满了政治色彩(“隐私与儿童安全”对立),而技术核心却如同黑箱。本篇文章将扮演该黑箱的解药。如果读完后您想要发表观点,您可以基于技术事实。如果不想,那至少您已经了解他们让您接受的是什么。 问题:SHA-256无法识别两张图片是同一张 任何开发者第一个冲动的解决方案可能是用SHA-256。如果两张图片生成了相同的哈希值,那么它们就是同一张图片。简单、快速、加密安全。 但这不起作用。 import hashlib def sha256(path): with open(path, "rb") as f: return hashlib.sha256(f.read()).hexdigest() # 同一张图片,不同压缩 sha256("foto.jpg") # "a7f3..." sha256("foto_reencoded.jpg") # "9c2e..." (完全不同) 只改变一个比特——例如JPEG压缩质量从95降低到90,或者从WebP格式转换为JPEG,甚至亮度的微小调整——SHA-256生成的哈希值都会完全不同。数据清晰地证明:加密相似性设计为完全不容忍差异,这是一种特性,而非漏洞。 为了检测“相同的图片即使压缩不同”,需要一个容忍度更高的算法。这意味着需要一个感知哈希:一个能在图片内容不变而细微改变的情况下仍然保持一致的64位签名。 aHash:亮度平均值 最早有用的方案被称为平均哈希(Average Hash)或aHash。其理念非常简单: import numpy as np from PIL import Image def average_hash(path, size=8): img = Image.open(path).convert("L").resize((size, size)) arr = np.asarray(img, dtype=np.float32) mean = arr.mean() bits = (arr.flatten() > mean).astype(int) return "".join(str(b) for b in bits) def hamming(a, b): return sum(x != y for x, y in zip(a, b)) 图像被缩小为8×8像素的灰度图,计算亮度平均值,每个像素亮度大于平均值时为1,否则为0。结果是64位,代表全局亮度分布。 两张“相同”的图片会生成汉明距离很小的哈希值(≤ 5 位不同)。两张不同的图片则会有大的汉明距离(40+ 位)。这种方法能在图片被缩放、稍微裁剪或者重新编码时起作用。但如果有人调整了对比度或全局亮度,aHash就会失效。这是第一步。 dHash:相对梯度 下一步是2011年由Neal Krawetz提出的差异哈希(Difference Hash),简称dHash: ...

2026年4月18日 · Fernando

你的CLI有了新用户,它不是人类

你向你的AI副驾驶要求捕获一个窗口。副驾驶写下peek app "Xcode"。工具尝试寻找一个精确名称为Xcode的窗口,但没有找到,因为实际的进程名是Xcode-16.3。工具打印出Error: application not found。这位情感记忆如金鱼般的副驾驶尝试peek app "Xcode-16.3",成功了。但浪费了一次对话轮次、输入和输出的令牌,还有支付账单的用户的耐心。 现在想象另一种场景:副驾驶写下peek app xcode。工具会自动标准化名称,进行模糊匹配,找到Xcode-16.3,捕获窗口,并返回/tmp/peek/Xcode-16.3-1712524800.png。一个输出令牌。零次无效尝试。 这两种情况的差别不是一个bug,而是一个设计决策。 那些不会看你--help的用户 2025年初,Netlify的CEO Mathias Biilmann创造了术语“智能代理体验”(Agent Experience,即AX),用来描述AI代理在与一款产品互动时所拥有的体验。正如用户体验(UX)关注的是人类用户,开发者体验(DX)面向的是开发者,智能代理体验(AX)则专注于LLM。 这个概念听起来很抽象,直到你把它应用到某些具体的东西上。例如,命令行工具(CLI)。CLI已有40年的历史,其设计一直是为了与人类用户交互:描述性的信息、颜色、高度可视化的进度条以及--help页面。这些对于LLM来说全是“噪音”。一个LLM不会查看帮助信息——它会根据命令名称推断出标志。它不会欣赏绿色的“成功”提示——它只处理纯文本。它也不会观察进度条——它只等待过程完成。 传统CLI的设计旨在让人类用户理解发生了什么,而AX则优化CLI工具,以便智能代理能用最少的令牌和轮次来完成操作。 五项原则,三款工具 在过去的几个月里,我创建了三款秉持同一理念的CLI工具:peek(用于macOS窗口捕获)、lql(用于Linear问题管理)和driftkit(用于智能代理的行为审核工具)。它们都设计成即使没有说明文档,LLM也可以使用。从这些经验中总结出了五项原则。 1. 输出是契约,不是对话 传统的CLI用于窗口捕获时可能会输出类似以下的信息: ✅ Screenshot saved successfully! File: /tmp/peek/Xcode-1712524800.png Size: 1920x1080 Format: PNG 这看起来很漂亮,也很有信息量。但对于需要将路径传递到另一工具的LLM来说完全没用。它不得不解析这个输出,忽略掉表情符号,找到以File:开头的那行,并提取路径。浪费了令牌。 peek只输出一个内容: /tmp/peek/Xcode-1712524800.png 一个路径,没有其他多余信息。LLM直接读取、使用并继续。stdout的输出是一种契约:始终是一个容易解析的路径,始终保持稳定。如果更改格式,就破坏了契约,也会让所有依赖该工具的代理出错。 也就是说:你的stdout是API,而不是用来“装饰”的。 2. 容忍幻觉——不要惩罚它们 LLM会产生幻觉命名。这是一种自然现象,就像地心引力或wifi总是在你赶时间时信号变差。如果你的工具需要准确名称,那就是在向一种基于概率的机器要求精确。这是行不通的。 peek有三种搜索模式以找到应用程序: 精确匹配(不区分大小写):xcode → Xcode 标准化匹配(去除空格和短横线):thinklocal → ThinkLocal 部分匹配:xcode → Xcode-16.3 LLM并不需要知道进程的确切名称。它只需给出一个大致的名字,工具会处理剩下的事情。lql也采取类似的方法来匹配项目和团队的名字——它会根据部分内容将tokamak解析为Tokamak,而无需使用UUID。 原则很简单:如果监督代理的那个人类能够推测出它的意思,那么工具也应该能够做到。 3. 错误信息应该告诉用户怎么做,而不是发生了什么 对比一下以下两个错误消息: Error: application not found in window list Error: "Xcode" is not running. Start it with: open -a "Xcode" 第一个描述了问题所在。第二个则提供了解决问题的方法。人类读了第一个会想“哦,没打开啊。”LLM读到第一个后会……用其他名字试试,或者去Google搜索,又或者发明一个--force标志。而对于第二个,LLM会直接执行open -a "Xcode",等待,然后再试一次。问题在一个轮次内得以解决。 ...

2026年4月7日 · Fernando

Apple的设备端模型在聊天方面表现糟糕,但在结构化输出和工具调用方面表现出色

我花了几周时间对Apple的设备端模型进行压力测试——这是一个约3B参数的模型,运行在任何Apple Silicon Mac的Neural Engine上。为了全面测试,我构建了Think Local,这是一个macOS应用,可以测试模型的每项功能:聊天、图像生成、结构化输出、工具调用和参数比较。 我的结论:作为聊天机器人,这个模型表现糟糕。但作为结构化输出和工具调用引擎,它出人意料地优秀。 这种区别很重要,因为它完全改变了你应该如何使用这个模型。 聊天表现令人失望——但这没关系 Apple的模型上下文窗口只有4,096个token。对比一下:Claude有100万个token,GPT-4o有128K。使用Apple的模型时,你放入200个token的系统提示,150个token的模式定义和三轮对话,就已经用了70%。 自由文本的质量也不令人印象深刻。回答正确但通用,长会话中重复性强,安全防护机制经常出现令人困扰的误报——问它网络安全相关问题,有时会拒绝回答,因为它对"攻击"这个词理解过于字面化。 如果你把它当聊天机器人来评估,它就是一个平庸的模型。但这样评估就像批评螺丝刀不是好锤子一样。 闪光点:约束解码 这里一切都不同了。Foundation Models支持@Generable——一个将Swift结构体转换为模式的宏,模型必须遵守这个模式。这不是"请求JSON然后祈祷"。这是约束解码:在生成过程中,模型字面上无法输出违反模式的token。 @Generable struct BugReport { @Guide(.anyOf(["bug", "feature", "task"])) var type: String @Guide(.anyOf(["low", "medium", "high", "critical"])) var priority: String var title: String var description: String } 使用这个模式,你告诉它"分类:应用在旋转设备时崩溃",它会返回: { "type": "bug", "priority": "high", "title": "Crash on device rotation", "description": "The application crashes when the user rotates..." } 受限字段(type、priority)始终有效——它不能编造列表之外的值。自由字段(title、description)连贯且简洁。我用几十种不同的输入进行了测试:在200多次执行中,模式错误为零。 在这个模型中,自由生成和约束解码之间的差异是巨大的。这就像让初级开发者随意写作与给他一个有定义字段的表单之间的区别。表单总是获胜。 工具调用:一个知道自己不知道什么的3B模型 这是最让我惊讶的。定义一个工具get_weather(city: String),问它"马德里天气如何?",它会生成一个带有正确参数的结构化调用。问它"法国的首都是什么?",它会直接回答——它知道不需要工具。 一个3B参数的模型,在你的笔记本电脑上运行,无需网络,能区分何时使用工具,何时不用。它不完美——对于模糊的提示有时会困惑——但在明确输入上的准确率很显著。 Apple的工具调用使用相同的约束解码机制:调用是一个@Generable结构体,所以参数始终有效。没有损坏的JSON解析,也没有编造的参数。 Image Playground:没人提到的另一个模型 Apple Intelligence不仅仅是文本。Image Playground是一个独立的框架,在设备上生成三种风格的图像:动画、插图和素描。它也完全在Neural Engine上运行,无需网络。 模式重复:对于它设计的用途,它工作得很好。图标、贴纸、简单构图——结果出人意料地好。图像中的文本、详细的人体解剖学、复杂构图——灾难性的。 Think Local包含一个图像工作室,你可以在其中测试提示并并排比较风格。通过十分钟的提示测试获得的直觉是任何基准测试都无法提供的。 数据 在Apple Silicon Mac上测量,使用Think Local的资源监视器: 指标 值 生成速度 ~40 tok/s 冷启动(无预热) ~800ms 冷启动(有预热) ~200ms 模型内存 ~1.2 GB 上下文窗口 4,096 tokens 参数 ~3B 电池影响 最小(Neural Engine) 电池数据很重要:Neural Engine在推理方面的消耗明显少于CPU。在生成过程中,CPU因为数据封装和UI略有上升,但繁重的工作交给Neural Engine。这使得在后台使用模型进行工具开发成为可能——git钩子、代码检查器、分类器——而不会耗尽笔记本电池。 ...

2026年4月7日 · Fernando

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

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

2026年4月5日 · Fernando