大模型结构化输出完全指南:从一次"我让模型返回 JSON、它却回了一段夹着解释的 Markdown"看懂可靠解析

2024 年我做一个功能让大模型从一段用户输入的文本里提取出结构化信息姓名金额日期之类再交给后面的程序去用。第一版我做得很省事在 prompt 里写一句请以 JSON 格式返回拿到模型的回复直接 json.loads。本地测了几条真不错模型乖乖回了 JSON 我也顺利解析出来了。我心里很踏实结构化输出嘛不就是在 prompt 里说一句返回 JSON 然后 json.loads 一下。可等它真正上线跑在五花八门的真实输入上一串问题冒了出来。第一种最先把我打懵 json.loads 频繁抛异常整个流程崩掉模型把 JSON 包在了 Markdown 代码围栏里或者在 JSON 前面加了一句好的这是提取结果。第二种偶尔 json.loads 成功了可字段对不上我要的 age 字段它没给或者 age 给了个字符串二十八。第三种模型还爱自作主张我没要的字段它给加上枚举值我只允许三个它给了第四个。第四种遇到长文本 JSON 直接被截断成半截缺一个右括号。我盯着这一连串问题想了很久才彻底想明白第一版错在我以为结构化输出就是在 prompt 里说一句返回 JSON 然后 json.loads。可它不是。大模型是一个概率生成器 prompt 里那句返回 JSON 只是一条建议不是一道保证它随时可能包围栏夹解释缺字段错类型被截断。真正用好结构化输出核心不是在 prompt 里说一句而是理解模型的输出天然不可靠并为此搭一整套从约束解析校验到重试的防线。本文从头梳理为什么说一句返回 JSON 不算结构化输出怎么用 JSON mode 从源头约束解析为什么必须容错 schema 校验该怎么做解析失败怎么带错误重试以及 few-shot 示例截断检测枚举约束这些把结构化输出真正做对要避开的坑。

2024 年我做一个功能:让大模型从一段用户输入的文本里,提取出结构化信息(姓名、金额、日期之类),再交给后面的程序去用。第一版我做得很省事:在 prompt 里写一句"请以 JSON 格式返回",拿到模型的回复,直接 json.loads。本地测了几条——真不错:模型乖乖回了 JSON,我也顺利解析出来了。我心里很踏实:"结构化输出嘛,不就是在 prompt 里说一句'返回 JSON',然后 json.loads 一下。"可等它真正上线、跑在五花八门的真实输入上,一串问题冒了出来。第一种最先把我打懵:json.loads 频繁抛异常、整个流程崩掉——模型把 JSON 包在了 Markdown 代码围栏里,或者在 JSON 前面加了一句"好的,这是提取结果:"。第二种:偶尔 json.loads 成功了,可字段对不上——我要的 age 字段它没给,或者 age 给了个字符串 "二十八"。第三种:模型还爱自作主张——我没要的字段它给加上,枚举值我只允许三个、它给了第四个。第四种:遇到长文本,JSON 直接被截断成半截,缺一个右括号。我盯着这一连串问题想了很久才彻底想明白,第一版错在一个根本的认知上:我以为"结构化输出就是在 prompt 里说一句返回 JSON,然后 json.loads"。这句话把"让模型返回 JSON"和"拿到我能可靠使用的结构化数据"当成了一回事。可它不是。大模型是一个概率生成器,prompt 里那句"返回 JSON"只是一条建议,不是一道保证——它随时可能包围栏、夹解释、缺字段、错类型、被截断。真正用好结构化输出,核心不是"在 prompt 里说一句",而是理解模型的输出天然不可靠,并为此搭一整套从约束、解析、校验到重试的防线。这篇文章就把大模型结构化输出梳理一遍:为什么"说一句返回 JSON"不算结构化输出、怎么用 JSON mode 从源头约束、解析为什么必须容错、schema 校验该怎么做、解析失败怎么带错误重试,以及 few-shot 示例、截断检测、枚举约束这些把结构化输出真正做对要避开的坑。

