我让大模型返回 JSON,平时一直解析得好好的,直到某次它在 JSON 外面裹了一段解释文字,我的 JSON.parse 当场崩了、整个功能瘫痪的深度复盘

我在 Prompt 里要求大模型以 JSON 返回结果,拿到回复直接 JSON.parse,测了几十次都正常就上线了。可上线后功能时不时崩:某次模型没只返回纯 JSON,而是裹了客套话、套了 ```json 代码块、还跟了句结尾,我的 parse 当场崩溃。我以为明确要求了它就一定听话,深究才懂:LLM 是概率性生成自由文本,Prompt 里"要求返回 JSON"只是引导不是保证,它大概率听话但总有自由发挥的时候;几十次成功只是虚假的安全感。这篇从 LLM 输出是概率性而非确定的本质讲起,到用结构化输出(JSON mode/function calling)机制保证、健壮解析+schema 校验+重试降级的正解、幻觉/截断/注入等不可靠面,以及那句最戳心的——和概率系统打交道要做容错,别假设它听话,在不可靠的部件之上搭可靠的系统。

我让大模型返回 JSON,平时一直解析得好好的,直到某次它在 JSON 外面裹了一段解释文字,我的 JSON.parse 当场崩了、整个功能瘫痪的深度复盘

这是一个让我对"和概率系统打交道"刻骨铭心的故事。我做了一个用大模型的功能:在 Prompt 里,我让模型帮我做个分析,并要求它"以 JSON 格式返回结果";拿到模型的回复后,我直接 JSON.parse 一下,就把结构化的数据取出来用了。在我朴素的认知里,这没问题啊——我都在 Prompt 里明确要求它返回 JSON 了,它当然就会乖乖返回纯 JSON 嘛;测试了几十次,它也确实每次都规规矩矩地返回了 JSON,我就放心地上线了。

可上线后,功能时不时就崩一下:某些请求,会突然抛出 JSON.parse 的语法错误,整个功能瞬间瘫痪。我把那次模型的原始返回打印出来一看,哭笑不得:模型这次,没有只返回纯 JSON,而是在 JSON 的外面,裹了一段它自己的解释!它返回的是类似:"好的,根据您的需求,我分析的结果如下:```json {"result": ...} ``` 希望对您有帮助!"这样的东西——前面有一句客套话、JSON 被包在了 ```json 的 Markdown 代码块里、后面还跟了一句结尾。而我的 JSON.parse,拿到这一整坨"客套话 + 代码块标记 + JSON + 结尾",自然就解析失败、当场崩溃了。我当时百思不得其解:明明要求它只返回 JSON 了,它平时也都听话,怎么这次就突然"不听话"、加了一堆别的东西?这一次的崩溃,让我对大模型,有了一个清醒到近乎残酷的认识:问题的核心,在于我把大模型的输出,当成了一个"确定的、可靠的"结果,可它本质上,是一个概率性的、自由文本的生成。我在 Prompt 里"要求它返回 JSON",这不是一个能保证它一定返回纯 JSON 的"指令/契约",而仅仅是一个引导——它会大概率地、倾向于满足我的要求,但它从不"保证"!模型是基于概率生成下一个 token 的,在绝大多数情况下,它会生成符合我要求的纯 JSON;但总有那么一些情况(随机性、或某些输入触发),它会"自由发挥"——加上客套话、套上 Markdown 代码块、或者偶尔还会生成格式都不对的 JSON。我那几十次成功的测试,给了我一种虚假的安全感,让我误以为"它总会返回纯 JSON";而我那个脆弱的 JSON.parse,则建立在这个不靠谱的假设之上——它假设拿到的永远是干净的 JSON,所以一旦模型"自由发挥"了一次,它就立刻崩溃。我错把一个"概率系统"的输出,当成了一个"确定系统"的、可以无条件信任的返回值。

故障现场:把概率性的文本输出,当成可靠的 JSON

我把这个"JSON.parse 崩溃"的现场,用代码摊开给你看:

// ✗ 灾难: 把 LLM 的输出, 直接当成可靠的纯 JSON 来 parse

const prompt = `分析以下内容, 并以 JSON 格式返回结果: ${content}`;
const response = await llm.chat(prompt);   // 模型返回的是"自由文本"!

// 直接 parse —— 假设它一定是纯 JSON
const data = JSON.parse(response);          // ✗ 模型"自由发挥"时, 这里就崩!
//           ↑ 如果 response 不是纯 JSON, 直接抛 SyntaxError, 功能瘫痪

