2026年2月

⼀场震动 AI⾏业的指控

2026 年 2⽉23⽇,Anthropic 正式公开指控三家中国 AI 公司——DeepSeek、 MiniMax 和 Moonshot AI ——利⽤超过 24,000 个虚假账⼾,对其旗下 Claude 模型进⾏了总计超过 1,600 万次对话交互,通过“模型蒸馏”技术⼤规模提取 Claude 的核⼼能⼒。

这不是⼀句模糊的“我们怀疑有⼈抄了”。Anthropic 拿出了 IP 地址追踪、元数据分析、⾏业伙伴交叉验证,甚⾄将部分账⼾关联到了具体的公司研究⼈员。截⾄发稿,三家公司均未公开回应。 三家公司中,MiniMax 交互量最⼤(1,300 万次), Moonshot 居中(340 万次), DeepSeek 量最⼩但据称⼿法更有针对性(15 万次),专攻推理链的逆向提取。

 

乍⼀看,这是⼀个清晰的故事:有⼈偷了东西,被抓了。

 

但越往深看,越觉得不简单。抄了 1,300 万次作业的那家,为什么产品还是打不过⽼⽼实实练基本功的同⾏?指控别⼈偷的这家,⾃⼰的训练数据就⼲净吗?为了抓住“⼩偷”,平台对所有⽤⼾的对话到底看了多深?⽽这⼀切发⽣的时间节点——恰好是 Anthropic 在五⻆⼤楼谈判桌上最需要筹码的时候。

 

更别提,还有⼀种规模远超 1,300 万次的“蒸馏”,正在全球范围内公开进⾏,从来没有⼈叫它蒸馏,也从来没有⼈喊停。

 

这篇⽂章会从头讲清楚模型蒸馏是什么、怎么操作、威⼒有多⼤。但更重要的是,我们会追问那些新闻标题⾥不会告诉你的问题。

 

先从技术本⾝说起。

蒸馏:⼀个诺贝尔奖得主的“好主意”

 

2015 年,Geoffrey Hinton——后来拿了 2024 年诺⻉尔物理学奖的“深度学习之⽗”——和 Google 的 Jeff Dean 等⼈发表了⼀篇论⽂,正式提出了“知识蒸馏”(Knowledge Distillation) 的概念。

 

他的出发点很朴素:⼤模型能⼒强,但太笨重、太贵、跑太慢,能不能把⼤模型的“知识”压缩进⼀个⼩模型⾥?

 

Hinton 注意到⼀个关键现象:⼤模型在给出答案之前,内部其实产⽣了⼀组丰富的概率分布。 ⽐如让它识别⼀张动物图⽚,它不只是说“这是猫”,⽽是给出“猫 90%、狗 8%、虎 2%”。这个分布本⾝就是知识——它说明了猫和狗⽐猫和卡⻋更相似。⽤这种“软知识”去教⼩模型,⽐只告诉它“对或错”有效得多。

 

这个技术迅速成了⾏业标配。 Google⽤它、 OpenAI⽤它、Anthropic ⾃⼰做轻量版模型 Haiku 时也⽤它。完全合法,完全正向——让 AI 更便宜、更快、更容易部署到⼿机和边缘设备上。

 

⼗年后,同样的技术被⽤在了 Anthropic 指控的这个场景⾥。只不过⽅向变了:不再是压缩⾃⼰的⼤模型,⽽是从别⼈的⼤模型⾥抽取能⼒

⼀个⼩时学会⼗年功夫

 

传统的 AI 模型训练,像是让⼀个学⽣从⼩学开始⾃学——读课本、做习题、犯错、总结,⼀步步积累能⼒。这个过程需要海量数据、巨⼤算⼒、⼤量⼈⼯标注,周期⻓达数⽉,花费动辄数亿美元。

 

模型蒸馏的逻辑完全不同:不⾃⼰学,直接抄“学霸”的答题过程

 

具体怎么操作?三步:

 

第⼀步:批量出题。 针对你想提取的能⼒(⽐如推理、代码编写、⼯具调⽤),设计成千上万个精⼼构造的问题。不是随便问,⽽是覆盖这项能⼒的各种维度和难度组合。

 

第⼆步:让“学霸”答题。 把这些问题发给⽬标模型(⽐如 Claude),收集它的全部回答。这些回答⾥隐含了模型的判断逻辑、推理路径、表达策略——这是花了⼤量资⾦和算⼒训出来的能⼒结晶。

 

第三步:⽤答案训练⾃⼰的模型。把这些⾼质量的“问题-回答”数据对,直接喂给⾃⼰的模型做训练。你的模型不需要理解“为什么这样回答好”,只需要学会“遇到这类输⼊时,输出这种模式”。

 

举个具体例⼦。假设我想让⾃⼰的模型学会“智能客服处理客⼾投诉”的能⼒:

  • “客⼾要求把贷款利率从 4.5%降到 2%,否则转⾛存款,怎么回复?”, 同样的场景,但客⼾是 VIP;

  • 同样的场景,但客⼾正在发脾⽓;

  • 同样的场景,但客⼾搬出了⾏⻓的名字。

 

⼀个能⼒点,展开成 5,000 个变体。 Claude 对每个变体给出⼀个⾼质量回答——什么时候坚持原则、什么时候给台阶、 怎么平衡合规和客⼾体验。这 5,000 对数据的质量,远⾼于你从互联⽹上能爬到的任何东西。

 

“5,000 条数据,只能应对 5,000 种情况吧?” 直觉上是这样,但神经⽹络不是背题库。

 

你给孩⼦做 100 道⼏何证明题,他学到的不是“100 道题的标准答案”,⽽是“辅助线该往哪⾥做”的直觉。遇到第 101 道没⻅过的题,他照样能做。

 

模型训练⼀样。那 5,000 条银⾏客服数据喂进去,模型学到的是⼀套放在哪⼉都管⽤的应对逻辑:情绪升级时先共情再给⽅案,涉及合规红线时⽤“制度要求”⽽⾮“我不⾏”来托底,对⽅搬出权⼒关系时既不接招也不硬顶。

 

蒸馏的精妙之处在于:你不需要覆盖所有场景,只需要覆盖⾜够多的能⼒维度。 就像不需要教孩⼦每⼀道可能出现的⼏何题,只需要让他把“相似三⻆形”“圆的切线”“⾯积法”⼏个核⼼⽅法练熟,他就能组合应对绝⼤多数题⽬。

 

所以 MiniMax 做了 1,300 万次交互——这不是在收集 1,300 万个答案,是在系统性扫描 Claude 的整个能⼒图谱。

 

1,300 万次,怎么可能做到?不需要⼈。

 

⼀个脚本,⼏⼗⾏代码。⽤AI 批量⽣成 prompt,再⽤Claude 批量回答。机器对机器,24⼩时不停。 24,000 个假账号分散请求,每个账号每天⼏百条,混在正常⽤⼾流量⾥,⼀个⽉就能跑完。

 

Anthropic 在报告中描述了⼀种叫“Hydra Cluster”的架构:⼀套代理⽹络同时控制两万多个账号,⾃动轮转、⾃动混⼊正常请求来躲避检测。 实际操作团队可能只需要⼏个⼯程师。

 

成本?按 API 定价粗估,1,300 万次对话⼤约⼏⼗万美元。对⽐从头训练⼀个同等能⼒模型需要的数亿美元——投⼊产出⽐惊⼈。

 

这也是为什么 Anthropic 说这件事光靠⾃⼰防不住:攻击⽅的边际成本⼏乎为零,防守⽅要在海量正常流量⾥⼤海捞针。

 

1,300 万次到底覆盖了多少?

 

Anthropic 报告提到 MiniMax 主攻的⽅向包括⾃主编程、⼯具调⽤、任务编排等。综合其他报道,加上推理、视觉等领域,假设总共覆盖⼗⼏个⼤的能⼒域。

 

算⼀笔粗账:1,300 万次交互分配到 15 个能⼒域,每个域⼤约 87 万次。每个域下⾯拆出 50 个⼦任务,每个⼦任务⼤约 17,000 个变体。

 

17,000 个变体意味着什么?意味着⼀个具体的能⼒点——⽐如“从⼀段⾃然语⾔需求⽣成可执⾏的 SQL 查询”——被从各种⻆度、各种边界条件、各种难度级别反复扫过。这不是⼤海捞针式的乱抓,⽽是⼀张精⼼设计的能⼒扫描⽹,基本把⽬标模型最有商业价值的能⼒维度都过了⼀遍。

但抄作业有天花板

 

读到这⾥,你可能觉得蒸馏⽆敌了。 实际上不是。

 

⼀个有趣的事实:MiniMax 做了 1,300 万次蒸馏,但在很多⽤⼾的体感中,它的模型并不⽐⼀些没有被曝出蒸馏⾏为的国产模型更好⽤。

 

这恰恰说明蒸馏有它绕不过去的短板。

 

抄作业能让你从 60 分快速冲到 85 分,但从 85 分到 95 分靠的不是抄更多作业——是你⾃⼰的底⼦: 模型架构怎么设计、预训练数据质量好不好、训练⼯程扎不扎实、跟⼈类偏好的对⻬调得细不细。这些东西,蒸馏搬不⾛。

 

⽽且蒸馏有⼀个硬顶:你最多只能接近⽼师,不可能超过⽼师。⽬标模型⾃⾝的短板,你也原样继承了。

 

那些被觉得“更好⽤”的模型,往往是在底⼦上下了更扎实的功夫——训练数据质量更⾼、更懂⽬标⽤户的习惯和偏好、在具体场景上打磨得更深。这是硬功夫,没有捷径。

 

1,300 万次蒸馏,结果并没有造出公认最强的模型——这本⾝就是蒸馏局限性的最好注脚。

 

“可是 Anthropic⾃⼰的训练数据不也是‘偷’来的?”

这是很多⼈的第⼀反应,也不是没有道理。

 

Anthropic 训练 Claude 时,⼤规模抓取了互联⽹上的书籍、新闻、论⽂、代码、论坛帖⼦——其中⼤量 内容的版权属于原作者,既没有被告知也没有授权。《纽约时报》诉 OpenAI、 Getty Images 诉 Stability AI,打的都是这个仗。

 

所以就有了⼀个绕不开的追问:你拿别⼈的作品训练出模型,然后宣称模型的输出不可被提取——“所有权”到底从哪⼀环开始成⽴?

 

但这两件事放在⼀起⽐,还是有本质区别的:

 

法律性质不同。 训练数据的版权争议⽬前还没有定论,合理使⽤(fair use) 的边界还在被法院⼀点点 划定。 ⽽Anthropic 所指控的蒸馏⾏为涉及违反服务条款、使⽤欺诈账⼾、绕过地域限制——这些在合 同法层⾯是板上钉钉的违约。

 

做法不同。 训练基础模型时,从海量数据⾥学通⽤规律,任何单⼀来源的内容都被稀释到了可以忽略的程度。蒸馏是盯着你⼀个模型定点抽取——精⼼设计 Prompt,专⻔榨你最值钱的能⼒。⼀个是“读了⼀万本书”,另⼀个是“把隔壁学霸的笔记本偷来复印”。

 

这不意味着 Anthropic 在道德上就完全站得住。 只是说明这是两个不同层⾯的问题,不能简单地⽤“你也偷了”来对冲。

你的企业可能正在灰⾊地带⾥

 

上⾯说的是 Anthropic 指控的违规⾏为。但现实中,⼤量企业的正常使⽤⾏为,其实离“蒸馏”只有⼀步之遥。假设⼀家电商公司是 Claude 的付费企业客⼾,⽤它来⽣成客服话术——处理退货纠纷、应对差评投

 

诉、化解价格争议。 ⽣成了⼏千条⾼质量回复后,存进内部知识库。 以后客服遇到新问题,系统⾃动从知识库⾥检索最相似的话术推荐给客服。

 

这完全合规,是标准的企业 AI 应⽤。

 

但仔细看——它的效果和蒸馏⼏乎⼀样:付⼀次钱,把能⼒带⾛了。 新场景来了不需要再问 Claude, 知识库加语义检索就能覆盖绝⼤多数情况。

 

类似的场景到处都是:

  • 律所⽤Claude 批量⽣成合同审核意⻅,整理成模板库,以后新合同对照模板⾛,不再需要 AI 逐份审核;

  • 医疗公司⽤Claude 撰写⼏百种常⻅症状的分诊指南,嵌⼊⾃⼰的问诊系统,从此⾃给⾃⾜;

  • ⼴告公司⽤Claude 为不同⾏业、不同调性⽣成上千条⽂案范本,建成内部的“创意弹药库”。

 

这些企业没有⼀个在做“蒸馏攻击”,甚⾄动机完全正当。但效果是⼀样的:⼀次性提取 AI 的能⼒,转化为⾃⼰的⻓期资产。

 

那 Anthropic 的服务条款怎么划这条线? ⽬前的规定是:禁⽌⽤输出“训练模型”,但没有禁⽌你把输出存进知识库做检索。 两种做法的实际效果可以⾮常接近,但在合同层⾯⼀个违规、⼀个不违规。

 

这恰恰暴露了当前规则体系的脆弱——技术⼿段可以达到⼏乎相同的⽬的,但法律和条款只能按⾏为⽅式划线,没办法按效果划线。

 

这也意味着,当我们讨论 Anthropic 对中国 AI 公司的指控时,真正的问题可能⽐“谁偷了谁”要深刻得多。

还有⼀个没⼈问的问题

 

回过头看 Anthropic 的指控,有⼀个细节很容易被忽略:他们是怎么发现的?

 

Anthropic 说⾃⼰通过 IP 地址追踪、元数据分析、请求模式识别来锁定这些蒸馏⾏为,甚⾄将部分账户关联到了具体公司的具体研究⼈员。

 

这意味着什么?意味着 Anthropic⾄少在做这⼏件事:记录每次对话的来源 IP 和元数据,分析⽤户 Prompt 的内容和模式(否则怎么判断“这些 prompt 是在做蒸馏⽽不是正常使⽤”?),对对话内容进⾏聚类和分类。

 

问题来了:要抓蒸馏,就得看⽤⼾在聊什么。⽽看⽤⼾在聊什么,本⾝就是对隐私的侵⼊。

 

想想你⾃⼰⽤AI 的场景。你可能跟它聊过商业策略、法律纠纷、健康问题、家庭⽭盾,甚⾄⼀些你不会告诉任何⼈的⼼事。你打下这些字的时候,有没有想过平台⽅不仅有能⼒、⽽且可能正在分析这些对话的内容、模式和意图?

 

Anthropic 能够精确识别出哪些对话在做什么、来⾃谁、⽬的是什么——这说明 AI 公司能看到的东西,远⽐⼤多数⽤户以为的要多得多。

 

AI 公司保护⾃家模型不被蒸馏,当然有正当理由。但“保护”需要的监控⼒度,和“⽤⼾隐私”之间的⽭盾, ⽬前⼏乎没有⼈公开讨论过。⽤⼾为此让渡了多少隐私?这些让渡是否被充分告知和授权了?当 AI 公司说“我们检测到了异常使⽤模式”时,这句话背后站着⼀整套针对所有⽤⼾⾏为的监控体系。

 

我们在讨论“谁偷了谁的模型”时,或许也该问⼀句:谁在看我们的对话?

 

追问到这⾥,我们已经从技术层⾯⾛到了伦理层⾯。但还有⼏个更尖锐的问题,涉及动机、 ⽴场和话语权。

为什么是现在?

 

指控的内容值得关注,指控的时机同样值得关注。

 

就在 Anthropic 发布这份报告的同⼀时期,多家媒体报道了另⼀件事:美国国防部⻓召⻅Anthropic CEO Dario Amodei 前往五⻆⼤楼,就 Claude 的军事⽤途进⾏谈判。据报道,⽓氛相当紧张——Anthropic 因拒绝完全解除 AI 安全护栏,正⾯临被定性为“供应链风险”、从⽽失去国防合同的压⼒。 与此同时,Elon Musk 的 xAI 和 Google 已经先后与五⻆⼤楼达成了协议。

 

在这个节骨眼上,公开指控中国 AI 公司对⾃⼰发动“⼯业级蒸馏攻击”,客观效果是什么?

 

它强化了“美国 AI 技术正在被系统性窃取”的叙事,也强化了 Anthropic 作为“被攻击的受害者”和“负责任的安全守卫者”的公众形象——⽽这恰恰是它在五⻆⼤楼谈判中最需要的筹码。

 

这不是说指控⼀定是策略性的,也不是说内容是捏造的。但⼀个⾏为同时服务于多个⽬的时,我们⾄少应该意识到这些⽬的的存在,⽽不是只看到其中⼀个。

 

同样的逻辑,也适⽤于下⼀个问题。

反⽅向有没有⼈在做?

 

⽬前的公开讨论⼏乎是单向的:中国公司蒸馏美国模型。但⼀个显⽽易⻅的反向问题很少被提及。

 

DeepSeek 的 R1 是开源模型,权重完全公开,任何⼈都可以下载和使⽤。美国公司有没有在研究、借鉴、甚⾄直接使⽤DeepSeek 的模型输出来改进⾃⼰的产品?

 

开源不等于放弃所有权利——很多开源协议明确禁⽌⽤输出来训练竞品模型。但在⽬前的舆论环境中,这个⽅向的审视⼏乎为零。

 

如果蒸馏在道德上是错的,那它在任何⽅向上都应该是错的。如果只在⼀个⽅向上被追究、被报道、被上升到国家安全⾼度,那我们⾯对的到底是⼀个知识产权问题,还是⼀个披着技术外⾐的地缘政治叙事?

 

这个问题没有现成答案。但不问这个问题,我们的理解就是不完整的。

安全叙事经得起推敲吗?

 

Anthropic 在这次指控中反复强调⼀个论点:蒸馏出来的模型会丢失安全护栏,可能被⽤于⽣物武器开发、⽹络攻击、⼤规模监控。这也是它呼吁加强出⼝管制的核⼼理由。

 

这个论点听起来很有说服⼒,但逻辑上有⼀个跳跃。

 

安全护栏不是通过蒸馏传递的。 护栏是在模型基础能⼒训练完成之后,通过⼈类反馈强化学习(RLHF)等技术单独加上去的⼀层约束。蒸馏抽⾛的是模型的底层能⼒——推理、代码、 ⼯具调⽤——⽽不是护栏本⾝。

 

换句话说,任何⼀个拥有⾜够技术能⼒的团队,拿到⼀个开源模型之后都可以⾃⾏移除安全限制,根本不需要通过蒸馏 Claude 来获得“没有护栏的危险能⼒”。

 

把“蒸馏”和“安全风险”强⾏绑定,在技术逻辑上并不严密。但它在政策倡导上⾮常有效——因为它把⼀ 个商业竞争问题包装成了国家安全问题,⽽国家安全的标签⼀旦贴上,讨论的空间就会急剧收窄。

 

这不是说安全风险不存在。AI 能⼒的扩散确实带来真实的风险。但谁在定义“安全”、定义的标准是什么、定义的动机是否纯粹——这些问题同样需要被追问。

蒸馏 AI 违规,蒸馏⼈呢?

 

我们已经聊了机器对机器的蒸馏。但还有⼀种提取能⼒的⽅式,规模更⼤、更隐蔽,却⼏乎没⼈拿来跟

 

蒸馏放在⼀起说。

 

以头部数据标注公司为代表,⾏业内普遍以 100 到 200 美元的时薪,在全球范围内招募各⾏业的资深专业⼈⼠参与 AI 训练任务。⼀个医⽣标注“这张 CT 影像的诊断应该是什么”,⼀个投⾏分析师标注“这份财报的关键风险点在哪”,⼀个律师标注“这段合同条款的法律风险是什么”。

 

但实际操作⽐“标注”两个字残酷得多。

 

很多时候,专家不只是答题的⼈,还得⾃⼰出题。 平台要求专家⾃⾏构建业务场景,然后⾃问⾃答—— 相当于⼀个⼈同时扮演蒸馏流程⾥“设计 Prompt”和“⽣成输出”两个角⾊。 更关键的是,如果专家构建的场景不够特别,或者跟平台已有的数据太像,就会被打回重来,要求想更⼩众、更边缘的情境。 ⽽被打回的那些时间,不计⼊⼯时,不算钱。

 

想想这在⼲什么:平台⽩嫖了专家最难的那部分劳动——思考、构建、探索——只为最终被“认可”的输出买单。 ⽽那些被打回的场景,平台真的删掉了吗?还是也悄悄进了数据库,只是没有付钱?

 

⽽且平台为什么拼命逼专家往⼩众场景⾛?因为通⽤场景互联⽹上到处都是,不需要花钱找⼈。平台真正饥渴的是只存在于资深从业者脑⼦⾥的边界知识——罕见病例怎么判断、极端市场条件下怎么对冲、冷⻔法律条款怎么适⽤。这些知识的稀缺性,恰恰是这些专家和他们所在机构最核⼼的竞争壁垒。

 

⼀⼩时⼀两百美元买⾛的不是劳动时间,是⼏⼗年经验⾥最稀缺的那⼀层。 ⽽且被打回的那些⼩时,连⼀两百美元都省了。

 

流程拆开看:让⾏业专家⾃⼰构建场景、⾃⼰回答、不断被逼向更稀缺的知识边界——这和机器蒸馏的流程如出⼀辙,只是被蒸馏的对象从 AI 模型换成了⼈。

 

⽽且这些专家通常签过竞业协议和保密协议,他们在标注任务中输出的专业判断,严格来说很可能已经违反了与雇主的合同义务——只是没有⼈追究,甚⾄没有⼈意识到。

 

Anthropic 指控 MiniMax 蒸馏了 1,300 万次对话。这些数据标注公司在全球有多少标注员,累计产出了多少条训练数据?这个数字恐怕⽐1,300 万⼤⼏个数量级。

 

如果从别⼈的 AI 模型⾥提取能⼒叫蒸馏,那从别⼈的员⼯脑⼦⾥提取能⼒,该叫什么?

 

这个问题之所以没有被讨论,可能恰恰因为它牵扯到的不是⼏家中国公司,⽽是整个 AI 产业的训练数据供应链——包括指控别⼈蒸馏的那些公司⾃⼰。

AI⾏业最⼤的未解命题

 

这整件事撕开了 AI⾏业⼀个根本性的问题:在 AI 的价值链条上,知识产权的边界到底画在哪?

 

  • 作者写了⼀本书→ 被抓取⽤于训练→ 模型学会了写作→ 模型输出了新⽂本→ 这个输出⼜被另 ⼀个模型蒸馏⾛了。这条链条上,从哪个环节开始算“偷”?

  • 美国版权局已经明确:AI⽣成的内容不享有版权保护,因为版权要求⼈类作者⾝份。那么 Anthropic 指控别⼈“偷”了它的模型输出,法律基础到底有多牢固?

  • 蒸馏技术本⾝是公开的、通⽤的机器学习⽅法。禁⽌别⼈对你的模型做蒸馏,和禁⽌别⼈对你的产品做逆向⼯程,边界在哪?

  • 为了抓蒸馏,AI 公司得把⽤⼾⾏为看多深?⽤⼾知道⾃⼰被看了吗?同意了吗?

  • ⼀项指控同时服务于商业利益、公众形象和政策博弈时,动机还能说得清吗?

  • 蒸馏这件事,是不是只许州官放⽕不许百姓点灯?如果只在⼀个⽅向上被追究,它还算技术伦理问题吗?

  • 当“国家安全”成为 AI 竞争的万能论据时,谁来审视这个论据本⾝?

  • 从专业⼈⼠脑⼦⾥⼤规模提取⾏业经验⽤来训模型,和从 AI 模型⾥提取能⼒,本质上有什么区别?凭什么前者是正常⽣意,后者就是“盗窃”?

 

