1. 从一段让人抓狂的代码说起去年冬天我接手了一个内部工单系统的重构。前任开发者离职时留下一句话“逻辑都在processOrder里你看代码就懂了。”我打开那个文件三千两百行嵌套了十七层 if-else变量名从a1排到z9注释只有一句“这里很复杂别动”。我花了整整三天才理清一条主流程用户提交工单根据部门、优先级、SLA 等级、当前值班人、历史处理时长决定派给谁、抄送谁、超时怎么升级。更让人崩溃的是业务方每周都在改规则。“周三下午提交的紧急工单如果值班组在开会就转给备班。”这种话翻译成代码就是又一层 if。改一次测三天上线还容易漏掉分支。那段时间我一直在想一个问题业务逻辑为什么一定要写成代码后来我尝试了一个在当时看来有点“离经叛道”的做法把派单规则从代码里抽出来写成一份结构化的 JSON 配置让大模型只负责“读懂规则 按规则执行”而不是“自己发明规则”。结果出乎意料地稳。模型不再乱编分支规则改动从“改代码 回归测试”变成了“改 JSON 跑一遍校验”。这篇文章就是我把这套方法完整落地的过程记录包括我踩过的坑、参数怎么定、JSON 结构怎么设计、模型提示词怎么写、怎么防止它“自作聪明”。如果你也在被复杂的业务规则折磨或者你正在尝试让大模型参与业务系统但总被它的“幻觉”坑这篇内容应该能帮你省下不少试错时间。它适合有一定后端或低代码平台经验的开发者也适合产品经理和业务架构师理解“规则外置”这件事到底怎么落地。核心关键词就三个业务逻辑 JSON 化、大模型约束执行、规则引擎轻量化。2. 为什么把业务逻辑写成 JSON 是个好主意2.1 代码写规则的三个死结先说说传统方式到底卡在哪。我用“工单派单”这个场景举例规则大概是这样的紧急工单 15 分钟内必须响应普通工单 2 小时如果当前值班人手里有超过 5 个未完成工单就转给备班如果备班也不在就升级给主管如果主管在休假就丢进公共池。这些规则用代码写大概长这样def assign(ticket): if ticket.priority urgent: if ticket.assignee.load 5: if backup_online(): return backup else: if manager_on_duty(): return manager else: return public_pool else: return ticket.assignee else: # 又是十几行 ...这段代码有三个死结。第一规则和代码耦合。业务方想改“5 个”为“8 个”得找开发改代码、走发布流程。第二分支爆炸。每加一个条件组合数指数级上升测试根本覆盖不全。第三不可读。除了写代码的人没人知道这条规则到底在表达什么业务方看不懂新人接手要命。我试过用规则引擎比如 Drools 那类确实能解耦但引入了一套新的 DSL学习成本高而且规则文件本身还是“代码”业务方依然改不动。后来我想能不能用最通用的数据格式——JSON——来表达规则JSON 的好处是结构清晰、任何语言都能解析、业务方稍微培训就能看懂、大模型天生擅长处理。2.2 JSON 作为规则载体的天然优势把上面的派单规则写成 JSON大概是这样{ rule_id: assign_urgent, priority: urgent, conditions: [ { field: assignee.load, operator: , value: 5 } ], actions: [ { type: check_backup, on_fail: check_manager } ], timeout_minutes: 15 }你看这条规则表达的意思和代码完全一样但它是声明式的。它不说“怎么算”只说“是什么”。业务方看到value: 5立刻知道是“超过 5 个就转”。改规则就是改数字不需要懂编程。而且 JSON 可以被程序解析也可以被大模型理解。这就引出了下一个关键点大模型在这里扮演什么角色2.3 大模型的角色执行者而非创造者很多人用大模型做业务第一反应是“让模型自己判断”。比如把工单描述丢给模型问“该派给谁”。这种做法在简单场景下能用但一旦规则复杂模型就开始“编”它会忽略某些条件或者自己发明一条不存在的规则。我实测过同一个工单问十次模型能给出三种不同答案。问题的根源在于模型在“创造”逻辑而不是“执行”逻辑。我的做法是把角色反过来规则由人写成 JSON模型只负责“读取 JSON 按 JSON 执行”。模型不需要知道“为什么超过 5 个要转”它只需要知道“JSON 里写了超过 5 个要转那我就转”。这样模型的输出就被严格约束在 JSON 定义的范围内幻觉空间被极大压缩。打个比方以前的用法是让模型当“法官”自己判案现在的用法是让模型当“书记员”照着法条念。法条JSON是人写的书记员模型只负责准确执行。这个角色转换是整个方案能跑通的核心。2.4 方案选型为什么不用纯代码解析 JSON你可能会问既然 JSON 是结构化的为什么不让程序直接解析执行非要拉上大模型答案在于规则的复杂度和变化频率。纯代码解析适合规则固定、字段明确的场景。但现实中业务规则往往带有“模糊性”。比如“如果工单描述里提到‘客户很急’就提升优先级”。这种语义判断纯代码很难写但模型很擅长。所以我的方案是混合执行结构化部分数值比较、字段判断由程序解析 JSON 执行语义部分文本理解、意图识别由模型补充。模型输出的结果依然要符合 JSON 定义的结构不能自由发挥。这样既保留了规则的确定性又利用了模型的语义能力。3. JSON 规则结构怎么设计才不踩坑3.1 核心字段的取舍设计 JSON 结构时我踩过最大的坑是“想表达一切”。第一版我设计了二十多个字段结果业务方看不懂模型也经常填错。后来我砍到六个核心字段反而稳定了。这六个字段是rule_id规则唯一标识用于日志追踪和冲突检测。priority规则优先级数字越小越先匹配。conditions条件数组每个条件包含字段、操作符、值。actions动作数组匹配后执行什么。timeout超时时间用于 SLA 控制。fallback兜底动作所有条件不满足时执行。这六个字段覆盖了 90% 的派单场景。剩下的 10% 怎么办用extensions字段挂载自定义数据但不参与核心逻辑。这样结构清晰模型也容易理解。提示字段命名尽量用业务语言不要用技术缩写。比如用assignee.load而不是usr_ld。模型对自然语言字段名的理解准确率明显更高。3.2 条件表达式的设计陷阱条件表达式是最容易出问题的地方。我见过有人写成condition: load 5 priority urgent这就是把代码塞进了 JSON失去了声明式的意义。正确的做法是拆成结构化数组conditions: [ { field: priority, operator: , value: urgent }, { field: assignee.load, operator: , value: 5 } ]多个条件默认是“与”关系。如果需要“或”加一个logic字段logic: or, conditions: [...]这里有个细节操作符要限定枚举。我只允许、!、、、、、in、contains这八种。模型在生成或解析时只能从这八种里选不能自己发明。实测下来限定枚举后模型解析错误率从 12% 降到了 1% 以下。3.3 动作定义的幂等性要求动作定义必须满足幂等性。什么意思就是同一个动作执行多次结果应该一样。比如“转给备班”这个动作如果执行两次不能变成“转给两个备班”。我在 JSON 里用action_id来保证每个动作有唯一 ID执行前先检查是否已执行过。actions: [ { action_id: act_001, type: reassign, target: backup } ]另外动作类型也要限定枚举reassign转派、notify通知、escalate升级、close关闭、tag打标签。模型只能从这五种里选。这样即使模型“想多了”也只能在这五种动作里选不会创造出奇怪的操作。3.4 版本控制与回滚机制规则 JSON 一定要有版本号。我吃过亏业务方改了规则没记录版本出问题后想回滚发现旧版本找不到了。现在我的做法是每个 JSON 文件头部带version字段格式主版本.次版本.修订号。每次修改必须提交到 Gitcommit message 写清楚改了什么。线上运行时加载的是“当前生效版本”但保留最近五个历史版本。出问题时一键切换到历史版本不需要重新发布。这个机制看起来简单但关键时刻能救命。有一次业务方把“5 个”改成了“0 个”导致所有工单都转给备班系统差点崩。幸好有版本回滚三分钟就恢复了。4. 让大模型“照着 JSON 执行”的提示词工程4.1 系统提示词的核心约束提示词是整个方案的“紧箍咒”。我试过很多版本最后稳定下来的系统提示词大概是这样你是一个规则执行器。你的唯一任务是根据给定的 JSON 规则判断当前输入应该执行哪个动作。 约束 1. 你只能从 JSON 的 actions 数组中选择动作不得创造新动作。 2. 你只能使用 JSON 中定义的字段和操作符不得引入外部知识。 3. 如果没有任何规则匹配返回 fallback 动作。 4. 输出必须是 JSON 格式包含 matched_rule_id 和 selected_action。 5. 不要解释你的推理过程只输出结果。这五条约束里最重要的是第 1 条和第 2 条。它们把模型的“创造力”锁死了。我实测过不加这两条模型有 30% 的概率会“补充”一条它认为合理的规则。加上之后这个概率降到几乎为零。4.2 少样本示例的选取技巧光有系统提示词还不够模型需要“看例子”才能准确执行。我在提示词里放了三个少样本示例覆盖三种典型情况单条件匹配、多条件匹配、无匹配走兜底。示例的选取有讲究示例要短每个不超过 10 行 JSON否则模型会迷失在细节里。示例要覆盖边界比如条件刚好等于阈值的情况。示例的字段名要和实际规则一致不要用foo、bar这种占位符。我试过放十个示例结果模型反而变差因为它开始“模仿”示例的结构而不是理解规则。三个刚刚好。4.3 输出格式的强约束输出格式必须强约束。我要求模型输出{ matched_rule_id: assign_urgent, selected_action: { action_id: act_001, type: reassign, target: backup }, confidence: 0.95 }confidence字段是我后来加的。它让模型给自己的判断打个分。如果分数低于 0.8系统就转人工审核。这个机制帮我拦住了不少模型“犹豫”的情况。实测下来模型在规则清晰时 confidence 普遍在 0.9 以上规则模糊时会降到 0.6 左右正好可以用来做分流。注意不要让模型输出“推理过程”。我试过让它解释为什么选这条规则结果它开始编理由而且编的理由和实际规则不符。后来我直接禁止它解释只输出结果反而更准。4.4 温度参数与重试策略温度参数temperature我设成 0。规则执行不需要“创意”需要的是确定性。设成 0 后同一个输入模型输出基本稳定。但即使这样偶尔还是会有波动。我的重试策略是如果模型输出不符合 JSON 格式或者matched_rule_id不在规则列表里就重试一次并把错误信息附在提示词后面。重试两次还失败就走兜底逻辑并记录日志。这个策略听起来简单但实测能把格式错误率从 5% 降到 0.5% 以下。关键是重试时要带上错误信息模型看到“你上次输出的 rule_id 不存在”第二次就会纠正。5. 完整实操从零搭建一个 JSON 规则执行器5.1 环境准备与依赖选择我用 Python 做示例因为生态最全。核心依赖就三个fastapi提供 HTTP 接口方便和现有系统集成。pydantic做 JSON 结构的校验确保规则文件格式正确。openai或兼容的模型 SDK调用大模型。如果你不想用云模型也可以用本地部署的小模型但实测下来7B 以下的模型在规则执行任务上准确率不够建议至少 13B 以上或者直接用云端的强模型。成本方面一次规则执行大概消耗 500 到 800 token按现在的价格一千次调用不到一块钱比人工处理便宜太多。5.2 规则文件的加载与校验规则文件我放在rules/目录下每个文件一个规则集。加载时用 Pydantic 做校验from pydantic import BaseModel, Field from typing import List, Literal class Condition(BaseModel): field: str operator: Literal[, !, , , , , in, contains] value: object class Action(BaseModel): action_id: str type: Literal[reassign, notify, escalate, close, tag] target: str class Rule(BaseModel): rule_id: str priority: int conditions: List[Condition] logic: Literal[and, or] and actions: List[Action] timeout: int 0 fallback: Action class RuleSet(BaseModel): version: str rules: List[Rule]校验不通过的规则文件直接拒绝加载并报警。这一步能拦住 90% 的人为错误。我见过业务方把operator写成如果不校验模型可能“猜”成结果就错了。校验之后直接报错让人去改。5.3 调用大模型执行规则调用模型的核心代码如下import json from openai import OpenAI client OpenAI() def execute_rules(ticket: dict, ruleset: RuleSet) - dict: prompt f 规则集{ruleset.model_dump_json()} 当前工单{json.dumps(ticket)} 请根据规则集判断应该执行哪个动作。 response client.chat.completions.create( modelgpt-4o, temperature0, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: prompt} ], response_format{type: json_object} ) result json.loads(response.choices[0].message.content) return result这里有几个细节response_format设成json_object强制模型输出 JSON。temperature0保证确定性。SYSTEM_PROMPT就是前面说的那五条约束。实测下来这套组合的准确率在 98% 以上。5.4 结果校验与兜底逻辑模型输出后不能直接信。我加了三层校验格式校验输出必须是合法 JSON且包含matched_rule_id和selected_action。规则校验matched_rule_id必须在规则集的rule_id列表里。动作校验selected_action的action_id必须在对应规则的actions里。任何一层校验失败就走兜底逻辑记录日志返回fallback动作并触发人工审核。这个兜底逻辑救过我很多次。有一次模型把reassign写成了reassign_backup格式校验直接拦住没有造成线上事故。5.5 性能优化与缓存策略规则执行是高频操作每次都调模型太慢。我加了两级缓存规则集缓存规则文件加载后缓存在内存不重复读盘。结果缓存相同的工单特征部门、优先级、负载等哈希后作为 key缓存模型输出。缓存有效期 5 分钟因为负载是动态变化的。加上缓存后平均响应时间从 800ms 降到了 120ms。对于实时性要求高的场景还可以把模型调用改成异步先返回一个“处理中”状态后台算完再更新。6. 常见问题与排查技巧实录6.1 模型“自作主张”怎么办这是最常见的问题。表现是模型输出了一个规则里没有的动作或者忽略了某个条件。排查思路先看系统提示词有没有明确“只能从 actions 里选”。如果没有加上。再看少样本示例里有没有“越界”的例子。如果有删掉。最后看温度参数是不是设成了 0。如果不是改成 0。我遇到过一次模型把“转给备班”理解成了“转给所有备班”。原因是示例里有一个“转给多人”的例子模型模仿了。删掉那个示例后问题消失。6.2 规则冲突与优先级处理多条规则同时匹配时按priority数字小的优先。如果priority相同按规则在文件里的顺序。这个逻辑要写进系统提示词让模型知道怎么选。我见过有人忘了定义优先级结果模型随机选一条导致行为不稳定。提示规则设计时尽量让priority不重复。如果实在要重复在规则里加一个weight字段用浮点数区分。6.3 字段缺失与类型错误的容错工单数据里经常有字段缺失。比如assignee.load可能不存在。我的做法是在 JSON 规则里定义default值{ field: assignee.load, operator: , value: 5, default: 0 }如果字段缺失就用默认值。这样模型不会因为“找不到字段”而报错。类型错误也一样比如load是字符串5而不是数字5我在预处理阶段统一转成数字。6.4 排查速查表问题现象可能原因排查方法解决方案模型输出非 JSON提示词未约束格式检查response_format设为json_object动作不在列表里提示词未限定枚举检查系统提示词加“只能从 actions 选”规则匹配错误优先级未定义检查priority字段补上优先级字段找不到数据缺失检查输入数据加default值响应太慢无缓存检查缓存逻辑加结果缓存同一输入结果不同温度非 0检查temperature设为 0这张表是我从实际故障里总结出来的基本上覆盖了 95% 的问题。每次出问题先查表再深入排查。6.5 独家避坑技巧最后分享几个我踩坑后总结的技巧规则文件不要太大。一个文件超过 50 条规则模型准确率会下降。拆成多个文件按业务域分。定期做回归测试。我每周跑一次全量规则测试用历史工单数据验证模型输出是否和预期一致。有一次发现模型把“紧急”和“加急”搞混了及时修正了。日志要详细。每次执行记录输入、规则集版本、模型输出、校验结果。出问题时这些日志是唯一的线索。不要完全信任模型。即使准确率 99%那 1% 也可能造成大问题。兜底逻辑和人工审核通道必须保留。这套方案我跑了半年多规则改动从平均三天缩短到半小时模型乱编代码的情况基本消失。当然它不是银弹复杂语义判断还是需要人工兜底。但至少业务逻辑终于从代码里解放出来变成了业务方也能看懂、能修改的 JSON。这件事本身就值得一试。