// 模型"听话"时, response 是: '{"result": "..."}'  → parse 成功
// 但模型"自由发挥"时, response 可能是:
//   '好的, 结果如下:\n```json\n{"result":"..."}\n```\n希望有帮助!'  ← 裹了客套+代码块
//   '{"result": "...",}'                    ← 多了个逗号, JSON 不合法
//   '抱歉, 我无法分析这个内容。'              ← 干脆没返回 JSON
//   '{"result": "他说\"你好\""}'             ← 引号转义出问题
//   → 这些, JSON.parse 全都会崩!

// 为什么会这样? 因为 LLM 是"概率性生成自由文本"的:
//   - Prompt 里"要求返回 JSON" = 引导, 不是保证。
//   - 它"大概率"听话, 但有随机性, 总会有不听话的时候。
//   - 几十次测试都成功 = 虚假的安全感, 不代表"永远"成功。

// 根因: 把"概率系统(LLM)的自由文本输出", 当成了
//   "确定系统的、可靠的结构化返回值", 直接信任、直接 parse。
//   → 一旦模型"自由发挥", 脆弱的 parse 就崩, 功能瘫痪。

看着这段代码,我才算真正理解了这次崩溃的深层原因。问题的核心,是我犯了一个认知上的根本错误:我把大模型的输出,当成了一个像传统函数返回值那样"确定的、可靠的"结果;可它本质上,是一个概率性的、自由文本的生成。这意味着:我在 Prompt 里"要求它返回 JSON",这根本不是一个能保证结果格式的"指令/契约"——它仅仅是一个"引导"。模型是基于概率,一个 token 一个 token 地生成文本的;在我的引导下,它会大概率地、倾向于生成符合要求的纯 JSON,但它从不"保证"这一点。所以,它的输出,充满了各种"不听话"的可能:裹上客套话和 Markdown 代码块(好的,结果如下:```json...```)、生成格式不合法的 JSON(多个逗号、引号转义出错)、或者干脆不返回 JSON(比如回一句"抱歉,我无法分析")——而这些,我那个直接 JSON.parse 的代码,全都接不住,全都会崩而我之所以会犯这个错,是因为那几十次成功的测试,给了我一种虚假的安全感:它们让我误以为"模型总会返回纯 JSON";于是,我那个脆弱的 JSON.parse,就建立在了这个不靠谱的假设之上——它假设自己拿到的永远是干净的 JSON,所以,模型只要"自由发挥"了哪怕一次,它就立刻崩溃。归根结底:我错把一个"概率系统"的输出,当成了一个"确定系统"的、可以无条件信任的返回值。传统的函数,你调用它,它保证按定义返回;可大模型,你"请求"它,它只是大概率地满足你——这两者之间的鸿沟,正是我那次崩溃的根源,也是和 AI 打交道时,一个必须时刻牢记的、根本性的区别。

第一件事:搞懂 LLM 输出是概率性的,不是确定的

定位到根源,我必须把"大模型输出的概率本质"这件事,彻底想清楚:

LLM 输出是"概率性的自由文本", 不是"确定的可靠返回值"

# 传统函数 vs 大模型, 一个根本区别:
#   - 传统函数: 确定性。给定输入, 按定义"保证"返回确定的、格式正确的结果。
#   - 大模型: 概率性。给定输入(Prompt), "大概率"生成你想要的, 但不保证。
#   → 一个是"契约", 一个是"引导"。

# LLM 怎么生成输出? 概率地预测"下一个 token":
#   - 它不是在"执行你的指令", 而是在"续写最可能的文本"。
#   - Prompt 里说"返回 JSON" → 提高了它生成 JSON 的概率, 但不是 100%。
#   - 有随机性(temperature)、有边界情况 → 总会有"不按你预期"的输出。

# 所以, LLM 的输出, 你不能假设它:
#   ✗ 一定是合法的 JSON / 指定的格式
#   ✗ 一定不包含多余的解释/客套/代码块标记
#   ✗ 一定符合你要求的 schema / 字段
#   ✗ 内容一定正确(还有"幻觉"问题, 另说)

# "测试几十次都对" ≠ "永远都对":
#   - 概率系统, 小样本测试看起来稳定, 不代表覆盖了所有情况。
#   - 上线后, 海量、多样的真实输入, 一定会触发那些"小概率的不听话"。