问题背景

先把那串问题的现象和我的误判讲清楚,后面所有的设计都是冲着纠正这个误判去的。

现象:在 prompt 里要求模型返回 JSON、再直接 json.loads 之后,上线冒出一串问题:json.loads 频繁抛异常(JSON 被包进 Markdown 围栏、或前面夹着解释文字);偶尔 parse 成功了字段却对不上(缺必填字段、类型不对);模型自作主张加字段、给超出枚举范围的值;长文本下 JSON 被截断成半截

我当时的错误认知:"结构化输出就是在 prompt 里说一句返回 JSON,然后 json.loads 一下就行了。"

真相:大模型本质是一个按概率逐字生成文本的模型。你在 prompt 里写"返回 JSON",对它来说只是一个倾向、一条建议,不是一道能保证的契约。它很可能:习惯性地把代码包进 Markdown 围栏、礼貌性地加一句开场白、漏掉某个字段、把数字写成中文、在 token 上限处被硬生生截断。可靠的结构化输出,是一整套防线:用 JSON mode 从 API 层约束语法、解析时容错剥离、用 schema 校验字段是否符合要求、失败时把错误反馈给模型重试、用 few-shot 示范格式、检测截断。让模型"返回 JSON"只是开头,把这套防线搭齐才是关键。

要把结构化输出做对,需要几块认知:

  • 为什么"说一句返回 JSON"不等于结构化输出——模型的输出天然不确定;
  • 源头约束——JSON mode / response_format 能保证什么、不能保证什么;
  • 容错解析——剥离围栏、抠出 JSON 片段;
  • schema 校验——能 parse 不等于字段对,要校验缺失、类型、枚举;
  • 失败重试、few-shot、截断检测这些工程坑怎么处理。

一、为什么"说一句返回 JSON"不等于结构化输出

先把这件最根本的事钉死:大模型不是一个"执行你指令"的程序,它是一个"根据上文,一个字一个字往下猜最可能的下一个字"的概率模型。你在 prompt 里写"返回 JSON",并没有在它身上装一个"只能吐 JSON"的开关——你只是让"接下来吐出 JSON"这件事的概率,变高了一些。但"概率高"不等于"必然"。它在训练时见过海量"把代码包在 Markdown 围栏里"的样本,也见过海量"回答前先礼貌寒暄一句"的样本——所以它很自然地、按概率地,就会这么干。你只在 prompt 里说一句,等于把整个解析的成败,赌在了一个概率事件上。

下面这段代码,就是我那个"上线就频繁崩"的第一版:

import json
from openai import OpenAI

client = OpenAI()


# 反面教材:以为 prompt 里说一句"返回 JSON",拿到的就是干净 JSON
def naive_extract(text):
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": f"请从下面这段话提取姓名和年龄,返回 JSON:\n{text}",
        }],
    )
    raw = resp.choices[0].message.content
    # 破绽:直接 json.loads,赌模型回的就是一段纯 JSON
    return json.loads(raw)
    # 破绽一:模型常把 JSON 包在 markdown 代码围栏里,json.loads 直接崩。
    # 破绽二:模型爱在 JSON 前后加一句"好的,这是结果:"之类的解释。
    # 破绽三:就算 parse 成功了,字段缺了、类型错了,也照样往下传。

这段代码在本地测试时能跑通,它的问题不在代码本身,而在一个被忽略的前提:它默认"模型一定会、且只会,吐出我要的那段纯 JSON"。可它把一个概率事件,当成了确定事件。于是那串问题就有了解释:json.loads 崩溃,是因为它没料到模型会加围栏、夹解释;字段对不上,是因为它以为 parse 成功就等于数据正确;截断,是因为它没考虑输出长度会撞上 token 上限。问题的根子清楚了:结构化输出的工程量,全在"承认模型输出不可靠"之后——你不为这份不可靠搭防线,它就出问题。先从第一道防线说起。

