Agent 写正则来实现规则 ≈ 踩坑

文章来源声明: 原文作者:hpoenixf; 来源站点:掘金; 原文链接:https://juejin.cn/post/7688246386424791078; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

适合设计 Agent 架构与 Runtime 边界的团队:这篇清单式文章能帮你识别伪装成安全规则的语义分类器,避免把自然语言判断偷偷塞进确定性层。

最近在升级我的个人投资研究 Agent 时,我碰到了一个很容易被忽略的问题:

确定性实现,不等于客观判断。

代码当然可以稳定地执行一个正则、关键词表或布尔条件。

但“执行稳定”并不会自动赋予 Runtime 理解开放自然语言的能力。

如果 Runtime 并不知道一句话的真实语义,就不要因为能把它写成 if,假装自己知道。

这篇文章讨论的,就是 Agent 系统里一条很重要的边界:

Runtime 只应该对自己真正知道、能够追溯和复核的事实负责。


前情提要:我们在做什么 Agent

我们做的是一个偏严肃场景的个人投资研究 Agent。

它不是简单把用户问题丢给大模型,也不是让模型自己执行投资操作。

更接近这样的流程:

用户提出问题
→ Agent 判断需要什么信息
→ 按权限读取组合、材料或公开来源
→ 根据结果继续调查
→ 形成分析
→ 系统校验证据、权限和确定性状态
→ 发布答案

例如:

我的组合风险是不是太集中?
这两类 ETF 哪个更适合长期配置?
结合这份材料,重新评估之前的判断。

这种系统里,除了 Model,还会有一层 Runtime。

这里的 Runtime 不是语言运行时,而是围绕 Model 的 Host-owned 执行与状态层。

它负责的事情大致包括:

工具授权
调用执行
状态和回执
来源与 scope
确定性数值
持久化
恢复
发布
终态

Model 更适合负责:

理解用户
决定下一步查什么
比较信息
发现矛盾
形成判断
组织回答

问题就在于:

Runtime 到底应该管到哪里?


先给结论:谁拥有事实,谁才有资格触发硬规则

Runtime 应该负责它能够客观证明的事实。

例如:

某个工具是否真的调用
某个来源是否属于当前用户和当前 run
某个数据是否已经过期
某个状态绑定是否仍有效
某个执行回执是否存在
某个 publication 是否真的提交

这些信息来自 Host、持久化状态或明确协议。

它们有一个共同特点:

可以被审计和复核。

但下面这些问题不是一回事:

这句话是不是“当前事实”?
这句话是不是个人持仓断言?
这句话是不是投资建议?
这句话是否真的被证据支持?
这句话是否完整回答了用户?

这些属于语义判断。

它们不会因为实现语言是 TypeScript,或者判断逻辑写成了正则,就突然变成客观事实。

这就是标题真正想表达的东西:

Runtime 不知道的,不要因为能写一个规则,就假装它知道。


问题通常不是从“大型自然语言分类器”开始的

实际工程里,这类问题往往从一个非常合理的安全需求开始。

例如:

模型没有读取用户组合
却生成了一句“你的组合目前没有某类资产”

我们当然不希望系统把这种话当成可信的个人状态。

于是一个很自然的想法是:

如果答案里出现:
“你的组合”
“当前持仓”
“目前没有”
……
就要求 portfolio evidence

第一版看起来很合理。

而且 deterministic:

命中 → reject
不命中 → pass

问题是:

命中的是字符串模式,不是事实身份。

同一句话可能是:

真实断言
假设
引用
例子
否定
转述
模型错误

Runtime 看到的是字符。

它并不知道这句话在当前上下文里究竟扮演什么语义角色。

于是系统开始出现两种错误:

误拦:只是举例,却被当成真实个人状态
漏拦:换一种说法,就绕过了关键词

然后工程上很容易继续补:

更多关键词
更多正则
更多 reason code
更多 repair
更多测试

最后,一个最初只是小 helper 的东西,慢慢变成了“安全基础设施”。

