大模型生成业务代码不可靠?用JSON中间表示实现可验证的逻辑描述 1. 从代码生成到逻辑描述的范式转移大模型写代码这件事过去两年我踩过的坑比写过的功能还多。你让它生成一个订单状态流转的模块它给你返回一段看起来语法完全正确、变量命名也像模像样的代码但一跑起来就发现状态A到状态B的转换条件写反了异常分支漏了三个边界值处理全靠猜。更让人头疼的是同一个需求你换个问法再问一遍它生成的代码逻辑又变了——上次是先校验库存再扣款这次变成先扣款再校验库存。这种不确定性在真实业务系统里是致命的。后来我换了个思路不让大模型直接写业务代码而是让它把业务逻辑翻译成一份结构化的JSON描述再由一个稳定的执行引擎去消费这份JSON。这个转变听起来简单但实际效果差异巨大。大模型在生成JSON时的准确率比直接生成业务代码高出不止一个量级。原因也不复杂——JSON的schema是强约束的字段名、类型、嵌套结构都有明确边界模型只需要做填空而不是创作而业务代码里充斥着命名风格、异常处理策略、框架API选择等大量自由度每一个自由度都是一个可能出错的维度。这个思路的核心价值在于把不可验证的代码生成变成了可校验的逻辑描述。JSON可以被schema验证可以被单元测试覆盖可以被人工review甚至可以被非技术人员读懂。而大模型生成的代码你只能靠跑测试来发现问题很多时候测试还没覆盖到那些隐蔽的逻辑错误。适合谁来参考这套方法我认为有三类人收益最大一是正在做低代码/无代码平台的技术负责人你们本来就在做配置驱动的事情JSON化业务逻辑是自然延伸二是需要频繁处理业务规则变更的后端团队比如电商促销、审批流、风控策略这些场景三是想在大模型辅助开发上找到一条更可控路径的独立开发者你们没有大团队做代码review更需要这种生成即可验证的方案。2. 为什么JSON比代码更适合大模型生成2.1 大模型在代码生成上的三个结构性缺陷先把这个问题的根因说透。大模型生成业务代码时面临的不是会不会写的问题而是约束太少的问题。第一个缺陷是命名空间的无限性。你让模型写一个检查用户是否有权限访问资源的函数它可能叫checkPermission、hasAccess、verifyUserAuth、canAccessResource……这些命名在语义上都对但当你需要跨模块调用时命名不一致就会导致集成失败。JSON不一样字段名是你预先定义好的模型只能从你的schema里选不存在自由发挥的空间。第二个缺陷是控制流的隐式假设。业务代码里大量的if-else嵌套、try-catch包裹、循环中断条件这些控制流的正确性依赖于模型对业务语义的完整理解。但模型看到的只是你的自然语言描述它不知道这个订单在什么情况下应该被标记为异常——除非你写得极其详细。而JSON描述可以把控制流显式化每个条件是一个独立的字段每个分支是一个独立的节点模型只需要把条件填对不需要自己构造控制流结构。第三个缺陷是副作用的不可预测性。代码里可能包含日志打印、数据库写入、外部API调用等副作用模型生成这些代码时往往顺手加了一些你没要求的东西或者漏掉了你隐含期望的东西。JSON描述天然是声明式的它只描述是什么不描述怎么做副作用由执行引擎统一处理模型不需要关心。2.2 JSON作为中间表示的天然优势把JSON当作业务逻辑的中间表示Intermediate Representation这个思路其实借鉴了编译器的设计哲学。编译器不会让前端直接生成机器码而是先生成一种中间表示经过优化和验证后再生成目标代码。我们这里做的事情是一样的大模型是前端负责把自然语言需求翻译成JSON中间表示执行引擎是后端负责把JSON翻译成实际可执行的业务操作。这种架构带来的好处是多方面的。可验证性是最直接的——JSON可以被JSON Schema验证字段类型、必填项、枚举值范围都能在生成后立即检查不需要等到运行时才发现问题。可测试性也很关键——你可以针对JSON描述写单元测试验证特定输入下生成的JSON是否符合预期这比测试生成的代码要稳定得多因为JSON的结构是确定的。可替换性则是长期价值——如果哪天换了一个更强的大模型或者换了一种执行引擎只要JSON schema不变整个系统就不需要改动。还有一个容易被忽略的好处JSON描述可以作为文档。业务人员看不懂代码但能看懂JSON里的字段和值。当产品经理问这个促销规则到底是怎么算的你直接把JSON贴给他他就能理解。这减少了大量沟通成本。2.3 一个直观的对比订单折扣逻辑为了让你有更具体的感受我拿一个真实的场景来对比。假设需求是VIP用户订单满100减20普通用户满100减10但如果订单里有促销商品折扣减半。如果让大模型直接写代码它可能生成这样的Pythondef calculate_discount(user_type, order_amount, has_promo_item): if user_type VIP: base_discount 20 if order_amount 100 else 0 else: base_discount 10 if order_amount 100 else 0 if has_promo_item: base_discount base_discount / 2 return base_discount这段代码看起来没问题但隐藏着几个坑order_amount是整数还是浮点数has_promo_item是布尔值还是字符串如果user_type是vip小写怎么办折扣减半后是保留小数还是取整这些细节模型不会主动处理你需要反复追问才能补全。如果改成JSON描述结构是这样的{ rule_id: order_discount_v1, conditions: [ { field: user_type, operator: equals, value: VIP, discount: 20 }, { field: user_type, operator: equals, value: NORMAL, discount: 10 } ], threshold: { field: order_amount, operator: greater_or_equal, value: 100 }, modifier: { condition: { field: has_promo_item, operator: equals, value: true }, action: halve } }这份JSON里每个字段的类型和取值都是明确的执行引擎可以严格按schema解析。模型生成这份JSON时只需要从预定义的operator列表里选不需要自己发明比较逻辑。而且这份JSON可以直接被产品经理看懂他如果觉得折扣减半应该改成打八折直接改action字段就行不需要改代码。3. 设计一份能扛住业务变化的JSON Schema3.1 从最小可用开始而不是从大而全开始我见过不少团队在落地这个方案时第一版schema就设计了几十个字段试图覆盖所有可能的业务场景。结果就是模型根本记不住这么多字段生成时频繁漏填或填错最后项目不了了之。我的建议是第一版schema只覆盖你当前最核心的3-5个业务规则字段数控制在10个以内。比如你做的是审批流第一版只需要支持审批人角色、审批条件、通过后的动作这三个核心概念。等这套跑通了再逐步扩展。这里有个经验数据当schema字段数超过15个时大模型生成JSON的准确率会明显下降。所以如果你的业务确实复杂更好的做法是分层设计——顶层schema只描述流程骨架每个节点引用一个子schema子schema各自独立定义。这样模型每次只需要关注一个层级的字段认知负担小很多。3.2 字段设计的三个原则原则一用枚举代替自由文本。任何有确定取值范围的字段都用枚举类型。比如操作符字段不要用字符串让模型自由填而是定义[equals, greater_than, less_than, contains, in]这样的枚举。模型从枚举里选准确率接近100%如果让它自由填它可能给你返回is_equal_to、、equal等各种变体。原则二用嵌套结构表达层级关系。业务逻辑往往有层级比如如果A成立则检查B如果B也成立则执行C。这种层级关系用嵌套的JSON对象表达比用扁平化的字段加前缀要清晰得多。模型在处理嵌套结构时也能更好地理解字段之间的从属关系。原则三每个字段都要有明确的语义注释。JSON Schema支持description字段一定要用上。比如discount: {type: number, description: 折扣金额单位为元必须为非负数}。这个注释不仅是给模型看的也是给后续维护的人看的。实测下来带description的schema模型生成准确率能提升20%以上。3.3 一个可扩展的schema模板下面这份schema是我在实际项目中反复迭代后的版本你可以直接拿去改{ $schema: http://json-schema.org/draft-07/schema#, type: object, required: [rule_id, trigger, conditions, actions], properties: { rule_id: { type: string, description: 规则唯一标识格式为业务域_规则名_版本号 }, trigger: { type: object, required: [event], properties: { event: { type: string, enum: [order_created, payment_received, user_registered, approval_submitted], description: 触发此规则的事件类型 } } }, conditions: { type: array, description: 条件列表所有条件必须同时满足才执行actions, items: { type: object, required: [field, operator, value], properties: { field: { type: string, description: 要检查的字段路径支持点号分隔的嵌套路径 }, operator: { type: string, enum: [equals, not_equals, greater_than, less_than, greater_or_equal, less_or_equal, contains, in, not_in], description: 比较操作符 }, value: { description: 比较值类型应与field的实际类型匹配 } } } }, actions: { type: array, description: 条件满足后执行的动作列表按顺序执行, items: { type: object, required: [type], properties: { type: { type: string, enum: [set_field, send_notification, call_webhook, create_record, update_status], description: 动作类型 }, params: { type: object, description: 动作参数根据type不同而不同 } } } } } }这份schema的设计逻辑是trigger定义什么时候触发conditions定义满足什么条件actions定义做什么。这三个部分覆盖了绝大多数业务规则的核心要素。如果你需要支持否则执行另一套动作可以在actions里加一个else_actions字段或者用多个规则组合来表达。4. 让大模型稳定输出JSON的提示词工程4.1 提示词的结构化设计很多人写提示词就是一段自然语言描述然后祈祷模型能理解。这种方式在生成JSON时尤其不可靠因为JSON对格式的要求极其严格——少一个引号、多一个逗号整个解析就失败了。我的做法是把提示词也结构化分成四个固定部分第一部分角色定义。明确告诉模型它是什么角色比如你是一个业务规则翻译器负责把自然语言描述的业务需求转换成符合指定schema的JSON。第二部分schema说明。把完整的JSON Schema贴进去包括所有字段的description。不要嫌长这是模型生成准确JSON的基础。第三部分转换规则。用列表形式列出转换时的注意事项比如如果需求中没有明确指定操作符默认使用equals、如果需求中提到了多个条件全部放入conditions数组、actions数组的顺序必须与需求中描述的执行顺序一致。第四部分示例。给2-3个完整的输入输出示例覆盖不同的业务场景。示例是最好的老师模型会模仿示例的格式和风格。4.2 处理模型自由发挥的三种策略即使有了结构化提示词模型有时还是会自由发挥——加一些schema里没有的字段或者把枚举值写错。我总结了三种应对策略策略一在提示词里明确禁止。直接写不要添加schema中未定义的字段不要修改枚举值的拼写。这句话看起来简单但实测能减少30%左右的格式错误。策略二生成后立即校验。用JSON Schema验证库比如Python的jsonschema对模型输出做校验如果校验失败把错误信息连同原始输出一起返回给模型让它重新生成。这个生成-校验-重试的循环通常重试1-2次就能得到合格结果。策略三用few-shot示例覆盖边界情况。如果你发现模型总是在某个特定场景下出错比如条件为空时怎么处理就在示例里专门加一个空条件的例子展示正确的输出格式。4.3 一个完整的提示词模板下面是我在实际项目中使用的提示词模板你可以直接复制使用你是一个业务规则翻译器。你的任务是将自然语言描述的业务需求转换为符合以下JSON Schema的JSON对象。 ## JSON Schema [在此处粘贴完整的schema] ## 转换规则 1. 只输出JSON不要输出任何解释性文字 2. 不要添加schema中未定义的字段 3. 枚举值必须严格从schema定义的列表中选择 4. 如果需求中未明确指定某个字段的值使用该字段的默认值如果有定义否则省略该字段 5. conditions数组中的条件按需求中出现的顺序排列 6. actions数组中的动作按需求中描述的执行顺序排列 ## 示例 输入当用户下单金额超过500元时给用户发送一条通知并将订单标记为VIP订单 输出{rule_id:order_vip_001,trigger:{event:order_created},conditions:[{field:order_amount,operator:greater_than,value:500}],actions:[{type:send_notification,params:{message:您的订单已升级为VIP订单}},{type:set_field,params:{field:order_type,value:VIP}}]} 输入当用户注册后如果用户来源是广告且注册时间在活动期间内给用户发放10元优惠券 输出{rule_id:user_coupon_001,trigger:{event:user_registered},conditions:[{field:source,operator:equals,value:advertisement},{field:register_time,operator:in,value:[2024-01-01,2024-01-31]}],actions:[{type:create_record,params:{record_type:coupon,amount:10}}]} ## 当前需求 [在此处粘贴你的自然语言需求]这个模板的关键在于示例覆盖了不同的动作类型和条件组合模型通过模仿示例就能学会如何组织JSON结构。实测下来使用这个模板后模型首次生成合格JSON的比例从不到50%提升到了85%以上。5. 执行引擎把JSON变成真实业务操作5.1 执行引擎的核心职责JSON描述本身只是一份声明它不会自己执行。你需要一个执行引擎来消费这份JSON把它翻译成实际的业务操作。执行引擎的核心职责有三个解析与校验读取JSON验证它是否符合schema提取出trigger、conditions、actions三个部分。这一步是纯技术性的不涉及业务逻辑。条件求值根据conditions数组中的每个条件从当前业务上下文中取出对应的字段值与条件中的value进行比较。所有条件都满足时才继续执行actions。动作执行按顺序执行actions数组中的每个动作。每个动作类型对应一个执行器executor比如set_field对应修改字段值的执行器send_notification对应发送通知的执行器。5.2 条件求值的实现细节条件求值看起来简单但有几个容易踩的坑。坑一类型不匹配。JSON里的value可能是字符串、数字、布尔值而业务上下文里的字段值可能是另一种类型。比如JSON里写value: 100但上下文里的order_amount是字符串100。这时候直接比较会失败。我的做法是在求值前做一次类型归一化——如果字段值是字符串但看起来像数字尝试转成数字再比较。坑二嵌套路径取值。conditions里的field可能是一个嵌套路径比如user.profile.level。你需要一个路径解析器来从上下文中取出这个值。Python里可以用functools.reduce配合operator.getitem来实现JavaScript里可以用lodash.get。坑三空值处理。如果字段在上下文中不存在应该视为条件不满足而不是抛异常。这个策略需要在执行引擎里明确实现否则一个缺失字段就会导致整个规则执行失败。下面是一个简化的Python实现展示了条件求值的核心逻辑def evaluate_condition(condition, context): field_path condition[field] operator condition[operator] expected condition[value] actual get_nested_value(context, field_path) if actual is None: return False actual normalize_type(actual, expected) if operator equals: return actual expected elif operator greater_than: return actual expected elif operator less_than: return actual expected elif operator contains: return expected in actual elif operator in: return actual in expected # ... 其他操作符 return False def get_nested_value(obj, path): keys path.split(.) for key in keys: if isinstance(obj, dict) and key in obj: obj obj[key] else: return None return obj5.3 动作执行器的注册机制动作执行器应该设计成可注册的这样新增动作类型时不需要修改执行引擎的核心代码。我的做法是维护一个字典key是动作类型value是对应的执行函数ACTION_EXECUTORS {} def register_action(action_type): def decorator(func): ACTION_EXECUTORS[action_type] func return func return decorator register_action(set_field) def execute_set_field(params, context): field params[field] value params[value] set_nested_value(context, field, value) return {status: success, field: field, value: value} register_action(send_notification) def execute_send_notification(params, context): message params[message] # 实际发送通知的逻辑 return {status: success, message: message}这种注册机制的好处是新增动作类型只需要写一个函数并加上装饰器不需要改动执行引擎的任何其他代码。而且每个执行器可以独立测试互不影响。6. 实测中遇到的五个典型问题与解法6.1 模型生成的JSON字段顺序不稳定JSON规范里字段顺序是不重要的但有些执行引擎的实现会依赖字段顺序比如按顺序执行actions。如果你发现模型生成的actions数组顺序不对有两个解法一是在提示词里强调actions数组必须按执行顺序排列二是在执行引擎里不依赖数组顺序而是给每个action加一个order字段执行时按order排序。我倾向于第二种方案因为它更健壮。模型有时候会忘记提示词里的顺序要求但如果你在schema里定义了order字段模型生成时就会自然地填上顺序号。6.2 条件之间的逻辑关系表达不清我的schema里conditions数组默认是AND关系——所有条件都满足才执行。但实际业务中经常有OR的需求比如用户是VIP或者订单金额超过500。这时候需要在schema里加一个logic字段取值and或or执行引擎根据这个字段决定如何组合条件。更复杂的场景可能需要嵌套的逻辑表达式比如(A AND B) OR (C AND D)。这种场景我建议不要试图用单个JSON表达而是拆成多个规则用规则之间的优先级和互斥关系来处理。单个JSON描述的逻辑越简单模型生成准确率越高执行引擎也越容易维护。6.3 数值精度问题业务逻辑里经常涉及金额计算而JSON里的数字是浮点数直接做加减乘除会有精度问题。比如0.1 0.2在浮点数里不等于0.3。我的做法是在schema里明确金额字段的单位是分整数所有金额计算都用整数进行最后展示时再除以100转换成元。这个策略需要在schema的description里写清楚否则模型可能生成小数的金额值。6.4 模型对默认值的理解偏差如果你在schema里定义了默认值比如operator: {default: equals}模型有时会显式地填上默认值有时又会省略。这本身不是问题但如果你在执行引擎里依赖字段存在性来判断就会出错。我的做法是执行引擎在解析JSON后先用schema的默认值填充缺失字段然后再进行后续处理。这样无论模型是否显式填写默认值执行引擎看到的都是完整的JSON。6.5 复杂嵌套结构的生成质量下降当JSON嵌套层级超过3层时模型生成的质量会明显下降——经常出现括号不匹配、字段放错层级的问题。我的应对策略是扁平化设计尽量把嵌套层级控制在2层以内如果业务逻辑确实需要更深的层级就拆分成多个JSON文件用$ref引用。比如主规则JSON里只放trigger和conditionsactions里的复杂参数单独用一个JSON文件描述主规则里用params_ref: action_params_001.json来引用。7. 这套方案适合什么场景不适合什么场景7.1 最适合的三类场景规则频繁变更的场景。比如电商促销规则、风控策略、审批流程。这些场景的特点是业务规则经常调整但调整的往往是参数值满减金额、审批人、风控阈值而不是逻辑结构。用JSON描述后调整规则只需要改JSON里的值不需要改代码也不需要重新部署。需要非技术人员参与规则维护的场景。产品经理、运营人员看不懂代码但能看懂JSON。你可以做一个简单的JSON编辑器界面让他们直接修改规则改完立即生效。这比提需求-排期-开发-测试-上线的流程快得多。多租户SaaS场景。不同租户的业务规则可能不同但底层逻辑结构相似。用JSON描述后每个租户可以有自己的规则JSON执行引擎统一处理。新增租户时只需要配置JSON不需要为每个租户写一套代码。7.2 不太适合的场景计算密集型逻辑。比如复杂的报表统计、大规模数据聚合。这些场景的性能瓶颈在计算本身JSON解析和执行引擎的抽象层会带来额外开销。直接用代码实现更合适。需要复杂状态机的场景。JSON描述擅长表达条件-动作这种无状态或简单状态的逻辑但对于需要维护复杂状态流转的场景比如订单的完整生命周期JSON描述会变得非常臃肿。这种场景更适合用专门的状态机框架。对延迟极度敏感的场景。JSON解析和条件求值需要时间虽然单次执行通常在毫秒级但如果你需要每秒处理数万次规则匹配这个开销就不能忽略了。这种场景可以考虑把JSON预编译成更高效的中间形式或者直接用代码实现核心规则。7.3 一个决策参考表场景特征适合JSON方案适合直接写代码规则变更频率高每周多次低每月一次以内规则维护者包含非技术人员纯技术人员逻辑复杂度条件-动作型嵌套不超过3层复杂状态机、循环、递归性能要求单次执行毫秒级可接受微秒级延迟要求多租户需求每个租户规则不同所有租户规则相同可验证性要求需要人工review规则只依赖自动化测试这张表不是绝对的但可以帮你快速判断。我的经验是如果你的场景在适合JSON方案这一列有3个以上的勾就值得尝试这套方案。8. 从JSON到业务逻辑即数据的延伸思考这套方案跑通之后我发现它的价值远不止让大模型不乱编代码。更深层的价值在于它把业务逻辑从代码里剥离出来变成了可管理、可版本化、可分析的数据。一旦业务逻辑变成了JSON你就可以做很多以前很难做的事情。比如规则冲突检测——写一个分析器扫描所有规则JSON找出条件重叠但动作矛盾的规则对。比如规则影响分析——当你要修改某个字段的定义时可以快速找出所有引用了这个字段的规则。比如规则执行追踪——每次规则执行时记录完整的输入上下文和输出动作事后可以回放分析。这些能力在传统的代码里写死业务逻辑的模式下实现成本极高。但在JSON模式下它们只是对数据的查询和分析实现成本低得多。我现在的一个习惯是每次新增业务规则时先写JSON再写执行器的扩展如果需要的话。写JSON的过程强迫我把业务逻辑想清楚——条件是什么、动作是什么、边界情况怎么处理。很多时候写着写着就发现需求本身有歧义这时候去找产品经理确认比写完代码再返工要高效得多。还有一个意外的收获JSON描述可以作为测试用例的生成源。你可以写一个测试生成器读取规则JSON自动生成覆盖所有条件和动作的测试用例。这比手工写测试用例要全面得多也快得多。最后分享一个我在实际项目中总结的小技巧给每个规则JSON加一个version字段和changelog数组。每次修改规则时递增version并在changelog里记录修改原因和修改人。这样当规则执行出现问题时你可以快速定位到是哪次修改引入的。这个习惯看起来简单但在排查线上问题时能省下大量时间。