二、用 JSON mode 从源头约束

第一道防线,是从 API 层面就把"语法"约束住。现在主流的大模型 API,都提供了一个叫 JSON mode(或 response_format)的功能——开启它之后,API 会保证返回给你的内容,是一段语法合法、能被 json.loads 解析的 JSON:

def extract_with_json_mode(text):
    """开启 JSON mode:让模型在 API 层面保证输出是合法 JSON。"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            # 用了 JSON mode,prompt 里通常必须出现 "JSON" 字样,否则会报错
            {"role": "system",
             "content": "你是数据提取助手,只输出一个 JSON 对象。"},
            {"role": "user",
             "content": f"提取姓名(name)和年龄(age):\n{text}"},
        ],
        # 关键:response_format 让 API 保证返回的是可解析的 JSON
        response_format={"type": "json_object"},
    )
    return json.loads(resp.choices[0].message.content)
    # 注意:JSON mode 只保证"语法合法",不保证"字段符合你的要求"。

JSON mode 是一道很有用的防线,但你必须清楚它的边界。它解决的是:不会再有 Markdown 围栏、不会再有"好的,这是结果:"的开场白、不会再吐出半个引号没闭合的烂 JSON——语法层面的脏东西,它基本扫干净了。但它不解决的是:它不保证里面有你要的字段、不保证 age 是个数字而不是字符串、不保证枚举值在你允许的范围内。换句话说,JSON mode 保证你拿到的是"一段合法的 JSON",但不保证它是"你想要的那段 JSON"。这里的认知要点是:JSON mode 把"能不能解析"这个问题解决了,但"解析出来对不对"这个问题,还得靠后面的 schema 校验。而且——并非所有模型、所有场景都能用上 JSON mode,所以下一道防线仍然不可省:容错解析。

三、解析必须容错:剥围栏、抠片段

就算用了 JSON mode,你也不该假设"拿到的一定是纯 JSON"——可能你对接的是不支持 JSON mode 的模型,可能是本地部署的开源模型,也可能 JSON mode 偶尔失灵。所以解析这一步,要自己写得足够皮实:不管模型回的是纯 JSON、带围栏的 JSON、还是夹着解释的 JSON,都要能把真正的 JSON 部分抠出来:

import re


def strip_to_json(raw):
    """从模型的回复里,把真正的 JSON 部分抠出来。"""
    text = raw.strip()
    # 情况一:被 markdown 代码围栏包住,形如三个反引号 json ... 三个反引号
    fence = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
    if fence:
        return fence.group(1).strip()
    # 情况二:JSON 前后夹着解释文字,就截取第一个 { 到最后一个 }
    start = text.find("{")
    end = text.rfind("}")
    if start != -1 and end != -1 and end > start:
        return text[start:end + 1]
    # 情况三:实在找不到,原样返回,交给后面的解析去报错
    return text

这个 strip_to_json 处理了三种最常见的情况:代码围栏(用正则把围栏内部抠出来)、前后夹解释(从第一个 { 截到最后一个 })、实在没有(原样交给下一步报错)。有了它,再包一层"解析失败不崩、而是返回 None"的安全解析:

def safe_parse(raw):
    """容错解析:先抠出 JSON 片段,再尝试 loads,失败返回 None。"""
    candidate = strip_to_json(raw)
    try:
        return json.loads(candidate)
    except json.JSONDecodeError:
        # 解析失败不抛异常,而是返回 None,
        # 把"接下来怎么办"的决定权,交给调用方(去重试,还是去兜底)
        return None

这里的设计要点,是 safe_parse 失败时返回 None、而不是抛异常。这看着是个小细节,意义却很大:抛异常,意味着"流程到此为止、出错了";返回 None,意味着"这次没成功,但还有别的办法"。对大模型这种"偶尔抽风、再试一次往往就好"的东西,后者才是对的姿态。这里的认知要点是:面对模型输出,解析这一步的目标不是"一次解析成功",而是"失败了也能优雅地知道失败、并把决定权交出去"。解析的皮实劲儿有了,可还有个更深的问题:JSON 能解析,不代表里面的数据是对的。

四、用 schema 校验:能 parse 不等于字段对

开头那个"parse 成功了、字段却对不上",根子是我把"能解析"误当成了"数据正确"。一个 {"naem": "张伟"}——字段名拼错了——它是一段完全合法的 JSON,json.loads 能顺利解析,可它对你的程序毫无用处。所以 parse 之后,必须再过一道校验。校验的依据,是一个你自己定义的 schema——它描述"我到底要什么",而不只是"我要个 JSON":

# 用一个 schema 描述"我到底要什么",而不只是"我要个 JSON"
PERSON_SCHEMA = {
    "name":   {"type": str, "required": True},
    "age":    {"type": int, "required": True},
    "gender": {"type": str, "required": False,
               "enum": ["male", "female", "unknown"]},
}

有了 schema,校验函数就逐条比对:必填字段在不在、字段类型对不对、枚举值合不合法:

def validate(data, schema):
    """对照 schema 校验:字段在不在、类型对不对、枚举值合不合法。"""
    errors = []
    for field, rule in schema.items():
        if field not in data:
            # 必填字段缺失,记一条错误;非必填缺失则跳过
            if rule.get("required"):
                errors.append(f"缺少必填字段 {field}")
            continue
        value = data[field]
        # 类型检查:比如 age 必须是 int,不能是字符串 "二十八"
        if not isinstance(value, rule["type"]):
            errors.append(f"字段 {field} 类型应为 {rule['type'].__name__}")
        # 枚举检查:取值必须落在允许的集合里
        if "enum" in rule and value not in rule["enum"]:
            errors.append(f"字段 {field} 取值 {value} 不在允许范围内")
    return errors

这个 validate 返回的不是一个简单的"对/错",而是一份具体的错误清单——"缺少必填字段 age"、"字段 age 类型应为 int"。这份清单非常关键,它有两个用处:第一,它替你的程序挡住了脏数据,不让"看起来是个 JSON、实则字段全错"的东西流进后面的业务逻辑;第二——也是更妙的——这份具体的错误,马上就能反过来喂给模型,让它自己改。这里的认知要点是:"能 json.loads"和"数据符合我的要求",是两道独立的关卡,JSON mode 管前者,schema 校验管后者,两道都不能少。校验能挑出错,下一个问题是:挑出错之后,怎么办?

五、解析失败要重试,且要带错误反馈

解析失败、或校验不通过,最朴素的反应是"原样再请求一次"。但这很笨——模型不知道自己上次错在哪,大概率会再错一次。聪明的做法,是把模型上一次的错误输出具体的错误信息,一起拼回对话里,让模型"看着自己的错误去改":

def extract_with_retry(text, schema, max_retries=3):
    """解析或校验失败时,把错误信息反馈给模型,让它自己改。"""
    messages = [
        {"role": "system", "content": "你是数据提取助手,只输出 JSON 对象。"},
        {"role": "user", "content": f"提取姓名和年龄:\n{text}"},
    ]
    for attempt in range(1, max_retries + 1):
        resp = client.chat.completions.create(
            model="gpt-4o", messages=messages,
            response_format={"type": "json_object"},
        )
        raw = resp.choices[0].message.content
        data = safe_parse(raw)
        errors = ["JSON 解析失败"] if data is None else validate(data, schema)
        if not errors:
            return data
        # 关键:把模型上一次的输出、和这次的具体错误,都拼回对话里
        messages.append({"role": "assistant", "content": raw})
        messages.append({"role": "user",
                          "content": f"上次输出有问题:{'; '.join(errors)}。"
                                     "请改正后,重新只输出 JSON。"})
    raise ValueError(f"重试 {max_retries} 次仍未得到合法输出")

这个 extract_with_retry精髓,在于那两行 messages.append。它把重试,从"再赌一把",变成了"带着上次的考卷和批改意见,重做一遍"。模型是很擅长"照着具体反馈改"的——你只要明确告诉它"你上次缺了 age 字段",它下一次补上的概率就非常高。这比"原样重发、盲目祈祷"有效得多。当然,重试必须有上限(max_retries),不能让一个死活搞不定的输入把模型无限调用下去。下面这张图,把一次可靠的结构化输出流程串起来:

六、工程坑:few-shot、截断与枚举

五块设计之外,还有几个工程坑,不处理就会让结构化输出不稳或踩雷坑 1:与其反复用文字描述格式,不如直接给范例。你在 prompt 里写一百字去描述"我要什么格式",效果往往不如直接甩给模型一两个"输入长这样、输出就该长这样"的范例。模型极擅长模仿范例,这就是 few-shot:

def build_fewshot_messages(text):
    """给模型一两个输入-输出范例,它会模仿范例的格式,稳得多。"""
    return [
        {"role": "system", "content": "从文本提取人物信息,只输出 JSON 对象。"},
        # 范例:用一问一答,演示"输入长这样、输出就该长这样"
        {"role": "user", "content": "张伟今年 28 岁。"},
        {"role": "assistant",
         "content": '{"name": "张伟", "age": 28, "gender": "unknown"}'},
        # 范例之后,才是真正要处理的输入
        {"role": "user", "content": text},
    ]

坑 2:输出可能被 max_tokens 截断,要主动检测。这是我开头那个"JSON 缺右括号"的真凶。如果模型要输出的内容,超过了你设的 max_tokens,API 就会在中途硬生生把它砍断——你拿到的是半截 JSON,必然解析失败。而且这种失败,你不能靠重试解决(再试还是会被截断)。要靠 finish_reason 主动识别它:

def extract_check_truncation(text):
    """检测输出是否因 max_tokens 不够而被截断。"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=build_fewshot_messages(text),
        response_format={"type": "json_object"},
        max_tokens=512,
    )
    choice = resp.choices[0]
    # finish_reason == "length" 说明输出是被截断的,JSON 多半不完整。
    # 这种错,重试也没用,必须调大 max_tokens 或把任务拆小。
    if choice.finish_reason == "length":
        raise ValueError("输出被截断,请调大 max_tokens 或拆分任务")
    return json.loads(choice.message.content)

