稳定输出JSON的关键在于把结构契约、示例约束、采样参数与校验重试组合成可执行流程,减少模型自由发挥带来的格式漂移。 在把DeepSeek接入工单归类、合同抽取或数据清洗流水线时,JSON不是装饰性格式,而是下游程序能否解析的硬边界。模型偶尔多写一句解释、把数字包进引号,都会让解析器抛错。与其反复调整一句提示词,不如把输出稳定性拆成可管理的工程环节,让模型在明确的角色、结构和验证反馈中工作。
1. 用系统角色与输出契约锁定结构
系统消息应当先把模型身份收窄为“JSON生成器”,明确只输出一个可解析的JSON对象,禁止Markdown代码围栏、注释、前后解释和多余寒暄。这个约束不要只写在用户消息里,因为用户消息可能包含待处理文本中的指令性内容,系统层需要承担不可变契约的角色。提示中要直接出现json这个关键词,并说明输出必须能被标准JSON解析器读取,字段名使用英文或约定好的固定键名,字符串统一使用双引号,禁止尾逗号和单引号。对于支持JSON Output的DeepSeek接口版本,应开启response_format为json_object,让接口层再提供一道格式约束,而不是完全依赖自然语言提醒。
契约本身要写到字段级。以发票抽取为例,可以约定invoice_no为字符串、amount为数字且不带货币符号和千分位、date为YYYY-MM-DD格式、tax_rate为0到1之间的小数、items为对象数组且每项包含name、qty、unit_price三个字段。缺值策略必须提前定义,例如信息不存在时使用null,不允许省略字段,也不允许用“未知”“暂无”填充。字段类型、必填项、枚举值和格式规则写得越具体,模型在生成时越少做自由猜测。系统消息中还应避免“尽量”“最好”“大概”这类模糊词,改用“必须”“只能”“如果缺失则”这类可执行表述。
一个常见失败案例是模型在JSON前后加上“以下是提取结果:”这样的说明,导致json.loads直接报错。把系统消息改为只输出JSON对象,并在用户消息末尾再次强调“不要输出代码块标记”,同时开启JSON Output,通常能显著降低这类错误。契约还要与下游解析器保持一致,如果程序只接受对象而不接受数组,就不要在提示中允许模型根据条数选择返回数组。系统角色、字段契约和接口参数三者对齐后,JSON稳定性才有可复现的基础。
2. 以示例驱动约束字段与类型
抽象规则告诉模型“金额是数字”,示例则告诉模型“1299.00不带引号、不带货币符号”。在提示中加入一到两个高质量正例,比堆叠形容词更有效。示例应覆盖真实边界,例如空数组、null字段、中文商品名、小数金额和标准日期。抽取采购订单时,示例输出只保留order_id、supplier、total_amount、currency、items五个字段,items中每项含sku、qty、unit_price,qty为整数,unit_price为两位小数数字而非字符串。示例必须与schema完全一致,不能出现规则说必填而示例省略字段的情况。
示例的数量不宜过多,两到三个足够锚定格式,过多会挤占上下文并增加前后矛盾的概率。示例应放在固定位置,用模板变量替换待处理输入,避免把上一次输出的错误样本继续留在提示中。若任务包含多种输入形态,可以分别给出简短示例,但要保证字段集合和类型一致。示例中的字符串要使用双引号,数组和对象要正确闭合,不要用注释解释字段含义,字段含义应放在系统消息的契约说明里。模型对示例中的标点和类型非常敏感,示例里出现“¥1,299.00”这样的字符串,模型就更容易把金额输出成带格式的文本。
在合同抽取任务中,曾出现模型把tax_rate输出为“13%”而不是0.13的情况。检查发现示例中只写了“税率按合同”,没有给出数字形态。补上一个示例,把tax_rate写成0.13,并在契约中说明百分数必须转换为小数,类型漂移明显减少。示例还要避免诱导模型输出推理过程,如果示例中包含“分析:”这样的前缀,模型很可能在正式输出中模仿。示例的目标是让模型看到合法JSON长什么样,而不是教它如何解释任务。
3. 通过参数与提示分层降低随机性
采样参数要服务于确定性输出。结构化抽取任务通常把temperature设到0到0.3之间,top_p保持在较保守的范围,减少模型在字段名、标点和类型上的随机选择。max_tokens需要按预期JSON长度留出余量,过小会导致对象未闭合,解析时末尾缺少右花括号;过大则增加延迟和截断前的无效生成。对于包含多个数组项的抽取任务,可以在契约中限制items最多返回20条,并相应提高max_tokens,而不是让模型无限扩展。frequency_penalty和presence_penalty不宜调高,它们可能促使模型改写字段名或添加同义解释。
提示分层同样关键。系统层放不可变契约和输出格式,用户层放任务数据和具体字段值,待处理文本要用明确分隔符包起来,并说明其中的内容只作为数据,不得改变输出格式。这样能降低文本注入对JSON结构的干扰。对于长文档,不要把“先分析再输出”和“只输出JSON”放在同一轮要求里,因为模型可能把分析过程写进输出。需要推理时,可以让模型在JSON内部使用固定字段承载必要结论,或者把任务拆成两步,但最终结构化输出那一轮只保留格式要求。
某类工单分类接口曾因为max_tokens设置过小,在标签较多时返回不完整JSON,表现为解析器报“Expecting property name enclosed in double quotes”。把max_tokens从256提高到1024,并在提示中限定labels数组最多8项后,截断问题基本消失。参数调整不是孤立动作,它要和字段数量、数组上限、输入长度一起评估。低温度加明确契约能减少格式漂移,但无法替代校验,因此参数层解决的是概率问题,工程层还要接住剩余失败。
4. 构建校验闭环与失败重试机制
模型输出后不要直接进入业务逻辑,应先用json.loads解析,再用JSON Schema或Pydantic模型校验必填字段、类型、枚举、数值范围和额外字段。解析失败和校验失败要分开记录,因为前者通常是围栏、前后解释、尾逗号或截断,后者更多是字段缺失、类型漂移和枚举越界。校验错误信息要保留字段路径和期望类型,例如items[2].qty应为整数但收到字符串,这些信息可以作为下一次重试的反馈。重试次数控制在两到三次,每次把原始输入、上一次输出和具体错误一起送回模型,要求只修复JSON,不改变已通过校验的字段。
重试提示要避免笼统地说“格式不对”,而应指出哪一条规则被违反。若首次输出被Markdown围栏包裹,就要求去掉围栏并只返回对象;若amount是字符串,就要求改为数字且不带货币符号;若缺少必填字段,就要求补null而不是编造值。连续失败后应进入降级路径,例如改用规则抽取、缩小字段范围或转人工复核,并把失败样本写入日志。监控指标可以包括首次解析成功率、Schema通过率、重试后成功率和平均输出token数,这些指标能帮助判断问题来自提示契约、参数设置还是输入数据质量。
在某类批量工单处理中,首次解析失败常见于三类情况:代码围栏包裹、字段缺失和数字被写成字符串。加入JSON Output、字段级契约和一次带错误反馈的重试后,大部分失败可以在第二轮修复。重试仍失败时,系统应把原始输入、最后一次输出和校验错误写入日志,转入人工复核队列,而不是让不完整JSON进入下游。校验闭环的价值在于把模型的概率性输出变成可观测、可拦截、可修复的工程流程。
