第二部 原理篇:规则驱动的智能体设计

第 14 章:学习与训练:治理下的自适应

14.1 工程问题的本质:学习与安全如何共存

智能体系统必须从反馈中改进——用户纠正"这句话识别错了"、训练产出"这个新词属于这个意图"、审批决定"这条规则允许"。但学习本身就是风险:一个能被任意修改的规则系统,其规则不再可信。

设计原理: 学习的工程核心不是"能不能学",而是学习与治理的边界。框架的统一答案:写库(学习必须发生)→ 审批(生效必须受控)→ 热更新(无需重启)。学习永远被记录,生效永远被治理,落地永远不需要重启——三者缺一,学习就会滑向"学不了"或"失控"两个极端。

14.2 反馈回路:从纠正到生效的闭环

L1(用户显式纠正)的闭环:纠正写入三通道(审批记录 + 规则表待审批行 + 训练样本)→ 审批链推进 → 通过后启用 + 热更新 → 识别链路第一步即时命中。

原理: 历史上 L1 反馈曾直接生效,存在绕过审批的风险——学习成果直接改变行为,等于用户可以无治理地改变系统规则。改为审批制后,学习的记录与生效被分离:纠正永远被记录(三通道写入),但生效必须经审批。这一设计承认学习的价值(记录不设门槛),同时拒绝学习失控(生效必经治理)。

把这条闭环摊开为时序,可看清"记录"与"生效"两个阶段的分离点,以及生效后如何通过缓存失效即时反哺识别,如图 14-1 所示:

图 14-1 训练反馈闭环时序:记录不设门槛,生效必经审批

sequenceDiagram
    autonumber
    participant U as 用户
    participant A as 业务智能体
    participant W as 三通道写入
    participant P as 审批链
    participant C as 进程内缓存
    participant R as 识别链路
    U->>A: 显式纠正(L1 信号)
    A->>W: 写审批记录
    A->>W: 写规则表待审批行
    A->>W: 写训练样本
    Note over W: 记录不设门槛(学习被承认)
    W->>P: 生成待训练提案(pending)
    P->>P: 审批门禁(可训练边界·硬规则除外)
    P-->>C: 通过 → 触发热更新
    Note over C: reload_rules / invalidate_cache
    C->>R: 缓存失效,下一步即时命中
    R-->>U: 同义表达被正确识别

读图要点: 分界在"三通道写入"与"审批链"之间——Note over W 之前的动作无条件执行(记录),之后才受治理约束(生效)。虚线返回箭头表示"审批通过"是一次回调式触发(14.4 节的热更新),而非轮询;invalidate_cache 之后,识别链路在下一次请求即可命中新样本,无需重启进程。

14.3 可训练对象的边界:一切皆可训练,硬规则除外

可训练对象覆盖意图规则、槽位、子意图关键词、消歧配置、规则逻辑、审批链、流程定义、模块开关、权限矩阵——但硬规则(bypass=false)与 SOD 不可训练。

原理: "一切皆可训练"服务于业务自适应的广度;"硬规则除外"守护治理的根基。若连"成本线""SOD"都可训练,治理就失去了参照系——训练自己给自己开绿灯成为可能。可训练性是一把以硬规则为轴心的伞:伞面越宽(可训练对象越多),轴心越必须坚固(硬规则越不可动)。

14.4 热更新:把"发版"变成"改数据"

训练生效的核心是进程级缓存失效:规则重载(reload_rules / 周期轮询)、槽位与子意图缓存失效(invalidate_cache)、规则参数缓存清除。

原理: 热更新的工程意义是把"上线"从部署动作变为数据动作。发版需要发布窗口、回滚计划、环境检查;改数据只需要审批通过 + 缓存失效。配置治理的价值正是使高频的、业务侧的调整不必经过低频的、工程侧的发布流程——变更的节奏匹配业务,而非匹配工程。热更新的前提是配置有版本与审批记录,可追溯、可回滚。

14.5 训练数据驱动的识别增强

训练样本反向增强识别:查询意图白名单由已审批样本动态加入(仅收录查询意图,执行意图不收录避免打断多轮延续);few-shot 样本按相似度动态选样(每意图限流 + 基线保底)。

原理: 这两个决策体现"训练影响识别"时的两个约束。白名单只收查询意图:执行意图的样本("我要入库"→stock_in)若进入白名单,会在多轮收集中打断 pending 延续——学习的成果不能以破坏对话状态为代价。few-shot 动态选样:全量注入成本高且新意图会挤出核心正例,相似度选样让"最相关的样本"进入上下文,兼顾质量与成本。