坑 3:枚举字段一定要在 prompt 里写死可选值,并在校验时兜底。如果你要一个 gender 字段、只接受 male / female / unknown 三个值,就必须在 prompt 里把这三个值明明白白列出来,否则模型可能给你返回"男"、"M"、"保密"这种你处理不了的值。光在 prompt 里说还不够,校验时也要再卡一道(就是上面 validate 里的 enum 检查)——prompt 是"软约束",校验是"硬约束",两道都要。坑 4:结构化任务,温度调到 0。提取、分类这类有标准答案的结构化任务,要的是稳定、可复现,不是创意。把 temperature 调到 0,能让模型的输出尽量确定,同样的输入尽量给同样的结果。把前面所有手段串成一条完整的可靠管线:

def robust_extract(text, schema, max_retries=3):
    """把所有手段串起来:few-shot + JSON mode + 容错解析 + 校验 + 重试。"""
    messages = build_fewshot_messages(text)
    for attempt in range(1, max_retries + 1):
        resp = client.chat.completions.create(
            model="gpt-4o", messages=messages,
            response_format={"type": "json_object"},
            temperature=0,          # 结构化任务,温度调 0 求稳定
            max_tokens=512,
        )
        choice = resp.choices[0]
        # 截断是无法靠重试解决的错,直接抛出
        if choice.finish_reason == "length":
            raise ValueError("输出被截断,请调大 max_tokens 或拆分任务")
        raw = choice.message.content
        data = safe_parse(raw)
        errors = ["JSON 无法解析"] if data is None else validate(data, schema)
        if not errors:
            return data
        # 把错误反馈拼回去,带着批改意见重做
        messages.append({"role": "assistant", "content": raw})
        messages.append({"role": "user",
                          "content": f"输出有误:{'; '.join(errors)},请改正。"})
    raise ValueError("多次重试仍未得到合法的结构化输出")

