
2026年了面试官已经不再问“什么是Agent”而是直接扔出一个场景题“给我一个方案让Agent每次返回的JSON都完美符合我的数据模型。”这个问题背后有一个残酷的现实很多开发者在Demo阶段觉得大模型神奇得不行一上生产就被输出稳定性毒打。你让Agent返回{“city”: “北京”}它偶尔给你返回{“city”: “北京市”}你让它返回布尔值它给你来一个字符串“true”你定义了枚举值它自由发挥给你发明一个新词。这不是模型不够聪明而是你的约束体系有漏洞。更好的做法是把“靠运气”变成“靠机制”。今天要聊的四层约束——Prompt强制、正反示例、原生参数、代码校验——就是解决这个问题的完整套路。这既是面试中展示工程经验的高频考点也是真实项目里让Agent从“玩具”变成“生产力工具”的必经之路。读完这篇文章你能掌握一套可落地的分层防御方案知道每层解决什么问题、在哪里拦截以及面试官追问“如果模型还是不听话怎么办”的时候该怎么答。1. 这篇文章真正要解决的问题很多人理解“Agent输出结构化内容”第一反应是“在Prompt里写清楚要求就行了”。但如果你真的写过生产级Agent应用一定会遇到下面这些场景Agent执行意图识别时说好返回“weather_query”和“city”两个字段实际返回里多了一个“unit”少了一个“city”。模型在正常对话里表现良好但一旦你塞入大量上下文、工具调用历史输出的JSON就开始出现截断、多余注释、甚至Markdown代码块包裹。你希望模型返回枚举值“sunny”它返回了“晴朗”你还有专门写一层兜底映射。更麻烦的是模型偶发性地在合法JSON后面追加一句话导致json.loads直接抛异常。这些问题不是偶然bug而是LLM生成机制决定的。大模型本质是“按概率预测下一个token”不是“按协议填充结构体”。它没有内存里的数据结构也没有编译器的类型检查。你能做的不是祈祷它记住你的格式要求而是设计一套多层防御体系让每一层都能拦截一类失败最后一层用代码兜底。从架构角度看这套体系的价值在于把模型输出的不确定性隔离在系统的边界区域而不是让它传导到核心业务逻辑里。面试官问这个问题本质是想看你有没有真正把Agent当作一个“带有概率性的外部依赖”来设计系统而不是当作一个“输出JSON的函数”来调用。2. 基础概念与核心原理在展开四层约束之前先对齐几个核心概念。很多面试者挂在第一轮不是因为不知道这些词而是理解得过于模糊。2.1 什么是结构化内容在Agent场景下结构化内容通常指机器可以直接消费的数据格式最常见的是JSON也包括YAML、XML、CSV以及函数调用中的参数对象。它要求语法合法能被标准解析器解析结构符合预定Schema字段存在且类型正确值域合法枚举值、范围、格式满足业务要求语义正确字段含义与业务场景一致。这里需要注意一个常见的误解“语法合法”和“结构正确”是两个层次。{city: 北京, weather: 晴}是合法JSON但如果业务要求字段名是cityName而不是city它在结构上就是不正确的。很多开发者只做了语法解析没做结构校验导致数据流到下游后才报错定位成本很高。2.2 为什么LLM难以稳定输出结构化内容根本原因在于LLM的训练目标是“预测下一个最高概率的词”不是“遵守数据格式协议”。它知道JSON长什么样因为训练数据里有大量JSON但它不知道你的业务Schema长什么样除非你在上下文中清楚告诉它并反复强化。同时生成过程中的概率采样、上下文长度压力、指令冲突都会让模型在“内容正确”和“格式正确”之间摇摆。当两者冲突时模型往往会选择更自然的自然语言表达。2.3 四层约束的分层思想层级机制核心作用对应职责第一层Prompt强制让模型“倾向于”输出格式降低出错概率第二层正反示例让模型“看到”理想输出长什么样消除歧义第三层原生参数平台层硬约束提高合法率第四层代码校验让非法输出无法进入业务逻辑兜底拦截前两层解决的是“让模型更可能做对”后两层解决的是“让模型错误无法造成影响”。分层的关键不是每一层都完美而是每一层失败之后下一层能接住。这个思路在工程上叫“纵深防御”。3. 第一层Prompt强制设计Prompt强制是最容易上手也最常被低估的一层。一个设计良好的System Prompt能把模型输出的结构化率从50%提升到90%以上。这里的关键不在于你“写了要求”而在于你怎么写。3.1 结构化Prompt的四个要素一个合格的“输出格式约束Prompt”至少需要包含四个部分角色设定告诉模型它在一个什么样的系统中工作任务描述明确模型现在要干什么输出格式定义给出完整的JSON Schema或示例结构边界约束告诉模型不要做什么比如不要输出解释、不要添加注释、不要使用Markdown代码块。以下是一个典型的代码示例# 文件路径prompts/structured_prompt.py SYSTEM_PROMPT 你是一个天气查询助手。用户会提供城市名你需要查询该城市的天气信息。 你的任务 1. 解析用户提到的城市名称。 2. 根据你的知识库或工具调用结果返回该城市的天气数据。 输出要求严格遵守 - 只输出一个JSON对象不要输出任何其他文字。 - 不要使用Markdown代码块包裹JSON。 - 不要添加注释。 - JSON必须包含以下字段 { city: 城市名称, weather: 天气状况只允许是 sunny、cloudy、rainy 中的一个, temperature: 摄氏温度整数, humidity: 相对湿度百分比整数, timestamp: 当前时间ISO 8601格式 } 记住你的输出会直接交给代码解析任何多余内容都会导致系统崩溃。3.2 Prompt设计中的关键细节这里有几个容易被忽略的细节使用“只输出一个JSON对象”而不是“返回JSON格式”。前者更明确后者容易被模型理解为“围绕JSON格式聊一聊”。明确“不要做什么”。LLM对负面约束的理解能力有限所以要给也同时给正面指引。可以写成“只输出一个JSON对象”而不是“不要输出其他内容”。这在语义上更清晰。强调后果。“你的输出会直接交给代码解析任何多余内容都会导致系统崩溃”这种后果描述在实验中被证明能有效降低模型输出自然语言解释的倾向。3.3 一个容易踩的坑一个新手常见的错误是在Prompt里用自然语言描述JSON结构但没有给出完整的结构示例。例如写“请返回以JSON格式表示的天气信息包括城市、天气、温度、湿度”是不够的。模型不知道字段名到底是cityName还是city不知道湿度是整数还是字符串。你的Prompt越是模糊模型越是有自由发挥的空间而这个空间就是不稳定性的来源。第一层能解决的问题告诉模型你想要的形状。解决不了的问题模型在具体取值上的不确定以及大段对话后“忘记”格式要求。所以我们需要第二层用示例强化记忆。4. 第二层正反示例与Few-shot强化如果说Prompt强制是“告诉模型规则”那么正反示例就是“给模型看标准答案”。在LLM的实际表现中Few-shot示例往往比规则描述更有效因为模型在训练阶段的核心能力是“模式匹配”给它看越清晰的输入输出配对它就越容易模仿那个模式。4.1 为什么示例比描述更有效举个直观的例子。你打算让一个实习生做数据录入最有效的做法不是告诉他“请严格按照Excel表头格式录入字段不能错数字不要加引号”而是直接给他一个“正确的Excel文件”和“一个错误的Excel文件”然后说“照着这个对的来别做成那个错的。”模型的运转逻辑与之类似——给它一串标注好的输入输出它就能学到“照葫芦画瓢”的能力。4.2 如何设计正例和反例正例设计时要覆盖典型场景反例设计时要覆盖最常见的错误模式。# 文件路径prompts/few_shot_examples.py FEW_SHOT_EXAMPLES [ { role: user, content: 北京今天天气怎么样 }, { role: assistant, content: {city: 北京, weather: sunny, temperature: 23, humidity: 40, timestamp: 2026-03-15T14:00:0008:00} }, { role: user, content: 上海明天会下雨吗 }, { role: assistant, content: {city: 上海, weather: rainy, temperature: 18, humidity: 85, timestamp: 2026-03-16T08:00:0008:00} } ] NEGATIVE_EXAMPLE { role: assistant, content: 以下是北京今天的天气情况城市是北京天气是晴天温度23度湿度40%。 }这段代码中正例给出了两个标准格式的JSON输出反例展示了一个虽然信息正确、但完全不符合格式要求的自然语言回答。你可以把反例放进System Prompt也可以把它作为用户消息里的“错误示范”传给模型。4.3 示例数量的平衡Few-shot示例不是越多越好。太多示例会占用宝贵的上下文窗口增加token成本还可能引入冗余信息导致模型不知道重点。根据实际经验2到4个正例、1个反例通常能在成本和效果之间取得较好平衡。另外要注意示例的多样性。如果所有正例都是“sunny”天气模型可能会把“sunny”当作默认输出。示例应该覆盖不同的枚举值、不同的城市名、不同的数值范围让模型真正学到“结构稳定内容随输入变化”的模式。第二层能解决的问题模型在取值、格式模仿上的不确定性。解决不了的问题当模型出现幻觉、生成中途截断、或者内容本身就是非合法JSON时无论多少示例都无法保证100%。因此我们需要第三层和第四层。5. 第三层模型原生参数约束如果说前两层是“说话的艺术”那么第三层就是“平台的硬件开关”。主流大模型接口都会提供一些原生参数来约束输出善用这些参数才是工程师思维和纯提示词玩家之间的重要区别。5.1 response_format: json_objectOpenAI及其他多家兼容接口都支持通过response_format参数强制模型输出JSON对象。使用方式如下# 文件路径examples/openai_json_mode.py from openai import OpenAI client OpenAI() response client.chat.completions.create( modelgpt-4o-mini, response_format{type: json_object}, messages[ {role: system, content: 你是一个天气助手只返回JSON。}, {role: user, content: 北京今天天气怎么样} ] ) content response.choices[0].message.content print(content)官方文档里对json_object模式的说明是模型会保证输出是合法的JSON对象但不会保证字段名和字段类型符合你的业务要求。也就是说它解决的是“语法合法”的问题不解决“结构正确”的问题。这一点在做技术选型时一定要想清楚。5.2 更强的约束方案函数调用Function Calling / Tool Calling函数调用是目前生产级Agent系统中控制结构化输出的最有效手段之一。它的工作方式是你向模型声明“我有什么函数、参数分别是什么类型、哪些必填”模型在需要调用工具时会直接输出一个符合声明的函数调用参数对象。# 文件路径examples/function_calling.py tools [ { type: function, function: { name: get_weather, description: 获取指定城市的天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称如北京 }, date: { type: string, description: 日期格式YYYY-MM-DD } }, required: [city, date] } } } ] response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: user, content: 北京明天天气怎么样} ], toolstools, tool_choiceauto ) print(response.choices[0].message.tool_calls)这里的关键在于模型返回的不再是自由文本而是一个结构化的tool_calls对象。你不需要去解析自然语言提取参数直接从arguments字段拿JSON即可。5.3 参数调优temperature与seed除了结构化参数之外temperature和seed也值得重视。temperature值越低输出越确定。对结构化输出任务建议设置在0到0.3之间避免模型发挥创造性。seed部分模型支持传入固定随机种子在相同输入下能获得更可复现的输出。这对测试和调试很有价值。需要说明的是temperature0不意味着绝对确定性只是显著降低随机性。模型推理本身仍然存在不确定性不要在生产环境里做“完全可复现”的假设。第三层能解决的问题语法合法性和结构偏好。解决不了的问题业务值域错误、模型幻觉产生的不存在字段、极端情况下的截断。这些最终只能靠第四层——代码校验来兜底。6. 第四层代码校验兜底现在来到整条防线里最关键的一层。无论Prompt写得多好、示例给得多标准、原生参数调得多严格模型输出都有可能出错。概率可能从“经常出错”降到“偶尔出错”但“偶尔”在产品里依然意味着线上事故。所以第四层不能省。6.1 为什么要用代码校验代码校验的思路很简单模型输出到达业务逻辑之前先用一个严格的数据校验器拦截。校验通过放行校验不通过按照预设策略处理重试、报错、或走降级逻辑。这一步的意义在于让非法数据无法进入系统内部。把不确定性隔离在系统边界而不是让它污染你的服务状态。6.2 最小示例Python PydanticPydantic是Python生态里最常用的数据校验库。下面用Pydantic实现一个结构化输出的完整校验链路# 文件路径validators/weather_validator.py from pydantic import BaseModel, Field, ValidationError from typing import Literal import json class WeatherInfo(BaseModel): city: str Field(description城市名称) weather: Literal[sunny, cloudy, rainy] Field(description天气状况) temperature: int Field(ge-50, le60, description摄氏温度) humidity: int Field(ge0, le100, description相对湿度百分比) timestamp: str Field(descriptionISO 8601时间格式) def parse_and_validate(content: str) - WeatherInfo: 将模型输出字符串解析为WeatherInfo对象。 任何一步失败都会抛出异常由上层决定如何处理。 try: data json.loads(content) return WeatherInfo(**data) except json.JSONDecodeError as e: raise ValueError(f模型输出不是合法JSON: {e}) from e except ValidationError as e: raise ValueError(f模型输出不符合Schema: {e.errors()}) from e这段代码里有几个值得注意的设计使用Literal类型约束枚举值相当于在类型层面定义数据字典使用Field(ge-50, le60)约束数值范围捕获极端异常值异常被统一包装成ValueError上游调用方只需处理一种异常。6.3 重试机制当校验失败时最简单的策略是让模型重新生成一次。在工程实践中一般会保留一个“重试N次”的循环并且把上次失败的原因反馈给模型作为提示。# 文件路径examples/retry_loop.py from openai import OpenAI from validators.weather_validator import parse_and_validate def get_weather_with_retry(user_input: str, max_retries: int 2): client OpenAI() messages [ {role: system, content: 你是一个天气助手只输出JSON不要输出多余内容。}, {role: user, content: user_input} ] for attempt in range(max_retries 1): response client.chat.completions.create( modelgpt-4o-mini, response_format{type: json_object}, messagesmessages ) content response.choices[0].message.content try: return parse_and_validate(content) except ValueError as e: if attempt max_retries: raise RuntimeError(f模型多次输出不符合要求: {e}) # 把失败信息反馈给模型 messages.append({ role: user, content: f你上次的输出没有通过系统校验错误原因是{e}。请重新生成一个完全符合要求的JSON。 }) # 正常情况下不会走到这里 raise RuntimeError(Unexpected retry exit)这段代码的核心思路是校验失败时把具体错误原因如“temperature字段缺失”或“weather字段值不在允许列表内”以用户消息的形式回传给模型让它基于错误信息自我修正。在实际项目中这种方式通常比无脑重试更有效因为模型获得了一次纠错机会。6.4 降级策略有些场景下重试仍然失败就需要降级。最常见的方式是使用一个默认值兜底或者直接丢弃该条数据并记录日志。方式并没有绝对的对错关键是在系统设计阶段就想清楚模型返回不正常时你的系统应该怎么办而不是把这个问题留给运行时才处理。第四层能解决的问题一切模型层没有拦住的问题。它是最后一道防线也是整个防御体系中最可靠的一环——因为代码逻辑是确定性的不依赖概率。7. 四层约束的综合实现前面四节分别讲了每一层的原理和做法现在用一个完整的例子展示如何把四层约束组合到一个生产级函数里。这个函数在架构上呈现“Prompt先行 → 示例辅助 → 参数强约束 → 代码兜底 → 重试修正”的完整链路。# 文件路径examples/four_layer_pipeline.py import json import logging from openai import OpenAI from pydantic import ValidationError from prompts.structured_prompt import SYSTEM_PROMPT from prompts.few_shot_examples import FEW_SHOT_EXAMPLES from validators.weather_validator import WeatherInfo, parse_and_validate logger logging.getLogger(__name__) def generate_structured_weather(city: str, max_retries: int 2) - WeatherInfo: 四层约束完整链路入口。 client OpenAI() messages [{role: system, content: SYSTEM_PROMPT}] messages.extend(FEW_SHOT_EXAMPLES) messages.append({role: user, content: f请查询{city}的天气并返回JSON。}) for attempt in range(max_retries 1): # 第三层模型原生参数约束 response client.chat.completions.create( modelgpt-4o-mini, temperature0.2, response_format{type: json_object}, messagesmessages ) content response.choices[0].message.content # 第四层代码校验兜底 try: result parse_and_validate(content) return result except ValueError as e: logger.warning(f第{attempt 1}次输出校验失败: {e}) if attempt max_retries: raise RuntimeError(f模型输出多次校验失败已放弃重试: {e}) messages.append({ role: user, content: ( f系统提示你上次的输出未通过数据校验。错误原因是{e}。 请重新生成一个完全符合系统要求的JSON不要再犯同样的错误。 ) }) raise RuntimeError(Unexpected execution path)这个函数的调用方式非常简洁# 文件路径examples/run_pipeline.py from examples.four_layer_pipeline import generate_structured_weather weather generate_structured_weather(北京) print(weather.model_dump())运行成功时输出类似{city: 北京, weather: sunny, temperature: 23, humidity: 40, timestamp: 2026-03-15T14:00:0008:00}注意一个关键点四层约束不是一次性堆砌的而是层层递进、逐层收口。第一层降低概率第二层消除歧义第三层强化格式第四层保证结果。每一层都在为后面的层减轻压力。8. 常见问题与排查思路在实际开发和面试交流中有几个高频问题值得单独拿出来讲。8.1 高频问题清单问题现象可能原因排查方式解决方案返回内容被Markdown代码块包裹Prompt里没说明禁止Markdown查看原始输出在Prompt中用“不要使用Markdown代码块”并加正例JSON字段名不符合预期Prompt中字段名描述模糊查看模型原始输出用完整的JSON示例代替自然语言描述枚举值出现自由发挥没有用Literal或枚举约束检查Schema定义在Schema中使用枚举类型并在Prompt中提供合法值清单输出内容被截断上下文过长或max_tokens不够查看响应中的finish_reason增加max_tokens或压缩输入上下文重试后错误依然相同模型没有理解错误原因检查回传给模型的消息把错误信息改写成更明确的修正指令temperature0仍然输出不稳定模型概率机制本身带有随机性多次运行观察规律接受概率现实用校验和重试兜底校验频繁失败影响性能重试次数过多导致时延变高添加日志和指标监控限制重试次数替换为降级策略8.2 排查的第一动作无论遇到什么输出问题第一件事永远是打印模型的原始输出。不要拿最终解析后的数据结构去猜模型经历了什么。很多问题的根源在下游解析层但表面现象会让你误以为问题出在模型。先看原始输出再对照Prompt和Schema逐层定位。8.3 关于“模型拒绝执行”的情况使用response_format{type: json_object}时有些模型在遇到“自己觉得无法回答”的问题时可能会返回一段“抱歉我无法……”之类的JSON。这同样是校验层需要兜底的行为。可以单独增加一个异常检测分支如果JSON里的内容是一个固定错误场的结构就按错误逻辑处理而不是按数据结果处理。9. 最佳实践与工程建议最后总结几条在真实项目中验证过的工程建议这些也是面试官比较喜欢的加分表达。9.1 把Schema作为唯一事实源在架构上建立一个“Schema单一来源”原则Prompt里的结构描述、Pydantic模型定义、最终业务代码里用的数据结构全部从同一个Schema派生。最容易出问题的是不同层各写各的Prompt用“cityName”Pydantic用“city”模型就一直校验失败。建议用Pydantic自动生成JSON Schema再拼进Prompt中从根源消除不一致。# 文件路径examples/schema_to_prompt.py from validators.weather_validator import WeatherInfo schema_json json.dumps(WeatherInfo.model_json_schema(), ensure_asciiFalse) print(schema_json)这样生成的Schema可以作为Prompt里的“严格格式定义”部分保证模型看到的字段定义和代码校验的字段定义来自同一份数据。9.2 监控“模型输出质量指标”在生产环境应该持续追踪几个关键指标首次校验通过率反映Prompt和示例的质量重试后的通过率反映错误反馈机制是否有效最终失败率反映降级策略触发频率无效JSON概率反映原生参数约束的效果。这些指标能帮你判断到底该优化哪一层约束体系。如果首次通过率已经达到95%再花很多精力优化Prompt收益可能有限如果重试后依然失败较多问题可能出在模型能力或任务复杂度上。9.3 版本管理与回归测试随着业务迭代Prompt会不断被修改。建议把“输入样例集合”和“期望输出Schema”保存下来每次调整Prompt后跑一遍回归测试观察通过率变化。这相当于给Prompt工程建立自动化测试基线能有效防止“改好了一个case踩坏了另一个case”。9.4 安全边界提醒当你让Agent处理包含敏感信息的请求时校验层还要承担一部分数据安全职责禁止模型在任何输出字段中携带与任务无关的隐私数据对输出内容做脱敏检查对外部传入的JSON做类型和大小限制防止“提示词注入”产生的异常数据流入下游。10. 总结与后续学习方向到这里四层约束的体系已经完整串起来了Prompt强制解决方向问题正反示例解决模仿问题原生参数解决格式问题代码校验解决结果问题。每一层都有自己的边界和局限组合成体系之后才能形成一个相对稳的Agent结构化输出机制。这套方案的价值不只是回答面试题更是日常开发中可以直接落地的工程框架。你可以先用最小示例跑通“Prompt 校验”的最简链路再逐步加入Few-shot示例、原生参数和重试机制观测每一层带来的通过率提升。如果想继续深入有几个相关的方向值得研究一是Function Calling的进阶用法理解它背后的结构化输出机制如何工作二是更复杂Schema下的校验策略比如嵌套对象、数组、可选字段的校验三是把四层约束封装成可复用的Agent框架组件让团队成员共享同一套防御体系。技术选型和深度可以灵活调整但“结构化内容不可靠”这个问题始终是Agent从原型走向生产绕不开的核心考点。建议收好这套方案真正用到自己的Agent项目里去验证和调优。