今天我用AI完成了一个小任务。感觉这个案例特别适合用来介绍AI的实战原则，所以写了这篇文章来分享一下。

任务本身是给这个blog里的每一篇文章都加一行summary，这样可以帮助搜索引擎理解这个网站的内容，从而提升这个网站的排名（SEO）。这个任务看起来简单，其实有很多坑，一不小心就会陷入AI鬼打墙、不可靠、使用繁琐的陷阱。下面主要分享在这个过程中我做了哪五个重要的决策，来让整个流程变得稳定可靠。

## 决策一：用本地Coding Agent，而不是ChatGPT

我做的第一个决策是：用Cursor/OpenCode作为讨论的平台，而不是ChatGPT。这件事其实并不显然，因为整个项目的开始来自于我想给这个网站做SEO。直观上看，这是个更适合ChatGPT的聊天性质的任务。但是我仍然坚持用了OpenCode。这里面最根本的原因是摩擦。

具体地说，摩擦在两个方面。第一是上下文传递的摩擦。用ChatGPT我需要把我的博客的内容甚至代码复制粘贴给它，或者让它去写代码抓取这些文章的内容。但在OpenCode里，我只要用@指定我的博客所在的文件夹就好了，摩擦小很多。

另一个方面是落地的摩擦。比如我们在ChatGPT里面通过聊天得出了结论：这个网站需要增加Summary元数据。为了把这个想法落地，我需要把我和ChatGPT来回几轮的聊天记录全部复制粘贴到Cursor/OpenCode里面去，然后再调用另一个AI来改文章的内容。相比之下，如果从头就在OpenCode里面做讨论的话，讨论之后立刻就能落地。

所以我做了这第一个决策：对几乎所有任务，抛弃基于聊天的AI环境，选择能执行的Agentic环境。为什么把这个决策放在第一个，是因为这是有和无的区别。摩擦一大，我们就懒得做下去了，整个项目花了时间，交付是0，纯浪费时间。只有摩擦小了，项目能继续下去，才有必要继续聊具体的方法和技巧。

## 决策二：动手之前，先定义成功，提供测试

我做的第二个决策是：在让AI动手生成任何summary之前，先让它写一个测试。这个测试做的事情很简单，就是检查所有.md文件，看有没有summary字段。如果不是100%的文件都有这个字段就fail，并且打印是哪些文件有问题。

为什么要先写测试？因为如果没有这个测试，AI说做完了，我也不知道它到底做完了没有。我确实可以抽查几篇，但300多篇文章，抽查没法覆盖全部。最后的局面就是我也不知道、AI也不知道，两个人都在wishful thinking。

但有了测试就不一样了。AI做完一轮，测试fail了，它自己就知道还有20篇没覆盖，下面就会重新看这些文章。测试通过了，就是100%完成了。不需要人工抽查，不需要猜，一切100%都是确定的。

这就是我们一直强调的[feedback loop](/agentic-ai-crisis.html)。很多人用AI陷入踢一脚动一下、动完了发现不对，再踢再动的循环，觉得AI好难用，根本原因就是没有建立反馈机制。AI不知道什么叫"做完"，你也不知道AI做到什么程度了。这是要首先解决的核心问题。确定性的测试就是一个非常有效的解决方法。事实上，只要这种测试到位了，后面三个决策都是锦上添花的东西。

所以在开始任何任务之前，我都会先问自己：我/AI有没有一个确定性的方式来判断任务完成了没有？如果没有，先把这个机制建起来。

## 决策三：让Agent自己去干，而不是我来写程序调用API

第三个决策是：我没有写程序去调用LLM API来生成summary，而是让coding agent自己去做这件事。

更详细的原因在[这篇文章](/result-certainty.html)中有解释。虽然让AI做概括听起来调个API就搞定了。但仔细想想，这里有很多corner case：有的文章已经有summary了不要重复加，有的metadata格式不一致，有的位置需要调整。如果写程序处理这些情况，代码会特别复杂，调试成本高，进展速度慢。最后可能AI会花大量的精力去调怎么处理这些细节。

另一种思路是用自然语言直接给Cursor/OpenCode布置任务："你去看一下XX.md，保证它有个面向SEO的summary元数据域"。这时候完成任务的主题就不是一个机械的程序，而是一个真的有智能，知变通的Agent。它会自己看情况处理——有summary就跳过，格式不对就调整，遇到特殊情况自己判断。

这就是把AI当agent用和把AI当工具用的区别。调用API的模式是：你写程序，AI是其中一个组件。这种模式确定性高，但灵活性低，遇到复杂情况反而更慢。而用Agentic AI，确定性从过程移到了结果上，你只需要讲清楚要什么结果。剩下的事，AI发挥自己的能动性和判断力自己搞定。

