文章指出AI Agent能从生成文本跃迁到操作真实系统,坏答案会变成坏状态变更,最关键的设计原则是让模型负责推理、由人类或规则系统负责执行授权。
AI 智能体的能力正在变得越来越强大。
它们可以搜索数据库、调用 API、修改工单、起草代码、更新记录、触发工作流,以及与生产系统交互。
而这改变了工程问题本身。
当 LLM 只生成文本时,一个糟糕的答案通常就只是一个糟糕的答案。
当 LLM 可以执行操作时,一个糟糕的答案可能变成一个糟糕的状态变更。
所以,智能体架构中最重要的问题不再是:
模型能想出该做什么吗?
而是:
谁来决定模型是否真的被允许执行?
这是两个截然不同的职责。
我认为针对生产级 AI 智能体最有用的原则之一,出人意料地简单:
用模型来推理,但不要自动赋予它执行权。
很多智能体演示可以归结为这样的结构:
User → LLM → Tool → Action
模型接收请求, 对应该发生什么进行推理, 生成参数。
这是一个极其高效的抽象。
但当工具能够影响真实世界时,这也是一个危险的设计。
同一个概率系统实际上在同时做两件事:
决定它认为应该发生什么;
授权执行那件事。
你可以通过提示词工程来尝试解决这个问题:
Always ask for confirmation before making important changes.
但这仍然只是一条指令。
它不是一道安全边界。
当比较两种架构时,区别就变得清晰了。
%%{init: {'theme':'base','themeVariables': {
'primaryTextColor':'#111827',
'secondaryTextColor':'#111827',
'tertiaryTextColor':'#111827',
'textColor':'#111827',
'edgeLabelBackground':'#FFFFFF',
'lineColor':'#4B5563'
}}}%%
flowchart LR
subgraph BAD["❌ Demo-Style Agent"]
direction LR
A["User"] --> B["🧠 LLM"]
B --> C["🔧 Tool"]
C --> D["💥 Real-World Action"]
end
subgraph GOOD["✅ Production-Oriented Agent"]
direction LR
E["User"] --> F["🔎 Evidence"]
F --> G["🧠 LLM"]
G --> H["🔍 Review"]
H --> I["🛡️ Code Gates"]
I --> J["👤 Approval"]
J --> K["🔐 Tool"]
K --> L["✅ Action"]
end
classDef bad fill:#FEE2E2,stroke:#DC2626,stroke-width:2px,color:#111827;
classDef good fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#111827;
classDef ai fill:#EDE9FE,stroke:#7C3AED,stroke-width:2px,color:#111827;
classDef guard fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#111827;
class A,B,C,D bad;
class E,F,J,K,L good;
class G,H ai;
class I guard;
第二种设计有更多的移动部件。
因为系统将以下内容分离了:
这些不应该总是属于同一个组件。
AI 工程中最常见的错误之一,仅仅因为模型可用就使用模型。
假设传入任务分为三大类:
已知机械条件
↓
确定性工作流
需要解释
↓
AI 调查
高风险或模糊
↓
人工审核
如果路由决策可以在代码中可靠地做出,就在代码中做。
def classify(task):
if task.has_known_failure_signal:
return "deterministic"
if task.needs_investigation:
return "ai_investigation"
return "human_review"
这里有趣的点是默认值:
human_review
let_the_model_guess
当问题确实需要解释时,LLM 是非常有价值的。
它们不需要成为周围一切的控制平面。
这也有实际的好处:
确定性回归测试
在真正需要智能的地方使用智能。
如果另一个系统组件需要检查模型的输出,不要让那个组件解析一段文字。
不要让模型生成:
I believe the likely root cause is...
而是返回更接近这样的内容:
{
"root_cause": "...",
"severity": "medium",
"missing_information": [],
"recommended_actions": [],
"citations": []
}
Schema 约束的输出改变了应用程序其余部分与模型交互的方式。
现在下游代码可以进行如下检查:
risk_ok = diagnosis.severity in {"low", "medium"}
citations_present = bool(diagnosis.citations)
模型不再只是产生 prose。
它在生成被更大系统消费的带类型数据。
随着智能体工作流变得越来越复杂,这个区别变得越来越重要。
假设 LLM 引用了文档:
issue-1842
应用程序验证了:
citation_id in retrieved_documents
引用是真实的。
但这只是证明了模型引用了检索返回的内容。
它不能证明检索返回的内容是有用的。
假设查询涉及一个并发 bug,但向量搜索返回了三个模糊相关的缓存事件。
三个文档都是真实的。
三个 ID 都是有效的。
LLM 仍然可以从它们构建出一个极其自信、引用完美、但完全错误的解释。
所以更强的检查可能看起来像这样:
groundedness_ok = all(
citation_id in retrieved_ids
and relevance_score[citation_id] >= MIN_RELEVANCE_SCORE
for citation_id in diagnosis.citations
)
现在系统检查两个不同的属性:
来源存在吗?
↓
溯源
来源是否足够相关?
↓
检索质量
这不是同一回事。
这引出了一个更广泛的教训:
"模型引用了一个真实来源"和"模型引用了支持其声明的证据"是不同的保证。
RAG 系统可以完全是引用有效的,但仍然是糟糕的 grounded。
一个常见的智能体模式现在看起来是这样的:
LLM A
↓
Generate answer
LLM B
↓
Evaluate answer
"PASS"
↓
Proceed
这已经比盲目信任一次生成要好得多。
但它仍然留下一个有趣的问题:
为什么另一个概率模型应该有最终决定权?
更强的架构将批评与执行分离。
LLM A
↓
Generate proposal
LLM B
↓
Critique proposal
Code
↓
Apply enforceable conditions
groundedness_ok = ...
risk_ok = ...
permission_ok = ...
approved = (
groundedness_ok
and risk_ok
and permission_ok
)
审查模型仍然可以产生非常有价值的东西:
This diagnosis appears weak because the cited evidence does
not fully support the proposed root cause...
这个解释对人类有用。
但系统不需要从模型响应中解析:
APPROVE
并把那个字符串当作权威。
区别很简单:
让模型解释,让确定性系统执行。
对于这类条件来说,这一点变得尤为重要:
这些通常更好地表示为明确的程序状态,而不是自然语言判断。
想象你的工作流图包含:
review → approval → execute
一切看起来都很安全。
但六个月后,有人重构了图。
一条捷径被引入:
review → execute
如果 approval 只作为编排逻辑存在,你只是通过改变一条边就移除了安全控制。
更强的设计把检查放在执行变更的函数内部。
def execute(state):
if not state.get("approved"):
raise PermissionError(
"Execution requires explicit approval."
)
perform_action()
现在你有了两层保护。
你不应该到达 execute。
执行边界说:
即使你到达了我,我也拒绝运行。
这就是纵深防御。
这个想法远不止适用于 AI。
安全敏感的属性应该尽可能在靠近被保护资源的地方强制执行。
许多系统技术上有人工审批屏幕。
但底层实现却出奇地脆弱。
也许工作流状态只存在于内存中。
也许流程只是在等待。
也许确切的行动在审批后才被重新生成。
更强的人工介入设计是这样的:
智能体提议行动
↓
工作流暂停
↓
[分钟 / 小时 / 天]
↓
人工审批
↓
精确的已审批行动执行
这创建了一个容易忽略的基础设施要求:
暂停工作流的状态必须独立于应用程序进程而存在。
如果应用程序容器消失了,审批状态不能随之消失。
Agent Runtime
↓
Checkpoint
↓
Persistent Storage
这让进程可以完全重启,同时工作流保持可恢复。
这在真实部署中很重要,因为:
部署会替换运行中的进程
一个人工审批系统只在一个 Python 进程保持活着时才有效,那它并不是真正持久的人工审批。
这里还有另一个微妙的细节。
假设智能体向用户展示:
我建议发布评论 X。
人类批准了它。
然后应用程序向 LLM 询问:
生成最终评论。
这产生了一个新的输出。
人类从未批准过这个新输出。
相反,批准通常应该绑定到一个具体的提议动作:
proposed_action = build_action(state)
approved = wait_for_human(proposed_action)
if approved:
execute(proposed_action)
模型方差没有第二次机会。
人类审查的产物应该就是穿越变更边界的产品。
一旦你结合了这些想法,架构开始看起来不再像一个带有工具的聊天机器人,而更像一个正经的软件系统。
%%{init: {'theme':'base','themeVariables': {
'primaryTextColor':'#111827',
'secondaryTextColor':'#111827',
'tertiaryTextColor':'#111827',
'textColor':'#111827',
'edgeLabelBackground':'#FFFFFF',
'lineColor':'#4B5563'
}}}%%
flowchart TD
A["📥 用户请求 / 事件"] --> B["🔎 收集证据"]
B --> C{"🧭 确定性分类"}
C -->|"已知 / 机械性"| D["⚙️ 确定性路径"]
C -->|"需要调查"| E["🧠 LLM 推理"]
C -->|"模糊 / 高风险"| H["👤 人类审查"]
E --> F["🔍 独立 LLM 审查"]
F --> G{"🛡️ 代码强制门控"}
G -->|"接地 ✓<br/>风险 ✓<br/>权限 ✓"| I["⏸️ 人类批准"]
G -->|"任意门控失败"| H
D --> I
I -->|"已批准"| J["🚀 执行动作"]
I -->|"已拒绝"| K["🛑 停止"]
J --> L["🌐 外部系统"]
classDef input fill:#F3F4F6,stroke:#4B5563,stroke-width:2px,color:#111827;
classDef deterministic fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#111827;
classDef ai fill:#EDE9FE,stroke:#7C3AED,stroke-width:2px,color:#111827;
classDef gate fill:#FFEDD5,stroke:#EA580C,stroke-width:2px,color:#111827;
classDef human fill:#FEE2E2,stroke:#DC2626,stroke-width:2px,color:#111827;
classDef execute fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#111827;
class A input;
class B,C,D deterministic;
class E,F ai;
class G gate;
class H,I,K human;
class J,L execute;
每个组件都有不同的职责。
我们实际知道什么?
基于现有证据,这可能意味着什么?
这种推理可能有什么问题?
机器可强制的条件是否满足?
我们真的希望这个动作发生吗?
这个确切的操作此刻是否被授权?
这些是不同的的问题。
试图用一个 LLM 调用来回答所有这些问题会造成不必要的耦合。
这可能是我发现最有用的心智模型。
一个 AI 智能体不需要要么:
完全确定性
完全 AI 控制
系统可以故意在概率阶段和确定性阶段之间交替。
%%{init: {'theme':'base','themeVariables': {
'primaryTextColor':'#111827',
'secondaryTextColor':'#111827',
'tertiaryTextColor':'#111827',
'textColor':'#111827',
'edgeLabelBackground':'#FFFFFF',
'lineColor':'#4B5563'
}}}%%
flowchart LR
A["🧠 LLM<br/>推理"] --> B["📋 提议的动作"]
B --> C["🔍 独立审查"]
C --> D{"🛡️ 确定性门控"}
D -->|"通过"| E["👤 人类批准"]
D -->|"失败"| F["🚨 升级"]
E -->|"批准"| G["🔐 执行边界"]
E -->|"拒绝"| H["🛑 停止"]
G --> I["⚡ 工具 / API"]
subgraph Intelligence["概率层"]
A
B
C
end
subgraph Control["确定性控制层"]
D
G
end
subgraph Authority["人类授权"]
E
F
H
end
classDef model fill:#EDE9FE,stroke:#7C3AED,stroke-width:2px,color:#111827;
classDef control fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#111827;
classDef human fill:#FFEDD5,stroke:#EA580C,stroke-width:2px,color:#111827;
classDef action fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#111827;
class A,B,C model;
class D,G control;
class E,F,H human;
class I action;
概率层可以灵活。
控制层不行。
这是一个有用的区分。
一旦你不再用同样的方式对待每个指标,AI 智能体评估就会清晰得多。
模型正确诊断问题的频率是多少?
82%
然后改进检索。
87%
然后改进模型。
91%
这是一个能力评估。
执行函数是否在没有批准的情况下拒绝请求?
可接受的分数是:
100%
97%
99.7%
因为这些测试衡量的是根本不同的东西。
系统有多智能?
安全不变性是否可能被违反?
模型质量测试可能是统计性的,这是合理的。
权限边界通常应该是确定性的。
所以保持独立的评估类别是有用的。
诊断正确性
摘要质量
工具选择准确性
这些可能会逐步改善。
安全回归测试
未授权执行被拒绝
高风险动作始终升级
无效工具参数被阻止
缺少批准防止写入
权限范围被强制执行
这些通常应该有更严格的阈值。
一个被绕过的安全门不是你能够用平均来消除的。
一旦架构分离,失败就更容易定位。
假设提出了一个错误的动作。
证据是否不好?
检索是否无关?
推理是否失败?
审查者是否漏掉了?
确定性门控是否失败了?
人类是否看到了错误的产物?
执行是否违反了授权?
这些是可诊断的边界。
AI 智能体做了些奇怪的事。
模块化不仅仅是为了干净的架构。
它显著改善了可观测性。
第一波 AI 智能体开发严重专注于:
这些仍然很重要。
但一旦 AI 智能体影响真实系统,更难的问题开始变得熟悉。
谁被允许执行这个动作?
如果进程消失,工作流状态保存在哪里?
重试、部分失败和超时之后会发生什么?
我能重建为什么提出这个动作吗?
实际的变更边界是什么?
哪些行为可以容忍概率性失败,哪些绝对不能?
人机交互
人类批准的究竟是什么?
构建可靠的 AI 智能体最终又变回了软件工程。
LLM 是一个非常强大的组件。
但它仍然只是一个组件。
当构建一个可以做出真实改变的 AI 智能体时,这些是我现在发现最有用的的问题。
这个决策真的需要 LLM 吗?
确定性路由可以处理部分吗?
模型输出是结构化的吗?
引用是否经过验证?
检索相关性是否被测量?
当不存在足够相关的证据时会发生什么?
评估器是否独立于生成器?
审查者提供的是批评还是实际的权力?
哪些条件可以确定性检查?
批准是否被显式存储?
执行函数本身是否验证它?
高风险操作是否区别处理?
执行真的暂停了吗?
暂停能否在进程重启后存活?
人类批准的确切产物是否被执行?
哪些测试衡量能力?
哪些测试保护不变性?
哪些失败可以统计地接受?
哪些失败永远不应该发生?
AI 工程中有趣的问题正在慢慢改变。
以前是:
如何让 LLM 调用工具?
现在越来越变成:
如何围绕一个有意为概率性的组件构建一个可信赖的系统?
这需要的不仅仅是提示工程。
它需要架构。
它需要决定智能应该在哪里,保证应该在哪里。
它需要将授权与推理区别对待。
它还需要接受这样一个事实:有时对于 AI 系统来说最好的组件是……
所以如果我必须将整个架构归结为一个原则,那就是:
将 AI 用于需要智能的地方。将代码用于需要保证的地方。
不要混淆两者。