14.6 学习效果的评估与样本质量

14.6.1 评估体系:学习要证明有效

训练是否改善了系统,必须可度量。评估体系围绕"行为是否变好"设计,见表 14-1:

表 14-1 学习效果的评估指标

指标 定义 观察对象
意图识别准确率变化 训练前后准确率差 识别器(回归测试集)
误路由率 错误路由 / 总输入 路由层(线上采样)
规则命中率变化 训练后高频句式命中 规则层
审批通过后的生效率 生效规则 / 审批通过规则 训练链路(治理健康度)
误报修正率 用户纠正后同输入不再错 L1 反馈闭环

原理: 评估的工程要点是回归基线——每次训练变更应跑同一组测试样本,观察准确率是否回退。没有回归基线,"训练"就变成不可验证的行为改变:改了、上线、不知道好坏。学习与评估是一体两面:不评估的学习不可信,不学习的评估无意义。

14.6.2 样本质量:标签噪声与数据泄漏

训练样本的两个经典质量问题,在"人机协同训练"场景下同样存在,见表 14-2:

表 14-2 样本质量问题与应对

问题 场景 应对
标签噪声 用户纠正本身就是错的(纠正到错误意图) 审批制(人工确认纠正),不直接生效
数据泄漏 测试样本进入训练集,评估虚高 训练/评估样本分离,版本化样本集

原理: 人机协同训练的样本源是"用户纠正"与"训练产出"——两者都可能错。审批制正是对标签噪声的结构性过滤:只有经过审批的纠正才影响行为。数据泄漏的应对是样本的版本化与集分离——训练集与评估集不重叠,评估结果才可信。这两个问题的共同本质是:训练数据与验证数据的可信度必须分开治理。

14.7 失败模式与权衡

表 14-3 学习与训练的失败模式与权衡

失败形态 根因 设计应对
学习成果绕过治理 L1 直接生效 三通道写入 + 审批制
训练给自己开绿灯 硬规则可训练 硬规则/SOD 不可训练
训练成果不生效 缓存未失效 热更新 + 周期轮询
白名单打断多轮 执行意图进白名单 仅收查询意图

权衡: 审批制延迟了学习生效(纠正后需审批才能改变行为),换取治理安全。这是"改进速度"与"治理安全"的平衡点,且该平衡点可由业务侧调节——审批链的步数与角色本身可训练(治理之治理)。

14.8 业务单号的可训练化:从硬编码前缀到生成规则

单号(SO/CT/RT/WO/PAY…)是企业里的"身份凭证",也是本应"一切可训练"却最常被写死的一类。框架把单号分成两段纳入训练:前缀与生成格式。

前缀可训练化:load_numbering_prefix(database, keywords, fallback, cache_key, ttl=60) 读 business_rules 的 ENTITY_NUMBERING 规则,命中 intent 关键字即返回该规则的 prefix,否则回退现状前缀,并以 60s 缓存统一各生成端;各 Agent 通过统一的 _numbering_prefix 泛型委托,消除全部写死前缀。

格式可训练化:build_number(database, type_key, prefix, dynamic, *, fallback_fn) 依据 ID-SPECS 的 generate_template 拼装,模板 token 白名单形如 {prefix}{YYYY}{MM}{SEQ:n}{MS};按"模板含唯一性段({SEQ:n} 或 {MS})→ 整体字符集合法([A-Z0-9] 打头)→ 无未识别占位符"三道护栏校验,任一不过即回退 fallback_fn(fail-closed),保证未训练时与旧逻辑逐位一致。

设计原理: 单号可训练化的边界与 14.3 节一致——训练的是数据(前缀与模板),不是代码。变更走 ID-SPECS 的 L2 审批链(submit_id_specs_change → approve → apply),经治理后才改行为;代码只解释模板,不固化格式。

失败防范: 护栏保证"没配模板=旧行为",杜绝半配置状态产出非法单号;rule_hash 完整性校验保证 DB 模板未被篡改。镜像双写(agent↔api↔workflow_effect)须共享同一模板口径,否则同一业务两端单号不一致。

14.9 实现剖析:待训练提案、审批门禁与参数热加载

