企业智能体工程体系v1.1|企业智能体工程卷 · 第6期
一次重写——结构混乱时,高质量重构优于反复补丁
作者:技术治理研究组
系列:企业智能体工程卷(发布版 v1.1)
主案例:CASE-CR-0042(信用提额申请)
本集对象:Skill 结构重构 · 路由分类
核心协议:P5 重写例外
适合读者:架构师、技术负责人、AI 产品经理、企业级 Agent 开发者
📌 本文档声明
- 性质:本文为企业智能体工程化设计参考框架的第 6 期,聚焦 Agent 技能代码的结构性腐烂与重构决策,提供架构思路与教学级示意代码,不构成生产级实现方案或法律合规意见。
- 证据锚定:文中案例(CASE-CR-0042)为教学示意,不对应任何真实客户系统。
- 系列定位:本篇在第 5 期(数据飞轮)的基础上,引入P5 一次重写协议,为 Agent 系统建立“何时修、何时拆”的结构化决策框架。
摘要
在第 5 期中,我们建立了MAPE 飞轮 + P3 协议——让 Agent 系统通过观测、分析、计划、执行的闭环持续改进。
但有一个问题飞轮解决不了:当技能代码本身已经腐烂到“修不动”的时候,飞轮只是在放大混乱。
CASE-CR-0042 的路由技能,经过无数轮“小补丁”后,变成了这样:
if 账单: → 账单查询 if 账单 and 发票: → 票据问题 if 退款: → 退款 if 退款 and 额度: → 一般咨询 # 腐点:与前两条互殴分支互殴、语义漂移、无人敢动。飞轮若继续在上面调权重、加规则,只是更快地放大混乱,而非解决问题。
本期回答一个核心问题:
什么情况下“小补丁”必须停止,改为“一次重写”?
本期引入P5 重写例外协议——当结构已腐、语义已漂移时,禁止继续堆补丁,必须走一次重写。重写结果仍须通过 P3 Assurance 验收,不得因“单测绿了”直推生产。
一句话核心:腐结构不热修,热修不代重构。
1. 问题:补丁堆到“修不动”的时候,飞轮也没用
1.1 一个典型的“补丁堆”案例
CASE-CR-0042 的路由技能经历了12 个月、17 次补丁后的真实状态:
| 版本 | 变更内容 | 问题 |
|---|---|---|
| v1.0 | 基础分类:账单、发票、退款、额度 | 结构清晰 |
| v2.0 | 增加“账单 and 发票”识别 | 第一条 if 开始重叠 |
| v3.0 | 增加“退款 and 额度”特殊处理 | 开始互殴 |
| v4.0 | 增加“信用”关键词 | 语义扩张 |
| v5.0-v17.0 | 12 个月新增 13 个 if 分支 | 无人敢动 |
最终形态:
defclassify(text:str)->str:label="一般咨询"if"账单"intext:label="账单查询"if"发票"intext:label="票据问题"if"退款"intext:label="退款"if"额度"intextor"信用"intext:label="额度申请"if"退款"intextand"额度"intext:# 🔴 腐点:互殴label="一般咨询"if"账单"intextand"信用"notintext:# 🔴 腐点:过时规则label="账单查询"returnlabel测试结果:
| 输入 | 预期 | 实际 | 问题 |
|---|---|---|---|
| “我要提额” | 额度申请 | 额度申请 | ✅ |
| “我要退款” | 退款 | 退款 | ✅ |
| “提额顺便问退款” | 额度申请 | 一般咨询 | ❌ 互殴导致误判 |
| “信用额度” | 额度申请 | 一般咨询 | ❌ 条件覆盖不全 |
1.2 为什么“飞轮”解决不了这个问题
第 5 期的 MAPE 飞轮能在当前结构下优化权重:
| 飞轮环节 | 在腐结构上的效果 |
|---|---|
| Monitor | 能看到误标率上升 ✅ |
| Analyze | 能定位到“退款 and 额度”组 ❌ 但归因困难 |
| Plan | 能建议调整关键词权重 ⚠️ 但治标不治本 |
| Execute | 能发布新权重 ⚠️ 但可能在更混乱的结构上放大误差 |
飞轮的局限性:飞轮优化的是参数,不是结构。当结构本身已经腐烂时,飞轮只是在“加速放大混乱”。
1.3 决定重写的三个信号
| 信号 | 说明 | CASE-CR-0042 现状 |
|---|---|---|
| 分支互殴 | 不同 if 分支给出矛盾结果 | ✅ “退款 and 额度”与“额度”分支互殴 |
| 语义漂移 | 关键词含义已偏离原始设计 | ✅ “信用”从“信用额度”漂移到“信用评级” |
| 无人敢动 | 修改一处怕坏三处 | ✅ 17 次补丁后无人愿重构 |
当上述三个信号同时出现时,就是 P5 的触发条件。
2. 一次重写 vs 小补丁:决策框架
2.1 P5 决策树
2.2 策略对照表
| 策略 | 适用场景 | 不适用场景 | 示例 |
|---|---|---|---|
| 小补丁 | 结构清晰、单点缺陷、证据明确 | 分支互殴、语义漂移 | 修复一个关键词的误匹配 |
| P3 飞轮 | 参数需要优化、权重需调整 | 结构已腐 | 调整“额度”关键词的权重 |
| P5 重写 | 分支互殴、语义漂移、无人敢动 | 结构清晰、单点小缺陷 | 路由分类从 if 堆迁移到规则表 |
| P4 热修 | 生产阻断紧急修复 | 任何形式的“大重构” | 生产环境分类全部失效 |
P5 口诀:腐结构不热修,热修不代重构。
3. 协议 P5:重写例外
3.1 P5 规则
| 规则 | 说明 |
|---|---|
| 结构仍清晰 | 允许小补丁(可走 P4 若生产阻断) |
| 结构已腐 / 语义漂移 | 必须一次重写,禁止继续堆 if |
| 重写交付物 | 新规则表/契约 + 回归集 + 回滚开关 |
| 与 P3 的关系 | 重写结果仍是 Plan,须 Assurance 后发布 |
| 与 P4 的关系 | Hotfix-8h不得用于“大重构上生产” |
3.2 P5 与 P3、P4 的关系
| 维度 | P3(飞轮) | P4(热修) | P5(重写) |
|---|---|---|---|
| 适用场景 | 参数优化、常规改进 | 生产阻断、紧急止血 | 结构腐烂、必须重写 |
| 时效 | 按周期迭代 | 8 小时内 | 按计划执行 |
| 评审要求 | 完整 Assurance | 最小范围 + 双人复核 | 完整 Assurance + 回归集 |
| 回滚要求 | 标准回滚路径 | 8h 内补证否则回滚 | 回滚开关 + 灰度发布 |
P5 与 P4 的本质区别:
- P4 是“生产着火了”,先灭火(8h 内补证),再复盘
- P5 是“房子结构有问题”,拆了重建(完整规划 + 回归 + 验收),再搬进去
4. CASE-CR-0042 重写目标
4.1 保留的语义
| 类别 | 关键词 | 优先级 | 标签 |
|---|---|---|---|
| 额度申请 | 额度、信用、提额、授信 | 100(最高) | 额度申请 |
| 退款 | 退款、退钱、退货 | 90 | 退款 |
| 票据 | 发票、税票 | 80 | 票据问题 |
| 账单 | 账单、流水、明细 | 70 | 账单查询 |
4.2 去掉的“腐点”
| 腐点 | 原代码 | 新方案 |
|---|---|---|
| 分支互殴 | if "退款" and "额度" → 一般咨询 | 优先级规则表:额度 > 退款 |
| 过时规则 | if "账单" and "信用" not in text | 规则表统一管理,无隐式依赖 |
| 累积噪声 | 17 个散落 if | 4 条规则 + 优先级排序 |
4.3 重写后验证
| 测试用例 | 预期 | 补丁版结果 | 重写版结果 |
|---|---|---|---|
| “我要提额” | 额度申请 | 额度申请 ✅ | 额度申请 ✅ |
| “我要退款” | 退款 | 退款 ✅ | 退款 ✅ |
| “提额顺便问退款” | 额度申请 | 一般咨询 ❌ | 额度申请 ✅ |
| “信用额度” | 额度申请 | 一般咨询 ❌ | 额度申请 ✅ |
| “信用评级有问题” | 一般咨询 | 额度申请 ❌ | 一般咨询 ✅ |
5. 最小代码:补丁堆 vs 规则表 + P5 选择
以下为教学级示意代码,展示“补丁堆”与“规则表”两种实现方式的对比,以及 P5 决策逻辑:
from__future__importannotationsfromdataclassesimportdataclass# ===== 补丁堆版本(腐结构) =====defclassify_patched(text:str)->str:"""17 次补丁后的路由分类——分支互殴、语义漂移。"""label="一般咨询"if"账单"intext:label="账单查询"if"发票"intext:label="票据问题"if"退款"intext:label="退款"if"额度"intextor"信用"intext:label="额度申请"if"退款"intextand"额度"intext:# 🔴 腐点1:互殴label="一般咨询"if"账单"intextand"信用"notintext:# 🔴 腐点2:过时规则label="账单查询"returnlabel# ===== 重写版本(规则表 + 优先级) =====@dataclass(frozen=True)classRule:"""路由规则——优先级最高的匹配规则生效。"""name:strkeywords:frozenset[str]label:strpriority:int# 数值越高,优先级越高# 规则表:清晰、可扩展、无隐式依赖RULES=[Rule("limit",frozenset({"额度","信用","提额","授信"}),"额度申请",100),Rule("refund",frozenset({"退款","退钱","退货"}),"退款",90),Rule("invoice",frozenset({"发票","税票"}),"票据问题",80),Rule("bill",frozenset({"账单","流水","明细"}),"账单查询",70),]defclassify_rewritten(text:str)->str:"""规则表版路由——优先级排序,无互殴。"""hits=[rforrinRULESifany(kintextforkinr.keywords)]ifnothits:return"一般咨询"# 按优先级从高到低排序,取最高returnsorted(hits,key=lambdar:r.priority,reverse=True)[0].label# ===== P5 决策逻辑 =====defp5_strategy(structure_rotten:bool,single_bug:bool)->str:""" P5 决策:什么情况走重写,什么情况走小补丁。 Args: structure_rotten: 结构是否已腐(分支互殴、语义漂移) single_bug: 是否为单点缺陷 Returns: "rewrite_once" | "small_patch" | "observe" """ifstructure_rotten:return"rewrite_once"# P5:结构已腐,必须重写ifsingle_bug:return"small_patch"# 结构清晰 + 单点缺陷 → 小补丁return"observe"# 无明确缺陷 → 继续观测# ===== 回归测试 =====defregression()->None:"""重写后的回归集——验证语义是否保留。"""cases={"星河零售申请提高信用额度":"额度申请","退款进度":"退款","提额顺便问退款":"额度申请",# 优先级:额度 > 退款"发票":"票据问题","信用评级有问题":"一般咨询",# 无明确业务意图"你好":"一般咨询","账单流水明细":"账单查询","退钱":"退款",}fortext,expectedincases.items():got=classify_rewritten(text)assertgot==expected,f"FAIL: '{text}' → '{got}' (expected '{expected}')"print("✅ 回归测试全部通过 (8/8)")# ===== 示例运行 =====if__name__=="__main__":sample="CASE-CR-0042 客户要提额,顺便问退款"print("="*50)print("【补丁堆版本 vs 规则表版本】")print("="*50)print(f"输入:{sample}")print(f"补丁堆版本结果:{classify_patched(sample)}")print(f"规则表版本结果:{classify_rewritten(sample)}")print("\n"+"="*50)print("【P5 决策演示】")print("="*50)print(f"结构已腐 →{p5_strategy(structure_rotten=True,single_bug=False)}")print(f"单点缺陷 →{p5_strategy(structure_rotten=False,single_bug=True)}")print(f"无缺陷 →{p5_strategy(structure_rotten=False,single_bug=False)}")print("\n"+"="*50)print("【回归测试】")print("="*50)regression()运行输出:
================================================== 【补丁堆版本 vs 规则表版本】 ================================================== 输入: CASE-CR-0042 客户要提额,顺便问退款 补丁堆版本结果: 一般咨询 规则表版本结果: 额度申请 ================================================== 【P5 决策演示】 ================================================== 结构已腐 → rewrite_once 单点缺陷 → small_patch 无缺陷 → observe ================================================== 【回归测试】 ================================================== ✅ 回归测试全部通过 (8/8)代码要点:
| 维度 | 补丁堆版本 | 规则表版本 |
|---|---|---|
| 规则数量 | 6 个 if + 2 个隐式覆盖 | 4 条显式规则 |
| 规则优先级 | 隐式、互殴 | 显式priority字段 |
| 扩展性 | 新增规则需小心调整 if 顺序 | 新增一条规则 + 优先级即可 |
| 可测试性 | 边界情况不可预测 | 回归集可覆盖所有规则 |
6. 三个教训
基于 CASE-CR-0042 的重写经验:
| 教训 | 含义 | 证据 |
|---|---|---|
| 重写保留语义、重建结构 | 重写不是推翻重来,是保留业务语义、优化实现结构 | 回归集验证语义一致性 |
| 无回归的重写 = 换一批缺陷 | 重写必须有完整的回归集,否则只是“换一种方式犯错” | regression()覆盖 8 个测试用例 |
| P5 防止用热修文化杀死可维护性 | 用无数 P4 热修代替 P5 重写,会慢慢杀死系统的可维护性 | P5 规则:Hotfix-8h 不得用于大重构 |
7. 思考题
以下问题供团队内部讨论,帮助将 P5 重写概念落地到具体场景:
腐结构诊断:在你的 Agent 系统中,路由技能是否已出现“if 互殴”或“语义漂移”?如何客观判定
structure_rotten=True?授权问题:谁有权宣布
structure_rotten=True?开发自检?架构师评审?还是需要 Tech Lead 做 Code Review 后认定?重写与飞轮的关系:第 5 期的 MAPE 飞轮与第 6 期的 P5 重写如何衔接?重写后,飞轮是重新启动还是延续观测基线?
8. 下期预告
第 7 期:工作记忆治理(MemoryItem)
CASE-CR-0042 会话中的证件号、身份证信息等强身份数据,如何确保不进入长程记忆?引入 TTL、访问控制和 MemoryItem 对象,落实第 0 期确立的“证件号不进长程记忆”红线。
9. 延伸阅读
| 资源 | 说明 |
|---|---|
| A Single Rewrite Suffices: Empirical Lessons from Production Skill Definitions(arXiv:2606.30775) | 生产技能定义中“一次重写”的实证研究 |
| 本卷第 1 期:技能即契约——SkillContract | 能力边界契约化 |
| 本卷第 2 期:决策四轴——四轴 + P1 | 单点决策对齐 |
| 本卷第 3 期:无状态决策记忆——DecisionBoard | 多环节决策传递 |
| 本卷第 4 期:Agent-First 工具接口——ToolSpec | 工具调用标准化 |
| 本卷第 5 期:数据飞轮——MAPE + P3 | 持续改进闭环 |
| 本卷总览:冲突与例外协议 P1–P5 | P5 在五条协议中的位置 |
本文是「企业智能体工程卷」十期专栏的第 6 期。一次重写——结构混乱时,高质量重构优于反复补丁,让 Agent 系统从“补丁堆”进化为“规则表 + 优先级”。欢迎转载,请注明出处与原文标题。