所以在我的工作流里，调用API是最后手段。能交给agent去做的，尽量交给agent。

## 决策四：用Divide and Conquer应对认知饱和

第四个决策是：我没有给一个agent一股脑布置300篇文章的任务，而是让它开了8个sub-agent，分配任务以后并行处理。

这里面的原因和context window saturation有关。一个agent一下处理300篇，前面可能还好，读了十几篇文章以后context window [会被占满](/wide-research.html)，后面就开始偷懒、跳文章、或者忘了前面踩过的坑。这和人有点像，认知负荷一高就会丢三落四，或者开始敷衍。

另一个原因是sub-agent是coding agent原生支持的功能。我不用自己写并发逻辑、分配任务、汇总结果。这些plumbing work都被外包出去了。我只要用一两句话描述一下这个工作流就好。

很多人用AI的时候没有意识到这个问题。他们没有针对AI的缺陷思考，预测到里面的坑，就用最符合直觉的方法去布置任务。但像我们管理下属的时候要知人善任一样，我们要意识到AI的认知资源尤其有限，context window是一种需要管理的稀缺资源。任务量太大，质量必然下降。所以任务量大的时候，我会主动考虑拆分，而不是让一个agent扛所有东西。

这个决策和前面几个的关系是：决策二保证结果是对的（测试通过），决策三保证过程是灵活的（agent自己处理corner case），决策四更进一步通过规避一个必然出现的坑，保证处理得又快又好。

## 决策五：保证Prompt Self Contained（自举）并且结果导向

第五个决策是：给AI的指令讲清楚所有的信息（不指望它读心），而且着重说acceptance criteria是什么，而不是每一个步骤怎么做。

我的prompt大概是这样：

> 对于blog/content下面每一篇.md文件，从SEO的角度写一个summary域放到metadata里。你可以用sub-agent来做。先看几篇文章找到感觉，然后想一个prompt，让不同的sub-agent分别处理不同的文章。开8个agent并行处理，每个agent负责写summary并直接编辑.md文件。另外，我希望有个测试能check summary coverage，如果coverage不到100%测试就fail。你的目标就是把这个测试搞到100%让它能过。

注意我没有告诉它具体怎么写这个测试程序、怎么处理各种corner case。

这是很多人容易搞反的地方。他们给AI写指令的时候，事无巨细地规定每一步怎么做。这其实是在把AI当程序用，浪费了Agentic AI的主观能动性。AI不是一个只会照本宣科的乙方，它有很强的判断力和执行力。我们要发挥它的主观能动性，但同时给它一个足够清晰的边界。

我总结写prompt有两个原则。第一，context要给足，不要指望AI能读心。它不知道metadata结构是什么样的。这些信息要么直接给，要么要保证它自己能搞清楚（比如这里我们给了具体路径，它可以通过读文件搞清楚）。第二，从结果出发，而不是从过程出发。你告诉AI你要什么，让它自己想怎么做。除非你预测到某个环节不给具体指导它会出问题——比如前面的context window问题——否则不用讲那么细。

这个决策和决策三是一体两面：决策三是说把执行交给agent，决策五是说把指令也写成适合agent的形式。

## 总结：AI是一种杠杆

最后说一点感受。

这个任务，我用语音识别花了大概两分钟把指令讲给AI。然后AI自己折腾了45分钟：并行开8个sub-agent，处理各种边界条件，写测试，返工，跑通，commit。全程我就没再管了。这就是一种leverage。用两分钟的时间，撬动了AI 45分钟的工作量。更准确地说，用5%的时间控制了100%的工程产出。

而且现在的Agentic AI能力已经足够强，可以长时间自主工作。我们不需要盯着它干活。只要讲清楚deliverable是什么、acceptance criteria是什么，就可以去干其他事了。这就带来了一种新的可能：scalable agentic workflow。比如我们用两分钟撬动一个Agent A，让他忙45分钟。然后这个时间我们再去指挥Agent B，C，D，... 同时启动多个AI并行推进。这样脑力负担确实会很高，但这是在单Agentic workflow的基础上，再进一步实现10倍生产力的切实可行的途径。

说完了10倍生产力的一面，这个项目的另一面是，有用AI的意识，但是方法不对——在ChatGPT里讨论、没有测试机制、让一个AI包办所有。这些决策做错了，我们可能要折腾几个小时才能做完，甚至鬼打墙做不出来。同一个任务，甚至同一个LLM，会用和不会用，决策做的质量高低，就是从从容容游刃有余和吃力不讨好，比人工做更慢的差别。

<script async data-uid="49b08a3e73" src="https://yage-ai.kit.com/49b08a3e73/index.js"></script>
