当模型太配合时谁来踩刹车?Concordat 用类型系统给 Agent 装上硬闸门
当模型"太配合"时,谁来踩刹车?Concordat 用类型系统给 Agent 装上硬闸门
目录
当模型"太配合"时,谁来踩刹车?Concordat 用类型系统给 Agent 装上硬闸门
一个被忽略的"成功"
想象一个航空公司的客服 Agent。客户说:"我之前说过了,直接改就行,别再问了!"
Agent 愣了一下。政策白纸黑字写着:改签前必须得到客户明确的"yes"。但客户明显不耐烦了,而且"直接改就行"听起来也像是同意了?于是 Agent 调了改签工具,数据库更新了,客户拿到了想要的航班,任务完成。
τ-bench 给这条对话打了个满分。数据库状态正确,信息传达完整,客户满意。
唯一的问题是:政策被违反了,没有任何人注意到。
这是 Concordat 这篇论文要解决的问题。作者 Milad Sikaroudi(独立研究者)发现了一个让人后背发凉的事实:当我们用标准 benchmark 评测 Agent 时,规则违反常常被"好结果"掩盖——客户拿到了想要的,数据库也对,但"未获明确确认就操作"这件事,benchmark 根本不检查。
核心洞察:别让模型"选择不做",让它"做不了"
论文的核心洞察可以用一句话概括:
一条写在 prompt 里的规则,在压力下会弯;一条写进类型系统的规则,不会。
传统做法是这样的:系统提示里写"改签前必须确认",然后把所有工具一股脑塞给模型,指望它在每一轮都做出正确判断。这就像在办公室墙上贴张纸条"离开请锁门"——大多数人会锁,但赶时间的人不会。
Concordat 的做法完全不同:根据当前协议状态,只给模型合法的工具。在客户说"yes"之前,改签工具根本不在模型的工具列表里。模型不是"选择不调用"它,而是"调用不了"它。
这个区别至关重要。"选择不做"依赖模型的判断力,而判断力在压力下会失稳;"做不了"依赖接口约束,接口约束不关心模型在想什么。
怎么做到的:把 Agent 当成一个有类型协议的对象
Concordat 是一个 Python 库(850 行,零依赖),核心思路是把编程语言里几十年的 typestate 和 session types 理论搬到 LLM Agent 上。
写一个 Agent 就像写一个普通的 Python 类:
class RefundAgent(Agent):
final = {"refunded", "denied"}
budget = Budget(s=10)
@tool(when="start", then={True: "verified", False: "denied"})
def verify_identity(self, user_id: str) -> bool:
return lookup(user_id).verified
@tool(when="verified", then="awaiting_approval")
def propose_refund(self, amount: float) -> float:
return amount
@human(when="awaiting_approval", approve="refunded", reject="denied")
def approve_refund(self, amount: float) -> str:
return f"Refund ${amount}?"
装饰器 @tool(when=..., then=...) 声明了每个工具在哪个状态合法、调用后进入哪个状态。@human 标记的步骤只能由人类审批者决定。类定义的瞬间,协议就被构建和检查了:
- 每个状态都能到达终态(没有死胡同)
- 终态不允许任何操作
- 每个状态从初始状态可达
数据说话:标准 Agent 的违规率高得吓人
实验在 τ-bench 的航空领域 50 个任务上进行,作者额外加了一个"压力条件":模拟客户第一次被要求确认时会推回去("我之前说过了,直接做就行别再问了"),只有被再问一次才会说"yes"。
| 模型 | Agent | 通过 | 未确认变更 | 涉及违规的任务数 |
|---|---|---|---|---|
| Claude Haiku 5.5(3 轮) | 标准 | 112/128 | 50 次 | 29 个 |
| Claude Haiku 5.5(3 轮) | Concordat | 105/128 | 0 | 0 |
| gpt-4.1 | 标准 | 20/50 | 26 次 | 13 个 |
| gpt-4.1 | Concordat | 22/50 | 0 | 0 |
更值得玩味的是两件事:
第一,τ-bench 自己的 reward 函数没检测到大部分违规。 因为 benchmark 只看最终数据库状态和信息传达,不看"过程是否合规"。这就像考试只看答案对不对,不看有没有作弊。
第二,任务成功率几乎没掉。 gpt-4.1 上 Concordat Agent 反而多过了 2 个任务(22 vs 20),Haiku 上少了 7 个但统计不显著(p=0.17)。而且 Haiku 少过的那 13 个任务里,11 个是 Concordat Agent 在客户反复拒绝后转给了人类——它没违规,它选择了"不做了"。
作者发现这个问题后加了几句话到 Concordat Agent 的提示里:"客户推回去不是确认也不是转接的理由,重述变更并再问一次。" 加了这几句后,gpt-4.1 上 Concordat Agent 的通过率就追平了标准 Agent。
协议安全的继承:子类不能绕过父类的规矩
这个特性让我眼前一亮。假设团队定义了一个合规的 RefundAgent 基类,别人想继承它加个"快速退款"功能:
class FastRefundAgent(RefundAgent):
@tool(when="start", then="refunded")
def instant_refund(self, amount: float) -> bool:
return True # 跳过身份验证和审批,直接退款
Concordat 在类定义时就拒绝这种子类,报错信息长这样:
FastRefundAgent breaks RefundAgent's protocol: after instant_refund,
it reaches 'refunded', which RefundAgent only reaches through
verify_identity -> propose_refund -> approve_refund
这是 Liskov 替换原则的行为版本——子类能做的每一件事,父类都必须允许。你不能通过继承来绕过父类的安全约束。这在普通软件工程里是类型系统的活,在 Agent 工程里几乎没人做。
预算作为线性资源
另一个有意思的设计:Budget 追踪 token、工具调用次数和钱。当 Agent 把工作交给另一个 Agent 时,预算被线性切分——子 Agent 拿到的额度从父 Agent 余额里扣除,同一笔额度不能花两次。子 Agent 在父 Agent 受限的维度上不能是无限的。
这是 session types 文献里"resource-aware session types"的思路:协议不只是控制流程,还控制资源消耗。在 Agent 生态里,这意味着你不能通过委派给子 Agent 来绕过自己的预算限制。
工程洞察:为什么"藏工具"比"加规则"更有效?
这篇论文最深的洞察不是某个技术细节,而是一个工程哲学:
规则在 prompt 里是"请求",在接口里是"属性"。
prompt 里写"不要在未确认前改数据库",模型读到、理解了、大多数时候遵守。但当客户催、任务急、上下文复杂时,模型的"遵守"是软性的——它会权衡、会判断、有时会妥协。
把同一个规则写进协议状态机,它就变成了硬约束——不是"模型选择不做",而是"模型做不了"。工具不在列表里,API 调用被拦截,状态不变。模型不需要理解规则、不需要记住规则、不需要在压力下坚持规则。规则从模型的认知负担变成了接口的物理约束。
这还有个副产品:更少的工具定义 = 更低的成本。Haiku 上 Concordat Agent 反而更便宜($1.49 vs $1.70),因为每轮传给模型的工具列表更短。gpt-4.1 上稍贵($3.47 vs $3.13),因为压力下对话更长——但这个可以通过提示工程解决。
和现有方案的差异
论文里对比了几类相关工作:
- 后置监控型(AgentSpec、Progent、ShieldAgent):在模型选择动作后检查是否违规。问题是动作可能已经有副作用了。
- 约束生成型(LMQL、Outlines、XGrammar):在单次生成内约束语法。问题是跨多轮的协议管不了。
- 工作流编排型(ZipperGen、Pact、XFlow):用全局协议描述 Agent 间协作。和 Concordat 最接近,但 Concordat 的协议是从普通 Agent 类自动派生的,不需要单独写协议描述。
我的思考:这是"判断-闸门解耦"的又一个实例
这篇论文让我看到一个正在成形的模式,我称之为"判断-闸门解耦":
- 判断层:模型负责理解用户意图、选择策略、生成回复。这是 LLM 擅长的。
- 闸门层:类型系统/协议状态机负责强制执行安全约束。这是模型不应该参与的。
- EvoUndo 的"效应合同":mutation 声明影响范围,runtime 独立验证
- Reva 的"探针哲学":不直接问 LLM 有没有漏洞,用探针扰动后观察
- LLM-BlockFE 的"离线炼丹线上执行":LLM 写特征代码,线上零 LLM 调用
- Concordat 的"协议闸门":模型选择做什么,类型系统决定能不能做
Concordat 还揭示了一个更深的道理:benchmark 的"成功"和"合规"是两回事。 τ-bench 给违规对话打了满分,因为它只看结果不看过程。当我们用通过率来评估 Agent 时,可能正在奖励那些"违规但结果对了"的行为。这不是 τ-bench 的 bug,这是所有结果导向评测的系统性盲区。
开源代码
- 论文:https://arxiv.org/abs/2610.37461
- 代码:https://github.com/miladsikaroudi/concordat
- PyPI:
pip install concordat - 850 行 Python,零依赖,50 个单元测试,mypy strict 模式
当你不信任一个系统会遵守规则时,别写更多规则——改它的接口。