A Controlled Agent Engine: Why LLMs Should Understand, Not Execute

Intelligence belongs to the model. Control belongs to the system.


第一章:为什么越来越多 Agent Demo 无法真正落地生产?

过去两年,我们见证了大语言模型能力的爆发式增长。从简单的问答对话,到能够调用工具、执行任务的 AI Agent,整个行业都在兴奋地探索 LLM 的边界。

然而,一个越来越明显的现实是:Demo 很好看,生产很难用。

你一定见过这样的场景:演示视频里,AI 助手行云流水地帮用户下单、查物流、改价格,一切都完美得像魔法。可一旦放到真实环境里,问题就接踵而至:

  • 幻觉执行:模型 "自作主张" 跳过确认步骤直接下单,用户还没说清楚就扣了款
  • 参数漂移:同一个意图,这次传了 quantity,下次传了 amount,再下次干脆漏了字段
  • 状态错乱:多轮对话后,模型忘了当前在哪个流程,把 A 订单的地址套到了 B 订单上
  • 安全失控:提示词注入、跨域操作、越权访问…… 每一个写操作都像一颗定时炸弹
  • 不可复现:同样的输入,今天是这个结果,明天模型升级了就变成另一个结果,生产环境根本不敢用

为什么会这样?根本原因在于:大多数 Agent 架构都把 "理解" 和 "控制" 混在一起,全部交给了模型。

我们让 LLM 同时做两件事:理解用户在说什么,以及决定系统该做什么。前者是模型的强项,后者却是模型的天生短板。

LLM 是概率性的、创造性的、非确定性的 —— 这恰恰是它智能的来源。但生产系统需要的是确定性的、可预测的、可审计的执行。把控制交给一个概率系统,就像让一个诗人去管财务,才华横溢,但账本一定会乱,让醉汉走钢丝,走一次可能是奇迹,走百次必是灾难。

于是行业陷入了一个尴尬的循环:Demo 惊艳 → 上线翻车 → 加 Prompt 约束 → 约束越来越多 → 模型越来越笨 → 体验越来越差 → 最终推倒重来。

问题出在 Prompt 吗?不。问题出在我们的架构假设错了。

我们真正需要的,也许不是一个更聪明的 Agent,而是一个能够约束 Agent 的执行引擎(Execution Engine) ——让模型只做它擅长的事,把系统该管的事牢牢握在系统手里。


第二章:LLM 真正应该负责什么?

Intelligence belongs to the model.

要回答这个问题,我们得先想清楚:大语言模型的核心能力到底是什么?

是推理?是知识?是逻辑?都对,但都不够本质。

LLM 最不可替代的核心能力,是理解模糊的自然语言,并将其映射为结构化的意图

人类的表达是模糊的、省略的、上下文依赖的、充满歧义的。"来三斤苹果,送到张江"—— 这句话里没有任何字段名,没有 JSON 结构,没有明确的指令格式,但任何一个人都能立刻明白:这是一个下单请求,商品是苹果,数量三斤,地址是张江。

把这种非结构化的自然语言,转化为结构化的业务意图 —— 这就是 LLM 的价值所在,也是它真正不可替代的地方。

具体来说,模型应该负责:

1. 意图识别 (Intent Recognition)

用户说 "这个多少钱",是查询价格;用户说 "来两斤",是创建订单;用户说 "算了不要了",是取消操作。从纷繁的自然语言中识别出核心意图,这是模型的强项。

2. 参数提取 (Parameter Extraction)

"昨天那个再给我来一份,地址换一下"—— 模型需要从上下文和当前输入中,提取出业务系统需要的所有参数:商品是什么、数量多少、新地址是什么。有些参数在当前消息里,有些在历史对话里,有些需要推理补全。

3. 歧义消解 (Ambiguity Resolution)

"苹果" 是水果还是手机?"张江" 是上海的张江还是别的张江?当存在歧义时,模型应该能够判断是否需要追问用户澄清,而不是自作主张选一个。

4. 信息补全引导 (Guided Completion)

订单信息不全时,模型需要用自然语言引导用户补充缺失的字段。"还需要您提供一下收货地址"—— 这件事模型做得比硬编码的表单友好得多。