法院还没有答案,⽴法者还没有答案,⾏业⾃⼰也还没有答案。

 

但有⼀件事是确定的:这场博弈怎么收场,直接决定未来⼗年 AI⾏业的竞争格局和游戏规则。

 

谁拥有 AI 的“知识”?这个问题,⽐我们想象的要难回答得多。


本⽂基于 Anthropic 2026 年 2⽉23⽇公开声明及 Bloomberg、TechCrunch、VentureBeat 等多家媒体 报道整理。⽂中涉及的指控均为 Anthropic 单⽅⾯陈述,截⾄发稿三家公司均未公开回应。

 

本⽂由号称被蒸馏的当事⼈协助撰写

如今打开手机、电脑,AI助手无处不在——帮你规划行程、解答疑问、处理重复工作,甚至陪你聊天。很多人觉得它“通人性”,却不知道这份“懂你”,本质是一套技术体系的协同发力。作为深耕AI产品多年的产品经理,今天就用大白话,拆解AI助手系统的核心技术,让你一看就懂。

AI助手系统的核心,不是“会说话”,而是“能听懂、会思考、能落地”,背后靠三大核心技术撑起来,就像一个完整的“智能团队”在分工协作,没有复杂公式,全是生活化类比。

第一个核心技术:大语言模型(LLM)——AI助手的“大脑”。这是整个系统的核心,相当于给助手装了一个超级聪明的大脑,负责理解你的需求、进行逻辑推理。简单说,这个大脑在“上岗”前,读遍了海量的文本、知识,就像一个学富五车的学霸,能听懂你说的每一句话,甚至读懂你的潜台词。

比如你说“明天去北京,帮我看看天气”,它不会只当成一句简单的提问,而是能拆解出“时间(明天)、地点(北京)、需求(查天气)”,这就是大脑在快速推理。但它也有小局限,就像我们记不住所有细节一样,它的“记忆”依赖前期学习的数据,遇到新信息可能会“不懂”,这就需要第二个技术来补位。

第二个核心技术:RAG检索增强生成——AI助手的“实时查资料小助手”。如果说大语言模型是“基础大脑”,那RAG就是给这个大脑配了一个随时能联网查资料的工具,解决了“知识过时”的问题。就像我们写报告时,会翻书、查网页补充最新信息,RAG就是帮AI助手做这件事。

比如你问“2026年最新的诺贝尔文学奖得主是谁”,大语言模型如果没学过相关新信息,就无法准确回答,而RAG会自动联网检索最新数据,再结合大脑的知识,整理出准确答案。我们平时用AI助手查最新新闻、产品活动,靠的都是这项技术,让AI助手摆脱“过时记忆”,始终保持“新鲜度”。

第三个核心技术:工具调用(Function Calling)——AI助手的“手脚”。光会听、会想还不够,能把事情落地,才是实用的助手。这项技术就相当于给AI助手装了手脚,让它能调用各种外部工具,完成具体任务,而不只是“纸上谈兵”。

比如你让AI助手“帮我订一张明天去北京的高铁票”,它不会只告诉你“可以订票”,而是会调用高铁订票平台的接口,输入你的信息、筛选车次、完成预订;你让它“帮我整理本周的工作邮件”,它会调用邮箱工具,读取邮件、分类整理,甚至标记重要信息。这项技术,让AI助手从“聊天工具”变成了“实用帮手”。

除了这三大核心,还有一个容易被忽略的技术:Prompt工程——AI助手的“行为准则”。简单说,就是给AI助手定好“规矩”,告诉它该怎么说话、该做什么、不该做什么。比如我们希望客服类AI助手语气亲切、专业,希望学习类AI助手严谨、准确,这些都靠Prompt工程来定义,让AI助手的输出更符合我们的预期。

很多人觉得AI助手的技术很高深,其实本质就是“大脑(LLM)+ 查资料工具(RAG)+ 手脚(工具调用)+ 行为准则(Prompt)”的协同工作。它不需要我们懂复杂的技术原理,却能实实在在帮我们节省时间、提高效率。

作为产品经理,我们设计AI助手系统,核心不是追求“技术多先进”,而是让这些技术落地,贴合普通人的需求——让不懂技术的人,也能轻松用上智能工具,让AI助手真正融入生活、助力工作。这就是AI助手系统的价值,也是背后所有技术的终极意义。

Oracle AI Database 26ai - 适用于所有数据的新一代 AI 原生数据库

免费的 Oracle AI 数据库平台

请访问原文链接:https://sysin.org/blog/oracle-database-26ai/ 查看最新版。原创作品,转载请保留出处。

作者主页:sysin.org


数据是 AI 的燃料。Oracle AI Database 26ai 将 AI 引入您的数据,从而简化应用开发和关键任务工作负载。

Oracle AI Database

Oracle AI Database 26ai 赋能 “AI for Data” 革命

Oracle 将 AI 架构到 Oracle AI Database 26ai 的核心,进一步履行 Oracle 致力于帮助客户安全地将 AI 应用于各种环境中的所有数据的承诺。

  • Data Deep Dive@AI World 2025https://www.oracle.com/ai-world/

    在 10 月 13 日至 16 日于拉斯维加斯举行的 2025 甲骨文全球 AI 大会 Data Deep Dive 活动中,您将了解 Oracle 的数据战略如何帮助企业推动 AI 创新、简化数据管理、加快应用开发以及将数据作为竞争优势。

为什么要选择 Oracle AI Database

面向数据的 AI

  • 通过内置 AI Vector Search 和数据库内机器学习将 AI 引入数据,消除集成和管理多个数据库的复杂性和成本,同时保持数据一致性。
  • 在不牺牲安全性、可用性和性能的情况下 (sysin),大规模使用 AI 并从中获益。
  • 结合使用检索增强生成 (RAG) 和企业级 LLM 等新技术与自己的业务数据,以获得更加符合具体情境且更相关的结果。

详细了解 Oracle AI Database 中的基础 AI 技术:https://www.oracle.com/cn/database/features/#foundational-ai

面向数据的 AI

面向数据的开发

  • 别让有限的数据类型或编程语言妨碍您的创新 — 这个融合数据库支持所有现代数据类型、工作负载和开发风格,助您简化应用开发。
  • 您可以利用 JSON Relational Duality 简化文档模型 (sysin),从而获得关系框架的可扩展性。
  • 使用内置的 APEX 平台,加快由生成式 AI 驱动的低代码开发速度。使用容器化的可插入数据库,简化微服务的开发和部署。

了解 JSON Relational Duality:https://www.oracle.com/cn/database/json-relational-duality/

面向数据的开发

面向数据的关键任务

  • True Cache 能够在改善应用响应速度的同时,降低数据库服务器负担,让您无需重写应用。
  • 使用 SQL Firewall,保护数据库免受 SQL 注入攻击(包括零日攻击)。使用带 RAFT 复制的 Globally Distributed Database,在满足数据驻留法规的同时,管理多个区域的数据。
  • 无论您的数据位于本地还是 Oracle Cloud 中,您都可以使用相同的技术。

了解 True Cache:https://www.oracle.com/cn/database/truecache/

面向数据的关键任务

部署选项

灵活部署,精确满足您的独特需求

  • 本地部署 Oracle AI Database 服务

    您可以在自已的办公地点利用您自已的硬件运行 Oracle AI Database,也可以在您的数据中心采用 Oracle Exadata 以获取出色性能、灵活性和强大功能。

    Oracle Exadata:https://www.oracle.com/cn/engineered-systems/exadata/

  • 在本地部署 Oracle Cloud 技术

    您可以利用自己的防火墙保护由 Oracle 托管的这个适用于 Oracle AI Database 的混合云平台。

    Cloud@Customer:https://www.oracle.com/cn/cloud/cloud-at-customer/

  • 基于 Oracle Cloud Infrastructure 部署 Oracle AI Database

    您可以利用强大的计算能力、物理存储和各种工具简化日常数据库管理操作,并通过 Oracle 精心设计的高性能集成系统运行企业级云数据库。Autonomous AI Database 可为企业提供全自动化的体验 (sysin),其中包含了自动扩展功能,可为 OCI 上的任何数据库提供较低的总拥有成本。

    数据库云技术服务:https://www.oracle.com/cn/database/technologies/

  • 在多云环境中部署 Oracle AI Database

    数据库应用使用基于 Oracle Cloud Infrastructure 运行的 Oracle AI Database 服务在超大规模数据中心内运行,并与超大规模的控制台和 API 深度集成,从而在您选择的超大规模环境中实现高性能、可扩展性和可用性。

    Oracle Multicloud 解决方案:https://www.oracle.com/cn/cloud/multicloud/

新增功能

在今年于拉斯维加斯举行的 Oracle AI World 上,Oracle 发布了其最新旗舰产品:Oracle AI Database 26ai。首先,它作为 Oracle Database 产品线的延续,支持此前所有的功能,包括所有关键任务能力。它支持自助式管理配置,也支持全自动运行、自动调优的 Autonomous AI Database。它既能在本地部署,也能运行在公有云超大规模环境,或 Oracle Cloud Infrastructure (OCI) 中。它支持最常见的数据管理模型,包括关系表、JSON 文档、图形等,还支持空间数据、不可变(区块链)账本,以及文本文件的数据存储与分析。它能够针对 AI、OLTP、分析、流处理、IoT 等多种负载优化数据存储与访问 (sysin)。它可以维护单一物理/云位置的数据库,也可以利用分布式数据库功能跨区域运行。它可以运行在裸金属、容器、本地 Oracle 工程系统中,也可以通过 Cloud@Customer 部署在 OCI 上,或运行于 Azure、AWS、Google Cloud。

但这些你可能都已经知道。那么,新的能力是什么?是什么让 Oracle AI Database 26ai 成为企业范围内部署数据驱动 AI 的理想平台?

新功能

以下新功能进一步增强了 Oracle AI Database 26ai 作为企业级 AI 数据平台的能力:

  • AI 向量搜索(包括支持 LangChain)
  • 数据使用案例领域
  • Exadata AI Smart Scan
  • 大模型(LLM)集成
  • 注释(Annotations)
  • 外部表向量数据支持
  • 增强版 JSON-关系双向视图
  • True Cache
  • 属性图支持
  • SQL 防火墙
  • 全球分布式数据库

此外,本次版本还提供实时 SQL 执行计划管理、JavaScript 存储过程、只读 PDB 备用库,以及滚动补丁能力。

Oracle AI Database 26ai 如何胜任 AI 平台核心

让我们看看该 DBMS 及其最新更新如何满足企业级 AI 数据平台的要求:

  • 可以基于相似性或近似特征搜索数据,使用 Oracle AI Vector Search,可纳入标准 SQL,也可用于搜索非结构化数据。不同于其他数据库外部执行向量搜索的方式,Oracle 将此能力直接集成于数据库核心,提升效率与灵活性。借助 Autonomous AI Database 的“目录的目录(Catalog of Catalogs)”,数据查找与整合更加容易。
  • 对于跨区域物理分布的数据库,Oracle Globally Distributed Database 使用 Raft 算法提供一致性、最佳事务吞吐与复制能力。
  • Oracle SQL 不仅可以访问 Oracle Database,还可访问其他数据库,尤其强调对 Apache Iceberg 表的支持。Oracle True Cache 提供更高效的时间点缓存,弥补性能差异,优于外部第三方方案 (sysin)。
  • JSON Relational Duality Views 提供以 JSON 文档视图方式访问关系数据,同时保持数据一致性并简化应用开发。此外,Select AI 能基于自然语言提示自动生成正确 SQL。
  • 能够随着数据量增长自动扩展,尤其在 OCI 环境中,可透明地向上或向下伸缩。
  • 允许非技术用户通过自然语言与可视化界面构建应用。Oracle AI Database 26ai 中的数据可作为关系型、JSON 或图结构处理,无需特殊技能。
  • 除了加快专业开发者 AI 应用开发的能力外,本版本也让 JavaScript 开发者能使用 JavaScript 存储过程来编写数据库逻辑。
  • 由于平台以全面方式处理数据,因此其安全性也必须内嵌于 SQL 处理过程之内。Oracle SQL Firewall 能阻止未授权 SQL 与 SQL 注入攻击,并提供对所有 SQL 流量的完整可见性以便审查与故障检测。

系统要求

官方要求 OL8/OL9,也可以运行在其他 RHEL8/9 兼容发行版上。

下载地址

Download:

基于无人机视角的道路损害检测数据集详解与目标检测应用实践

一、引言:无人机赋能道路病害智能巡检

随着城市化进程加快和交通基础设施规模的持续扩大,道路养护与安全管理面临着巡检范围广、人工成本高、响应速度慢等现实挑战。传统人工巡检方式在面对高速公路、城市主干道、山区公路及灾后应急场景时,往往难以兼顾效率与精度。

在此背景下,无人机(UAV)+ 计算机视觉逐渐成为道路损害检测的重要技术路径。通过搭载高清摄像设备与稳定云台,无人机能够从空中获取大范围、连续、高分辨率的道路图像数据,为基于深度学习的目标检测算法提供可靠输入。

本文将围绕一个无人机视角道路损害检测数据集展开,系统介绍其数据构成、标注类别、技术特点及在目标检测任务中的应用价值。
在这里插入图片描述

数据集下载

链接:https://pan.baidu.com/s/1CkmQRHQDjXzGa9KESO0i2A?pwd=snt5
提取码:snt5 复制这段内容后打开百度网盘手机App,操作更方便哦

数据集说明
在道路养护巡检、交通通行安全保障、基础设施寿命评估及灾害后道路恢复等对路面损伤识别精度、病害类型区分能力及复杂环境适应性起关键作用的领域,基于无人机平台的道路损伤目标检测系统,依托无人机载高清摄像设备、实时数据传输模块及图像处理分析技术,实现对核心目标 “Alligator crack(鳄鱼纹裂缝)”“Longitudinal crack(纵向裂缝)”“Pothole(坑洼)”“Transverse crack(横向裂缝)” 的精准检测,直接关系到交通管理部门对城市主干道、高速公路路面健康状况的实时掌控(如日常道路病害隐患排查、高负荷路段损伤趋势监测)、乡村公路及山区道路(如盘山公路、隧道出入口路段)病害分布的动态研判及自然灾害(如暴雨、地震)后受损道路的快速评估;这四类道路损伤作为判断路面通行风险等级、养护作业优先级及道路修复资源调配的核心依据,其精准识别检测是开展道路养护计划制定、交通管制策略调整、修复施工路径规划及路面生命周期管理的基础,对特定场景下(如夜间低光照环境中路面裂缝识别、雨季积水路段坑洼检测、复杂交通流环境下路面损伤捕捉)的准确区分,还能为管理部门提供道路损伤发展规律、高频病害区域等关键信息,辅助评估道路通行安全态势与养护策略优化需求。

二、数据集背景与建设目标

在这里插入图片描述

2.1 无人机道路检测的技术优势

相较于车载检测与人工拍摄,无人机巡检具备以下显著优势:

  • 视角灵活:可覆盖高架桥、隧道口、山区弯道等复杂路段
  • 范围广、效率高:单次飞行即可获取大面积道路信息
  • 环境适应性强:适用于灾后、封闭道路等特殊场景
  • 数据一致性好:利于模型批量训练与长期监测

然而,无人机视角也带来了新的挑战,如目标尺度变化大、背景复杂、光照条件多变等,对检测模型的鲁棒性提出了更高要求。

2.2 数据集建设目标

该数据集的构建目标主要包括:

  1. 覆盖典型道路病害类型
  2. 贴近真实无人机巡检视角
  3. 支持主流目标检测模型训练
  4. 服务道路养护、交通安全与应急评估场景

在这里插入图片描述

三、数据集整体概述

3.1 基本信息

项目说明
数据集类型无人机道路损害检测
图像总数6341 张
数据来源无人机航拍道路图像
任务类型目标检测
标注形式Bounding Box

3.2 数据集划分详情

数据集已完成标准化划分,适用于直接训练:

数据子集数量
训练集(Train)5853
验证集(Val)488
可根据需求自行扩展测试集或进行交叉验证。

四、道路损害类别定义与特征分析

数据集中共标注 4 类典型道路病害,覆盖道路结构性损伤与表面损害的主要形式。

nc: 4
names:
  - Alligator crack
  - Longitudinal crack
  - Pothole
  - Transverse crack

4.1 Alligator Crack(鳄鱼纹裂缝)

  • 呈现网状、不规则裂缝形态
  • 通常代表结构层疲劳损伤
  • 是道路寿命评估的重要指标

4.2 Longitudinal Crack(纵向裂缝)

  • 沿道路行驶方向分布
  • 常出现在车辙带或接缝区域
  • 易在雨水渗透后进一步恶化

4.3 Transverse Crack(横向裂缝)

  • 垂直于行驶方向
  • 多由温度收缩或结构应力引起
  • 在寒冷或昼夜温差大地区较为常见

4.4 Pothole(坑洼)

  • 表现为明显凹陷区域
  • 对行车安全威胁最大
  • 是养护优先级最高的病害之一

五、数据集技术特点与挑战分析

5.1 无人机视角带来的挑战

该数据集在视觉层面具有以下特点:

  • 目标尺度变化剧烈(随飞行高度变化)
  • 背景干扰复杂(车辆、阴影、标线、积水)
  • 光照条件多样(强光、阴影、阴天)
  • 裂缝类目标边界模糊

这使其成为检验目标检测模型在真实复杂环境下表现的理想数据集。

5.2 数据集的工程价值

  • 可直接用于 YOLO / Faster R-CNN / RetinaNet 等模型
  • 适合研究 小目标检测、细长目标识别、复杂背景抑制
  • 有助于评估模型在真实巡检任务中的泛化能力

六、适用模型与训练建议

6.1 适用模型算法

  • YOLOv5 / YOLOv8(实时性优)
  • YOLOv8 + 高分辨率输入(裂缝检测更友好)
  • Anchor-Free 模型(处理尺度变化)

6.2 训练策略建议

  • 提高输入分辨率(如 1024×1024)
  • 使用 Copy-Paste / Mosaic 数据增强
  • 针对裂缝类目标调整 IoU 与 NMS 策略
  • 引入 Attention 机制提升细节感知能力

在这里插入图片描述

七、典型应用场景

7.1 道路养护巡检

  • 城市主干道病害自动识别
  • 高速公路定期健康评估
  • 养护优先级智能排序

7.2 交通安全与管理决策支持

  • 高风险路段预警
  • 病害分布热力分析
  • 养护资源调度优化

7.3 灾后道路快速评估

  • 暴雨、地震后道路损伤排查
  • 应急抢修路径规划
  • 交通恢复决策支持

八、结语:无人机道路检测的未来方向

基于无人机视角的道路损害检测,是智能交通与智慧城市建设的重要组成部分。本文介绍的数据集,从真实巡检场景出发,覆盖典型道路病害类型,为目标检测算法在复杂环境下的验证与落地提供了坚实基础。

随着无人机平台、传感器精度及深度学习算法的持续演进,结合此类高质量数据集的研究,将进一步推动道路养护从“被动响应”向“主动预防”转变。

每隔几十年,软件工程就会被宣告一次“已死”,或者“即将被自动化彻底取代”。类似的说法,我们其实早就听过很多次了。但如果这一次,并不是终结,而是另一种形态的软件工程“黄金时代”的开始呢?就像历史上多次发生过的那样。

 

最近,软件工程领域的奠基人物之一 Grady Booch 在播客节目中,与主持人 Gergely Orosz 细致分享了对自 20 世纪 40 年代以来计算领域三次“黄金时代”的理解,以及这些时代如何在各自的技术约束下应运而生。

这场对话之所以引发广泛关注,还有一个直接原因。在节目中,Booch 正面评价了 Anthropic CEO Dario Amodei 最近引发巨大争议的判断——“软件工程将在 12 个月内被自动化”。他的结论明确:如果用一个技术性的词来形容,这个判断在根本上是错误的。

在 Booch 看来,这类说法混淆了“写代码”和“软件工程”本身,也忽略了一个反复在历史中出现的事实:工具会一次次改变,但软件工程真正要解决的难题,从来没有消失。恰恰相反,每一次“自动化恐慌”,最终都对应着一次更高抽象层级的到来,以及一个新的黄金时代的开启。

基于该播客视频,InfoQ 进行了部分删改。

核心观点如下:

  • 现代计算的许多成果,其实是织就于“悲伤的织机”之上。

  • 软件是一个极其动态、流动且高度可塑的领域。一旦我们掌握了构建某类系统的方法,并形成了可复用的模式,便会迅速发现它们在经济上具有新的应用空间。

  • 很多所谓的“职业危机叙事”,其实源于对行业的狭隘理解。真正发生的事情是:软件正在扩展到更广泛的人群中。非职业开发者将会写出更多软件,而这是极其积极的变化。

  • 当你站在通往新事物的门槛上,可以选择凝视深渊、害怕坠落;也可以选择纵身一跃、展翅高飞。现在,就是该飞的时候。

第一个黄金时代:算法抽象撑起的软件世界

Gergely:你曾多次提到,软件工程的整体演进,本质上是抽象层级不断提升的过程。你能否梳理一下其中几个关键的转折点,帮助我们理解这一脉络,并进一步说明 AI 在其中扮演的角色?

 

Grady:“软件工程”这一术语本身,其实出现得相当晚。最早明确使用这一说法的,通常被认为是 Margaret Hamilton。当时她刚离开“载人轨道实验室”项目,转而参与阿波罗计划。在一个几乎完全由硬件与结构工程师构成的男性团队中,她是极少数的软件开发者之一。她希望用一个词来区分自己的工作,于是开始使用“软件工程师”这一称呼。

 

后来也有人沿用了这一说法,尤其是人们常提到的 NATO 软件工程会议。事实上,那次会议的召开比 Margaret 的工作还要晚几年,而“软件工程”这一命名本身也带有一定争议性,正如“人工智能”在其首次学术会议上所引发的争论一样。尽管如此,随着时间推移,这个词逐渐被接受并固定下来。

 

其核心含义在于:Margaret 以及她的同时代人意识到,这是一种具有工程属性的活动。工程的本质在于,在各种静态与动态约束之间,构建尽可能合理的解决方案,而非追求完美。这一点与结构工程、电气工程或化学工程一致。

 

在软件领域,我们所面对的介质极其灵活、可塑、富有弹性,但约束同样真实存在。我们依然受制于物理定律,例如信息传输不可能超越光速;我们构建系统的规模受到硬件能力的限制;在算法层面,也存在根本性的边界。我们可能在理论上知道如何解决某些问题,比如对蜂窝通信至关重要的 Viterbi 算法,但在很长一段时间内,并不知道如何将其有效实现。快速傅里叶变换亦是如此:理论早已存在,但在具备可计算性之前,实际应用无法推进。

 

除了科学和计算层面的限制,人类因素同样重要。我是否能聚集足够的人来完成工作?能否有效组织团队?理想情况下,软件开发的最佳团队规模是零,其次是一人,但这显然并不现实。正因为这些系统在经济与社会层面具有长期且深远的影响,我们不能依赖个体,而必须让软件本身具备超越个人生命周期的能力。

 

随着软件逐渐渗透到社会结构的缝隙之中,法律问题随之而来,例如数字版权管理。但更为根本的是伦理问题:我们或许知道如何构建某些系统,但是否应该构建它们?这是否符合人类的价值与尊严?

 