# 核心认知: 和 LLM 打交道, 是和一个"概率系统"打交道。
#   - 它的输出是"建议", 不是"保证"。
#   - 你必须假设它"会输出不符合预期的东西", 并为此做好"容错"。
#   → 别把概率系统的输出, 当成确定系统的返回值, 直接信任。

想清楚之后,我对"和大模型打交道"这件事,有了一个根本性的、清醒的认识。传统函数和大模型之间,有一个根本的区别:传统函数是确定性的——给定输入,它按定义保证返回确定的、格式正确的结果;而大模型是概率性的——给定 Prompt,它只是大概率地生成你想要的,但不保证。一个是"契约",一个是"引导"。这要从大模型的工作方式说起:它生成输出,是在概率地预测"下一个 token"——它不是在"执行你的指令",而是在"续写最可能的文本";你 Prompt 里说"返回 JSON",只是提高了它生成 JSON 的概率,而不是把这个概率变成了 100%;再加上它本身的随机性(temperature)和各种边界情况,总会有"不按你预期"的输出冒出来。所以,对于 LLM 的输出,你绝不能假设它:一定是合法的 JSON/指定格式、一定不含多余的解释和代码块标记、一定符合你要求的 schema、内容一定正确(这还没算"幻觉"问题)。而那句"测试几十次都对",也绝不等于"永远都对":概率系统,在小样本测试下看起来很稳定,但这不代表它覆盖了所有情况;一旦上线,面对海量、多样的真实输入,一定会触发那些"小概率的不听话"。我那几十次成功的测试,恰恰是给了我最危险的、虚假的信心。由此,我得出了一个和 AI 打交道时、最核心的认知:和 LLM 打交道,本质上,是和一个"概率系统"打交道——它的输出,是"建议",而不是"保证";你必须假设它"会输出不符合预期的东西",并为此,提前做好"容错"。千万别把一个概率系统的输出,当成一个确定系统的返回值,去直接信任——这,是我用一次 JSON.parse 的崩溃,补上的、关于 AI 工程最根本的一课。

第二件事:正解——用结构化输出,并对输出做容错

搞懂了根因——"把概率性输出当可靠 JSON"——正解就清晰了:第一选择,是用模型提供的"结构化输出"能力(JSON mode / function calling / tool use)——它能从机制上保证返回合法的、符合 schema 的 JSON;此外,无论如何,都要对模型的输出,做健壮的解析 + 校验 + 失败重试/降级

// 正解1(首选): 用模型的"结构化输出"能力, 从机制上保证 JSON 合法
// OpenAI: response_format / function calling; Claude: tool use; 都能强制结构化
const response = await llm.chat({
    messages: [...],
    response_format: {        // ✓ JSON mode / 结构化输出
        type: "json_schema",
        json_schema: { /* 定义你要的 schema */ }
    }
});
// → 模型被约束着, 必须返回"合法的、符合 schema"的 JSON, 不会再裹客套/代码块。
const data = JSON.parse(response);   // 此时才相对可靠

// 正解2: 没有结构化输出时, 健壮地"提取 + 解析 + 校验"
function parseRobustly(text) {
    // a. 先尝试从 markdown 代码块里抽 JSON
    const match = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
    const jsonStr = match ? match[1] : text.trim();
    // b. 容错解析
    let obj;
    try {
        obj = JSON.parse(jsonStr);
    } catch (e) {
        throw new ParseError("模型返回的不是合法 JSON: " + text);  // 别让它裸崩
    }
    // c. 校验 schema(用 zod 等), 确认字段齐全、类型对
    return MySchema.parse(obj);   // 不符合 schema 也抛错
}

// 正解3: 解析/校验失败时, 重试 或 降级
async function getResult(content) {
    for (let i = 0; i < 2; i++) {        // 有限重试(模型有随机性, 重试可能就对了)
        try {
            const resp = await llm.chat(buildPrompt(content));
            return parseRobustly(resp);
        } catch (e) {
            log.warn(`第 ${i+1} 次解析失败, 重试`, e);
        }
    }
    return fallbackResult();             // 多次失败 → 降级, 给个兜底, 别让功能瘫痪
}

// 核心: 优先用结构化输出(机制保证); 拿到输出后, 当"不可信"的,
//   健壮解析 + schema 校验 + 失败重试/降级。绝不裸 parse、裸信任。

