1. 从野路子到正规军:先搞清楚我们到底在解决什么问题
这两年做大模型应用,你会发现一个特别魔幻的现象:模型对话能力越来越强,但一旦你让它返回结构化数据,它就开始“自由发挥”。要么给你一段前后带引号的JSON字符串,要么字段名突然从下划线变成驼峰,要么一个中文字符编码乱掉导致整个解析失败。如果你接的是本地部署的开源模型,比如Ollama拉下来的那几个,这种情况更是家常便饭。
先说清楚一个概念:JSON(JavaScript Object Notation)本质上是数据的字符串表示法,它只有对象、数组、字符串、数字、布尔值和null六种数据类型。模型要做的不是“把JSON写出来”,而是在给定文本约束下,逐token地生成一段符合JSON语法的字符流。问题就出在这里——语言模型的目标函数是“预测下一个最可能的token”,而不是“生成一段合法JSON”。这两个目标在大多数时候是一致的,但在边界情况下会产生肉眼可见的偏差。
这篇文章我想完整梳理一下让大模型稳定输出JSON的几条路径,从最基础的提示词约束,到函数调用、JSON Schema约束,再到借助框架做结果校验和重试。我会结合Ollama本地部署、OpenAI兼容接口、Pydantic这类实际工具来拆解,也会把我踩过的一些坑放进来。适合正在做大模型应用开发、或者想在本地跑一个可靠的智能体后端的朋友参考。
2. 为什么大模型会输出“非法JSON”:根因拆解
在动手解决之前,得先明白问题出在哪一层。你别一上来就责怪模型蠢,很多时候是我们用错了工具,或者压根没理解它内部的生成机制。
2.1 解码策略带来的不确定性
模型在生成token时,本质上是在一个概率分布上做采样。你设置了temperature=0.7,它就敢在低概率的词上稍微冒险;你设置了top_p=0.95,它就可能为了追求语义连贯而忽略json语法规则。这就导致一个非常现实的问题:同一个prompt,同一条输入,模型这次给你的JSON是合法的,下次就可能在数组末尾多了一个逗号。
我在本地用Ollama跑Qwen系列的时候,遇到过最离谱的情况是把值为null的字段直接给省略了。结果下游程序拿着schema去校验,报了个missing field错误。这类问题在开放域对话模型上特别常见,因为模型的训练数据里充塞着大量“非严格JSON”的文本——比如博客里的markdown代码块、聊天记录中的JSON片段、配置文件里的注释等等,模型对“近似JSON”的容忍度远高于你的解析器。
2.2 上下文长度与注意力衰减问题
另一个容易被忽视的坑是:当你把一长串示例JSON塞进prompt里,期望模型严格模仿它的格式时,模型的注意力会随着序列长度增加而衰减。它在生成早期字段时可能还规规矩矩,但到了后面十几个字段时,格式逐渐“放飞自我”。这就是为什么很多人在短示例上测试一切正常,一旦换成真实业务的长schema就开始翻车。
我的经验是,大模型的JSON输出稳定性和目标JSON的长度成反比。这不是玄学,而是模型注意力机制的内在特性。所以当你发现加长prompt反而导致出错率上升时,不要总觉得是prompt写得不够好,压缩约束长度往往更有效。
2.3 微调阶段的数据偏差
这个更隐蔽。如果你用的是经过微调的垂直模型,比如基于Llama做过的中文对话微调,它在微调阶段使用的数据里如果JSON示例不够规范,模型学到的“JSON分布”就是歪的。我遇到过某个模型,它特别喜欢在JSON里加注解性质的冗余字段,像“description”这种东西,导致输出体积膨胀,解析没报错但内容严重偏离预期。
所以做模型选型时,如果你明确知道自己要大量使用结构化输出,最好选择那些在指令微调阶段就强化过JSON能力的模型,或者在部署时打开后端自带的结构化输出约束开关,而不是单纯依赖模型“自觉”。
3. 方案一:纯Prompt约束的极限操作
这是最基础的手段,也是很多人的第一反应。我一说你就会觉得太简单:在系统提示词里写“请严格输出JSON,不要输出多余内容”,然后期待模型乖乖听话。但实际效果嘛,我只能说时灵时不灵。
3.1 提示词的写法确实有讲究
我在实践里摸索出几个还比较有效的写法原则,分享出来:
- 给模型一个明确的“出发点”:不要笼统说“返回JSON”,而是给一个完整的JSON开头,比如让模型接着这个开头继续写。这样能大幅降低模型另起炉灶的概率。
- 用示例把schema的形状说清楚:与其用自然语言描述“你要返回一个包含用户姓名、年龄和邮箱的对象”,不如直接给一段包含真实值的示例JSON。模型对具体例子的模仿能力远强于对抽象描述的理解能力。
- 明确禁止markdown标记:很多模型习惯把JSON包在
json ...代码块里,这对人阅读友好,但对程序解析是个噩梦。你要在prompt里明确说“不要使用markdown代码块标记”。
实际操作中,我用过一个相对好用的“模板式”prompt结构,大概长这样:
你需要抽取用户信息并返回JSON。要求: 1. 仅输出JSON对象本身,不要输出任何其他解释文字。 2. 不要使用markdown代码块。 3. 输出格式必须严格匹配以下示例: {"name": "张三", "age": 28, "email": "zhangsan@example.com"} 4. 如果某字段无法确定,用null填充,不要省略字段。这套写法比单纯说“请用JSON回答”要稳得多。至少在我用Qwen2.5 7B、Llama 3.1 8B本地部署测试时,格式正确率能从不到50%提到80%以上。但这远远不够——你还有五分之一的概率拿到一串带前缀解释的文本。
3.2 止损方案:用正则和解析兜底
纯Prompt约束做不到100%,所以必须有一个后置的兜底逻辑。我的做法是先做一个“宽松提取阶段”,用正则匹配出最像JSON的部分,再去尝试解析。这里有几个常见的正则思路:
- 找到第一个
{和最后一个},截取中间内容,尝试json.loads。 - 如果内容是
{"key": value}形式,但value一侧有换行或多余符号,先做一次清洗,比如去掉不可见控制字符。 - 如果JSON是嵌在markdown代码块里的,优先提取代码块内容再解析。
这个思路能救回不少本来要失败的输出。但它只是“亡羊补牢”,治标不治本。遇到嵌套层级深的JSON,或者数组里面套对象的复杂结构,正则就力不从心了。
4. 方案二:让模型自己知道要调用工具——Function Calling / Tools
如果你觉得Prompt约束像“靠嘴说服”,那Function Calling就是“给人发了一张标准操作手册”。通过工具调用机制,模型不再自由生成JSON字段,而是在预设好的函数签名里填入参数。这一步的效果提升是质的飞跃。
4.1 Function Calling的基本原理
简单说,你在请求里向模型声明了一批工具函数,每个函数列出参数名、类型、是否需要必填和描述。模型如果判断需要调用工具,就会在它的回复中输出一个结构化的调用意图,比如:
{"name": "get_weather", "arguments": {"city": "北京"}}这里的关键点在于:模型输出的不是“一段符合JSON语法的自由文本”,而是“在一个已经定死的框架里填槽位”。生成难度从“凭空创造结构”降级成“按提示填词”,正确率自然高很多。
在OpenAI兼容接口上,请求格式大致是:
from openai import OpenAI client = OpenAI( api_key="ollama", base_url="http://localhost:11434/v1" ) response = client.chat.completions.create( model="qwen2.5:7b", messages=[ {"role": "system", "content": "你是一个信息抽取助手。"}, {"role": "user", "content": "张三的邮箱是zhangsan@example.com"} ], tools=[ { "type": "function", "function": { "name": "extract_user", "description": "抽取用户姓名、年龄和邮箱", "parameters": { "type": "object", "properties": { "name": {"type": "string", "description": "用户姓名"}, "age": {"type": "integer", "description": "用户年龄"}, "email": {"type": "string", "description": "用户邮箱"} }, "required": ["name", "age", "email"] } } } ], tool_choice="required" )注意这里tool_choice="required"或者tool_choice={"type": "function", "function": {"name": "extract_user"}},这两个参数非常关键。它强制模型必须调用函数,而不是“可选地”决定是否调用。如果你不设置,模型可能自然回复一段文本,根本不走工具调用通道。
4.2 本地模型能用吗?哪些模型支持得比较死
这是大家最关心的问题。因为Function Calling的能力是在模型训练阶段注入的,不是靠提示词就能“启发”出来的。你拿一个纯对话模型去调tools接口,它可能直接无视你的工具声明,给你一段普通的markdown文本。
就我在Ollama上的实测经验来说:
- Qwen 2.5系列(7B及以上)对Function Calling的支持还算能打,基本能输出结构合理的工具调用。
- Llama 3.1 8B在简单场景下能跑通,但复杂工具定义下偶尔会漏参数。
- 一些小参数模型或者旧版模型(比如Llama 2)基本指望不上,该用Prompt还是得用Prompt。
这个版本依赖问题其实是目前本地大模型落地的主要堵点。有些号称支持Function Calling的模型,只是在Prompt里假装“理解”了工具,实际返回的内容根本不走结构化通道。所以当你选型本地模型时,不要只看跑分和对话效果,必须在你自己的任务上实测一遍工具调用流程。
4.3 工具选择与控制强度
如果你希望模型输出完全匹配你的JSON Schema,OpenAI兼容接口还支持response_format参数,很多本地推理服务(包括Ollama)也已经兼容了。这个参数的逻辑是,模型在大脑里先草拟一个符合格式约束的输出计划,再调整生成时的概率分布,让合法JSON的token概率更高。
这里有一个细节值得注意:response_format和tools同时使用的时候,有些后端会有优先级差异。Ollama的format=json虽然能保证输出是合法JSON,但它的约束条件是“任何合法JSON”,不会强制跟你给的schema匹配。也就是说,模型自己决定了一个结构,可能是 {“a”: 1, “b”: 2},而你本来期望的是 {“name”: “...”, “age”: ...}。字段不匹配同样会导致下游解析失败。
所以如果你对字段结构有强要求,更可靠的组合是tools+tool_choice强制指定某一个函数,让模型必须往预设的parameters里填。这时候你拿到的arguments基本就是你schema里的那些字段了。
5. 方案三:JSON Schema约束——让后端去“卡脖子”
如果你嫌Function Calling在本地模型的稳定性不够,或者说你的项目压根不依赖OpenAI兼容层,那么你可以试试后端直接做JSON Schema约束。这一步的思路已经超越了提示词工程,而是从生成源头就限制模型只能输出合法且匹配schema的token序列。
5.1 结构化输出与采样约束的实现思路
Ollama在比较早的版本就支持了format参数。简单说,你可以传format=json让它只输出JSON,甚至可以传一个完整的JSON Schema,后端会用基于上下文无关文法的受限采样逻辑,把生成过程约束在合法JSON结构内。此时模型每一步只能从语法允许的token里采样,非法逗号、缺失引号这类基础问题天然消失。
用起来大概是这样的:
curl http://localhost:11434/api/generate -d '{ "model": "qwen2.5:7b", "prompt": "张三今年28岁,邮箱是zhangsan@example.com,请提取用户信息", "format": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "email": {"type": "string"} }, "required": ["name", "age", "email"] }, "stream": false }'Ollama拿到这个schema之后生成的JSON就是严格匹配的:字段不会多,不会少,类型不会错。
5.2 结构约束对生成质量的影响
但这里有个tradeoff值得讲清楚:约束越强,模型的生成多样性越低,有时还会影响它对语义的理解。道理也简单,你把模型的搜索空间限制在一个非常窄的token集合里,它每一步的选择变少了,生成与用户query强相关的内容的能力也会下降。在极端情况下,模型可能输出完全正确但语义完全不符合输入的内容,比如把年龄填成null,或者把email截断到一个奇怪的字符串。
一个真实的坑:有一次我用JSON Schema约束模型抽取用户的“所在城市”,schema里限定了只能是字符串。结果模型为了满足语法需求,把一个无法识别的城市名直接换成了一个随机城市,而不是输出null。这种“为了合规而乱填”的幻觉行为,在强约束下反而更容易出现,因为模型在做语法约束和语义理解的多目标优化时,很容易牺牲语义保真度。
所以我在生产环境里的用法是双层结构:第一层用强约束确保JSON语法绝对合法,第二层在后处理逻辑里对关键字段做人工校验和规则审查。永远不要假设“格式正确”等于“内容正确”。
5.3 处理API层格式校验失败的常见错误
如果你接触过.NET或者FastAPI这类框架,应该见过类似报错:
failed to deserialize the json body into the target type: input: missing fields这个问题看起来像是JSON反序列化失败,但根因往往在模型输出端:模型返回的JSON缺了一个必填字段。我在排查这类问题时总结了一个通用的处理顺序:
- 先把模型的原始输出打印出来,确认是不是被截断了(很多本地模型输出长度到了max_tokens就一刀切,JSON只剩半个)。
- 检查字段命名。模型可能把
created_at输出成了createdAt,这种大小写和命名风格不一致在本地模型里非常普遍。 - 检查字段类型。模型可能把数字输出成了字符串“28”,而不是整数28,这在JSON反序列化时会触发类型转换异常。
- 如果以上都对,再考虑是不是schema本身有问题,比如漏了数组类型定义导致模型不知道该怎么套结构。
6. 方案四:框架层面的“封装对抗”——Pydantic与with_response_model
当你把上述方法都掌握之后,你会发现生产环境还有个更现实的问题:你不能在代码里到处写try-catch来处理模型返回的垃圾JSON。你需要一个统一的抽象层,让模型输出自动变成类型安全的Python对象。于是Pydantic这套方案就成了很多人的首选。
6.1 为什么Pydantic适合做这层“防腐层”
你在热词里能看到很多关于Java Bean转JSON时大写字母变下划线的讨论,这本质上是语言层面对序列化/反序列化规则的默认约定不同。Python世界里,Pydantic做的事情类似:它定义数据模型,然后负责从JSON字符串或字典里校验、清洗、转换成Python对象。放在大模型输出后面,它就变成了一道“防线”:模型输出的JSON先进Pydantic,合法就过,不合法就把错误信息返回去,让你决定是重试还是走旁路。
举一个实际例子。我现在写大模型抽取任务,通常是这样设计的:
from pydantic import BaseModel, Field from openai import OpenAI class UserProfile(BaseModel): name: str = Field(description="用户姓名") age: int = Field(description="用户年龄,必须为整数") email: str = Field(description="用户邮箱") is_member: bool = Field(default=False, description="是否会员") client = OpenAI( api_key="ollama", base_url="http://localhost:11434/v1" ) resp = client.chat.completions.create( model="qwen2.5:7b", messages=[ {"role": "system", "content": "从用户描述中抽取结构化信息"}, {"role": "user", "content": "张三,28岁,邮箱zhangsan@example.com,是高级会员"} ], response_format={"type": "json_object"}, ) try: result = UserProfile.model_validate_json(resp.choices[0].message.content) print(result) except Exception as e: print("解析失败,原始输出:", resp.choices[0].message.content) print("错误信息:", e)如果模型返回的JSON里缺了name字段,Pydantic会直接抛ValidationError,提示你Field required。这比你自己手工判断字段缺失要稳健得多。
6.2 with_response_model的便利与适配问题
前面铺垫了逻辑,现在说一个更偷懒的上层封装。如果你用的是openai的Python SDK,并且想要更简洁的写法,可以考虑用with_response_model这类语法糖(在Instructor、LangChain等框架里都有类似实现)。
以Instructor为例,它会自动完成三件事:
- 将Pydantic模型的字段描述转成tools参数格式,发给模型。
- 强制模型走工具调用通道,直接返回
arguments。 - 在拿到结果后用Pydantic做一步反序列化校验,失败会执行重试策略。
示例近似这样:
import instructor from openai import OpenAI from pydantic import BaseModel client = instructor.from_openai(OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")) class UserProfile(BaseModel): name: str age: int email: str user = client.chat.completions.create( model="qwen2.5:7b", response_model=UserProfile, messages=[ {"role": "user", "content": "张三 28岁 zhangsan@example.com"} ], )这层封装的好处是:你不再关心模型输出的是不是“纯JSON”、要不要剥离markdown标记、字段命名对不对——框架全都帮你处理了。不好的一面是:它引入了一层抽象,一旦出了问题(比如模型不支持工具调用、后端返回格式不符合instructor预期),排查起来会绕一个圈子。
7. Ollama本地部署中的格式与兼容性细节
既然热词里反复出现Ollama、本地部署、私有大模型这些关键词,这块我得多说几句,因为本地部署模型做结构化输出时,环境因素比模型本身更影响结果。
7.1 模型量化级别对输出稳定性的影响
我用Ollama跑过Qwen2.5 7B的Q4_K_M和Q8_0两个量化版本,明显的感受是:Q8_0在JSON输出上的语法正确率更高,字段遗漏概率更低。这不是心理作用,而是量化会引入微小但真实的信息损失,尤其是在处理那些低概率的高维语义特征时。
如果你的业务对结构化输出质量要求很高,我的建议是:
- 能跑Q8_0或FP16,就不要用Q4_K_M。
- 如果显存只够跑低量化级别,尽量配合Function Calling或JSON Schema这类强约束手段,把“格式正确”这条底线交给机制而不是模型智力。
- 不要迷信“参数越大越强”,有时候7B模型在JSON语法稳定性上反而不如一个专门微调过的3B模型。
7.2 混用OpenAI SDK与Ollama时的兼容问题
很多教程会让你把base_url指向http://localhost:11434/v1,这样就能用OpenAI SDK来调本地模型,这在结构化输出场景下确实方便。但这里有几个坑你得提前知道:
第一,Ollama的/v1/chat/completions不是100%兼容OpenAI的协议。比如response_format参数,OpenAI官方支持json_object和json_schema两种,Ollama只实现了部分json_object语义,而且不保证json_schema能强制执行。
第二,tools参数在Ollama上是可选的,但它的实现基于模型本身是否支持工具调用。你在Ollama上声明tools,后端会把工具的JSON定义拼进提示词里,模型如果本身没有工具调用能力,它依然会按照普通对话的方式回复。
第三,你还得注意reasoning_effort、thinking这类附加参数,在不同后端上可能完全不存在。不要以为OpenAI SDK的每个参数都是通用的,换到Ollama后最好先跑一个最小验证,确认关键参数真的生效了,再放到业务流里。
7.3 模型上下文长度对JSON完整性的影响
这是个非常隐蔽的问题。当模型的上下文窗口较短,而你输入了一段很长的用户文本,再让模型输出一个长JSON,它可能没有足够的“余量”把整个JSON结构完整生成出来。我遇到过模型输出到一半就停止,JSON尾部缺失,下游解析直接失败。
解决办法有几个:
- 调高
num_ctx参数,但要注意这会加大显存占用。 - 把输入文本先做截断或精简,给输出留足空间。
- 在prompt里明确要求“只输出JSON,不要重复用户输入内容”,避免模型在输出里复述原文导致长度耗尽。
8. 常见问题与排查技巧速查
为了让你以后遇到问题时能快速定位,我把实际项目中使用结构化输出时的高频问题整理成了一个速查表。这些问题里有些是模型问题,有些是参数配置问题,有些是框架兼容问题,别看它们各不相同,但排错思路有规律可循。
| 现象 | 可能原因 | 优先排查方向 |
|---|---|---|
| 输出内容虽然看起来像JSON但解析失败 | 有markdown代码块包裹 | 用正则提取代码块内容,或者检查prompt是否已明确禁止代码块 |
| JSON解析报missing field | 模型漏了必填字段 | 改用Function Calling强制补全,或后置校验时用默认值兜底 |
| 类型错误(字符串和数字混用) | 模型对类型约束不敏感 | 在JSON Schema里明确type,并在后端做一次强制类型转换 |
| 字段命名与预期不一致(snake_case / camelCase) | 模型的训练数据风格导致 | 在prompt或schema中加入命名风格示例,让模型照着写 |
| 输出被截断,JSON不完整 | max_tokens不足,或上下文窗口过短 | 调大输出长度上限,压缩输入内容,为输出留空间 |
| 模型在特定输入下反复编造内容 | 数据中实体信息确实缺失 | 关闭无法确认的数据返回null,并做规则校验,别全信模型 |
| 强约束下语义质量下降 | 约束过严导致生成空间受限 | 降低约束强度,比如只锁定JSON骨架,字段内容留给模型自由生成 |
还有几个我个人觉得价值比较高的实操心得,一并写出来:
一是永远保留一份原始输出日志。很多排查类问题的起点都是“我猜模型输出了什么”,为此我很早就把一个中间环节固定下来:raw_output永远先落日志,再丢给解析器。这样即使解析失败,也能在日志里看到原汁原味的模型输出,而不是被包装错的异常堆栈。没有原始输出,你就是盲人摸象。
二是重试机制的粒度要细,代价要限住。一次失败就整体重跑整个流程,既慢又贵。我的习惯是先做“轻量修复”:剥离代码块、纠正常见标点错误、去除多余字符;修复失败再做“二次请求”:带着报错信息回到模型,让它重新生成;再失败才触发“降级策略”:用默认值填充关键字段,或者把这个样本标记为人工处理。重试最多不要超过两次,不然延迟不可控。
三是区分服务端校验和客户端校验。如果你用的是API网关或后端服务,建议在服务端统一做一次严格校验,客户端只做展示。因为模型输出格式的问题常常是概率性的,同一个prompt这次成功下次失败,你必须在统一的入口把所有可能出错的报文拦下来,而不是让业务方各自处理。
四是不要忽略输入侧的数据质量。很多时候结构化输出的错误根本不在“格式”,而在“内容”。例如你要模型从用户消息里抽取地址,用户输入了一串无法识别的乱码,模型为了强行凑出结构,可能会填一个不存在的城市。这种问题靠约束JSON是解决不了的,必须在前端把输入数据清洗好,或者在模型判断不出时宁可返回null也不要返回猜测值。
9. 结合一下实际:一个信息抽取的端到端小案例
前面写了太多理论,我拿一个近期的实际任务把整个流程串起来。这个任务是:从一段包含用户信息的聊天记录里,自动抽取姓名、年龄、所在城市、工作年限和一句话职业简介,并返回符合后端的UserProfile模型。我用qwen2.5:7b配合Ollama来完成,要求实现三个目标:JSON语法必须合法、字段必须齐全、内容尽量准确。
我设计的处理流水线拆成五步:
第一步是预处理。把原文去掉表情符、多余空格、语气词,整理成干净的文本。原文如果特别长,我用一个截断函数保留下可能包含实体的部分,比如包含“我今年”“我的邮箱”“工作”等关键词附近的段落。
第二步是构建prompt。系统提示词里,我会放一段示例JSON,并声明“仅返回JSON,不要包含额外解释”。用户消息里附带经过预处理的文本。示例里要故意包含null场景,让模型学会缺失字段时返回null而不是编造值。
第三步是请求配置。模型用qwen2.5:7b,format参数放一个完整的JSON Schema,把num_ctx调到8192,max_tokens调到1024,temperature调整到0.3。这里温度不要设太高,结构化输出任务对随机性几乎没有需求。
第四步是后处理。用Pydantic的model_validate_json做解析校验,如果失败,进入修复逻辑:用正则尝试剥离多余字符,再校验一次;还不行就调用模型做第二轮生成,并附带上第一次的错误信息。
第五步是输出。解析成功的对象转成Python字典,传给下游入库。这一整套流程跑下来,最直观的感受是:严谨的数据链路本身,比更强的模型更能提升稳定性。在我的一次对照测试中,相同模型、相同参考数据,纯prompt方案的正确率大约在61%,加了JSON Schema约束后提升到83%,再加上函数调用与校验填充可以到91%以上。剩下的个别失败样本,基本都是模型语义幻觉问题,不是格式问题。格式问题能被机器机制彻底挡掉,内容问题才是后续真正需要打磨的部分。
10. 绕不开的下一个话题:从“格式合法”到“内容可信”
写到这里,我不太想做一个“总结陈词”,因为这条路还远没有走完。结构化输出解决的只是最外面的那层壳——JSON合法了,字段齐了,类型对了,但模型返回的内容是不是真的符合事实,依然没有机制能保证。
我自己的体会是,做工程和做研究最大的区别就在这里:工程上你追求的是一个可预期的结果,哪怕这个结果不是最优的,但必须是稳定的。所以我更愿意把大模型当成一个“语法上可以被约束、语义上仍需要校验”的组件,而不是一个“说什么都对”的答案机器。
后续如果你在这个方向继续深入,有几个延伸点值得留意:
- 多模态模型的结构化输出:图像输入配合JSON输出,比如从图片里抽取票据信息返回结构化字段,这在传统OCR领域已经有成熟方案,但大模型的加入让这事的门槛和成本都降了很多。热词里的“农业大模型”就是一个很有意思的场景——实时监测土壤、气象数据,通过多模态输入生成结构化的灌溉建议,模型输出端和传感器数据端差不多可以无缝对接。
- 微调与结构化能力绑定:如果你有足够多的垂直领域数据,可以在微调阶段就在模型的输出格式上做文章。数据准备阶段把大量JSON样本放进训练集,模型会在权重层面记住“这类任务应该输出什么形状的结果”。这比任何后置校验都更本质。
- 数据结构从JSON到更复杂的Graph或嵌套Schema:有些业务并不适合扁平JSON,需要输出带层级关系、多实体关联的结构。这时JSON Schema的嵌套定义能力很重要,但模型的稳定性挑战也会成倍上升。
最后分享一个小技巧:让模型先输出一个“思考计划”,再让它输出JSON时,反而容易扰乱JSON结构。如果你真的需要做复杂任务,建议让思考过程放在前一次调用里,第二次调用只让它基于上一步结论输出JSON。把一个复杂任务拆成“对话”和“抽取”两个阶段,比让模型一次性从思考跳到结构化输出,稳定得多。
好了,这篇就写到这。你自己动手跑一遍,比我写再多都有用。有什么你在本地部署或JSON输出上踩过的坑,欢迎在评论区一起聊聊。