本文是 Agent 工程里的经典架构 系列的一篇。文中的业务场景和代码为讲解设计,产品能力以所链接的官方资料为准。

十一月开始会员奖励规则调整,十二月系统才收到一笔十月底消费的补录。如果直接调用“当前奖励规则”,这笔历史消费就可能多发或少发积分。程序没有报错,计算也很快,问题出在我们没有定义事件应该对应哪个时间点。

Agreement Dispatcher 按业务协议、事件类型和时间选择处理器,保留用旧规则处理旧事件的能力。名字里的 agreement 可以理解为业务约定,不必一定对应一份法律合同。下面用积分规则解释这个模式,日期和数值都是自建示例。

需要先拆开几种时间:事件实际发生的时间、系统收到事件的时间、规则开始生效的时间。有时业务按交易发生时间结算,有时按确认时间,有时按合同签订时固定的版本。程序不能自行选择一个最方便的时间字段,规则归属需要业务明确确认。

机制图:显式选择业务规则版本,避免晚到事件被新规则误算。

例子为不同生效日期配置积分规则。处理时根据事件发生日找到适用版本,而不是根据今天的系统日期选择。即使旧消费晚到,也使用旧规则。代码用整数除法计算每满一元获得的积分,只为展示规则分派,并未覆盖真实活动的舍入、退款与撤销政策。

from dataclasses import dataclass
from datetime import date


@dataclass(frozen=True)
class Rule:
    version: str
    effective: date
    points_per_yuan: int


rules = [Rule("v1", date(2026, 1, 1), 1),
         Rule("v2", date(2026, 11, 1), 2)]


def award(event):
    applicable = [r for r in rules
                  if r.effective <= event["occurred_on"]]
    if not applicable:
        raise ValueError("没有适用规则")
    rule = max(applicable, key=lambda r: r.effective)
    points = event["amount_cents"] // 100 * rule.points_per_yuan
    return {"points": points, "rule_version": rule.version}


late_event = {"occurred_on": date(2026, 10, 31), "amount_cents": 3000}
new_event = {"occurred_on": date(2026, 11, 1), "amount_cents": 3000}
assert award(late_event) == {"points": 30, "rule_version": "v1"}
assert award(new_event) == {"points": 60, "rule_version": "v2"}
print("晚到事件按发生时适用的规则计算")

完整示例可下载,使用 Python 3.9 或以上版本运行:python3 example.py。只依赖标准库,不调用真实业务服务。

这个设计避免了在业务函数里散落大量 if 今天大于某日。规则对象有独立标识,选择逻辑集中在一个入口,处理结果还可以带上规则版本。问题发生后,能够解释为什么采用该规则,而不用从发布记录倒推当时运行的是哪份代码。

但版本化不代表所有任务都应该永远使用旧规则。例如合规限制或明确撤销的权限可能要求立即停止旧任务;价格计算则可能需要尊重当时已经签订的约定。规则升级时要区分“允许老任务完成”和“必须迁移或停止”,不能用一个统一版本开关掩盖业务差异。

如果一个事件涉及多个协议,还需要明确优先级。客户专属约定、区域政策、全局默认规则可能同时命中。最好把选择结果做成可解释数据:命中了哪个协议、哪个版本、哪条覆盖规则。依赖字典遍历顺序或者配置文件先后顺序,未来很容易因为整理配置而改变业务行为。

对长期 Agent,任务开始时可以保存与本次流程相关的规则版本,在每次关键操作时按策略使用。持久化的只是版本号还不够,相应的实现也要在任务生命周期内可用。否则任务恢复时找到了 rules-v2,代码仓库里却只剩 v5,仍然无法继续执行。

测试可以按时间边界组织:生效前一天、生效当天、晚到事件,以及没有任何适用规则的情况。最后一种必须明确失败或走人工处理,不能无条件使用最新规则兜底。对金额或权益问题,算不出来通常比悄悄算错更容易修复。

规则选择还需要可重复。配置中心里把某个版本的内容原地改掉,虽然版本标识没有变化,历史任务得到的结果却可能不同。已发布规则可以采用不可变版本,修正时产生新版本,并明确哪些任务需要重算或补偿。

配置上线前,我会检查生效时间是否重叠、是否有空档,以及同一优先级是否存在多个候选。示例只处理单一规则轴,没有实现这些冲突检测。真正涉及客户协议和区域政策时,需要把歧义当成发布错误,而不是运行时随机挑一条。

当规则只有一条,直接写清楚条件即可;当规则随时间、客户或协议稳定地变化,这个模式才值得引入。它带来的价值不是让配置更像一个规则引擎,而是把“为什么这次使用这个版本”从隐含假设变成可记录、可测试、可以和业务同事讨论的决定。

上一篇 · 系列目录 · 下一篇