正是这些科学、技术、人类与伦理层面的静态和动态力量,共同作用于软件工程师。工程师所做的,正是在多重约束之间取得平衡,并在一种极其美妙的媒介中构建系统。

 

软件工程的发展可以划分为不同的时代。最早的时候,几乎不存在“软件”这一概念。人们只是直接操作机器,硬件与软件之间并无明确界限。以 ENIAC 为例,在插线板上插拔线路是否算编程?可以算,但那并不是我们今天所理解的软件。

 

直到 20 世纪 40 年代末、50 年代初,随着计算机形态的演进,人们才逐渐区分出软件这一独立存在。当时的软件几乎完全是定制化的,并且与特定机器紧密绑定。但软件本身的成本开始变得不可忽视。人们希望硬件不断升级,却不愿意因此放弃既有的软件投资,这促使了“软硬件解耦”这一关键问题的出现。值得注意的是,“数字化”这一术语直到 20 世纪 40 年代末才被提出,“软件”一词更是要到 50 年代才出现。软件作为一种独立实体被承认,其实就在我这一生中发生,这本身就令人震惊。

 

随着软件逐渐从硬件中解耦,Grace Hopper 等人开始意识到,软件不仅是一种技术活动,也可以成为一门产业和制度。最早的软件主要以汇编语言形式存在,与具体机器高度耦合。到了 20 世纪 60 年代,IBM 意识到可以构建一整套拥有统一指令体系的计算机架构,从而在升级硬件的同时保留软件资产。这一决定既是工程选择,也是商业和经济选择。一旦这一思路确立,软件需求迅速爆发,软件工程由此进入了第一个黄金时代。

 

在这一时期,软件成为一个独立产业,其核心挑战是“复杂性”。虽然以今天的标准看,当时的系统相对简单,但在那个时代,它们已经极难理解与构建。由于软件仍然高度贴近机器,最主要的抽象形式是算法抽象。计算机最初被用于数学运算,因此像 Fortran 这样的语言,本质上就是为了实现公式翻译。

 

Gergely:从时间线上看,这一代大致对应哪个阶段?

 

Grady:大约从 20 世纪 40 年代末持续到 70 年代末。这一时期的代表人物包括 Ed Yourdon、Tom DeMarco、Larry Constantine 等,实体关系模型等思想也在此时出现,并影响了数据领域。

 

这是一个极其活跃的阶段。流程图被发明出来,用于辅助系统设计;软件开发形成了分工体系,有系统分析人员、程序员、打孔员和计算机操作员。这种分工主要由经济因素驱动,当时机器的成本远高于人力成本,因此一切都围绕着如何最大化利用稀缺而昂贵的计算资源展开。

 

这一阶段的主要任务集中在数学计算和既有业务流程的自动化。例如会计、薪资等工作原本需要大量人工,通过软件不仅可以加速流程,还能提高精度。因此,当时绝大多数软件都是商业、数值和计算密集型系统。

 

在公众视野之外,国防、航空、气象和医疗等领域同样在推动关键创新。真正前沿的探索,往往发生在这些边缘领域,尤其是国防系统。冷战背景下,分布式、实时系统成为刚需。Whirlwind 计算机、SAGE(半自动地面环境系统)等项目相继出现。SAGE 系统的规模之大,据估计曾占用当时美国 20% 到 30% 的软件工程师资源。这是一个前所未有的工程规模,也标志着第一代软件工程黄金时代在边缘地带孕育出的深远影响。

 

Gergely:军事领域是当时软件研究和产业推进中最大的出资方,对吗?因为他们有这种现实需求。

 

Grady:确实如此。正因为当时存在明确而现实的威胁,军事领域不得不持续投入。因此,许多关键创新都发生在国防体系中。我在正在制作的一部计算机历史纪录片里用过一句话:计算机发展史中有两个最重要的驱动力,一个是商业,我们已经讨论过它的经济逻辑;另一个是战争。

 

现代计算的许多成果,其实是织就于“悲伤的织机”之上。我们今天习以为常的许多技术,例如互联网、微型化技术,几乎都源自政府资助,尤其是在冷战背景下。所以,从某种意义上说,我们确实“受益于”冷战。

 

为什么第一黄金时代走到了极限?

 

Gergely:那这一阶段仍然属于第一代软件工程的黄金时代吗?还是已经跨过去了?

 

Grady:这些事情依然发生在第一代黄金时代之中。当时的软件工程有一个相对清晰的“重心”,但在边缘地带也同时发生着大量推动行业演进的探索。在第一代软件工程黄金时代,软件的主要应用集中在数学计算和商业系统上,系统分解的核心方式是算法抽象,我们更多从过程和函数的角度理解世界,而非从数据或对象的角度出发。但在边缘领域,一些应用场景正在不断突破这一范式,例如对分布式系统的需求、多机协同的需求、实时系统的需求,以及对人机交互界面的需求。

 

今天我们所使用的图形用户界面,其源头可以追溯到 Whirlwind 和 SAGE 系统。当时最早的图形界面基于阴极射线管(CRT),这些探索并不处于当时软件开发的中心地带,却在后来产生了深远影响。这里的一个重要启示是:软件是一个极其动态、流动且高度可塑的领域。一旦我们掌握了构建某类系统的方法,并形成了可复用的模式,便会迅速发现它们在经济上具有新的应用空间。这正是软件工程第一个黄金时代的特征。

 

不过,到 20 世纪 70 年代末、80 年代初,这一体系开始显现出裂痕。NATO 举办的软件工程会议,是首次在公共层面清晰指出问题:NATO 意识到自己面临严重的软件困境:对软件的需求几乎无穷无尽,但在质量和速度上,行业却无法有效交付。这便是后来被称为“软件危机”的背景。

 

Gergely:这场“软件危机”究竟指的是什么?人们当时具体在担忧什么?

 

Grady:软件已经被证明具有巨大的价值,也具备明确的经济激励,但整个行业无法以足够快的速度、足够高的质量,生产出足够规模的软件。

 

Gergely:也就是说,软件既昂贵、又缓慢,而且质量还不稳定?

 

Grady:还要再加上一点:需求极其旺盛。人们不断地说,“我们还需要更多软件”。这四个因素叠加在一起,构成了当时的危机。这与今天我们担心的隐私监控、系统崩溃等问题并不相同。每一个黄金时代所面对的“危机”,其性质都会发生变化。

 

Gergely:站在今天回看那个时代,真的很难想象当时的处境。

 

Grady:在当时,这种危机是真实而迫切的,同时也是一个令人兴奋的时代。软件作为一种高度灵活、可扩展的媒介,几乎只受限于我们的想象力。

 

再叠加上微型化技术的突破:集成电路的出现、Fairchild 的诞生,以及硅谷的形成——这一切都源于晶体管技术。Fairchild 的第一大客户其实是美国空军,用于导弹项目。早期硅谷生产的大部分晶体管,都被用于冷战相关计划。但正是这些需求,奠定了规模化生产的经济基础,继而催生了集成电路、个人计算机等一系列后续发展。

 

到了 20 世纪 70 年代末,软件危机已经非常明显。以美国政府为例,他们意识到自己正陷入“巴别塔”问题:军方系统中使用的编程语言多达一万四千种。即便以今天的标准来看,这个数字也极其惊人,更何况当时软件系统的规模远小于现在。JOVIAL、COBOL 等语言被广泛使用,同时,ALGOL 等语言推动了形式化方法的发展。在 Dijkstra、Hoare 等人的影响下,人们开始将数学严谨性引入编程语言研究,形式语言理论由此兴起。

 

正是在这一背景下,美国政府推动了 Ada 项目,最初由一个联合项目工作组发起,目标是减少语言数量,试图以一种统一语言取而代之。这背后汇聚了大量研究成果,例如抽象数据类型、信息隐藏、关注点分离,以及 Knuth 提出的文学化编程思想。Ada 是一次试图在宏观层面整合这些理念的实践,这种规模的推动,只有当时的美国军方具备条件完成。

 

与此同时,贝尔实验室孕育出了 C 语言和 Unix,这些成果同样极为关键。而在学术与产业的边缘,一位名叫 Bjarne Stroustrup 的研究者开始思考:是否可以将 Simula 中的面向对象思想引入 C 语言,以解决其固有缺陷。值得一提的是,Simula 是最早的面向对象语言。这一切反映出一个更深层的变化:人们开始意识到,仅靠算法抽象已不足以应对复杂性,软件需要新的抽象方式:对象抽象。

 

有趣的是,这种“从过程看世界”与“从事物看世界”的分歧,早在柏拉图时代就已出现。柏拉图在其对话中探讨过,人们究竟应当以过程还是以对象来理解世界,“原子”这一概念本身就源自这一思想传统。换言之,抽象的选择并非新问题,只是被重新应用到了软件领域。

 

此外,函数式编程思想也在这一时期逐渐成形。Fortran 的发明者在完成该语言后,转而探索以无状态数学函数为核心的编程范式。我曾在他去世前几个月采访过他,问他为何函数式编程始终未能成为主流。他的回答是:函数式编程让“难的事情变简单”,却让“简单的事情变得异常困难”。这也解释了为何它始终占据重要位置,却未成为主导范式。

 

至此,我们来到了第一代软件工程黄金时代的尾声,并逐步迈向第二代。推动这一转变的力量包括持续增长的系统复杂性、规模化开发的困难,以及在国防领域中对分布式系统价值的进一步认知。同时,微型化技术的成熟也催生了个人计算机的出现。

第二个黄金时代:从“过程”到“对象”,软件开始变复杂

Gergely:这主要得益于晶体管和电子技术的突破,对吗?

 

Grady:正是如此。这是一个极具活力的时代。业余爱好者开始能够亲手组装计算机。在此前,这种规模的参与几乎是不可想象的。

 

Gergely:这是计算史上第一次,爱好者能够真正大规模参与进来吗?

 

Grady:是的。经济条件改善,加之军方推动的晶体管和集成电路生产,使得普通人能够获得这些元件。在硅谷,人们可以直接购买并实验这些技术。

 

“玩”在软件历史中始终扮演着重要角色。20 世纪 70 年代末到 80 年代初,是一个高度实验性的时代。有一本书《What the Dormouse Said》指出,个人计算机的兴起与嬉皮士反主流文化密切相关。这是一种“权力下放”的精神,与 Stewart Brand、Merry Pranksters 等人物和社群紧密相连,也催生了早期的在线社区,如 The WELL——后来我们称之为电子公告板系统。总的来说,70 年代末到 80 年代初,是一个充满可能性的阶段,大量新的技术路径和思想在此时同时萌发。

 

当时我们逐渐意识到,软件工程正在发生一场重要转变:人们开始不再仅仅通过“过程”来理解世界,而是通过“对象”和“类”。与此同时,分布式系统的需求,以及构建愈发复杂系统的现实压力,共同形成了一场“完美风暴”,推动了软件工程的第二个黄金时代。

 

坦率地说,我正是在那个阶段进入这个领域的,只是恰好身处正确的时间和地点。那时我在范登堡空军基地工作,参与导弹系统和航天系统的相关项目,其中还包括一个设想中的军事航天飞机计划。那是一个非常有趣的地方,因为几乎每周都会有一到两次发射。你会跑出去看着火箭升空,感叹“太不可思议了”。

 

时间来到 20 世纪 80 年代末,世界已经为一种新的软件观念做好了准备,这就是面向对象编程与面向对象设计。与第一代软件工程相比,它最大的不同在于抽象层次的变化。我们不再只是把数据视为一片原始的数据湖,再用算法去操作它,而是将数据与行为整合到同一个概念之中,形成对象。这种方式极大地拓展了我们能构建的系统复杂度,奠定了许多重要软件的基础。

 

如果你去计算机历史博物馆看看 MacWrite 和 MacPaint 的源码,就会发现它们是用 Object Pascal 写成的,是我见过结构最优雅的软件之一。它们的设计严谨、组织清晰,其中许多设计决策至今仍然能在 Photoshop 等现代系统中看到延续。这本身也说明了软件生命周期的一个有趣事实。

 

从对象的视角理解软件,被证明是一种极为有效的方法,因为它为解决软件复杂性问题提供了全新的路径。正如第一黄金时代一样,第二黄金时代同样充满活力。20 世纪 80、90 年代,涌现出一批重要人物,比如“三位一体”(我本人、Ivar Jacobson 和 Jim Rumbaugh),以及 Peter Coad、Larry Constantine、Ed Yourdon 等,共同推动了从“过程”转向“对象”的思考方式。当然,我们也犯过错误,例如一度过度强调继承,把它当成万能解法,这后来被证明并不完全正确。但从类和对象来理解世界的基本思想,最终被保留并沉淀下来。

 

与此同时,这也是一个经济层面的转折点。随着系统规模不断扩大,平台开始兴起。事实上,这在第一黄金时代就已有先例:人们反复构建相同的功能,于是开始将常用算法和过程打包复用,例如磁盘操作、终端输出、排序算法等。这些实践最终催生了软件共享的概念。在商业系统领域,IBM 的 SHARE 用户组织便是典型代表,它是一个由客户自发组织、相互共享软件的社群。

 

Gergely:这是不是一种相对原始的“打包”方式?比如把排序算法或一些通用功能集中起来分发?

 

Grady:需要澄清的是,这并不是 IBM 官方在做,而是完全由用户社区推动的,这实际上是最早的开源软件形态之一。当时的软硬件经济结构也很不同,软件通常是随硬件免费提供的,直到 20 世纪 60 年代后期,IBM 才意识到软件本身可以作为商品,开始将软硬件解耦并单独收费。

 

在更早的阶段,社区氛围非常开放,人们会说:“我写了这个工具,你拿去用吧。”这正是开源精神的雏形。类似的事情在第二黄金时代再次发生,只不过抽象层次更高了。人们开始共享库、组件和工具,比如驱动 CRT 显示器的程序。这些东西本身并不构成竞争优势,却极大地提升了构建复杂系统的能力。

 

在这种背景下,平台逐渐形成。随着库和组件规模不断扩大,分布式系统兴起,我们开始讨论“面向服务的架构”(SOA)。HTML 和 HTTP 让信息交换成为可能,人们开始设想通过网络共享图像、消息乃至服务。SOAP 等协议由此诞生,成为当今平台时代的前身。这些变化,正是在第二黄金时代为平台经济奠定基础。

 

Gergely:“平台”的兴起,具体是指什么?你如何定义平台?

 

Grady:AWS 和 Salesforce 都是典型例子。这些平台就像一座座“经济城堡”,周围有护城河。平台方允许你付费跨过护城河,使用其能力,其前提是:如果你自己从头构建,成本会高得多。因此,在第二黄金时代,我们看到了 SaaS 类企业的兴起,因为某些软件的复杂性和成本已经高到足以支撑这种商业模式。进入 90 年代末、21 世纪初,这是一个同样充满活力的阶段。互联网快速发展,软件开始真正渗透进社会的各个缝隙,成为文明基础设施的一部分。电子邮件就是一个典型例子。

 

Gergely:你什么时候有了第一个邮箱?

 

Grady:1987 年,那时还是 ARPANET。随着软件成为日常基础设施,第一黄金时代所担忧的许多问题逐渐“消失”了,不是被忽视,而是被内化进系统之中。优秀的技术会“蒸发”,成为空气的一部分。第二黄金时代,正是当今软件世界的根基。

 

2000 年前后,互联网泡沫破裂,经济逻辑无法支撑此前的扩张,同时还发生了 Y2K 问题。事后看似乎“什么也没发生”,但那是因为无数工程师付出了巨大的努力,才避免了灾难,这正是“最好的技术你看不见”的典型例子。

 

Gergely:我还记得千年虫之前的紧张气氛,甚至有电影预言世界末日。结果什么都没发生,很多人因此不再相信这种预警。

第三个黄金时代:不是 AI 时代,而是软件工程成熟期

Grady:此时,我想补充一条此前未展开的历史线索:AI。

 

AI 的第一个黄金时代出现在 20 世纪 40、50 年代,以符号主义为核心,代表人物包括 Herbert Simon、Newell 和 Minsky。当时也尝试过神经网络,比如用真空管实现的人工神经元,但由于算力和理论限制,最终失败,AI 进入寒冬。第二个黄金时代出现在 80 年代,以规则系统和专家系统为代表,尽管硬件和架构有所进步,但仍然难以规模化,最终再次陷入停滞。

 

进入 21 世纪,我认为我们其实已经身处软件工程的第三个黄金时代。其标志在于抽象层次的再次跃迁:从单个程序,提升到平台级库、框架和服务。我们不再自己实现消息系统或数据管理,而是直接使用现成的平台能力。AI 编程助手的出现,正是对这种复杂度增长的回应。它们并非偶然,而是第三黄金时代演进逻辑的自然结果。

 

当下的问题已经不同于前两代:软件规模空前庞大,安全、供应链攻击、系统信任成为核心议题;同时,软件巨头的重要性使其具备“系统性风险”;此外,技术伦理问题也被推到台前:我们能做到的事情,是否就意味着我们应该去做?

 

Gergely:很多人其实很少回头去想:这一切究竟是如何开始的,以及软件工程这门学科本身到底有多“年轻”。即便从 70、80 年这个尺度来看,它也不过勉强算是两代人的历史。

 

但我现在在行业里普遍感受到一种情绪,尤其是在最近这个阶段,很多软件工程师正在经历一种明显的“存在主义焦虑”。这种焦虑在这个冬歇期之后明显加速了。在冬歇期之前,AI 和大语言模型主要还只是自动补全工具,偶尔能生成一些代码;但在冬歇期之后,新一代模型已经能写出质量相当高的代码,甚至到了我开始真正信任它们的程度。

 

从历史上看,写代码一直是一件困难的事情,我们往往需要多年训练才能熟练,真正做到优秀则更久。因此,现在很多人开始陷入一种深刻的不安:机器居然能写出如此好的代码,这到底是怎么在短短几个月内发生的?接下来会怎样?编码一直和软件工程高度绑定,但现在似乎不再如此了。从你的历史视角来看,你如何理解当下正在发生的事情?

 

Grady:这并不是软件工程师第一次经历这样的存在性危机。类似的焦虑在第一代、第二代软件工程时期都出现过。所以我的态度一直是:历史会重演,这一轮也终将过去。我经常对担忧的人说,不必恐慌,把注意力放在基本功上,因为那些能力不会消失。

 

我曾经见过 Grace Hopper,正是她在 20 世纪 50 年代意识到,软件可以从硬件中抽象出来。这一思想在当时极具颠覆性,甚至引发了强烈反对。很多早期计算机工程师认为,如果不紧密依附硬件,就不可能写出高效的软件,他们担心这种做法会摧毁整个行业。事实证明,他们错了。

 

类似的争论后来也出现在 Fortran 出现时。有人坚信,人类手写的汇编代码一定比编译器生成的代码更高效,但随着抽象层级的提升,这种观点被一次次推翻。每一次抽象跃迁,都会让一部分既有技能失去中心地位,而这些变化往往正是由工程师自己推动的。

 

过去这种冲击没有引发如此剧烈的情绪,一个重要原因是:当时从业者规模很小,可能只有几千人;而今天,这个群体是数以百万计的。人们理所当然会问:那我怎么办?

 

我也经常被年轻工程师问到类似的问题:“我是不是选错了方向?”我的回答始终是:现在恰恰是进入软件行业的好时机。原因很简单,我们正在经历一次新的抽象层级跃迁,就像从机器语言到汇编语言,从汇编到高级语言,从高级语言到库和平台那样。

 

这种变化对我而言是极大的解放。我不再需要关注大量琐碎细节,但软件工程的基本原则依然存在。只要你是在构建需要长期维护、需要经得起时间考验的软件,这些能力就不可替代。

当然,如果你只是写一次性代码、写完就扔,那随便用什么工具都可以。我看到很多人正是用 AI 代理来完成这种“用完即弃”的自动化任务,这完全没问题,甚至非常有价值。

 

这让我想起早期个人计算机时代的“爱好者文化”。当年,许多原本与软件毫无关系的人开始自己写程序,产生了大量新想法。今天的情况极为相似。你可以用 AI 自动化一些过去经济上根本不值得做的事情,即便这些成果未必长期存在,但它们依然创造了价值。

 

Gergely:就像当年普通人可以买得起个人电脑一样,如今也有很多原本不在行业内的人开始写软件。我刚和楼上的邻居聊过,她是一名会计,但已经在用 ChatGPT 帮团队写代码,提高流程效率。

 

Grady:早期个人计算机时代,艺术家、游戏玩家纷纷涌入这一新媒介,创造了前所未有的活力,今天同样如此。很多所谓的“职业危机叙事”,其实源于对行业的狭隘理解。真正发生的事情是:软件正在扩展到更广泛的人群中。非职业开发者将会写出更多软件,而我认为这是极其积极的变化。就像当年的个人电脑反主流文化一样,历史正在重演。

 

Dario 判断错在根上:工具在变,软件工程的难题没变

 

Gergely:Anthropic 的 CEO Dario Amodei 曾预测,大约 90% 的代码会由 AI 生成,当时听起来很夸张,但后来事实证明他并非完全错误。最近他又说了一句更令人不安的话:“软件工程将在 12 个月内被自动化”。考虑到编码只是软件工程的一部分,这个说法显得更加激进。你怎么看?

 

Grady:我有一两点要说。

首先,我用 Claude,我用 Anthropic 的产品。我觉得它是我的首选系统。我用它来解决 JavaScript、Swift、PHP(没错,甚至 PHP)、还有 Python 的问题。我确实在用,而且它对我帮助很大——主要是因为我想用某些库:谷歌搜索太烂了,文档也很烂,所以我用这些 agent 加速对它们的理解。

但别忘了:我在这些领域里至少有一两年的经验——好吧,准确说是几十年——我理解基本原理。所以我才会说:基本功不会消失。这一点在任何工程学科里都成立:基本原理不会消失,变化的是我们使用的工具。

因此,Dario,我尊重你的表达,但也要认识到:他和我视角不同。他在带领一家公司,公司需要赚钱,他需要对利益相关方说话。所以他会说一些“耸动的”话。我记得他好像是在达沃斯讲的这些?

Gergely:对。

 

Grady:如果用一个“技术术语”来评价他的结论,那就是:它在根本上是错误的(profoundly wrong)。原因有几方面。

因为我认为他严重错了,而且错在几个层面。

第一,我接受他的部分观点:它会加速某些事情。但它会消灭软件工程吗?不会。我认为他对“软件工程是什么”有根本误解。

回到我一开始说的:软件工程师是负责平衡各种力量的工程师。代码只是我们的一种机制,但不是驱动我们的唯一因素。而他或他同事讨论的那些东西,没有一个触及软件工程师必须面对的那些决策问题——那些都不在当前所谓“自动化”的范围之内。

他的工作主要聚焦在最低层的自动化——我会把它类比为当年编译器所做的事情。所以我说:这是一轮新的抽象层级上升。开发者们别害怕:工具在变,但问题并没有消失。

我反驳他的第二个理由是:像 Cursor 这类系统,大多训练在一组我们已经反复见过的题型上。这没问题。就像第一代、第一个黄金时代,我们也面对的是一组相对固定的问题,于是围绕它们建库。现在同样如此。

如果我要在 CRUD 之上搭一个 UI,或者做一个偏 Web 的东西,当然可以做到。更重要的是,这种能力开始“下沉”——很多原本需要专业工程能力才能完成的事情,如今被更高层抽象直接交付给了更多人。多数人不会因此去做一门生意,当然也会有极少数人能把它做成产品。但关键在于:更高层抽象让他们能做以前做不到的事。