这个 robust_extract,就是把六节内容收成的一条管线:few-shot 示范格式、JSON mode 约束语法、温度调 0 求稳、截断主动检测、容错解析、schema 校验、带错误反馈重试坑 5:别忘了给最终的兜底。哪怕这套管线已经很皮实,重试 N 次仍然失败的情况,在海量真实输入下依然会发生。所以调用 robust_extract 的地方,必须接住它最后抛出的异常,并想好兜底策略:是记一条日志、返回一个默认值,还是把这条输入转人工处理?一个健壮的系统,不是"假设永不失败",而是"想清楚失败了怎么办"。

关键概念速查

概念 / 手段 说明
结构化输出 让模型返回程序能可靠解析使用的结构化数据
JSON mode response_format 让 API 保证返回语法合法的 JSON
JSON mode 边界 只保证语法合法,不保证字段符合你的要求
容错解析 剥离 markdown 围栏、抠出 JSON 片段再解析
safe_parse 解析失败返回 None 而非抛异常,把决定权交出去
schema 校验 校验必填字段、类型、枚举值,能 parse 不等于对
带反馈重试 把上次输出和具体错误拼回对话,让模型照着改
few-shot 范例 给输入输出范例,模型模仿范例格式比读描述更稳
截断检测 finish_reason 为 length 说明被截断,重试无用
温度调 0 结构化任务求稳定可复现,不需要创意

