发布时间:2026/8/19 22:16:21
AI Agent 开发实战(八):输出 Schema 约束与结构化输出 上一篇我们用 Harness Engineering 把 LLM 的行为范围框住但还有一类问题没解决输出格式。LLM 默认吐的是自由文本而下游系统要的是 JSON、枚举、数组——格式不对整条链路就断了。今天聊输出 Schema 约束让 LLM 的输出像 API 返回值一样可预测。一、为什么需要结构化输出先看一个没有约束的 Agent 输出用户帮我查一下某市的租房补贴政策 Agent好的某市的租房补贴政策如下 1. 补贴标准本科500元/月硕士800元/月博士1200元/月 2. 申请条件需要在本区就业并缴纳社保满3个月... 3. 申请方式通过人才服务中心官网在线申请看起来没问题但对下游系统来说这是一坨文本不是数据。前端没法渲染成卡片数据库没法存入字段。我们真正想要的是{subsidy:{bachelor:500,master:800,doctor:1200},requirements:[本区就业,社保满3个月],applyUrl:https://...}结构化输出的核心价值让 LLM 的输出从文本变成数据下游可直接消费。二、结构化输出的三种策略结构化输出策略 │ ├── 1. Prompt 约束最弱 │ ├── 在 Prompt 中要求返回 JSON │ ├── 给出 JSON 示例 │ └── 问题LLM 可能不听话格式不稳定 │ ├── 2. 函数调用 / Tool Use中等 │ ├── 利用 LLM 的 function calling 能力 │ ├── 输出自然就是结构化的 │ └── 问题不是所有场景都适合伪装成工具调用 │ └── 3. 输出 Schema 约束最强 ├── 强制 LLM 按 Schema 输出 ├── 支持 JSON Schema、Pydantic、Zod 等 └── 原理引导采样过程 输出校验 自动重试策略约束强度兼容性实现复杂度典型框架Prompt 约束弱所有模型低无需框架函数调用中需模型支持中OpenAI / Spring AISchema 约束强需模型或框架支持高Instructor / Spring AI Structured三、Prompt 约束最基础的方式# 在 Prompt 中要求返回 JSONprompt请分析以下文本的情感倾向以 JSON 格式返回。 要求 - sentiment: 正面/负面/中性 - confidence: 0-1 之间的浮点数 - keywords: 关键词数组 示例输出 {sentiment: 正面, confidence: 0.85, keywords: [好, 优秀]} 待分析文本{text}问题在哪可能出现的意外输出 │ ├── 1. 输出前后多了解释文字 │ 好的分析结果如下\n{\sentiment\: ...}\n希望对你有帮助 │ ├── 2. JSON 格式不规范 │ {sentiment: 正面, confidence: 0.85, keywords: [好, 优秀],} ← 末尾多了逗号 │ ├── 3. 字段名不一致 │ {emotion: 正面, score: 0.85} ← 你要的是 sentiment它给了 emotion │ └── 4. 嵌套结构错误 {result: {sentiment: ...}} ← 多包了一层Prompt 约束的本质是请求而非要求。LLM 大部分时候会配合但你无法保证 100%。四、函数调用借力 Tool Use利用 LLM 原生的 function calling 能力让输出天生就是结构化的// Spring AI 方式BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.build();}// 定义函数描述Description(分析文本情感返回结构化结果)publicrecordSentimentAnalysis(Description(情感倾向正面、负面、中性)Stringsentiment,Description(置信度 0-1)Doubleconfidence,Description(关键词列表)ListStringkeywords){}// 调用varresponsechatClient.prompt().user(分析这段文本的情感今天天气真好).functions(sentimentAnalysis).call().entity(SentimentAnalysis.class);函数调用的局限函数调用的局限 │ ├── 1. 语义不匹配 │ 帮我分析情感不是调用工具而是返回结果 │ 强行用 tool call 表达语义上有点别扭 │ ├── 2. 不是所有模型都支持 │ 开源模型如 Qwen、DeepSeek对 function calling 支持参差不齐 │ ├── 3. 只能返回单层结构 │ 复杂嵌套 Schema 用 function call 描述起来很别扭 │ └── 4. 一次只能调一个函数 多个输出字段需要拆成多个函数不合理五、Schema 约束终极方案5.1 核心原理Schema 约束的底层机制 │ ├── 1. Schema 转化为 Prompt 引导 │ ├── JSON Schema → 自然语言描述注入 System Prompt │ ├── 告诉 LLM 必须按以下格式输出 │ └── 相当于 Prompt 约束的工程化版本 │ ├── 2. 约束解码Constrained Decoding │ ├── 某些框架/模型支持在 token 采样时强制合法 │ ├── 例如当 Schema 要求整数采样时只允许数字 token │ ├── 效果100% 格式合规如果模型支持 │ └── 实现llama.cpp 的 GBNF 语法、Outlines 的 FSM │ └── 3. 输出校验 自动重试 ├── 解析 LLM 输出校验是否符合 Schema ├── 不符合则将错误信息拼回 Prompt 重试 └── 最多重试 N 次兜底返回错误5.2 JSON Schema 示例{type:object,properties:{subsidy:{type:object,properties:{bachelor:{type:integer,description:本科补贴元/月},master:{type:integer,description:硕士补贴元/月},doctor:{type:integer,description:博士补贴元/月}},required:[bachelor,master,doctor]},requirements:{type:array,items:{type:string},description:申请条件列表},applyUrl:{type:string,format:uri,description:申请链接}},required:[subsidy,requirements,applyUrl]}5.3 Spring AI 结构化输出Spring AI 从 1.0.0 开始支持结构化输出底层是 JSON Schema 自动映射// 定义输出结构publicrecordSubsidyInfo(JsonProperty(requiredtrue)Description(各学历补贴标准)SubsidyDetailsubsidy,Description(申请条件列表)ListStringrequirements,Description(申请链接)StringapplyUrl){}publicrecordSubsidyDetail(intbachelor,intmaster,intdoctor){}// 使用varresultchatClient.prompt().user(查询某市租房补贴政策).call().entity(SubsidyInfo.class);// ← 一行搞定Spring AI 的.entity()做了什么.entity() 内部流程 │ ├── 1. 将 SubsidyInfo.class 转换为 JSON Schema │ ├── 2. 将 Schema 注入 Prompt作为格式要求 │ ├── 3. 调用 LLM获取文本输出 │ ├── 4. 尝试将输出解析为 SubsidyInfo 对象 │ ├── 解析成功 → 返回 │ └── 解析失败 → 拼入错误信息重试 │ └── 5. 重试 N 次后仍失败 → 抛出异常六、约束解码从引导到强制Prompt 引导 重试只是软约束。真正的硬约束是约束解码Constrained Decoding。6.1 原理常规解码自由采样 │ ├── 每步从词表中采样一个 token ├── 任何 token 都可能被选中 └── 输出可能是任意文本 约束解码Constrained Decoding │ ├── 根据 Schema 构建一个有限状态机FSM或文法Grammar ├── 每步只允许生成 FSM 当前状态合法的 token ├── 例如Schema 要求整数 → 只允许 0-9 token ├── 例如Schema 要求布尔 → 只允许 true/false token └── 输出 100% 符合 Schema在模型支持的前提下6.2 实现方案对比方案语言原理支持模型接入方式OutlinesPythonFSM 约束解码本地模型Python 库llama.cpp GBNFC文法约束GGUF 模型API 参数InstructorPythonSchema 重试OpenAI 兼容 APIPython 库Spring AIJavaSchema 注入 重试任何模型Java 框架OpenAI Structured OutputsAPI约束解码GPT-4o 等API 参数6.3 OpenAI Structed Outputs 示例fromopenaiimportOpenAIfrompydanticimportBaseModelclassSubsidyDetail(BaseModel):bachelor:intmaster:intdoctor:intclassSubsidyInfo(BaseModel):subsidy:SubsidyDetail requirements:list[str]apply_url:strclientOpenAI()responseclient.beta.chat.completions.parse(modelgpt-4o,messages[{role:user,content:查询某市租房补贴政策}],response_formatSubsidyInfo,# ← 直接传 Pydantic 类)# 输出 100% 符合 SubsidyInfo 结构七、实战多层 Schema 设计真实 Agent 的输出往往不是扁平的 JSON而是多层嵌套结构。7.1 一个商机分析 Agent 的输出 SchemapublicrecordOpportunityAnalysis(Description(商机基本信息)BasicInfobasicInfo,Description(风险评估)RiskAssessmentrisk,Description(推荐动作)ListActionactions,Description(综合评分 0-100)intscore){}publicrecordBasicInfo(Description(客户行业)Stringindustry,Description(预计预算万元)doublebudget,Description(决策周期天)intdecisionCycle){}publicrecordRiskAssessment(Description(风险等级低/中/高)Stringlevel,Description(风险因素列表)ListStringfactors,Description(风险说明)Stringdescription){}publicrecordAction(Description(动作类型联系/跟进/放弃/升级)Stringtype,Description(动作描述)Stringdescription,Description(优先级 1-5)intpriority){}7.2 枚举约束// 使用枚举限制取值范围publicenumRiskLevel{LOW,MEDIUM,HIGH}publicenumActionType{CONTACT,FOLLOW_UP,ABANDON,ESCALATE}publicrecordRiskAssessment(RiskLevellevel,// ← 只能是四个值之一ListStringfactors,Stringdescription){}publicrecordAction(ActionTypetype,// ← 只能是四个值之一Stringdescription,Min(1)Max(5)intpriority// ← 限制范围){}枚举约束的效果没有枚举约束 ├── risk.level 比较危险 ← 不在预期范围内 ├── action.type 打电话 ← 下游系统无法识别 └── score 150 ← 超出 0-100 范围 有枚举约束 ├── risk.level MEDIUM ← 严格限定 ├── action.type CONTACT ← 严格限定 └── score 75 ← 范围限定八、Schema 约束的代价结构化输出不是免费的有三个关键代价8.1 推理质量下降自由输出 vs 结构化输出 │ ├── 自由输出 │ 经过分析这个商机值得跟进但预算可能需要调整 │ 建议先与客户技术负责人建立联系... │ → 表达丰富推理自然 │ └── 结构化输出 │ {score: 75, risk: {level: MEDIUM, ...}} │ → 表达受限推理被压缩进字段 │ → 某些微妙判断可能丢失缓解策略在 Schema 中加reasoning或explanation字段让 LLM 先推理再输出结构。publicrecordOpportunityAnalysis(Description(推理过程先思考再给结论)Stringreasoning,// ← 给 LLM 一个思考空间BasicInfobasicInfo,RiskAssessmentrisk,ListActionactions,intscore){}8.2 Token 消耗增加自由输出~200 tokens 结构化输出~500 tokensJSON key 语法 枚举值 增加约 2-3 倍 token 消耗8.3 延迟增加自由输出1-2 秒 结构化输出 ├── 首次调用 解析2-3 秒 ├── 重试 1 次2-3 秒 └── 重试 2 次2-3 秒 最差情况7-9 秒代价总结代价幅度缓解方式推理质量下降 5-15%加 reasoning 字段Token 消耗增加 2-3 倍精简 Schema少用嵌套延迟增加 1-3 秒限制重试次数选快速模型九、Schema 约束 vs Harness 约束互补而非替代上一篇讲的 Harness Engineering 和这篇的 Schema 约束两者互补约束体系 │ ├── Harness 约束行为层面 │ ├── 限制可用工具 │ ├── 限制执行流程 │ ├── 限制输入范围 │ └── 解决Agent 能做什么、不能做什么 │ └── Schema 约束输出层面 ├── 限制输出格式 ├── 限制字段取值 ├── 限制嵌套结构 └── 解决Agent 输出什么、怎么输出实战中的组合// Harness限制工具和流程varagentChatAgent.builder().tools(List.of(searchTool,calculatorTool))// 只允许两个工具.maxSteps(5)// 最多 5 步.build();// Schema限制输出格式varresultagent.run(分析这个商机).entity(OpportunityAnalysis.class);// 输出必须是这个结构完整约束链 │ ├── 输入约束Harness 限制可接收的用户输入范围 │ ├── 工具约束Harness 限制可用工具集 │ ├── 流程约束Harness 限制执行路径和步骤数 │ └── 输出约束Schema 限制输出格式和字段取值 ├── 格式约束必须是 JSON ├── 字段约束字段名、类型、必填 ├── 取值约束枚举、范围、正则 └── 嵌套约束对象引用、递归结构十、框架实战对比10.1 InstructorPythonimportinstructorfromopenaiimportOpenAIfrompydanticimportBaseModel clientinstructor.from_openai(OpenAI())classAnalysis(BaseModel):score:intreason:strresultclient.chat.completions.create(modelgpt-4o-mini,messages[{role:user,content:分析这段文本}],response_modelAnalysis,# ← 核心参数max_retries3,# ← 自动重试次数)特点Pydantic 定义 自动重试 流式支持Python 生态最成熟的方案。10.2 Spring AIJavavarresultchatClient.prompt().user(分析这段文本).call().entity(Analysis.class);// ← 一行搞定特点Java 生态最简洁的方案但底层是 Prompt 注入 重试非约束解码。10.3 LangChainPython/JSfromlangchain_core.output_parsersimportPydanticOutputParser parserPydanticOutputParser(pydantic_objectAnalysis)promptChatPromptTemplate.from_messages([(system,按以下格式输出\n{format_instructions}),(human,{input})])chainprompt|model|parser resultchain.invoke({input:分析这段文本,format_instructions:parser.get_format_instructions()})特点Parser 模式灵活但啰嗦适合已有 LangChain 链的项目。十一、选择建议如何选择结构化输出方案 │ ├── 场景一只需要简单字段容忍偶尔格式错误 │ └── Prompt 约束就够了 │ ├── 场景二需要稳定的 JSON 输出Python 生态 │ └── Instructor首选或 LangChain Pydantic │ ├── 场景三需要稳定的 JSON 输出Java 生态 │ └── Spring AI .entity() │ ├── 场景四需要 100% 格式保证使用 OpenAI API │ └── OpenAI Structured Outputs │ └── 场景五需要 100% 格式保证使用本地模型 └── Outlines 或 llama.cpp GBNF十二、最佳实践 Checklist#实践说明1优先用 Schema 约束而非 Prompt 约束工程化 提示词技巧2给 Schema 的每个字段加 DescriptionLLM 看到 description 才知道该填什么3枚举字段用 enum 而非 string避免 LLM 自由发挥4加 reasoning 字段保留推理质量让 LLM 先思考再结构化输出5限制重试次数建议 2-3 次避免无限重试浪费 token6监控 Schema 校验失败率失败率高说明 Schema 设计有问题7嵌套不超过 3 层太深 LLM 容易出错8Schema 和 Harness 配合使用输入约束 输出约束 完整约束9用 Min/Max 限制数值范围防止 LLM 输出离谱的数值10输出校验失败时降级为文本给用户兜底体验下一篇我们聊Grill Me 反问式规划Agent 不是应该什么都听用户的而是要学会反问——在动手之前先搞清楚需求边界、风险点和遗漏信息。这是一套让 Agent 从执行者变成协作者的关键模式。本文是 AI Agent 开发实战系列第 8 篇系列目录AI Agent 核心概念与架构三大基石之 LLM 调用与 Prompt 工程三大基石之记忆系统三大基石之工具调用Java 生态 Agent 框架横评用 Spring AI 搭建第一个 AgentHarness Engineering 与约束管理本文输出 Schema 约束与结构化输出

相关新闻

2026/8/19 22:16:21

BBDown完整使用手册:让哔哩哔哩视频下载变成一行命令的事

BBDown完整使用手册:让哔哩哔哩视频下载变成一行命令的事 【免费下载链接】BBDown Bilibili Downloader. 一个命令行式哔哩哔哩下载器. 项目地址: https://gitcode.com/gh_mirrors/bb/BBDown 周末想躺在沙发上把追了一个月的纪录片一口气看完,结果…

2026/8/19 22:11:21

从0到1产品设计全流程:MVP验证与PRD撰写实战指南

1. 从0到1:产品设计的核心挑战与价值 做产品,尤其是从零开始做一个新产品,听起来很酷,但真正干过的人都知道,这活儿既烧脑又烧心。它不像在现有产品上做个功能迭代,修修补补,有迹可循。从0到1&a…

2026/8/19 22:11:21

智能查询计划灰度发布,先验证什么

智能查询计划灰度发布,先验证什么 把模型接到优化器前,先不要急着看它能不能跑出更快的计划。灰度阶段真正要回答的是:它在什么查询上可靠,失效时是否能退回已有优化器,以及观测本身会不会干扰主业务。 先做影子评估&a…

2026/8/19 23:31:39

离线纳什求解与在线树搜索融合:图博弈多智能体决策新范式

1. 从棋盘到图:多智能体博弈的战场变迁在传统的多智能体博弈研究中,我们常常想象一个棋盘——围棋、国际象棋,或者一个二维网格世界。智能体在其中移动、交互、争夺资源或达成目标。然而,现实世界中的许多交互远比棋盘复杂&#x…

2026/8/19 23:31:39

树莓派AI摄像头Docker化部署:环境隔离与一键部署实践

1. 项目概述:为什么要在树莓派上用Docker跑AI摄像头? 如果你手头有一块树莓派,又对AI视觉应用感兴趣,比如想做个智能门铃、宠物监控器或者一个能识别手势的互动装置,那你大概率会碰到一个经典难题:环境配置…

2026/8/19 23:31:39

零基础C语言入门:Visual Studio 2022社区版安装与Hello World实战指南

这类工具最值得先看的不是功能列表,而是能不能在普通环境里稳定跑起来。对于零基础学C语言来说,Visual Studio(简称VS)是一个绕不开的集成开发环境,它功能强大,但安装和初始配置对新手来说可能是个坎。很多…

2026/8/19 23:31:39

掌握循环语句:while与for的实战技巧

一、while循环语句:1、倘若你所给予的条件是真实的, 那么它便会持续不断地循环执行下去(即死循环), 故而在运用while语句之际, 你就得思索好条件该如何给予。2、任何事情确实都是是能够进行商量的, 你能够运用break语句, 就算while条件处于为…

2026/8/19 23:31:39

单片机毕业设计-基于 STM32 或 51 单片机的带温度补偿多路距离监测预警系统设计 基于 STM32 或 51 单片机的三路 HC-SR04 测距液晶显示报警系统设计(023003)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/8/19 4:14:28

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/19 15:09:57

工业传感器与变送器详解:序章 从物理世界到工业数据

序章 从物理世界到工业数据 ——重新认识工业传感器与变送器 工业自动化系统正变得日益复杂。今天的工业现场早已不是简单的控制回路,而是由多层技术共同构成的立体体系:PLC、DCS、SCADA、MES、工业互联网、边缘计算与人工智能。控制系统可以执行复杂算法,工业网络可以实现…

2026/8/19 0:00:35

【单片机课程设计/毕业设计】基于 STM32 与 WiFi 模块的室内通风智能管控系统设计 基于 STM32 的人体存在感知自适应风扇控制系统设计(018503)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/8/19 0:00:35

AI如何驱动数学猜想生成:从大语言模型到自动化数学发现

1. 项目概述:当AI开始“猜”数学定理 最近在AI研究圈里,一个名为“Moonshine”的项目引起了不小的讨论。这名字本身就挺有意思,直译是“月光”,但在数学史上,它特指一个神秘而美丽的联系——魔群月光猜想,连…

2026/8/19 0:00:36

Agentic Web:构建智能体原生网络的基础设施挑战与四大支柱

1. 从“被动网络”到“能动网络”:一个正在发生的范式转移 如果你最近关注AI和Web技术的前沿动态,可能会频繁听到“Agentic Web”这个词。它不像“Web3”那样带着浓厚的金融色彩,也不像“元宇宙”那样充满科幻感,但它所描绘的未来…

2026/8/18 18:23:10

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/19 4:14:38

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/19 16:39:34

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…