这套正解,核心是"能让机制保证就让机制保证,机制保证不了的,就靠容错兜住"。正解1(首选:用结构化输出):现代的大模型,大多提供了"结构化输出"的能力——OpenAI 的 response_format/function calling、Claude 的 tool use 等——它们能从机制上约束模型,让它必须返回"合法的、符合你定义的 schema"的 JSON,而不会再裹客套话、套代码块;这是从根上解决问题的最佳方案,能用就一定要用。正解2(健壮解析):如果用不了结构化输出,那就要把模型的输出,当"不可信"的,做健壮的处理——先尝试从 Markdown 代码块里把 JSON 抽出来、再容错地解析(try/catch 包住,别让它裸崩)、最后用 schema(如 zod)校验字段是否齐全、类型是否正确。正解3(重试/降级):解析或校验失败时,可以有限重试(模型有随机性,重新请求一次,很可能这次就对了);如果多次都失败,就降级,给一个兜底的结果,别让整个功能瘫痪归根结底:优先用结构化输出(让机制来保证),拿到输出后,把它当"不可信"的——做健壮解析 + schema 校验 + 失败重试/降级;绝不要像我那次一样,裸 parse、裸信任。我那次的错误,正是对一个概率系统的输出,毫无防备地、直接信任并解析;而正解,就是用机制去约束它、用容错去兜住它。

下面这张图,对比了"裸信任 LLM 输出"和"结构化+容错"两条路径:

这张图的对比很清楚:左边红色那条,裸 JSON.parse 直接信任,假设输出一定是纯 JSON,模型听话时碰巧成功、一旦自由发挥(裹客套/代码块/格式错)就崩溃、功能瘫痪;右边绿色那条,用结构化输出从机制上保证 JSON 合法,再加健壮解析 + schema 校验,成功就拿到可靠数据、失败就重试或降级兜底、绝不瘫痪。两条路的根本分野,在于你有没有把那个概率性的输出,当成"不可信"的来对待。

第三件事:还有哪些"把 LLM 输出当可靠"的坑

填平了 JSON 这个坑,我系统排查了一遍:还有哪些地方,我也错误地"把大模型的输出,当成了可靠的东西":

其它"把 LLM 输出当可靠"的坑:

# 1. 格式不可靠(本文): 以为返回纯 JSON, 实则裹客套/代码块/格式错。
#    → 结构化输出 + 健壮解析 + 校验。

# 2. 内容不可靠(幻觉 hallucination): 模型会"一本正经地胡说八道"。
#    - 编造不存在的事实、API、引用、数字。
#    → 关键信息要核实/给来源(RAG)/让用户可验证, 别盲信生成内容。

# 3. 长度不可靠: 输出可能被 max_tokens 截断 → JSON 截一半、内容不完整。
#    → 设足够的 max_tokens; 检测是否被截断(finish_reason)。

# 4. 稳定性不可靠: 同样的输入, 多次调用结果可能不同(temperature)。
#    → 要稳定/可复现, 把 temperature 调低(甚至 0); 但也别期望 100% 一致。

# 5. 指令遵循不可靠: 复杂的、多条的指令, 模型可能漏掉某几条。
#    → 指令要清晰、可拆解; 关键约束用结构化/校验来兜底, 别只靠 Prompt 说。

# 6. 注入风险: 用户输入可能"劫持"你的 Prompt(prompt injection)。
#    → 别盲目信任拼进 Prompt 的用户输入; 输出也要警惕被注入操纵。

# 共同点: 都源于"把一个概率系统的输出, 当成了确定可靠的"。
# 原则: LLM 的一切输出(格式、内容、长度、稳定性), 都"假设它不可靠",
#   用"机制约束 + 校验 + 重试 + 降级 + 人工核实"去构建可靠的系统。