避坑清单

  1. prompt 里说一句"返回 JSON"只是建议,不是保证,不能直接 json.loads。
  2. 开启 JSON mode 约束语法,但它只保证能解析,不保证字段对。
  3. 解析要容错:剥掉 markdown 围栏、从第一个花括号抠到最后一个。
  4. 解析失败返回 None 而非抛异常,把重试还是兜底的决定权交出去。
  5. 能 json.loads 不等于数据对,必须再用 schema 校验字段类型枚举。
  6. 重试别原样重发,要把上次输出和具体错误反馈给模型让它改。
  7. 重试必须设上限,别让搞不定的输入把模型无限调用下去。
  8. 与其用文字描述格式,不如直接给一两个输入输出的 few-shot 范例。
  9. finish_reason 为 length 是截断,重试无用,要调大 max_tokens。
  10. 枚举值要在 prompt 里写死并在校验时兜底,温度调 0 求稳定。

总结

回头看那串"json.loads 频繁崩、字段对不上、模型自作主张、JSON 被截断"的问题,以及我后来在结构化输出上接连踩的坑,最该记住的不是某一个 API 参数,而是我动手前那个想当然的判断——"结构化输出就是在 prompt 里说一句返回 JSON,然后 json.loads"。这句话错在它把大模型当成了一个"听话的程序"。我以为我下了一道"返回 JSON"的命令,模型就会像一个函数一样,精确地、确定地执行它。可大模型根本不是程序,它是一个概率生成器——它做的事,是"根据上文,猜下一个最可能的字"。我那句"返回 JSON",没有给它装上任何开关,只是让它"接下来吐 JSON"的概率高了一些。而"概率高",意味着"大多数时候是对的",也意味着"总有些时候是错的"——它会按概率包围栏、按概率夹解释、按概率漏字段。