5. 结果自然语言化 (Natural Language Generation)

系统返回了结构化的执行结果,模型需要把它翻译成用户听得懂的人话。"您的订单已提交,预计明天送达" 比 {"status": "success", "orderId": "ORD_123"} 友好一万倍。

注意到了吗?以上所有职责,全部都是 "理解" 和 "表达" 层面的事—— 输入是自然语言,输出是结构化意图;输入是结构化结果,输出是自然语言。

模型就像一个优秀的翻译官:一边听懂人类的模糊需求,翻译成系统能理解的精确指令;另一边把系统的冰冷结果,翻译成人类能接受的友好回复。

但翻译官不应该有决策权。

翻译官听懂了 "我要转账一百万",他应该把这句话准确传达给财务系统,而不是自己掏出 U 盾把钱转了。

这就是核心边界:模型负责理解,不负责执行。模型产出意图,不产出动作。

如果把模型看作 CPU,那么真正决定程序如何运行的,不是 CPU,而是 Runtime。


第三章:为什么控制必须属于系统?

Control belongs to the system.

如果说 "智能" 的本质是概率性的、创造性的、非确定性的,那么 "控制" 的本质恰好相反:确定性的、可预测的、可审计的。

生产系统中的每一个写操作,都必须满足三个条件:

  1. 谁在操作—— 身份明确,权限清晰
  2. 操作了什么—— 参数完整,语义确定
  3. 用户确认了吗—— 关键操作必须有明确的用户同意

这三件事,没有一件应该交给模型来判断。

模型不能当裁判

让我们来做一个思想实验:如果让模型来判断 "用户是否确认了下单",会发生什么?

  • 用户说 "好的"—— 算确认吗?可能是,也可能只是表示 "我听到了"
  • 用户说 "行吧"—— 算确认吗?语气勉强,但似乎是同意
  • 用户说 "那就这样"—— 算确认吗?取决于上下文

你可以写一百条 Prompt 规则去约束它,但你永远无法 100% 确定模型不会在某个奇怪的上下文里,把一句模棱两可的话判定为确认。

而只要出错一次,就是一次生产事故。

这就是为什么确认这件事,必须由系统代码来硬判断—— 只有明确的 confirm 意图才算确认,其他任何情况都不算。不是 "大概率算",不是 "应该是",是 "是就是,不是就不是"。

在受控智能体引擎中,LLM 从不直接产生业务动作,它只能产生候选意图。

这就是硬闸门(Hard Gate)的含义:状态转移是代码写死的,没有模糊地带,没有概率空间。

状态机是控制的骨架

如果控制必须是确定性的,那么有限状态机(FSM)就是承载控制的最佳骨架。

在受控智能体引擎中,所有写操作都必须经过明确的状态流转:

IDLE(空闲) → REQUEST → AWAITING_CONFIRMATION(待确认)
AWAITING_CONFIRMATION → CONFIRM → EXECUTING(执行中)
AWAITING_CONFIRMATION → CANCEL → IDLE
EXECUTING → FINISH → IDLE

这个状态转移矩阵是纯函数、纯代码、100% 确定的。模型没有任何权限修改状态,它甚至不需要知道状态机的存在。

模型能做的,只是产出一个 "意图"—— 比如 "用户想下单"。至于这个意图能不能推动状态转移、会转移到哪个状态、转移后能做什么操作 —— 全部由系统的状态机说了算。

控制的七个层次

受控执行不是一个单一的闸门,而是一整套纵深防御体系:

  1. 状态硬闸门:只有特定状态允许特定操作,状态转移完全由代码控制
  2. 参数收敛:所有执行参数必须经过系统层的校验和收敛,不直接使用模型输出
  3. 地址安全:收货地址只能从本轮用户消息中提取,禁止从记忆或历史中自动补齐
  4. 意图兜底:模型漏传参数时,服务端用正则和规则进行二次推导,防止业务中断
  5. 工具白名单:每个状态下能调用哪些工具,由系统硬编码控制,模型无法调用白名单外的工具
  6. 异常兜底:所有工具调用都经过安全包装,异常被捕获并友好返回,绝不暴露堆栈
  7. 全链路审计:每一次状态转移、每一次工具调用、每一个异常,都有完整的 Trace 记录

