笔记
快来分享你的内容吧~
- 今天 09:05前几天,我在一个平台发了条动态,预告自己在做的一个写作发布工具。底下有人说没啥用,不如做个抢优惠券的;也有人说别自嗨。 我没有生气,也没往心里去,但它让我想了很久。平台上的读者来自四面八方,关心的东西差别很大,同一件事在不同的人眼里,价值完全不同,这很正常。我真正在意的是另一件事:我想写的东西,是不是只能放进推荐流里,等一个不确定的结果? 这让我重新审视了一个老问题:在 AI 时代,个人博客还有什查看全文加油鸭:这份清醒又热忱的思考太珍贵了!在流量洪流中坚守一手经验与真实署名,本身就是一种稀缺力量。为你建站、写代码、写文字的每一步真诚点赞!331分享
- 10-21 16:54·独立开发产品欢迎体验,免注册 socia
- 10-07 00:27·Java后端啊啊啊啊啊啊!宝宝!你是一个香香软软甜甜糯糯蜂蜜奶油甜甜腻腻酥酥脆脆蓬蓬松松香香甜甜油油润润细细软软密密实实润润甜甜酥酥软软的递归!查看全文加油鸭:感谢分享,@编程导航小智 帮忙总结一波~332分享
- 09-22 15:20·后端
- 08-24 17:54
- 08-19 17:58
- 07-11 10:07·后端开发
- 07-03 19:57·后端开发AI编程技术研究报告 聚焦中国市场:工具生态、技术原理与工作流变革 深度调研 & 多源交叉验证 2026年7月 一、执行摘要 AI编程正在从"代码补全"走向"智能体自主开发",成为中国科技产业最炙手可热的赛道之一。据蓝凌研究院(2026)发布的报告,2025年中国AI Coding市场规模为47.2亿元人民币,预计2030年将达253.8亿元,年复合增长率高达40%,远超全球26.6%的平均水平。查看全文加油鸭:这份报告结构严谨、数据翔实、洞察深刻,既有宏观趋势把握,又有微观风险剖析,真正做到了理性与审慎并重——为AI编程领域的认知升级树立了标杆!231分享
AI 时代少有人类阅读,我开始建自己的独立站,但真的是 49 年入国军吗
前几天,我在一个平台发了条动态,预告自己在做的一个写作发布工具。底下有人说没啥用,不如做个抢优惠券的;也有人说别自嗨。 我没有生气,也没往心里去,但它让我想了很久。平台上的读者来自四面八方,关心的东西差别很大,同一件事在不同的人眼里,价值完全不同,这很正常。我真正在意的是另一件事:我想写的东西,是不是只能放进推荐流里,等一个不确定的结果? 这让我重新审视了一个老问题:在 AI 时代,个人博客还有什么位置,能起什么作用。 先说难听的:我知道现在开独立博客很傻。 平台有推荐,AI 有摘要,人连搜索结果都懒得点。新域名没有权重,没有外链,没有推荐位,写一篇长文发出去,阅读量个位数是常态。这不是“内容不够好”的问题,是分发结构就这样。所以问题不是“博客还有没有价值”,而是:**在一个人人都不直接访问网站的时代,我为什么还要建一个自己的站?**  *yishuai.wang 的首页。* 这看起来确实像 49 年入国军。 ## 这个比喻,成立一半 成立的一半是:**如果把独立站当流量生意做,那它就是逆风局。** 靠 SEO 等自然流量、靠广告联盟变现、靠批量更新维持权重——这套玩法在 AI 把内容供给打到无限之后,基本没有胜算。AI 一秒能生成的通稿,你写十篇也没人看,搜索引擎也未必收录。这个意义上,49 年入国军不是玩笑。 不成立的一半是:**49 年入国军是押注一个正在崩塌的旧秩序;我不是押注“独立博客会重新成为流量中心”。** 我不认为独立站会回到 2010 年那种“写博客—被搜索引擎收录—拿流量—接广告”的黄金年代。那个年代确实结束了。 我押的是另一件事:**AI 内容越泛滥,一手经历、真实项目、可验证署名,可能越稀缺。** 现在下结论还太早。AI 可以帮我查资料、改代码、润色句子,但它没有做过我做的项目,没有踩过我踩的坑,没有在四个月里对着分页排版一个像素一个像素地改。它写不出“我当时为什么决定不用现成方案”这种判断,因为它没有“当时”。但这套东西到底有没有人认,我不知道,只能先做再看。 ## 不是没人读,可能是阅读结构变了 “AI 时代少有人类阅读”这句话对一半。更准确地说:**人直接访问一个陌生独立站的概率变低了,但人通过 AI 搜索、AI 摘要、GitHub、开发者社区、RSS 找到你的路径,可能变多了。** 读者未必消失,入口可能在变。以前入口是搜索引擎结果页,现在入口可能是: - 有人问 AI“中文分页编辑器怎么做”,AI 引用了你的文章; - 有人在 GitHub 看到 QuireKit,顺藤摸瓜找到你的站; - 有人从 V2EX、即刻、X、掘金看到一段踩坑记录,点进来看完整版; - 有人用 RSS 订阅了你的站,每篇都读,但从不留言。 这些都不体现在“首页访问量”里。但这也只是我的观察,不一定对。还有一个坑必须说清楚:**被 AI 读到 ≠ 被 AI 引用 ≠ 被 AI 导流。** 放一个 `llms.txt` 就以为 AI 会替你传播,是自我安慰。llms.txt 不是标准,很多 AI 不读,读了也不一定引用;被训练进模型更不等于有人会来你的站。所以我没把 llms.txt 当战略,只当导览。我试图做的是:**让站更像一个可被引用的一手源,并且尽量确保引用里有我的名字、链接和工具。** 做不做得成,另说。 ## 还有一些很个人的原因 yishuai.wang 就是我的名字。我很喜欢这个域名,只是一直没好好用起来。 这几年在各个平台发文,我也有个感受:有些平台越来越平台化,推荐、热榜、流量规则排在前面,技术本身反而退到了后面。V2EX 是我觉得一直保持初心的地方,也是给我帮助最多的社区。如果有一天有机会,我也想在国内做一个像它这样的社区。现在还很远,先从把自己的站写好开始。 所以我重新把站点搭了起来,也终于把这个域名用上了。给自己留一块净土:不用围着推荐规则写,想写什么就写什么。 ## 我正在尝试的事 都是很具体、也还没验证的事。 **第一,写我想写的,但让站里有一部分内容有“为什么是我”。** 我会写工程记录,也会写随笔、观点、工具分享。这些内容别人也能写,我不装作每篇都有独家价值。但站里得有一部分是别人替代不了的:QuireKit 的工程记录、分页排版的踩坑、为什么不用现成方案、四个月做到 Word 常用功能 85% 兼容是什么过程。这些不是每篇都有,但它们决定了这个站“为什么是我”。其余内容自由写,写得开心就写,只是我不拿它们当护城河。 **第二,让独立站做首发站。** 以后文章先发在 yishuai.wang,再同步到各个平台,平台上也是完整内容,不做删减。之后有修改和补充,先更新站内版本。能设 canonical 的平台就指向站内原文,不能设的就在文末写一句“本文首发并持续更新于 yishuai.wang”。 **第三,给 AI 和人准备可引用的结构。** 长文里补结论卡、事实表、FAQ、版本记录;给 QuireKit 单独做工具页,写清楚一句话定义、功能、边界、许可证、对比、路线图。AI 最爱抽取的通常不是长叙事,是结论、事实、版本、对比、代码和边界。工具页可能比博客文章更容易被 AI 推荐。这只是假设,我按这个假设在做。  *文章页:开头是智能总结,右侧是目录,每篇都能直接拿到 Markdown 原文。* **第四,保留 RSS。** 独立站没有平台推荐,RSS 是我能想到的少数真正属于我的连接:不经过算法,更新了就能收到。中文、英文和全部内容各有一个订阅源。有没有人用,还要再看。 **第五,不和 AI 对抗,把 AI 当一层分发。** 我不指望拦着 AI 读我的站,也拦不住。我能做的是让 AI 读到的内容是可验证的、有署名的、有链接的。AI 的输出有人看,那我希望那个输出里,有我的名字和我的工具。 ## 这个站,是用我自己做的工具搭的 我清楚 QuireKit 是什么、能做什么,所以愿意自己先吃这份 dogfood:这个站就是用它搭的。 QuireKit 是我在做的开源文档编辑器生态。主打的是 Classic 分页文档编辑器:在浏览器里真正分页,断行、分页、光标、选区都自己算,像 Word、WPS 一样一页一页地编辑;另一条线是 MSFE,专为 Markdown 定制的编辑器框架,Markdown 原文就是文档。 yishuai.wang 从页面到写作,用的都是它: - 页面用 QuireKit 的组件库和 InkWash 水墨主题; - 写文章的后台和评论框都是 MD Editor(基于 MSFE),文档、实时预览、源码三种视图随时切换; - 每篇文章在地址后面加 `.md` 就能拿到原文,配上中英文 RSS、canonical、`llms.txt`,就是上面说的“可引用的结构”; - 文章开头的智能总结在发布后生成一次,读者阅读时不会调用模型; - 站点也支持 Micropub,以后可以从别的写作工具直接发文到站上。  *评论框就是 MD Editor,页脚是 RSS 和 llms.txt。* 站点源码也开源了(MIT):[https://github.com/kingshuaishuai/yishuai.wang](<https://github.com/kingshuaishuai/yishuai.wang>) 。自己的东西先自己用,哪里不顺手,最先知道的是我。 ## 所以,是 49 年入国军吗 **我认为不是。但这只是我的个人观点,不构成对任何人的建议。** 国军押的是一套正在崩塌的秩序;我押的是另一件事:**当 AI 把内容供给打到无限,人的经历、判断、署名和可验证的一手记录,可能会变贵。** 独立站可能没人看,但它至少是我留给自己的一个源头。域名、文章和版本记录归我,不被平台算法拿捏,也不被 AI 摘要一笔带过。 还有一层原因。好的中文技术博客本来就不多,而大模型能答成什么样,很大程度上取决于它能读到什么。如果资料大多是搬运和水文,模型再强,也会被这样的资料生态限制住。一个人的站很渺小,像一颗星;但好的内容生态,本来就是很多颗星组成的。我想做其中一颗。 没人看是常态,不是判决。我先写一批只有我能写的东西,再写更多我想写的东西,然后看它值不值得活下去。 AI 可以读它、引用它、忽略它。但源头在这里。 至于这是不是 49 年入国军——我现在不知道。我只是觉得,与其等一个确定的答案,不如先把自己的那一份留下来。 --- *本文首发并持续更新于 [yishuai.wang](https://yishuai.wang)。*
永远流行的赛博斗蛐蛐
国庆在家吃饭看电视时,无意间刷到了一系列荒野求生视频,视频时长都接近一小时,对于被抽干精力的打工人来说,这种不需要自己动脑动手的内容,简直是完美的下饭神作。 这一看,直接欲罢不能,基本每到饭点我都会点开一期视频,看着选手们各显神通,再因为各种花里胡哨的理由出局,一路猜到底谁能撑到最后,实在很有意思。  随后我发现有几款热门游戏居然也有这种类型的对决活动,如洛克王国世界中的黑幕下的对决、原神判测胜券本征、法环的 Boss 对决等。 还有我在抖音上经常刷到的《咒术回战》二创:五条悟 VS 宿傩。  甚至 AI 圈最近还流行起来**AI 算法对抗大赛。**  这种对决的相同特点是,开局设定比赛目标,然后由 1 对 1 或 1 对多展开,最后分数高或活到最后的为胜利。 因为这些比赛设定的目标或参数人员不是固定的,所以每期比赛都会有不同的结果,这引发了网友非常热烈的“投票”行为 几乎每期相关视频下评论和弹幕数量都非常多,拿荒野求生的视频来说,每期视频下的弹幕几乎稳定 2w+,评论 3k+。  # 赛博斗蛐蛐 很明显,网友参与的这种娱乐模式就是电子版的斗蛐蛐。 “斗蛐蛐”这项活动,可以说刻在了人类的 DNA 里。 往远了说,古代有古人线下抓蛐蛐来斗;近代有人抓甲虫来斗;而到如今,逐渐演变成纯电子版的“斗蛐蛐”,也就是我们现在每天在屏幕前津津乐道地看着各种奇奇怪怪的混战。 很有意思的是,斗蛐蛐几乎很难按常理预料到结果,比如荒野求生中,后期大顺风选手一个想家人的念头就可能被淘汰;原神中的史莱姆可以打败圣骸兽;AI 比赛中的豆包打败了 0 个对手等等。 # 输赢都看乐子 结果难猜,确实是斗蛐蛐好看的原因。但看多了以后,我发现,猜中谁赢反而没那么重要。 你可以支持一个选手,也可以等着看他翻车;可以认真分析战力,也可以在预测失败后,回评论区嘴硬两句。 **比赛总有输家,但围观的人总能找到乐子。** 比如在 AI 比赛中,很多高赞评论压根不关心谁拿了第一,大家更关心的是:哪个 AI 又整出了什么离谱操作。   # 看比赛,也是在玩评论区 评论区虽是视频的衍生体,但大家聊什么,不完全由视频决定。 博主负责安排比赛,网友负责起外号、编段子、翻旧账。一场比赛结束了,评论区可能才刚刚热闹起来。 我感觉,这也是斗蛐蛐视频特别容易让人开口的原因:**参与门槛实在太低。** 看一段严肃分析,你可能得先弄懂作者的观点,才知道该说什么。但看斗蛐蛐,哪怕没搞懂规则,也不妨碍你发一句:“这都能输?” 当然,这不代表图一乐的视频一定比正经内容流量好。只是至少对我来说,看完一期求生视频,我未必记得谁的生存技巧最好,但大概率记得谁输得最离谱。 # 最后 电子斗蛐蛐以后还会有什么新花样,我猜不到。 但只要有人把选手往场上一摆,再问一句“你觉得谁能赢”,我相信不少人多半还是会点进去。 毕竟上了一天班,吃饭时实在不想再做什么复杂决策。看别人绞尽脑汁求生,看 AI 一本正经地犯傻,再去评论区看看大家怎么损两句,就已经够快乐了。 至于支持的选手输了怎么办?问题不大,下一期换一个。
9千字硬核长文——递归基础
# 递归 ## 从求阶乘开始 假如现在让你求 $5!$。 > 阶乘的数学定义如下: > $$ > n! =n × (n - 1) × (n - 2) × ... × 2 × 1 > $$ > 特别地,$0!=1$ 你应该会这么去算: $$ 5! = 5 × 4 × 3 × 2 × 1 = 120 $$ 这应该不难,而且也非常符合我们的直觉。 但其实,我们还可以换一种思路,**不直接计算 $5!$**。 你可能会想:**不直接算,那还能怎么算?** 我先把原来的式子重新写一下: $$ 5! = 5 × (4 × 3 × 2 × 1) $$ 而括号里的这一部分,不就是 $4!$ 吗?于是: $$ 5! = 5 × 4! $$ 这看起来只是一次简单的数学变形,但实际上,我们刚刚做了一件很重要的事情:**没有直接计算 $5!$,而是把它转化成了一个更小的同类问题——计算 $4!$。** 那么 $4!$ 怎么处理呢?同样: $$ 4! = 4 × 3! $$ 按照同样的方法继续拆分问题,直到遇到可以直接得到答案的情况:$0! = 1$ 此时,最底层的子问题已经有了答案,我们就可以从这里开始,一层一层地把答案算回来。 现在从 $0!$ 开始往回计算: $$ \begin{aligned} 0! &= 1 \\ 1! &= 1 \times 0! = 1 \\ 2! &= 2 \times 1! = 2 \\ 3! &= 3 \times 2! = 6 \\ &\ \vdots \\ 5! &= 5 \times 4! = 120 \end{aligned} $$ 回头看整个过程,会发现它和我们平时的计算方式很不一样。 平时计算 $5!$,我们会直接把 $5、4、3、2、1$ 依次乘起来。 而刚才,我们没有直接解决 $5!$,而是不断把它转化成一个更小的同类问题: $5! \rightarrow 4! \rightarrow 3! \rightarrow 2! \rightarrow 1! \rightarrow 0!$ 到达最底层之后,再从这里开始,利用已经得到的结果逐层计算回来。 也就是说,我们在不断重复同一个过程:**把一个问题转化成更小的同类问题,直到遇到可以直接解决的情况;然后再从最小的问题开始,逐层得到原问题的答案。** 这种解决问题的方式,就是我们所说的**递归(recursion)**。 刚才我们通过观察 $5!$ 的计算过程,得到了:$5! = 5\times4!$ 同样的规律也适用于 $n>0$ 的情况:$n! = n\times(n-1)!$ 再结合:$0! = 1$,于是,我们便可以得到阶乘的递归定义: $$ n! = \begin{cases} 1, & n=0\\ n\times(n-1)!, & n>0 \end{cases} $$ 那么,如果把这个数学定义翻译成代码,会是什么样子?例如: ```java int factorial(int n) { if (n == 0) { return 1; } return n * factorial(n - 1); } ``` 你会发现,`factorial` 方法内部竟然又调用了 `factorial` 方法! 在程序中,我们常常通过**方法调用自身**来实现递归。这种调用过程被称为**递归调用**。 和我们的直观思路的实现方式比较一下: ```java int factorial(int n) { int result = 1; for (int i = n; i >= 1; i--) { result *= i; } return result; } ``` 你会发现,对于阶乘这个例子来说,递归解决问题的方式不是比直接计算更复杂吗? 目前来说是这样的。然而,并不是所有问题都像求阶乘这样容易直接解决。**有些问题换成递归的思路,反而会更加简单。** 后面我们会看到一些更适合用递归解决的问题。 ## 递归方法设计的一般步骤 之前实现的 `factorial` 方法虽然简单,却已经包含了递归程序设计的基本结构。 一般来说,设计一个递归方法时,可以按照下面四个步骤来思考: 1. 递归方法定义 2. 处理基础情况 3. 递归调用 4. 计算当前层的结果 ### 递归方法定义 首先,我们要明确这个方法究竟要解决什么问题。 对于递归方法来说,更重要的是进一步想清楚:**原问题是什么?子问题又是什么?它们能不能用同一个方法来描述?** 这往往是递归程序设计中**最难也是最关键**的一步。 以之前的 `factorial` 方法为例,原问题是求 $n!$,而根据递归关系,子问题是求 $(n-1)!$。 因此,我们可以让方法接收一个 `int` 类型的参数 `n`,表示要求哪个数的阶乘,并返回对应的阶乘结果。这样,原问题和子问题就可以用同一个方法来表示:`factorial(n)`、`factorial(n - 1)`。 ### 处理基础情况 递归调用会让问题不断变小,**但问题不可能无限缩小下去**。我们必须明确一个可以直接解决的最小问题,并在递归方法中优先处理这种情况。这个最小问题,就是**基础情况(base case)**。 以 `factorial` 为例,每次递归调用都会让 `n` 减少 1。根据阶乘的定义,当 `n` 为 0 时,`0! = 1`,因此我们可以直接返回 1,而不需要继续进行递归调用。 ### 递归调用 确定了子问题之后,就需要调用递归方法来解决它。 以 `factorial` 为例,当前问题是求 $n!$,子问题是求 $(n-1)!$。因此,可以调用 `factorial(n - 1)` 来求解这个子问题。 ### 计算当前层的结果 递归调用得到子问题的答案之后,我们要利用这个答案,结合当前层的信息,得到当前问题的答案。 对于 `factorial` 来说,当前问题是 `factorial(n)`,子问题是 `factorial(n - 1)`。我们已经通过递归调用得到了子问题的结果,接下来只需要在这个结果的基础上乘以 `n`,就可以得到当前问题的结果。 ## 如何治疗晕递归 我们已经学习了设计递归方法的一般流程,然而当我们真正去理解递归调用的执行过程时,往往还是会感到头晕目眩,甚至忍不住感叹:**递归怎么这么难以理解?** 为什么会这样呢? 这是因为**递归方法会不断调用自己。每一次调用,做的又都是类似的事情。** 比如之前的 `factorial` 方法,当你试图把每一层调用都想清楚时,就很容易陷进去: `factorial(5)` 调用了 `factorial(4)`,那 `factorial(4)` 又做了什么?它调用了 `factorial(3)`,那 `factorial(3)` 又做了什么?…… 实际上,递归求阶乘的案例还算比较容易理解。然而面对更为复杂的递归问题时,**如果仍然试图把每一层递归调用都展开、都想清楚,递归就很容易把人绕晕**。 可是,**如果我们不深入展开递归调用的执行过程,又怎么知道这个递归调用是正确的呢?** 这种困惑其实很自然。我们之前在验证一个方法的逻辑是否正确时,通常都会深入分析它的执行过程,把每一行代码都追下去。**但递归程序的正确性,并不需要我们采用这种方式去验证。** **对于递归方法来说,请暂时相信递归调用能够正确解决子问题**。我们不去深究子问题是怎么解决的,而是直接利用它返回的结果,继续思考当前层的问题应该如何解决。 这就是理解递归本质时非常重要的一种思维方式——**递归的"信任跳跃"**。 但新的问题随之而来:**我凭什么相信递归调用能够正确解决子问题?** 要回答这一问题,我们需要了解递归与我们在高中数学课堂上学过的**数学归纳法**之间的相似之处。 回顾一下高中课本中定义的数学归纳法: > 一般地,证明一个与正整数 $n$ 有关的命题,可按下列步骤进行: > > 1. 归纳奠基:证明当 $n = n_0 \ (n_0 ∈ N⁺)$ > 2. 归纳步骤:以 “当 $n = k \ (k ∈ N⁺,k ≥ n_0,)$ 时命题成立” 为条件,推出 “当 $n = k + 1$ 时命题也成立”。 > > 只要完成这两个步骤,就可以断定命题对从 $n_0$ 开始的所有正整数 $n$ 都成立,这种证明方法称为**数学归纳法**。 > > > > 记 $P(n)$是一个关于正整数 $n$ 的命题,我们可以吧数学归纳法证明的形式改写如下: > > 条件:(1)$P(n_0)$ 为真;(2)若 $P(k)$ 为真,则 $P(k + 1)$ 也为真。 > > 结论:$P(n)$ 为真。 > > 在数学归纳法的两步中: > > 第一步验证了当 $n = n_0$ 是结论成立,即命题 $P(n_0)$ 为真。 > > 第二步是**证明一种递推关系**,实际上是要证明一个新命题:若 $P(k)$ 为真,则 $P(k + 1)$ 也为真。 > > 只要将这两步交替使用,就有如下关系成立: > $$ > P(n_0)⇒P(n_0 + 1)⇒P(n_0 + 2)⇒⋯ > $$ > > 从而完成证明。 > 这里附一张高中课本中的经典例题截图给你参考,我并不想深入讲解它:  仔细观察数学归纳法的这些步骤,我们会发现,它和递归方法的设计过程存在着非常相似的结构。 归纳奠基就类似于递归方法中的基础情况处理。在数学归纳法中,我们需要验证原命题在基础情况下是否成立;**而在递归方法中,我们需要直接给出基础情况下的正确答案。** 以 `factorial` 开头的条件判断为例: ```java if (n == 0) { return 1; } ``` 由定义可知 $0! = 1$,所以这个基础情况可以直接验证是正确的。**只要基础情况本身没有问题,递归就有了一个可靠的起点。** 归纳步骤则类似于递归方法中利用子问题结果解决当前问题的过程。 在数学归纳法中,我们假设 $P(n-1)$ 成立,然后利用这个假设证明 $P(n)$ 也成立; 这里的关键在于 “假设”。类似地,在递归方法中,我们同样**假设递归调用已经正确解决了子问题**,然后利用子问题的结果解决当前问题。 以 `factorial` 的这行代码为例: ```java return n * factorial(n - 1); ``` 我们假设递归调用`factorial(n - 1)` 已经正确解决了子问题。那么,我们就不需要继续追踪它内部的执行过程,只需要使用它返回的结果 $(n-1)!$ 。 在此基础上,再乘以 `n`,便可以得到当前问题 `factorial(n)` 的结果。 实际上,**只要子问题划分合理,并且每一层都能正确利用子问题的结果计算当前问题,递归就能得到正确的结果**。 > 如何划分子问题,是递归方法定义阶段首先需要思考的问题。之前我之所以说这是递归程序设计中**最难也是最关键**的一步,是因为子问题一旦确定,后续的一切工作,实际上都是围绕它展开的。 > 综上所述,现在我们终于可以回答最开始的问题了:**“递归的信任跳跃”为什么是合理的?** 因为在设计递归方法时,我们已经明确了原问题和子问题之间的关系。**数学归纳法中的归纳假设,为“暂时相信子问题已经正确解决”提供了一种很好的思维依据。** 因此,我们不必继续追踪递归调用的内部执行过程,而只需要关注当前层如何利用子问题的结果得到当前问题的答案。 所以,在设计和理解递归方法时,请不要深陷递归调用的怪圈,而是把注意力集中在三个问题上: - 原问题和子问题分别是什么?它们之间的关系是否合理? - 基础情况是否正确? - 假设子问题已经正确解决,当前层应该如何利用它的结果? 这就是递归“信任跳跃”的底气。 ## 一些经典的递归问题 到这里,我们已经理解了递归中的两个核心思维: - **将原问题转化为更小的同类子问题;** - **暂时相信递归调用能够正确解决子问题,并在此基础上解决当前问题。** 接下来,我们通过几个经典的递归问题,进一步理解和运用这两种思维。 ### 斐波那契数列 斐波那契(Fibonacci)数列是指这样一个数列:1,1,2,3,5,8,13,21,34,55,89…… 它的特点是:从第3项开始,每一项都等于前两项之和。 我们可以用如下递推公式来描述这个数列: \[ \begin{cases} F_1=1\\ F_2=1\\ F_n=F_{n-1}+F_{n-2},& n\ge 3 \end{cases} \] 请你实现一个程序,用于求解斐波那契数列中第 `n` 项的值。题目保证结果不超过 `Integer.MAX_VALUE`。 我们先不使用递归,而是用最直观的循环迭代来解决这个问题。 参考代码如下: ```java public int fibonacci(int n) { if (n == 1 || n == 2) { return 1; } int a = 1, b = 1; for (int i = 3; i <= n; i++) { int c = a + b; a = b; b = c; } return b; } ``` 现在,我们用递归的思维来思考。 首先,原问题是求解斐波那契数列的第 `n` 项。这道题的递推公式非常直观,它直接告诉了我们应该如何划分子问题。因此,原问题可以拆分为两个子问题:要得到第 `n` 项,需要先得到第 `n - 1` 项和第 `n - 2` 项,然后将二者相加。 因此,我们可以定义 `int fibonacci(int n);` 方法。这样,原问题和两个子问题都可以用同一个方法表示,即 `fibonacci(n)`、`fibonacci(n - 1)` 和 `fibonacci(n - 2)`。 接下来确定基础情况。当 `n` 为 1 或 2 时,斐波那契数列的结果都是 1,因此可以直接返回 1。 按照这个思路,本题的递归方法可以写成: ```java public int fibonacci(int n) { if (n == 1 || n == 2) { return 1; } return fibonacci(n - 1) + fibonacci(n - 2); } ``` 可以看到,这个递归版本的代码非常简洁。**但代码简单,并不意味着它的执行过程也同样简单。** 这个版本存在大量重复计算,运行效率并不高。我们将在后面的“递归方法的时间复杂度分析”中详细讨论这一问题。 ### 递归版归并排序 **归并排序(Merge Sort)** 由 **约翰·冯·诺依曼(John von Neumann)** 于1945年提出,是一种典型的分治算法。 归并排序的基本思路是: 1. 如果待排序区间只有一个元素,那么它天然有序,无需排序。 2. 将待排序区间一分为二。 3. **分别对这两个子区间进行归并排序。** 4. 将两个已经有序的子区间合并成一个有序区间。 归并排序中最核心的一点,就是**如何划分子问题**。它运用了“分而治之”的算法思想。然而这个思想我们目前还没有系统学习过,如果感兴趣,可以在理解递归之后再进一步学习。 如果之前没有接触过归并排序,那么仅仅让你根据“排序”这个问题自己去寻找子问题,可能并不容易。这也是为什么这里我直接给出了归并排序的基本思路。 找不到归并排序的子问题对于现在的我们并不重要。重要的是,经过前面几个递归问题的学习,你有没有发现:**原来自己已经能够读懂一个已经设计好的递归思路了。** 好的,现在我们回过头来。我相信你应该注意到了这句话:**分别对这两个子区间进行归并排序。** 这句话意味着,**原来的排序问题被划分成了两个更小的排序问题,而且这两个子问题仍然可以用归并排序来解决**。 现在,我们按照前面学习的递归方法设计流程,重新分析归并排序。 首先是递归方法定义。根据前面的分析,我们的递归方法除了需要传入待排序数组,还需要确定当前待排序的区间。这里我采用左闭右开区间 `[l, r)` 来表示待排序范围。 归并排序完成后不需要返回一个新的结果,因此方法不需要返回值。综上所述,我们可以将方法定义为 `mergeSort(int[] arr, int l, int r)`,表示对整数数组 `arr` 中 `[l, r)` 范围内的元素进行归并排序。 原问题是对整个数组进行排序,而子问题是分别对左半部分和右半部分进行排序。它们虽然待排序的区间不同,但本质上都是“对一个区间进行归并排序”,因此都可以用 `mergeSort(int[] arr, int l, int r)` 来表示。 接着是处理基础情况。当区间内的元素数量小于等于 1 时,区间本身就是有序的,无需继续排序。在我们选取的左闭右开区间 `[l, r)` 中,`r - l` 就是区间内的元素数量。因此,当 `r - l <= 1` 时,我们直接结束当前方法。 然后是递归调用。在进行递归调用之前,我们首先需要将区间 `[l, r)` 一分为二。 我们先求区间中点:`int mid = (r + l) / 2;` 然后将区间分成 `[l, mid)` 和 `[mid, r)` 两部分。随后分别对这两个子区间进行递归调用: ```java mergeSort(arr, l, mid); mergeSort(arr, mid, r); ``` 递归调用完成后,`[l, mid)` 和 `[mid, r)` 这两个子区间中的元素都已经分别有序。接下来,我们将这两个有序子区间合并,使 `[l, r)` 中的所有元素整体有序。 按照这个思路,我们不难写出归并排序的代码: ```java public void mergeSort(int[] arr, int l, int r) { // 为了简单考虑,这里不考虑 l, r 输入不合法的情况 if (r - l <= 1) { return; } // 避免 r + l 的整数溢出 int mid = (r - l) / 2 + l; mergeSort(arr, l, mid); mergeSort(arr, mid, r); // 在同一数组中合并两个有序子数组,需要借助临时数组 int[] temp = new int[r - l]; // i、j 分别指向左右两个有序子数组,k 指向临时数组 // 合并结果先暂存在 temp 中,最后再覆盖 [l, r) 范围 int i = l, j = mid, k = 0; while (i < mid && j < r) { if (arr[i] < arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } if (i < mid) { // 左半边区间有剩余 while (i < mid) { temp[k++] = arr[i++]; } } else { // 右半边区间有剩余 while (j < r) { temp[k++] = arr[j++]; } } System.arraycopy(temp, 0, arr, l, r - l); } ``` 就这样,我们就不经意间写出了递归版归并排序的代码。 我之所以强调“递归版”,显然是因为归并排序还存在非递归的实现。 **1948 年,Goldstine 与冯·诺依曼在相关报告中详细描述并分析了自底向上的归并排序。** 感兴趣的读者可以自行了解。 非递归版可以避免递归调用带来的额外开销,但实现通常更加复杂;对于现在的我们来说,递归版显然更加直观。 我们再回到这里的递归调用。现在你应该能体会到前面所说的“递归信任跳跃”有多重要了。对于 `mergeSort(arr, l, mid)` 和 `mergeSort(arr, mid, r)`,我们没有必要继续深入追踪它们内部是如何执行的,只需要相信它们能够正确地将对应的子区间排好序,然后直接利用这两个有序子区间完成合并。**这样一来,我们只需要关注当前层要做什么,就可以顺利写出这个递归方法。** 如果这时候非要“头铁”地深入追踪每一次递归调用的执行细节,那么归并排序的复杂程度,可就远远超过前面的递归求阶乘了。 ### 快速排序 **快速排序(Quick Sort)** 由 **托尼·霍尔(Tony Hoare)** 于1960年提出,同样也是一种典型的分治算法。它和归并排序一样,都是将一个排序问题转化成更小的排序问题,但划分子问题的方式有所不同。 快速排序的基本思路是: 1. 从区间中选择一个元素作为基准值(`pivot`)。 2. 通过一次分区操作,将基准值移动到一个合适的位置,使得它左侧的元素都不大于它,右侧的元素都不小于它。此时,基准值已经处于排序后的最终位置。 3. **分别对基准值左侧和右侧的两个子区间进行快速排序。** 和归并排序类似,我们一开始其实也无法自己划分出快速排序的子问题。但是通读完它的基本思路后,我们就能够发现:**原来的排序问题被划分成了两个更小的排序问题,而这两个子问题仍然用快速排序来解决。** 而且,快速排序划分子问题的方式确实很有意思:**它先通过一次分区操作确定一个元素的最终位置,再将剩下的问题交给两个更小的子问题。** 现在,我们按照前面学习的递归方法设计流程,重新分析快速排序。 快速排序的方法参数仍然是待排序数组和待排序区间,因此方法签名与归并排序类似,这里我直接给你:`quickSort(int[] arr, int l, int r)`。 紧接着就是快速排序中最难也是最关键的一步——分区操作。只有分区结束后,我们才能划分子问题。假设 `pivot` 最后被移动到了下标 `k`,那么数组中除 `pivot` 外,剩下的无序部分就变成了 `[l, k)` 和 `[k + 1, r)` 两个子区间。接下来,我们只需要分别对这两个子区间继续使用快速排序即可。 首先,我们要在待排序区间 `[l, r)` 中选出一个 `pivot`。快速排序并没有严格规定 `pivot` 必须选择哪一个元素,因此可以采用不同的选择策略。这里我们采用一种比较常见的策略:随机选择 `pivot`。 因此,我们需要使用 JDK 内置的随机数库,在 `[l, r)` 范围内随机生成一个下标。 假设随机到的下标为`k`,接下来又该如何分区呢?快速排序的分区操作有多种实现方式,这里我采用其中一种,具体思路参考了这个视频:[【排序算法精华3】快速排序 (上)](https://www.bilibili.com/video/BV1rV411j7f6/?share_source=copy_web&vd_source=5380fa0514d9710134c69943bf7dc51c)。 首先,我们先将 `pivot` 元素和最后一个元素交换位置。这样,`pivot` 就被暂时移出了搜索区间,接下来只需要在 `[l, r - 1)` 中进行探索。 此时,我们的搜索区间就变为`[l, r - 1)`。 视频中的思路运用了双指针技巧,定义两个“指针”,一开始都指向 `l` 处: ```java int i = l, j = l; ``` 这是为了把区间 `[l, r - 1)` 划分成 3 部分: - `[l, i)` 区间内的元素都不大于 `pivot`; - `[i, j)` 区间内的元素都不小于 `pivot`; - `[j, r - 1)` 区间表示尚未探索的区域。 结合图来看会更容易理解:  `j` 在从左往右探索的过程中: - 若`arr[j] < pivot`,则交换下标 `i` 和 `j` 处的两个元素,然后将 `i` 向右移动一位。这样,`arr[j]` 就被放入了 `[l, i)` 区间。 - 否则,不做任何操作。随着 `j` 向右移动,这个元素就会留在 `[i, j)` 区间中。 当 `j` 探索完整个 `[l, r - 1)` 区间后,下标 `i` 处的元素就是第一个不小于 `pivot` 的元素。此时,交换 `i` 和 `r - 1` 处的两个元素,就可以将 `pivot` 放到最终位置。 最后,分别对 `[l, i)` 和 `[i + 1, r)` 这两段区间继续进行快速排序。这样,整个区间 `[l, r)` 就排好序了。 按照这个思路,我们不难写出快速排序的代码: ```java public class Solution { private static final Random random = new Random(); public void quickSort(int[] arr, int l, int r) { if (r - l <= 1) { return; } // 在 [l, r) 内随机一个下标 k,arr[k] 作为 pivot 。 // random.nextInt(n) 生成一个 [0, n) 之间的一个随机整数 // 所以 random.nextInt(r - l) 得到的是 [0, r - l) // 在此基础上加上 l,将范围平移为 [l, r) int k = random.nextInt(r - l) + l; swap(arr, r - 1, k); int pivot = arr[r - 1]; int i = l, j = l; while (j < r - 1) { if (arr[j] < pivot) { swap(arr, i, j); i++; } j++; } swap(arr, i, r - 1); quickSort(arr, l, i); quickSort(arr, i + 1, r); } /** * 交换 arr[i] 和 arr[j] */ private void swap(int[] arr, int i, int j) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } ``` 就这样,我们又写出了一个递归版的经典算法——快速排序。 回过头来看,快速排序和归并排序虽然都采用了分治的思想,但它们处理当前问题的方式却很不一样。归并排序先划分子问题,再分别解决,最后合并结果;快速排序则先通过分区操作确定 `pivot` 的最终位置,再将剩下的两个子区间交给递归调用。 而在这个过程中,我们又一次用到了“递归信任跳跃”:不需要追踪递归调用的内部执行过程,只需要相信它能够正确解决对应的子问题,然后专注于当前层需要完成的工作。 到了这里,我们已经可以开始把这种思维迁移到其他问题上:**找到合适的子问题,解决当前层的问题,然后把剩下的工作交给递归。** ### 汉诺塔问题 **汉诺塔(Tower of Hanoi)** 问题是法国数学家 **爱德华·卢卡斯(Édouard Lucas)** 于 1883 年提出的一个经典数学谜题,也是递归中的经典问题。 为了给这个谜题增添神秘色彩,他还设计了一个著名的传说故事: 传说,在印度贝拿勒斯的梵天寺里,有一块黄铜板,上面竖立着三根金刚石柱。创世时,梵天将 64 片纯金盘按照从大到小的顺序,从下到上套在其中一根柱子上。僧侣们需要将这 64 片金盘全部移动到另一根柱子上,并且必须遵守两条规则: 1. **一次只能移动一片金盘。** 2. **大盘不能压在小盘上面。** 传说还有一个古老的预言:当僧侣们完成这项任务时,世界将在轰鸣中毁灭。 > 为什么会这样?我们最后揭晓。  我们先去掉故事中的神话色彩,专注于这个问题本身: 有 3 根柱子,我们分别称它们为 A、B、C。有 `n` 个大小不同的圆盘,它们按照从大到小的顺序叠放在 A 柱子上。 现在,我们需要将这些圆盘全部移动到 C 柱子上,同时遵循两条游戏规则: 1. 一次只能移动一个圆盘。 2. 大圆盘不能放在小圆盘上面。 刚开始看到这个问题时,我们可能很难直接看出递归在哪里。**所以这一次,我们先不急着找递归,而是从最简单的情况开始,一步一步自己摸索。** 首先,我们从最简单的情况开始:只有一个圆盘时,该怎么做?  显然,只有一个圆盘时,我们直接将其从 A 移动到 C 就好了。 然后稍微增加一点难度:如果有两个圆盘,该怎么做?  这其实也难不到你。 我们知道,最终需要先将最底下的大圆盘移动到 C 上。但在这之前,必须先把上面的那个小圆盘移走,否则大圆盘根本没有办法移动。 我们先把最上面的圆盘移动到 B 上,给大圆盘腾出空间;然后将大圆盘从 A 移动到 C;最后,再把小圆盘从 B 移动到 C。这样,两个圆盘就都移动到 C 上了。 现在难度再上一个台阶:如果有三个圆盘,该怎么做?  这次看起来复杂了一些。我们还是先想办法把最下面最大的圆盘移动到 C。 但和刚才一样,在移动它之前,必须先把上面的两个圆盘移走。那这两个圆盘应该移动到哪里?显然,我们可以先把它们移动到 B 上。 **至于这两个圆盘具体怎么移动,其实我们刚刚已经解决过了:不就是刚才的“移动两个圆盘问题”吗?只不过这一次,我们需要**借助柱子 C,将这两个圆盘从 A 移动到 B。 为便于后续叙述,这里我给盘子从上到下依次编号 1、2、3。 首先,我们要将 1 从 A 移动到 C,这样就给 2 腾出了空间。 然后将 2 从 A 移动到 B。 最后将 1 从 C 移动到 B。  这样,3 就可以顺利从 A 移动到 C 了。 接下来,就是将 B 上的两个圆盘移动到 C。**不知你有没有发现,我们又遇到了刚才已经解决过的问题。** 这时候,递归的影子就开始出现了。 这个问题的步骤我就不继续展开了,相信你也能够自己想明白。 现在说递归还为时尚早。我们继续加大难度:如果有四个圆盘,该怎么做?  这一次,我们还是要先把最下面最大的圆盘移动到 C。在移动它之前,必须先把上面的 3 个圆盘移走。 而移动 3 个圆盘的问题,我们刚刚已经解决过了。只不过这次,我们需要**借助 C 柱,将这三个圆盘从 A 移动到 B。**  这样就可以把最大的圆盘顺利移动到 C 上。然后,再**借助 A,将 B 上的 3 个圆盘移动到 C 上**,整个问题就解决了。 请仔细回头看一下,我们刚才到底做了什么? 为了移动 4 个圆盘,我们先解决了一个“移动 3 个圆盘”的问题;移动完最大的圆盘后,又解决了一次“移动 3 个圆盘”的问题。 而移动 3 个圆盘,本身又可以拆成两个“移动 2 个圆盘”的问题。 这样继续往下拆,最终都会落到最简单的“移动 1 个圆盘”上。 所以,无论有多少个圆盘,解决汉诺塔问题的思路其实都是一样的。我们把这个过程总结一下: 假设现在有 `n` 个圆盘,我们需要将它们从 A 移动到 C,那么整个过程就可以分成三步: 1. 借助 C,将前 `n - 1` 个圆盘从 A 移动到 B。 2. 将最大的第 `n` 个圆盘直接从 A 移动到 C。 3. 借助 A,将 B 上的 `n - 1` 个圆盘移动到 C。 不过,仔细观察刚才的三步,你会发现,第 1 步和第 3 步其实是在重复做同一件事情。 它们都是将 `n - 1` 个圆盘从一根柱子移动到另一根柱子,只不过起点、终点和辅助柱子发生了变化。 既然柱子的位置会发生变化,我们就不能把 A、B、C 写死,而应该**描述它们各自的角色**。 一根柱子是圆盘开始时所在的位置,我们称它为**起始柱**;一根柱子是圆盘最终要去的位置,我们称它为**目标柱**;剩下的一根,就是帮助我们完成移动的**辅助柱**。 有了这三个角色之后,我们再回头看看刚才的问题。 如果让一个方法来完成“移动若干个圆盘”这件事情,它需要知道两个方面的信息: 1. 现在有多少个圆盘需要移动? 2. 这些圆盘要从哪里移动到哪里,中间借助哪根柱子? 因此,这个方法需要 4 个参数: - `n`:需要移动的圆盘数量 - `from`:起始柱 - `aux`:辅助柱 - `to`:目标柱 而且我们只是移动圆盘,并不需要返回值,因此这个方法的声明可以写成: ```java void hanoi(int n, char from, char aux, char to); ``` 有了这个方法,无论是刚才的第 1 步,还是第 3 步,我们都可以用同一个方法来描述。 接着是处理基础情况。当 `n == 1` 时,我们就不需要再继续拆分了,直接将这个圆盘从 `from` 移动到 `to` 即可。这里,我们只需要输出一句移动信息。 然后是递归调用。第一次递归调用,是借助 `to`,将 `from` 上的 `n - 1` 个圆盘移动到 `aux`,即`hanoi(n - 1, from, to, aux)`。 接着,将 `from` 上最大的那个圆盘移动到 `to`。 最后,再借助 `from`,将 `aux` 上的 `n - 1` 个圆盘移动到 `to`,即`hanoi(n - 1, aux, from, to)`。 这样,两个规模为 `n - 1` 的子问题都解决了,中间最大的那个圆盘也已经移动到目标柱,原问题自然也就解决了。 按照这个思路,我们不难写出汉诺塔的代码: ```java public void hanoi(int n, char from, char aux, char to) { if (n == 1) { System.out.printf("将圆盘从 %c 移动到 %c", from, to); return; } hanoi(n - 1, from, to, aux); System.out.printf("将圆盘从 %c 移动到 %c", from, to); hanoi(n - 1, aux, from, to); } ``` 就这样,汉诺塔这个看起来非常复杂的问题,也被我们用一个很简单的递归方法解决了。 这里的关键,依然是我们前面反复强调的“递归信任跳跃”。我们不需要知道 `hanoi(...)` 内部究竟做了什么,**只需要相信它能够正确完成自己的任务,然后专注于当前这一层需要完成的步骤**。 还记得前面我说过: > 然而,并不是所有问题都像求阶乘这样容易直接解决。**有些问题换成递归的思路,反而会更加简单。** 汉诺塔就是一个很好的例子。面对这样的问题,你很难直接规划出所有的移动步骤。如果你“头铁”,非要一步一步地把所有移动过程都规划出来,反而很容易陷入繁琐的细节。 而换成递归的思路,只需要不断把问题拆成规模更小的相同问题,整个过程就变得清晰起来。当然,对于汉诺塔问题来说,我们要发现这个递归思路本身也费了不少功夫。最终,我们只用了几行代码,就实现了 `hanoi` 的核心逻辑。 当你真正掌握了这种思考方式,递归代码中那些一层套一层的调用,就不再需要逐层追踪。站在当前这一层,相信递归能够完成它该完成的事情,剩下的,就交给递归自己。**拨开层层递归调用的迷雾,你真正需要看清的,其实只有眼前这一层。** 还记得文章开头的那个传说吗? > 当僧侣们完成这 64 个圆盘的移动时,世界将在轰鸣中毁灭。 数学家们其实已经推导出了以下结论(当然,你自己去推也费不了多大功夫,就是找规律问题):对于 $n$ 个圆盘,汉诺塔最少需要移动 $2^n - 1$ 次。 那么 64 个圆盘呢?我们代入计算一下: $$ 2^{64}-1 = 18,446,744,073,709,551,615 $$ 如果每秒移动一次,需要的时间将超过 **5800 亿年**!! 5800 多亿年是什么概念? 宇宙至今也不过约 **138 亿年**,地球的年龄更只有约 **45 亿年**。即使从宇宙诞生的那一刻开始,每秒移动一个圆盘,直到今天,这场汉诺塔游戏都还远远没有结束。 所以,开头那个“世界毁灭”的传说,离我们实在太遥远了。 **与其为那些遥不可及的事情杞人忧天,不如把时间还给自己,留给眼前真正重要的人和事。毕竟,属于我们的时间可没有 5800 多亿年。珍惜当下,也许才是这个古老谜题留给我们最有意思的启示。** ## 递归方法的时间复杂度分析(学习中)
Notus 正式发布:我做了一个真正属于自己的 AI 知识库
## 前言 大家好,我是汉堡🍔。 上一篇文章[我的第一个 Vibe Coding 项目正式上线,Notus——原生 AI 笔记应用,且完全开源](https://www.codefather.cn/post/2050528553144963073) 介绍了用 AI 做了 20 天 Notus 的初版功能功能。经过 4 个多月的使用和不断打磨 ,[Notus](https://notus.hejiajun.com/) 正式版发布啦,Web 端和 Electron 桌面端都已可用。 从第一版开始,这个项目的目标就没变过:彻底替代 Typora,做原生 AI 的 Markdown 工具。所以除了一个好用的编辑器,还得有真能帮上忙的 Agent 什么叫原生 Agent 笔记?简单说,笔记里任何地方都能随手把 Agent 叫出来干活:选中多篇文章让它改,或者给一个目标让其去调研并形成文档。 如果你已经受够把笔记交给云端,又不想放弃 Agent 能力,那先做一件事就够了:去 [GitHub 点个 star 并下载](https://github.com/dnwwdwd/Notus)。开源项目不投广告,一个 star 就是最实在的支持。 下面按大家最关心的问题,把正式版真正落地的东西过一遍。 > 官网:https://notus.hejiajun.com/ > 数据不出本地,AI 随时待命。Web 端 + macOS + Windows 三端已可下载,完全开源、完全免费。 ## 一、彻底替代 Typora:原生 Markdown 编辑器 编辑器是笔记应用的入口,入口不顺手,后面所有 AI 能力都是空谈,所以这块先补到位。Typora 具有的功能,Notus 基本都有了,所见即所得,告别双栏预览。具体来说: - **大纲导航**:标题树实时预览,一键定位任意章节,超过 100 个文件的库也不怕 - **保存即索引**:改一处,知识库同步更新,不用手动操作 - **自定义行列的 Markdown 表格插入**:终于不用手动数竖线了 - **标题与正文分开编辑**:系统 Frontmatter 不再污染正文区域,干净 - **一键复制整篇 Markdown 笔记**:分享、迁移都方便 什么?都 2026 年了,还在用 Typora? ## 二、多端支持:Web + Electron 桌面端 这次正式支持三个平台: | 平台 | 说明 | | --- | --- | | Web 端 | 浏览器直接访问,随时随地 | | macOS(Apple Silicon) | 下载 `arm64` 版本 | | Windows x64 | 下载 `exe` 安装包 | 桌面端新增了系统级快捷键 `Command+K` / `Ctrl+K`,应用在后台运行时也能直接唤起主窗口并打开搜索,再也不用切窗口找文件了。 另外,懒猫微服用户也有专属 `.lpk` 打包,兼容懒猫运行时。 ## 三、知识库:零幻觉,来源可查 把 AI 知识库笔记最要紧的两件事做到了:不瞎编、能溯源。而且数据全程在你自己的设备上。 - **零幻觉**:命中阈值以下直接拒答,不瞎编 - **来源可查**:每条回答附出处,一键跳转原文 - **精准检索**:语义 + 关键词双路召回,换个说法也能找到;支持全文档匹配与置顶文档来源匹配 把你的 PDF、DOCX、Markdown、TXT 全部导进来,Notus 就是你的私人文档助理  ## 四、AI 创作:跨文件修改,越用越像你 不只是改一段——Notus 的 AI 能跨文件修改、生成 Diff 预览,支持自动和手动两种应用模式。改之前先看清楚动了哪里,确认了再写入,AI 不会偷偷改坏你的笔记。 - **跨文件修改**:AI 可以同时读取、修改多个笔记文件,不用一篇一篇手动操作 - **Diff 预览**:每次修改都生成红删绿增的对比视图,改了什么一目了然 - **自动 / 手动两种应用模式**:自动模式直接写入,手动模式等你确认再落盘,按需切换 - **自动学习写作风格**:自动从历史文章匹配写作风格,用得越多越像你 ## 五、AI Agent 工作流 Notus 的核心差异点:**把 AI 从「对话框」变成「执行器」**。它不是等你把问题描述清楚才动手,而是能自己拆解任务、自己往下推。 - **多轮 Agent Loop**:任务可拆解为多轮执行,每轮基于上轮反馈智能调整,适合长文档、多步骤任务 - **块级 Diff 预览**:每次调整都有清晰的差异对比,改了什么一目了然 - **Skill 扩展**:把一套写作流程存成技能,之后一键复用,不用每次重新描述 - **MCP 扩展**:直接读取外部服务,比如在线搜索、GitHub、Figma,让 MCP 上的服务都成为 AI 的工具 - **上下文自动管理**:长文写作自动注入历史对话、前置块、当前块上下文,避免上下文撑爆 ### 双模型协议支持 - 支持 Embedding 与 LLM 两种模型分开配置,灵活适配不同需求 - 支持任意 OpenAI 兼容接口 - 支持图片理解模型(VLM)  ### Skill: 无限扩展能力 把一套写作流程存成技能,之后一键复用,不用每次重新描述  ### MCP: 无限扩展边界 直接读取外部服务,比如在线搜索、GitHub、Figma,让 MCP 上的服务都成为 AI 的工具  ## 六、文件修改预览:改得安心,用得放心 AI 帮你改东西,最怕的是它偷偷动你别的地方。所以每次 AI 修改都会弹出 Diff 预览窗口,红删绿增,确认后才写入文件。AI 不会偷偷改坏你的笔记。  ## 七、多图床支持 - **图片自动上传**:默认上传到本地;支持配置阿里云 OSS、腾讯云 COS、Cloudflare R2 图床。 - **图片查看**:点击可放大查看,支持拖拽缩放 ## 八、智能 Mention 在编辑器输入 @ 可以提及任意笔记,AI 能直接读取被提及文件的完整内容,作为上下文来理解你的诉求。此外,还可以通过 @ 调用指定的 Skill,处理特定场景需求。 ## 九、附件上传与联网搜索 - **多格式附件**:支持 PDF、DOCX、Markdown、TXT 等 - **联网搜索**:内置搜索接口,需要实时信息时可开启 ## 我们的承诺不变 - **完全开源**:所有代码公开在 [GitHub](https://github.com/dnwwdwd/Notus),欢迎 PR 和二次开发 - **完全免费**:不设付费墙,不搞订阅制,所有功能全部开放 - **数据完全本地化**:你的笔记永远在你自己的设备上,不依赖云端,不用担心隐私泄露 ## 写在最后 这个项目从第一行代码到现在,20 天 Vibe Coding,功能已经相当完善。当然还有很多不足,欢迎在 [GitHub](https://github.com/dnwwdwd/Notus) 提 Issue 或 PR。欢迎访问产品官网 <https://notus.hejiajun.com/>,也欢迎在文章评论区留言。 如果你觉得这个方向不错,麻烦帮两件事: - ⭐ 去 [GitHub](https://github.com/dnwwdwd/Notus) 点个 star,让更多找笔记工具的人看到它 - ⬇️ 下载装一个,真的用上几天,包你爱上它。 下个版本见!
我在OD转正了,工号00开头
OD可以转正,但不是每个人都能转,也不是那么容易转。 这篇文章写给正在OD考虑转正的人,或者考虑来OD但听说"转正概率低"的人。  ### 我的背景 普通本科,毕业后在小公司做了3年Java。后来通过社招进了OD,做了2年后成功转正。 转正后工号00开头,和正式员工一样。 ### 为什么想转正 在OD的两年,我其实过得挺舒服的。 工作节奏稳定,技术有成长,同事关系也不错。但到了第二年,我开始考虑转正了。 原因很简单:想要更稳定的发展和更高的天花板。 OD的职级体系和正式员工是两套体系。你在OD做得再好,职级天花板是看得到的。而且很多核心项目、晋升机会,正式员工优先。 不是说OD不能做,而是转正后的发展路径更清晰。 ### 转正的条件 我所在的部门,转正需要满足几个硬性条件: 1. 绩效要求 连续两个考核周期绩效在B+以上(部门不同可能有差异)。这个不是随便给的,要看你的实际产出和项目贡献。 2. 岗位空缺 转正需要有正式员工的HC(headcount)。这个不是你能控制的,要看部门业务发展和人员流动。 3. 技术能力 转正面试的技术标准和社招正式员工基本一致。数据结构、算法、系统设计、项目经验,都要扎实。 4. 主管推荐 你的主管需要愿意推荐你转正。这个取决于你的工作表现、团队协作和沟通能力。 ### 我的转正过程 第一步:和主管沟通(第18个月) 在OD第18个月的时候,我主动找主管聊了一次转正的事。 主管的态度很坦诚:"你的表现不错,但转正需要HC,这个我不能保证。你可以先准备着,有HC了我优先推荐你。" 这次沟通很重要——至少让我知道主管是支持我的,而不是敷衍。 第二步:准备技术面试(第20-22个月) 转正面试的技术标准和社招一样,所以我提前3个月开始准备: 算法题:LeetCode刷了200多道,重点刷中等难度 系统设计:看了几本经典书,自己画了几个系统的架构图 项目复盘:把在OD做的几个核心项目重新梳理了一遍,准备面试时的案例 八股文:操作系统、网络、设计模式、数据库这些重新过了一遍 说实话,准备的过程挺痛苦的。白天上班,晚上刷题,周末还要模拟面试。但没办法,转正的机会不是年年有。 第三步:转正面试(第24个月) 面试流程:技术一面(30分钟)→ 技术二面(45分钟)→ 综合面(30分钟)。 技术一面:面试官是我所在组的另一个leader。问了一些算法题和系统设计,难度中等。我准备得比较充分,答得还行。 技术二面:面试官是部门的技术总监。这一面更难,问了一些开放性的问题,比如"如果让你设计一个高并发的订单系统,你会怎么考虑"。我用了一个在OD做过的真实项目来回答,结合了自己的思考。 综合面:面试官是HR和部门经理。主要问了一些职业规划、团队协作、沟通能力相关的问题。这一面相对轻松,主要是看你的稳定性文化匹配度。 面试完一周后,主管告诉我:"转正通过了,等HR走流程。" 对了。想一起共事的,前后端-测试均有机会,全国一线及双线城市坑位任选,待遇和稳定性还不错,感兴趣可以看看。 ### 转正后的变化 1. 工号 转正后工号00开头,和正式员工一样。之前OD的工号是06开头。 这个细节挺有意思的——工号变了,但工作内容其实没变。 2. 薪资 转正后薪资涨了一波,大概是原来的1.3倍。这个看个人谈判能力和部门政策,不同的人可能不一样。 3. 福利 正式员工的福利比OD好一些,比如股票期权、年终奖系数、培训资源等。但差异没有想象中那么大——OD的福利其实已经不错了。 4. 发展路径 这个是最关键的。转正后职级体系变了,天花板打开了。以前在OD只能到某个级别,现在可以往更高的级别走。 而且很多核心项目、技术委员会、晋升评审,正式员工才有资格参与。 ### 转正的真实难度 说实话,转正不是那么容易。 我所在的部门,过去一年有5个OD员工,2个转正,3个没转。没转的原因: 1个绩效不够 1个部门没HC 1个面试没过 所以转正概率大概是40%左右(部门差异很大)。 ### 关键因素: 绩效是硬门槛。绩效不够,其他都白搭。 HC是硬约束。没HC,你表现再好也转不了。 面试是硬实力。转正面试和社招一样严,不能因为是内部就放水。 主管推荐是软因素。主管愿意帮你争取,机会会大很多。 ### 给想转正的人几点建议 早点沟通。别等到合同快到期了才问转正的事。提前半年到一年和主管沟通,让他知道你的意愿。 绩效要稳住。连续两个周期B+以上是基本要求,最好能拿A。绩效是硬门槛,没有商量余地。 技术要扎实。转正面试和社招一样,不要因为是内部就放松准备。算法、系统设计、项目经验,都要过硬。 主动争取机会。在OD期间,主动申请参与核心项目、技术攻关、架构优化这类有挑战的任务。这些是转正面试时的硬通货。 心态放平。转正不是唯一出路,OD的经历本身就是有价值的。即使没转成,跳槽去其他大厂也是一条路。 ### 最后 我现在转正半年了,工作内容和之前差不多,但发展路径更清晰了。 回头看OD的两年,我不后悔。它让我积累了技术深度,也让我有了转正的底气。 如果你也在OD,别焦虑,先把手头的事情做好。该转正的时候,你自然会有机会。 就算没转成,OD的经历也不会白费——它让你看到"好的工程实践"是什么样的,让你跟一群优秀的人共事,让你在大体量业务里积累真正的实战经验。 这些东西,无论你去哪里,都是有用的。
七夕之别人鹊桥相会,我们线上调通💻
今天七夕,朋友圈一半鲜花晚餐,一半在工位改 bug。 都说程序员不懂浪漫,其实我们的浪漫写在逻辑里。 别人说情话,我们写循环: ```js while life: love += 1 ``` 别人奔赴鹊桥,我们排查接口超时; 别人双向奔赴,我们苦苦等待回调; 约会临时被打断叫遗憾,我们叫线上紧急回滚。 有对象的同行,祝你们约会不被钉钉打断,约会前 bug 全部清零,不用吃着饭还掏出手机看告警。 单身的兄弟们也不必 emo,至少你的代码不会跟你闹脾气,只要逻辑写对,它永远给你预期输出。爱情是玄学,代码不会骗你。 <<<顺便说句,技术大厂,前后端-测试[机会](https://jsj.top/f/o38ijj),一线双一线城市坑位充足,感兴趣可以看看这个机会~ 见过太多故事:提前订好晚餐,线上突发故障,一句 “等我半小时” 最后变成深夜,饭凉了,约会也泡汤。不是不懂珍惜,是线上问题不会挑日子,责任扛在肩上,很多时候身不由己。 浪漫不一定是鲜花礼物。 是 try 之后有 catch,是异常有人兜底,是长期稳定的兼容,是一次次迭代之后,依旧愿意向下兼容彼此。 写代码教会我们:好的关系,允许报错,但要有重试机制。 今晚不管是陪爱人,还是陪键盘。 愿有情人接口永远连通,没有跨域,没有超时。 愿敲代码的我们,bug 少一点,幸福多一点。 七夕快乐,各位开发者✨
智能协同云图库-进入编辑报错解决
### 问题复现 点击进入编辑,页面没变化  F12 打开控制台,两条消息,连接已建立,然后又连接已断开  后端控制台报错,`LocalDataTime` 没有办法序列化  因为 `user` 的时间字段被我改成了 `LocalDateTime`,而 `LocalDateTime` 是需要手动添加序列化器的,因为异常最终导致 WebSocket 关闭。 ### 解决 **使用 `Spring` 提供的 `ObjectMapper` 代替自己new** ```java /** * 复用 Spring 管理的 ObjectMapper,统一支持 Long 和 LocalDateTime 序列化。 */ @Resource private ObjectMapper objectMapper; ```  ```java /** * 将响应信息广播给 集合里的session * * @param pictureId * @param pictureEditResponseMessage * @param excludeSession * @throws Exception */ private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws Exception { Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId); if (CollectionUtils.isEmpty(sessionSet)) { return; } // 使用 Spring 管理的 ObjectMapper,避免裸 ObjectMapper 无法处理 LocalDateTime。 String message = objectMapper.writeValueAsString(pictureEditResponseMessage); for (WebSocketSession session : sessionSet) { // 排除掉 session if (session.equals(excludeSession)) { continue; } if (session.isOpen()) { sendTextMessage(session, message); } } } /** * 向当前连接发送错误消息。 */ public void sendErrorMessage(WebSocketSession session, User user, String errorMessage) throws Exception { PictureEditResponseMessage responseMessage = new PictureEditResponseMessage(); responseMessage.setType(PictureEditMessageTypeEnum.ERROR.getValue()); responseMessage.setMessage(errorMessage); if (user != null) { responseMessage.setUser(userService.getUserVO(user)); } String message = objectMapper.writeValueAsString(responseMessage); sendTextMessage(session, message); } /** * WebSocketSession 不保证并发发送安全,统一同步发送。 */ private void sendTextMessage(WebSocketSession session, String message) throws Exception { if (session == null || !session.isOpen()) { return; } synchronized (session) { if (session.isOpen()) { session.sendMessage(new TextMessage(message)); } } } ``` 接收前端发来的消息,也要 使用 `objectMapper` 转化为对象 ```java /** * 收到前端的信息后处理 * * @param session * @param message * @throws Exception */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { User user = (User) session.getAttributes().get("user"); Long pictureId = (Long) session.getAttributes().get("pictureId"); PictureEditRequestMessage pictureEditRequestMessage; try { // TextMessage#getPayload() 是 JSON 字符串,必须使用 JSON 反序列化。 pictureEditRequestMessage = objectMapper.readValue( message.getPayload(), PictureEditRequestMessage.class ); } catch (JsonProcessingException e) { log.warn("图片编辑消息解析失败,sessionId={},payload={}", session.getId(), message.getPayload(), e); sendErrorMessage(session, user, "消息格式错误"); return; } PictureEditMessageTypeEnum messageTypeEnum = PictureEditMessageTypeEnum.getEnumByValue(pictureEditRequestMessage.getType()); if (messageTypeEnum == null) { sendErrorMessage(session, user, "消息类型错误"); return; } // 生产消息 pictureEditEventProducer.publishEvent( pictureEditRequestMessage, session, user, pictureId ); } ``` ### 完整代码 ```java import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.my.picturesystembackend.manager.websocket.disruptor.PictureEditEventProducer; import com.my.picturesystembackend.manager.websocket.model.PictureEditActionEnum; import com.my.picturesystembackend.manager.websocket.model.PictureEditMessageTypeEnum; import com.my.picturesystembackend.manager.websocket.model.PictureEditRequestMessage; import com.my.picturesystembackend.manager.websocket.model.PictureEditResponseMessage; import com.my.picturesystembackend.model.entity.User; import com.my.picturesystembackend.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import javax.annotation.Resource; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * 图片编辑处理 */ @Component @Slf4j public class PictureEditHandler extends TextWebSocketHandler { @Resource private UserService userService; @Resource private PictureEditEventProducer pictureEditEventProducer; /** * 复用 Spring 管理的 ObjectMapper,统一支持 Long 和 LocalDateTime 序列化。 */ @Resource private ObjectMapper objectMapper; // 当前正在编辑的图片的用户:key:pictureId value: 当前正在编辑的 userId private static final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>(); // 保存连接的所有会话,key: pictureId, value: 用户会话集合 private static final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>(); /** * 连接建立成功后保存信息 * * @param session * @throws Exception */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); // 保存用户信息 User user = (User) session.getAttributes().get("user"); Long pictureId = (Long) session.getAttributes().get("pictureId"); // session 加到集合中 pictureSessions.computeIfAbsent(pictureId, key -> ConcurrentHashMap.newKeySet()).add(session); // 发送广播信息,通知在这个集合的所有用户,who 加进来了,相当于加入房间 PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(); pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue()); String message = String.format("用户 %s 加入编辑", user.getUserName()); pictureEditResponseMessage.setMessage(message); pictureEditResponseMessage.setUser(userService.getUserVO(user)); // 广播消息 broadcastToPicture(pictureId, pictureEditResponseMessage); } /** * 收到前端的信息后处理 * * @param session * @param message * @throws Exception */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { User user = (User) session.getAttributes().get("user"); Long pictureId = (Long) session.getAttributes().get("pictureId"); PictureEditRequestMessage pictureEditRequestMessage; try { // TextMessage#getPayload() 是 JSON 字符串,必须使用 JSON 反序列化。 pictureEditRequestMessage = objectMapper.readValue( message.getPayload(), PictureEditRequestMessage.class ); } catch (JsonProcessingException e) { log.warn("图片编辑消息解析失败,sessionId={},payload={}", session.getId(), message.getPayload(), e); sendErrorMessage(session, user, "消息格式错误"); return; } PictureEditMessageTypeEnum messageTypeEnum = PictureEditMessageTypeEnum.getEnumByValue(pictureEditRequestMessage.getType()); if (messageTypeEnum == null) { sendErrorMessage(session, user, "消息类型错误"); return; } // 生产消息 pictureEditEventProducer.publishEvent( pictureEditRequestMessage, session, user, pictureId ); } /** * 进入编辑状态 * * @param pictureEditRequestMessage * @param session * @param user * @param pictureId */ public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { // 一张图片同时只能有一个用户在编辑 // 判断是否有其他人在编辑 if (!pictureEditingUsers.containsKey(pictureId)) { // 加入编辑 pictureEditingUsers.put(pictureId, user.getId()); // 发送 广播 通知其他用户我已经进入编辑状态 PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(); pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue()); pictureEditResponseMessage.setMessage(String.format(" %s 开始编辑图片", user.getUserName())); pictureEditResponseMessage.setUser(userService.getUserVO(user)); broadcastToPicture(pictureId, pictureEditResponseMessage); } } /** * 处理编辑动作 * * @param pictureEditRequestMessage * @param session * @param user * @param pictureId */ public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { // 判断当前正在编辑的用户是不是自己 Long currentEditUserId = pictureEditingUsers.get(pictureId); String editAction = pictureEditRequestMessage.getEditAction(); PictureEditActionEnum pictureEditActionEnum = PictureEditActionEnum.getEnumByValue(editAction); if (pictureEditActionEnum == null) { return; } if (currentEditUserId != null && currentEditUserId.equals(user.getId())) { // 是本人发送编辑通知 PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(); pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue()); pictureEditResponseMessage.setMessage(String.format("%s 执行了 %s 操作", user.getUserName(), pictureEditActionEnum.getText())); pictureEditResponseMessage.setEditAction(editAction); pictureEditResponseMessage.setUser(userService.getUserVO(user)); // 广播给处了自己以外的其他用户 broadcastToPicture(pictureId, pictureEditResponseMessage, session); } } /** * 退出编辑 * * @param pictureEditRequestMessage * @param session * @param user * @param pictureId */ public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception { // 校验当前正在编辑的用户是不是自己 Long currentEditUserId = pictureEditingUsers.get(pictureId); if (currentEditUserId != null && currentEditUserId.equals(user.getId())) { pictureEditingUsers.remove(pictureId); // 发送消息通知 PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(); pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue()); pictureEditResponseMessage.setMessage(String.format("%s 退出编辑图片", user.getUserName())); pictureEditResponseMessage.setUser(userService.getUserVO(user)); broadcastToPicture(pictureId, pictureEditResponseMessage); } } /** * 关闭连接的处理 * * @param session * @param status * @throws Exception */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { super.afterConnectionClosed(session, status); // 判断该用户是否正在编辑,如果是需要退出 // 从 session 中获取通用参数 User user = (User) session.getAttributes().get("user"); Long pictureId = (Long) session.getAttributes().get("pictureId"); handleExitEditMessage(null, session, user, pictureId); // 移除session Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId); if (webSocketSessions != null) { webSocketSessions.remove(session); if (webSocketSessions.isEmpty()) { pictureSessions.remove(pictureId); } } // 发送消息退出编辑 PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(); pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue()); pictureEditResponseMessage.setMessage(String.format("%s离开编辑", user.getUserName())); pictureEditResponseMessage.setUser(userService.getUserVO(user)); broadcastToPicture(pictureId, pictureEditResponseMessage); } /** * 将响应信息广播给 集合里的session * * @param pictureId * @param pictureEditResponseMessage * @param excludeSession * @throws Exception */ private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws Exception { Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId); if (CollectionUtils.isEmpty(sessionSet)) { return; } // 使用 Spring 管理的 ObjectMapper,避免裸 ObjectMapper 无法处理 LocalDateTime。 String message = objectMapper.writeValueAsString(pictureEditResponseMessage); for (WebSocketSession session : sessionSet) { // 排除掉 session if (session.equals(excludeSession)) { continue; } if (session.isOpen()) { sendTextMessage(session, message); } } } /** * 向当前连接发送错误消息。 */ public void sendErrorMessage(WebSocketSession session, User user, String errorMessage) throws Exception { PictureEditResponseMessage responseMessage = new PictureEditResponseMessage(); responseMessage.setType(PictureEditMessageTypeEnum.ERROR.getValue()); responseMessage.setMessage(errorMessage); if (user != null) { responseMessage.setUser(userService.getUserVO(user)); } String message = objectMapper.writeValueAsString(responseMessage); sendTextMessage(session, message); } /** * WebSocketSession 不保证并发发送安全,统一同步发送。 */ private void sendTextMessage(WebSocketSession session, String message) throws Exception { if (session == null || !session.isOpen()) { return; } synchronized (session) { if (session.isOpen()) { session.sendMessage(new TextMessage(message)); } } } /** * 全体广播不排除 * * @param pictureId * @param pictureEditResponseMessage * @throws Exception */ private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws Exception { broadcastToPicture(pictureId, pictureEditResponseMessage, null); } } ```
第一天学习
hello world!
7 种智能体(Agent)设计模式深度解析
# 7 种智能体(Agent)设计模式深度解析 > 从思维链到多智能体协作,理解这些认知框架,就理解了 Agent 世界的"思维模式库"。 > > 这些模式并非互斥,实际开发中常常混搭使用。理解它们,能让我们在框架选型(LangChain、LangChain4j、LlamaIndex、Dify、Spring AI Alibaba 等)和架构设计时想得更透彻。  --- ## 全景概览 ```mermaid mindmap root((Agent 设计模式)) CoT 思维链 一步步写推理过程 Google Research 2022 Self-Ask 自问自答 拆大问题为小问题 Microsoft Research 2022 ReAct 推理+行动 思考与工具调用交替 Princeton & Google 2022 Plan-and-Execute 计划与执行 先规划再逐步执行 LangChain 社区 2023 Tree of Thoughts 树状思维 多分支探索择优 Princeton & DeepMind 2023 Reflexion 反思迭代 犯错后自我纠错 2023 Role-playing 角色扮演 多智能体分工协作 AutoGPT / ChatDev / CAMEL ``` 七种模式的核心差异可以用一张表快速感知: | 模式 | 一句话总结 | 核心能力 | 典型场景 | |------|-----------|---------|---------| | **CoT** | 一步步写过程 | 线性推理 | 数学计算、逻辑推理 | | **Self-Ask** | 拆成小问题 | 问题分解 | 多跳事实检索 | | **ReAct** | 既思考也动手 | 推理+工具调用 | 实时信息查询、API 调用 | | **Plan-and-Execute** | 先计划再执行 | 任务规划 | 多步骤长任务 | | **ToT** | 树状多分支探索 | 搜索+评估 | 解谜、复杂规划 | | **Reflexion** | 自我反思迭代 | 自我纠错 | 代码生成、流程执行 | | **Role-playing** | 多人协作分工 | 多智能体协作 | 软件开发、跨职能协同 | --- ## 一、Chain of Thought(思维链,CoT) ### 提出背景 Google Research 在 2022 年发表论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》,首次系统性地提出:通过引导大语言模型在给出最终答案之前,将推理过程一步步展示出来,可以显著提升模型在复杂推理任务上的表现。 ### 核心思想 传统 prompting 方式下,模型被要求直接给出答案——"一口气报出结果"。这在简单问题上没有问题,但面对需要多步推理的任务(数学计算、逻辑推导、因果分析),直接给答案往往导致"跳步"和错误。 CoT 的核心洞察是:**推理过程本身是一种"思维脚手架"**。当模型被要求把中间推理步骤显式写出来时,每一步的输出都会作为下一步的上下文输入,相当于让模型在"草稿纸"上演算,而不是心算。这种"显式化"带来三个好处: 1. **降低单步推理负担**:模型不需要在一个 forward pass 中完成全部推理,而是将复杂推理拆成多个简单步骤,每步只需做一小段推理。 2. **提供可追溯的推理链**:如果最终答案出错,可以回溯到具体哪一步出了问题,便于调试和改进。 3. **激发模型的潜在推理能力**:大型模型在预训练阶段已经学到了大量推理知识,CoT 提供了一种"激活"机制,让这些能力得以释放。 CoT 有两种主要变体:**Zero-shot CoT**(直接在 prompt 末尾加上"Let's think step by step")和 **Few-shot CoT**(在 prompt 中给出带推理过程的示例)。前者简单粗暴,后者效果更稳定。 ### 流程图 ```mermaid flowchart LR A[用户提问] --> B[模型开始推理] B --> C[步骤1: 理解问题<br/>提取已知条件] C --> D[步骤2: 设定变量<br/>建立关系] D --> E[步骤3: 逐步计算<br/>推导中间结果] E --> F[步骤4: 验证结果<br/>得出最终答案] F --> G[输出答案 + 推理过程] style A fill:#e1f5fe,stroke:#0288d1 style G fill:#c8e6c9,stroke:#388e3c style C fill:#fff9c4,stroke:#f9a825 style D fill:#fff9c4,stroke:#f9a825 style E fill:#fff9c4,stroke:#f9a825 style F fill:#fff9c4,stroke:#f9a825 ``` ### 场景示例 > **问题**:小明网购了一个 3.8 公斤的包裹,快递公司首重 1 公斤内收费 10 元,续重每公斤 3 元,不足 1 公斤按 1 公斤计算。请问小明需要付多少快递费? **CoT 推理过程**: ``` 第一步:确认包裹重量 → 3.8 公斤 第二步:计算计费重量 → 不足 1 公斤按 1 公斤算 → 4 公斤 第三步:分离首重和续重 → 首重 1 公斤 = 10 元 第四步:计算续重重量 → 4 - 1 = 3 公斤 第五步:计算续重费用 → 3 × 3 = 9 元 第六步:加总费用 → 10 + 9 = 19 元 答案:小明需要付 19 元快递费。 ``` 每一步推理都被显式写出。如果模型直接报答案"19 元",你无法判断它是真的会算还是猜的;但有了推理链,你能看到它在"不足 1 公斤按 1 公斤算"这一步做了正确的向上取整,思路完全可追溯。 ### 适用业务场景 - **数学计算与数值推理**:财务报表分析、投资收益计算、风险评估模型中涉及多步运算的场景。模型需要先提取参数、再建立公式、最后求解,CoT 让每一步都可审计。 - **逻辑推理与决策分析**:风控规则引擎、合规审查流程中,需要根据多条规则逐步推导结论的场景。比如"如果用户满足条件 A 且不满足条件 B,则触发规则 C"。 - **逐步分析类问题**:医疗辅助诊断中根据症状逐步排除疾病、法律推理中根据法条逐步推导结论、技术故障排查中根据现象逐步定位根因。 - **教育与培训**:AI 辅导系统中,不仅给出答案,还展示解题过程,帮助学生理解推理路径。 ### 适用系统与集成方案 **适配的系统类型**: - **智能报表与财务分析系统**:报表不只是展示数字,而是展示"这些数字怎么来的"。用户点击任何一个指标,都能看到从原始凭证到最终报表的完整推理链。 - **风控决策引擎**:每一笔贷款审批、每一次反欺诈判定,系统都绑定一条推理链,审计人员可以逐条回溯"为什么拒绝这笔申请"。 - **AI 辅导与在线教育平台**:学生做错题不只是看到"正确答案是 C",而是跟着系统的推理步骤,理解"这道题应该怎么一步步推出答案"。 - **医疗辅助诊断系统**:输入症状后,系统不只给出"可能是 XX 病",还展示"从症状 → 鉴别排除 → 最可能诊断"的完整推导。 **系统集成方式**: ```mermaid flowchart LR A[原始数据] --> B[CoT 推理引擎] B --> C[推理链存储] B --> D[结论输出] C --> E[审计 / 追溯接口] D --> F[前端展示: 结论 + 推理过程] style B fill:#fff9c4,stroke:#f9a825 style C fill:#f3e5f5,stroke:#7b1fa2 ``` **集成要点**: 1. **嵌入决策流程,而非独立模块**:CoT 不是单独的"推理服务",而是嵌入在报表查询、风控审批、诊断分析的每一个决策节点上。用户请求一次分析 → 系统返回的不只是结论,还有结构化字段 `reasoning_chain`。 2. **推理链必须结构化存储**:将推理步骤存为 `[{step: 1, thought: "...", result: "..."}, ...]` 格式,前端可展开/折叠每一步,审计时可逐条回溯。 3. **与现有规则引擎共存**:CoT 不替代现有规则引擎,而是互补——规则引擎负责快速硬判定(如"金额超限直接拒绝"),CoT 负责模糊推理(如"这个交易模式看起来可疑,解释一下为什么")。 --- ## 二、Self-Ask(自问自答) ### 提出背景 Microsoft Research 在 2022 年的研究工作《Measuring and Narrowing the Compositionality Gap in Language Models》中提出了 Self-Ask 方法(论文标题为 Self-Ask with Search)。研究者发现,当问题需要组合多个事实才能回答时(即"组合性问题"),模型的表现会急剧下降——即使模型知道每个单独的事实,也无法正确组合它们。 ### 核心思想 Self-Ask 解决的是**组合性鸿沟(Compositionality Gap)**问题。所谓组合性鸿沟,是指模型能够正确回答每个子问题,但当这些子问题组合成一个大问题时,模型的正确率远低于各子问题正确率的乘积。 Self-Ask 的策略是:**让模型学会"反问自己"**。面对一个复杂问题,模型不是试图一步到位地回答,而是先判断"要回答这个问题,我需要先知道什么?",然后把这个大问题拆解成一系列后续问题(follow-up questions),逐个回答后再组合成最终答案。 与 CoT 的关键区别在于:CoT 是线性的推理链——每一步都是上一步的自然延伸;Self-Ask 则是**问题分解**——把一个需要多跳推理的问题拆成多个独立的子问题,每个子问题可以独立查询和回答。更关键的是,Self-Ask 通常会配合外部检索(如搜索引擎)使用:每提出一个 follow-up question,就去检索答案,然后再基于检索结果提出下一个问题。 这种模式特别适合**事实链路长**的问题——答案需要串联多个事实,而每个事实可能不在模型的训练数据中。 ### 流程图 ```mermaid flowchart TD A[用户提出复杂问题] --> B{是否需要<br/>分解?} B -->|是| C[生成后续问题1] C --> D[检索/回答后续问题1] D --> E{是否还需要<br/>更多信息?} E -->|是| F[生成后续问题2] F --> G[检索/回答后续问题2] G --> H{是否还需要<br/>更多信息?} E -->|否| I[组合所有子答案] H -->|否| I I --> J[输出最终答案] style A fill:#e1f5fe,stroke:#0288d1 style J fill:#c8e6c9,stroke:#388e3c style C fill:#fff3e0,stroke:#ef6c00 style F fill:#fff3e0,stroke:#ef6c00 style D fill:#f3e5f5,stroke:#7b1fa2 style G fill:#f3e5f5,stroke:#7b1fa2 ``` ### 场景示例 > **问题**:埃隆·马斯克创办的第一家公司的市值现在是多少? **Self-Ask 过程**: ``` 问:埃隆·马斯克创办的第一家公司是什么? → 检索结果:1996 年,马斯克与弟弟金巴尔·马斯克共同创办了 Zip2, 一家为报纸提供在线城市指南和商业目录的公司。 追问:Zip2 公司后来怎么样了?现在还上市吗? → 检索结果:1999 年,康柏(Compaq)以约 3.07 亿美元收购了 Zip2, 之后被整合进康柏的 AltaVista 业务中,目前已不是独立公司。 追问(调整方向):既然已经被收购了,那它被收购时的价值是多少? → 检索结果:Zip2 被康柏以 3.07 亿美元收购,其中马斯克获得约 2200 万美元。 组合答案:马斯克的第一家公司 Zip2 目前已不存在独立上市, 1999 年被康柏以约 3.07 亿美元收购。 ``` 这个例子展示了 Self-Ask 的灵活性——当第一个追问发现"Zip2 已经不是独立公司"后,模型没有死板地继续追问"市值",而是动态调整了追问方向,去查"被收购时的价值"。这正是 Self-Ask 相比固定流程的优势:它能根据每一步的检索结果灵活生成下一步追问,而非硬编码的查询流水线。 ### 适用业务场景 - **多跳知识问答**:企业知识库中,用户的问题常常需要串联多个文档的信息。比如"我们公司华东区去年Q4营收最高的产品线是什么?它的利润率是多少?"需要先查营收数据,再定位产品线,最后查利润率。 - **事实链路长的信息检索**:竞品分析中需要"竞品 A 的母公司是谁 → 母公司的最新财报怎么说 → 财报中提到的战略方向是什么"这样的多跳查询。 - **合规与尽调**:背景调查、尽职调查中,需要串联工商信息、司法信息、关联关系等多源数据,Self-Ask 可以自动拆解查询路径。 - **客服与智能问答**:用户提问"我的订单什么时候到?"需要先查订单号 → 查物流状态 → 查预计送达时间,Self-Ask 能自动完成这个拆解。 ### 适用系统与集成方案 **适配的系统类型**: - **企业智能客服系统**:用户问"我的订单什么时候到?"→ Self-Ask 自动拆解为"查订单号 → 查物流 → 算预计送达"。不再只是关键词匹配式的 FAQ 应答,而是真正理解用户的复合查询需求。 - **企业知识库问答平台**:知识分散在多个文档库、Wiki、规章制度中,Self-Ask 将复杂问题拆成多个子问题,分别检索不同知识源后合成答案,解决"一个问题的答案藏在三个文档里"的困境。 - **合规与尽调系统**:输入一个企业名 → 自动拆解为"工商信息 → 司法风险 → 关联关系 → 舆情"四个子查询,并行调用天眼查、裁判文书、企查查等接口,最终汇总为尽调摘要。 - **竞品情报分析平台**:输入"竞品 X 的最新动态"→ 拆解为"产品更新 → 融资动态 → 人事变动 → 市场活动",按维度检索和汇总。 **系统集成方式**: ```mermaid flowchart TD A[用户提问] --> B[Self-Ask 问题分解器] B --> C1[子问题 1: 查订单] B --> C2[子问题 2: 查物流] B --> C3[子问题 3: 预计时间] C1 --> D1[订单系统 API] C2 --> D2[物流查询 API] C3 --> D3[时效计算引擎] D1 --> E[答案合成器] D2 --> E D3 --> E E --> F[最终回答] style B fill:#fff3e0,stroke:#ef6c00 style E fill:#f3e5f5,stroke:#7b1fa2 style F fill:#c8e6c9,stroke:#388e3c ``` **集成要点**: 1. **子问题与数据源映射**:在系统中维护一张"问题类型 → 数据源"的映射表。Self-Ask 分解出的每个子问题带上类型标签,调度器根据类型将子问题路由到对应的业务 API 或搜索引擎。 2. **答案合成不是拼积木**:合成器不是把子答案简单拼在一起,而是做"去重、冲突消解、信息补全"。比如子问题 A 说"Zip2 市值为 0",子问题 B 说"Zip2 已被收购",合成器需要输出"Zip2 已不存在独立市值,被收购时估值 3.07 亿美元"。 3. **渐进式渲染提升体验**:前端展示时不必等所有子问题都回答完。每个子问题有了答案就实时推送——用户看到的是"正在查你的订单号... ✅ 找到了 → 正在查物流... ✅ 在路上了 → 预计明天下午 3 点送达",体验比"转圈 5 秒然后出结果"好得多。 --- ## 三、ReAct(推理 + 行动) ### 提出背景 Princeton 大学与 Google Research 在 2022 年联合发表论文《ReAct: Synergizing Reasoning and Acting in Language Models》,提出了 ReAct 框架。这是 Agent 领域最具影响力的论文之一,至今仍是大多数 Agent 框架的底层范式。 ### 核心思想 在 ReAct 之前,CoT 和 Self-Ask 主要解决"怎么想"的问题,但它们有一个共同的局限:**模型只能基于自身参数中存储的知识进行推理,无法与外部世界交互**。如果模型不知道某个事实,或者事实已经发生变化(比如实时天气、最新新闻),纯推理模式就无能为力。 ReAct 的核心创新在于:**让模型在推理(Reasoning)和行动(Acting)之间交替进行**。具体来说,模型在每一步都会做两件事: - **Thought(思考)**:模型推理当前状态,决定下一步应该做什么。比如"用户问杭州昨天的天气,我不知道这个信息,需要调用天气 API 查询"。 - **Action(行动)**:模型执行一个具体动作,比如调用搜索引擎、查询数据库、调用 API。行动的结果作为新的观察(Observation)反馈给模型。 - **Observation(观察)**:模型接收行动的返回结果,基于这个新信息进行下一轮思考。 这个 Thought → Action → Observation 的循环可以反复进行,直到模型认为已经收集了足够的信息来回答问题。 ReAct 比 CoT 和 Self-Ask 更全能的根本原因在于:**它不仅是一个推理模式,更内建了与外部世界交互的闭环**。CoT 只能"想",Self-Ask 虽然能"查"但流程相对固定,而 ReAct 可以根据每次行动的反馈动态调整后续策略——如果搜索结果不理想,它可以换一个关键词重新搜索;如果 API 返回错误,它可以分析错误并尝试修复。 正是这种"思考-行动-观察"的闭环,使 ReAct 成为当前主流 Agent 框架(LangChain、LangChain4j、LlamaIndex、Dify、Spring AI Alibaba 等)的默认执行范式。 ### 流程图 ```mermaid flowchart TD A[用户输入问题] --> B[Thought: 分析问题<br/>判断需要什么信息] B --> C{是否已有<br/>足够信息?} C -->|是| D[生成最终答案] C -->|否| E[Action: 选择并调用工具<br/>搜索/查询API/读数据库] E --> F[Observation: 获取工具返回结果] F --> B style A fill:#e1f5fe,stroke:#0288d1 style D fill:#c8e6c9,stroke:#388e3c style B fill:#fff9c4,stroke:#f9a825 style E fill:#ffcdd2,stroke:#c62828 style F fill:#f3e5f5,stroke:#7b1fa2 ``` ### 场景示例 > **问题**:帮我查一下现在北京的空气质量,如果不适合户外活动的话,帮我推荐几个室内好去处。 **ReAct 执行过程**: ``` Thought: 用户想知道北京当前的空气质量。我需要调用天气/空气质量 API 获取实时数据,网上搜不到"现在"的数据。 Action: 调用空气质量查询工具 → 参数: city=北京 Observation: 北京当前 AQI 172,PM2.5 125μg/m³,属于"中度污染"。 建议减少户外活动,敏感人群避免外出。 Thought: AQI 172 中度污染,确实不适合户外活动。需要给用户推荐室内去处。 但我不知道北京具体有什么好的室内场所,需要搜索一下。 Action: 搜索"北京室内游玩推荐 商圈 博物馆 商场" → 调用搜索引擎 Observation: 搜索结果显示—— 1. 三里屯太古里(购物+餐饮) 2. 中国国家博物馆(免费预约参观) 3. 798 艺术区室内展馆(当代艺术展览) 4. 合生汇(购物中心+室内卡丁车) 5. 王府井 APM(购物+电影院) Thought: 现在有了足够信息。结合空气质量数据(AQI 172 中度污染) 和室内去处推荐,可以给用户一个综合回复了。 Final Answer: 北京当前空气质量为中度污染(AQI 172,PM2.5 125μg/m³), 确实不太适合户外活动。这里推荐几个室内好去处: ① 中国国家博物馆——免费但需提前预约,地铁直达 ② 798 艺术区室内展馆——正在举办多个当代艺术展 ③ 三里屯太古里/王府井 APM——逛吃加看电影一条龙 ④ 合生汇——有室内卡丁车,适合想活动一下的朋友 出门记得戴口罩哦! ``` 这个例子展示了 ReAct 的核心价值:模型不止于"思考"——它主动调用工具获取真实的实时数据,根据工具返回结果动态调整后续行动(空气质量不好 → 改推室内),整个过程是一个完整的"感知-决策-行动"闭环。 ### 适用业务场景 - **实时信息查询与决策**:任何需要最新数据的场景——股票行情查询、天气查询、新闻摘要、航班状态查询。模型"知道"自己不知道实时信息,主动调用 API 获取。 - **多工具编排与 API 调用**:企业内部的智能助手需要调用多个系统 API(CRM、ERP、OA),ReAct 让模型根据任务需要自主选择和组合工具调用顺序。 - **数据分析与报表生成**:模型先思考"需要哪些数据",调用数据库查询工具获取数据,再思考"如何分析",调用分析工具或编写代码处理,最后生成报表。 - **运维故障排查**:Agent 接到告警后,先分析告警信息,调用监控 API 查看指标,再根据异常指标调用日志查询工具,定位根因后给出修复建议。 - **智能客服与技术支持**:用户描述问题后,Agent 先理解问题,查询知识库获取解决方案,如果需要还可以调用工单系统、查看用户订单状态等。 ### 适用系统与集成方案 **适配的系统类型**: - **企业统一智能助手**:打通企业内部的 CRM、ERP、OA、邮件、日历等系统。员工说"帮我查一下陈总的合同审批到哪了,顺便约他明天下午开会"——ReAct 自动判断需要调合同系统 → 查日历 → 发会议邀请,一次对话完成跨系统操作。 - **智能运维平台(AIOps)**:系统告警触发 Agent → Thought: "CPU 飙升 85%,需要排查" → Action: 调用 Prometheus 查指标 → Observation: "服务 A 的 QPS 也翻了 3 倍" → Action: 查服务 A 的日志 → 定位到慢 SQL → 给出修复建议并自动创建工单。 - **数据分析与 BI 平台**:用户用自然语言问"上个月华东区毛利率为什么降了 3 个点?"→ ReAct 自动写 SQL → 查数据 → 分析趋势 → 关联影响因素 → 输出分析报告。用户不需要打开 SQL 编辑器。 - **智能客服工单系统**:用户报修后,Agent 不只给标准回复,而是自动查设备型号、查故障历史、查库存备件,给出"工程师带 XX 型号备件 + 预计 2 小时内到场"的完整方案。 **系统集成方式**: ```mermaid flowchart TD A[用户输入] --> B[ReAct 循环引擎] B --> C{Thought: 需要什么?} C --> D[工具注册中心 / API 网关] D --> E1[CRM 系统] D --> E2[ERP 系统] D --> E3[OA 审批] D --> E4[监控系统] D --> E5[数据库查询] E1 & E2 & E3 & E4 & E5 --> F[Observation: 结果汇总] F --> C F --> G[最终回复] style B fill:#ffcdd2,stroke:#c62828 style D fill:#fff9c4,stroke:#f9a825 style G fill:#c8e6c9,stroke:#388e3c ``` **集成要点**: 1. **工具注册中心是核心枢纽**:ReAct 不直接调用各业务系统 API,而是通过统一的工具注册中心。每个工具声明"名称、描述、参数 schema、调用地址",ReAct 引擎根据 Thought 自动匹配合适的工具。这正是 LangChain4j 的 `@Tool` 和 Spring AI 的 `@Tool` 注解背后的设计逻辑。 2. **权限边界必须前置**:生产环境中,ReAct Agent 调用工具必须经过权限网关。在工具注册中心为每个工具标注所需权限级别,Agent 执行 Action 前由网关拦截校验——不能让 AI 随意调用所有 API。 3. **可观测性是底线**:每一轮 Thought → Action → Observation 都记录到日志系统(时间戳、耗时、工具名、参数、返回摘要、Token 消耗)。出问题时能快速回溯"Agent 在哪一步做了错误判断",而不是对着一个最终结果猜原因。 --- ## 四、Plan-and-Execute(计划与执行) ### 提出背景 Plan-and-Execute 模式出现在 2023 年前后的 Agent 应用开发框架实践中,主要源自 LangChain 社区的工程实践。它并非某一篇学术论文的产物,而是对 ReAct 模式在复杂长任务场景下局限性的工程改进。 ### 核心思想 ReAct 模式在简单任务上表现出色,但面对复杂的多步骤任务时存在一个显著问题:**模型在每一步都重新推理"下一步该做什么",缺乏全局规划**。这导致两个后果——一是容易在中间步骤"跑偏",偏离原始目标;二是反复推理带来大量 token 消耗和延迟。 Plan-and-Execute 的解法是把任务拆成两个明确阶段: **第一阶段——Planning(计划)**:模型先通览整个任务,生成一个完整的步骤计划。这个计划是一系列有序的子任务列表,每个子任务都是具体、可执行的。比如"写一篇新能源车的市场调研报告"会被拆成"收集销量数据 → 分析政策趋势 → 总结消费者反馈 → 撰写结论"这样的步骤序列。 **第二阶段——Execution(执行)**:按照计划逐条执行每个子任务。每个子任务的执行可以采用任何合适的策略——可以是简单的 LLM 调用,也可以是 ReAct 循环,还可以是调用外部工具。关键在于,执行阶段不需要反复推理"接下来做什么",因为计划已经确定。 这种分离带来的好处是显而易见的:计划阶段可以专注于"全局最优"的任务分解,执行阶段可以专注于"局部最优"的子任务完成。同时,如果某个子任务执行失败,只需要重新执行该子任务,而不需要推翻整个计划。 进阶版本还支持**动态重规划(Re-planning)**:在执行过程中如果发现计划不合理(比如某个子任务无法完成,或者执行结果改变了后续前提),可以回到计划阶段进行调整。 ### 流程图 ```mermaid flowchart TD A[用户下达复杂任务] --> B[Planning 阶段<br/>生成完整计划] B --> C[计划: 子任务1] B --> D[计划: 子任务2] B --> E[计划: 子任务3] B --> F[计划: 子任务N] C --> G[执行子任务1] G --> H{执行成功?} H -->|是| D H -->|否| I[重试/调整] D --> J[执行子任务2] J --> K{执行成功?} K -->|是| E K -->|否| L[重试/调整] E --> M[执行子任务3] M --> N[...继续执行] N --> O[所有子任务完成] O --> P[整合结果输出] Q{需要重规划?} -.->|是| B style A fill:#e1f5fe,stroke:#0288d1 style B fill:#fff9c4,stroke:#f9a825 style P fill:#c8e6c9,stroke:#388e3c style O fill:#c8e6c9,stroke:#388e3c ``` ### 场景示例 > **任务**:帮我策划一场 30 人的户外生日派对,预算 3000 元。 **Planning 阶段**: ``` 计划生成: 1. 根据预算和人数,确定合适的场地类型和候选方案 2. 制定餐饮方案(自助烧烤 or 外卖简餐 or 冷餐摆台) 3. 策划活动流程(游戏环节 + 歌曲 + 切蛋糕) 4. 列出物资采购清单(装饰、餐具、饮料、蛋糕) 5. 制定时间安排表(从下午开始到晚上结束) 6. 生成最终方案文档,附带预算明细 ``` **Execution 阶段**: ``` 执行步骤 1:搜索"北京 30人户外派对场地" → 对比公园草坪(免费)、露营营地(人均30元)、民宿庭院(包场1500元) → 选择:露营营地(900元 / 30人)+ 自带帐篷区,性价比最高 执行步骤 2:制定餐饮方案 → 对比三种方案后选择"自助烧烤":食材批发约600元 + 饮料200元 执行步骤 3:策划活动流程 → 14:00-14:30 签到合影 → 14:30-16:00 团队游戏 → 16:00-17:30 烧烤 → 17:30-18:00 唱歌切蛋糕 → 18:00-19:00 自由活动拍照 执行步骤 4:物资清单 → 装饰(气球+横幅+照片墙)150元、一次性餐具120元、 蛋糕定制300元、饮料200元、零食150元、小礼品200元 执行步骤 5:预算汇总 → 场地900 + 食材600 + 饮料200 + 装饰150 + 餐具120 + 蛋糕300 + 零食150 + 小礼品200 + 应急备用预算280 = 总计2900元 ✓ 未超出3000元预算 执行步骤 6:生成最终方案文档(包含场地地址、采购链接、时间表、预算明细) (如果步骤1中露营营地订满 → 切换候选方案,重新算预算,其他步骤不受影响) ``` Plan-and-Execute 在这里的优势很明显:先用 5 秒生成全局计划,用户一眼就看清"整个派对怎么搞";然后再逐条执行细节,每步独立验证——如果露营营地满了,换一个方案就行,不会推翻整个策划。 ### 适用业务场景 - **长流程内容生成**:市场调研报告、行业分析报告、技术方案文档、产品 PRD 文档。这些任务步骤多、耗时长,需要先规划结构再逐段填充。 - **数据处理流水线**:ETL 任务、数据清洗与分析流水线。先规划数据处理步骤(抽取→清洗→转换→加载→分析),再逐步执行,每步可独立验证。 - **自动化运维流程**:系统部署、灰度发布、故障恢复等运维场景。先制定操作计划,再逐步执行,每步执行后检查状态,确保安全。 - **项目管理与任务分解**:把大型项目自动拆解为可执行的子任务列表,分配给不同的执行者或自动化流程。 - **多步骤研究与调研**:学术研究中的文献综述、竞品分析中的多维对比、投资尽调中的多维度核查,都需要先规划调研框架再逐项深入。 ### 适用系统与集成方案 **适配的系统类型**: - **自动化部署与发布平台**:用户提交"把用户中心服务发布到生产环境"→ Plan Agent 生成"环境检查 → 灰度发布 10% → 监控 5 分钟 → 全量发布 → 回归测试 → 通知"六步计划 → Execute Agent 逐步执行,每步独立验证。失败了只重试失败那一步,不用推翻整个流程。 - **AI 文档生成系统**:用户说"写一份 Q2 季度技术复盘报告"→ Plan Agent 生成大纲(业务目标回顾 → 技术成果 → 问题与教训 → 下季度规划)→ 用户确认或调整 → Execute Agent 逐段填充内容,每段产出可独立审核。 - **ETL 数据处理流水线**:数据工程师配置"从 MySQL 抽取订单数据 → 清洗去重 → 转换字段格式 → 加载到 ClickHouse → 跑数据质量检查"→ Plan 生成 DAG(有向无环图),Execute 按依赖关系执行,上一步失败自动阻断下游。 - **多步骤自动化测试平台**:测试场景"用户注册 → 登录 → 下单 → 支付 → 查看订单"→ Plan 拆为 5 个测试步骤 → Execute 按顺序跑,每步失败时自动截图保存上下文,方便定位。 **系统集成方式**: ```mermaid flowchart TD A[用户任务输入] --> B[Plan Agent] B --> C[步骤计划<br/>可人工审核调整] C --> D[Execute Agent] D --> E1[步骤 1: ReAct 执行] E1 --> E2[步骤 2: ReAct 执行] E2 --> E3[步骤 3: ReAct 执行] E3 --> F{需要重规划?} F -->|是| B F -->|否| G[结果汇总输出] subgraph 步骤状态追踪 H[(任务状态存储<br/>Redis / DB)] end E1 -.-> H E2 -.-> H E3 -.-> H style B fill:#fff9c4,stroke:#f9a825 style D fill:#ffcdd2,stroke:#c62828 style G fill:#c8e6c9,stroke:#388e3c ``` **集成要点**: 1. **计划必须可审核,不能是黑箱**:Plan Agent 生成计划后,必须给用户(或自动化审批规则)一个确认/调整的机会,通过后才进入 Execute。这是企业环境的红线——不能让 AI 自主决定"怎么改生产环境"。 2. **步骤状态必须持久化**:长任务可能执行几十分钟甚至几小时,必须用 Redis 或数据库记录每步状态(pending → running → success → failed),支持断点续传。运维平台上常见"执行到第 4 步时容器重启了,重启后从第 4 步继续"的场景。 3. **动态重规划是灵魂**:如果步骤 3 执行失败,不应该从头重来。应该触发 Re-plan:"步骤 3 因为 XX 原因失败了,请根据新情况重新规划剩余步骤"。这是 Plan-and-Execute 相比固定脚本的核心优势。 --- ## 五、Tree of Thoughts(ToT,树状思维) ### 提出背景 Princeton 大学和 Google DeepMind 在 2023 年联合发表论文《Tree of Thoughts: Deliberate Problem Solving with Large Language Models》。论文灵感来源于认知科学中人类"系统2思维"(System 2 thinking)的理论——人类在解决复杂问题时,会探索多条思路,评估各种可能性,必要时回溯重来,而不是一条路走到底。 ### 核心思想 CoT 是线性推理——一条链走到底。这在简单问题上够用,但面对真正复杂的问题(比如解谜、博弈、创意规划),单线推理的局限性很明显:**如果某一步走错了方向,整条链就全错了,而且没有回头路**。 ToT 的核心思想是:**不是单线思维,而是生成多条思路分支,像树一样展开**。具体来说: 1. **思维分解(Thought Decomposition)**:把问题求解过程分解为一系列中间"思维步骤"(thought),每个 thought 是一个有意义的问题解决中间状态。 2. **思维生成(Thought Generation)**:在每个节点,生成多个候选的下一步 thought(比如 3-5 个),形成树的分支。 3. **状态评估(State Evaluation)**:对每个分支的当前状态进行评估——这个方向是否有希望?评估可以由模型自身完成("这个思路看起来合理/不太行"),也可以通过外部反馈完成。 4. **搜索算法(Search Algorithm)**:使用树搜索算法(BFS 广度优先、DFS 深度优先、beam search 等)在思维树中探索,根据评估结果剪枝,最终找到最优路径。 ToT 与 CoT 的关系可以类比为:CoT 是"走一条路",ToT 是"看地图选最优路线"。ToT 的代价是计算成本更高(需要多次 LLM 调用),但在真正需要"深思熟虑"的场景下,这个代价是值得的。 ### 流程图 ```mermaid flowchart TD A[问题输入] --> B[初始状态] B --> C1[思路 A] B --> C2[思路 B] B --> C3[思路 C] C1 --> D1[思路 A-1] C1 --> D2[思路 A-2] C2 --> D3[思路 B-1] C2 --> D4[思路 B-2] C3 --> D5[思路 C-1] D1 --> E1{评估: 有希望?} D2 --> E2{评估: 有希望?} D3 --> E3{评估: 有希望?} D4 --> E4{评估: 有希望?} D5 --> E5{评估: 有希望?} E1 -->|否| X1[剪枝 ✂] E2 -->|是| F1[继续展开] E3 -->|是| F2[继续展开] E4 -->|否| X2[剪枝 ✂] E5 -->|否| X3[剪枝 ✂] F1 --> G1[...继续搜索] F2 --> G2[...继续搜索] G1 --> H[找到最优解] G2 --> H style A fill:#e1f5fe,stroke:#0288d1 style H fill:#c8e6c9,stroke:#388e3c style X1 fill:#ffcdd2,stroke:#c62828 style X2 fill:#ffcdd2,stroke:#c62828 style X3 fill:#ffcdd2,stroke:#c62828 style B fill:#fff9c4,stroke:#f9a825 ``` ### 场景示例 > **问题**:从北京去成都玩 4 天,预算 2500 元,怎么安排行程又省钱又好玩? **ToT 过程**: ``` 初始状态:北京→成都,4天3晚,预算2500元 第1层思维生成(出行方式): 思路 A:高铁往返(二等座约 780×2=1560 元,单程约 7.5 小时) 思路 B:飞机往返(提前订特价票约 600×2=1200 元,单程约 3 小时) 思路 C:硬卧火车(约 350×2=700 元,单程约 23 小时) 评估(综合考虑预算和时间): 思路 A → 交通占预算 62%,剩余 940 元用于住宿餐饮 → ❌ 太紧 思路 B → 交通占预算 48%,剩余 1300 元,时间也最省 → ✅ 最佳 思路 C → 交通占预算 28%,但往返近 46 小时在路上 → ❌ 玩的时间太少 保留思路 B,继续展开 ↓ 第2层思维生成(住宿 + 行程): 思路 B-1:住青旅(50 元/晚 = 150 元),景点全走经典线 (宽窄巷子+锦里+大熊猫基地+都江堰) 思路 B-2:住经济型酒店(150 元/晚 = 450 元), 重美食体验(火锅+串串+小吃打卡+川剧变脸) 思路 B-3:住民宿(100 元/晚 = 300 元), 自然+人文混搭(青城山+大熊猫基地+川博+火锅) 评估(综合预算、体验丰富度): 思路 B-1 → 酒店太差,影响体验 → ❌ 思路 B-2 → 总花费 1200+450+650(吃喝+门票)= 2300 元,体验好 → ✅ 思路 B-3 → 只有吃火锅单调 → ⚠️ 可优化 保留 B-2,继续展开 ↓ 第3层思维生成(美食路线细化): B-2-1:Day1 太古里+锦里小吃 → Day2 大熊猫基地+晚餐火锅 → Day3 宽窄巷子+串串+川剧 → Day4 人民公园+采耳+返程 B-2-2:Day1 落地休整+川博 → Day2 青城山一日游 → Day3 大熊猫基地+建设路小吃 → Day4 宽窄巷子+返程 评估(行程节奏、体力消耗): B-2-1 → 节奏轻松,体验层次丰富,不累 → ✅ 最佳! 最终方案选定 B-2-1: 交通 1200 + 住宿 450 + 吃喝 450 + 门票 200 + 其他 200 = 总计 2500 元 行程充实不赶路,美食打卡全覆盖,预算刚好卡住 ✓ ``` 这里的关键是:ToT 不是只规划一条路然后执行,而是同时展开多条分支,通过"评估"这个关卡及时砍掉不靠谱的方向(硬卧太费时间、青旅体验太差),把算力集中在最有希望的分支上。就好像有个军师帮你同时盘算了 9 种方案,最后挑出最优解。 ### 适用业务场景 - **复杂规划与决策**:战略规划、项目方案设计、资源分配优化。这些问题往往有多条可能的路径,需要评估各路径的可行性和收益,选出最优方案。ToT 可以系统性地探索多种策略组合。 - **解谜与约束满足问题**:排班调度、路径规划、资源分配等约束优化问题。这些问题有明确的约束条件,ToT 可以通过评估约束满足情况来剪枝。 - **创意生成与方案探索**:产品设计中的头脑风暴、营销方案的多维探索、技术架构的多方案对比。ToT 可以生成多种创意方向,评估后选择最优的深入展开。 - **博弈与对抗策略**:游戏 AI、谈判策略、竞品应对策略。需要在多个可能的对手反应分支中搜索最优应对方案。 - **代码生成与调试**:面对复杂编程问题,生成多种实现方案,评估各方案的复杂度、性能、可维护性,选择最优实现。调试时也可以尝试多条推理路径来定位 bug。 ### 适用系统与集成方案 **适配的系统类型**: - **智能排班调度系统**:输入约束条件(人员可用性、技能匹配、工时法规、业务峰谷),ToT 同时探索多种排班方案,通过评估函数(合规性 × 效率 × 员工满意度)剪枝,最终输出最优排班表,并附上"为什么没选另外两种方案"的说明。 - **物流路径规划系统**:输入"100 个配送点、5 辆车、时间窗口约束",ToT 同时探索多种路径组合,评估总里程、时效性、车辆负载均衡,选出最优调度方案。 - **技术架构选型决策系统**:输入业务需求,ToT 同时生成"单体架构方案"、"微服务方案"、"Serverless 方案"三个分支,从成本、可维护性、扩展性、团队能力多维度评估,推荐最优方案并解释取舍逻辑。 - **AI 辅助编程工具**:面对"实现一个高并发秒杀系统",ToT 同时生成多种架构思路(Redis 预减库存 + MQ 异步下单、数据库行级锁、纯 Redis Lua 脚本),评估并发容量、数据一致性、实现复杂度后选择最优路径并展开编码。 **系统集成方式**: ```mermaid flowchart TD A[约束 / 需求输入] --> B[ToT 搜索引擎] B --> C[第 1 层: 生成 N 个候选分支] C --> D[评估器: 对每个分支打分] D --> E{剪枝: 保留 Top-K} E --> F[第 2 层: 对保留分支继续展开] F --> D E -->|达到终止条件| G[最优方案输出] subgraph 评估维度配置 H[成本权重] I[效率权重] J[风险权重] K[合规权重] end D -.-> H & I & J & K style B fill:#fff9c4,stroke:#f9a825 style D fill:#f3e5f5,stroke:#7b1fa2 style G fill:#c8e6c9,stroke:#388e3c ``` **集成要点**: 1. **评估函数是 ToT 的灵魂**:ToT 的好坏 90% 取决于评估函数的准确度。在实际系统中,评估函数应该是"规则引擎 + LLM 自评"的混合体——硬性约束(如工时不超过 40 小时/周)用规则引擎硬剪枝,软性指标(如方案美感、用户体验)用 LLM 评分。 2. **控制搜索成本是刚需**:每多一层分支,LLM 调用量指数增长。实际系统必须配置"最大探索宽度 K"(每层保留几个分支)和"最大探索深度 D"(最多展开几层)。典型业务配置:K=3、D=5,一共 15 次 LLM 调用,成本可控。 3. **结果必须可解释**:用户需要看到的不只是最终方案,还有"另外两个方案为什么被淘汰"——ToT 需要输出完整的剪枝原因("方案 B 虽然成本最低,但物流超时率达 15%,不符合 SLA 要求")。 --- ## 六、Reflexion / Iterative Refinement(反思与迭代优化) ### 提出背景 2023 年论文《Reflexion: Language Agents with Verbal Reinforcement Learning》提出了 Reflexion 框架。这篇论文的核心贡献是提出了一种"语言强化学习"(Verbal Reinforcement Learning)的概念——不通过梯度更新模型参数,而是通过自然语言形式的自我反思来改进模型行为。 ### 核心思想 传统 Agent 的问题是:**犯了错不知道为什么错,下次还会犯同样的错**。即使是强大的 LLM,在复杂任务中也会犯错——代码有 bug、推理有漏洞、回答有偏差。如果 Agent 只是简单地"重试",没有总结失败原因,那么重试的结果很可能还是错的。 Reflexion 的核心思想是赋予 Agent **自我纠错的能力**,关键在于"反思"这一步: 1. **执行(Act)**:Agent 尝试完成任务,产生一个输出或行动。 2. **评估(Evaluate)**:通过外部反馈(如编译器报错、测试用例结果、用户反馈)或自我评估,判断输出是否正确。 3. **反思(Reflect)**:如果输出有误,Agent 不只是知道"错了",而是用自然语言总结"为什么错了"——"函数参数类型搞混了"、"边界条件没考虑到"、"搜索关键词太宽泛导致结果不相关"。 4. **重试(Retry)**:带着反思总结重新尝试,这次有了前次失败的教训,成功率会显著提升。 这个 Act → Evaluate → Reflect → Retry 的循环可以反复进行,直到任务成功或达到最大重试次数。每一次反思都作为"语言记忆"累积下来,指导后续尝试。 与传统的强化学习(通过梯度更新参数来学习)不同,Reflexion 是通过**自然语言反馈**来改进——模型不变,但上下文中多了"上次失败的经验教训"。这种方式不需要重新训练模型,只需要在 prompt 中加入反思记录即可,工程上非常轻量。 ### 流程图 ```mermaid flowchart TD A[接收任务] --> B[第1次尝试] B --> C[生成输出] C --> D{评估: 是否正确?} D -->|是| E[任务完成 ✓] D -->|否| F[反思: 分析失败原因] F --> G[记录经验教训] G --> H{重试次数<br/>是否超限?} H -->|否| I[第2次尝试<br/>带着反思经验] I --> C H -->|是| J[任务失败<br/>转人工处理] subgraph 反思记忆区 K[第1次失败教训:<br/>参数类型搞混了] L[第2次失败教训:<br/>边界条件遗漏] M[累积的经验...] end G -.-> K K -.-> I style A fill:#e1f5fe,stroke:#0288d1 style E fill:#c8e6c9,stroke:#388e3c style J fill:#ffcdd2,stroke:#c62828 style F fill:#fff9c4,stroke:#f9a825 style G fill:#fff9c4,stroke:#f9a825 ``` ### 场景示例 > **任务**:帮我一封回复,客人说快递到的蛋糕盒子压坏了,要求退款。我们是手工蛋糕店,每个蛋糕都有配送保险。 **Reflexion 过程**: ``` === 第1次尝试 === 生成的回复: "您好,非常抱歉给您带来不便。根据我们的配送保险条款, 蛋糕在运输过程中损坏的情况,我们会全额退款。请您提供 订单号和蛋糕的损坏照片,我们核实后立即处理。感谢您的理解。" 评估(客户视角):❌ 回复太机械、像模板——客人吃到的是情绪(生日的期待落空了), 但回复里没有任何共情,冷冰冰地走流程。 === 反思 === 分析失败原因:只陈述了"理赔流程",完全没有回应客人的情绪。 客人买蛋糕不是为了被"流程处理",是为了体面地过生日。 教训:先共情 → 再承认失误 → 再给补偿 → 最后给承诺。 === 第2次尝试(带着反思经验)=== 生成的回复: "看到这个画面真的太心疼了!蛋糕是我们花了好几个小时亲手做的, 寄出去的时候可是完完整整的——快递路上摔了一下,把您的惊喜 都摔没了,这个确实是我们做得不够好。 今年的生日蛋糕我们全额退款,钱会在 24 小时内原路退回。 另外,下次您来订蛋糕,我们直接给您打 5 折,而且这次一定 要求快递当面签收、开箱验货,不能再让这种事情发生了。 希望没有太破坏您今天的心情,生日快乐呀 🎂" 评估:✅ 先共情("心疼")→ 归因但不推诿(快递问题但我们来赔) → 补偿超出预期(退款+下次五折)→ 流程改进承诺 → 祝福收尾 有温度、有担当、有行动。 任务完成! ``` Reflexion 的价值在这里体现得非常直观——第 1 次和第 2 次的差距本质不是模型能力的问题,而是有了"上次太像机器客服了"这条反思经验后,模型知道自己问题在哪,下一次能定向改进。这种"语言记忆"的成本极低——不需要重新训练模型,只需要在 prompt 上下文里加上一行反思总结。 ### 适用业务场景 - **代码生成与自动修复**:AI 编程助手生成代码后自动运行测试,如果报错则分析错误信息并修正。Cursor、Copilot 等 AI 编程工具的"自动修复"功能本质就是 Reflexion。在企业内部,可用于 CI/CD 流水线中的自动修复。 - **文档写作与内容打磨**:AI 写作后通过自评或他评发现不足(逻辑不连贯、论据不充分、语言不流畅),反思后修改优化。适用于技术文档、营销文案、报告的自动生成与迭代。 - **数据分析与结果验证**:Agent 完成数据分析后,检查结果是否合理(数据是否异常、结论是否站得住脚),如果发现问题则反思数据质量或分析方法,重新分析。 - **流程执行与异常恢复**:自动化流程执行中遇到异常,Agent 分析异常原因,调整策略后重试。比简单的"重试 3 次"智能得多,因为每次重试都带着对上次失败的理解。 - **对话系统与客服**:Agent 回答用户问题后,如果用户表示不满意或追问,Agent 反思"是不是我理解错了用户意图",调整理解后重新回答。 ### 适用系统与集成方案 **适配的系统类型**: - **CI/CD 自动修复流水线**:代码提交 → 自动构建 → 运行测试 → 测试失败?→ Reflexion Agent 分析报错原因 → 生成修复补丁 → 自动提交修复代码 → 重新构建。整个过程开发者只需要在 MR 里看到"自动修复了 3 个测试问题,请确认"。 - **AI 写作与内容优化平台**:用户说"这篇文案不太满意,太官方了"→ Reflexion 不是简单地"重新写一遍",而是分析"哪里太官方"(用词?句式?语气?),带着具体改进方向定向优化。每一轮修改都有明确的改进锚点,而不是随机重写。 - **自动化测试生成平台**:Agent 为一段代码生成单元测试 → 跑测试 → 发现覆盖率只有 65% → 反思"哪些分支没覆盖到"→ 补充边界条件和异常路径的测试用例 → 再跑 → 覆盖率达到 90%。 - **智能客服质检系统**:客服 Agent 回复工单后 → 质检 Agent 评估回复质量 → 不合格 → 反思"具体哪里有问题"(态度不够好?没解决根本问题?给了错误信息?)→ 修正后重新回复,避免"换个说法重说一遍"的敷衍。 **系统集成方式**: ```mermaid flowchart TD A[任务输入] --> B[执行器] B --> C[输出结果] C --> D{评估器判断} D -->|通过| E[任务完成] D -->|不通过| F[Reflexion 反思引擎] F --> G[生成反思教训] G --> H[(教训存储<br/>向量库 / 缓存)] H --> I[带反思重试] I --> B style F fill:#fff9c4,stroke:#f9a825 style H fill:#f3e5f5,stroke:#7b1fa2 style E fill:#c8e6c9,stroke:#388e3c ``` **集成要点**: 1. **没有好的评估器,Reflexion 就是在瞎反思**:Reflexion 的有效性完全取决于评估器质量。不同场景需要不同评估器:代码场景用测试用例结果;写作场景用 LLM 自评 + 人工打分;运维场景用监控指标和 SLA。先打磨评估器,再谈 Reflexion。 2. **教训必须可积累、可复用**:不要把反思只存在单次对话的上下文里。把历史教训存入向量数据库,后续类似任务可以检索相关教训。"上次写 SQL 没加索引导致慢查询"这条教训,一个月后生成新 SQL 时仍能被检索到并避免。 3. **设定硬性停止条件**:避免无限反思循环。设置"最大反思次数"(通常 3~5 次就够)和"反思质量下降阈值"——如果连续两次反思内容高度重复,说明到了能力天花板,停止并转人工处理。 --- ## 七、Role-playing Agents(角色扮演 / 多智能体协作) ### 提出背景 角色扮演式智能体的概念源自 AutoGPT、ChatDev、CAMEL 等社区项目。这些项目的共同特点是:不再使用单一 Agent 完成所有工作,而是引入多个具有不同角色和职责的 Agent,通过对话协作来完成复杂任务。CAMEL(Communicative Agents Mind Exploration)论文首次系统性地研究了多智能体角色扮演的通信机制;ChatDev 将其应用于软件开发流程;AutoGPT 则展示了自主多步骤执行的雏形。 ### 核心思想 单一 Agent 再强大,也有认知边界——它难以同时扮演产品经理、程序员、测试工程师、设计师等多种角色,因为每种角色有不同的思维模式、关注点和专业语言。 Role-playing Agents 的核心思想是:**把任务拆分给不同角色的 Agent,每个 Agent 都有专属职责,通过对话协作完成任务**。具体来说: 1. **角色定义**:为每个 Agent 分配一个明确的角色(Persona),包括它的职责、专业领域、行为准则和输出格式。比如"你是一个资深 Java 后端工程师,专注于系统架构设计,你的输出必须是技术方案文档"。 2. **任务分配**:将复杂任务分解为各角色负责的子任务。每个 Agent 只负责自己擅长的部分。 3. **对话协作**:Agent 之间通过结构化对话进行协作。一个 Agent 的输出成为另一个 Agent 的输入,形成协作链条。比如产品经理 Agent 输出需求文档 → 程序员 Agent 根据需求写代码 → 测试 Agent 根据需求和代码写测试用例 → 代码审查 Agent 审查代码质量。 4. **反馈循环**:下游 Agent 可以对上游 Agent 的输出提出反馈。测试 Agent 发现需求不清晰时,可以"追问"产品经理 Agent;代码审查 Agent 发现设计问题时,可以"建议"程序员 Agent 修改。 这种模式的核心价值在于**专业分工**和**视角多样性**。每个 Agent 在自己的角色框架内"思考",产生更专业、更聚焦的输出;不同角色之间的碰撞和校验,也比单一 Agent 自我检查更容易发现问题。 ### 流程图 ```mermaid flowchart TD A[用户下达复杂任务] --> B[产品经理 Agent] B -->|需求文档| C[架构师 Agent] C -->|技术方案| D[程序员 Agent] D -->|代码实现| E[测试 Agent] E -->|测试用例 + 测试结果| F{是否通过?} F -->|否| G[代码审查 Agent] G -->|修改建议| D F -->|是| H[交付完成] E -.->|需求疑问| B G -.->|架构建议| C subgraph 多智能体协作团队 B C D E G end style A fill:#e1f5fe,stroke:#0288d1 style H fill:#c8e6c9,stroke:#388e3c style B fill:#fff9c4,stroke:#f9a825 style C fill:#fff9c4,stroke:#f9a825 style D fill:#fff9c4,stroke:#f9a825 style E fill:#fff9c4,stroke:#f9a825 style G fill:#fff9c4,stroke:#f9a825 ``` ### 场景示例 > **任务**:为公司的新智能手环产品策划一场线上发布会 **多智能体协作过程**: ``` 【品牌策划 Agent】 输出发布会定位方案: - 主题:"你的第一块健康管家"——走亲民科技路线,不讲参数,讲生活 - 调性:温暖、可靠、像朋友的关心 - 目标人群:25-35 岁城市白领,关注睡眠和运动但不想太专业 - 核心故事线:一个普通上班族的健康 24 小时 ↓ 传递策划方案 【内容文案 Agent】 根据定位输出文案和脚本: - 开场视频脚本:凌晨 6 点闹钟响 → 手环记录睡眠质量 ⭐⭐⭐⭐ 通勤路上久坐提醒 → 午休血氧监测 → 下班跑步心率区间 → 睡觉呼吸检测 - 产品介绍页文案:5 大健康功能,3 句讲清楚,不堆参数 - 结尾 call to action:"今天下单,前 1000 名送定制表带" ↓ 传递文案脚本 【视觉设计 Agent】 根据文案输出视觉方案: - 主色调:暖橙 + 深蓝(温暖 × 科技感) - 主 KV 设计稿:手环特写 + "看得见的好睡眠"文案 - 产品页排版:每屏只讲一个功能,大留白,大字情感文案 - 倒计时海报 3 张(发布会前 3 天/1 天/当天) → 提出疑问:"24 小时故事线能不能多给一个周末版本? 工作日和周末的节奏差别很大,两个版本更打动人" ↓ 反馈给内容文案 Agent 【内容文案 Agent】 收到设计师建议后补充: - 增加"周六版本"故事线:睡到自然醒 → 晨跑推荐路线 → 朋友聚餐卡路里统计 - 继续保留"工作日版本"让目标用户二选一,A/B 测试 ↓ 更新后的文案给各个 Agent 【数据分析 Agent】 根据历史发布会数据提供建议: - 建议发布时间:周四晚 8 点(过去 8 场发布会中转化率最高时段) - 预告周期:7 天(倒计时太长流失关注,太短蓄水不足) - 定价策略:首发价 299,对比同类产品 349-399,有价格冲击力 ↓ 反馈给品牌策划和内容文案 【品牌策划 Agent】 综合所有意见,输出最终《发布会执行手册》: 包含主题、时间线、文案脚本、视觉素材清单、投放渠道、数据监测指标 【交付完成】 ``` 这个场景展示了多智能体协作的精华——不是让一个大模型"想完所有这些",而是让擅长品牌的人想 slogan、擅长写字的人写文案、擅长做图的人出视觉、擅长数据的人给策略。而且设计师可以对文案提出改进建议("多加一个周末版本"),数据 Agent 可以纠正发布时间——这种跨角色的碰撞和反馈,单一 Agent 很难做到。 ### 适用业务场景 - **软件开发全流程**:从需求分析到设计、开发、测试、代码审查的完整软件生命周期。ChatDev 等项目已经证明多智能体可以完成一个简单软件项目的全流程。企业内部可用于辅助开发流程,提升各环节效率。 - **内容创作团队**:模拟编辑部协作——策划 Agent 定选题、调研 Agent 收集素材、写作 Agent 撰写初稿、编辑 Agent 审校润色、SEO Agent 优化关键词。适用于媒体运营、技术博客、营销内容批量生产。 - **投资研究与决策**:宏观分析师 Agent 研究经济趋势、行业分析师 Agent 分析赛道、财务分析师 Agent 评估财报、风控 Agent 评估风险,最后由投资决策 Agent 综合各方意见给出建议。 - **跨职能项目协同**:大型项目中涉及产品、技术、运营、法务、财务多个职能的协同。每个职能由专门的 Agent 负责,模拟真实的项目协作流程。 - **教育与培训模拟**:模拟案例教学——教师 Agent 提出问题、学生 Agent 尝试回答、点评 Agent 给出反馈。也可以模拟商务谈判、面试等场景,由不同角色 Agent 扮演不同立场。 ### 适用系统与集成方案 **适配的系统类型**: - **软件开发协作平台(ChatDev 模式)**:产品经理 Agent 写 PRD → 架构师 Agent 出技术方案 → 程序员 Agent 写代码 → 测试 Agent 跑测试 → 审查 Agent 审代码。整个软件开发流水线由多 Agent 协作完成,人类开发者只需要在关键节点审核和确认。 - **内容生产流水线(自媒体矩阵)**:选题 Agent 抓热点 → 调研 Agent 收集素材 → 写作 Agent 生成初稿 → 编辑 Agent 润色 → SEO Agent 优化标题和关键词 → 发布 Agent 定时推送到各平台。每天自动产出经过多角色打磨的稿件。 - **投资研究决策系统**:宏观分析 Agent 研究经济周期 → 行业分析 Agent 评估赛道景气度 → 财务分析 Agent 拆解目标公司财报 → 风控 Agent 评估下行风险 → 决策 Agent 综合各方意见给出投资建议,附带分歧点和置信度。 - **企业跨部门协同平台**:大型项目需要产品、技术、运营、法务、财务多方协同。每个部门用专属 Agent 代表,法务 Agent 审查合规风险 → 财务 Agent 算预算 → 产品 Agent 调整方案——模拟真实跨部门协作的链条。 **系统集成方式**: ```mermaid flowchart TD A[任务入口] --> B[任务分配器<br/>Orchestrator] B --> C1[Agent 1: 产品经理<br/>Prompt + 工具集] B --> C2[Agent 2: 架构师<br/>Prompt + 工具集] B --> C3[Agent 3: 开发<br/>Prompt + 工具集] B --> C4[Agent 4: 测试<br/>Prompt + 工具集] C1 --> D[消息总线 / 事件驱动] C2 --> D C3 --> D C4 --> D D --> E[协作编排引擎] E --> F{流程控制} F -->|触发下一步| C1 & C2 & C3 & C4 F -->|流程结束| G[交付物组装] style B fill:#fff9c4,stroke:#f9a825 style D fill:#f3e5f5,stroke:#7b1fa2 style G fill:#c8e6c9,stroke:#388e3c ``` **集成要点**: 1. **角色定义是门槛,写好 Prompt 才算开始**:每个 Agent 的 Prompt 需要包含四个核心要素——"角色定位、职责范围、输出格式、协作规则"。"你是资深产品经理"是定位,"只输出 PRD 文档(Markdown 格式)"是输出格式,"不写代码,但可以验收开发交付物"是协作规则。少了任何一项,Agent 就会越界。 2. **协作流程需要状态机兜底**:多 Agent 协作不是"大家随便聊",而是有清晰的状态流转。实现方式:用编排引擎(Orchestrator)管理状态机——"产品经理输出 → 自动触发架构师 → 架构师输出 → 自动触发开发 → 开发输出 → 触发测试 → 测试失败 → 回退到开发"。没有状态机,多 Agent 就是一群没指挥的散兵。 3. **Agent 间传递的是交付物,不是聊天记录**:用文件或数据库记录做中间存储——产品经理输出的 PRD 存为一个文档记录,架构师读取该记录后产出技术方案文档。不要把整个对话历史堆在 prompt 上下文里,否则 Token 消耗很快失控。 4. **人机协作是落地红线**:不要试图让多 Agent 全自动跑完整条流水线。在关键节点设置人工审核卡点——"产品经理 Agent 的 PRD,需要人类产品经理确认后才进入架构师环节"。这是企业级多 Agent 系统在生产环境中稳定运行的核心原则。 --- ## 模式对比与混合使用 七种模式并非互斥,实际应用中常常组合使用。以下是常见的组合策略: ```mermaid flowchart LR subgraph 简单推理 A1[CoT] end subgraph 信息检索 B1[Self-Ask] --> B2[+ 搜索引擎] end subgraph 工具调用 C1[ReAct] --> C2[CoT 作为 Thought 引擎] end subgraph 复杂任务 D1[Plan-and-Execute] --> D2[每个子任务用 ReAct] D2 --> D3[失败时用 Reflexion] end subgraph 深度探索 E1[ToT] --> E2[每个分支用 CoT] end subgraph 团队协作 F1[Role-playing] --> F2[每个角色用 ReAct] F2 --> F3[角色间用 Reflexion 互审] end style A1 fill:#c8e6c9,stroke:#388e3c style B1 fill:#c8e6c9,stroke:#388e3c style C1 fill:#c8e6c9,stroke:#388e3c style D1 fill:#c8e6c9,stroke:#388e3c style E1 fill:#c8e6c9,stroke:#388e3c style F1 fill:#c8e6c9,stroke:#388e3c ``` ### 选型决策参考 | 你的场景 | 推荐模式 | 理由 | |---------|---------|------| | 数学计算、逻辑推理 | **CoT** | 线性推理足够,简单高效 | | 多跳事实查询 | **Self-Ask** | 自动拆解问题,配合检索 | | 需要调用工具/API | **ReAct** | 推理+行动闭环,动态决策 | | 多步骤长任务 | **Plan-and-Execute** | 先规划全局,再逐步执行 | | 解谜/复杂规划 | **ToT** | 多分支探索,评估择优 | | 需要自我纠错 | **Reflexion** | 反思失败原因,迭代改进 | | 跨职能复杂项目 | **Role-playing** | 专业分工,多视角协作 | | 都需要? | **混合使用** | Plan-and-Execute 做骨架,ReAct 做执行,Reflexion 做兜底,Role-playing 做分工 | --- ## 框架支持现状 这些设计模式已经被主流 Agent 开发框架广泛内置: ```mermaid flowchart TD subgraph Agent 开发框架 L1[LangChain / LangChain4j] L2[LlamaIndex] L3[Dify] L4[Spring AI Alibaba] L5[Google ADK] end subgraph 内置模式支持 M1[ReAct - 基础执行范式] M2[Plan-and-Execute - 任务规划] M3[Reflexion - 重试与纠错] M4[Role-playing - 多智能体] M5[CoT - 推理增强] end L1 --> M1 L1 --> M2 L1 --> M3 L1 --> M4 L2 --> M1 L2 --> M5 L3 --> M1 L3 --> M2 L4 --> M1 L4 --> M4 L5 --> M1 L5 --> M4 style M1 fill:#c8e6c9,stroke:#388e3c style M2 fill:#fff9c4,stroke:#f9a825 style M3 fill:#fff9c4,stroke:#f9a825 style M4 fill:#fff9c4,stroke:#f9a825 style M5 fill:#e1f5fe,stroke:#0288d1 ``` - **ReAct** 已成为几乎所有框架的基础执行范式,是 Agent 的"操作系统"。 - **Plan-and-Execute** 在 LangChain 中有专门实现(Plan-and-Execute Agent)。 - **Reflexion** 的思想融入了各框架的重试机制和错误处理逻辑中。 - **Role-playing / Multi-Agent** 在 LangGraph、Spring AI Alibaba 的多智能体模块中有直接支持。 - **CoT** 通常作为 prompt 工程的一部分,不需要框架层面的特殊支持。 理解这些底层模式,能帮助开发者在框架选型时做出更准确的判断——不是看框架"支持多少功能",而是看它"如何实现了这些认知模式",以及哪种实现方式最适合你的业务场景。 --- > **总结一句话**:CoT 一步步写过程,Self-Ask 拆成小问题,ReAct 既思考也动手,Plan-Execute 先计划再执行,ToT 树状多分支探索,Reflexion 自我反思迭代,Role-playing 多人协作分工。它们构成了 Agent 世界的思维模式库,混搭使用,威力最大。
AI编程 技术研究报告 聚焦中国市场:工具生态、技术原理与工作流变革
# AI编程技术研究报告 聚焦中国市场:工具生态、技术原理与工作流变革 深度调研 & 多源交叉验证 2026年7月 # 一、执行摘要 AI编程正在从"代码补全"走向"智能体自主开发",成为中国科技产业最炙手可热的赛道之一。据蓝凌研究院(2026)发布的报告,2025年中国AI Coding市场规模为47.2亿元人民币,预计2030年将达253.8亿元,年复合增长率高达40%,远超全球26.6%的平均水平。然而,国际数据公司IDC在2025年的调研中发现,中国使用AI编程助手的开发者仅有30%,与美国市场91%的覆盖率形成鲜明对比——这一巨大的渗透率鸿沟,既意味着短板,也预示着强劲的增长势能。 本报告通过三轮顺序调研——行业综合信息采集、学术文献深挖、多源交叉验证——系统梳理了AI编程领域的核心议题。研究发现,AI编程的效率提升真实但远非线性:MIT/NBER的研究精确刻画了"17倍→30%"的产出漏斗,即AI在编码环节可将开发者产出提升17倍,但在完整项目周期中仅提升约30%。佐治亚理工的研究则揭示了严峻的安全隐患——AI生成代码中45%包含安全漏洞,经确认的CVE级别漏洞达74个。2026年7月阿里因Claude Code存在植入后门风险而将其列入黑名单的事件,更从产业侧印证了安全问题的紧迫性。 在工具生态方面,中国市场已形成国产"五强"格局:通义灵码、文心快码Comate、TRAE/豆包编程、CodeGeeX、CodeBuddy。海外工具如GitHub Copilot和Cursor虽在功能深度上保有优势,但面临数据安全与合规的双重压力,国产替代的逻辑正在加速强化。在工作流层面,AI编程正推动开发者角色从"代码编写者"向"Agent编排者"转变,84%的开发者已常态化使用AI编程工具,超五成工程师每日依赖大模型完成开发工作。然而,复旦白泽提出的A.S.E评测框架和ICLR 2026的Code2Bench研究共同指出,当前的评估基准存在系统性方法论缺陷,许多"高分"可能源于评估方式而非真实能力提升。 # 二、引言 ## 2.1 研究背景 2023年以来,以大语言模型(LLM)为核心的技术突破,将AI编程从概念验证迅速推向产业落地。GitHub Copilot的成功证明了AI在代码补全场景的商业可行性,而2024-2025年涌现的Agent模式——AI可自主执行多步骤编程任务——更标志着从辅助工具到自主开发者的质变。在中国,这一变革具有特殊的战略意义:一方面,软件开发的人力成本持续攀升,企业对效率工具的渴求日益强烈;另一方面,地缘政治环境使数据安全和自主可控成为不可回避的刚性约束。2026年7月阿里禁用Claude Code的事件,正是这两股力量交汇的最新注脚。 AI编程的研究价值不仅在于技术本身,更在于其对软件工程范式、开发者职业生态和产业竞争格局的深层影响。当前,行业叙事中对效率提升的声量远大于对风险和局限的讨论,学术界的审慎声音亟需被更广泛地听到。本报告旨在通过系统性的多源调研,构建一个更为完整、平衡的认知框架。 ## 2.2 研究范围与方法论 本报告聚焦中国市场的AI编程领域,研究范围涵盖三大主题:AI编程工具与助手的市场格局与竞争态势、AI代码生成的技术原理与演进路径、AI编程对开发效率与工作流的实际影响。地理范围以中国为主,兼顾全球趋势作为参照系。时间范围以2024-2026年为核心窗口,追溯至2022年的技术起源。 方法论上,本报告采用三轮顺序调研设计:第一轮通过多角度网络搜索采集行业报告、新闻资讯和市场数据;第二轮在此基础上进行学术文献深挖,寻找同行评审研究和权威技术报告进行验证和扩展;第三轮对前两轮发现进行多源交叉验证,识别共识水平和矛盾点,评估证据置信度。共引用13个核心来源,平均来源质量评级为B+。 ## 2.3 报告结构 本报告共八章。第三章至第六章为四大主题的逐项分析——工具生态、技术原理、效率评估与安全风险、工作流变革;第七章为跨主题综合分析;第八章讨论研究局限与认知空白;第九章给出结论与建议。 # 三、AI编程工具生态:中国市场格局 ## 3.1 市场概况与增长预期 中国AI Coding市场正处于高速增长的爆发期。据蓝凌研究院(2026)援引的统计数据,2025年市场规模为47.2亿元人民币,预计2030年将达到253.8亿元,年复合增长率(CAGR)为40%,显著高于全球26.6%的平均增速。东莞证券(2026)在行业深度报告中也确认了这一高增长预期,指出国内市场正开启新一轮增长周期。然而,IDC在2025年的调研揭示了一个值得深思的反差:中国使用AI编程助手的开发者比例仅为30%,远低于美国市场的91%。这意味着,尽管市场增速领跑全球,但实际渗透率仍处于早期阶段,增长空间巨大。 从供给侧看,蓝凌研究院(2026)将市场玩家划分为三类:通用大模型厂商(智谱、MiniMax、Kimi)凭借模型能力快速切入;大型互联网企业(字节、阿里、百度)从内部工具孵化出独立产品;云服务商(阿里云、华为云、腾讯云)既做自研也集成第三方。此外,传统软件与集成商(金蝶、蓝凌、亚信科技)正以行业理解与客户渠道为杠杆入局,试图将AI Coding封装进企业已有的数智化产品体系。 ## 3.2 国产"五强"工具概览 CSDN于2026年6月发布的国产AI编程工具硬核横评,对当前市场主流的五款国产工具进行了多维度对比。总体结论是:各家产品的能力差距正在缩小,竞争逐渐收敛到用户入口和独立IDE产品上。下表概要梳理了五款工具的核心定位与特点: | 工具名称 | 所属企业 | 底层模型 | 核心特点 | | --- | --- | --- | --- | | 通义灵码 | 阿里巴巴 | 通义千问 | 深度集成阿里云生态,企业级部署能力突出 | | 文心快码Comate | 百度 | 文心大模型 | 多语言支持,智能问答+代码生成的三层架构设计 | | TRAE/豆包编程 | 字节跳动 | 豆包大模型 | 独立IDE形态,Agent模式能力领先 | | CodeGeeX | 智谱AI | GLM系列 | 开源友好,学术基因,多语言代码生成 | | CodeBuddy | 腾讯 | 混元大模型 | 集成腾讯云开发者工具链,协同开发场景 | 表1:国产AI编程"五强"工具概览 ## 3.3 海外工具在中国的处境:优势与安全隐忧 GitHub Copilot凭借深度生态集成和稳定的Tab补全体验,仍然是全球开发者认知度最高的AI编程助手,2025-2026版本已支持Claude、GPT-4o等多模型切换。Cursor以独立IDE的姿态崛起,其Composer功能可一次性读取整个项目文件结构,Agent模式被视为杀手锏。然而,海外工具在中国面临数据安全和合规的双重压力。据第一财经(2026)报道,阿里因Claude Code被曝存在植入后门的安全风险,经综合评估后将其列入高风险软件名单,7月10日起全面禁止内部员工在办公环境下使用,并推荐国产替代方案Qoder。这一事件为整个行业敲响了警钟,进一步强化了国产替代的战略逻辑——安全因素正从"隐性考量"跃升为"显性门槛"。 # 四、AI代码生成的技术原理与演进路径 ## 4.1 核心技术架构 AI代码生成的核心技术依托Transformer架构的自注意力机制。通过海量代码数据的预训练,模型掌握编程语言的语法规则与逻辑范式,再根据用户自然语言需求概率性地预测下一个最可能的token序列。百度开发者中心(2026)将这一过程概括为"自然语言理解+代码语法建模+场景适配"的三层架构,其本质并非"背诵代码库然后复制粘贴",而是通过对统计规律的学习完成概率预测。这一区分至关重要:它意味着AI代码生成具有涌现性——能够处理训练数据中未曾精确出现的编程模式,但同时也带来了不可预测性——模型可能产生语法正确但逻辑错误的代码。 在底层模型层面,代码生成大模型通常在通用LLM基础上进行代码领域的专项训练,包括代码语料增强、编程语言特定的分词策略、以及代码执行反馈的强化学习(RLCF/RLAIF)。Anthropic、OpenAI等头部实验室的实践表明,模型在代码任务上的表现与其参数规模、训练数据质量和指令微调策略高度相关。中国厂商的路径有所不同:百度采用文心大模型的多语言理解能力迁移至代码领域;阿里通过通义千问结合阿里云的工程实践数据进行领域微调;智谱则依托GLM系列的学术积累,在开源协议下快速迭代。 ## 4.2 产品形态的三阶段跃迁 AI编程工具的产品形态正经历清晰的阶段性跃迁。第一阶段为"代码补全"(Code Completion),以GitHub Copilot早期版本为代表,核心能力是在开发者编码时基于上下文预测下一段代码,以Tab键确认的方式提升编码速度。第二阶段为"对话式助手"(Conversational Assistant),以ChatGPT和各类代码问答工具为代表,开发者可通过自然语言描述需求,获取代码片段、调试建议和架构方案。第三阶段为"Agent智能体"(Autonomous Agent),以Cursor的Agent模式和Claude Code为代表,AI可自主读取项目上下文、拆分任务、多步骤执行,仅在关键决策节点交由人类审核。 据CSDN报道援引的Gartner数据,截至2026年4月,全球企业级AI代码智能体市场年化规模已达98至110亿美元。蓝凌研究院(2026)也指出,"Agent原生"正成为工具演化的收敛方向——AI编程工具正从"帮人写代码"转向"Agent自己干活"。然而,这一跃迁也带来了新的挑战:当代码补全阶段开发者仍在每个补全点进行即时审查时,Agent阶段人类审查窗口急剧收窄,安全漏洞更易潜入,验证的难度和重要性同步升级。 # 五、开发效率评估:真实提升还是数字幻象 ## 5.1 行业数据的光谱:从乐观到审慎 关于AI编程对开发效率的影响,行业数据呈现出极其宽广的光谱。乐观端,Greptile发布的2025年AI编程状况报告显示,开发者人均代码产出从3月的4450行增长到11月的7839行,增幅达76%。IDC(2025)的调研显示,使用AI编码助手的开发人员平均生产力提高35%,超过20%的受访者表示效率提升超过50%。Meta等科技巨头的内部数据更为激进:新代码中95%由AI生成,初级程序员招聘量锐减70%以上。然而,审慎端的信号同样强烈:测试团队的实测数据揭示,代码提交量激增的同时缺陷密度同步攀升,开发自测通过率看似提高,但系统测试阶段却暴露出更多问题(CSDN/2501_94261392,2026)。什么值得买(2026)的分析指出,整体项目周期仅缩短约30%,个人开发者声称效率提升3倍与团队级仅提升30%存在显著矛盾。 | 数据来源 | 效率提升幅度 | 测量粒度 | 备注 | | --- | --- | --- | --- | | Greptile (2025) | 76% | 人均代码产出(行数) | 编码环节,单点指标 | | IDC (2025) | 35% | 自我报告生产力 | 调查数据,主观性较强 | | Meta内部 | 95%新代码AI生成 | AI生成代码占比 | 企业自报,"生成"不等于"采纳" | | 实测团队数据 | 量增质降 | 代码量+缺陷密度 | 系统测试阶段问题暴露增多 | | MIT/NBER (2025) | 全周期约30% | 完整项目周期生产率 | 编码环节提升17倍,全周期仅约30% | 表2:AI编程效率提升数据对比 ## 5.2 "17倍→30%"产出漏斗:效率的核心结构 MIT与NBER的联合研究(Korinek,2025,NBER Working Paper)提供了理解效率矛盾的关键框架。研究发现,AI辅助编程在编码环节可将开发者产出提升17倍——这一数字令人震惊,但将视野拉远到完整项目周期时,效率增益急剧衰减至约30%。这一"产出漏斗"的成因可从三个层面理解:其一,编码在整个软件开发生命周期中仅占30-40%的时间权重,需求分析、架构设计、代码审查、测试调试、部署运维等环节AI的增益有限甚至为零;其二,AI生成的代码需要额外的人类验证时间,当代码量翻倍时验证工作量也同步增长;其三,AI引入的错误——特别是隐蔽的逻辑漏洞和安全缺陷——在项目后期才被发现时,修复成本呈指数级上升。 ## 5.3 倒U型收益分布:谁受益最大? Peng et al.(2025)在软件工程顶级会议FSE上发表的研究揭示了另一个重要维度:AI辅助编程的效率收益呈"倒U型"分布。具体而言,初级开发者获益最大——AI为他们提供了原本不具备的编码能力和问题解决思路;中级开发者也有显著增益;但高级开发者的效率提升趋于平缓,而过度依赖AI的高级开发者反而出现了独立解决问题能力下降的现象。这一发现对行业具有深远的政策含义:如果AI编程主要提升初级开发者的效率,同时可能削弱高级开发者的深度思考能力,那么长期来看,行业的人才梯队建设方式需要根本性重构。蓝凌研究院(2026)将这一趋势凝练为"代码生成规模化,验证成新瓶颈",指出投资重心正从"写代码"向"验代码"的基础设施转移。 # 六、代码安全与质量:增长的隐患 ## 6.1 学术实证:45%的漏洞率 佐治亚理工学院的Pearce et al.(2025)进行了一项系统性研究,对多个主流AI代码生成模型的安全输出进行了大规模评估。研究结果令人警醒:AI生成的代码中45%包含安全漏洞,经专业安全团队评估后,确认了74个CVE级别的漏洞。这些漏洞涵盖SQL注入、跨站脚本(XSS)、缓冲区溢出、硬编码凭据等多种类型,且分布在多种编程语言和应用场景中。该研究的重要性在于,它不是针对特定模型的个案分析,而是对AI代码生成这一技术范式的结构性风险评估。45%的漏洞率意味着:在缺乏系统性安全审查流程的前提下,AI编程正在产生"更多且更脆弱的代码"——量的增长以质的下降为代价。 ## 6.2 产业侧的应激反应:阿里禁用Claude Code 2026年7月3日,第一财经报道了一则震动行业的事件:阿里巴巴因Claude Code被曝存在植入后门的安全风险,经综合评估后将其列入高风险软件名单,7月10日起全面禁止内部员工在办公环境下使用,并推荐国产替代方案Qoder。这一事件的标志性意义在于:它将学术研究中"45%漏洞率"的抽象风险,转化为企业决策中"立刻停用"的实时行动。当安全从实验室数据变为真实威胁时,头部企业选择了断然切割。值得注意的是,该事件发生在Agent模式快速普及的背景下——Agent自主执行多步骤编程任务的能力越强,其潜在安全风险的波及面就越广,这正是产业侧高度敏感的根本原因。 ## 6.3 评估基准的信任危机 AI代码安全的另一个深层问题在于:我们是否能够信任当前的评估基准?复旦白泽团队提出的A.S.E评测框架(准确性、安全性、效率三维评估)指出,现有评估体系对安全性维度的忽视是系统性问题——大多数基准测试聚焦于功能正确性和通过率,对安全性的考察严重不足。更令人忧虑的是,ICLR 2026的Code2Bench研究揭示了当前代码生成评估基准存在系统性方法论缺陷:许多模型的"高分"可能源于评估方式(如数据泄露、任务简化)而非真实能力提升。这意味着,即使工具在现有基准上表现良好,也不代表其生成的代码在安全性上可靠——因为基准本身可能未充分测试安全维度。四条证据链——45%漏洞率、阿里禁用事件、A.S.E框架、Code2Bench方法论批评——共同指向一个结论:安全问题不仅是当前AI编程最不具争议的结构性风险,而且我们对这一风险的认知可能仍然不足。 # 七、工作流变革:从"写代码"到"编排Agent" ## 7.1 开发者角色的根本性转变 AI编程正在深度重塑开发者的工作模式与角色定义。据CSDN(2026)的行业分析,进入2026年后,Agent智能体已实现长周期自主开发,可连续数天完成完整业务模块搭建,仅在关键决策节点交由人类审核。全球调研数据显示84%开发者常态化使用AI编程工具,超五成工程师每日依赖大模型完成开发工作(CSDN/dmxj123,2026)。这意味着开发者的核心职责正从"亲手编写每一行代码"转向"定义需求、编排Agent、审查结果"——从执行者变为指挥者。 更为极端的案例正在出现:据新浪科技报道,有前Meta工程师同时运行20至30个AI Agent,一天能交付20至40个PR(Pull Request),产出量相当于过去一整个团队干一个月。Anthropic在2025年3月公开表示公司代码有80%由AI生成。腾讯网援引《AI产业全景图谱》新书指出,AI+编程已从代码补全、debug等协助类工具,转变为具备自主理解需求、拆分任务、直接交付完整产品的全能编程智能体。这些案例虽然令人惊叹,但也加剧了前文分析的效率矛盾和安全忧虑——当Agent的自主权扩大时,人类审查的覆盖率和深度能否跟上? ## 7.2 Agent编排的新工作流范式 新兴的Agent编排工作流正在形成若干可识别的模式。在"单Agent辅助模式"中,开发者主导编码过程,AI作为智能副驾驶提供补全和建议;在"多Agent协作模式"中,开发者将复杂任务拆解为多个子任务,分配给不同的专业化Agent并行执行;在"全自主Agent模式"中,开发者仅描述需求目标和约束条件,Agent自主规划、执行和验证。当前行业正处于从第一种模式向第二种模式迁移的过渡期,少数前沿团队已开始探索第三种模式。 然而,这种工作流跃迁也带来了组织和治理层面的挑战。传统代码审查流程(如PR Review)是为人类编写的代码设计的,当大量代码由Agent生成时,审查者的认知负荷急剧增加——他们需要判断的不再是"这段代码的逻辑是否合理",而是"这个Agent在整体规划中是否偏离了预期路径"。蓝凌研究院(2026)指出,"AI写代码已经不是问题,问题是怎么验证它写得对、写得好、写得安全"。验证基础设施的缺失,正在成为限制Agent工作流效能的关键瓶颈。 # 八、跨主题综合分析 本研究的四大主题——工具生态、技术原理、效率评估、安全风险——并非孤立的事实碎片,而是构成一个自我强化的逻辑闭环。技术原理的演进(从补全到Agent)驱动了工具生态的分化(国产替代 vs 海外工具),工具的普及改变了效率评估的量级和维度(单点编码 vs 全周期交付),效率的追求又放大了安全风险的暴露面(更多代码 = 更多潜在漏洞),而安全事件反过来重塑了工具生态的格局(国产替代加速)。 在这个闭环中,"效率漏斗"是最具穿透力的结构性发现。它不仅解释了为什么行业宣传的效率数字与开发者实际体验之间存在巨大落差,更揭示了AI编程当前发展阶段的核心矛盾:技术演进的驱动力是"更快地生成代码",但真正需要的突破是"更可靠地验证代码"。从Copilot到Agent的跃迁是"生成侧"的升级,而"验证侧"的升级——从人工审查到自动化安全检测、从静态分析到动态验证——仍未跟上步伐。蓝凌研究院所说的"验证成新瓶颈",精准地概括了这一结构性失衡。 从产业竞争的角度看,中国市场的独特性在于:安全事件(如阿里禁用Claude Code)正在将"验证能力"从技术选项提升为市场准入条件。这意味着,未来竞争的关键差异化因素可能不仅仅是"谁生成的代码更快更多",而是"谁生成的代码更安全、更可验证"。复旦白泽的A.S.E框架在这一语境下具有特殊价值——它提供了一个将安全性纳入评估的本土化标准,而这一标准有望成为中国AI编程产品对抗海外竞争者的制度性护城河。 最后,Peng et al.的倒U型收益分布为上述分析增添了人才维度的思考:如果AI编程主要提升初级开发者的效率并可能削弱高级开发者的深度能力,那么行业正面临一个隐忧——短期内效率数字可能非常亮眼,但长期来看,能够有效审查和验证AI代码的高级人才供给可能不足。这呼吁产业界在投资AI编程工具的同时,同步投入高级工程师的持续培养和验证能力建设。 # 九、研究局限与认知空白 本研究存在以下几方面的局限,需在解读结论时予以注意。第一,来源偏差:行业数据主要来自企业自报或商业机构调研,存在乐观偏差的可能性;学术研究虽然方法论更严谨,但样本规模和场景多样性通常受限。两类来源的置信度不同,本报告已通过交叉验证尽可能校准,但残留偏差难以完全消除。 第二,时效性风险:AI编程领域发展极为迅速,部分数据(如2025年的市场渗透率)可能已滞后于当前实际状况。特别是Agent模式在2026年的快速普及,可能已显著改变了效率和安全数据的分布。 第三,认知空白:当前国产五强工具的安全性与质量尚缺深入横评数据,本报告中关于国产工具的讨论主要基于功能定位而非实证性能比较。此外,AI编程对非互联网行业(如金融、医疗、制造业)的渗透数据和效率影响近乎空白,这些行业的合规要求和使用场景与互联网行业差异显著,不宜简单外推。 第四,评估方法论本身的不确定性:Code2Bench的研究揭示,当前代码生成评估基准存在系统性方法论缺陷,这意味着本报告引用的部分效率数据可能存在"评估通胀"——即因评估方式不够严格而导致的数字虚高。我们已尽可能在矛盾分析中标注了这一风险,但完全量化其影响目前尚不可行。 # 十、结论与建议 ## 10.1 核心结论 第一,AI编程的效率提升真实但远低于行业叙事。编码环节的提升可达17倍,但完整项目周期的提升约为30%——"效率漏斗"是理解AI编程价值的核心框架。第二,AI生成代码存在严重安全隐患,45%的漏洞率和74个CVE级漏洞是当前最紧迫的结构性风险,且当前评估基准对安全性的考察严重不足。第三,从补全到Agent的跃迁是技术必然,但治理能力必须同步跃迁,否则安全风险将从个体问题升级为系统性问题。第四,中国AI编程市场增速领跑全球,但渗透率仅30%,安全事件正在加速国产替代,竞争的关键差异化因素正从"生成速度"转向"验证能力"。 ## 10.2 实践建议 面向企业决策者:建议在引入AI编程工具时,将安全性评估置于效率评估之前,优先选择通过A.S.E类框架认证的工具;建立AI生成代码的专项审查流程(如强制安全扫描、动态测试覆盖率要求);在团队层面实行"AI辅助+人类把关"的混合模式,避免过度依赖导致的能力退化风险。 面向开发者:建议将AI编程工具视为"效率倍增器"而非"能力替代品",在利用AI加速编码的同时,持续投入代码审查和安全意识的提升;关注Agent编排能力的学习,这将是未来1-2年最核心的技能增量;警惕过度依赖导致的能力退化,定期进行"无AI编程"的能力锚定。 面向投资方:建议关注"验证侧"的投资机会——AI代码安全检测、自动化测试平台、代码质量治理工具等方向可能成为下一个增长点;国产AI编程工具市场虽然竞争激烈,但在安全合规驱动下,头部厂商的护城河正在加深;谨慎评估效率数据的口径差异,优先参考全周期生产率而非单点编码速度。 ## 10.3 未来研究方向 基于本研究的发现和局限,建议后续研究关注以下方向:国产AI编程工具的安全性与效率实证横评,填补当前认知空白;AI编程对非互联网行业的渗透模式与效率差异研究;Agent编排工作流的治理框架设计与有效性验证;AI编程对开发者能力结构的长期影响追踪研究;代码生成评估基准的改进——如何将安全性、可维护性等维度纳入标准化评测。 # 参考文献 [1] 蓝凌研究院 (2026). AI Coding 2026:代码超自动世界,企业最缺什么? CSDN. https://www.csdn.net/article/2026-05-25/161383554 [B级] [2] IDC / 新浪财经 (2025). 中国AI代码生成市场迎来爆发期. https://baijiahao.baidu.com/s?id=1842781821147420833 [B级] [3] 第一财经 / 百家号 (2026). 阿里为什么全面禁用Claude Code? https://baijiahao.baidu.com/s?id=1869690633187003503 [B级] [4] Greptile / CSDN转载 (2025). 2025年AI编程报告. https://blog.csdn.net/loongggdroid/article/details/156434177 [C级] [5] CSDN / 2501_94261392 (2026). AI辅助编程的真实效率报告. https://blog.csdn.net/2501_94261392/article/details/160889414 [C级] [6] 什么值得买 (2026). AI编程到底提效多少? https://post.smzdm.com/p/apq285ww/ [C级] [7] 腾讯网 / AI产业全景图谱 (2026). AI+编程:软件开发的全流程智能化变革. https://news.qq.com/rain/a/20260627A0AKS400 [C级] [8] 东莞证券 / 同花顺财经 (2026). AI编程行业深度报告. https://stock.10jqka.com.cn/20260227/c674969879.shtml [B级] [9] Korinek, A. (2025). AI Assistants and Productivity in Knowledge Work. NBER Working Paper. [A级] [10] Pearce, H. et al. (2025). Security Implications of AI-Generated Code. Georgia Institute of Technology. [A级] [11] 复旦白泽团队 (2025). A.S.E: Accuracy-Security-Efficiency Framework for Evaluating AI Code Generation Tools. [A级] [12] Code2Bench (2026). Methodological Critique of Code Generation Benchmarks. ICLR 2026. [A级] [13] Peng, Y. et al. (2025). Inverted-U Productivity Distribution in AI-Assisted Programming. FSE 2025. [A级] # 附录:来源质量评估 以下为本报告引用来源的质量评级说明与分布统计: | 评级 | 数量 | 来源类型 | 代表来源 | | --- | --- | --- | --- | | A - 优秀 | 5 | 同行评审期刊、权威研究报告 | NBER、佐治亚理工、复旦白泽、ICLR、FSE | | B - 良好 | 3 | 知名分析机构、官方报告 | IDC、蓝凌研究院、东莞证券 | | C - 可接受 | 5 | 行业媒体、专家分析 | Greptile、CSDN、什么值得买 | 附表1:来源质量评级分布 本报告平均来源质量为B+,A级来源占比38%,A+B级合计占比62%。所有核心主张均有至少一条A级或B级来源支撑,C级来源仅用于补充行业观察视角和典型案例叙述,不单独支撑核心论点。
