- Published on
Agent 写正则来实现规则 ≈ 踩坑
- Authors

- Name
- hpoenixf
最近在重构一个个人投资研究 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,我会优先问下面这些问题:
- 这个 gate 判断的到底是什么?
- 它的 authoritative input 来自哪里?
- 这个输入是 Host-owned fact,还是 Model prose?
- 如果把同一句话换个说法,结果会不会变化?
- 如果会变化,变化的是语义,还是系统事实?
- 自由文本是否能创建个人状态、金额或执行结果?
- evidence chain 证明的是来源追溯,还是语义正确?
- repair 成功以后,事实真的变了吗,还是 wording 变了?
- 没有语义评审时,系统有没有诚实保留“未评估”状态?
- 这个规则是在减少风险,还是把自然语言分类偷偷塞进 Runtime?
如果其中有几项答不上来,就值得重新考虑这条 gate 是否应该存在。
最后
Agent 系统很容易在追求可靠性的过程中,把越来越多判断写成确定性规则。
这本身不是问题。
问题在于:
不是所有能写成代码的判断,都是 Runtime 真正知道的事实。
正则可以稳定。
关键词表可以稳定。
布尔逻辑可以稳定。
但:
稳定执行一个判断,不等于这个判断拥有客观依据。
好的 Runtime 应该很严格。
但严格的前提是:
它只对自己真正拥有的事实负责。
Model 可以理解、推理和表达。
Runtime 可以授权、执行、记录和校验。
语义质量可以由 Model、独立评审或 Human 判断。
但不要让任何一层冒充另一层。
所以最后还是这两句话:
Runtime 已经知道的,不要让 Model 再维护。
Runtime 不知道的,也不要因为能写规则,就假装它知道。
好的 Agent 架构不是把所有复杂度搬进代码。
而是:
把确定性复杂度放进 Runtime,把语义复杂度留给 Model。