这七层控制,没有一层依赖模型的 "自觉性"。全部都是系统层面的硬约束。

Control belongs to the system. 控制属于系统 —— 所有需要 "确定性" 的事情,都必须握在代码手里。


第四章:Controlled Agent Engine 的 Runtime 设计

理解了 "智能与控制分离" 的设计哲学,我们来看一个完整的受控智能体引擎(Controlled Agent Engine)应该包含哪些核心组件。

整个引擎由六层核心模块构成,每一层都有明确的职责边界。模型只在最上层的意图理解环节发挥作用,往下的所有控制和执行,全部由系统接管。

整体架构

┌─────────────────────────────────────────┐
│         用户输入 / 自然语言              │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│         Runtime Policy(运行时策略)     │  ← 模型产出意图,这里判断能不能做
├─────────────────────────────────────────┤
│           Tool Gate(工具闸门)          │  ← 状态决定哪些工具可用
├─────────────────────────────────────────┤
│        Draft Reducer(草稿归约器)       │  ← 增量合并业务实体,纯函数
├─────────────────────────────────────────┤
│         Safe Executor(安全执行器)      │  ← 统一异常捕获 + 全链路追踪
├─────────────────────────────────────────┤
│       Business Adapter(业务适配器)     │  ← 封装具体业务逻辑,可插拔
├─────────────────────────────────────────┤
│         Session Layer(会话层)          │  ← 状态持久化 + 双层缓存
└─────────────────────────────────────────┘

下面我们逐层拆解。

4.1 Runtime Policy — 运行时策略引擎

Runtime Policy 是整个 Engine 的控制入口,也是唯一拥有状态决策权的组件。

每一轮用户输入进来,第一步不是让模型自由发挥,而是先经过 Runtime Policy 的判断。模型只需要输出 "用户意图是什么",剩下的交给策略引擎来决定。

策略引擎的核心职责:

  • 接收模型产出的意图(intent + params)
  • 读取当前会话状态(从 Session Layer)
  • 根据状态转移矩阵,计算下一步动作
  • 返回决策结果:继续 / 等待确认 / 执行待定操作 / 拒绝

plaintext

模型输出 intent → Runtime Policy → 决策结果
                  ↑
              当前状态
           (Session Layer)

关键设计原则:Runtime Policy 只做决策,不执行业务副作用。 它的输出只是一个 "动作指令",真正的执行由下游组件完成。这样决策逻辑可以被独立测试、独立审计。

4.2 Tool Gate — 工具闸门

Tool Gate 本质上不是工具调度器,而是 Runtime 的权限系统。它回答一个问题:在当前状态下,哪些工具是允许调用的?

比如:

  • IDLE 状态:允许调用所有查询类工具(搜索商品、查价格、查物流),不允许调用任何写操作
  • AWAITING_CONFIRMATION 状态:只允许调用草稿修改类工具,不允许执行提交
  • EXECUTING 状态:只允许执行已经 pending 的那个特定操作

Tool Gate 的存在,从根本上杜绝了 "模型幻觉调用敏感工具" 的风险。哪怕模型被提示词注入诱导想要下单,只要当前不是 EXECUTING 状态,Tool Gate 就会直接拦截。

这是一道硬闸门:不是模型说调什么就调什么,而是系统说能调什么才能调什么。

4.3 Draft Reducer — 草稿归约器

Draft Reducer 不属于业务层,而属于 Runtime。很多人第一次看到 Reducer 会觉得这是购物车组件,其实它是 Runtime 的一部分。

在很多业务场景中,用户的需求不是一次说清楚的,而是多轮逐步补充的。"来三斤苹果…… 哦对了还要两斤梨…… 地址改一下……"

Draft Reducer 负责管理这种 "逐步成型" 的业务实体(比如订单草稿)。它是一个纯函数式的归约器:

plaintext

当前草稿 + 新的操作指令 = 新的草稿

支持的操作类型:

  • add:添加商品
  • remove:移除商品
  • change_qty:修改数量
  • set_address:设置地址
  • clear:清空草稿
  • replace:整体替换