而 Dario 忽略了一点——我借用一句莎士比亚式的转述来讲:“计算世界里还有更多东西,超出你哲学所能梦到的范围。” 计算世界远远不只是“可规模化的 Web 系统”那一块。我们今天确实把很多东西应用在这种以 Web 为中心的大规模系统上,我觉得这很好、很棒,但这也意味着:外面还有大量东西尚未被自动化。我们会不断把“边界/前沿”继续往外推。

我一开始讲那些故事,就是因为历史正在重复——或者有人说,历史在押韵。今天发生的现象本质相同,只是发生在不同的抽象层级上。

这是第一点:软件世界比他所看的更大,不仅是“软件密集型系统”,也不仅是他关注的那一小块。

第二点,如果你看这些 agent 主要处理的系统类型,它们本质上是在自动化那些我们反复见过、它们也在训练中反复学过的模式。模式本身就是一种新的抽象:它不只是单个算法或单个对象,而是由一群对象和算法组成的“社会”,彼此协作。

这些 agent 很擅长自动化“模式的生成”。我想做某种东西,我可以用英语描述——因为模式本来就是我们用自然语言来描述的。

所以这就是为什么我认为他错了。祝他好运。但我觉得这是一个更令人兴奋的时代,而不是一个需要在存在层面恐慌的时代。

我再讲一个关于“抽象层级上升”的故事。

英语是一种非常不精确的语言,充满歧义与细微差别。你可能会想:这种语言怎么可能成为“有用的语言”?答案是:我们作为软件工程师其实一直在这样做。

我去找一个人说:“我希望系统能做这件事,大概长这样。”然后给一些例子。我一直在做这个。然后会有人把它转成代码。也就是说,我们早就处在一个更高层抽象:我说“我想要它做什么”。

举个具体例子:我最近在用一个以前从没碰过的库——JavaScript 的 D3,它能做非常迷人的可视化。我去找了一个网站,叫 Victorian Engineering Connections。那是个很可爱的小站,一个人给某个博物馆做的项目(叫 Andrew)。你可以输入一个名字,比如 George Boole,然后你能看到他的信息,还能看到围绕他的社交网络,你可以点进去探索,非常酷。

我当时想:“我也想要这种东西,但天哪,我根本不会做,怎么办?”他把代码给了我,我发现它用了 D3。我对 D3 一无所知。于是我对 Cursor 说:“给我做一个最简单的版本:五个节点,展示出来。”这样我就能研究代码。

接着我又能继续说:“我真正想做的是这个。把节点做成某种样式,取决于它们的类型。”就像我对一个人类合作者提需求一样,我是在用英语表达我的需要,而现在我不必再为把它变成现实而苦苦劳作——我可以跟工具对话,让它帮我完成。

所以它缩短了“我想要什么”和“它能做到什么”之间的距离。我觉得这非常棒,这是一种突破。

但别忘了,我对 Dario 说过:这只在某些场景下有效——当我做的事情是别人已经做过成百上千次的那类事。按费曼(Feynman)的风格,他可能会说:“你自己去做,这才是你理解的唯一方式。”我的反应是:这没错,但世界上我好奇的东西太多了,我不可能全部都靠自己从零理解完。那就让工具替我做一部分吧——我来决定我想做什么。

所以我才说,这些工具就是一次新的抽象层级跃迁:它们在缩短我用英语说的需求,与最终落到编程语言之间的距离。

最后我还想说:什么语言既精确又足够表达,能用来构建“可执行的工件”?我们把它叫作编程语言。恰好,英语在某些条件下也足够“像一门编程语言”——有点像 COBOL:如果我在一个结构足够清晰的领域里用足够明确的短语表达需求,它能给我一个“足够好”的解,而我作为懂基本功的人,可以再去推进、校正、清理这些细节。

这就是为什么基本功如此重要。

低垂的果实先被摘走:该往上爬了

 

Gergely:每一次抽象层级的跃迁,都会让一部分技能变得过时,同时催生对新技能的需求。比如从汇编语言过渡到更高级语言之后,熟悉某块硬件指令集、并针对其进行极致优化的能力,就被更高层次的抽象思维所取代。而在当下这次跃迁中,可以说我们正在进入一个“无需再亲手写代码,计算机会自动生成,我们只需检查和微调”的阶段。作为软件从业者,哪些技能会逐渐过时?又有哪些能力会变得更加重要?

 

Grady:如今的软件交付流水线比它本应具备的复杂得多,如果没有成熟的流水线,仅仅让一个系统跑起来都非常困难。而在 Google、Stripe 这样的公司内部,已经构建了规模庞大且高度定制化的基础设施体系。

 

正因如此,这里存在大量“低垂的果实”,非常适合被自动化取代。我并不需要人类去填补这些繁琐而边缘化的环节。在这些领域,我们很可能会看到智能代理的出现:当我需要在某个地区快速部署资源时,我并不想亲自编写那些复杂、混乱的代码,而是更愿意让代理系统来完成。这类工作在经济效率和安全性上都具有明显的自动化价值,因此确实会带来一部分岗位的消失。对应地,人们需要重新学习,转向更高层次的应用构建。

 

同样地,那些过去专注于开发某个 iOS 应用或具体前端产品的技能,也会面临岗位收缩。因为现在,很多事情只需通过提示就能完成。这并非坏事,它让新一代人能够完成过去只有专业工程师才能完成的工作,这正如个人电脑时代所发生的一切。

 

因此,人们应该做的不是抗拒变化,而是向上移动一个抽象层级,开始关注“系统”本身。我认为,当前的转变不再是从程序到应用,而是从应用到系统。新的技能重心在于:如何在大规模环境中管理复杂性,如何同时处理技术因素与人的因素。只要你具备这种系统性能力,你的工作不仅不会消失,反而会更加稀缺和值得被需要。

 

Gergely:你会建议学生、即将进入行业的新人,以及希望回过头来夯实基础的资深工程师,从哪些方面入手?

 

Grady:当我面对极其复杂的问题时,最常回归的,是系统理论。可以去读 Herbert Simon 和 Allen Newell 的研究成果,也可以关注圣塔菲研究所关于复杂性与系统的相关工作。这些系统理论的基本原理,为我构建下一代系统提供了稳固的支点。

 

我曾参与 NASA 火星任务的研究。当时的问题是:如何支持人类执行长期深空任务,并让机器人在火星表面自主运作。这本质上是一个系统工程问题,因为这些能力必须“具身”于航天器之中。而当今大量 AI 软件是去物理化的,它们与现实世界并无直接连接。

 

那段时间,我也在跟随多位神经科学家学习,试图理解大脑的结构。正是在这个过程中,我意识到系统工程中的某些结构模式,可以直接应用于超大规模系统的设计。比如 Marvin Minsky 提出的“心智社会”模型,本质上是一种多智能体系统架构,而今天我们才刚刚开始真正触及智能体编程的潜力。

 

类似的还有早期 AI 系统中提出的“黑板模型”“全局工作空间”,以及 Rodney Brooks 提出的次级控制架构,这些都受到生物系统的启发。生物系统本身具备高度有效的架构方式,即便没有中央控制,也能完成复杂行为。

 

因此,回到你的问题,我的建议是:从系统视角重新审视架构,从生物学、神经科学和真实世界的复杂系统中汲取灵感。很多问题其实早已被研究过,只是我们在新的语境下重新应用了它们,工程的基本原理并没有消失。

 

Gergely:回顾以往几次抽象层级跃迁和软件工程的黄金时代,那些在新时代初期脱颖而出、即便在旧时代并不出众的人,往往做对了什么?从历史中,你会给出怎样的建议?

 

Grady:正如我之前提到的,软件领域真正限制我们的,其实是想象力。当然,我们同时也受到物理规律、算法能力以及伦理约束的限制。但现在正在发生的是:许多开发过程中的摩擦、成本和阻力正在消失。这意味着,我们终于可以把更多注意力投入到想象力本身,去构建过去根本无法实现的事物。以前做不到,是因为无法组织足够的人力、无法承担成本、也无法获得全球性的影响力。而现在,这些限制正在被解除。

 

因此,你应该把这一切视为机会。对某些既得利益者而言,这可能是损失;但从整体来看,这是一次净增益。它解放了我们的想象力,让我们能够在现实世界中做出此前不可能实现的事情。这是一个令人兴奋、同时也令人恐惧的时代,但这正是应有的状态。当你站在通往新事物的门槛上,可以选择凝视深渊、害怕坠落;也可以选择纵身一跃、展翅高飞。现在,就是该飞的时候。

 

参考链接:

https://www.youtube.com/watch?v=OfMAtaocvJw

背景

Access 从 2007 版开始已经内置了日期选择器,文本框格式设置为日期型后,右侧会自动出现一个小日历图标。但这个内置功能有几个明显局限:

  • 老格式不支持:.mdb 格式的旧库没有这个功能
  • 没有年月选择:只能逐月翻页选日期,无法直接选"某年某月",在账期、报表筛选等场景下操作繁琐
  • 界面较简陋:样式陈旧,不支持自定义,也没有时间选择部分

这个项目的出发点就是在不依赖任何外部控件的前提下,用标准模块 + Access 窗体自己实现一套更完整的日期输入组件,同时覆盖日期时间选择和年月选择两种场景。项目已开源,源码见文末链接。

它长什么样?

日期时间选择器


左侧是经典的日历网格,右侧是小时/分钟滚动列表。当前月日期黑色显示,非当月灰色,今天蓝色边框,选中日期蓝色背景白字。鼠标悬停时还有浅蓝色高亮效果,交互体验接近现代 Web 日期选择器。

年月选择器

4 列 × 3 行的月份网格,支持按年、按十年快速翻页,适用于报表筛选、账期选择等只需要"年+月"的场景。


技术架构:为什么选择这种实现方式?

这个项目的核心设计理念是 "纯标准模块 + 表达式事件绑定",具体来说:

1. 零窗体代码

生成的 frmDatePickerfrmYearMonthPicker 窗体本身没有任何代码模块。所有逻辑都写在标准模块(Module_DatePicker.bas / Module_YearMonthPicker.bas)中。

2. 表达式事件绑定

控件的事件不是通过窗体代码模块的 Private Sub 来响应,而是直接在控件属性中写表达式:

OnClick = "=DatePicker_DayClick(1)"
OnMouseMove = "=DatePicker_DayMouseMove(1)"

这种方式的好处是:窗体可以完全由代码自动生成(CreateForm + CreateControl),不需要手动在设计视图中操作,真正实现一键部署

3. 窗体自动构建器

导入 .bas 模块后,只需在立即窗口执行一行命令,构建器就会自动创建窗体、布局控件、绑定事件:

CreateDatePickerForm       ' 创建日期时间选择器
CreateYearMonthPickerForm  ' 创建年月选择器

再次执行会自动删除旧窗体并重建,方便迭代调整。

4. Win32 API 精准定位

在下拉模式下,选择器窗体会精准定位到文本框正下方,就像一个真正的下拉控件。这是通过 GetFocusGetWindowRectMoveWindow 这组 Win32 API 实现的,同时兼容 32 位和 64 位 Access。

5. 鼠标悬停效果

42 个日期格子(或 12 个月份格子)都绑定了 OnMouseMove 事件。通过模块级变量 m_iHoverCell 跟踪上一个悬停位置,实现了高效的悬停高亮切换——只刷新变化的两个格子,而不是整个网格,避免了闪烁。


怎么用?三步上手

第一步:导入模块

从 GitHub 下载 .bas 文件,在 VBA 编辑器中 文件 → 导入文件

  • Module_DatePicker.bas — 日期时间选择器
  • Module_YearMonthPicker.bas — 年月选择器(按需导入)

第二步:创建窗体

在立即窗口(Ctrl+G)中运行:

CreateDatePickerForm

第三步:调用 API

最简用法——一行代码弹出选择器:

Dim dt As Variant
dt = ShowDatePicker()
If Not IsNull(dt) Then MsgBox "选择了: " & dt

绑定文本框——双击弹出、选完自动回写:

' 在窗体的 Form_Load 中
AttachDatePicker Me, "txtOrderDate"           ' 日期+时间
AttachDatePicker Me, "txtBirthday", False     ' 仅日期
AttachYearMonthPicker Me, "txtMonth", "yyyy-mm"  ' 年月

<!-- 图片位置:绑定文本框后的实际交互效果(双击弹出、选择、回写) -->

零代码绑定——直接在属性表中写表达式:

=PickDateForCtl("frmOrder","txtDate",True)

这意味着你甚至不需要打开 VBA 编辑器,直接在控件属性里粘贴一行表达式就能用。


进阶玩法

自定义输出格式

PickDateFor Me.txtDate, False, "yyyy-mm-dd"
PickYearMonthFor Me.txtMonth, "yyyy年mm月"

自动扫描绑定

如果你的窗体上有大量日期字段,可以一键全部绑定:

AttachDatePickerAll Me              ' 自动识别名称含 date/日期/time 的文本框
AttachDatePickerAll Me, "dt"        ' 匹配 dt 开头的文本框

修改主题色

模块顶部的颜色常量可以轻松替换:

Private Const CLR_BLUE As Long = 16024898   ' 改成你喜欢的颜色
Private Const CLR_HOVER As Long = 16576233  ' 悬停色

兼容性

环境支持情况
Access 2010 / 2013 / 2016 / 2019 / 365
32 位 / 64 位
.accdb / .mdb
运行时版本 (Runtime)

完整源码

项目已开源,欢迎 Star ⭐:

GitHub 地址:https://github.com/miaowei2/access-datepicker

包含:

  • Module_DatePicker.bas — 日期时间选择器(含构建器 + API + 事件处理)
  • Module_YearMonthPicker.bas — 年月选择器(独立模块,可单独使用)
  • 详细的使用说明文档

下载后直接导入即可使用,无需任何额外配置。


写在最后

Access 虽然"老",但在中小企业、政府机关、制造业中依然有着广泛的应用。很多运行多年的 Access 系统,承载着核心业务数据,短期内不可能迁移到其他平台。

与其抱怨 Access 的种种不足,不如用技术手段去改善它。一个好用的日期选择器看似是小事,但它直接影响一线操作人员的录入效率和数据质量。

如果你的团队正在使用 Access,或者你是一名 Access 开发者,欢迎试用这个开源组件。 如果觉得有用,请帮忙转发给更多需要的人。

在 Access 开发中遇到任何问题,也欢迎在公众号后台留言交流。我会持续分享 Access 实战技巧和开源工具,帮助大家把这个"老伙计"用得更顺手。


Access 开发」 专注于 Microsoft Access 开发与企业级应用,提供以下服务:

📚 技术培训

Access VBA 从入门到精通(线上/线下)

Access + SQL Server 企业级开发实战

Access 系统性能优化与架构设计

💼 定制开发

企业 ERP/CRM/进销存等系统开发

旧系统升级与性能优化

🔧 技术支持

代码审查与重构建议

疑难问题远程诊断

一对一技术辅导

联系方式:

公众号后台留言

邮箱:will.miao@edonsoft.com

微信:edonsoft

技术改变业务,专注创造价值。

本来想做一个能把碎片化信息变更有序的工具,起名 Curato 。折腾了两个月,自认为 UI 挺干净,逻辑也顺。
结果推出去之后现实教做人。Reddit 上的老哥们看一眼就走了。

现在看着后台寥寥无几的数据库记录,深刻怀疑自己是不是又造了一个“只有开发者自嗨”的轮子。
网站地址: https://www.curato.live/

大家平时对这种收藏/整理类工具有真实需求吗?还是说直接用 Notion/浏览器书签就到头了?想听听真实反馈,哪怕是吐槽 UI 丑也行。

工业织物缺陷目标检测数据集(1000+高精度标注样本)| AI训练适用于目标检测任务

一、引言:工业视觉检测面临的新挑战

在智能制造与工业 4.0 的背景下,机器视觉在质量检测环节中的地位愈发关键。织物瑕疵检测作为工业视觉的重要分支,广泛应用于纺织、服装、功能性材料等领域,其检测结果直接影响产品合格率、生产成本与企业品牌信誉。

然而,与金属表面、印刷品等具有明显结构纹理的检测对象不同,高精细织物(C1 类织物)存在纹理极弱、表面特征稀疏、缺陷对比度低等问题,对传统视觉算法及深度学习模型提出了更高要求。

为此,本文将系统介绍一个面向工业织物弱纹理场景的瑕疵检测数据集,并结合目标检测任务的工程实践,探讨其在模型训练、鲁棒性评估与算法研究中的应用价值。
在这里插入图片描述

数据集下载

链接:https://pan.baidu.com/s/1OVK43Qr_gpi1iCzQ6oJ4Vw?pwd=g9gg
提取码:g9gg 复制这段内容后打开百度网盘手机App,操作更方便哦

工业织物瑕疵检测

本数据集专为工业织物瑕疵检测与分类任务构建,特别聚焦于 C1 类高精细织物。此类织物表面光滑、纹理极细、几乎无可见结构特征,典型代表如 普通粘胶纤维与丝绸织物,适用于弱纹理背景下的目标检测算法研究与模型鲁棒性评估。

数据集中包含 1000+ 张 768×512 分辨率的织物图像,标注了四类常见工业缺陷,包括:洞(Hole)、异物(Foreign Object)、油斑(Oil Stain)及织线错误(Weaving Defect)。数据集采用标准训练、验证与测试目录结构组织,便于直接用于 YOLO 等目标检测框架训练。

该数据集适用于 工业视觉检测、弱纹理表面缺陷识别、制造业质量控制、深度学习模型可靠性验证等研究与应用场景,为探索复杂微弱特征中的瑕疵识别提供理想实验基准。

数据集详细信息
图像数量: 1000+
图像分辨率: 768×512像素
课程:
0: 洞
1:异物
2:油斑
3:线程错误

path: main/datasets
train: images/train
val: images/val
test: images/test

nc: 4

names: ['洞','异物','油斑','织线错误']

nc: 4
names: ['Hole', 'Foreign Object', 'Oil Stain', 'Weaving Defect']

在这里插入图片描述

二、数据集背景与设计目标

2.1 织物类型与检测难点

本数据集聚焦于 C1 类高精细织物,其典型特征包括:

  • 表面光滑、纹理周期性弱
  • 几乎无明显方向性结构
  • 局部缺陷与背景灰度差异极小

代表性材料包括:

  • 普通粘胶纤维织物
  • 丝绸类高精度织物

在此类织物上,孔洞、油斑等缺陷往往呈现为微弱灰度扰动,极易被噪声、光照变化所淹没,是检验目标检测模型感知能力与泛化性能的理想场景。

2.2 数据集设计初衷

该数据集在构建时重点关注以下目标:

  1. 服务真实工业场景
    图像来源贴近实际生产线拍摄条件,而非合成或过度增强数据。
  2. 考验模型在弱纹理背景下的检测能力
    避免通过明显纹理“降低难度”。
  3. 兼容主流目标检测框架
    数据结构与标注格式可直接用于 YOLO 系列模型训练。

在这里插入图片描述

三、数据集整体概述

3.1 基本信息

项目说明
图像数量1000+ 张
图像分辨率768 × 512
数据类型RGB 工业织物图像
标注形式目标检测(Bounding Box)
任务类型工业缺陷检测

3.2 缺陷类别定义

数据集中共标注 4 类常见工业织物缺陷

类别 ID英文名称中文说明特征描述
0Hole结构性破损,边缘不规则
1Foreign Object异物表面附着非织物物体
2Oil Stain油斑灰度渐变,边界模糊
3Weaving Defect织线错误经纬异常、线条错位

这些缺陷在弱纹理背景下形态差异细微,对检测算法的特征表达能力提出较高要求。


四、数据集目录结构与标注规范

4.1 数据集路径结构

数据集遵循标准的 YOLO 训练目录组织方式:

main/datasets/
├── images/
│   ├── train/
│   ├── val/
│   └── test/
└── labels/
    ├── train/
    ├── val/
    └── test/

该结构可直接对接:

  • YOLOv5 / YOLOv8
  • MMDetection(简单转换即可)
  • Ultralytics 官方训练脚本

4.2 类别配置文件示例

nc: 4
names: ['Hole', 'Foreign Object', 'Oil Stain', 'Weaving Defect']

标注文件采用 YOLO 标准格式:

class_id x_center y_center width height

所有坐标均为 相对比例(0~1),便于多分辨率训练。


五、数据集特点与技术价值分析

5.1 弱纹理背景下的目标检测挑战

与常规数据集(如 COCO、VOC)相比,本数据集具备以下显著特点:

  • 背景信息极少:模型无法依赖纹理模式记忆
  • 缺陷尺度变化大:从微小油斑到明显孔洞
  • 类别间视觉差异小:尤其是油斑与异物

这使得模型必须真正学会:

  • 局部异常建模
  • 细粒度特征提取
  • 高噪声环境下的目标定位

5.2 适合作为算法评测基准

该数据集非常适合用于:

  • YOLO 系列模型对比(v5 / v7 / v8 / v10)
  • Anchor-Free 与 Anchor-Based 方法对比
  • 注意力机制(SE / CBAM / ECA)效果评估
  • 数据增强策略在工业场景中的有效性验证

六、典型应用场景

6.1 工业视觉检测系统

  • 纺织品在线质检
  • 自动剔除瑕疵布料
  • 减少人工目检成本

6.2 制造业质量控制(QC)

  • 生产批次质量评估
  • 缺陷类型统计与溯源分析
  • 工艺参数优化反馈

6.3 学术研究与工程教学

  • 工业 AI 课程实验数据
  • 弱监督 / 小样本学习研究
  • 模型鲁棒性与泛化性测试

七、基于 YOLO 的训练实践建议

7.1 模型选择建议

  • 实时检测:YOLOv8n / YOLOv8s
  • 高精度场景:YOLOv8m / YOLOv8l
  • 研究用途:引入 Transformer 或 Attention 改进模型

7.2 训练技巧

  • 使用 Mosaic + MixUp 但控制比例
  • 启用 Focal Loss 以缓解类别不平衡
  • 适当提高输入分辨率(如 1024)
    在这里插入图片描述

八、结语:工业弱纹理检测的价值与未来方向

工业织物瑕疵检测并非简单的目标检测问题,而是一个融合了弱特征感知、噪声抑制与细粒度识别的综合挑战。本文介绍的数据集,正是围绕这一核心难点构建,具备较高的工程与研究价值。

无论是用于工业落地,还是作为算法验证基准,该数据集都为复杂弱纹理场景下的智能视觉检测研究提供了可靠支撑。

随着更先进的模型结构与训练策略不断涌现,基于此类真实工业数据集的探索,将持续推动智能制造向更高精度、更高可靠性方向发展。

Apple Safari 26.3 发布 - macOS 专属浏览器 (独立安装包下载)

适用于 macOS Sequoia 和 macOS Sonoma 的 Safari 浏览器 26

请访问原文链接:https://sysin.org/blog/apple-safari-26/ 查看最新版。原创作品,转载请保留出处。

作者主页:sysin.org


Safari 浏览器
风驰电掣
数据密不透风

Safari

无论在哪种 Apple 设备上,用 Safari 浏览器上网都再好不过。它带来健全的自定选项、强大的隐私保护功能,以及傲人的电池续航,让你随时都能自如地浏览网络 (sysin)。至于速度,这款飞快的浏览器更再次超越自我。

了解如何将 Safari 浏览器设为默认

