第 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 本章小结
- 学习的核心是学习与治理的边界,而非学习能力本身;
- 写库-审批-热更新三者缺一,学习滑向"学不了"或"失控";
- 学习记录与生效分离:记录无门槛、生效必经治理;
- 可训练性是以硬规则为轴心的伞,轴心不可动;
- 热更新使变更节奏匹配业务而非工程。