ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

稳定大模型JSON输出的四层防线:解析、校验与重试实践

稳定大模型JSON输出的四层防线:解析、校验与重试实践 在接大模型的项目里JSON 解析失败大概率不是偶发事件而是日常事件。你可能已经见过这些现象模型输出里多了一句“好的结果如下”花括号少了一个字段名从user_name变成了username说好的status字段整个消失甚至直接在 JSON 外面包了一层代码块。每次遇到这种情况第一反应基本都是改 Prompt把“请务必输出 JSON”写得更用力一些。改了几版之后会发现作用有但根治不了。问题在于Prompt 的本质是“提高输出符合预期的概率”它没法变成一个文法和语法层面的硬约束。这篇文章想和你聊的不是某个单点技巧而是一整套稳定线上大模型 JSON 输出的方案提示词设计、容错解析、Schema 校验、字段后处理、带诊断的重试。这五件事各管一段组合起来才能把“JSON 偶发损坏”从线上事故变成可管理的小概率事件。读完你会得到三个东西一套适合直接落地的架构思路、一份可以直接改来用的 Python 示例代码、以及一份线上排查和重试策略的避坑清单。1. 你真正要解决的问题不是“偶发”而是线上稳定性先描述一个很典型的场景。你做了一个客服助手大模型需要从用户问题里抽取意图、实体、参数然后返回给后端去调用订单、售后等系统。后端拿到的是一个 JSON格式大概是{ intent: query_order, params: { order_id: 20250101001 }, confidence: 0.92 }这个 JSON 如果解析失败后端就无法路由。于是线上表现是大部分请求正常但隔一段时间就有一条请求报错。用户侧看到的是“系统繁忙”技术侧看到的是JSONDecodeError和Invalid JSON的告警。这种问题让人头疼的点有三个它不固定。不是某个 Prompt 写了就永远不犯而是概率性出现。它随模型版本变化。同一个 Prompt换了模型版本错误率可能明显变化。它会在下游放大。一个 JSON 解析失败会直接导致业务流程中断。从工程经验看更稳妥的判断是不要追求“让模型永远输出合法 JSON”这是不可靠的。真正可落地的方案是把“模型可能输出非法 JSON”当成一个默认前提在系统层面建立防护层。你不需要让大模型变得更确定你需要让你的系统在模型不确定的情况下依然稳定。这也是这篇文章的核心思路跳出“只调 Prompt”的惯性用程序化的方式兜住大模型输出的不确定性。2. 为什么大模型的 JSON 输出会“飘忽不定”先说结论大模型本身是一个“按概率生成下一个 token”的系统它没有编译器级别的约束来保证输出的 JSON 文法合法。你看到的花括号、引号、逗号都是模型根据训练数据学出来的“最像样”的结构而不是经过解析器验证过的结构。有几个因素会让这个问题更明显解码策略。高 temperature、top_p 等采样参数越高输出越随机格式漂移的概率也会增加。Prompt 扰动。系统提示词后面拼接了动态内容、few-shot 示例数量变化、上下文长度变化都会影响模型对输出格式的“注意力”。输出长度和截断。max_tokens 设置过小JSON 生成到一半被截断必然产生不完整括号。平台差异。不同大模型平台对 JSON 格式的支持程度不同有的支持强约束有的只支持弱约束有的字段名和转义规则还不一样。理解这点之后你就知道为什么“重试一次”很多时候不是好方案。因为单纯重试只是再去撞一次同样的概率没有把上一次失败的原因利用起来。真正有效的重试是“带诊断的重试”明确告诉模型“你上一次输出解析失败了失败原因是第几层出错、缺少什么字段、哪里不合法”让模型在修正信息的基础上重新生成。这个思路会贯穿后面多个章节。3. 稳定 JSON 的四层防线先定架构再写代码我建议把稳定方案拆成四层每一层解决一类问题职责单一容易测试也方便单独调优。层次解决什么问题失败时怎么办核心手段第一层提示词降低非法 JSON 出现概率进入第二层输出规范、few-shot、低温度第二层解析与修复把“接近 JSON 但不是 JSON”的内容变成可解析 JSON进入第四层重试提取代码块、括号修复、注释清理第三层校验与后处理保证业务字段完整、类型正确、值合法进入第四层重试Schema 校验、默认值补全、类型转换第四层重试对可修复错误进行有节制、有诊断的重新调用最终走降级方案错误分类、指数退避、携带失败原因一个很容易犯的错误是只做第一层和第四层也就是“使劲改 Prompt失败就重试”。这样做的结果是 Prompt 越来越长重试次数越来越高成本翻倍问题却没有真正解决。原因是第二层和第三层承担了最关键的“兜底”职责——它们不依赖模型情绪只依赖确定性代码。在展开之前我强烈建议你先把四层都装上哪怕某些层现在看起来用不上。线上项目里第一层只能覆盖 80% 的情况第二层能再兜住 15%第三层负责定义“什么叫成功”第四层才是最后的保险。缺了任何一层都会有漏洞。4. 第一层提示词设计如何让模型“尽量听话”提示词不能保证输出合法但能明显降低出错概率。设计上有几个要点4.1 在 System Prompt 里明确输出约束不要把“输出 JSON”的要求藏在最后一句。推荐的做法是在 system prompt 里反复强调以下几点“只输出一个 JSON 对象不要输出 markdown 代码块。”“不要输出任何解释、前言或后记。”“所有字段都必须包含在 JSON 中即使值为空也要输出 null。”“确保输出可以被标准 JSON 解析器解析。”一个可复用的 system prompt 模板可以是这样SYSTEM_PROMPT_TEMPLATE 你是一个结构化数据抽取助手。你的任务是根据用户输入生成 JSON 数据。 必须遵守 1. 只输出一个 JSON 对象不要输出 markdown 代码块。 2. 不要输出任何解释、前言或后记。 3. JSON 必须包含以下字段且字段名不要做任何改动 {field_descriptions} 4. 如果某字段的信息不存在使用 null不要省略该字段。 5. 确保所有字符串都使用双引号确保输出可以被标准 JSON 解析器解析。 字段说明 {field_schema_text} 示例 输入帮我查一下订单 20250101001 到哪里了 输出{{intent: query_order, params: {{order_id: 20250101001}}, confidence: 0.9}} 注意模板里的{{和}}这是因为如果用 Python 的format拼接需要转义。这里真正容易踩坑的地方是字段描述太长反而干扰模型对“只输出 JSON”的理解。实践经验是few-shot 示例的质量和数量比冗长的描述更有效。两到三个高质量示例通常会比一大段“不要做什么”的说明效果好。4.2 用 few-shot 固定结构模型会模仿示例的格式。如果你的 few-shot 示例里输出了{intent: query_order, ...}这样的结构模型大概率会照做。但示例里的字段顺序、缩进也会被模仿所以示例要保持干净、统一。4.3 对结构化任务把 temperature 调低如果任务本身不要求创造性比如抽取、分类、摘要推荐把 temperature 调成 0 或接近 0。这样能显著减少随机性虽然不能根治格式问题但会明显降低错误率。你可以在调用大模型时设置response model_call( messagesmessages, temperature0.0 )4.4 提示词层的局限性即使做了以上所有事模型仍可能在极端情况下输出非法 JSON。比如输入非常长、包含大量特殊字符、上下文里出现与 JSON 格式很像的文本都会干扰输出。所以提示词层只是降低成本永远不要把它当成唯一防线。真正的兜底在下一层。5. 第二层鲁棒解析与 JSON 修复当模型输出不合法时第一件事不是重试而是尝试“修复”。原因是即使 JSON 不合法大部分情况下模型输出的内容离合法 JSON 只差一步多了代码块标记、多了尾部逗号、括号没闭合、外层多了一段解释文字。修复的基本思路是先提取 JSON 片段再做保守的语法修复然后交给标准json.loads。# json_repair.py import json import re class JSONParseError(Exception): 模型输出无法被修复为合法 JSON 时抛出。 def extract_json_block(text: str) - str: 从模型原始输出中提取最可能包含 JSON 的片段。 这里的修复策略是保守的只去掉常见的代码块包裹和解释文字。 如果需要更激进地提取务必增加日志避免静默丢失业务信息。 text re.sub(rjson|, , text).strip() start text.find({) end text.rfind(}) if start -1 or end -1 or end start: raise JSONParseError(no_json_object_found) return text[start:end 1] def repair_and_parse(raw: str) - dict: 将模型输出修复为合法 JSON。 修复步骤 1. 提取 JSON 主体 2. 清理 // 注释和尾随逗号 3. 补齐未闭合的括号 4. 交给标准 json.loads 解析。 body extract_json_block(raw) # 清理 // 注释 body re.sub(r//[^\n]*, , body) # 清理字符串值里的换行控制字符 body re.sub(r[\x00-\x1f\x7f], , body) # 去掉尾随逗号例如 {a: 1,} body re.sub(r,\s*([}\]]), r\1, body) try: return json.loads(body) except json.JSONDecodeError as e: # 尝试补齐未闭合的括号 open_braces body.count({) close_braces body.count(}) if open_braces close_braces: body } * (open_braces - close_braces) try: return json.loads(body) except json.JSONDecodeError as final_error: raise JSONParseError(frepair_failed: {final_error})这段代码重点解决几种最常见的脏数据模型在 JSON 外层加了 json 代码块。模型在 JSON 前面输出了解释性文本。JSON 里出现了//注释。数组或对象最后一个元素后带了尾随逗号。输出被截断导致右括号缺失。真正需要谨慎的是“修复动作的边界”。如果模型输出是{name: Alice, name: Bob}重复键应该交给校验层处理而不是在解析层静默取舍。如果模型输出的{出现在字符串值内部比如{text: 这是一个{测试}}上面的find({)和rfind(})提取逻辑基本安全因为{和}会正确配对但如果字符串里包含不对称的大括号提取就会出错。所以在修复层宁可抛错进入重试也不要靠粗暴的正则强行“修复”出错误的结果。修复完成后你会得到一个 Python dict。但解析成功不代表可以放心用下一层的校验才是控制质量的关键。6. 第三层Schema 校验与字段后处理很多项目只做json.loads只要不报错就直接用。这在简单场景下没问题但在业务字段比较多的场景很容易出事。最典型的例子是模型把order_count输出成了字符串3下游代码却直接做整数运算或者模型把amount输出成了18.50前端展示时需要数值类型。JSON 标准允许3这种字符串业务不允许。这一层的目标有两个第一校验字段是否存在、类型是否正确、枚举值是否合法第二对可以自动修正的字段做后处理比如类型转换、默认值补全、日期格式化。6.1 定义一个字段级 Schema不引入复杂依赖可以用 Python 字典定义 Schema结构简单也方便团队 review。# schema.py from typing import Any, Dict, List def validate_and_normalize(data: Dict[str, Any], schema: Dict[str, Dict[str, Any]]) - Dict[str, Any]: 校验并归一化模型输出。 schema 格式示例 { intent: { type: string, required: True, enum: [query_order, cancel_order, other] }, order_id: { type: string, required: False, default: None }, count: { type: integer, required: True, default: 0 } } result: Dict[str, Any] {} warnings: List[str] [] for field, rule in schema.items(): required rule.get(required, False) # 字段缺失 if field not in data or data[field] is None: if required: if default in rule: result[field] rule[default] warnings.append(fmissing_required_field:{field}:use_default) else: raise ValueError(fmissing_required_field:{field}) else: result[field] rule.get(default) if data.get(field) is not None or field not in data: continue continue value data[field] # 类型校验和宽松转换 expected_type rule.get(type) if expected_type string and not isinstance(value, str): value str(value) warnings.append(fcoerce_to_string:{field}) elif expected_type integer: if isinstance(value, str) and value.lstrip(-).isdigit(): value int(value) warnings.append(fcoerce_to_int:{field}) if not isinstance(value, int) or isinstance(value, bool): raise ValueError(finvalid_type:{field}:expect_integer) # 枚举校验 if enum in rule and value not in rule[enum]: raise ValueError(finvalid_enum:{field}:{value}) result[field] value return { data: result, warnings: warnings, }6.2 为什么“类型转换”要谨慎3转成3通常没问题但3abc转成整数就会出错。有风险的类型转换建议做成白名单而不是无条件int(value)。上面的代码用了value.lstrip(-).isdigit()做保守判断就是这个考虑。另一个需要注意的点是不要把模型输出直接当成数据库写入或权限判断的依据。模型可能输出一个看起来合法但语义完全错误的字段值比如在枚举里输出了query_order但用户本意是取消订单。校验层只能验证“形式合法”不负责“语义正确”。语义正确性要靠业务侧二次判断这也是为什么我推荐在方案里保留warnings输出让下游能感知哪些字段被修正过。6.3 后处理补默认值、清洗字段除了类型转换常见的后处理还包括把空字符串统一成None。把日期字符串统一成YYYY-MM-DD格式。把前端和历史版本兼容的旧字段名映射到新字段名。这些操作写在校验之后的“归一化”阶段不要写进解析层。因为解析层职责是“得到合法 JSON”归一化层才是“得到符合业务要求的字段”。职责分离会让每个模块更容易测试。7. 第四层重试策略与错误分类重试是整个方案里最容易翻车的一层。很多线上事故不是模型输出导致而是重试导致重试次数太多、超时累加、成本翻倍、接口被拖垮。7.1 先把错误分成两类可重试和不可重试不是所有错误都适合重试。我强烈建议在代码里显式区分两类异常可重试错误JSON 解析失败、字段校验失败、临时限流、请求超时。不可重试错误提示词被内容策略拦截、鉴权失败、输入本身非法、模型服务明确返回业务错误码。为什么内容策略拦截不能重试因为它返回的往往不是 JSON 语法问题而是“这个请求不适合继续处理”。无论重试多少次结果大概率还是一样的。碰到这类错误直接记录日志并返回给上层比盲目重试更有意义。7.2 指数退避 抖动需要重试时不要让每一次重试都紧接着上一次。推荐指数退避策略并加入随机抖动避免多个请求在同一时刻集中重试打爆模型服务。# retry.py import random import time class RetryableError(Exception): 可重试的错误例如 JSON 解析失败、校验失败。 class NonRetryableError(Exception): 不可重试的错误例如内容策略拦截、鉴权失败。 def backoff_delay(attempt: int, base_delay: float 1.0, max_delay: float 8.0) - float: delay min(base_delay * (2 ** (attempt - 1)), max_delay) return delay random.uniform(0, delay * 0.3) def call_with_retry( func, max_attempts: int 3, base_delay: float 1.0, max_delay: float 8.0, ): last_error None for attempt in range(1, max_attempts 1): try: return func() except NonRetryableError as e: raise e except (RetryableError, TimeoutError) as e: last_error e if attempt max_attempts: break delay backoff_delay(attempt, base_delay, max_delay) time.sleep(delay) raise last_error这里有个细节真正的线上系统里重试不应该只在同步调用里做。如果请求链路很长或者模型调用本身就要 3-5 秒同步重试会让接口超时风险急剧上升。更好的做法是把这类任务丢进消息队列在消费者侧做重试。重试次数可以放在消息头里例如 Kafka 或 RabbitMQ 的 header 里存取retry_count达到上限后进入死信队列或退避队列。这和常规中间件重试的思路是一致的核心原则是“重试不能无限、不能瞬时、不能阻塞主链路”。7.3 带诊断的重试把失败原因带回下一次 Prompt不带诊断的重试是“再撞一次运气”带诊断的重试是“给模型第二次机会并告诉它哪里错了”。实践效果差别很明显。在重试时你可以构造一段补充提示RETRY_HINT_TEMPLATE ( 注意上一次输出解析失败失败原因{error}。 请重新生成确保输出是合法 JSON只输出 JSON 对象不要包含任何其他内容。 )然后把这段内容作为额外的 user message 追加到 messages 里。模型看到“解析失败”和具体原因后通常会修正输出。这个技巧很基础但也是我认为投入产出比最高的单点优化。重试次数建议控制在 2-3 次。再多会导致单次请求耗时过长而且边际收益很低。与其无限重试不如让系统在重试耗尽后走“降级方案”返回默认 JSON、使用上一次成功的缓存、或转人工处理。8. 不要忽略平台原生能力JSON Mode 与函数调用前面讲的是纯应用层解决方案。这里必须补充一个角度很多大模型平台已经提供了结构化输出能力比如“JSON Mode”、函数调用function calling、结构化输出。这些能力能在模型层面把输出格式的约束做得更严格。这些能力的价值和局限分别是优势是语法层面更可靠。平台会在解码过程中约束输出很大概率避免缺括号、多解释文字这类基础问题。局限是它不负责业务校验。JSON Mode 只保证输出是一个 JSON 对象不保证字段名一定对、类型一定对、枚举值一定合法。函数调用也类似平台能帮你把参数组织成 JSON但参数值是否合理仍然要业务自己判断。所以更推荐的组合方式是平台能力 应用层解析校验。先用 JSON Mode 或函数调用降低基础格式错误率再在应用层做 Schema 校验和后处理。不要二选一。需要提醒的是不同平台对结构化输出的支持程度不同有的要求messages里必须有示例有的对字段名有长度和字符限制有的平台对嵌套结构有深度限制。接入前先看官方文档并在环境准备阶段用一个最小例子验证。不要因为某个平台支持 JSON Mode 就默认所有模型都能稳定输出。9. 完整工程示例把四层逻辑封装成一个 Pipeline现在把前面四层串起来做一个最小可用的封装。下面这个类模拟了一个稳定的 JSON 客户端实际使用时只需要替换model_call为大模型官方 SDK 的调用即可。# stable_json_client.py from typing import Any, Callable, Dict, Optional from json_repair import JSONParseError, repair_and_parse from schema import validate_and_normalize from retry import RetryableError, NonRetryableError, call_with_retry class StableJsonClient: 一个把四层防线集成在一起的客户端。 model_call 是一个可调用对象负责调用大模型并返回原始字符串输出。 你需要根据自己使用的大模型 SDK 实现这个函数。 统一约定model_call(messages: list, temperature: float) - str def __init__( self, model_call: Callable[..., str], schema: Dict[str, Dict[str, Any]], max_attempts: int 3, ): self.model_call model_call self.schema schema self.max_attempts max_attempts def _build_messages( self, user_input: str, retry_hint: Optional[str] None, ) - list: field_schema_text \n.join( [f- {name}: {rule} for name, rule in self.schema.items()] ) system_prompt ( 你是一个结构化数据抽取助手。只输出一个 JSON 对象。 不要输出 markdown 代码块不要输出任何解释。 JSON 必须包含以下字段\n f{field_schema_text}\n 如果字段值不存在使用 null不要省略字段。 ) messages [ {role: system, content: system_prompt}, {role: user, content: user_input}, ] if retry_hint: messages.append( {role: user, content: retry_hint} ) return messages def _call_once(self, user_input: str, retry_hint: Optional[str]) - dict: messages self._build_messages(user_input, retry_hint) # 这里是唯一需要对接真实 SDK 的地方 raw_output self.model_call( messagesmessages, temperature0.0, ) # 第 2 层解析与修复 candidate repair_and_parse(raw_output) # 第 3 层校验与归一化 checked validate_and_normalize(candidate, self.schema) return checked[data] def generate(self, user_input: str) - dict: retry_hint None def attempt(): nonlocal retry_hint try: return self._call_once(user_input, retry_hint) except JSONParseError as e: retry_hint f上一次输出解析失败原因{e}。请重新生成合法 JSON。 raise RetryableError(str(e)) except ValueError as e: retry_hint f上一次输出校验失败原因{e}。请重新生成包含完整字段的 JSON。 raise RetryableError(str(e)) try: return call_with_retry(attempt, max_attemptsself.max_attempts) except NonRetryableError as e: # 这里建议记录结构化日志并返回给上层做降级处理 raise这个 Pipeline 的关键点在于重试提示retry_hint是在循环外维护的。每次失败都会把上一次的具体错误原因拼接到下一次的 messages 里这就是“带诊断的重试”。运行一个最小验证可以写一段简单的测试# demo.py def fake_model_call(messages: list, temperature: float) - str: 模拟一个大模型输出第一次返回带解释文本的脏 JSON第二次返回合法 JSON。 user_input messages[-1][content] if 解析失败 in user_input: return {intent: query_order, order_id: 20250101001, count: 3} return ( 这是一个查询结果\n json\n {intent: query_order, order_id: 20250101001, count: 3,}\n \n ) schema { intent: {type: string, required: True, enum: [query_order, cancel_order, other]}, order_id: {type: string, required: True}, count: {type: integer, required: True, default: 0}, } client StableJsonClient(model_callfake_model_call, schemaschema) result client.generate(帮我查一下订单 20250101001 到哪里了) print(result)预期输出{intent: query_order, order_id: 20250101001, count: 3}你可以观察到几个细节第一次调用返回了带 json 代码块的脏 JSON被repair_and_parse成功修复。同时count是字符串3被第三层转换成了整数3。如果第一次返回的内容严重到无法修复第二次 Prompt 会因为携带了解析失败原因而主动修正。这段代码的核心思路就是让你把对模型输出的信任从“盲信原始字符串”转成“层层验证后的数据”。10. 常见问题与排查思路线上接入这套方案后你大概率会碰到下面这些问题。这里整理了一份排查表按“现象 - 原因 - 解决方案”的结构记录。问题现象可能原因排查方式解决方案输出被内容策略拦截输入或 Prompt 触发了平台安全策略检查模型返回的原始错误码和错误信息这种错误不可重试记录日志并返回上游检查 Prompt 用词每次重试都解析失败重试没有携带失败原因模型不知道要修正什么查看重试请求的 messages 里是否包含失败诊断信息把解析失败信息拼接到重试 Prompt 中字段总是缺失Prompt 中字段声明不够明确或 few-shot 没覆盖该字段对比失败样本和 Prompt 模板把字段声明为 required并在 few-shot 中展示所有字段解析层把字段名改坏了修复逻辑过于激进比如正则替换了字符串内部内容记录修复前后的原文和修复结果人工抽检修复策略保持保守只处理确定的语法问题并发高峰时重试导致调用成本翻倍重试策略没有设置上限或所有请求同步重试查看重试次数分布和限流指标限制最大重试次数引入消息队列异步重试模型升级后输出格式突变新模型对 Prompt 的理解或生成习惯不同建立回归测试集在模型切换前跑一遍使用固定版本的模型或对 Prompt 做版本回归max_tokens 太小输出被截断生成长度超过上限JSON 不完整检查输出是否包含截断标记或长度统计调大 max_tokens并把截断检测纳入校验层需要特别强调“输出被内容策略拦截”这一类问题。如果你在重试循环里遇到它应该直接归类为不可重试错误。从工程经验看很多线上成本浪费和超时问题都源于团队没有区分可重试错误和不可重试错误把策略拦截、鉴权失败这类错误也拿去重试了。11. 最佳实践与工程建议最后补充几条在真实项目里验证过的工程建议尤其适合你已经准备把这套方案落地到生产环境时参考。11.1 为 Prompt 建立版本并纳入回归测试Prompt 不是一次性写好的。你可能会微调 few-shot、增加字段说明这些改动都可能导致模型输出行为变化。建议给每个 Prompt 模板加version标识在一批固定测试用例上做回归。测试用例不要只覆盖正常输入还要覆盖空值、超长文本、特殊字符。只有回归通过才能上线新 Prompt。11.2 记录 raw_output 和修复日志遇到解析失败时不要只记录“失败”。建议把模型原始输出、修复动作、warnings、重试次数、耗时都记录下来。这样当线上出现问题时你才能判断是模型问题、Prompt 问题还是修复逻辑问题。没有原始输出排查会非常被动。import logging logger logging.getLogger(stable_json_client) logger.info( model_output_raw%s parse_result%s warnings%s attempt%s, raw_output, result, warnings, attempt, )11.3 不要把模型输出直接用于敏感操作这一点再强调都不为过。模型输出的 JSON 即使通过了解析和校验也只代表“形式合法”不代表“语义安全”。如果这个 JSON 要用于数据库写操作、权限判断、前端动态渲染必须在业务侧再做一次白名单校验。尤其是涉及命令执行、代码生成、SQL 拼接的场景必须把模型输出当作不可信输入处理。11.4 先做兼容再收紧校验如果你接手的是一个历史项目之前没有这套校验逻辑不要第一天就把所有字段都设为 required。更稳妥的做法是先记录缺失情况运行一段时间的观测确认哪些字段真的 100% 必填再逐步收紧校验规则。否则线上会出现大量新报错反而影响稳定性。11.5 给调用链设置超时和熔断大模型调用的延迟波动很大高峰期可能从 2 秒涨到 10 秒。稳定方案里除了重试还要有超时控制和熔断机制。比如单次调用超时 8 秒连续失败 5 次后熔断 30 秒。熔断期间直接走降级方案不再继续打模型接口。11.6 落地优先级如果你准备在一周内落地这套方案建议按这个顺序推进先加解析与修复层这是成本最低、收益最明显的一步。再定义核心字段 Schema增加校验与默认值。然后重写重试逻辑加入错误分类和带诊断的重试。最后再考虑平台 JSON Mode 或函数调用作为格式问题收敛后的额外优化。反过来做最容易放弃一上来就调 Prompt、接入 JSON Mode语法问题减少了但字段缺失和类型错误仍然在依然不稳定。先把确定性代码兜底做扎实再依赖模型能力提升上限才是更稳妥的路径。
返回列表