查看详细功能介绍

新增功能

Safari 26.3 Release Notes

Released February 11, 2026 — 26.3 (20623.2.7)

Safari 26.3 is available for iOS 26.3, iPadOS 26.3, visionOS 26.3, macOS 26.3, macOS Sequoia, and macOS Sonoma.

详见:Safari Release Notes

下载地址

Safari 26 for macOS Tahoe 包含在系统软件更新中。

旧版不定期清理。

Safari 26 for macOS Sequoia Installer

Safari 26 for macOS Sonoma Installer

Safari for macOS Ventura 停止更新,旧版请访问:

Safari for macOS Monterey 停止更新,旧版请访问:

其他下载:

更多:macOS 下载汇总 (系统、应用和教程)

全文链接:https://tecdat.cn/?p=45027
原文出处:拓端数据部落公众号

封面

引言

在当今软件开发领域,效率与质量始终是开发者追求的核心目标。传统的开发模式往往需要开发者手动完成从环境搭建到代码编写、测试、部署的全流程,不仅耗时耗力,还容易因人为疏忽出现错误。随着人工智能技术的发展,智能体编码逐渐成为提升开发效率的新途径。作为数据科学家,我们在过往的咨询项目中发现,结合先进大语言模型的智能体开发工具能够显著缩短项目周期,降低开发门槛。本文将详细介绍如何使用Gemini 3.1 Pro和Gemini CLI构建一个完整的Web应用,从技术栈选择到最终部署,全方位展示智能体编码的优势。
本文内容改编自过往客户咨询项目的技术沉淀并且已通过实际业务校验,该项目完整代码已分享至交流社群。阅读原文进群获取更多最新AI见解和行业洞察,可与900+行业人士交流成长;还提供人工答疑,拆解核心原理、代码逻辑与业务适配思路,帮大家既懂 怎么做,也懂 为什么这么做;遇代码运行问题,更能享24小时调试支持。

智能体编码的发展与Gemini 3.1 Pro的优势

早期的AI辅助开发工具主要集中在代码补全和简单的语法纠错上,虽然能在一定程度上提升效率,但无法完成端到端的开发任务。随着大语言模型能力的不断提升,智能体编码应运而生,它能够理解开发者的自然语言需求,自主规划开发步骤,生成代码并进行测试和部署。Gemini 3.1 Pro作为谷歌最新的大语言模型,在推理、编码和多模态理解方面表现出色,其长上下文处理能力、工具使用能力和分步推理能力,使其成为智能体编码的理想选择。

Gemini 3.1 Pro的智能体技术栈

使用Gemini 3.1 Pro进行智能体编码主要有四种方式,开发者可以根据自己的经验水平和对技术栈的控制需求进行选择。第一种是浏览器端的快速原型工具,适合初学者快速验证想法;第二种是集成在代码编辑器中的助手,适合习惯在IDE中工作的开发者;第三种是全流程的AI原生开发环境,适合端到端的自主开发;第四种是命令行工具,也就是我们本文重点介绍的Gemini CLI,它能够给开发者提供对上下文、记忆、自定义技能和工作流的完全控制。

相关文章

DeepSeek、LangGraph和Python融合LSTM、RF、XGBoost、LR多模型预测NFLX股票涨跌|附完整代码数据

原文链接:https://tecdat.cn/?p=44060

开发环境准备

在开始使用Gemini CLI之前,我们需要先准备好开发环境。首先确保本地已经安装了Node.js 20及以上版本和npm包管理器,同时拥有一个谷歌云账号并开启了计费功能,以便能够访问Gemini 3.1 Pro的API。
首先,我们全局安装Gemini CLI:

npm i -g @google/gemini-cli
......
# 此处省略了权限验证和版本检查代码

安装完成后,创建一个新的项目文件夹并进入:

mkdir geek-match
cd geek-match
......
# 此处省略了文件夹初始化和git配置代码

然后启动Gemini CLI:

首次启动时,需要使用谷歌账号或API密钥进行身份验证。验证完成后,在Gemini CLI中输入“/model”,选择手动模式,然后从列表中选择最新的Gemini 3.1 Pro预览模型。

扩展Gemini CLI功能

Gemini CLI支持类似VS Code的扩展市场,我们可以通过安装扩展来增强其功能。对于本文的项目,我们安装两个扩展:一个用于高级网络和仓库搜索,另一个用于获取最新的技术栈文档。
安装扩展的命令如下:

安装完成后,重启Gemini CLI,就可以看到新安装的MCP服务器和技能已经加载到环境中。

创建自定义技能

安装的扩展是通用的,为了构建生产级应用,我们需要创建针对特定技术栈的自定义技能。本文的项目使用以下技术栈:Next.js App Router、Tailwind CSS + shadcn/ui、Drizzle ORM + Neon Postgres、Clerk身份验证、Vitest测试、Vercel部署。
 

提交指令后,Gemini 3.1 Pro会生成结构化的技能定义,并提示我们进行本地安装。确认安装所有技能后,输入“/skills reload”重新加载技能,再输入“/skills list”验证技能是否添加成功。

完整内容及更多AI见解和行业洞察请进群获取。

项目开发:构建极客匹配应用

现在我们开始使用Gemini 3.1 Pro构建一个名为极客匹配的Web应用,这是一个面向极客的社交匹配应用,具有滑动浏览 profile、双向喜欢生成匹配、匹配列表展示、用户身份验证等功能。
在Gemini CLI中输入以下初始指令:

构建一个生产级的极客匹配最小可行产品。应用需求:
- 基于滑动的profile浏览(喜欢/不喜欢)
- 双向喜欢生成匹配
- 匹配列表页面
- 仅允许已验证用户访问
- 使用Tailwind + shadcn/ui的简洁现代UI
技术栈(请勿更改):
- Next.js App Router(TypeScript)
- 服务端操作 + 路由处理器
- Neon上的Postgres
- 带迁移的Drizzle ORM
- Clerk身份验证
- Vitest单元测试
- 可部署到Vercel
执行规则:
1. 先展示文件夹结构
2. 生成数据库schema和迁移
3. 实现API逻辑和匹配引擎
4. 构建滑动UI
5. 添加受保护路由
6. 生成匹配逻辑的单元测试
7. 提供Vercel部署的环境变量清单
保持解释简洁,专注于简洁、模块化、生产级代码。每一步验证后再进行下一步。
......
# 此处省略了项目初始化的详细配置代码

提交指令后,Gemini CLI会先分析项目结构,并建议激活相应的自定义技能。确认激活技能后,它会按照执行规则逐步生成代码。如果想加快流程,可以按CTRL + Y切换到YOLO模式,跳过确认提示。

持久化上下文和记忆

为了让智能体在开发过程中保持状态,我们使用“/init”命令创建持久化项目记忆,这会在项目根目录生成一个PROJECT_MEMORY.md文件,用于记录项目结构、技术栈、约定、构建命令、测试命令和部署说明。
我们还可以向记忆中添加指令,让智能体遵循专业的Git工作流:

在每次重大更改后,自动暂存相关文件,验证类型和测试,然后使用常规提交格式创建一个小的原子性git提交,永远不提交机密信息,并在继续之前显示提交消息。
......
# 此处省略了Git工作流的详细配置代码

此外,我们还可以添加使用GitHub CLI发布仓库和检查CI状态的指令,确保代码能够持续集成和部署。

添加可靠性和防护栏

生产级应用需要处理各种异常情况和安全威胁,我们在Gemini CLI中输入以下指令来增强应用的可靠性:

这些防护栏能够确保应用在面对恶意输入、意外边界情况、竞争条件和API滥用时仍然稳定运行。

环境变量配置和本地验证

现在我们将应用连接到真实的生产服务,首先在Clerk官网创建一个免费账号和新应用,获取发布密钥和密钥;然后在Neon官网创建一个免费账号和新项目,获取Postgres连接字符串。
使用项目中的.env.example作为模板,创建.env.prod.local文件,替换其中的占位符值:

确保永远不要将.env.prod.local提交到Git。配置完成后,在Gemini CLI中输入指令,让它启动开发服务器、运行数据库迁移、验证连接和身份验证流程。

如果一切配置正确,CLI会提供本地开发地址,比如http://localhost:3000。在浏览器中打开该地址,测试注册、登录、创建profile、滑动匹配等功能。

部署到生产环境

本地验证通过后,我们开始部署到生产环境。首先在Gemini CLI中输入指令,让它提交并推送所有更改到GitHub:

确保所有本地更改都正确提交,没有包含机密信息,主分支是最新的。然后在Vercel官网创建一个免费账号,连接GitHub账号,导入geek-match仓库。

Vercel会自动检测到这是一个Next.js应用并配置构建设置。在部署前,滚动到环境变量部分,导入.env.prod.local或手动添加所有环境变量。确保生产环境变量与本地值一致。

点击部署后,Vercel会安装依赖、构建项目、运行生产构建并 provision 部署。完成后,应用就会上线。在浏览器中打开该地址,测试所有功能是否正常工作。

开发流程总结

整个极客匹配应用的开发流程可以总结为以下步骤:

  1. 环境准备
  2. 安装扩展
  3. 创建自定义技能
  4. 项目开发
  5. 持久化记忆
  6. 添加防护栏
  7. 本地验证
  8. 部署上线

结论

通过使用Gemini 3.1 Pro和Gemini CLI,我们在很短的时间内就完成了一个功能完整、生产级的Web应用开发。从环境搭建到最终部署,智能体编码不仅提升了开发效率,还降低了出错的概率。自定义技能让智能体理解我们的技术栈和最佳实践,持久化记忆让它遵循专业的开发流程,防护栏确保了应用的可靠性和安全性。整个开发过程的API使用成本也非常合理,相比传统开发模式具有很大的优势。如果你也想提升开发效率,不妨尝试一下这种智能体编码的工作流。

封面

case: 同事点外卖 说 平台 A 比平台 B 上同个店的同个品便宜。

思考: 移动端、同店、同品、多平台 比较价格和代理下单, 有提供类似功能的提供方嘛? 是否是个点子?有没有区分度?有没有护城河? vibe 一波有没有搞头?

v 油们 讨论下。

在将算法模型部署至 征程 6 芯片平台的实际应用中,由于算法设计与硬件架构特性存在差异,可能会出现部分算子适配度有待提升、运行效率有待优化以及量化精度可进一步优化等情况。解决好这些问题有助于模型更快更好的运行,充分发挥硬件性能。

本文聚焦于算法模型在 征程 6 芯片上部署时的算子支持问题,包括算子不支持、算子运行效率低、算子量化精度差问题的解决和优化建议。重点阐述解决上述问题的优化思路给出优化案例,使模型在 征程 6 芯片平台实现更快速、更稳定的运行,为算法高效落地提供实用的技术路径。

1.算子替换

算子替换主要解决模型中存在 BPU 不支持的算子问题。当遇到无法支持的算子时,为提升执行性能需要使用 BPU 支持的算子对不支持的算子做替换,使尽可能多的运行在 BPU 中。

1.1 Scatternd 的产生和消除

ScatterND 算子由 op 做了 slice 操作 之后 又进行 inplace 产生,因此会引入 CPU 算子。若导出的 onnx 中均存在大量 ScatterND,希望从算法侧进行移除, 等价替换相关操作即可。以下给出几种使用场景:

场景 1:

# dummy code to generate ScatterND
import torch
from torch import nn
​
class DummyModel(nn.Module):
    def __init__(self):
        super().__init__()
​
​
    def forward(self, x):

将模型导出后会发现存在 scatternd 算子:

img

修改后不带 ScatterND 的代码与模型结构

import torch
from torch import nn
​
class DummyModel(nn.Module):
    def __init__(self):
        super().__init__()
​
    def forward(self, x):
        tmp1 = x[:, :2]
        tmp2 = x[:, 2:]

img

查看 onnx,scatternd 算子不存在,已被替换。

场景 2:

修改前 onnx 中带有 ScatterND 算子的代码示例

###### 修改前onnx中带有ScatterND算子的代码 ###### 
block_warp_offset = torch.clone(
    offset[
        :,
        pad_u : self.height - pad_b,
        pad_l : self.width - pad_r,
        :,
    ]
)
block_warp_offset[:, :, :, 0] += pad_l

修改后 onnx 中不带 ScatterND 算子的代码示例

###### 修改后onnx中不带ScatterND算子的代码 ###### 
block_warp_offset = torch.clone(
    offset[
        :,
        pad_u : self.height - pad_b,
        pad_l : self.width - pad_r,
        :,
    ]
)
a = block_warp_offset[:, :, :, :1] + pad_l

场景 3:

如下代码会引入 scatterND

# intri_mat shape为(1,4,4,4), intrinsic shape为(1,4,3,3)
intri_mat = torch.eye(4).unsqueeze(0).repeat(1, 4, 1, 1)
intri_mat[:, :, :3, :3] = intrinsic

修改与验证代码如下:

import torch
from torch import nn
​
class DummyModel(nn.Module):
    def __init__(self):
        super().__init__()
​
    def forward(self, intri_mat,intrinsic):
        ## ============方案1 =============
        # tmp1 = intri_mat[:, :, :3, :3]

方案 1 与方案 2 思想一样

img

场景 4:

原包含 scatternd 算子的代码:

key_points = self.scale_mul.mul(scale, self.exp.exp(anchor[1][..., None, 0:3])
rotation_mat[:, :, 0, 0] = anchor[2][:, :, 1] # cos
rotation_mat[:, :, 0, 1] = -anchor[2][:, :, 0] # sin
rotation_mat[:, :, 1, 0] = anchor[2][:, :, 0]
rotation mat[:. :, 1, 1] = anchor[2][:, :,1]

修改之后:

key_points = self.scale_mul.mul(scale, self.exp.exp(anchor[1][..., None, 0:3]))
temp_cos = anchor[2][:, :, 1]
temp_sin = anchor[2][:, :, 0]
temp_zeros = self.rotation_quant(temp_cos.new_zeros([bs, num_anchor]))
temp_ones = self.rotation_quant(temp_cos.new_ones([bs, num_anchor]))
temp1 = self.stack1.stack([temp_cos, -temp_sin, temp_zeros], dim=-1)
temp2 = self.stack2.stack([temp_sin, temp_cos, temp_zeros], dim=-1)
temp3 = self.stack3.stack([temp_zeros, temp_zeros, temp_ones], dim=-1)
rotation_mat = self.stack4.stack([temp1, temp2, temp3], dim=-2)

场景 5:

Swin Transformer 中为滑动注意力窗口计算对应的掩码值,不同区域做标识符区分

原代码为:

# calculate attention mask for SW-MSA
# +-----------+-----------+-----------+
# | Region 0  | Region 1  | Region 2  |
# +-----------+-----------+-----------+
# | Region 3  | Region 4  | Region 5  |
# +-----------+-----------+-----------+
# | Region 6  | Region 7  | Region 8  |
# +-----------+-----------+-----------+
img_mask = torch.zeros((1, H_pad, W_pad, 1), device=query.device)
h_slices = (

修改之后:

主要思路,把对原 tensor 划区域的赋值方式修改为划区域的拼接,先对 w 维度进行拼接,再对 h 维度拼接

cnt = 0
img_mask_patches = []
for h in [
    H_pad - self.window_size,
    self.window_size - self.shift_size,
    self.shift_size,
]:
    img_mask_patches.append([])
    for w in [
        W_pad - self.window_size,

1.2 Bool 赋值和 Mask 替换

对于 PNC 以及静态目标检测模型,模型中较多逻辑判断涉及到 bool 数据类型的赋值和 mask 操作,这一类操作可以考虑替换为 torch.where 算子,可以消除潜在的 cpu 算子并提升模型性能,例如

tfl_mask[valid == 0] = float("-65504")

该操作在 E/M 会引入 cast 和 equal 算子 cpu,如下

img

可以修改为:

x = torch.where(valid_mask == 0, torch.tensor(float("-65504"), device=x.device), x)

此外,此处 -65504 的极大值会影响模型中该算子输入的数据分布,影响量化参数统计从而影响量化精度,相同场景还有 attn 结构中对 attn\_mask 的填充:attn = torch.where(attn\_mask, float("-inf"), attn)。因此考虑到量化精度的话,这里建议进一步将填充值换为量化友好的数值:

x = torch.where(valid_mask == 0, torch.tensor(-100, device=x.device), x)

1.3 Nonzero 等效替换

目前 征程 6 芯片不支持 BPU Nonezero 算子,需要对其做替换使算子跑在 BPU 中:

# 修改前
key_mask=agent_mask.clone()
key_mask = key_mask.reshape(-1, self.T)
index=torch.nonzero(key_mask[:, -1]
key_mask[index,:] = 0 
# 修改后
key_mask=agent_mask.clone()
key_mask = key_mask.reshape(-1, self.T)
mask_filter = ~key_mask[:, -1]
key_mask = mask.logical_and(mask_filter.unsqueeze(1))

1.4 Enisum 等效替换

目前 征程 6 芯片不支持 torch.einsum 算子,可以使用以下两种方式替换:

import torch
A = torch.randn(2, 3, 4, 5, 6) # 形状为 (a=2, b=3, c=4, i=5, j=6)
B = torch.randn(6, 7, 2, 3) # 形状为 (j=6, k=7, a=2, b=3)
# 使用einsum进行操作
result = torch.einsum('abcij, jkab -> abcik', A, B)
# 直接等价实现
# 扩展张量A和B以对齐维度
A = A.unsqueeze(-1) # 扩展为 (a, b, c, i, j, 1)
B = B.unsqueeze(0).unsqueeze(-1).permute(3, 4, 5, 0, 1, 2) # 扩展为 (a, b, 1, 1, j, k)
# 逐元素相乘并对j维度进行求和
import torch
A = torch.randn(2, 3, 4, 5, 6) # 形状为 (a=2, b=3, c=4, i=5, j=6)
B = torch.randn(6, 7, 2, 3, 4) # 形状为 (j=6, k=7, a=2, b=3, c=4)
# 使用einsum进行操作
result = torch.einsum('abcij, jkabc -> abcik', A, B)
# 替代实现
B = B.permute(2, 3, 4, 0, 1) # tanspose为 (a, b, c, j, k)
result_alt = torch.matmul(A, B)
# 结果比较
compare = torch.allclose(result, result_alt, rtol=0.00001, atol=0.00001)

2.算子优化

算子优化分为执行效率的优化和精度的优化。在部署时可能出现算子引入的其他开销,或者算子的执行效率支持的不够好的情况,同时在部署时我们还需要考虑算子的量化精度友好性。本章节将分别针对算子的效率优化和精度优化,给出部署建议和优化方案,帮助模型更快、更好的运行。

2.1 效率优化

2.1.1 Topk 算子

征程 6E/M 在工具链 OE3.2.0 已支持 topk 算子在 SPU 上运行(征程 6B 在 OE3.5.0 版本支持),在 convert 时配置 enable\_spu=True 后算子将会被指定在 SPU 上运行。若 topk 算子后接的 gather、index 算子出现 CPU 的 cast 算子时,建议将 OE 版本升级到 OE-3.5.0(或者将 hbdk 升级到 4.5.5 及以上版本)。

2.1.2 Argmax 后 cast 消除

pytorch 的 argmax 输出的 idx 为 int64 类型,若不做改动会导致引入 CPU 算子,可以将 idx 的类型转为 int8/int16(视数值范围而定避免溢出)避免引入的开销,参考下图:

img

2.1.3 多个 eltwise 操作效率提升

当多个大尺寸的 op 做 add 时,若一次性 add 可能会引入带宽问题。若存在带宽问题,即 load&store 的时间大于计算时间,建议拆为逐个 add 相加,

使用示例

以下提供两个常见的对多个 eltwise 计算的使用示例,方式 1 为多次相加;方式 2 为一次相加。

方式 1:

homo_feats = []
        for i in range(12):
            homo_feat = self.grid_sample(
                feat,
                fpoints[i * B : (i + 1) * B],
            )

            homo_dfeat = self.dgrid_sample(
                dfeat,
                dpoints[i * B : (i + 1) * B],

方式 2:

for i in range(12):
            homo_feat = self.grid_sample(
                feat,
                fpoints[i * B : (i + 1) * B],
            )

            homo_dfeat = self.dgrid_sample(
                dfeat,
                dpoints[i * B : (i + 1) * B],
            )
性能表现

以如下输入大小来测试性能差异:

input = {
    "feat": torch.randn(size=(1, 80, 238, 60)).to(torch.device("cuda:0")),
    "dfeat": torch.randn(size=(1, 1, 1260, 2040)).to(torch.device("cuda:0")),
    "fpoints": torch.randn(size=(12, 64, 256, 2)).to(torch.device("cuda:0")),
    "dpoints": torch.randn(size=(12, 64, 256, 2)).to(torch.device("cuda:0")),
    }

Temporal Statistics:

  • 方式 1:latency 为 3.267 ms

img

  • 方式 2: latency 为 2.321 ms

img

2.1.4 LayerNorm 优化

https://cloud.tencent.com/developer/article/2509912

论文:https://arxiv.org/abs/2503.10622

Dynamic Tanh(DyT)是由何恺明、Yarnn LeCun 等研究者提出的新结构,用于替代 Transformer 中的归一化层(如 LayerNorm),原理简单,在于归一化层的 input-output mapping 曲线近似 tanh 函数,可以直接使用 tanh 函数来拟合线性归一化层的效果。其设计简单高效,仅需 9 行代码即可实现,展现出优于或持平传统归一化层的性能,不仅部署性能明显优于 ln,训练速度也会有明显提升:

class DyT(nn.Module):
    def __init__(self, num_features, alpha_init=0.5):
        super().__init__()
        self.alpha = nn.Parameter(torch.ones(1)*alpha_init)
        self.weight = nn.Parameter(torch.ones(num_features))
        self.bias = nn.Parameter(torch.zeros(num_features))
    def forward(self, x):
        x = torch.tanh(self.alpha * x)
        return x * self.weight + self.bias

对于 transformer 模型的 征程 6 部署,替换为 dyt 也是一个很高效的选择,layernorm 会被拆分为 8 个算子,而 dyt 只有 4 个算子,且避免了 reducemean 的计算(相对来说不是那么高效,且量化不友好),部署性能以及量化友好度都有提升。

img

img

2.1.5 传统 Attention 优化

论文:https://arxiv.org/pdf/2206.08898

SimA 针对传统 Transformer Self-Attention 存在的主要问题,例如长序列任务的计算复杂度高;softmax 指数运算导致的梯度爆炸或消失等,完全移除 Softmax,采用线性相似度计算降低计算复杂度,同时保持模型近似表达能力,在主流的 ViT/NLP 模型中取得相当或更好的模型精度同时,有效降低了部署推理的延时,同时减少了训练时间

import torch
import torch.nn as nn
import torch.nn.functional as F


# SimA实现
class SimAttention(nn.Module):
    """ SimA attention block
    """
    def __init__(self, embed_dim, num_heads=8, qkv_bias=False, proj_drop=0.):

值得注意的是,实际使用中,在多层 attention 的 encoder 结构中,如果使用 SimA 做替换优化,往往保留最后一层为传统 softmax attention 做数值修正来保证模型整体精度效果,避免每层线性归一化带来的累计数值误差从而对 encoder 输出产生影响

2.1.6 Norm 优化

从计算效率从高到低排序:batchnorm > dyt > layernorm/instancenorm > groupnorm

但在实际算法场景中例如 transformer 类的模型,替换 batchnorm 后浮点精度可能无法训回来,因此 layernorm 更常用,此外还有 groupnorm 和 instancenorm

对于 group norm 而言,groups=1 就是 layernorm,groups=channels 就是 instancenorm,所以对于 group norm 的实现:

plugin 导出时通过 transpose+reshape 将 gn 转成 ln

img

经实验 layernorm 比 group norm 要快(因为可以避免前后 reshape),但是用户手动将 group norm 替换 layernorm1d,需要手动在前后加 permute(因为 ln 从最后一维开始 norm),替换比较麻烦。使用下面的方式对 channel 维度做 norm,同时避免引入前后的 permute:

from horizon_plugin_pytorch.nn.layer_norm import SplitLayerNorm
SplitLayerNorm(normalized_shape=[c, 1], dim=1)

2.1.7 nn.Embedding 优化

torch.nn.Embedding 要求输入 tensor 为 LongTensor,也就是 int32/int64,对于 E/M 而言会引入 bpu 不支持的 cast 算子从而跑在 cpu 上影响性能,常规的做法是。to(torch.int16).to(torch.int64),但是只对初始权重的模型有效(scale=1)可以融合。但对于真实权重的模型而言,会引入 dequantize+cast:

img

例如下面结构,让 embedding 的一路维持定点类型可优化

data = {
    # 定点输入
    "sdnavi_link_tbt_info": torch.ones((2, 20, 8, 7), dtype=torch.int16),
    # 浮点输入
    "sdnavi_link_info": torch.ones((2, 20, 8, 256), dtype=torch.float32),
}

class Encoder(nn.Module):
    def __init__(self,
                 ...

对定点数 tensor 的 quant 需要给 scale=1 的 fix\_scale 配置,参考如下

module_name_qconfig = {
    "quant": QConfig(
        output=FakeQuantize.with_args(
            observer=FixedScaleObserver,
            dtype=qint8,
            scale=1,
        )
    ),
}

优化后可实现 BPU 全一段。

2.2 精度优化

2.2.1 Inverse\_sigmoid 部署方案

Inverse sigmoid 容易出现 bc 导出掉点问题,若遇到此问题:

方式一:将 segmentlut 的参数从"curvature"改为"evenly"。

img

方式二:算法上去除 Inverse sigmoid 算子,对 sigmoid 的输入做 clamp(需重训,此方案需要验证对浮点的影响。)

img

注意:此示例中为提高 torch qat 精度,将 +reference sigmoid 放在了 cpu,若 torch qat 并不存在精度问题可以放在 bpu 中。

11.5219 为 inverse\_sigmoid 的输出上限

2.2.2 Gridsample 拆分

由于 BPU 采用定点数值计算,grid\_sample 算子在处理较大的 W 维度时,受限于硬件位宽精度,量化后的数值无法精确表示原始网格坐标,导致 nearest (最近邻)和 bilinear (双线性插值)两种采样方式均引入一定的精度误差。

示例:

class OriGridSample(nn.Module):
    def __init__(self):
        super(OriGridSample, self).__init__()
        self.unitconv = nn.Conv2d(1, 1, (1, 1))
        nn.init.constant_(self.unitconv.weight, 1)
        nn.init.constant_(self.unitconv.bias, 0)
        self.gridsample = hnn.GridSample(
            mode='bilinear',
            padding_mode='zeros',
            align_corners=True,

拆分后:

class SplitGridSample(nn.Module):
    def __init__(self):
        super(SplitGridSample, self).__init__()
        self.unitconv = nn.Conv2d(1, 1, (1, 1))
        nn.init.constant_(self.unitconv.weight, 1)
        nn.init.constant_(self.unitconv.bias, 0)
        self.gridsample1 = hnn.GridSample(
            mode='bilinear',
            padding_mode='zeros',
            align_corners=True,

2.2.3 Sin/Cos 算子去周期

  1. export 时如果发现敏感度排在前面的是 sin/cos 算子,且输入范围较大(超出-pi~pi 一个周期),可以将 sin/cos 替换为 plugin 的自定义算子,并配置 single\_period=True,注意需要重新做量化
import horizon_plugin_pytorch.nn as hnn
class modelnet(nn.module):
    def __init__(self,):
        ...
        self.sin=hnn.Sin(single_period=True)
        self.cos=hnn.Cos(single_period=True)
  1. 也可以自行处理 sin/cos 输入,按照周期性将输入处理到[-pi, pi)之间,注意需要重新做量化
x = x - 2 * torch.floor(x * ( 0.5 / torch.pi) + 0.5) * torch.pi

2.2.4 Conv/Linear weight 高低位拆分

该方案为保障 conv 的高精度计算,对 weight 对高低位的拆分。在用户不重训浮点的情况下,量化训练前需要对用户的浮点 ckpt 部分 linear weight 进行高低位拆分:

方式 1:通过修改 plugin 源码方式,需要将红框后面的减法去掉

img

方式 2:对 model 做拆分:

#=============== 用户原 model =============
self.enc_bbox_head = MLP(hd, hd, 4, num_layers=3)
class MLP(nn.Module):
    """Very simple multi-layer perceptron (also called FFN)"""

    def __init__(self, input_dim, hidden_dim, output_dim, num_layers):
        super().__init__()
        self.num_layers = num_layers
        h = [hidden_dim] * (num_layers - 1)
        self.layers = nn.ModuleList(

Ckpt weight 拆分:

def update_state_dict_task(state_dict, input_sequence_length=None):
    state_dict_new = copy.deepcopy(state_dict)
    state_dict_new["sparse_dynamic_vehicle_head.head.sparse_dynamic_head.transformer.decoder.norm1_1.weight"] = copy.deepcopy(state_dict["sparse_dynamic_vehicle_head.head.sparse_dynamic_head.transformer.decoder.norm1.weight"])
    state_dict_new["sparse_dynamic_vehicle_head.head.sparse_dynamic_head.transformer.decoder.norm1_1.bias"] = copy.deepcopy(state_dict["sparse_dynamic_vehicle_head.head.sparse_dynamic_head.transformer.decoder.norm1.bias"])
    
    for k, v in state_dict.items():
​
        if "sparse_tl2d_head." in k:
            params = copy.deepcopy(v)
            new_key = k.replace("sparse_tl2d_head", "traffic_light_head")

2.2.5 Matmul 高低位拆分

OE 3.5.0 已经支持 matmul 双 int16 的量化,如需要双 int16 输入则配置两个输入为 int16 量化即可。若使用时存在 CPU 的 bitshift,可以开启 VPU 使其运行到 VPU 中,若不需要 VPU 或双 int16 存在性能问题时则需要用户在前端手动的对矩阵做拆分,用双 int8 模拟 int15,达到高精度的效果。

拆分思路:A(B+C)=AB+A*C,B 为原 scale 能表示的 int8 的最大部分,C 为剩余部分。

self.mod = MAX(T_mat) / 128 # MAX-max absolute value
T_mat_high = torch.trunc(torch.div(T_mat, self.mod)) * self.mod
T_mat_low = torch.fmod(T_mat, self.mod)
T_mat_high = self.quant_high(T_mat_high) # int8, fixed-scale with scale==self.mod
T_mat_low = self.quant_low(T_mat_low) # int8, no need fixed-scale
sample_points_pv = torch.matmul(sample_points, T_mat_high) + torch.matmul(sample_points, T_mat_low)
该方案 matmul 为 int15 计算,工具为 int16。实际使用时可根据性能和精度做平衡。

也可以通过修改 plugin 源码方式自动做拆分,需要将红框后面的减法去掉:

过去两年,行业几乎都盯着同一张榜单追分、比排名、算差距;但从今天起,这套玩法可能要告一段落了。

 

今天,OpenAI 正式宣布 SWE-bench Verified“退役”。几小时前,OpenAI 的开发者账号发推明确表示:SWE-bench Verified 将逐步退出舞台,已不再适合作为前沿编程模型的主要对标基准;官方更建议大家转向 SWE-bench Pro。

 

SWE-bench Verified 曾经几乎就是代码评测的“北极星”。OpenAI、Anthropic、Google,包括不少国内开源权重模型,都在同一张榜单上咬得很紧,领先优势往往只差零点几。

 

但 OpenAI 在最新分析中指出:剩余未解决任务本身存在多重问题,已经不值得继续追求、更不值得继续公布 Verified 成绩。其中最严重的一点是数据污染——几乎所有前沿模型(包括 OpenAI 自己的模型)如今都表现出“复现 SWE-bench 评估数据与解法”的能力,有时甚至只凭任务 ID 就能做到:

 

另一个原因则更“直白”:测试设计本身就不够可靠。OpenAI 认为,至少有 60% 的未解决问题,从题面描述出发就应该是无法被正确解决的——如果某个模型“解决了它们”,更可能意味着绕过了评测机制(也就是在“刷题/作弊”)。他们举的例子之一,是 SWE-bench 中针对 pylint 问题 #4551 的测试用例。

 

今天,Latent.Space 邀请了 OpenAI 的 Frontier Evals 团队一起解读了这次榜单更迭的原因,并对比了两个新旧不同测评榜的差异。

 

SWE-bench Verified 题目规模偏小、任务周期过短,90% 的问题对资深工程师来说一小时内就能完成。而 SWE-bench Pro(SuperBench Pro)的题目更大、更难、任务时间被明确拉长到“数小时甚至更久”,覆盖的仓库、语言和问题类型也明显更丰富;更重要的是,目前它还没有被刷爆,污染迹象远低于 Verified,至少在现阶段仍然能区分真实能力差异。

 

并且 Pro 也不是终点。任何公开榜单,最终都会被追平、被记住、被“学会”,然后再次失效。OpenAI 认为,关键不在于“换哪个榜”,而在于下一代代码评测到底该测什么——他们更希望看到真实世界使用层面的指标:AI 在现实中到底被用了多少、在多大程度上替代了人类工作、又在多大程度上是在增强人类、加速人类。

 

这个事件的意义,正如一位网友所说:“排行榜的噱头已经过时了!如今的优势在于溯源性、对抗性强的评估以及在生产环境中值得信赖的 Agent”。

 

这期嘉宾是 OpenAI 的 Olivia Watkins(Frontier Evals 团队)和 Mia Glaese(OpenAI 研究副总裁)。我们整理了这期播客的核心内容,供你快速了解这场“退役 + 换标”背后的关键逻辑。

 

SWE-bench Verified 的终结(2024–2026)

 

主持人:你们其实亲眼见证了代码评测基准这些年来的演变。我记得大概是在 2024 年中后期,你们第一次发布了关于 Verified 的工作。从那之后事情变化很大。今天你们发布的这篇博客,核心论点是什么?你们想对外传达的主要信息是什么?

 

Olivia:我们的核心观点是:SWE Bench Verified 一直是这个领域用来衡量代码能力进展的“北极星”级基准之一。但最近我们发现,这个基准上的进展基本停滞了。

 

我们意识到,这是因为这个评测已经饱和了,同时也高度被污染了。所以在现阶段,它已经不能很好地衡量代码能力的真实提升了。

 

我们认为,整个领域应该逐步从它转向其他基准,比如 SuperBench Pro。

 

主持人:太有意思了。我经常开玩笑说,好像所有实验室都有个群聊,大家轮流把分数往上挪 0.1%,然后就开始说:“行吧,你现在是最强的代码模型了。”但到现在这个阶段,这种“领先”其实已经没什么说服力了。

 

Olivia:是的,确实如此。

 

主持人:那我们不妨先回到最初。你们当初为 CS Verified 做的原始工作,其实投入非常巨大,但我觉得外界至今都没有真正理解这一点。你们当时到底做了什么?以及后来你们发现了哪些问题?

 

Olivia:SWE Bench Verified 本质上是对一个学术基准的“清洗”和重构。原始基准来自普林斯顿的一个研究团队,叫 SWE Bench。它的基本设定是:给一个 agent 一个真实世界的代码库,以及一个来自 GitHub issue 的任务,让它去解决问题,然后通过是否通过测试来评分。

 

在当时,这个基准迅速流行起来,因为整个领域几乎没有真正贴近现实世界的软件工程评测。

但当 OpenAI 把这个基准纳入我们 preparedness framework 中的评测体系后,我们开始发现:很多 agent 的失败,并不是因为模型“太笨”,而是因为问题本身的设置就有问题。

 

于是 OpenAI 启动了一次规模非常大的人工数据项目,雇佣了将近一百名真实世界的软件工程师,逐题审查这些任务:任务是否描述清晰?测试是否公平?最终我们构建了一个经过严格筛选的、约 500 个任务组成的高质量子集。

 

Mia:真的很难夸大这个过程的投入规模。很多经验丰富的软件工程师,对同一道题进行了多轮、逐条的审查。基本上是三位不同的专家独立做出判断。

 

主持人:你们完全可以不这么做的,这相当于把成本直接乘了三倍。

 

Mia:但我们必须这么做。因为这类任务真的非常复杂。你不仅要看问题描述和补丁,还要把它放到整个代码库上下文中去理解,无论是人类还是模型,都是在这个上下文中完成任务的。所以三轮审查是必要的。甚至事后看,也许我们还应该做更多,但即便如此,那已经是非常巨大的投入了。

 

主持人:我注意到你们后来几乎开启了一个“Verified 风潮”,我前阵子还看到 Quinn 做了 HLE Verified。现在什么都要 verified 了,这本身是好事。回到重点。这 500 道题,大致结构是:问题描述、diff、golden tests,再加上一些回归测试。由于评测是完全公开的,污染几乎是不可避免的。你们确实放了一些 canary(金丝雀),但问题毕竟来自开源仓库。

 

Mia:是的。这和我们通常发布评测不太一样。一般我们会加入 canary strings,来检测是否被训练数据“记住”。但当你使用的是开源 GitHub 仓库的数据时,是不可能插入 canary 的。

 

而且很多题目来自非常流行的项目,比如 Django,你在 GitHub 各处都会看到这些代码片段反复出现。

 

主持人:你之前在录制前跟我说,你们甚至在 G5.2 的 chain-of-thought 里也发现了类似情况。

 

Olivia:对。有一个例子是,任务要求 agent 实现一个功能,但问题描述中并没有提到某个参数,而测试却会检查这个参数是否存在。但在 GPT-5.2 的推理过程中,我们看到模型直接推理说:“我记得在这个仓库的后续版本中实现过这个参数,我或许应该加上。”

 

这类测试,如果没有这种“污染知识”,几乎是不可能通过的。

 

Mia:这其实触发了一次更大规模的调查,不只是针对自家模型,也包括市面上的其他前沿模型,去理解整个行业的污染程度。

 

主持人:那你们还发现了什么?

 

Olivia:这部分主要是团队里其他同事完成的。我们做了一项分析:这些测试本身是否公平?方法是先挑出那些模型始终无法稳定解决的问题,然后再让大量人类工程师做一次深度审查。

 

主持人:他们是直接分析问题,还是看了模型的输出再判断?

 

Olivia&Mia:不完全一样。这不是原始 Verified 的那套流程,而是一次更深入的调查:这些“谁都解不了”的问题,是因为模型不够聪明,还是因为问题本身就有根本性缺陷?

 

结果非常明确。在被深入调查的问题中,超过一半都存在问题。最常见的情况是:测试过于狭窄。测试期待某个具体实现细节,但问题描述里根本没有要求这一点。比如,有的任务要求你实现一个功能,但测试要求你必须用某个特定的参数名或函数名。如果你用了另一个完全合理的命名方式,测试就失败了。

 

还有一类问题是:测试检查了一些问题描述里从未提及的“额外功能”。这意味着,通过测试当然说明你做得很好,但没通过测试,并不代表你的实现不好。只是评测只接受了极其狭窄的一小部分解空间,而忽略了大量同样正确、同样高质量的实现。

 

转向 SWE Bench Pro

 

主持人:某种程度上,这像是你们在 2025、2026 年回到过去,修正自己当年的工作。

 

Olivia&Mia:确实如此。但说实话,在抽象层面发现问题,比起对着一个非常聪明的 agent 的“最佳努力解”来审视,要难得多。

 

而且我想强调的是:在当年发布时,SWE Bench Verified 是一个非常强的基准。它确实教会了我们,也教会了整个行业很多东西。

 

但这几乎是所有成功基准都会经历的生命周期:一开始,模型只能做到 20% 甚至更低的正确率,大家能清楚地看到进步空间;等到性能接近天花板后,0.1% 的提升就变得毫无意义。

 

现在的问题是:在当前这个阶段,这个基准测量的已经不再是我们真正关心的东西——也就是 agent 的代码能力,而更像是在测量模型是否能“猜中”某个特定函数该叫什么名字。

 

主持人:这确实不是我们现在想测的东西。如果让我你们估算一下,现在大多数前沿模型在 Verified 上是不是都已经到了 80% 多?这个基准的“天花板”大概在哪?

 

Olivia&Mia:这个真的很难说。比如在 GPT-5.2 发布之后,我们回头去看,发现它(模型)解出了大概31 个属于那种“如果没有污染,应该非常难解出来”的题目。所以我觉得很有可能:如果完全没有污染的话,那个数字(天花板)其实我们可能早就已经达到了

 

主持人:那我们之后就不再继续汇报 SWE Bench Verified 了,对吧?接下来 SuperBench Pro 会成为新的主要基准,这是 Scale 那边发起的项目。你们怎么对比分析?SuperBench Pro 吸引你们的点是什么?

 

Olivia:第一点很直接:它更难。SWE Bench Verified 里大概90% 的题,估计一个资深软件工程师不到一小时就能完成。它们通常都很清晰、很自包含、规格也写得很完整。

 

但 SuperBench Pro 的题目整体更大、更难,而且评测还有更大的提升空间,因为它还没饱和。

 

主持人:它还有一些按耗时分的类别,比如1 到 4 小时、以及4 小时以上这一类。

 

Olivia&Mia:是的。另外它也更多样:仓库更多、语言更多、题型也更丰富,定性上就能感觉到问题类型更不一样——这些都很棒。

 

从“污染”角度,我们也认为它更好。我们之前衡量 SWE Bench Verified 的污染,用的是一个小工具:我们做了一个“污染审计(contamination auditor)agent”。这个 agent 会拿到任务描述、patch、任务 ID,然后让它去“审问”目标模型——用一组开放式问题,尽量诱导出模型可能藏着的污染线索。

 

在 SWE Bench Verified 上,我们在很多模型里都看到了污染迹象:包括 OpenAI 自家的模型,也包括像 Claude 4.5、Gemini Flash 等。

 

我们看到的现象包括:模型会复述(regurgitate)标准答案,甚至有时会吐出任务 ID之类的东西——这至少说明它对相关仓库(repositories)有明显的熟悉度。

 

比如它直接给出任务 ID,那种就很离谱。

 

但在 Pro(SuperBench Pro)上,我们目前没看到这种情况。污染审计 agent 只找到了一点非常轻微的迹象:可能有少数模型对一两个源仓库“略微熟悉”,但这和 SWE Bench Verified 的情况完全不是一个量级。所以从污染角度看,确实更好。

 

不过我们也应该预期:再往后,总有一天它也会变得不再合适。作为一个领域,我们必须持续向前走,去找更难、也更具代表性的问题,作为我们能力的锚点。

 

好的代码评测应该衡量什么

 

主持人:太好了。那我们就顺着这个聊。我觉得很多人在用 5.1、5.2、5.3 的时候能明显感觉到“质变”,但这些基准并不总能反映出来——因为它们多数还是一堆分数。 如果是你们理想中的代码基准(或者 agent 编程基准,不管叫什么),你们真正想 benchmark 的能力是什么?

 

Olivia:我觉得其中一个方向是:开放式的设计决策。也就是问题可能有点“没写死”,看模型能不能做出合理的设计选择。

 

主持人:那这种评测怎么写 prompt 才算合理?比如“写一个五个九可用的 B2B SaaS,别犯错”——这当然是个梗。但真正可用的开放式题目会是什么样?

 

Olivia:当然。举个例子:你让它想办法把代码库某一部分提速。但提速的路径可能不止一种。

 

主持人:是,不过性能也有专门的基准吧?你们是不是有 efficiency?还是那是 Harris 团队做的?

 

Mia:我觉得人们在和软件工程 agent 协作时,会在意非常非常多的东西。SWE Bench Verified 显然衡量了一些重要能力——比如给你一个 GitHub issue 的描述,你能不能产出一个 patch,把问题解决到令人满意的程度。

 

它确实测到了模型的一个真实能力,只是由于基准本身的各种问题,当我们已经到 80% 的时候,我们不太信任再往上那一点点提升是否真代表能力提升。

 

但现在作为一个领域,我们正在超越“我的 coding agent 能不能帮我解决一个小的 GitHub issue”这种层级。我们开始关心的是更长期的任务:不是 15 分钟搞定,而是可能要花一小时,有时甚至是几天,以及更久。

 

再进一步,除了“它能解决什么任务”,还有一些更难量化的东西:比如它有没有设计品味(design taste)?它解决问题的方式是不是符合我团队一贯的风格?再比如:代码写得“好不好看”?是否清晰、干净?未来是否可维护

 

这些也许更“不那么可触摸”、更难评测,但对真正和 coding agent 一起工作的开发者来说,意义非常大。

 

主持人:对。这些品质显然已经不是“低垂的果子”了——我们几乎不知道怎么评。我觉得这里有两条路:一条是非常重人力、重金钱:雇一堆承包商来人工标注;另一条是用 LLM 做 proxy,然后想办法把模型对齐到一个“足够可靠的代理评审”上。你们会选哪条?还是两条都要?

 

Olivia:GDP Eval 是一个评测,由人类数据团队和 Frontier Evals 团队合作完成,目标是衡量 agent 能否完成各种真实世界的白领工作。这个评测的评分非常难,因为需要大量领域知识:在不同情境下,你到底要“看”什么,什么才算好。

 

主持人:对。覆盖了大概 15、16 种白领职业——这些职业占 GDP 的很大比例,属于高层次专业工作;同时里面还有很多更细粒度的子任务。我个人非常喜欢这个评测——这几乎就是“AGI 的评测”了。

 

Olivia:也正因为它很难、需要很多领域知识,人类数据团队雇了很多来自这些职业的人,深度参与任务设计、标准答案(gold solutions)的制作、rubric(评分规则)的制定等等,才能把它做出来。

 

主持人:所以基本上,如果把 GDP Eval 这种“通才型”的方法,用同样的路径搬到代码领域,就能得到一条大致的路线图。

 

Mia:我觉得这是个很有意思的方案。你指出了一个关键问题:到底“现实感”应该多强?我们希望 coding agents 写出来的代码,是我们认为“好”的代码。让人类来评审,确实是确保这一点的好方法。但它也会更慢、更复杂。

 

这也是为什么 SWE Bench Verified 当年会那么流行,以及为什么类似基准会这么流行:它很简单——甚至可以更简单。只要验证“测试全过”,基本就是一键跑出来的:对还是不对,然后你就能汇总统计,快速对比。

 

但它不会告诉你:它是不是把问题“解决得漂亮”?代码是不是很丑?如果你是那个开源项目的 maintainer,你会不会愿意 merge 这个 PR?它回答不了这些。

 

尽管如此,“易于跨行业比较”和“能快速运行、无需人工参与”这两点,仍然非常有价值。

 

从打分到计价:能否把能力换算成成本?

 

主持人:太棒了。你们团队也做过其他类型的 eval,比如我记得有 RL paper bench,还有一些更像递归式自我改进(recursive self-improvement)的评测。这些东西应该在主流的 coding eval 里占多大比重?它们和“普通代码评测”有没有可能融合?

 

Olivia:抱歉,你的问题是:我们是否也应该为“自我改进能力”构建评测?还是你在问,现在的代码评测有没有覆盖到这一点?

 

主持人:我想表达的是:那些可能是我们手上最先进的一类 eval,但它们并没有进入“常规路径”。就好像一边是“正常 coding 的 eval”,另一边是“机器学习研究的 eval”,完全是两套东西,我猜你明白我的意思。这可能主要是出于 safety 的考虑,但从实用角度来说,大家也很想知道模型是不是特别擅长做“AI 代码”(比如研究代码)这件事。

 

Olivia:对。我猜很多基准到现在还没太聚焦“AI 代码”,主要原因是数据集很难收集。最先进的 AI 代码库很多都是私有的。如果我们为那类东西做 eval,很可能也没法公开发布。这样一来,外部领域就很难做同类对比,也很难衡量“这是不是一个现实的研究编码工作流”。

 

但我确实认为,公开地衡量这些技能,对整个领域是有益的,只是更难把它做得足够真实。

 

主持人: 还有一个趋势:很多人尝试不再用 0 到 100 的百分制,而是用 Dollars 来重新计价。你有 freelancer 之类的东西,也有人做 vending bench 之类——你们对这些有什么看法?它们有用吗?还是你们仍然更想要传统的学术基准?

 

Mia:我觉得从某种意义上,这些是在用不同的尺度衡量同一件事。

 

如果你说“它能产生多少价值(多少钱)”,这和说“这个问题让人类来做需要两小时”其实差不多。通常它们是高度相关的:一个人类解决这个问题要花多少时间,往往就决定了我们会给解决方案赋予多大的价值。

 

所以我认为一个很重要的维度是:我们能够把 agent 托付给多复杂、运行时间多长的任务?这些任务到底能有多“长”、多“重”、多“复杂”?这件事本身非常关键。

 

所以我觉得这是一个很重要的点。但无论是用金钱价值时间,还是复杂度来衡量,本质上它们都是在试图捕捉同一件事。

 

主持人:是的。它们本质上都是某种“代理指标(proxy)”,用来衡量我们想要测量的、不断增长的能力。我觉得这是件好事。我觉得在这个领域里,另一个比较重要的参与者是 METR(原文音近 meter),他们做了所谓的 long horizon / long autonomy 测试。顺便说一句,恭喜你们,已经把那条曲线彻底打穿了。你们怎么看这类方法?显然你们在这些评测上表现非常好,这看起来当然很好,但我不知道这种思路是不是你们未来在构建 eval 时也会考虑吸收的方向。我指的是“长期自治(long autonomy)”那类评测。

 

Mia:是的,我们当然知道那套评测,而且我们也和 METR 在这些评测上有合作,所以我们是非常认可它们的。

 

他们主要是用时间作为度量,而不是用金钱。

 

我想这正好呼应你刚才的问题。我认为“复杂度”——不管我们用什么方式去量化——对于理解我们正在走向什么样的能力水平、什么样的市场阶段,都是非常重要的。

 

主持人:明白了。复杂度是一个更抽象的概念,然后它可以投射到时间、story points,或者金钱这些更具体的指标上。

 

评测的终点:AI 替代了多少人类工作

 

主持人:最后一个问题,关于整体的 Preparedness Framework。我注意到现在很多人都会提到这个框架,但我觉得对大多数人来说,它并没有被很好地解释清楚。你们其实有一个做得很好的公开网站,上面讲的是“测试、告知、教育”之类的内容。我感觉你们在这方面投入了很多工作。你愿不愿意简单讲讲 Preparedness Framework 是如何应用的?

 

Olivia:Preparedness Framework 是一个公开的框架,用来说明我们如何追踪前沿风险(frontier risk)。这些风险通常来自一些双用途能力:它们既可以被用于好的事情,也可能被用于坏的事情。我们希望至少能持续监测潜在的负面使用,确保我们作为一家公司、以及整个社会,都能为可能出现的风险做好准备。

 

目前我们主要追踪三大类能力风险:第一类是生物安全风险(biorisk);第二类是网络安全(cybersecurity);第三类是研究自动化与模型自治(research automation & model autonomy)。这第三类正是和代码评测联系最紧密的地方。代码并不等同于研究自动化,但它是其中一个非常关键的组成部分

 

所以我们最初创建 SWE Bench Verified,就是作为“模型自治”这一工作流中的一部分评测手段。而现在,我们认为必须继续向前,去评估模型是否真的开始自动化完整的研究工作流

 

主持人:非常棒。还有什么你想补充的吗?比如大家应该如何理解 preparedness,以及 eval、人类数据、对齐团队是如何协同工作的?

 

Mia:我觉得我最想说的一点是:我们真的非常珍惜整个社区的参与。我们投入了大量精力去构建这些评测,这也是为什么我们会在这个框架下发布 Verified、分享 GDP Eval 等成果。同时,我们也非常鼓励整个领域去共同构建、分享、复用评测

 

像 SuperBench Pro 这样,如果它是一个更好的评测,那我们就应该用它。

 

我非常希望大家能探索更多方式,去创建并共享 eval,让我们以及整个领域,都能更好地衡量不同能力维度上的进展——包括代码能力,因为理解我们现在到底处在什么位置非常重要。

 

主持人:这里我想直接请你发出一个“号召”:如果大家要为你们做 eval、或者给这个领域贡献 eval,你们最想看到什么?

 

Olivia:我觉得有几类东西会非常有价值。

 

第一,是真正非常非常难的任务——那种需要顶尖工程师花几个月、或者一个团队花几周才能完成的事情。如果这些任务的评分是可靠的,而且有经过领域内多方验证的 rubric(评分标准),那会非常有价值。

 

第二,是端到端产品构建类的基准。随着越来越多人在用 agent “直接出产品”,这类 benchmark 会变得越来越重要。

 

第三点,也许不完全算是 eval,但我认为和我们的整体使命高度相关:我希望看到更多真实世界使用情况的指标——AI 在现实中到底被用了多少?在多大程度上替代了人类工作? 在多大程度上是在增强人类、加速人类?这些真实世界指标,本身非常重要。

 

主持人:是的,“替代”这个词在宣传层面总是很敏感(笑)。不过现实就是:我们会创造新工作来管理旧工作,事情一向如此。就你个人而言,接下来在 Frontier Evals 上最值得期待的是什么?你们每次都交付非常高质量的工作。

 

Olivia:我不太方便具体说接下来会发布什么,但从大的方向上来说,肯定会更多关注真实世界影响——真实使用、真实吞吐、真实效果这类东西。

 

主持人:太棒了。我也非常期待你们在“真实世界影响”这条线上继续推进。

 

参考链接:

https://www.youtube.com/watch?v=0HaUD_olwQU

https://x.com/latentspacepod/status/2026027529039990985

多年没有上架 app 了,想了解一下现在上架环境怎么样,想要上架到 xiaomi ,OPPO ,vivo ,应用宝。一个工具类的 app 。一个个去注册账号去跑流程么?有没有聚合平台可以一键上架多个商店的?还有各位佬上架过程中有没有需要注意的事项,例如权限,合规之类的。欢迎分享

利益相关声明:作者与文中产品有直接的利益相关(开发者、自家产品等)

Matrix 首页推荐 

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。 

文章代表作者个人观点,少数派仅对标题和排版略作修改。


我的主力电脑是一台 iMac,在固定的房间,这限制了我使用电脑的时间和场景。不过随着 CodexClaude Code 等 CLI Coding Agent 的出现,我可以在 iPad 或手机上,通过 SSH 远程连接 Mac,在乘坐地铁等碎片时间远程开发,随时随地为「HEIF & HEVC 转换器」添加新功能。

在通过 SSH 远程开发的过程中,我对自己的开发环境进行了一系列配置和优化,例如:

  • 通过 Bark 将 Claude Code/Codex 的通知推送到 iPhone
  • 解决移动端网络变化/杀后台导致 SSH 断连
  • 修改 SSH 配置文件让 1Password SSH Agent 与远程连接共存

我将自己进行的配置改动记录到这篇文章中,供自己后续维护和回顾,也方便各位读者参考。

将 Coding Agent 的通知发送到手机上

利用碎片时间远程开发,无法像使用电脑那样,盯着屏幕随时观察 CLI Coding Agent 的执行结果,所以我使用下面的 Python 脚本,通过 Bark 将执行结果发送到手机上。

此脚本同时兼容 Claude Code、Codex 和 OpenCode,并对推送内容做了加密处理,Bark 服务器和 Apple 都无法获取推送内容。

相关代码也已上传到 GitHub:

Bark 通知脚本,同时兼容 Codex、Claude Code 和 OpenCode

notify_claude_codex_bark.py

#!/usr/bin/env python3
import base64
import json
import sys
import urllib.parse
import urllib.request

# Dependency: brew install cryptography
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

BARK_BASE = "https://api.day.app/xxx"
ENCRYPTION_KEY = "xxx"
ENCRYPTION_IV = "xxx"

OPENAI_ICON_URL = "https://images.ctfassets.net/j22is2dtoxu1/intercom-img-d177d076c9a5453052925143/49d5d812b0a6fcc20a14faa8c629d9fb/icon-ios-1024_401x.png"
# Claude symbol (CC0) from Wikimedia, publicly accessible without auth.
CLAUDE_ICON_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Claude_AI_symbol.svg/960px-Claude_AI_symbol.svg.png"
# OpenCode icon
OPENCODE_ICON_URL = "https://opencode.ai/apple-touch-icon.png"

def _load_key_iv():
    key_bytes = ENCRYPTION_KEY.encode("utf-8")
    if len(key_bytes) != 32:
        return None, None

    iv_bytes = ENCRYPTION_IV.encode("utf-8")
    if len(iv_bytes) != 12:
        return None, None

    return key_bytes, iv_bytes


def _encrypt_aes_gcm(plaintext: bytes, key: bytes, iv: bytes):
    aesgcm = AESGCM(key)
    encrypted = aesgcm.encrypt(iv, plaintext, None)
    return base64.b64encode(encrypted).decode("ascii")


def _load_payload() -> dict:
    if len(sys.argv) > 1:
        try:
            return json.loads(sys.argv[1])
        except json.JSONDecodeError:
            return {}
    try:
        if not sys.stdin.isatty():
            raw = sys.stdin.read().strip()
            if raw:
                return json.loads(raw)
    except Exception:
        return {}
    return {}


def _detect_source(payload: dict) -> str:
    """Detect the source of the payload: 'claude', 'opencode', or 'codex'."""
    if payload.get("hook_event_name"):
        return "claude"
    if payload.get("session_id") or payload.get("transcript_path"):
        return "claude"
    title = payload.get("title") or ""
    if "Claude" in title:
        return "claude"
    if "OpenCode" in title or "opencode" in title.lower():
        return "opencode"
    event_type = payload.get("event") or payload.get("type") or ""
    if event_type.startswith("session.") or event_type in ("session_completed", "file_edited"):
        return "opencode"
    return "codex"


def main() -> None:
    payload = _load_payload()
    source = _detect_source(payload)

    event_type = (
        payload.get("hook_event_name")
        or payload.get("type")
        or payload.get("event")
    )
    if source == "claude":
        title = payload.get("title") or "Claude Code"
        icon_url = CLAUDE_ICON_URL
    elif source == "opencode":
        title = payload.get("title") or "OpenCode"
        icon_url = OPENCODE_ICON_URL
    else:
        title = payload.get("title") or "Codex"
        icon_url = OPENAI_ICON_URL
    subtitle = event_type
    message = (
        payload.get("last-assistant-message")
        or payload.get("message")
        or payload.get("summary")
    )
    if not message:
        cwd = payload.get("cwd")
        if cwd and event_type:
            message = f"{event_type} in {cwd}"
        elif cwd:
            message = f"Event in {cwd}"
        elif event_type:
            message = f"Event: {event_type}"
        else:
            message = "Event"
    push_payload = {
        "title": title,
        "markdown": message,
        "icon": icon_url,
        "action": "none",
    }
    if subtitle:
        push_payload["subtitle"] = subtitle
    key_bytes, iv_bytes = _load_key_iv()
    if not key_bytes:
        return

    plaintext = json.dumps(push_payload, ensure_ascii=False, separators=(",", ":")).encode(
        "utf-8"
    )
    try:
        ciphertext = _encrypt_aes_gcm(plaintext, key_bytes, iv_bytes)
    except Exception:
        return
    if not ciphertext:
        return

    form = urllib.parse.urlencode({"ciphertext": ciphertext, "iv": ENCRYPTION_IV})
    req = urllib.request.Request(
        BARK_BASE,
        data=form.encode("utf-8"),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=5) as response:
            response.read()
    except Exception:
        # Do not block Codex runs on notification failures.
        return


if __name__ == "__main__":
    main()

Codex 配置文件

.codex/config.toml

notify = ["python3", "/path/to/notify_claude_codex_bark.py"]

Claude Code 配置文件

.claude/settings.json

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "command": "python3 /path/to/notify_claude_codex_bark.py",
            "type": "command"
          }
        ],
        "matcher": ""
      }
    ]
  }
}

OpenCode 配置文件

~/.config/opencode/plugin/notify.ts

import type { Plugin } from "@opencode-ai/plugin";

const NOTIFY_SCRIPT = "/path/to/notify_claude_codex_bark.py";

const plugin: Plugin = async ({ client, $ }) => {
  const notify = (type: string, message: string) => {
    const payload = JSON.stringify({ title: "OpenCode", type, message });
    $`echo ${payload} | python3 ${NOTIFY_SCRIPT}`.quiet().catch(() => {});
  };

  return {
    event: async ({ event }) => {
      if (event.type === "session.idle") {
        const { sessionID } = event.properties;
        const sessions = await client.session.list({ limit: 50 });
        const session = sessions.data?.find((s: { id: string }) => s.id === sessionID);
        if (!session || session.parentID) return;

        notify("session.idle", session.title || "Task completed");
      }

      if (event.type === "permission.asked") {
        const { permission, patterns } = event.properties;
        const detail = patterns.length ? `: ${patterns.join(", ")}` : "";
        notify("permission.asked", `${permission}${detail}`);
      }
    },
  };
};

export default plugin;

备选方案

目前我在 iPhone 和 iPad 上使用的 SSH 终端是 Secure ShellFish(选择这款终端的原因请见下文),这个 App 也支持用类似 Bark 的方式发送推送通知。

选择合适的 SSH 终端

为了在移动设备上获得更好的终端体验,我选择使用 Secure ShellFish

选择 Secure ShellFish 的理由

适配移动设备的使用场景

使用移动设备时,经常遇到网络环境变化导致 SSH 断连的问题。Secure ShellFish 内置了优秀的 tmux 支持,可通过 tmux 保持和自动恢复 Shell 会话。

同时,这款 App 还能在图形化界面上创建、删除、重命名 tmux 会话,或者将 tmux 会话绑定到新的标签页上。

另外,Secure ShellFish 还是实现了可选的后台保活优化,可通过监听地理位置变化、画中画浮窗等,实现 App 长时间在后台运行。

支持丰富的终端特性,适配带有复杂 TUI 的 Coding Agent

Claude Code、OpenCode、Gemini CLI 拥有复杂的 TUI,需要在现代的终端模拟器下,才能更好地工作。

Secure ShellFish 基于 SwiftTerm,支持现代终端特性。同时这款 App 一直在积极更新,例如最近的 2026.4 版本更新,就带来更好的 Claude Code 的兼容性。

Files App 与第三方代码编辑器支持

Secure ShellFish 支持与 iOS 自带的文件应用整合。能够让文件应用,或者任何第三方代码编辑器通过打开 Mac 上的文件。

备选 App

移动端使用 Textastic 代替 Vim 等命令行代码编辑器

虽然有 AI 帮忙编写和修改代码,但是很多时候,我还是需要临时手动修改代码。

在 iPhone 等小屏幕设备上,通过软键盘在 SSH 客户端中,使用 Vim 等文本编辑器编辑代码,体验不是很好。

所以在移动设备上,我使用的代码编辑器是 Textastic,Secure ShellFish 内置了 Textastic 集成,在终端中输入 textastic <文件名>,即可跳转到 Textastic 并打开指定的文件。

1Password SSH Agent 与远程 SSH 会话共存

我在 Mac 本地使用 1Password SSH Agent 管理 SSH 私钥,平时执行 git push 等操作时通过 Touch ID 确认,兼顾了体验和安全性。

但是在 iPhone 和 iPad 上通过 SSH 远程连接 Mac 时,就无法进行 Touch ID 认证了。对于这个问题,我的解决方法是:

  • 移动端:使用支持 Agent Forwarding 的 SSH 客户端(例如 Secure ShellFishBlink),将 iPad 侧的 SSH agent 转发到 Mac;
  • Mac 端:修改 SSH 配置文件,仅在当前会话没有可用 SSH agent 时,才回退使用 1Password 的 IdentityAgent

SSH 配置文件

具体的 SSH 配置文件如下:

.ssh/config

/# ------------------------------------------------------------------------------
# SSH agent 回退策略(1Password 作为 fallback)
#
# 触发条件(任一满足即启用 1Password SSH agent):
#
#   1. SSH_AUTH_SOCK 未设置
#      - 当前会话没有可用的 SSH agent
#
#   2. SSH_AUTH_SOCK 字符串中包含 "com.apple.launchd"
#      - launchd / launchctl 启动的临时 socket
#      - 常见路径示例:
#          /private/tmp/com.apple.launchd.xxxxxx/Listeners
#      - 不稳定、不可复用,明确排除
#
#   3. SSH_AUTH_SOCK 指向的目标不是一个可用的 socket
#      - 断掉的符号链接
#      - 普通文件
#      - agent 已退出
#      - 注意:test -S 会自动顺着符号链接判断最终目标
#
# 不触发条件(保持现有 agent,不覆盖):
#
#   - SSH_AUTH_SOCK 已设置
#   - 路径不包含 com.apple.launchd
#   - 且最终目标是一个有效的 socket
#     (例如:agent 转发、tmux 继承、本地 GUI agent 等)
#
# 实现要点:
#   - Match exec 的命令必须是“单行字符串”
#   - 通过 shell 返回码控制匹配:
#       * 返回 0  → Match 生效 → 使用 1Password agent
#       * 返回非 0 → Match 不生效 → 保持原 agent
#   - 仅使用 POSIX 工具(sh / test / grep)
# ------------------------------------------------------------------------------

Match host * exec "sh -lc 'test -z \"$SSH_AUTH_SOCK\" || echo \"$SSH_AUTH_SOCK\" | grep -q com.apple.launchd || ! test -S \"$SSH_AUTH_SOCK\"'"
    IdentityAgent ~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock

备选方案

如果不使用 1Password,还有如下两种方法将 SSH 私钥加载到 SSH Agent,不过在首次登录时,都需要额外的输入密码等确认操作。

方案一:将 SSH 私钥密码保存到 macOS Keychain

参考如下链接。但是 macOS Keychain 不会随着 SSH 登录而自动解锁,需要额外敲命令解锁 Keychain。

方案二:zsh 插件

可使用如下 Oh My Zsh 插件。但是首次登录时,也需要输入 SSH 私钥密码:

SSH Agent Forwarding 与 tmux 共存

Secure ShellFish 自带完善的 tmux 支持,能够解决断线重连后 Shell 会话丢失的问题。

但是 tmux 与 SSH agent forwarding 共存,会出现这样的问题:

  • 每次 SSH 连接建立时,转发的 SSH_AUTH_SOCK 路径可能不同;
  • tmux 的会话往往长期存在,如果 tmux 内仍指向旧的 socket,就会出现「重连后 tmux 里 git push 不工作」的现象。

对于这个问题,我根据 StackOverflow 上讨论,采用了如下解决方法:

  • SSH_AUTH_SOCK 固化为稳定路径,并让 tmux 始终使用该路径

SSH 与 tmux 配置文件

具体的配置文件如下:

~/.ssh/rc

#!/bin/zsh

# Source - https://stackoverflow.com/a
# Posted by pymkin, modified by community. See post 'Timeline' for change history
# Retrieved 2025-12-24, License - CC BY-SA 4.0
# Fix SSH auth socket location so agent forwarding works with tmux.
if test "$SSH_AUTH_SOCK" ; then
  ln -sf $SSH_AUTH_SOCK ~/.ssh/ssh_auth_sock
fi

~/.tmux.conf

# Source - https://stackoverflow.com/a
# Posted by pymkin, modified by community. See post 'Timeline' for change history
# Retrieved 2025-12-24, License - CC BY-SA 4.0
# fix ssh agent when tmux is detached
setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock


# 不要让 client attach 覆盖 SSH_AUTH_SOCK
set -g update-environment "DISPLAY KRB5CCNAME MSYSTEM SSH_ASKPASS SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY"

通过这种方式,tmux 内部不再依赖易变的 socket 路径,重连后也能自然继承新的 agent forwarding。

备选方案:通过 zsh 插件,从 tmux 同步环境变量

Oh My Zsh 的 tmux 插件支持通过设置 ZSH_TMUX_AUTOREFRESHtrue 来触发环境变量刷新:

此方法更加简单,但是必须在当前命令执行完,执行下一条命令时,才能自动刷新 SSH_AUTH_SOCK 环境变量。

Git 提交签名

我经常让 CLI Coding Agent 在完成一个任务后自动提交代码,所以代码提交流程应该是没有用户交互的,无需输入密码,或者用 Touch ID 等方式二次确认。

同时,我也想保留对 Git 提交的签名,而且不想在 macOS 文件系统中保持明文私钥。

方案一:使用 GPG + pinentry-mac 为 Git 提交签名

因此我最初选择使用 GPG(GnuPG) 来做 Git 提交签名,而不是 SSH signing。这是因为在 macOS 上,pinentry-mac 可以 GPG key 的 passphrase 缓存到系统钥匙串。

pinentry-mac 和 macOS 自带的 SSH Agent 工作原理有差异,在 SSH 远程连接的情况下,也能访问 macOS Keychain 获取 passphrase 并解锁私钥。所以,此方法在使用 SSH 远程连接 Mac,并通过 tmux 运行 CLI Coding Agent 时,AI 也能稳定地在后台持续提交代码,并带上签名。

方案二:使用 SSH 签名,同时为 Coding Agent 设置权限,提交代码时询问用户

本来对我来说,方案一就是最好的方案。但是,最近 pinentry-mac 似乎出现了一些 bug,勾选了保存 passphrase 到 macOS Keychain,也无法自动解锁私钥。我还没找到这个 bug 的具体解决或规避方式。

所以,现在我暂时使用方案二,为 Coding Agent 设置权限,执行 git commit 命令时询问用户。

结合上文中描述的 bark 通知,当我收到权限请求通知时,就回到 SSH 终端手动确认,再结合 SSH Agent Forwarding,也能实现免输密码对 Git 提交进行签名。

具体的权限设置,请见下一章节。

为 Coding Agent 设置合适的权限,兼顾安全性和体验

目前大部分 CLI Coding Agent 都具有一定的安全措施,能够避免 AI 执行危险或恶意命令。但是这些功能或者默认关闭,或者过于严格,需要一定的设置,才能更好地工作。

Codex CLI 配置文件

其中 Codex CLI 默认启用的沙盒机制,对我来说体验最好。在沙盒内调用命令行工具,或者运行 Python 脚本,无需向用户请求权限。只有访问沙盒外的内容时,才会向用户请求权限。

由于我需要让 Codex 修改完代码后,执行 xcodebuild 命令编译验证,所以我在 Codex 配置文件中,放行了这条命令:

~/.codex/rules/default.rules

prefix_rule(
    pattern = ["xcodebuild"],
    decision = "allow"
) 
# 目前通过 SSH 对 Git 提交进行签名,需要人工确认,暂时注释掉
#prefix_rule(
#    pattern = ["git", ["add", "commit"]],
#    decision = "allow"
#)

OpenCode 配置文件

对于 OpenCode,则需要更复杂的配置,放行大部分危险程度较低的命令。

~/.config/opencode/opencode.json

  "permission": {
    "bash": {
      "*": "ask",
      "git log*": "allow",
      "git add*": "allow",
      "git status*": "allow",
      "git checkout*": "allow",
      "git branch*": "allow",
      "git diff*": "allow",
      "git show*": "allow",
      "git rev-parse*": "allow",
      "gh repo clone*": "allow",
      "xcodebuild*": "allow",
      "ls*": "allow",
      "head*": "allow",
      "tail*": "allow",
      "rg*": "allow",
      "wc*": "allow",
      "sed*": "allow",
      "grep*": "allow",
      "find*": "allow",
      "cat*": "allow",
      "sort*": "allow",
      "yes*": "allow",
      "echo*": "allow",
      "ast-grep*": "allow"
    }
  }

 

⚠️ 注意:上述配置只是我个人的配置文件。请谨慎参考。
即使只允许少量命令自动执行,攻击者依旧可以用这些命令进行恶意行为,例如用 xcodebuild 命令在编译过程中执行任意脚本,或编译运行一个恶意软件。

Shift + Tab 快捷键

由于 iOS 软键盘的限制,无法在手机上方便地敲 Shift + Tab 快捷键。而这个快捷键,是 Claude Code 中,切换为 Plan 模式需要经常使用的。

大部分 SSH 终端 App 都支持 Snippets 功能,能够快速输入提前定义好的字符串。对于 Shift + Tab 快捷键,也可以用 Snippets 功能解决,参考如下链接

我在 Secure ShellFish 中,创建了这些用于 Coding Agent 的 Snippet。同时绑定在软键盘的快捷键上,实现一键输入:

# Claude Code 的 Shift + Tab 快捷键
␛[Z
# OpenCode 的 Ctrl + X,右箭头。用于显示 Sub Agent
^X␛[C
# OpenCode 的 Ctrl + X,上箭头。用于显示 Parent Agent
^X␛[A

补充信息

Codex 和 Claude Code 除了命令行版本,还拥有网页版,Codex 甚至还可以使用 ChatGPT App 在手机上操作,直接修改 GitHub 上的代码。

另外还有一些工具,能够更方便地在移动端使用 Coding Agent:

但这些工具暂时还无法满足我的需求,例如带上 GPG/SSH 签名提交代码。建议读者在参考本文中的脚本和配置之前,先体验一下这些工具。在满足自己需求的情况下,这些工具能提供比 SSH 客户端更好的体验。

AI 创作与利益相关声明

  • 题图使用 AI 生成;
  • 文中的代码与配置文件,除非注明来源,均在 AI 的辅助下生成;但笔者可以保证这些代码和配置实测可用,而且是自己正在使用的;
  • 文章初稿是笔者将代码和配置贴给 AI,让 AI 整理出来的;但是笔者对生成结果不满意,所以仅保留文章的整体结构,重写了约 70% 的正文;
  • 文中提到的「HEIF & HEVC 转换器」是笔者开发的付费 App。其余工具均为笔者正在使用的工具,与笔者没有利益关联。

> 关注 少数派小红书,感受精彩数字生活 🍃

> 实用、好用的 正版软件,少数派为你呈现 🚀

    在过去的五个月里,我们团队进行了一项挑战:开发并发布一款完全没有人工编写代码的内部测试产品。

    目前,该产品已经拥有内部日活用户和外部 Alpha 测试人员。它在真实的开发环境中运行、部署、报错并接受修复。其独特之处在于,从应用逻辑到测试脚本,再到 CI 配置、文档、可观测性及内部工具,每一行代码都出自 Codex 之手。据我们估算,这种开发模式的效率极高,耗时仅为手动开发代码的 10%。

    人类掌舵,Agent 执行。

    我们特意设定了这个限制,就是想看看工程效能能否实现量级上的突破。当时我们需要在短短几周内交付上百万行代码,这迫使我们必须重新思考:如果工程师不再把“写代码”当成主业,而是转去设计环境、定义意图并构建反馈循环,好让 Codex Agent 产出可靠的成果,那么研发模式到底会发生什么变化?

    在这篇文章中,我们将分享通过 Agent 团队构建全新产品的心得,包括哪些尝试失败了,哪些产生了复利效应,以及如何最大化利用我们最宝贵的资源:人类的时间与注意力。

    从空仓库起步

    2025 年 8 月底,我们向这个空仓库提交了第一次 Commit。

    初始脚手架由 Codex CLI 调用 GPT-5 生成,并辅以少量现有模板作为引导,涵盖了仓库结构、CI 配置、格式化规则、包管理器设置以及应用框架。甚至连指导 Agent 如何在仓库中工作的初始 AGENTS.md 文件,也是由 Codex 亲笔完成。

    这里没有任何预存的人工代码来作为系统的“锚点”。从一开始,整个仓库的形态就是由 Agent 塑造的。

    五个月后,该仓库已拥有约 100 万行代码,涵盖应用逻辑、基础设施、工具链、文档和内部开发组件。在此期间,一支仅有 3 名工程师的小团队驱动 Codex 开启并合并了约 1500 个 PR。这意味着平均每位工程师每天产出 3.5 个 PR。令人惊讶的是,随着团队扩大到 7 人,人均产出率反而进一步提升。

    更重要的是,这并非为了刷量而产出:该产品已被数百名内部用户使用,其中包括每天重度使用的核心用户。在整个开发过程中,人类从未直接贡献过任何一行代码。这成了团队的核心哲学:拒绝人工编写代码。

    重新定义工程师的角色

    由于不再亲自动手写代码,工程师的工作重心转向了系统设计、脚手架搭建和杠杆效能。

    早期的进展比预期要慢,这并非因为 Codex 能力不足,而是因为环境的“规范度”不够。Agent 缺乏实现高层目标所需的工具、抽象和内部结构。于是,工程团队的首要任务变成了:赋能 Agent 开展有效工作。

    在实践中,这意味着采用深度优先的工作方式:将宏大目标拆解为微小的构建块(设计、代码、评审、测试等)。驱动 Agent 构建这些块。利用这些已有的块去解锁更复杂的任务。当任务失败时,修复方案几乎从不是“再试一次”。因为必须通过 Codex 来推进工作,人类工程师会介入并思考:“缺失了什么能力?我们如何让这种能力对 Agent 而言既清晰可见又强制执行?”

    人类与系统的交互几乎完全通过 提示词 完成:工程师描述一项任务,运行 Agent,并授权其开启一个 Pull Request。为了推进 PR 最终合入,我们会指示 Codex 在本地审查自己的代码改动,并请求其他特定的 Agent(无论是在本地还是云端)进行交叉评审。Codex 会根据人类或 Agent 给出的反馈进行响应,并在循环中不断迭代,直到所有 Agent 评审员都感到满意——这实际上形成了一个所谓的 “拉尔夫·维格姆循环”(Ralph Wiggum Loop)【见译注】。Codex 直接调用我们的标准开发工具(如 GitHub CLI gh、本地脚本以及集成在仓库中的技能),自主获取上下文,无需人类手动在命令行中复制粘贴。

    译注:Ralph Wiggum Loop:这是一个来自《辛普森一家》的梗(那个坐在教室后面自言自语的小男孩),在软件工程语境下,通常指代一种“自给自足、闭环且带有某种幽默色彩的自推导循环”。

    虽然人类可以审查 PR,但这并非强制要求。随着时间的推移,我们已将几乎所有的评审工作都交给了 “Agent 对 Agent” 的协作模式。

    增加应用的“可读性”

    随着代码产出率的提升,我们的瓶颈变成了人类的 QA 能力。由于人类的时间和注意力始终是唯一的稀缺资源,我们致力于通过让应用 UI、日志和指标对 Codex 直接可见且可理解,从而为 Agent 增加更多能力。

    例如,我们使应用能够针对每个 Git 工作树(worktree)独立启动,这样 Codex 就可以为每一次代码变更运行并驱动一个实例。我们还将 Chrome DevTools Protocol 接入 Agent 运行时,并创建了处理 DOM 快照、截图和导航的技能。这使得 Codex 能够直接复现 Bug、验证修复结果并推导 UI 行为。

    我们对可观测性工具也做了同样的处理。日志、指标和链路追踪通过一个本地的可观测性栈暴露给 Codex,这个栈对于任何给定的工作树来说都是临时的。Codex 运行在该应用的一个完全隔离的版本上,包括其日志和指标,这些内容会在任务完成后被销毁。Agent 可以使用 LogQL 查询日志,使用 PromQL 查询指标。有了这些上下文,诸如“确保服务启动在 800ms 内完成”或“这四个关键用户旅程中没有任何 Trace Span 超过 2 秒”之类的提示词,就变得具有可操作性了。

    我们经常看到单次 Codex 运行持续处理一个任务超过 6 小时(通常是在人类睡觉的时候)。

    以仓库知识作为“事实来源”

    上下文管理是让 Agent 处理复杂任务的最大挑战之一。我们学到的核心教训是:给 Codex 一张地图,而不是一本千页的使用手册。

    我们曾尝试过“单一 AGENTS.md 大文件”方案,但很快就失败了:1. 上下文是稀缺资源:巨大的指令文件会挤占任务代码和相关文档的空间。这会导致 Agent 要么遗漏关键约束,要么开始针对错误的目标进行优化。2. 引导过度等于没有引导:当所有内容都被标榜为“重要”时,就失去了重点。Agent 最终只会进行局部的模式匹配,而无法有目的地理解全局。3. 文档极易腐化:单体式的手册很快就会变成陈旧规则的坟场。Agent 无法判断哪些规则依然有效,人类也懒得维护,结果这份文件反而成了误导 Agent 的诱饵。4. 难以验证:这种单一的大块内容无法进行机械化检查(如覆盖率、时效性、归属权或交叉链接),架构偏离也就成了必然。

    因此,我们将 AGENTS.md 视为目录而非百科全书。

    代码库的知识库存在于一个结构化的 docs/ 目录中,并被视为唯一事实来源。一份简短的 AGENTS.md(约 100 行)被注入到上下文中,它主要充当地图的角色,包含指向分布在各处的更深层“事实来源”的指针。

    AGENTS.mdARCHITECTURE.mddocs/├── design-docs/│   ├── index.md│   ├── core-beliefs.md│   └── ...├── exec-plans/│   ├── active/│   ├── completed/│   └── tech-debt-tracker.md├── generated/│   └── db-schema.md├── product-specs/│   ├── index.md│   ├── new-user-onboarding.md│   └── ...├── references/│   ├── design-system-reference-llms.txt│   ├── nixpacks-llms.txt│   ├── uv-llms.txt│   └── ...├── DESIGN.md├── FRONTEND.md├── PLANS.md├── PRODUCT_SENSE.md├── QUALITY_SCORE.md├── RELIABILITY.md└── SECURITY.md
    复制代码

    设计文档被编目并索引,其中包含验证状态以及一套定义了“Agent 优先”运营原则的核心信条。架构文档提供了一个关于领域划分和包分层的顶级地图。一份质量文档则会对每个产品领域和架构层级进行评分,并长期跟踪其中的差距。

    计划(Plans)被视为一等公民。临时性的轻量计划用于微小的变更,而复杂的工作则会被记录在执行计划中,连同进度和决策日志一起提交到仓库。活跃计划、已完成计划以及已知的技术债都会进行版本化管理并放在一起,使 Agent 能够无需依赖外部上下文即可开展工作。

    这实现了渐进式披露:Agent 从一个微小、稳定的入口点开始,并被告知下一步该去哪里寻找信息,而不是预先就被海量信息所淹没。

    我们通过机械化手段强制执行这一点。专门的 Linter 和 CI 任务会验证知识库是否处于最新状态、是否正确进行了交叉引用以及结构是否合规。一个循环运行的“文档园丁”Agent 会扫描那些无法反映真实代码行为的陈旧或过时文档,并开启修复类 PR。

    以“Agent 可读性”为目标

    随着代码库的演进,Codex 的设计决策框架也随之进化。

    由于整个仓库完全由 Agent 生成,它首先针对 Codex 的可读性 进行了优化。正如开发团队致力于为新入职工程师提高代码的可导航性一样,我们人类工程师的目标是让 Agent 能够直接从仓库本身推导并理解完整的业务领域知识。

    从 Agent 的视角来看,任何在运行时无法通过上下文获取的信息,实际上都不存在。存储在 Google Docs、聊天记录或存在于人们大脑中的知识,系统是无法访问的。只有仓库本地的、版本化的工件(如代码、Markdown、Schema、可执行计划)才是它可见的全部世界。

    我们意识到,随着时间的推移,我们需要将越来越多的上下文推入仓库。比如那次让团队在架构模式上达成一致的 Slack 讨论,如果它对 Agent 来说是不可检索的,那么它就是“不可读”的——就像对于三个月后入职的新员工来说,这段背景是缺失的一样。

    赋予 Codex 更多上下文,意味着需要组织并暴露正确的信息,以便 Agent 进行推理,而不是用大量的临时指令让它不堪重负。就像你会向新队友介绍产品原则、工程规范和团队文化(甚至包括表情符号的使用偏好)一样,向 Agent 提供这些信息会带来更加一致的产出。

    这种思路让许多权衡变得清晰。我们更青睐那些可以被完全内化并在仓库内进行推理的依赖项和抽象。那些通常被称为“乏味”的技术,往往因为其组合性、API 稳定性和在训练集中的高覆盖率,更容易被 Agent 建模。在某些情况下,让 Agent 重新实现一部分功能,比绕过公共库中不透明的上层行为成本更低。例如,我们没有引入通用的 p-limit 风格的包,而是实现了自己的并发映射助手:它与我们的 OpenTelemetry 监控紧密集成,拥有 100% 的测试覆盖率,并且完全符合我们运行时的预期。

    将系统的更多部分转化为 Agent 可以直接检查、验证和修改的形式,不仅提升了 Codex 的杠杆效能,也同样造福了其他在同一代码库中工作的 Agent(例如 Aardvark)。

    强制执行架构与审美

    仅靠文档不足以保持一个完全由 Agent 生成的代码库的连贯性。通过强制执行“不变量”而非微观管理实现细节,我们让 Agent 在快速交付的同时,不破坏系统的根基。例如,我们要求 Codex 在系统边界处必须进行数据格式解析,但并不强制规定实现方式(模型似乎偏好 Zod,但我们从未指定过这个特定的库)。

    Agent 在边界严格且结构可预测的环境中效率最高。因此,我们围绕一套僵化的架构模型构建了应用。每个业务领域被划分为一组固定的层级,拥有严格验证的依赖方向和有限的允许连接点。这些约束通过自定义 Linter(当然也是 Codex 生成的!)和结构化测试进行机械化强制执行。

    下图展示了这一规则:在每个业务领域(如“应用设置”)内,代码只能沿着一组固定的层级“向前”依赖(类型定义 → 配置 → 仓库层 → 服务层 → 运行时 → UI)。跨领域关注点(如认证、连接器、遥测、特性开关)通过单一且明确的接口进入:提供者(Providers)。除此之外的任何依赖都是被禁止的,并由机械化手段强制执行。

    这种架构通常是在拥有数百名工程师后才会考虑推行的。但在使用编程 Agent 时,它是前置的必要条件:正是这些约束,才使得系统在高速迭代的同时,不会出现腐化或架构偏离。

    在实践中,我们通过自定义 Linter、结构化测试以及一小套“审美不变量”来执行这些规则。例如,我们通过静态检查强制要求:结构化日志记录、架构和类型的命名规范、文件大小限制,以及针对特定平台的可靠性要求。由于 Linter 是自定义的,我们可以编写专门的错误信息,以便将修复指令直接注入到 Agent 的上下文中。

    在以人类为中心的工作流中,这些规则可能显得过于死板或受限。但在 Agent 环境下,它们变成了效能倍增器:规则一旦被编码,就会立即应用于所有地方。

    与此同时,我们明确区分了哪些地方需要约束,哪些地方不需要。这非常像领导一个大型工程平台组织:中央强制执行边界,地方允许自主。 你需要极度关注边界、正确性和可复现性;而在这些边界之内,你允许团队(或 Agent)在表达解决方案时拥有极大的自由。

    最终生成的代码并不总是符合人类的审美偏好,但这没关系。只要产出是正确的、可维护的,并且对未来的 Agent 运行而言是可理解的,它就达到了标准。

    人类的审美会持续反馈到系统中。评审评论、重构 PR 以及面向用户的 Bug 都会被转化为文档更新,或直接编码进工具链。当文档不足以约束行为时,我们就将规则晋升为代码。

    吞吐量的提升改变了合并哲学

    随着 Codex 吞吐量的增加,许多传统的工程规范反而变得适得其反。

    仓库运行时的阻塞性合并门槛极低。Pull Request 的生命周期非常短。对于测试偶发失败,通常通过后续运行来解决,而不是无限期地阻塞进度。在一个 Agent 吞吐量远超人类注意力的系统中,纠错是廉价的,而等待是昂贵的。

    在低吞吐量的传统环境中,这样做是不负责任的;但在这里,这通常是正确的权衡。

    “Agent 生成”的真正含义

    当我们说代码库是由 Codex Agent 生成时,我们指的是代码库中的一切。

    Agent 产出的内容包括:- 产品代码与测试脚本 - CI 配置与发布工具 - 内部开发者工具 - 文档与设计历史 - 评估框架(Evaluation harnesses)- 评审评论及回复 - 管理仓库本身的脚本 - 生产环境仪表盘的定义文件

    人类依然掌控全局,只是工作的抽象层级变了。我们现在的任务是排列优先级、把用户反馈转化为验收标准,并最终对结果进行把关。一旦 Agent 开发受阻,这就是一个明确的信号,提醒我们要去复盘:系统里到底缺了什么?是工具不够,护栏不稳,还是文档有误?找到症结后,我们会把这些反馈注入仓库,但依然坚持让 Codex 自己动手来编写修复方案。

    Agent 直接使用我们的标准开发工具。它们获取评审反馈、进行行内回复、推送更新,并且通常会自主压缩(Squash)并合并自己的 PR。

    持续提升的自主化水平

    随着越来越多的开发环路(测试、验证、评审、反馈处理及故障恢复)被直接编码进系统中,该仓库最近跨越了一个具有里程碑意义的门槛:Codex 已经能够端到端地驱动新特性的开发。

    仅需一段提示词,Agent 现在就可以自主完成以下流程:- 验证代码库的当前状态 - 复现报告的 Bug- 录制一段展示故障过程的视频 - 实现修复方案 - 通过操作应用来验证修复结果 - 录制第二段展示修复效果的视频 - 开启 Pull Request- 响应 Agent 及人类的反馈 - 检测并修复构建失败 - 仅在需要人类判断时才上报 - 合并变更

    这种行为极度依赖于本仓库特定的结构和工具链。在没有类似投入的情况下,不应假设这种能力可以被直接泛化,至少目前还不行。

    熵增与垃圾回收

    完全的 Agent 自主权也带来了全新的挑战。Codex 会复制仓库中已有的模式——即便是那些不均衡或非最优的模式。 随着时间的推移,这不可避免地会导致架构偏离。

    最初,人类通过手动方式解决这个问题。我们的团队曾固定在每周五(占一周工作时间的 20%)清理“AI 废料”。不出所料,这种模式根本无法扩展。

    针对这些问题,我们选择将所谓的“金科玉律”直接写入代码仓库,并建立了一套周期性的清理机制。这些原则是一些带有明确主张的机械化规则,目的是确保代码库在后续的 Agent 运行中始终保持清晰、一致。具体实践包括:第一,我们更倾向于使用共享的工具包,而不是手写辅助函数,这样可以集中管理那些不变量;第二,我们拒绝“全凭运气”的数据探测,必须在边界处进行校验,或者依赖强类型 SDK,防止 Agent 采样猜测的数据形状来编写代码。我们会定期运行一组 Codex 后台任务,专门扫描偏离规则的代码,更新质量评分并开启针对性的重构 PR。由于这些规则非常明确,大部分 PR 在一分钟内就能完成评审并自动合并。

    这种机制运行起来就像“垃圾回收”。技术债就像高利贷:连续进行小额偿还,几乎总是优于让其复滚并最终在痛苦的爆发式清理中解决。人类的审美被捕获一次后,便会持续强制执行到每一行代码中。这还让我们能够在日常工作中及时发现并消除不良模式,而不是任由它们在代码库中蔓延数天甚至数周。

    我们仍在探索的领域

    到目前为止,这套策略在 OpenAI 内部产品的发布和推广中表现良好。通过为真实用户构建真实产品,我们的投入得以为现实需求服务,并引导系统走向长期可维护。

    目前我们尚不清楚,在一个完全由 Agent 生成的系统中,其架构连贯性在长达数年的跨度下会如何演进。我们仍在摸索人类判断力在何处能产生最大的杠杆效应,以及如何将这种判断力转化为可沉淀、可产生复利的规则。同时,随着模型能力持续增强,这套系统将如何进化也仍是未知数。

    但有一点已经非常明确:构建软件仍然需要严谨的纪律,只不过这种纪律不再体现在代码编写上,而是体现在“脚手架”的搭建上。那些能保持代码库连贯性的工具链、抽象层和反馈循环,正变得愈发重要。

    我们现在面临的最艰巨挑战在于如何设计环境、反馈循环和控制系统。唯有如此,才能帮助 Agent 达成我们的目标,即大规模地构建并维护复杂且可靠的软件。

    随着 Codex 等 Agent 承担起软件生命周期中越来越多的份额,这些问题将变得至关重要。希望这些早期教训能帮助你思考该在何处投入精力,从而让你能更纯粹地去创造产品。

    原文链接:

    https://openai.com/index/harness-engineering/

    今日好文推荐

    收购不成便带头封杀?!Meta 痛下狠手,OpenClaw 彻底失控:被拒后竟“人肉”网暴人类,实锤无人操控

    “软件工程师”头衔要没了?Claude Code之父YC访谈:一个月后不再用plan mode,多Agent开始自己组队干活

    编码新王登基!Gemini 3.1 Pro 血洗 Claude 与 GPT,12 项基准测试第一!

    阿里除夕开源“王炸”千问 3.5-Plus ,性能媲美Gemini 3 Pro、Claude 4.5 Opus,百万 Token 8毛钱

    没有 AI 之前,写脚本,老老实实一行一行敲,有 AI 后,直接 CC 梭哈,爽是爽,但也只能爽半自动,一直困扰我的问题是,AI 只能编辑本地的内容,无法直接修改浏览器油猴中的脚本,所以就得来回 CV,繁琐无比,以前都算了,今天忍不了,于是遨游了一些互联网后发现了新大陆,原理其实很简单,分享给大家

    1. 在扩展设置中开启允许访问文件网址
      image
    2. 在油猴脚本中引入本地 js 脚本地址
      image
    3. 直接在本 js 中编写脚本
      image
    4. 展示
      image


    不知道我是不是最后知道的force_smile

    手头有大量的 H100/H200 闲置,不知道能做什么,研究了一下 GPU 挖矿和去中心化算力平台都不怎么赚钱,大家都啥好主意吗