八项原则
有八个理念,把“只能成功一次的构建”和“能够持续运行下去的构建”区分开来。它们都不需要什么 特殊的技术。大多数只是关于一段逻辑应该放在哪里、由谁来修改它的决定。
1. 可溯源性(Provenance)
含义。 答案中出现的每一个数字,都能追溯到它的出处;如果数据中没有这项信息,答案就应该 直接说清楚。一个没有出处的数字,并不是“便宜版”的有出处数字——它是另一种完全不同的东西,而 这种差别在有人据此采取行动之前,是看不出来的。
做得好是什么样子。
- 答案中的每一个数字,都附有它所依据的记录,以及数据截至的日期。
- 每一条引用的文字,都会与它声称出自的文档进行核对,答案会说明它出现在该文档的哪个位置。
- 当数据中没有答案时,回复会直接说明这一点,绝不会编造一个数字来填补空缺。
- 每一类答案都有固定的格式——用哪些数字、来自哪些来源、按什么顺序排列。固定的格式能挡住 编造的数字,而仅靠成文规则做不到这一点。
常见的出错方式。
- 自信满满的总结。 一个由 AI 生成、读起来很顺的数字,背后却什么依据都没有。它之所以能 蒙混过审核,恰恰是因为读起来很顺。
- 装饰性的溯源。 来源列表只写出了文档名称,却没有指明具体的行或段落,导致没有人能在不 重做一遍工作的情况下真正核实这个数字。
- 被悄悄软化的结论。 一个已被标记的问题,在之后的处理中被悄悄降级,没有人注意到它发生 了变化。
- 只是“存在”的引用。 核实一条引文出现在原文中,只能证明这句话确实存在,不能证明它支持 了引用它所要论证的结论。
该问自己的问题。
- 从最近一份答案中任选一个数字。你能不问搭建这套系统的人,就追溯到它的出处记录吗?
- 答案是否带有一个“截至日期”?
- 找一条引文。系统能证明它出现在原文中,并说出具体位置吗?
- 当数据无法回答某个问题时,用户实际看到的是什么?
- 每一条引文,是真的支持它所附着的结论,还是仅仅出现在原文里而已?
2. 确定性(Determinism)
含义。 由 AI 来判断该套用哪一条规则;规则本身以数据的形式存在,人可以读取和修改它。运算 永远按同一种方式进行。任何有实质影响的结果,都不会只在 AI 自己的推理过程里算出来;同一个问题 问两遍,得到的答案应当一样。
做得好是什么样子。
- 运算始终经过同一个专门的计算步骤,每一项输入都清楚可见。
- 规则以人可以编辑的行或字段形式存在,因此修改一个阈值不需要开发者,也不需要发布新版本。
- 一条系统尚未实现的规则,会干脆地拒绝执行,而不是被悄悄跳过或凭空猜测。
常见的出错方式。
- 写进提示词里的规则手册。 把限制、标准或阈值写进 AI 的指令里,谁都无法编辑、引用或 审计它们。
- 可以绕过的计算器。 没有任何机制强制 AI 使用专门的计算步骤,于是在压力较大时它会自己算 出结果——通常是对的,直到某次悄悄地算错。
- 混在一起的规则。 系统自动套用的数字,和 AI 应当原样引用的措辞,被存放在一起,让人分不 清哪个是哪个。
- 反向表述的规则。 一条以“不得发生什么”的方式写出的规则,无论是人还是模型都很容易理解 反。把每条规则写成你想看到的那个条件,并且把一个“二选一”的要求当作一条规则来处理,而不是 拆成两条。
该问自己的问题。
- 输出中的每一个数字,具体是在哪里算出来的?
- 业务人员能不能不找开发者,自己修改一个阈值或限制?
- 把同一个问题问两遍,两次的答案是否完全一致?
- 当系统遇到一条它还没被教过的规则时,现在会发生什么?
3. 模块化(Modularity)
含义。 一个构建是由多个部件组装而成的,每个部件都有明确的职责和明确的接口约定,而不是 一大团纠缠不清的定制逻辑。只有当两个各自独立、指名道姓的用途,都在原样使用某样东西时,它才 算得上“共享”——而不是因为它看起来可以复用。
做得好是什么样子。
- 每个部件只做一件事,有明确定义的输入和输出,并且可以单独测试。
- 一个被称为“共享”的部件,确实有两个真实的使用方在原样使用它,其中至少一个用途并不是它最 初被搭建出来时所针对的那个。
- 两个内部细节被改动过的版本,老老实实地算作两个不同的部件,而不是假装成一个通用部件。
常见的出错方式。
- 靠愿望撑起来的“共享”。 一个被标注为可复用的部件,实际上只有一个真实使用方,于是下 一个团队继承了一个从未真正成立过的承诺。
- 分叉出来的副本。 第二个版本只是需要再调整一点,于是有人直接复制了整个部件,而不是把 它参数化。现在需要维护两样东西,却共用一个名字。
- 没有使用方的部件。 因为看起来“大概用得上”而被搭建出来,从此被永远维护下去,实际最多 只被用过一次。
该问自己的问题。
- 找一个被标注为“共享”的部件,说出它两个真实、各自独立的使用方是谁。
- 有没有哪个“变体”,实际上是靠改动核心逻辑、而不是靠改动配置来实现差异的?
- 一个部件能不能在不依赖真实账户或网络连接的情况下,单独完成测试?
- 这个构建里,有没有什么东西没有真实、指名道姓的使用方?它为什么还存在?
4. 无领域内核(Domain-free cores)
含义。 一个构建核心处可复用的逻辑——智能体的核心指令、一项共享技能——不带任何客户名称, 不带具体的业务流程,不带任何只属于某一家公司的细节。凡是特定于某一家公司的内容,都以配置的 形式随核心一起提供,而不是被写死在核心里面。
做得好是什么样子。
- 核心读起来像是在描述一个角色或一项职责,而不像是把某一家公司的流程写成了文字。
- 每一处特定于某家公司的细节,都通过配置传入,核心本身不包含任何超出该配置所声明范围的 内容。
- 同一个核心,只需更改配置,就能服务于另一家公司或另一个行业,不需要对核心本身做任何改动。
常见的出错方式。
- 写死进去的业务。 一段提示词或一段逻辑里,带着某一家公司的产品列表、专用术语或流程, 一旦该公司的数据发生变化,它就立刻过时了。
- 通用部件里出现的公司名字。 一个以某笔交易或某个客户命名的部件,下一个团队若不重新命 名、重新改写,就无法复用它。
- 说谎的配置。 有些设置项被声明了,核心却从未真正用到;有些则被核心用到了,却从未被声 明过——结果没有人能说清楚核心到底依赖什么。
- 写死的地址。 把某台机器的网络地址写进了工作流程里。这会把构建与某一个网络绑死,一旦要 迁移部署,就得逐个找出并修改所有用到它的步骤。应当用部署自身内部的名称来指代各项服务,并 向负责运行该部署的人确认这些名称。
该问自己的问题。
- 在核心逻辑里搜索公司名称、账户标识符,或任何特定于某个客户的术语,结果会找到什么?
- 这个核心能不能只靠更改配置,就服务于另一家公司或另一个行业?
- 配置里的每一项设置,核心是否都真的用到了?核心有没有用到任何配置里没有声明的东西?
- 有没有哪个工作流程里,包含着某台机器的网络地址?
5. 人的治理(Human governance)
含义。 一个关卡,就是针对某一具体事项、被记录下来的决定——谁做的决定、决定了什么、什么 时候做的——而且一旦出现异常,它默认拒绝放行,而不是默认放行。没有记录在案的决定,就没有任 何输出会被放出去。人默认在自己本来工作的地方做决定——一封带有“批准”和“拒绝”按钮的邮件,每个按钮都会打开一个确认页面;但“决定必须被记录下来”这一点不是可选的。
做得好是什么样子。
- 关卡会依据底层数据自己算出答案,而不是直接相信 AI 在同一轮对话里刚说过的话。
- 决定被记录下来时,包含谁做的决定、决定了什么、什么时候做的——由系统本身捕获记录,而不只是在聊天记录里看得到。
- 人在自己本来工作的地方做决定,文档就在眼前:一封按钮会打开确认页面的邮件。仅仅打开链接,不会决定任何事;只有页面上的“确认”才会。
- 任何有实质影响的事项都不会被自动批准。缺少决定会拦住输出,而不是默认放行。
- 这次请求会让流程暂停,而不是终止它。一旦决定落定,同一次运行会自行继续下去——完成任务, 或者把结果告知发起请求的人——不需要任何人再回来重新问一遍。
常见的出错方式。
- 背后空无一物的关卡。 一个本该由人来更新的状态字段,实际上没有任何东西在监听它、对它的变 化做出反应。它看起来像是有监督,实际上什么监督都没有。
- 填在单元格里的决定。 人被要求在数据表里选一个值来批准。那个值藏在一张他们没有理由打开的表格里,要保存后才会生效,也让人感觉不像是在做决定。
- 一打开就做出决定的链接。 一个只要被访问就会记录决定的按钮,会被第一个打开它的邮件扫描程序或预览功能替人做出决定。
- 对话里的“好”。 一个人对着 AI 打出“好”字,却没有任何持久的记录留下,于是这个决定只存 在于一份再也不会有人翻看的聊天记录里。
- 轻信的关卡。 审批环节直接读取 AI 提出的数字,而不是自己独立算出来,结果一个错误的数字 也能让自己获得批准。
- 自我批准的智能体。 智能体能够通过它做其他一切事情所用的同一个工具,自己写入决定——状 态、批准结果、决定人。记录决定的这些列,应当在记录创建时就取好初始值,此后拒绝智能体的任 何写入。
- 被当作“通过”的缺失结论。 一项尚未运行、或已经失败的检查,会让记录看起来和真正通过的 记录一模一样。应当给每一条记录都打上明确结论——连没问题的记录也不例外——并让此后的每一步 都等待这个结论,没有它就拒绝继续。
- 问完就挂起的请求。 决定被记录了下来,但之后没有任何环节继续推进。发起请求的人得自己留 意并主动跟进。
该问自己的问题。
- 找出最近一次决定的记录。是谁、决定了什么、什么时候?
- 如果审核人当时联系不上,会不会仍有东西被放出去?
- 这个关卡是自己算出答案,还是直接相信别人递过来的数字?
- 审核人本来在哪里工作、他们看到什么?那条路径是否曾以那个人的身份端到端走通过?仅仅打开链接,会不会决定任何事?
- 如果同一个决定被提交了两次,会发生什么?
- 智能体能不能写入那些记录决定的列?
- 当检查还没有给记录打上结论时,后面的步骤会怎么做?
- 分别试一试批准、拒绝,以及放着不管不处理。在每一种情况下,流程是否都会自行继续下 去——完成任务,或者把结果报告出来——不需要任何人问第二遍?
参见人工批准,了解人如何从一封邮件做出决定、确认页面做了什么,以及为什么单凭聊天回复算不上一项决定。
6. 原生优先(Native-first)
含义。 如果平台已经提供了完成某件事的方式——搬运数据、发送消息、等待某个人处理、连接到 另一个系统——就用平台提供的方式,而不要自己写自定义代码去做同样的事。如果你能 明确说出某项能力“这是平台的一个功能”,那么用自定义代码把它重新做一遍,只会把这项 能力藏起来、随着时间推移而老化,并且通常最终还需要一个内置方式原本根本不需要的凭证。
做得好是什么样子。
- 构建是由平台自身的构件一步步搭起来的,非开发者也能看懂。
- 与其他系统的连接,保存在平台自身的连接存储里,权限范围明确、可以复用,绝不会被嵌入到构 建的逻辑内部。
- 在确实需要自定义代码的地方,它只对拿到的输入做计算,不会自作主张地去调用其他任何东西。
- 任何例外情况都被记录下来:考虑过哪项能力、为什么没有使用它,以及满足什么条件就可以把这 段自定义代码退役。
常见的出错方式。
- 自建捷径。 用自定义代码去做平台本来就已经提供的事,通常是因为事先没有人查证 过。它往往需要一个内置方式本不需要的凭证,而且构建一旦搬到别处,它就会立刻失效。
- 被包装过的脚本。 一大段自定义逻辑,外面只裹着薄薄一层 AI。它能算出正确答案,却完全说 明不了这个设计本身是否站得住脚。
- 悄无声息的凭证。 一个登录信息或密钥被嵌在构建的某个角落,没有人记录下来,于是也就没 有人再回头把它清理掉。
该问自己的问题。
- 打开这个构建,一个非开发者能不能说出每一步在做什么?
- 在构建里搜索任何看起来像凭证或访问密钥的东西,结果会找到什么?
- 对每一段自定义代码:当时考虑过平台的哪项能力,为什么它不够用?
- 把这个构建搬到另一个账户或环境中,会有什么东西失效?
更多内容见原生优先。
7. 可运维性(Operability)
含义。 让构建持续运行,是搭建它的一部分,而不是事后才想起来补的东西:要有办法把它重置 回一个已知状态,要有一套固定的测试用例可以拿来打分,构建的每一种模式都要真正被跑过一遍,还 要有第二道独立的核查,检查第一道工作的成果。
做得好是什么样子。
- 构建的每个部分,都有一项不需要真实账户或凭证就能运行的测试,而且这些测试会在任何东西上 线之前自动运行。
- 一套固定的测试用例,每次都按同样的方式打分;跑不了的用例会被报告为“跳过”,绝不会被悄悄 算作“通过”。
- 第二道独立的核查会审查主流程,并标记出分歧之处,而不是自己悄悄把它们改掉。
- 构建可以在平台内部被重置回起始状态,而不是靠某个人从自己的笔记本电脑上手动恢复。
- 工具返回的错误信息简短,每一段对话的长度都控制在运行它的模型的上下文范围之内。
常见的出错方式。
- 没人看见的绿灯。 一份“测试通过”的报告,是被粘贴进来的,而不是被真正盯着跑完的。
- 各算各的分。 每个构建都自己发明一套比对结果与预期的方法,于是任何两份报告出来的分 数,含义都对不上。
- 重置不了的演示。 一个只能成功运行一次的构建,之后必须由它的作者亲自动手重新拼好,才 能再演示一遍。
- 未经核查的通过。 一个结果之所以被相信,仅仅是因为从来没有别的东西核查过它。
- 把状态当成结果。 一个步骤的状态告诉你的是这一步运行过了;它本该产出的那条记录,告诉 你的才是工作真的完成了。要核查的是那条记录。
- 被信任的标准答案。 测试的标准答案是人写出来的,也可能是错的。当一个结果与标准答 案不一致时,也要检查一下标准答案本身,并同时按原始标准答案和修正后的标准答案两个版本来报 告结果。
该问自己的问题。
- 你是不是亲自运行了这些测试,并且亲眼看着它们通过?
- 那套固定的测试集在哪里,最近一次打分是什么时候?
- 重置这个构建,它是否会精确地回到同一个状态?
- 是什么在独立核查主流程,它最近一次发现分歧是什么时候?
- 一个没有参与搭建的人,能不能仅凭安装包和文档,就把它装起来、跑起来?
- 对每一步而言,是哪一条记录证明了这项工作真的发生过?
8. 安全与主权(Security and sovereignty)
含义。 在这样的平台上,一个凭证是能打开很多东西的钥匙,而不是打开某个小地方的一个密 码。要这样对待它:严格限定权限范围,保存在配置里,绝不嵌入构建自身的逻辑,绝不提交进共 享文档,也绝不作为工作上下文的一部分,交给 AI 编程工具。
做得好是什么样子。
- 构建通过平台自身的连接来访问其他系统,因此它的逻辑里完全不包含任何凭证,可以安全地分享 或迁移。
- 任何刻意为之的例外,都被记录在构建本身上,写明它是什么、满足什么条件就能不再需要它。
- 自动化检查会在任何东西上线之前,拦下其余的问题:没有形似凭证的文本,没有内部机器名称或 文件路径,没有个人联系方式。
- 敏感字段的脱敏,由平台本身在底层数据上强制执行,而不是靠逻辑里一个可能被跳过或遗忘的步 骤。存放原始、未脱敏标识符的存储,绝不会与任何 AI 连接在一起。
- 哪些 AI 模型可以读取数据,是按每个部署、每个组织分别设定的。在个人数据被录入之前,要向负 责运行该部署的人确认:它所使用的每一个模型——无论用于对话、读取文档还是搜索——都运行在部 署内部。
常见的出错方式。
- 图方便的捷径。 为了回答一个棘手的问题,临时把存放原始敏感数据的存储直接连到 AI 上。两 者之间的隔离本来就是整个保障所在,而它就这样被悄悄打破了。
- 导出包里的凭证。 一个共享出去的包,之所以能用,是因为里面带着一个正式凭证,而它被交 给了团队之外的人。
- 分不清的凭证。 整个流程里,没有任何标记能把用完即弃的测试凭证和正式凭证区分开来,结 果构建可能用错了凭证。应当给测试凭证和正式凭证打上不同的标签,并在分享或运行一个构建之 前,先确认它手里拿的是哪一种。
- 未经检查的导出。 没有先打开看一眼,就把一份导出分享了出去。应当在每一份导出离开团队 之前打开检查,确认里面不含凭证、不含客户数据、也不含个人信息。
- 发送给 webhook 的凭证。 运行历史会保留每次运行的输入,以便日后可以回顾——这正是凭证绝 不该出现在 webhook 调用里的原因。应当发送一个标识符,让工作流程去使用一个已保存的连接。
该问自己的问题。
- 在即将分享出去的所有内容里,搜索任何看起来像凭证的东西,结果会找到什么?
- AI 编程工具能访问到的凭证,是不是一个权限有限、用完即弃的凭证?
- 敏感字段的脱敏,是由平台强制执行的,还是靠一个有人可能会跳过的步骤?
- 谁能看到原始的、未脱敏的数据?究竟是什么在阻止 AI 被连接到这份数据上?
- 你上一次分享给别人的包,里面实际上装了什么?
- 有哪些 AI 模型会读取这个构建的数据?其中有没有哪一个是在部署之外的?