导读:面试官问“如何应对 JSON 格式幻觉”,真正考的不是
json.loads(),而是你能不能让模型输出在进入生产链路前,被一层层拦住。读完本篇你会拿到 30 秒与 90 秒两套回答,也能带走一套能写进 Agent 系统的防线。
你做过 Agent。面试官问:
“模型输出不是预期 JSON,你怎么处理?”
大多数人会先答:
“我会在 Prompt 里要求它只返回 JSON,解析失败就重试。”
这句话不算错。
但面试官如果接着问:
“JSON 解析成功了,却把普通告警判成 P1;它建议回滚一个不存在的发布版本;重试两次还是错,你让它继续猜吗?”
这时如果只会说“再优化 Prompt”,基本就暴露了:你做的是会说话的 Demo,不是能进生产的 Agent。
能解析 JSON,只说明模型交了答题卡;不说明它答对了题。
一、先让你答错一次:一段 JSON,差点让服务回滚
小明给团队做了一个值班 Agent。
它会读取监控摘要、最近一次发布记录和错误日志,然后输出一张“故障处置建议卡”,供告警平台决定是通知值班同学,还是调用受控的回滚工具。
下游最早的代码很朴素:
import jsondef handle_incident_response(response_str: str) -> None: data = json.loads(response_str) # alert_platform 换成你自己告警平台的客户端,这里仅示意下游怎么消费这三个字段 alert_platform.create_ticket( service=data["service"], severity=data["severity"], action=data["recommended_action"], )测试时,模型每次都返回:
{ "service": "checkout-api", "severity": "P2", "recommended_action": "escalate"}跑得很顺。直到小明一度觉得:结构化输出嘛,一行json.loads()的事。
然后,值班环境给他上了一课。
1.1 第一种翻车:连 JSON 都不是
模型先解释了两句,再给 JSON:
根据最近 5 分钟的错误率,我建议优先观察:```json{"service": "checkout-api", "severity": "P2", "recommended_action": "observe"}```也可能是模型输出被截断:
{"service": "checkout-api", "severity":这类问题会让json.loads()抛异常。它刺耳,但不算最可怕——至少你立刻知道系统坏了。
1.2 第二种翻车:JSON 合法,接口契约却变了
有一次模型返回:
{ "target_service": "checkout-api", "priority": "P2", "action": "escalate"}括号、引号、逗号都很完美。
可下游要的是service、severity、recommended_action。于是有的框架抛KeyError,有的框架把缺失字段吞成None,最后出现一张“未知服务、未知等级”的工单。
1.3 第三种翻车:最危险——格式完美,判断却错了
{ "service": "checkout-api", "severity": "P1", "recommended_action": "rollback", "change_id": "deploy-2026-09-04-999"}它是合法 JSON;字段名也对;rollback甚至是系统允许的动作。
但真实监控只是单个机房短暂抖动,当前没有正在生效的发布变更,change_id也是模型自己编的。
如果这段 JSON 直接进入工具执行链路,问题就从“模型答错了”升级为“系统替模型做错了事”。
生产里最危险的幻觉,不是让程序报错,而是让程序平静地执行错误。
这就是这道面试题的核心。你要解决的从来不是“怎样把文本切成 JSON”,而是:怎样把不可信的模型建议,变成可验证、可拒绝、可审计的工程输入。
二、这题真正考什么:不是格式,是信任边界
面试官问 JSON 幻觉,通常在考三件事。
| 考察维度 | 他想听什么 | 容易失分的回答 | 有生产味的回答 |
| 机制边界 | Prompt、Json Mode、Structured Outputs 各管什么 | “让模型严格返回json” | “先区分语法、结构、语义和执行风险” |
| 数据契约 | 字段、枚举、范围、来源由谁裁决 | “能parse就能用” | “Schema 定型,业务代码验证事实” |
| 故障治理 | 出错后如何止损、恢复、追踪 | “失败就无限重试” | “分类、限额 重试、降级、挂起、审计” |
换句话说,面试官在确认:
你把 LLM 当作可信服务,还是当作一个必须验收的外部依赖?
我的答案很明确:模型可以很聪明,但不能因此跳过验收。
三、先给你 30 秒回答版本
时间紧时,可以直接这样答:
“我不会只靠 Prompt 要求模型返回 JSON。生成侧会优先使用 Structured Outputs 或严格的 Tool Calling Schema,把字段、类型、枚举和额外字段约束住,先降低语法和结构错误。服务端仍然会用 Pydantic 或 JSON Schema 再校验一次,并用数据库、权限和状态机验证业务事实,因为格式合法不代表内容真实。失败时我会区分拒答、截断、Schema 失败、业务校验失败和下游故障:只有可修复的格式或结构问题才做一到两次定向重试;业务事实错误不能让模型猜。高风险工具调用还要经过确认、幂等和审计,模型只能提议,最终执行权在服务端。”
这段回答里有五个关键词:原生结构化输出、服务端校验、业务事实、有限重试、执行隔离。
如果你能把它们串成因果关系,而不是背成名词列表,这题就稳了一半。
四、第一道门:别把 Prompt 当成约束
4.1 三档机制,到底差在哪
很多文章把“让模型返回 JSON”的办法混在一起讲。其实它们的防线建在不同位置。要分清一个方案,就回答三个问题:
它是干啥的?怎么用?优点和缺点各是什么?
先看一张总览表:
| 方案 | 是干啥的 | 怎么用 | 优点 | 缺点 |
| Prompt约束 | 在提示词里“口头要求”返回JSON | 直接改提示词文本 | 零成本、所有模型通用 | 只是概率,随时可能失效 |
| JSON Mode | 打开“只输出合法JSON的开关” | response_format={“type”:“json_object”} | 专治括号没闭合、混进解释中文 | 不管字段名和内容对不对 |
| Structured Outputs | 用JSON Schema 在生成阶段锁死结构 | response_format 传json_schema | 字段、类型、枚举都锁死 | 只保证“长得对”,不保证“是真的” |
| Tool Calling | 把操作定义成受约束的工具结果 | 传tools 参数 | 参数严格遵循工具契约 | 不保证调用被授权、后果安全 |
下面逐个展开,先看最原始的一种。
Prompt 像这样:
请只返回 JSON,禁止输出解释文字。优点:不用改任何代码,所有模型都吃这一套,写起来最省事。
缺点:它只是“提高概率”,不是“保证”。上下文变长、用户输入夹带干扰、模型换版本、温度升高,都可能让模型“不小心话多了”。
用 Prompt 守生产协议,就像在机房门口贴纸:
未经允许,请不要进入。礼貌有了。
门禁没有。
4.2 JSON Mode:箱子能打开,不代表里面装对了
JSON Mode 是各家 API 提供的一个输出开关,意思是“你只给我 JSON,别的话少说”。
怎么用(以 OpenAI 为例,其他家参数名略有不同):
import jsonfrom openai import OpenAIclient = OpenAI()resp = client.chat.completions.create( model="gpt-5", messages=[ # 小坑:OpenAI 要求提示词里必须出现 "JSON" 这个词,否则会报错 {"role": "system", "content": "把结果以 JSON 格式返回。"}, {"role": "user", "content": "当前 checkout-api 状态如何?"}, ], response_format={"type": "json_object"}, # 关键:打开 JSON Mode)print(json.loads(resp.choices[0].message.content))开了它,你就更有把握执行:
json.loads(response)优点:专治“没闭合大括号”“混进解释文字”这类语法病,能放心json.loads()。
缺点:它只承诺“是合法 JSON”,不承诺“内容符合你的接口”。
你期待:
{ "service": "checkout-api", "severity": "P2"}它仍可能给你:
{ "system": "checkout-api", "level": "严重", "note": "建议尽快关注"}这也是合法 JSON。
你的下游不认识它。
4.3 Structured Outputs:把 Schema 变成生成边界
Structured Outputs 的关键不是“模型更听话”,而是平台在生成阶段根据 JSON Schema 施加约束。不同平台的实现与支持子集并不完全相同,常见做法是 grammar 或约束解码:当前只允许生成符合 Schema 的 token。
怎么用:先把你想要的字段结构写成 JSON Schema(这份“图纸”长这样),再塞进response_format(以 OpenAI 为例):
from openai import OpenAIclient = OpenAI()# 故障处置建议的"图纸":字段、类型、枚举都在这里定死incident_schema = { "type": "object", "additionalProperties": False, "required": ["service", "severity", "recommended_action"], "properties": { "service": {"type": "string", "enum": ["checkout-api", "search-api", "profile-api"]}, "severity": {"type": "string", "enum": ["P1", "P2", "P3", "none"]}, "recommended_action": {"type": "string", "enum": ["rollback", "restart", "escalate", "observe"]}, },}resp = client.chat.completions.create( model="gpt-5", messages=[{"role": "system", "content": "根据监控信息输出故障处置建议。"}], response_format={ "type": "json_schema", "json_schema": { "name": "incident_decision", "strict": True, # 严格模式:禁止出现 Schema 之外的字段 "schema": incident_schema, }, },)print(resp.choices[0].message.content)在严格模式下,模型不应把service偷换成target_service,也不该临时增加一个run_shell_command字段。
优点:字段、类型、枚举、额外字段,都被约束在生成阶段,结构错误大幅下降。
缺点:它只保证数据“长得对”,不保证“是真的”。
但注意这个转折:
Structured Outputs 能让数据“长得对”,不能让数据“是真的”。
模型仍可能在允许的枚举中,错选P1;也可能选对rollback,却给出一个不存在的change_id。
这也是为什么我不喜欢把它宣传成“彻底解决 JSON 幻觉”。它解决的是协议层的一大块麻烦,不是生产正确性的全部。
五、第二道门:Schema 之后,谁来验证事实
5.1 一句话定义
生产级结构化输出,不是让模型生成一段漂亮 JSON,而是让每个字段都接受与其风险匹配的验收。
可以把 JSON Schema 想成故障工单的模板:服务名写在哪、等级能选什么、动作有哪几种。
Pydantic、监控系统、发布平台和权限系统,则像真正的事故指挥台:告警是否存在?服务是否真的异常?有没有可回滚的版本?当前账号有没有执行资格?
工单写得工整,不等于故障真的发生过。
5.2 三层验收链路
模型响应状态与 JSON 语法 ↓应用 Schema:字段、类型、枚举、范围、跨字段关系 ↓可信业务系统:监控事实、发布记录、权限、状态机 ↓受控工具执行:确认、幂等、审计| 验收层 | 拦截什么 | 例子 |
| 响应与语法 | 非JSON、截断、拒答、空输出 | 少右括号、输出长度耗尽 |
| 应用Schema | 缺字段、多字段、非法枚举、错误范围 | severity=“critical”、多出危险参数 |
| 业务事实 | 模型编造的ID、错误判断、越权状态 | 不存在的发布版本、普通波动被判P1 |
| 执行控制 | 重复调用、未确认的高风险操作 | 重复回滚、无授权重启服务 |
5.3 一段能直接运行的最小示例
下面不绑定任何模型厂商 API,只演示最重要的边界:模型候选输出通过应用 Schema 后,仍要再做可信事实校验。
先安装依赖:
pip install "pydantic>=2.0"这段代码做五件事:
- 限制服务、故障等级与动作的可选范围;
- 规定
rollback必须同时具备 P1 等级和发布版本号;
- 规定
- 模拟模型第一次返回合法 JSON、但违反契约;
- 将一条精确错误摘要交给下一轮修复;
- 通过 Schema 后,再检查发布记录是否真实存在。
import jsonfrom typing import Literalfrom pydantic import BaseModel, ConfigDict, ValidationError, model_validatorclass StructuredOutputFailed(Exception): """模型在限定修复次数内仍无法满足输出契约。"""class IncidentDecision(BaseModel): # 禁止模型自行增加未定义字段 model_config = ConfigDict(extra="forbid") service: Literal["checkout-api", "search-api", "profile-api"] severity: Literal["P1", "P2", "P3", "none"] recommended_action: Literal["rollback", "restart", "escalate", "observe"] change_id: str | None = None @model_validator(mode="after") def validate_action_contract(self): # 回滚是高风险动作:必须由最高等级告警触发,且必须指定发布版本 if self.recommended_action == "rollback": if self.severity != "P1" or not self.change_id: raise ValueError("rollback 必须同时具备 P1 severity 与 change_id") # 没有故障时只能观察,不能让模型顺手重启服务 if self.severity == "none" and self.recommended_action != "observe": raise ValueError("severity 为 none 时,recommended_action 必须为 observe") return selfdef summarize_error(error: Exception) -> str: """只保留首条关键错误,避免把整段堆栈灌回模型上下文。""" if isinstance(error, ValidationError): first = error.errors()[0] location = ".".join(str(part) for part in first["loc"]) or "对象" return f"字段 {location} 校验失败:{first['msg']};实际输入为 {first.get('input')!r}" return f"JSON 解析失败:{error}"def deployment_exists(service: str, change_id: str | None) -> bool: """真实项目中应查询发布平台;此处用可信记录模拟。""" active_changes = { "checkout-api": {"deploy-2026-09-04-042"}, "search-api": set(), "profile-api": {"deploy-2026-09-04-017"}, } return change_id in active_changes[service]def validate_candidates(candidate_outputs: list[str]) -> IncidentDecision: max_repairs = 1 last_error = "" for attempt, raw in enumerate(candidate_outputs): if attempt > max_repairs: break try: payload = json.loads(raw) decision = IncidentDecision.model_validate(payload) # 这一层不是模型能证明的:必须到发布平台查真实记录 if decision.recommended_action == "rollback": if not deployment_exists(decision.service, decision.change_id): raise StructuredOutputFailed("change_id 不存在或不属于目标服务,阻断回滚") return decision except (json.JSONDecodeError, ValidationError) as error: last_error = summarize_error(error) print(f"第 {attempt + 1} 次校验失败:{last_error}") # 真实接 LLM 时:仅回传 last_error,让模型按原 Schema 定向修复。 raise StructuredOutputFailed( f"结构化输出在 {max_repairs + 1} 次尝试后仍失败:{last_error}" )if __name__ == "__main__": candidates = [ # JSON 合法,但 severity 不在契约允许的枚举中 '{"service": "checkout-api", "severity": "critical", "recommended_action": "rollback", "change_id": "deploy-2026-09-04-042"}', # 模拟模型根据错误摘要修复后的输出 '{"service": "checkout-api", "severity": "P1", "recommended_action": "rollback", "change_id": "deploy-2026-09-04-042"}', ] result = validate_candidates(candidates) print("验收通过:", result.model_dump())运行结果:
第 1 次校验失败:字段 severity 校验失败:Input should be 'P1', 'P2', 'P3' or 'none';实际输入为 'critical'验收通过: {'service': 'checkout-api', 'severity': 'P1', 'recommended_action': 'rollback', 'change_id': 'deploy-2026-09-04-042'}这里最值得记住的不是 Pydantic 语法,而是顺序:
模型说“应该回滚”≠系统允许它回滚Pydantic 通过,只说明应用层契约通过;deployment_exists()通过,才说明这条变更在可信发布系统里真的存在。
Schema 校验负责判断“像不像”;业务校验负责判断“是不是真的”。
六、第三道门:失败后怎么做,才不把错误变贵
6.1 先分类,再谈重试
“校验失败就重试”是最常见的半对答案。
对在哪里?格式错误确实可能靠一次定向修复解决。
错在哪里?有些错误根本不该让模型再猜。
| 失败类型 | 典型现象 | 是否让模型重试 | 应对动作 |
| 安全拒答 | 模型明确拒绝生成 | 否 | 返回安全兜底或转人工 |
| 响应截断 | 输出未完成、长度耗尽 | 视情况 | 调整输出预算后重试一次 |
| JSON/Schema失败 | 非法枚举、缺字段、类型错误 | 是 | 回传精确错误摘要,最多1-2次 |
| 业务事实失败 | 发布版本不存在、告警已恢复 | 通常否 | 查询可信系统、追问或阻断 |
| 下游暂时故障 | 发布平台503、数据库超时 | 是,但不访问模型 | 调用层退避重试,保留幂等键 |
例如change_id不存在,不是模型“格式没写好”,而是它在编造事实。你让它再写十次,只是在让它换十种方式编。
6.2 自纠错循环的三条铁律
第一,限制次数。
重试一般给一到两次。第一次可能是模型忽略了某条约束;第二次仍错,就应该怀疑输入信息、Schema 设计或任务边界,而不是追加第 37 次重试。
第二,回传精确摘要。
不要把几十行ValidationError原样扔回上下文。模型真正需要的是:哪个字段错、规则是什么、刚才填了什么。
字段 severity 必须是 P1、P2、P3、none 之一;实际值为 critical。第三,修复后重新验收。
第二次输出仍然是不可信输入。它不能因为“已经被修过”就自动获得绿灯。
重试不是给模型发免死金牌,而是再给它一次参加验收的机会。
6.3 降级要预先定义,不要临时糊弄
“流程不能停,所以先塞一个默认值”是典型的生产事故预备动作。
可以降级的是展示型字段:例如故障摘要、建议观察时长。它们失败时,可以明确展示“暂无法生成”。
不能降级的是决策型字段:服务名、严重等级、发布版本、执行动作。一旦它们不可信,就应该阻断、要求人工确认,或者把任务挂起。
| 字段性质 | 是否降级 | 示例 |
| 展示型、非核心 | 可以,明确标记未知 | 告警摘要、观察建议 |
| 决策型、核心 | 不可以 | severity、service |
| 有副作用参数 | 绝对不可以 | change_id、回滚目标、删除资源id |
6.4 观测:别只盯“成功率”
如果你只看“Agent 调用成功率 99%”,那剩下的 1% 可能正好是一条错误回滚指令。
建议记录如下指标,并对日志中的用户数据、密钥、命令参数做脱敏:
request_idschema_versionmodel_namefinish_reasonstructured_output_successschema_validation_failurebusiness_validation_failureretry_countfallback_typetool_nametool_execution_resultlatency_mstoken_usage有这些数据,才看得出问题到底来自模型升级、Schema 演进、输入缺失,还是下游服务不稳定。
没有观测的重试,就像在黑屋里反复按电灯开关。
七、最后一道门:Agent 调工具时,模型只能提议
7.1 不要让模型吐出“动作 JSON”就直接执行
初学者很容易定义这种协议:
{ "action": "rollback_deployment", "service": "checkout-api", "change_id": "deploy-2026-09-04-042"}然后服务端看到action=rollback_deployment,直接调发布平台。
危险不在于 JSON,而在于你让模型同时决定了:做什么、对谁做、什么时候执行。
更合理的方式是 Tool Calling,把操作定义成受约束的工具接口:
rollback_deployment( service: "checkout-api" | "search-api" | "profile-api", change_id: string, reason: "confirmed_regression" | "security_incident")模型只能提出调用建议。
真正的执行器仍必须:
- 从发布平台读取变更是否存在、是否仍可回滚;
- 校验当前操作者、Agent 身份与环境权限;
- 判断是否满足回滚策略和审批条件;
- 向值班同学展示目标服务、版本和影响范围,要求确认;
- 生成幂等键并写入审计日志;
- 执行后回查实际结果,而不是相信“工具已调用”。
模型可以提议,代码必须裁决;模型可以调用工具,系统不能交出主权。
7.2 幂等性:真正容易被追问的细节
假设回滚请求已经到达发布平台,但网络在返回结果前超时。Agent 不知道操作是否成功,于是又发了一次。
第一次已经回滚。
第二次可能回滚到更早的版本,或者直接触发异常。
因此,发布、重启、发消息、删数据这类操作都应该带由服务端生成并持久化的幂等键。关键不是某个固定拼接公式,而是:同一个业务意图重放时,系统必须识别它,并返回第一次的结果,而不是再执行一次。
这也是为什么“JSON 幻觉”最终会讲到权限、状态机、确认和审计——模型输出只是这条风险链的入口。
八、面试官继续追问,你怎么接
追问一:“开了 Structured Outputs,还要 Pydantic 吗?”
容易失分:
“不用,Structured Outputs 已经保证 JSON Schema 了。”
加分回答:
“还需要。Structured Outputs 是生成侧防线,主要降低语法和结构偏离;Pydantic 是应用服务侧防线,确保进入业务代码的数据符合应用模型。两者之后还要查监控、发布记录、权限和状态机,因为它们验证的是事实和授权,不是 JSON 形状。”
追问二:“为什么不能无限重试,直到它合法?”
容易失分:
“多试几次总会成功。”
加分回答:
“我会按错误类型给重试预算。截断和 Schema 失败可以带精确错误摘要修复一到两次;发布版本不存在、无权限这类业务事实错误不能让模型猜。发布平台 503 则由调用层按退避策略重试,不应该重新让模型决策。每一类重试都要有幂等和日志。”
追问三:“模型建议回滚,怎么保证不会误操作?”
容易失分:
“工具参数也做 JSON Schema 校验。”
加分回答:
“工具 Schema 只解决参数格式。模型只是提议者,执行前我会从发布平台重新读取变更记录,校验当前环境、用户权限、审批规则与状态机;高风险操作需要人类确认、限额或白名单。执行请求带幂等键并记录审计日志,最终操作结果还要回查。模型没有最终执行权。”
九、最后复述:90 秒回答框架
面试时,你可以这样完整回答:
“我会把 LLM 输出当作不可信的外部输入,而不是把 JSON 解析成功当作正确。生成侧优先使用 Structured Outputs 或 Tool Calling Schema,约束字段、类型、枚举和额外字段,降低语法和结构幻觉;但这不保证业务事实正确。服务端我会用 Pydantic 或 JSON Schema 再校验应用模型,并到监控、数据库、发布平台和权限系统验证真实状态。失败处理按类型区分:拒答和业务事实错误不盲目重试;截断或可修复的 Schema 错误带精确错误摘要最多修复一到两次;下游临时故障由调用层按退避和幂等策略处理。重试耗尽后,非核心展示字段可以明确降级,核心决策字段则阻断或转人工。对回滚、删除等有副作用的工具,模型只能提议调用,服务端还要做事实校验、权限、确认、幂等和审计,确保最终执行权不在模型手里。”说完之后停一下。
你没有背一串 API 参数,但你把模型、协议、业务系统和执行器的责任边界说清楚了。
这才是面试官真正想听的东西。
十、把它记成一个工程公式
结构化生成+ 应用 Schema 校验+ 可信事实与权限校验+ 有预算的重试与降级+ 幂等、确认、审计= 可被信任的 Agent 执行链路格式正确,是模型通过了格式检查;业务正确,才是它真的答对了题。
不要让模型裸写 JSON 进入业务系统。模型负责建议,契约负责约束,代码负责拍板。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~