类型化决策:让模型选动作,程序负责判断能不能做
本文是 Agent 工程里的经典架构 系列的一篇。文中的业务场景和代码为讲解设计,产品能力以所链接的官方资料为准。
很多 Agent 的核心动作其实很简单:在查询资料、请求审批、执行操作和结束任务之间选一个。为了得到这个选择,我们可能让通用模型先生成很长的解释,再从文字里提取工具名。多了一层解析,也多了一些无法预料的格式问题。
原文使用的 Jev 是 TypeSafe AI 面向软件决策提供的模型。按其官方介绍,它输出带类型的决策与置信信息,目标是让程序能够直接消费这些结果。本文讨论这种接口思路,不复述产品性能宣传,也不假设一个置信分数就足以保证正确。
我会把一次决策拆成三件事。首先,模型建议做什么;其次,系统对这个建议有多大把握;最后,在当前业务状态下是否允许执行。第二件事不能覆盖第三件事。即使模型非常确信应该退款,没有审批记录也不能退款。
下面的例子使用手工构造的模型结果。它没有调用 Jev,也不是官方 SDK 示例。返回值必须满足结构约束,动作必须属于程序提供的集合,低于演示阈值则进入人工复核。最后,执行退款仍然需要单独验证审批条件。
from dataclasses import dataclass
import math
@dataclass(frozen=True)
class Decision:
action: str
confidence: float
def validate(raw):
action, score = raw["action"], raw["confidence"]
if action not in {"lookup", "request_approval", "refund"}:
raise ValueError("未知动作")
if type(score) not in (float, int) or not math.isfinite(score):
raise ValueError("置信值不合法")
if not 0 <= score <= 1:
raise ValueError("置信值越界")
return Decision(action, score)
def route(decision, approved):
if decision.action == "refund" and not approved:
return "blocked"
if decision.confidence < 0.9: # 演示阈值,不能直接用于生产
return "review"
return decision.action
assert route(validate({"action": "refund", "confidence": 0.99}), False) == "blocked"
assert route(validate({"action": "lookup", "confidence": 0.6}), False) == "review"
assert route(validate({"action": "lookup", "confidence": 0.95}), False) == "lookup"
print("高置信不能越过授权,低置信进入复核")
完整示例可下载,使用 Python 3.9 或以上版本运行:python3 example.py。只依赖标准库,不调用真实业务服务。
类型化接口首先解决的是结构歧义。程序可以明确拒绝未知动作、错误字段和不合法的数值,不需要从一段自然语言解释里猜测含义。不过,结构合法的结果仍然可能选择了错误动作。JSON Schema 验证与业务正确性评估,是两项不同的工作。
置信度的使用也需要小心。模型输出一个零点九,不表示每个业务场景都有九成正确率。只有在与实际分布相近的数据上观察预测和结果之间的关系,才能讨论分数是否校准。数据变化、工具说明调整或模型升级,都可能改变这种关系。
示例中的阈值只是为了展示控制流。生产阈值需要根据错误代价决定:多查一次资料和错误发起退款的代价不同,不能用同一个数字覆盖全部动作。人工复核容量也要算进去,否则阈值设得很高,所有请求都转人工,系统表面上很稳,业务队列却越来越长。
我会建立一组包含边界情况的样本,记录动作选择、最终业务结果和复核结论。评估时不只看总体准确率,还看高影响动作的误触发率、拒绝比例和每类任务的覆盖率。分类分布不均衡时,只会选择最常见动作的模型,也可能得到一个看起来不错的平均指标。
与高并发系统的关系,在于这类决策可以成为一个职责明确的独立服务。调用预算、超时、熔断与降级都能够围绕它设计。决策服务不可用时,可以使用确定性规则处理简单场景,其余暂停或转人工。但不能为了维持吞吐量,跳过关键授权检查。
需要持久执行时,还应记录本次输入、候选动作、模型标识和返回结果。在历史回放中复用已经确认的决策,新的决策轮次才重新调用模型。这样既减少重复成本,也让“为什么昨天选了这个动作”有可追踪的依据。
参数也需要同样严格。选对了“退款”动作,却填错订单编号或金额,仍然会产生错误结果。工具应限制参数类型与取值范围,并从服务端读取可操作对象;如果候选对象很多,可以先缩小范围,再让模型选择,减少开放式生成标识的机会。
需要对比模型时,可以使用同一份脱敏样本和同一套执行约束。分别观察决策质量、等待时间和总调用成本,才能判断专门的决策模型是否适合当前环节,而不是把产品定位当成选型结论。
类型化决策适合选择空间明确、结果能够核验的环节;开放式写作、复杂方案生成仍然需要不同能力。工程上的收益来自清楚地划分职责:让模型处理需要判断的部分,让程序承担结构、权限和执行约束。这样即使以后更换模型,业务系统仍然知道自己在接受什么样的输入。