所以做结构化输出,真正的工程量不在"在 prompt 里写一句返回 JSON"那一句上。那一句话,谁都会写。真正的工程量,在于你要承认"模型的输出天生不确定",并为这份不确定,搭起一整道纵深防线:它语法可能脏,你就用 JSON mode 从源头滤一遍;它仍可能带围栏,你就用容错解析把 JSON 抠出来;它能 parse 不代表数据对,你就用 schema 校验逐字段查;它错了,你就把错误反馈给它、让它带着批改意见重做;它可能被截断,你就finish_reason 主动识别。这篇文章的几节,其实就是顺着这条防线展开的:先想清楚"说一句返回 JSON"为什么不算结构化输出,再用 JSON mode 守住语法、用容错解析守住"抠得出 JSON"、用 schema 校验守住"字段是对的"、用带反馈重试守住"错了能改回来",最后是 few-shot、截断、枚举这几个把结构化输出做扎实的工程细节。

你会发现,跟大模型要结构化数据,和现实里"让一个很有才、但很随性的实习生帮你填一张表"完全相通。一个不会带这种实习生的人,会怎么做?他只丢一句"把信息填成表格给我",然后对结果不闻不问(这就是只说一句"返回 JSON")。实习生很有才,大多数时候填得不错,可他很随性:有时在表格边上画满了批注(这就是夹解释、加围栏);有时漏填了一栏(这就是缺字段);有时该填数字的地方填了一句话(这就是类型错);写得太长时纸不够、最后一行戛然而止(这就是截断)。而一个会带实习生的人怎么做?他先拿一张填好的样表给实习生看——"照这个样子填"(这就是 few-shot);他拿到表格后会逐栏核对,而不是看一眼就归档(这就是 schema 校验);发现哪栏错了,他不会把表撕了重来,而是指着那一栏说"这里填错了,这样改"(这就是带错误反馈的重试);他还会提前说清"性别这栏只能填这三个里的一个"(这就是枚举约束)。跟模型要结构化数据,成败从来不在于你那句指令下得多漂亮,而在于你认不认得清它"有才但随性"的本性,并为此把核对、反馈、重做这一整套流程建起来。

最后想说,结构化输出做没做对,差距永远不会在"本地测几条都通了"时暴露——本地你测的就那么几条、还都是你精心挑的、规规整整的输入,模型恰好都乖乖配合,你会觉得"说一句返回 JSON、json.loads 一下"已经是全部。它只在真实的、有海量五花八门输入、模型偶尔就要抽一次风的线上环境里才显形。那时候它会用最让人措手不及的方式给你结账:做不好,你会像我一样,被一串解析崩溃追着跑——后台日志里全是 JSONDecodeError;偶尔没崩的,字段又是错的,脏数据悄悄流进了下游;你查不出规律,因为模型这次这么抽风、下次那么抽风;而做了,你的提取流程会稳得让人踏实:模型偶尔的抽风,被 JSON mode 和容错解析悄悄抹平;字段错了,schema 校验当场拦下、重试自动修好;真正搞不定的极少数,也有兜底、有日志、转了人工。所以别等"满屏的解析异常"找上门,在你写下那句"返回 JSON"的那一刻就该想清楚:我面对的不是一个会精确执行命令的程序,而是一个有才却随性的概率生成器——它的语法、它的字段、它的长度、它的取值,这一道道防线,我是不是每一道都搭上了?这些问题有了答案,你拿到的才不只是一段"看起来像 JSON"的文本,而是真正可靠、可校验、能放心交给下游程序使用的结构化数据

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

JWT 鉴权完全指南:从一次"用户改了密码、旧 token 却还能畅通无阻登录三天"看懂无状态令牌

2026-5-22 0:35:47

技术教程

Redis 持久化完全指南:从一次"服务器一重启、Redis 里的数据就少了一大半"看懂 RDB 与 AOF

2026-5-22 0:48:19

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索