这时候真正应该问的,已经不是:

正则覆盖率够不够?

而是:

这个规则到底有没有资格判断这件事?


一个最小例子:执行状态不能从文案里推出来

假设 Model 写了一句:

“已经替你完成了调整。”

Runtime 可以做两种事情。

错误方式:解释文本

发现“已经完成”
→ 判断这是执行声明
→ 检查有没有执行记录

这看起来像安全保护。

但问题是:

“已经完成”可能是引用
可能是否定句的一部分
可能是举例
可能只是文案错误

真正的执行状态,其实根本不需要从文字里猜。

Runtime 已经可以知道:

有没有执行请求
用户有没有确认
有没有调用允许的执行工具
有没有 execution receipt
账本状态有没有变化

所以执行状态应该由这些事实决定:

有 receipt → executed
没有 receipt → not executed

而不是:

文案像执行成功 → 猜它执行了

如果 Model 文案和真实状态冲突,那是语义质量问题

可以拒绝、降级或交给评审。

但“执行是否真的发生”,不应该由 prose 决定。


自由文本不能创造权威事实

这条边界可以进一步推广。

自然语言里可能出现:

“你的组合没有黄金”
“当前净值是……”
“我已经帮你调仓”

这些句子看起来分别像:

个人状态
当前事实
执行声明

但 Runtime 不应该仅凭文本,把它们升级成系统事实。

真正能触发确定性验证的,应该是 Host-owned typed facts,例如:

状态绑定
来源引用
数值句柄
执行回执
明确 scope

也就是说:

自由文本可以表达内容,但不能创建 authority。

Model 可以提出:

“我认为这里需要引用当前组合”

但是否真的存在当前组合、是否有权限、是否仍有效,必须由 Host 决定。

Model 也可以写出一个金额。

但一个自由形式的金额,不应该自动获得“这是可信当前投资金额”的身份。

真正的权威必须来自系统自己拥有的绑定。


哪些文本检查仍然适合 Runtime

这里不是说 Runtime 完全不能检查文本。

有一类检查是合理的:

1. 机器语法

例如:

ID
placeholder
cursor
protocol marker

2. 明确受保护字面量

例如:

secret
credential
token

3. 机械约束

例如:

长度
JSON/schema 是否合法
字段是否存在
格式是否符合协议

这些检查共同特点是:

不需要理解语言的意义。

真正危险的是把下面这些概念也交给字符串规则:

当前事实
个人状态
投资建议
执行
完整性
语义支持

这些不是“正则写得够不够好”的问题。

而是所有权就错了。


证据链存在,不等于自然语言一定正确

另一个很容易混淆的地方,是 evidence。

Runtime 可以客观证明:

某个来源确实被读取
结果确实进入了下一轮
最终 publication 确实绑定了这个来源
scope 和 freshness 检查通过

这些都很重要。

但它们并不能单独证明:

答案正确理解了来源
答案没有把数字写反
答案完整回答了问题
答案没有语义歧义

举一个最简单的例子。

来源里写的是:

A

模型回答:

B

即使:

read 发生了
citation 也绑定了

“B 是否被 A 支持”依然是语义问题。

所以:

provenance 完整,不等于 entailment 成立。

Runtime 可以证明:

“这句话绑定了哪个来源。”

但不能仅凭这一点证明:

“这句话一定正确表达了来源。”

这两个层次最好不要混在一起。


为什么 repair 成功,也不等于规则正确

这是这类 heuristic 最容易制造的错觉。

假设:

第一轮:
模型写了一个当前数值
规则命中 → reject

系统反馈:
不要输出未经验证的当前事实

第二轮:
模型把数值删掉,改成泛泛描述
规则不命中 → pass

从测试角度看:

repair 成功

但 underlying facts 并没有变化。

变化的只是:

wording

这可能说明:

事实真的被修正了

也可能只是:

模型学会绕过 matcher

因此至少要把两个概念分开:

mechanical repair success
semantic correctness

前者 Runtime 可以测。

后者不能从前者推出。


正确的边界不是“全部交给模型”

看到这里,很容易得出另一个极端结论:

那 Runtime 什么都别管,让 Model 自己判断。

也不对。

Runtime 非常适合负责:

permission
scope
freshness
receipt
state binding
numeric binding
publication
terminal state

因为这些是真实系统状态。

Model 非常适合负责:

这句话是什么意思
当前结果说明什么
还要不要继续查
两份资料是否矛盾
怎样回答用户

所以真正的目标不是:

Model 替代 Runtime

也不是:

Runtime 替代 Model

而是:

确定性事实交给 Runtime,开放语义交给 Model。


“Runtime 能知道的都不要让 Model 推理”还缺另一半

我们经常会说:

Runtime 能知道的,就不要让大模型推理。

这句话是对的。

例如:

某个工具是否调用成功
某个 publication 是否已经提交
某个数值是否来自可信计算

都没必要让 Model 猜。

但这句话只有一半。

另一半同样重要:

Runtime 不知道的,也不要因为能写一个 if,就假装它知道。

两句话合起来才完整:

Runtime 已知的
→ 不让 Model 重复维护

Runtime 不知道的
→ 不让规则冒充理解

这也是我们最近越来越认可的分工:

Runtime 维护世界,Model 理解世界。


两个简单案例

案例一:个人持仓

用户的真实组合状态应该来自:

授权
scope
组合数据
状态绑定
freshness

而不是:

答案里有没有出现“你的组合”

如果没有有效状态绑定,Model 写得再像真实持仓,也不能获得个人状态 authority。

如果状态绑定存在但已经过期,也不能因为文案听起来合理就升级成 current。


案例二:执行结果

执行是否发生应该来自:

明确请求
用户确认
工具调用
执行回执
实际状态变化

而不是:

答案里有没有“已完成”“已下单”

Model 可以写错。

Runtime 不能因为 Model 写错,就让世界状态跟着变化。

这其实是 Agent 系统最重要的一条原则:

语言描述世界,不等于语言创造世界。


一个实用的 Code Review 清单

以后看到一个新的 deterministic gate,我会优先问下面这些问题:

  1. 这个 gate 判断的到底是什么?
  2. 它的 authoritative input 来自哪里?
  3. 这个输入是 Host-owned fact,还是 Model prose?
  4. 如果把同一句话换个说法,结果会不会变化?
  5. 如果会变化,变化的是语义,还是系统事实?
  6. 自由文本是否能创建个人状态、金额或执行结果?
  7. evidence chain 证明的是来源追溯,还是语义正确?
  8. repair 成功以后,事实真的变了吗,还是 wording 变了?
  9. 没有语义评审时,系统有没有诚实保留“未评估”状态?
  10. 这个规则是在减少风险,还是把自然语言分类偷偷塞进 Runtime?

如果其中有几项答不上来,就值得重新考虑这条 gate 是否应该存在。


最后

Agent 系统很容易在追求可靠性的过程中,把越来越多判断写成确定性规则。

这本身不是问题。

问题在于:

不是所有能写成代码的判断,都是 Runtime 真正知道的事实。

正则可以稳定。

关键词表可以稳定。

布尔逻辑可以稳定。

但:

稳定执行一个判断,不等于这个判断拥有客观依据。

好的 Runtime 应该很严格。

但严格的前提是:

它只对自己真正拥有的事实负责。

Model 可以理解、推理和表达。

Runtime 可以授权、执行、记录和校验。

语义质量可以由 Model、独立评审或 Human 判断。

但不要让任何一层冒充另一层。

所以最后还是这两句话:

Runtime 已经知道的,不要让 Model 再维护。

Runtime 不知道的,也不要因为能写规则,就假装它知道。

好的 Agent 架构不是把所有复杂度搬进代码。

而是:

把确定性复杂度放进 Runtime,把语义复杂度留给 Model。