这一排查,让我对"LLM 输出的不可靠性"有了全面的认识。除了格式不可靠(本文),还有一连串的"不可靠":内容不可靠(幻觉)——模型会"一本正经地胡说八道",编造不存在的事实、API、引用、数字;关键信息要核实、给来源(RAG)、或让用户能验证,别盲信生成的内容。长度不可靠——输出可能被 max_tokens 截断,导致 JSON 截了一半、内容不完整;要设足够的 max_tokens、并检测是否被截断(看 finish_reason)。稳定性不可靠——同样的输入,多次调用结果可能不同;要稳定就把 temperature 调低(甚至 0),但也别期望 100% 一致。指令遵循不可靠——复杂的、多条的指令,模型可能漏掉某几条;指令要清晰可拆解,关键约束用结构化/校验来兜底,别只靠 Prompt 嘴上说。注入风险——用户输入可能"劫持"你的 Prompt(prompt injection),别盲目信任拼进 Prompt 的用户输入,输出也要警惕被操纵。这些坑的共同点,都源于"把一个概率系统的输出,当成了确定可靠的"。所以,核心原则就一条:LLM 的一切输出(格式、内容、长度、稳定性),都要"假设它不可靠";然后,用"机制约束 + 校验 + 重试 + 降级 + 人工核实"这一整套手段,去在一个不可靠的概率部件之上,构建出一个可靠的系统。这,才是 AI 工程的真正功夫所在。

第四件事:在不可靠的 LLM 之上,搭一个可靠的系统

这次踩坑,让我系统地思考了一个更大的命题:既然 LLM 本身是不可靠的概率部件,那怎么用它,搭出一个对用户可靠的系统?我把这套"稳健集成"的方法,梳理了出来:

如何在"不可靠的 LLM"之上, 搭一个"可靠的系统":

# 把 LLM 当成一个"不可靠的外部部件"来对待(类似不可靠的网络依赖)。
# 围绕它, 建立几道防线:

# 防线1: 约束输入/输出的"格式" —— 让结果更可控
#   - 输出: 用结构化输出(JSON mode/function calling), 机制保证格式。
#   - 输入: Prompt 清晰、给例子(few-shot)、明确约束, 提高"听话率"。

# 防线2: 校验(validate) —— 不信任, 先验证
#   - 拿到输出, 用 schema 校验格式、用规则校验内容合理性。
#   - 校验不过 = 这次输出不可用, 进入重试/降级。

# 防线3: 重试(retry) —— 利用概率性, 再试一次可能就对了
#   - LLM 有随机性, 失败重试(可微调 Prompt/温度), 往往能成功。
#   - 但要有次数上限, 别无限重试烧钱。

# 防线4: 降级(fallback) —— 实在不行, 给个兜底
#   - 多次失败 → 返回默认值/规则引擎结果/友好提示, 别让功能瘫痪。

# 防线5: 人在回路 / 可核实 —— 高风险场景, 别让 AI 单独拍板
#   - 关键决策, 让 AI"建议"、人来"确认"。
#   - 生成的内容, 给出处/让用户能验证(对治幻觉)。

# 防线6: 观测 —— 看清 LLM 在实际中的表现
#   - 记录: 输入、输出、解析成功率、重试率、降级率、耗时、成本。
#   - 发现"某类输入老是解析失败/胡说" → 针对性优化 Prompt/流程。

# 核心心智: LLM 是个"能力强但不可靠"的部件。
#   工程师的活, 是用这些防线, 把它"不可靠的输出", 包装成
#   "对用户可靠的功能"。可靠性, 来自你围绕它建的工程, 而非 LLM 自己。

这一梳理,让我对"AI 工程"的本质,有了更深的理解。核心,是把 LLM,当成一个"能力强、但不可靠的外部部件"来对待(就像对待一个不可靠的网络依赖);然后,围绕它,建立起好几道防线:防线1(约束格式):输出用结构化输出(机制保证格式),输入则把 Prompt 写清晰、给例子、明确约束(提高"听话率")。防线2(校验):拿到输出,先不信任,用 schema 校验格式、用规则校验内容的合理性,校验不过就进入重试/降级。防线3(重试):利用 LLM 的随机性,失败时重试一次(可微调 Prompt 或温度),往往就能成功;但要有次数上限,别无限重试烧钱。防线4(降级):多次失败,就返回默认值、规则引擎的结果、或友好提示,别让功能瘫痪防线5(人在回路/可核实):高风险场景,别让 AI 单独拍板——关键决策让 AI"建议"、人来"确认",生成的内容给出处、让用户能验证(对治幻觉)。防线6(观测):记录输入、输出、解析成功率、重试率、降级率、耗时、成本,一旦发现"某类输入老是解析失败/胡说",就针对性地优化。归根结底,这背后,是一个 AI 工程的核心心智:LLM 是一个"能力强但不可靠"的部件;而工程师的活,正是用这一道道防线,把它"不可靠的输出",包装成一个"对用户可靠的功能"。系统的可靠性,来自你围绕它建立的工程,而不是来自 LLM 自己。把"裸用 LLM"和"工程化集成 LLM"对比成一张表:

维度 裸用 LLM(踩坑) 工程化集成(可靠)
对输出 直接信任、直接用 当不可信,先校验
格式 靠 Prompt 嘴上要求 结构化输出机制保证
失败 直接崩/瘫痪 重试 + 降级兜底
高风险决策 AI 单独拍板 人在回路/可核实
可靠性来自 指望 LLM 自己靠谱 围绕它建的工程防线

第五件事:和"概率系统"打交道,要做容错而非假设它听话

这次踩坑,在认知层面给了我最大的纠偏——它让我建立起了"和不确定系统打交道"的正确姿势。我把这层反思,沉淀了下来:

认知纠偏: 和"概率/不确定系统"打交道, 要容错, 别假设它听话

# 我的误解(错误的):
#   我用"和确定系统打交道"的习惯, 去用 LLM——
#   假设"我要求它返回 JSON, 它就一定返回纯 JSON", 然后直接信任。
#   → 我把一个"不确定系统", 当成"确定系统"来用了。

# 真相: 确定系统和不确定系统, 要用完全不同的姿势去对待
#   - 确定系统(传统函数/API): 有契约, 给定输入保证确定输出 → 可以信任。
#   - 不确定系统(LLM/网络/外部依赖/用户输入): 行为有随机性/会出意外
#     → 不能信任, 必须"假设它会出问题"并容错。

# 这是一个更普遍的工程原则: 对"不确定的东西", 永远做防御
#   - LLM 输出: 假设它格式/内容会出错 → 校验、重试、降级。
#   - 网络调用: 假设它会超时/失败 → 超时、熔断、重试。
#   - 用户输入: 假设它会乱来/恶意 → 校验、过滤、防注入。
#   - 外部数据: 假设它不符合预期 → 运行时校验。
#   → 凡是"你控制不了其行为"的东西, 都要当"不可靠的"来防御。

# 心态转变:
#   ✗ 乐观: "它应该会按我想的来吧" → 直接信任 → 一出意外就崩。
#   ✓ 防御: "它随时可能不按我想的来" → 容错设计 → 出意外也稳。

# 而 LLM, 是一类"全新的、概率性的"不确定系统, 尤其需要这种容错心态——
#   它能力越强, 我们越容易"高估"它的可靠性, 越容易栽在"假设它听话"上。

核心: 和概率/不确定系统打交道, 要做容错, 别假设它听话。
  把它的输出当"建议", 用工程的防线, 兜住它的不确定性。

这层反思,是这次踩坑给我最高维度的收获。复盘我的误解,根源是:我用"和确定系统打交道"的习惯,去用了 LLM——我假设"我要求它返回 JSON,它就一定返回纯 JSON",然后直接信任。我,把一个"不确定系统",当成"确定系统"来用了而真相是:确定系统和不确定系统,要用完全不同的姿势去对待:确定系统(传统函数/API)有契约,给定输入保证确定输出,所以可以信任;而不确定系统(LLM、网络、外部依赖、用户输入)行为有随机性、会出意外,所以不能信任,必须"假设它会出问题"并做容错而这,其实是一个更普遍的工程原则——对"不确定的东西",永远做防御:LLM 输出,假设它格式/内容会出错 → 校验、重试、降级;网络调用,假设它会超时/失败 → 超时、熔断、重试;用户输入,假设它会乱来/恶意 → 校验、过滤、防注入;外部数据,假设它不符合预期 → 运行时校验——凡是"你控制不了其行为"的东西,都要当"不可靠的"来防御这,本质上是一种心态的转变:乐观("它应该会按我想的来吧"→ 直接信任 → 一出意外就崩),转向防御("它随时可能不按我想的来"→ 容错设计 → 出意外也稳)。而 LLM,作为一类"全新的、概率性的"不确定系统,尤其需要这种容错心态:恰恰因为它能力很强,我们才越容易高估它的可靠性、越容易栽在"假设它听话"这个坑上——我那次的崩溃,正是被它平时的"听话",给麻痹了。归根结底:和概率/不确定系统打交道,要做容错,而不是假设它听话。把它的输出,当成"建议",然后,用工程的防线,去兜住它的不确定性——这,是 AI 时代,每一个工程师都必须修炼的、新的基本功。把"假设它听话"和"做容错"两种心态对比成一张表:

维度 假设它听话(踩坑) 做容错(成熟)
对 LLM 输出 当确定可靠的返回值 当不确定的建议
处理方式 直接信任、直接用 校验+重试+降级
对不确定的东西 乐观地信任 防御性地容错
能力强的系统 更容易高估其可靠 越强越要警惕
出意外时 崩溃/瘫痪 仍然稳得住

一套"用 LLM 输出该怎么处理"的决策流程

把这次踩坑的全部教训,我浓缩成了一张"拿到大模型的输出、该怎么安全地处理"的决策图,贴在了团队做 AI 应用的文档里:

这张图,把我"血泪换来"的整套方法论,串成了一条可执行的路径:拿到 LLM 输出,先看需不需要结构化数据——需要就看模型支不支持结构化输出:支持就用 JSON mode/function calling 机制保证,不支持就健壮提取(抽代码块 + try/catch 解析);拿到后用 schema 校验字段和类型,通过才安全使用,不通过就有限重试、重试还不行就降级兜底。而如果是自由文本、又是高风险内容,则要人工核实、给出处,别盲信幻觉。这条"结构化优先、处处校验、失败兜底"的处理链,现在是我们团队处理每一个 LLM 输出时的准则。

我立下的几条 LLM 集成规矩

这次"JSON.parse 崩溃"的踩坑,让我把集成大模型的注意事项,认真地立成了几条规矩:

  1. 需要结构化数据,优先用结构化输出。JSON mode/function calling/tool use,从机制上保证格式合法,别只靠 Prompt 嘴上要求。
  2. LLM 输出一律当"不可信"。绝不裸 JSON.parse、裸信任;健壮提取 + schema 校验。
  3. 解析/校验失败要重试 + 降级。利用随机性重试(有上限),多次失败给兜底,别让功能瘫痪。
  4. 警惕幻觉。内容也不可靠,关键信息要核实、给出处、可验证,高风险决策人在回路。
  5. 注意长度、稳定性、注入。防截断(够的 max_tokens)、要稳定调低 temperature、防 prompt injection。
  6. 观测 LLM 的实际表现。记录解析成功率、重试率、降级率,针对性优化。
  7. 和概率系统打交道要容错。它的输出是建议不是保证;别假设它听话,用工程防线兜住不确定性。

写在最后

这次"我让模型返回 JSON、它却裹了段解释文字、JSON.parse 当场崩"的经历,是我在 AI 应用开发路上,一次很典型、也很受用的成长。它教给我的,远不止"用结构化输出"这一条具体的技术经验,更是一个进入 AI 时代必须建立的根本认知——和大模型打交道,是和一个"概率系统"打交道;它的输出是"建议",不是"保证"。我那次的崩溃,根源就在于,我用"和确定系统打交道"的老习惯,去对待一个本质上不确定的概率系统:我以为"要求它返回 JSON,它就一定会",然后毫无防备地直接信任——而它平时的"听话",恰恰麻痹了我,让我忘了它随时可能"自由发挥"。

所以,当你在系统里集成大模型、或任何一个"不确定的部件"时,请别用"它应该会听话吧"的乐观去信任它,而要用"它随时可能不听话"的防御去对待它:把它的输出,当成不可信的建议,然后用机制约束、校验、重试、降级、人工核实这一整套工程防线,去兜住它的不确定性就像那个返回 JSON 的模型,你只要用上结构化输出、再加一层健壮的解析和校验,就绝不会再被它某一次的"自由发挥",轻易击垮。从"假设它听话"到"为它的不确定性做容错",从"裸用 LLM"到"在不可靠的部件之上,搭建可靠的系统",是从一个"会调大模型 API"的人,走向一个"能交付可靠 AI 产品"的工程师,必经的修炼。愿你构建的每一个 AI 功能,都既借得了大模型的强大,又兜得住它的不确定;也愿你我,在和这个全新的、概率性的智能打交道时,永远怀着一份清醒的、防御的智慧。共勉。

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

我每次发布服务监控就报一批 5xx,我一直以为是发布时正常的网络抖动,最后发现是 Pod 被杀时根本没做优雅停机、正在处理的请求被硬生生掐断的深度复盘

2026-6-1 23:14:34

技术教程

我的下单接口被同一个请求重复调用,结果生成了两笔一模一样的订单,我一开始以为是前端 bug,最后才明白重试在分布式里根本无法避免、而我没做幂等的深度复盘

2026-6-1 23:27:37

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