本节从 training/pending_training.py(481 行)与 runtime/param_loader.py(86 行)摘取两段,印证 14.2 节的反馈闭环、14.4 节的热更新与 14.3 节的可训练边界。

14.9.1 反馈闭环:提案挂起 → 审批门禁 → 生效

def add_pending(self, int_id, user_input, spec_intent,
                rule_intent, llm_intent="", source="spec_train"):
    """新增一条待人工判断的修正样本"""
    self.items.append({
        "int_id": int_id,               # 规格书意图编号(如 INT-01)
        "user_input": user_input,       # 用户原始输入
        "spec_intent": spec_intent,     # 规格书定义的正确意图
        "rule_intent": rule_intent,     # 当前规则识别结果(错误值)
        "llm_intent": llm_intent,       # 大模型判定意图(辅助参考)
        "source": source,               # 样本来源
        "status": "pending",            # ★ 初始状态:挂起,不生效
        "created_at": datetime.now().isoformat(),
    })
    self._save()
    return len(self.items)


def approve(self, idx, recognizer=None, database=None, user_role=None):
    """人工判断通过:按现有审核机制写入训练数据库"""
    # ★ 审批角色门禁:非 manager/admin 拒绝(与 L1 审批对称)
    if user_role and user_role not in ("manager", "admin"):
        raise RuntimeError(
            f"训练样本审批需 manager/admin 角色,当前角色「{user_role}」无权限")
    item = self.get_by_index(idx)
    if item is None or item.get("status") != "pending":
        return None

    # 通过审核机制写库:intent_rules 待审批 + workflow_configs 审批链
    self._submit_rule(item, idx=idx, recognizer=recognizer, database=database)
    item["status"] = "approved"
    item["approved_at"] = datetime.now().isoformat()
    self._save()
    return item

这段代码印证 14.2 节与 14.3 节:"可训练"不等于"可直接改"。修正样本先以 status="pending" 挂起(不生效),经人工判断 approve 后才写库;而 approve 本身还带角色门禁——非 manager/admin 直接抛 RuntimeError。

这就是 14.3 节"学习与安全共存"的落点:反馈回路(用户纠正 → 提案)与生效回路(审批 → 写库 → 热更新)被刻意分开,任何一次学习都不能绕过治理。_submit_rule 抛异常时不标记 approved,保证"写库失败不误报成功"。

14.9.2 参数热加载:rule_hash 完整性校验 + fail-closed

def _load_config(rule_id, db=None):
    """读取规则配置字典(失败返回空字典)"""
    try:
        row = db.query_one("business_rules", {"rule_id": rule_id},
                           ["config_json", "rule_hash"])
        if row and row.get("config_json"):
            cfg = row["config_json"]
            if isinstance(cfg, str):
                cfg = json.loads(cfg)
            if isinstance(cfg, dict):
                _stored = row.get("rule_hash")
                if _stored:
                    _salt = os.environ.get("RULE_HASH_SALT", "ai-factory-rule-hash-v6")
                    _canonical = json.dumps(cfg, ensure_ascii=False, sort_keys=True)
                    _computed = hashlib.sha256(
                        f"{rule_id}:{_canonical}:{_salt}".encode("utf-8")).hexdigest()
                    if _computed != _stored:            # ★ 哈希不符 → fail-closed
                        logging.getLogger(__name__).error(
                            "规则配置哈希校验失败(疑似篡改,拒绝加载): rule_id=%s",
                            rule_id)
                        return {}                        # 拒绝加载,回退默认值
                return cfg
    except Exception:
        pass
    return {}


def get_param(rule_id, key, default, db=None):
    """读取单参数(无 DB/失败/键缺失返回默认值)"""
    cfg = _load_config(rule_id, db)
    if key in cfg and cfg[key] is not None:
        return cfg[key]
    return default                                     # ★ 降级:代码内硬编码兜底

这段代码印证 14.4 节与 11.7 节:"改数据即改行为"的前提是数据可信。热更新省去了发版,但也意味着配置一旦被篡改就会直接改变业务行为——因此每次加载都校验加盐 SHA256(rule_id + 规范化 JSON + salt),不符即 return {} 拒绝加载。

拒绝加载后又由 get_param 回退到代码内的 default(硬编码基线),保证"篡改不会生效、系统仍可运行"。这条"校验—拒绝—回退"链是 11.7 节 rule_hash 设计在参数层的具体实现,也是"可训练"不蜕变为"可注入"的技术保障。

14.10 本章小结