1. 从执行层失控说起:为什么车控与运营 Agent 需要一套独立防御架构
车控 Agent 和运营 Agent 这两类系统有个共同特征:它们不只是"回答问题",而是直接"动手做事"。车控 Agent 可能下发一条底盘控制指令、调整一次能量回收策略、触发一次远程诊断;运营 Agent 可能改一条定价规则、发一批优惠券、调一次库存分配。这类系统的风险不在于"说错话",而在于"做错事"——一旦执行层被污染或误导,后果是物理层面的或资金层面的,不可逆。
我在实际接触这类项目时发现一个普遍现象:大多数团队把安全重心放在模型对齐和输入过滤上,却忽略了执行链路本身的结构性缺陷。模型对齐解决的是"模型想不想做坏事",输入过滤解决的是"用户能不能让模型做坏事",但真正出事故的场景往往是——模型本身没问题,输入也合规,可执行层把不该拼在一起的东西拼在了一起。
举个典型场景。某运营 Agent 的提示词里同时包含"系统配置说明"和"用户请求处理"两部分内容,模型在生成回复时,把系统配置里的某个内部阈值当成了用户请求的一部分,直接触发了一次超出预期的批量操作。这不是模型被越狱,而是指令与数据在同一个上下文里没有边界,模型无法区分"这是规则"还是"这是待处理的数据"。
ISO/IEC 27090:2026 这个标准框架里提到的"智能体防御三范式",本质上就是针对执行层的三道结构性防线:指令-数据分离解决"边界模糊",双智能体核验解决"单点决策不可信",沙箱推演解决"执行前无法预判后果"。这三者不是并列的选项,而是有先后依赖关系的纵深防御链条。
这篇文章面向的是正在做车控或运营类 Agent 落地、并且已经开始担心执行层安全的工程师和架构师。我会把这三个范式的设计逻辑、落地方式、以及我在实际搭建中踩过的坑,按可复现的粒度拆开讲。读完之后你应该能判断:你的 Agent 系统当前处于哪一层防御缺失的状态,以及补哪一层性价比最高。
2. 指令-数据分离:把"规则"和"素材"从物理上隔开
2.1 为什么提示词拼接是执行层最大的隐性漏洞
绝大多数 Agent 系统的输入构造方式是这样的:把系统提示词、工具描述、历史对话、当前用户请求,按顺序拼成一个长字符串,丢给模型。这个做法在对话类应用里问题不大,但在执行类 Agent 里是致命的。
原因在于,模型对"这段文字是什么角色"的判断,完全依赖于位置和措辞,而不是结构化标记。当系统提示词里包含"当库存低于 X 时触发补货"这样的规则,而用户请求里恰好也出现了"库存低于 X"这样的表述,模型在生成工具调用参数时,很容易把规则里的 X 当成用户指定的 X。这不是模型笨,而是上下文里没有机制告诉它"这两个 X 来自不同的信任域"。
指令-数据分离的核心思想很简单:规则永远不进入模型的可编辑上下文,只以结构化约束的形式存在于执行层。模型看到的只有"当前需要处理的数据",规则由外部的策略引擎在执行工具调用之前做校验。
2.2 落地方式:策略引擎前置 + 上下文分区标记
具体怎么做?我推荐的结构是三层:
第一层是策略引擎,独立于模型运行。它持有一份规则表,每条规则定义了"什么条件下允许什么操作、参数范围是多少、频率上限是多少"。这份规则表不进入模型的任何提示词。
第二层是上下文分区。给模型的输入被显式分成几个区块,每个区块用不可伪造的分隔标记包裹。比如用 XML 风格的标签:
<system_policy>仅包含操作类型枚举,不含具体阈值</system_policy> <user_request>用户原始请求,原样传入</user_request> <tool_schema>工具的参数结构定义</tool_schema>关键在于,system_policy区块里只放操作类型的白名单,比如"允许的操作有:查询库存、调整定价、发送通知",但不放任何具体数值。具体数值全部由策略引擎在执行时注入。
第三层是执行前校验。模型输出的工具调用请求,先经过策略引擎,引擎检查:操作类型是否在白名单内、参数是否在允许范围内、当前频率是否超限。任何一项不通过,直接拒绝,不进入执行。
2.3 一个车控场景的具体拆解
假设车控 Agent 需要处理"调整能量回收强度"这个操作。传统做法是把"能量回收强度可选范围 0-3,默认 1"写进系统提示词。用户说"帮我调高一点",模型可能输出强度 3。
分离之后的做法是:系统提示词里只写"你可以调用 set_regen_level 工具",不写范围。用户请求原样传入。模型输出set_regen_level(level=3)。策略引擎收到后,检查当前车辆状态:如果当前车速高于某阈值,或者电池温度不在允许区间,直接拒绝这次调用,返回"当前工况不允许调整"。模型收到拒绝后,向用户解释原因。
这样做的好处是,规则变更不需要改提示词,也不需要重新调模型。策略引擎的规则表可以独立热更新,而且规则是确定性的,不会因为模型的理解偏差而失效。
注意:上下文分区标记必须是模型无法通过用户输入伪造的。如果你用普通的
---做分隔,用户完全可以在请求里也写---来混淆边界。建议用带随机后缀的标签,或者干脆用结构化 API 的 role 字段来区分,而不是纯文本拼接。
2.4 实操中容易忽略的两个细节
第一个细节是工具描述的污染。很多团队把工具的参数说明写得很详细,比如"level 参数:0 表示关闭,1 表示低,2 表示中,3 表示高"。这段描述本身没问题,但如果它和用户请求在同一个上下文里,模型可能会把描述里的示例值当成用户意图。我的做法是,工具描述只保留参数名和类型,具体枚举值由策略引擎在执行时校验,不告诉模型。
第二个细节是历史对话的信任级别。多轮对话里,上一轮模型自己生成的回复会进入下一轮的上下文。如果上一轮回复里包含了某个数值,下一轮模型可能把它当成"已确认的事实"。分离方案里,历史对话应该被标记为"低信任数据",策略引擎在校验时,不采信任何来自历史对话的数值,只采信当前轮用户请求和策略引擎自身的规则。
3. 双智能体核验:让"提议"和"批准"变成两个独立角色
3.1 单智能体决策的结构性风险
指令-数据分离解决了"规则被污染"的问题,但没解决"模型判断本身可能出错"的问题。一个 Agent 在面对复杂工况时,可能给出一个参数上合法、但业务上不合理的操作。比如运营 Agent 在促销期间把某商品定价调低了 30%,参数在允许范围内,但结合当前库存和竞品价格,这个操作是亏的。
单智能体架构下,这个操作会直接执行,因为没有第二双眼睛。你可能会说,可以加规则校验。但规则只能覆盖已知的、可枚举的场景,对于"参数合法但组合起来不合理"的情况,规则很难穷举。
双智能体核验的思路是:把"提议"和"批准"拆成两个独立的模型实例,它们不共享上下文,不共享提示词,只通过结构化的提议单通信。
3.2 提议单的结构设计
提议单是整个机制的核心。它不是自然语言,而是一个结构化对象,包含:操作类型、目标对象、参数、提议理由、置信度、以及提议智能体认为的风险点。
{ "action": "set_price", "target": "sku_8821", "params": {"price": 69.9, "currency": "CNY"}, "reason": "竞品降价,建议跟进", "confidence": 0.82, "self_reported_risks": ["毛利率可能低于阈值", "库存周转天数偏高"] }核验智能体收到这个提议单后,独立地做几件事:检查参数是否在策略引擎允许范围内(这一步和策略引擎有重叠,但核验智能体可以做更复杂的业务判断)、检查提议理由是否成立(比如去查竞品价格是否真的降了)、检查自报风险是否被充分评估。
核验智能体的输出只有三种:批准、拒绝、要求补充信息。它不能修改提议单,只能整体批准或拒绝。这个限制很重要,因为如果核验智能体可以修改参数,它就变成了另一个提议者,失去了核验的意义。
3.3 两个智能体如何做到真正独立
独立性是双智能体核验能否生效的关键。如果两个智能体用同一个模型、同一份提示词、同一份上下文,那它们大概率会犯同样的错误。我见过一些实现,两个 Agent 只是把同一个请求跑了两遍,然后取一致的结果,这没有任何意义。
真正的独立需要做到三点:
模型独立。如果条件允许,用不同厂商、不同架构的模型。如果只能用同一个模型,至少用不同的温度参数和不同的系统提示词。提议智能体的提示词偏向"积极给出方案",核验智能体的提示词偏向"找出方案的问题"。
上下文独立。核验智能体不应该看到提议智能体的完整推理过程,只看提议单。因为推理过程里可能包含误导性的中间结论,核验智能体如果看到了,容易被带偏。
信息源独立。核验智能体查证事实时,应该走独立的查询通道,而不是复用提议智能体已经查过的缓存。比如提议智能体说"竞品价格是 59.9",核验智能体应该自己去查一次,而不是采信提议单里的数字。
3.4 车控场景下的核验实例
车控 Agent 提议"将能量回收强度从 1 调到 3"。核验智能体收到提议单后,独立查询当前车辆状态:车速、电池温度、SOC、驾驶模式。如果发现当前处于运动模式且 SOC 高于 80%,核验智能体可能判断"高能量回收在当前工况下会导致驾驶体验突兀",拒绝提议。
这里有个经验:核验智能体的拒绝理由要结构化,不能只说"拒绝",要说"拒绝,原因代码 R03:工况不匹配"。这样提议智能体收到拒绝后,可以针对性地调整,而不是盲目重试。
提示:双智能体核验会增加延迟和成本。我的做法是分级——低风险操作(查询类)跳过核验,中风险操作(参数调整类)走轻量核验(只查策略引擎),高风险操作(资金、物理控制类)走完整双智能体核验。分级规则本身也放在策略引擎里,不进入模型上下文。
4. 沙箱推演:在执行之前先"演一遍"
4.1 为什么核验通过还不够
双智能体核验解决的是"这个操作本身合不合理",但没解决"这个操作执行之后会发生什么"。有些操作单看是合理的,但放到系统当前状态里,会引发连锁反应。
比如运营 Agent 批准了一次批量发券,单看券的面额和数量都在允许范围内,但当前系统里已经有一批未核销的券,叠加之后可能导致某个商品的库存被瞬间清空,进而触发补货流程,补货流程又占用了本该用于另一场活动的预算。这种跨模块的连锁反应,核验智能体很难预判,因为它只看提议单,不看系统全局状态。
沙箱推演的思路是:在执行之前,把操作放到一个模拟环境里跑一遍,观察系统状态的变化,如果出现异常,就拦截。
4.2 沙箱的三种实现层次
沙箱不是只有一种做法,根据系统复杂度,可以分三个层次:
第一层:状态快照回放。适用于状态可枚举、变化可预测的系统。做法是定期对系统关键状态做快照,执行前把操作应用到快照上,看结果是否符合预期。车控场景里,车辆状态可以用一个状态向量表示,操作对状态向量的影响可以用规则推导,这一层就够用。
第二层:影子执行。适用于有外部依赖、但依赖可以 mock 的系统。做法是把操作发给一个影子服务,影子服务用 mock 的外部依赖执行,返回模拟结果。运营场景里,发券操作可以发给影子库存服务,看库存变化是否符合预期。
第三层:数字孪生。适用于物理系统或复杂业务系统。做法是维护一个与真实系统同步的孪生体,操作先在孪生体上执行,观察一段时间,确认无异常后再同步到真实系统。这一层成本最高,但也是车控这类物理系统最需要的。
4.3 推演结果的判定标准
沙箱跑完之后,怎么判断"这个操作能不能放行"?我总结了几条判定标准:
状态越界。操作执行后,任何关键状态变量超出了预设的安全区间,拦截。比如电池 SOC 低于 20%、库存低于安全库存、预算余额为负。
变化率异常。操作执行后,某个状态变量的变化率超过了历史正常范围,拦截。比如定价一次调低超过 15%、能量回收强度一次跳变超过 2 级。
连锁触发。操作执行后,触发了预期之外的二级操作,拦截。比如发券触发了补货、调价触发了竞品跟价监控告警。
不可逆性。操作本身不可逆,且推演结果存在任何不确定性,拦截。比如物理控制指令、资金划转。
4.4 一个完整的推演链路示例
以运营 Agent 的批量发券为例,完整链路是这样的:
提议智能体生成提议单,核验智能体批准。提议单进入沙箱推演模块。沙箱从当前系统拉取状态快照:当前库存、未核销券数量、预算余额、活动排期。沙箱把发券操作应用到快照上,模拟券的核销概率(基于历史数据),推演未来 24 小时的库存变化和预算消耗。
推演结果显示:库存将在 18 小时后低于安全线,预算消耗将达到 92%。判定标准里"状态越界"触发,拦截。系统返回拦截理由,提议智能体收到后,可以调整发券数量或面额,重新走流程。
整个链路里,模型只负责生成提议和核验,所有数值计算和状态推演都在沙箱里完成,模型不参与。这样既保证了推演的确定性,又避免了模型在数值计算上的不可靠。
注意:沙箱推演的准确性依赖于状态快照的实时性和模拟模型的准确性。如果快照是 10 分钟前的,推演结果可能已经过时。我的做法是,高风险操作的沙箱推演强制要求实时拉取状态,低风险操作可以用缓存快照,但缓存时间不超过 1 分钟。
5. 三范式的组合方式与执行层防御架构的完整形态
5.1 三者的依赖关系不是并列
很多人会把这三个范式理解成三个独立的防御模块,可以任选其一。实际上它们是有依赖关系的:指令-数据分离是基础,双智能体核验是中间层,沙箱推演是最后一道闸。
没有指令-数据分离,双智能体核验的提议单本身就可能被污染,核验智能体核验的是一个已经被污染的提议。没有双智能体核验,沙箱推演会收到大量低质量提议,推演资源被浪费在明显不合理的操作上。三者叠加,才形成完整的执行层防御。
5.2 完整架构的数据流
一个完整的执行层防御架构,数据流是这样的:
用户请求进入,经过指令-数据分离模块,被拆成"用户数据"和"策略约束"两部分。用户数据进入提议智能体,策略约束进入策略引擎。提议智能体生成提议单,提议单先过策略引擎的静态校验,通过后进入核验智能体。核验智能体独立查证后,给出批准或拒绝。批准的提议单进入沙箱推演,推演通过后,才真正执行。
整个链路里,模型只在两个位置出现:提议智能体生成提议单、核验智能体做核验。其余环节都是确定性的代码逻辑。这个设计的关键在于,把模型的不可靠性限制在最小的范围内,让它在结构化的框架里工作,而不是让它在开放环境里自由发挥。
5.3 性能与安全的平衡
三范式全开,延迟会显著增加。我的实测数据是,一个完整的链路(提议+核验+推演)比单模型直出慢 3-5 倍。对于车控这种实时性要求高的场景,这个延迟可能不可接受。
平衡的做法是分级防御。把操作按风险等级分成三级:
| 风险等级 | 操作类型 | 防御配置 | 典型延迟 |
|---|---|---|---|
| 低 | 查询、状态读取 | 仅指令-数据分离 | 与单模型相当 |
| 中 | 参数调整、非关键配置 | 分离 + 轻量核验 | 1.5-2 倍 |
| 高 | 物理控制、资金操作、批量操作 | 三范式全开 | 3-5 倍 |
分级规则放在策略引擎里,由操作类型和参数范围共同决定。比如同样是调价,调价幅度小于 5% 算中风险,大于 5% 算高风险。
5.4 监控与回滚
防御架构本身也需要被监控。我建议至少监控这几个指标:拦截率(被拦截的操作占比)、误拦率(被拦截但事后证明合理的操作占比)、核验智能体与提议智能体的一致率、沙箱推演与实际执行的偏差率。
误拦率特别重要。如果误拦率过高,业务方会绕过防御架构,整个体系就失效了。我的经验是,误拦率控制在 5% 以内是可接受的,超过 10% 就需要调整规则。
回滚机制也要提前设计。即使三范式全开,也不能保证 100% 正确。对于可逆操作,执行后保留回滚能力;对于不可逆操作,执行前要有更强的人工确认环节。回滚日志要记录完整的决策链路,方便事后复盘。
6. 落地过程中踩过的坑与几条硬经验
6.1 提议单的字段设计比想象中重要
我最初设计的提议单只有操作类型和参数,核验智能体经常因为信息不足而拒绝。后来加了reason和self_reported_risks两个字段,核验智能体的批准率明显提升,因为它能看到提议者的思考过程,判断这个思考是否成立。
但reason字段有个坑:如果提议智能体在 reason 里写了误导性的理由,核验智能体可能被带偏。我的做法是,核验智能体对 reason 字段只做"逻辑一致性检查",不采信 reason 里的任何事实性陈述。事实性陈述必须由核验智能体独立查证。
6.2 沙箱推演的模拟模型需要持续校准
沙箱推演的准确性取决于模拟模型。我见过一个项目,沙箱里的库存消耗模型用的是三个月前的历史数据,结果推演结果和实际偏差很大,导致大量误拦。后来改成每周用最新数据重新校准模拟模型,误拦率从 18% 降到了 4%。
校准的频率取决于业务变化速度。运营场景变化快,建议每周校准;车控场景相对稳定,可以每月校准。校准的数据来源应该是真实执行日志,而不是人工构造的测试数据。
6.3 双智能体的模型选择有讲究
如果条件允许,提议智能体和核验智能体用不同厂商的模型。我实测下来,同厂商不同版本的模型,独立性不如不同厂商的模型。如果只能用同一个模型,至少让核验智能体的温度参数低于提议智能体,核验智能体偏向保守,提议智能体偏向积极。
还有一个细节:核验智能体的提示词里不要出现"请批准"或"请拒绝"这样的倾向性措辞,要用中性的"请评估"。我试过用"请找出问题"作为核验智能体的提示词,结果它变得过度保守,批准率降到 30% 以下,大量合理操作被拦。
6.4 策略引擎的规则表要版本化
策略引擎的规则表是执行层防御的核心,它的变更必须版本化。每次变更记录:变更内容、变更原因、变更人、生效时间。执行日志里要记录当时使用的规则表版本号,方便事后追溯。
我踩过的坑是,规则表热更新时没有做灰度,一次性全量生效,结果一条新规则误拦了大量正常操作。后来改成灰度发布,新规则先在 5% 的流量上生效,观察 24 小时无异常再全量。
6.5 不要试图用模型解决所有问题
最后一个经验,也是最重要的:执行层防御的核心是确定性,不是智能。模型适合做"生成提议"和"语义核验"这类需要理解力的工作,但参数校验、状态推演、频率控制这些,必须用确定性代码实现。
我见过一些团队试图用一个更大的模型来做所有防御,结果延迟高、成本高、还不可解释。正确的做法是,模型做模型擅长的事,代码做代码擅长的事,两者通过结构化的提议单和策略引擎衔接。这样既保证了防御的有效性,又保证了系统的可维护性和可解释性。
这套架构我在两个不同类型的 Agent 系统上落地过,车控场景的拦截率稳定在 8% 左右,运营场景在 12% 左右,误拦率都控制在 5% 以内。数字不算惊艳,但胜在稳定和可解释。如果你正在做类似的事情,建议先从指令-数据分离开始,这一层的投入产出比最高,而且它是另外两层的基础。