☰
大模型生成业务代码不可靠?用JSON中间表示实现可验证的逻辑描述
2026/10/19 12:56:20 网站建设 项目流程

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,但如果订单里有促销商品,折扣减半。"

如果让大模型直接写代码,它可能生成这样的Python:

def 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 Schema

3.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 obj

5.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和conditions,actions里的复杂参数单独用一个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里记录修改原因和修改人。这样当规则执行出现问题时,你可以快速定位到是哪次修改引入的。这个习惯看起来简单,但在排查线上问题时能省下大量时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询