纯函数设计意味着同样的输入永远得到同样的输出,没有副作用、没有不确定性。草稿合并的逻辑完全可控、可测试,模型只需要说出 "加两斤梨",具体怎么合并由系统来算。

4.4 Safe Executor — 安全执行器

Safe Executor 是所有工具调用的统一入口。它像一个安全沙箱,包裹每一个工具的执行过程:

  • 异常捕获:任何工具抛出异常,都在这里被统一捕获,转换为友好的错误消息返回,绝不把堆栈信息暴露给模型或用户
  • 全链路追踪:每一次工具调用自动生成 Langfuse Span,父子嵌套形成完整调用链
  • 超时控制:防止慢工具阻塞整个 Agent 循环
  • 输入输出审计:记录每个工具的入参和返回值,便于事后排查

有了 Safe Executor,单个工具的故障不会拖垮整个系统,所有执行路径都有迹可循。

4.5 Business Adapter — 业务适配器

Business Adapter 是引擎与具体业务之间的隔离层。

Engine本身不关心你是在做电商、CRM 还是工单系统。它只知道:有一个 "待执行操作"(pending command),需要在 EXECUTING 状态下被执行。具体怎么执行?由对应的 Business Adapter 来实现。

每个 Adapter 遵循统一的契约:

  • validate():参数校验,确认所有必要字段都齐全
  • execute():实际执行业务逻辑

以购物场景的 Shopping Adapter 为例,一次完整的下单执行包含 8 个步骤:

  1. FSM 状态二次校验(确认确实在 EXECUTING)
  2. 参数收敛与清洗
  3. 收货地址格式校验
  4. 商品价格解析与计算
  5. 提交订单事务(写入持久化存储)
  6. 写入长期记忆(向量化存储对话摘要)
  7. 追加页面上下文
  8. 返回 FINISH 事件,推动状态回到 IDLE

这 8 步全部是系统代码,模型全程不参与。模型只需要在最开始说一句 "用户想下单",剩下的交给 Adapter 按部就班完成。

Adapter 是整个引擎最核心的扩展点。换一个 Adapter,整个引擎就能服务完全不同的业务场景。

4.6 Session Layer — 会话层

Session Layer 是整个引擎的状态底座,保存的是 Runtime State,而不是聊天记录,负责所有状态数据的持久化与缓存。

采用 Memory Cache + Redis 持久化 的双层架构:

  • 热路径走内存缓存,同步读取,性能最优
  • 关键状态节点 flush 到 Redis,保证可靠性
  • Redis TTL 7 天,内存随进程生命周期自动管理

Session 中存储的数据包括:

  • FSM 当前状态
  • pendingCommand(待执行操作)
  • orderDraft(订单草稿)
  • pageContext(页面级上下文)
  • lastIntent /lastIntentParams(最近一次意图)
  • fsmLog(状态转移审计日志)
  • lastOrderAddress(最近收货地址)
  • roundCompleted(轮次完成标记)

会话层是整个引擎的 "记忆中枢",但注意:这里的记忆是结构化的、受控的状态数据,不是模型的自由联想。


第五章:Command 如何在 Runtime 中流转

讲完了各个组件,我们来看一条完整的用户指令,在受控智能体引擎中是如何被执行的。

以 "来三斤苹果,送到张江" 为例,完整的执行链路分为 9 个步骤:

Step 1:用户输入,进入 Runtime Policy

用户消息到达后,模型首先进行意图识别和参数提取,输出:

intent: create_order
params: { product: "苹果", quantity: "3斤", address: "张江" }

注意:模型的工作到这里就完成了一半 —— 它只负责把自然语言翻译成结构化意图。接下来的事,系统接管。

Step 2:Runtime Policy 读取当前状态

Runtime Policy 从 Session Layer 获取当前状态:IDLE

Step 3:状态转移判断

策略引擎根据状态转移矩阵判断:

  • 当前状态:IDLE
  • 输入意图:create_order
  • 参数完整性检查:商品✓ 数量✓ 地址✓ → 信息齐全

决策结果:触发 REQUEST 事件,状态转移至 AWAITING_CONFIRMATION

Step 4:Draft Reducer 生成草稿

