很长一段时间以来,我一直在构建一个复杂的AI系统。
它最初是一个相当具体的产品。随着时间推移,它逐渐扩展到包含执行、工具、记忆、权限、长时间运行的任务、外部能力以及多种产品形态。随着它变得越来越大,我花在思考如何再添加一个功能上的时间越来越少,而花在思考另一类问题上的时间越来越多:一项能力应该归属于哪里?谁拥有解释它的最终权威?哪些决策应该留给模型,哪些保证必须来自软件?今天帮助我们交付的机制,明天会不会变成我们无法摆脱的边界?
这些问题看起来像是架构问题。但在实践中,它们改变了我对软件本身的思考方式。
传统软件是围绕指令构建的。程序员预先定义路径,机器精确地遵循它:如果发生A,就做B;如果输入是C,就返回D。软件的许多价值来自于将人类的判断转化为稳定、可重复的步骤。
AI改变了这种安排。机器第一次能够在有意义范围的情境中决定下一步做什么。我们不再需要枚举每一条路径。我们可以描述一个目标,提供一个环境和反馈,让模型制定计划、选择工具、观察结果并进行调整。
这可能会造成一种印象:随着模型能力越来越强,软件变得越来越不重要。
我的经验让我得出了相反的结论。软件并没有消失。它所承担责任的对象正在改变。
过去,软件告诉机器下一步做什么。现在模型可以选择下一步,但软件仍然必须回答另一组问题:什么是真实的?什么是被允许的?什么算作完成?出问题时谁负责?一次智能行动如何成为人们可以信赖的结果?
我逐渐相信这一点:
模型生成可能性。软件将可能性转化为秩序。
软件不再仅仅是围绕AI的指令集。它正在成为AI所栖居的世界的制度层。
以下原则是在我构建、丢弃、重建和验证这个系统不同部分的过程中逐渐形成的。它们不是针对某个特定产品的架构指南,也不是一种通用的方法论。它们只是我现在认为值得带入下一个构建项目中的想法。
1. 在今天的界面定义目的地之前,先定义你在构建什么
一个长期运行的项目中最危险的漂移不是把某个功能做错。而是逐渐忘记你最初要构建什么。
产品总是通过一个具体的入口开始。它可能是一个聊天界面、一个桌面工具、一个编辑器、一个浏览器或一个工作台。因为这些事物可见且可用,它们很容易开始反向定义整个项目:项目变成了今天存在的那些屏幕,而它的未来变成了当前最成熟功能的扩展。
但当前的产品往往只是验证目的地的载体,而不是目的地本身。
我学会了刻意区分三件事:我最终想要构建什么、支持该目标所需的共同基础,以及用户今天真正可以使用什么。
三者必须相互连接,但绝不能相互冒充。
如果你只谈论目的地,项目就变成了没有交付物的愿景。如果你只谈论当下,项目就会被当前的界面和临时实现所困。如果没有共同基础,每一种新产品形态都必须重新发明执行、状态、权限和记忆。
这种区分改变了我许多决策。我不再仅仅通过问“在哪里实现最快”来决定一个功能应该归属何处。我开始问它究竟是当前产品独有的体验,还是未来产品也会需要的系统能力。临时实现可以是不完美的,但不应该让它重新定义长期目标。
一个长寿命系统首先需要的不是路线图。而是一种层次感:什么是方向,什么是基础,什么仅仅是系统今天触达用户的方式。
2. 在问“我们如何构建它?”之前,先问“谁拥有它?”
软件中的许多复杂性不是由过多的代码引起的。而是由没有归属的责任引起的。
当一个概念没有明确的所有者时,它自然会开始复制。界面保留一份状态,后端保留另一份,配置文件保留第三份。没有哪一份明显是错的,但当它们不一致时,没有哪一份能给出最终答案。
然后系统开始增长同步逻辑。一个地方的变化必须通知另外两个地方。通知偶尔会丢失,所以添加了恢复机制。恢复顺序被证明不稳定,所以添加了补偿机制。补偿又产生了另一个状态,所以添加了对账机制。代码越来越努力地工作,而系统变得越来越难以解释。
我以前会立即被“如何连接一切”的问题所吸引。现在我首先问不同的问题:
- 这个概念真正属于谁?
- 谁创建它、解释它并终结它?
- 如果它被禁用、迁移或删除,系统的哪一部分理解这一变化的完整含义?
- 其他组件接收的是真相,还是仅仅是从它投射出来的视图?
只有在这些问题有了答案之后,连接策略才重要。
“所有权”听起来像是一个代码组织术语,但我认为它表达了一个更广泛的原则:任何持久存在的东西都需要有一个对其整个生命周期负责的主体。否则,每个人都可以改变它,但没有人真正理解它。
这也让我强烈倾向于为每一个事实设置唯一的最终解释者。其他组件可以观察、缓存、呈现和使用该事实,但它们不应该发明同样权威的版本。
复杂系统不怕拥有大量状态。它们怕的是拥有太多权威。
3. 整合机制,而非用户体验
随着系统增长,为每个新需求创建新机制既是最自然也是最危险的选择。
一项能力略有不同,所以它获得了一条新的注册路径。一个用例有些特殊,所以它获得了一个新模式。一份状态还没有明显归属,所以它获得了一个独立的存储。一条新的执行路径不方便集成,所以它绕过了现有路径并创建了另一条。
每个决策孤立来看都合理。合在一起,它们就产生了用于发现、权限、加载、更新、禁用和恢复的平行系统。真正的代价不是多出几个类。而是未来每一项能力都必须在所有这些平行机制中重新回答同样的问题。
我逐渐学会了区分两种多样性:用户体验到的多样性和系统内部必须维护的多样性。
用户可以而且应该遇到非常不同的产品、入口和交互。不同的情境值得拥有自己的语言和心理模型。但在这些体验之下,能力可以通过少量稳定的机制进入系统、获得权限并拥有生命周期。
整合机制,而非体验。
这条原则的难点在于抵制因为更容易接线就创建新东西的诱惑。在引入另一个机制之前,我现在会问:当前机制是真的无法表达这一需求,还是新机制在短期内只是更方便?
“它看起来有点不同”通常不足以成为在系统中创建新轴心的理由。
稳定的机制不是为了架构图看起来更整洁。它们降低未来的认知成本:模型、开发者和产品不再需要在几种几乎等价的做事方式之间猜测。
4. 软件应该约束后果,而非规定模型的思考过程
这是我的想法改变最大的地方。
当模型行为不一致时,自然的冲动是在软件中编码理想的工作流。要求先研究再规划,先规划再执行,一个阶段完成后才能开始下一个阶段。每次更改后都运行验证。每当模型看起来可能走错路时,就再加一道运行时关卡。
这些系统通常在设计时出于善意。但它们往往会变成僵化的流水线。一个本可以根据任务的大小和形态调整方法的模型,却被迫即使面对简单请求也要完成每一个阶段。多重防护栏还可能相互作用,制造出模型无法逃脱的死锁。
我开始用一个更简单的测试来判断什么应该属于软件、什么应该留给模型:这条规则约束的是一种方法,还是一个后果?
“编辑前先阅读”“实施前先提交方案”“记得在最后进行验证”都是方法。它们属于提示词、技能和上下文,模型可以在那里理解并灵活运用它们。
“未经授权不得访问数据”“未经确认不得造成不可逆的损失”“不得绕过用户明确禁用的权限”是对后果的边界。它们不能依赖模型去记住。软件必须强制执行它们。
针对重复、空转和循环的机械式防护栏也应谨慎使用。第一次尝试应当被允许。当系统阻止某个操作时,它应当解释原因。而且它必须提供一条清晰的恢复路径,而不是把模型困在防护栏后面。
这改变了我对模型与软件之间分工的理解:
把目标和方法交给模型。把不可协商的边界交给软件。
如果软件规定了推理过程的每一个步骤,我们只不过是用 AI 重建了一个工作流引擎。如果每一条边界都只存在于提示词中,我们就把真正的责任交给了概率。
一个好的系统会避免这两种极端。
5. 保持现实无歧义,而不是让模型听起来确定
模型的推理可以保持开放。它的语言可以变化。它的计划可以修改。但一旦它开始作用于现实世界,有些事实就不能有歧义。
一个任务是否真的开始了?一个变更是否真的发生了?某个权限是否已启用?哪个会话拥有某个结果?这些问题的答案不应因为由哪个界面来查看而改变。
我越来越倾向于把软件视为 AI 系统的现实层。模型可以提供对世界的解读,但软件记录的是世界中实际发生了什么。
这意味着界面可以表达状态,但不能成为状态本身。附加在请求上的参数可以表达意图,但不能临时凌驾于持久权限之上。缓存可以提升性能,但不能获得与其来源相同的地位。当系统无法确立某个事实时,它应当诚实地停下,而不是为了维持流畅感而猜测最方便的答案。
传统软件同样需要一致性,但 AI 让它变得更加重要。模型极其擅长从不完整的信息继续推进,并生成听起来合理的解释。当底层事实已经出现分歧时,顶层的智能反而会让矛盾感觉更加连贯,让问题隐藏得更久。
因此我的原则很简单:允许模型不确定,但不允许系统事实变得有歧义。
当系统不知道时,它应当说它不知道。当权威数据已损坏时,它应当拒绝行动。当两个来源冲突时,它应当要求解决冲突,而不是悄悄选择更容易的那个。
智能可以与歧义共处。制度必须守护现实。
6. 可逆性是自主性的前提
我们常常把 AI 自主性定义为向用户提更少的问题。但决定用户是否愿意放手的,不是确认对话框的数量。而是系统是否让他们能够看见、停止、纠正和恢复。
如果每一步都需要确认,AI 就变成了一件需要不断点击的半自动工具。如果什么都不需要确认,也没有记录或回退路径,自主性就变成了失控。
我开始更关注另一组品质:过程是否可见?操作是否可以中断?变更是否可以审查?历史是否可追溯?失败之后,系统能否恢复到一个含义清晰的状态?
可逆性不是出错时的额外一层保险。它是授予权限的前提条件。
当一个操作容易撤销时,系统可以给模型更多尝试的空间。当一个操作不可逆时,系统应当收紧权限,把决定权交还给用户。自主性的程度不应由我们对模型有多乐观来决定。它应当由后果是否可恢复来决定。
这也改变了我对“完成”的定义。完成不是界面上的一个绿色状态。它意味着系统已经把结果、剩余风险和恢复路径都表达清楚。当一个长时间运行的任务被中断时,动画不能就这么消失。当一个变更被拒绝时,不能留下半截执行状态。当一个能力被禁用时,恢复逻辑不得在下次应用启动时悄悄把它带回来。
我们越想让人工智能独立行动,就越要仔细设计它停止和返回的方式。
7. 不要只修 Bug。让每一次失败都留下一条原则
任何长期运行的项目都会遇到许多失败。区分项目的不是失败是否发生,而是失败之后以什么形式留存下来。
最弱的应对是让眼前的症状消失。更好的应对是找出并修复根本原因。最有价值的应对则更进一步:这属于哪一类失败?同样的失败还可能通过哪些其他路径回来?系统是否缺少一条规则、一个单一归属者,或一种能防止问题重新滋生的结构?
我养成了一个习惯:不只记录症状和修复。我还记录根本原因、归属者,以及能够防止回归的证据。一个只被症状描述、没有责任边界、没有证据的问题,还没有成为知识。
一个补丁修复一次失败。一条原则减少一整类失败。
如果一个同步 Bug 靠再加一条通知来修复,它日后可能在另一种事件顺序下再次出现。如果这次失败反而确立了一条原则——每一份状态只有一个归属者——未来的设计自然会包含更少的同步链。如果一个旧的入口点造成权限歧义,再加一个特例只会多出一个分支。确立一个意图只有一个规范入口点,就给了系统一条持久的决策规则。
我认为在 AI 参与软件开发的今天,这一点更加重要。代码产出更快了,局部修复变得更便宜了。没有原则,系统会以前所未有的速度积累看似合理的重复实现。
AI 可以帮助我们更快地写补丁。但每一次失败应当教会系统什么,仍然必须由人来决定。
一个项目真正的价值积累,不是代码量或关闭的工单数量。而是过去的失败成本是否已被转化为原则,从而防止同样的成本再次付出。
8. 不要把完成的外观当作证据
AI 非常擅长制造完整感。它会生成结构良好的解释、语法正确的代码和令人安心的总结。但工程中最困难的部分往往存在于这层表面之外。
界面没有显示错误,并不能证明后台没有东西在继续运行。一个能力在开发环境中可用,并不能证明它存在于交付给用户的产物中。在一个平台上验证通过,并不能证明在另一个平台上行为相同。代码通过类型检查,并不能证明旧的权威路径已从系统其余部分消失。
久而久之,我问“完成了吗?”的次数变少了,问“什么证据足以证明它完成了?”的次数变多了。
证据不是一个万能的绿色指示灯。不同的层次回答不同的问题。代码检查确立局部结构。运行时验证确立真实的执行路径。产物检查确立用户实际会收到什么。恢复测试确立系统在非理想条件下是否保持含义。
在 AI 时代,验证不仅是为了防止模型写出错误的代码。它还要对抗一种更隐蔽的风险:一个结果可以在逻辑上如此令人信服,以至于所有人都愿意相信它已经存在于现实中。
我最重要的一条规则之一是:不要从表面状态推断底层事实,不要从局部通过推断全系统正确,不要把未经证实的未来当作今天已交付的能力来呈现。
尊重现实有时意味着承认:“这还没有完成。”这种诚实不会削弱产品。它给系统一个可靠的起点,让它继续演进。
当AI能够决定下一步做什么时,软件的意义何在?
回顾过去,我从这项工作中带走的东西并不是某一种特定的架构。而是一种对AI软件的不同看法。
过去,软件试图枚举路径。未来,更多路径将由模型在当下生成。软件不再需要为智能规定每一个动作,但它确实需要提供一个稳定的世界:清晰的身份与归属、不可绕过的边界、保留现实的状态、退出与恢复的方式,以及能够确立结果是否真实的证据。
这类似于制度与个人之间的关系。
机构不会替每个人思考,也不应把每一个行动都变成固定的程序。它们界定共同现实、权利、责任,以及当事情出错时会发生什么。好的机构为行动留出空间,同时防止自由以牺牲秩序为代价。
在人工智能时代,软件正开始扮演类似的角色。
它不是模型的大脑,也不应与模型争夺工作。它是模型所处世界的法律、账本、边界与记忆。它把当下即兴的聪明举动转化为稳定的能力。它把偶然的成功转化为可复制的产品。它让用户能够把更重要的工作托付给AI,而不必以盲目信任换取便利。
我不认为AI会让软件变得不那么重要。恰恰相反,模型的能力越强,软件就越必须回答那些模型不应自行决定的问题。
什么是真实的?
什么是被允许的?
谁拥有它?
失败后我们如何回归?
什么证据能证明完成?
模型探索答案。软件守护问题。
我们正在从人们为机器写下每一步的时代,迈向人们确立原则、机器探索路径的时代。软件的角色也随之转变:从描述动作到确立秩序。
模型生成可能性。
软件让这些可能性值得信赖。