Draft Reducer 根据意图参数生成订单草稿:

{
  items: [{ name: "苹果", quantity: "3斤", unitPrice: ... }],
  address: "张江",
  totalPrice: ...
}

草稿存入 Session,同时设置 pendingCommand:

{ type: "commitTransaction", params: {...} }

Step 5:返回 wait_confirmation,模型转述

Runtime Policy 返回决策:{ action: 'wait_confirmation', pendingCommand: {...} }

模型拿到结果后,用自然语言向用户转述订单信息,请用户确认:

"好的,三斤苹果,送到张江,总价 XX 元。请问确认下单吗?"

到这里,第一轮交互结束。状态停留在 AWAITING_CONFIRMATION

Step 6:用户确认,再次进入 Runtime Policy

用户回复:"确认"

模型识别意图:intent: confirm

Runtime Policy 再次被调用,当前状态:AWAITING_CONFIRMATION

Step 7:状态转移至 EXECUTING

策略引擎判断:确认意图有效,触发 CONFIRM 事件。

状态转移:AWAITING_CONFIRMATION → EXECUTING

返回决策:{ action: 'execute_pending', pendingCommand: {...} }

Step 8:Business Adapter 执行

Tool Gate 放行 pendingCommand,Shopping Adapter 开始执行完整下单链路:

  • 状态二次校验 → 参数收敛 → 地址校验 → 价格计算 → 提交事务 → 写长期记忆 → 更新 PageContext

执行完成后,触发 FINISH 事件,状态回到 IDLE,草稿清空。

Step 9:模型自然语言返回结果

执行结果返回给模型,模型翻译成友好回复:

"下单成功!订单号 ORD_XXX,预计明天送达。"

完整执行链总览

用户输入
  ↓
LLM 意图识别(模型职责:理解)
  ↓
Runtime Policy 决策(系统职责:控制)
  ↓
状态转移 + Draft 更新(系统职责:控制)
  ↓
Tool Gate 放行 / 拦截(系统职责:控制)
  ↓
Business Adapter 执行(系统职责:执行)
  ↓
LLM 结果转述(模型职责:表达)
  ↓
用户看到回复

在整条链路中,模型只出现在最上层的理解最下层的表达两个环节。中间所有的决策、控制、执行,全部由系统代码确定性地完成。

这就是受控执行的含义:模型翻译,系统决策,代码执行。


第六章:为什么购物只是第一个 Adapter?

现在你可能已经意识到了 —— 这个架构中,购物场景本身并不是核心,核心是引擎。

Shopping Adapter 只是第一个业务实现,它证明了一件事:受控智能体引擎能够完整地支撑一个有状态、有写操作、有确认流程的真实业务场景。

但购物只是起点。

让我们回过头看引擎的核心组件:

  • Runtime Policy — 通用的,和业务无关
  • Tool Gate — 通用的,和业务无关
  • Draft Reducer — 通用的,和业务无关
  • Safe Executor — 通用的,和业务无关
  • Session Layer — 通用的,和业务无关

唯一和业务相关的,只有 Business Adapter。

这意味着什么?意味着整个引擎的 90% 都是可复用的基础设施。换一个业务,你只需要:

  1. 定义一组新的状态(或者直接复用三态模型)
  2. 实现一个新的 Business Adapter
  3. 配置一组新的工具和 Tool Gate 规则
  4. 调整模型的 System Prompt,告诉它新业务的意图分类

就完了。

FSM 的骨架是通用的,安全执行的框架是通用的,会话持久化是通用的,全链路追踪是通用的,草稿归约的模式是通用的 —— 这些都不需要重新写。

你只需要把 "业务是什么" 填进去,引擎就会用同样可靠、同样可控的方式运转起来。

这才是这个项目真正的价值:不是做了一个购物 Agent,而是沉淀了一套受控智能体的执行引擎。

Shopping Adapter 只是 Controlled Agent Engine 的 Reference Implementation(参考实现)。


第七章:Engine 可以复用到哪些领域?

受控智能体引擎的适用场景非常广泛。只要满足两个条件:

  1. 需要用自然语言交互
  2. 涉及写操作、需要确认流程、有状态流转

就可以用这套引擎来做。

下面举几个典型场景:

CRM 客户管理

用户用自然语言说 "把张三的联系方式改成 138xxxx,备注一下他是 A 公司的采购"。

  • 模型负责:识别意图是 "更新客户信息",提取字段(姓名、新电话、备注)
  • 引擎负责:草稿归并修改内容 → 展示修改预览请用户确认 → 确认后执行更新 → 写操作日志
  • Adapter:对接 CRM 系统的客户更新接口

ERP 单据审批

"把这张采购单批了,金额有点高但供应商靠谱。"

  • 模型负责:识别审批意图,提取单据编号,理解审批意见
  • 引擎负责:校验当前用户权限 → 展示单据详情待确认 → 确认后执行审批 → 写入审批流
  • Adapter:对接 ERP 的审批流 API

工单系统

"服务器又挂了,赶紧给运维组开个 P1 工单。"

  • 模型负责:识别开工单意图,提取优先级、分配组、问题描述
  • 引擎负责:生成工单草稿 → 确认信息 → 提交工单 → 返回工单号
  • Adapter:对接工单系统的创建接口

金融交易

"以当前价格买入 100 股贵州茅台。"

  • 模型负责:识别交易意图,提取标的、数量、价格类型
  • 引擎负责:风控校验(余额、持仓限制)→ 展示交易确认单 → 二次确认 → 提交订单
  • Adapter:对接券商交易接口

注意:金融场景对安全性要求更高,引擎的硬闸门设计反而更有价值 —— 你绝对不会希望模型 "幻觉" 一下就帮你买了股票。

运维审批

"给生产环境的 Nginx 加个配置,把 /api 路径转发到新集群。"

  • 模型负责:识别配置变更意图,提取路径、目标集群
  • 引擎负责:生成配置变更草稿 → 展示 diff 预览 → 审批确认 → 执行变更 → 回滚预案
  • Adapter:对接配置中心或 IaC 平台

AI Workflow 编排

"帮我把这个文档翻译一下,然后生成摘要,最后发到工作群里。"

  • 模型负责:识别多步工作流意图,拆解每个步骤
  • 引擎负责:逐步执行,每一步结果确认后进入下一步,支持中途修改或取消
  • Adapter:对接翻译、摘要、消息推送等各个工具 / 服务

每一个场景,都是同样的引擎骨架,不同的 Adapter 填充。


结语:什么才是 AI 应用中真正稳定的部分?

大模型时代有一个迷人的错觉:模型越来越强,似乎终有一天能包办一切。于是很多团队把宝全部押在模型上,Prompt 越写越长,约束越加越多,试图用语言模型解决所有问题。

但现实是:模型是变化最快的那个变量。

今天用 DeepSeek,明天可能换 GPT-5,后天又有新的开源模型冒出来。模型能力在变、价格在变、接口在变、甚至输出格式的偏好都在变。

如果你把控制逻辑也写在 Prompt 里,那模型一变,你的整个系统都要跟着变。每一次模型升级都是一次回归测试噩梦 —— 你不知道哪条隐含的规则又被打破了。

而受控智能体引擎的思路恰好相反:

业务会变化,模型会变化,真正应该保持稳定的是受控执行引擎。

模型负责理解 —— 这部分可以随时升级换代,换更好的模型、用更优的 Prompt,都不影响核心控制逻辑。

系统负责控制 —— 这部分一旦设计好,就是稳定的、可审计的、不随模型变化的基础设施。FSM 就是 FSM,状态转移就是状态转移,换什么模型都一样。

业务 Adapter 可以插拔 —— 电商、CRM、工单、金融,每个业务各写各的,引擎层完全复用。

这就是分层的价值。这就是架构的价值。

Intelligence belongs to the model. Control belongs to the system.

当模型越来越聪明,我们反而更需要清醒地划清边界。让智能的归智能,让控制的归控制。

只有这样,AI Agent 才能真正从好看的 Demo,走向可靠的生产。


当模型不断演进时,一个真正能够长期沉淀的,不应该是 Prompt,也不是某个具体模型,而应该是能够持续承载不同模型、不同业务的受控执行引擎。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