Spring AI 统一结构化返回:ChatModel / ChatClient 两种实现方式

📅 2026/8/15 12:39:04
Spring AI 统一结构化返回:ChatModel / ChatClient 两种实现方式
在Spring AI开发中大模型默认返回自然语言文本混杂多余描述、代码块标记业务代码无法直接反序列化使用。本文基于Spring AI官方规范详解结构化输出Structured Output核心概念、两套实现方案、技术选型对比及高频踩坑解决方案适配日常业务开发与项目落地。一、结构化输出Structured Output核心概念1.1 官方定义引用 Spring AI 官方定义如果您想从 LLM 接收结构化输出Structured Output 可以协助将 ChatModel/ChatClient 方法的返回类型从 String 更改为其他类型。LLM 生成结构化输出的能力对于依赖可靠解析输出值的下游应用程序非常重要。开发人员希望快速将 AI 模型的结果转换为可以传递给其他应用程序函数和方法的数据类型例如 JSON、XML 或 Java 类。Spring AI 结构化输出转换器可自动将 LLM 原始文本输出转为标准化结构化格式。1.2 传统开发业务痛点不做结构化约束时大模型返回的文本通常混杂自然语言描述并非纯净结构化数据示例如下好的这是结果{title:SpringAI,desc:Java AI开发框架}这种格式存在严重问题前端、后端业务代码无法直接反序列化需要手动编写正则清洗文本开发效率低、容错性差极易出现解析异常。1.3 结构化输出核心目标强制约束大模型输出规则让模型稳定输出标准结构化数据JSON由 Spring AI 框架自动完成 原始文本 → Java 实体对象 的转换彻底规避手动清洗数据的问题。1.4 Spring AI 两大实现技术路线底层APIChatModel OutputParser灵活性最高支持多角色模板、外部文件Prompt适配复杂场景高层DSLChatClient 流式调用 call().entity()代码极简内置封装转换器适合快速开发二、前置准备定义统一接收POJO本文所有案例统一使用该实体类接收结构化JSON结果简化代码冗余基于Lombok简化开发import lombok.Data;/*** AI结构化输出统一接收实体*/Datapublic class ArticleDTO {// 文章标题private String title;// 文章简介private String description;// 文章作者private String author;}三、方案一底层 ChatModel JsonOutputParser标准企业方案JsonOutputParser 是Spring AI官方核心结构化输出转换器也是复杂项目首选方案。核心具备两大能力getFormatInstructions()自动生成JSON格式约束提示词自动注入Promptparse()接收模型原始文本自动解析转换为对应Java实体类完整可运行代码import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.ai.chat.prompt.SystemPromptTemplate;import org.springframework.ai.parser.JsonOutputParser;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;import java.util.Map;RestControllerpublic class StructuredOutputController {private final ChatModel chatModel;// 构造器注入统一Spring AI开发规范public StructuredOutputController(ChatModel chatModel) {this.chatModel chatModel;}GetMapping(/structured/model-parser)public ArticleDTO structuredByModel(String topic) {// 1. 创建结构化输出转换器绑定接收实体类JsonOutputParserArticleDTO parser new JsonOutputParser(ArticleDTO.class);// 2. 定义系统角色模板基础输出约束 自动注入格式规范SystemPromptTemplate sysTemplate new SystemPromptTemplate(禁止输出任何前言、解释、markdown代码块标记。{format_instructions});// 注入框架自动生成的JSON格式约束Message sysMsg sysTemplate.createMessage(Map.of(format_instructions, parser.getFormatInstructions()));// 3. 定义用户角色模板动态接收业务参数PromptTemplate userTemplate new PromptTemplate(生成一篇关于{topic}的短文信息);Message userMsg userTemplate.createMessage(Map.of(topic, topic));// 4. 组装多角色Prompt消息Prompt prompt new Prompt(List.of(sysMsg, userMsg));// 5. 调用大模型自动解析文本为Java实体String rawResult chatModel.call(prompt).getResult().getOutput().getText();return parser.parse(rawResult);}}适用场景需要手动管理SystemPrompt、外部txt提示词文件、复杂多角色消息编排的场景与PromptTemplate、多消息角色体系完全无缝衔接是中大型复杂AI项目的标准方案。四、方案二高层 ChatClient DSL 流式写法简洁开发首选ChatClient 高层DSL内部已封装结构化转换器提供 call().entity(ClassT) 极简API底层依旧复用JsonOutputParser无需手动创建解析器代码极度简洁适合快速开发、接口原型搭建。4.1 基础字符串User写法适合固定提示词、无复杂参数渲染的简单场景GetMapping(/structured/client-simple)public ArticleDTO structuredClientSimple(String topic) {return chatClient.prompt()// 系统级输出约束.system(禁止输出多余文字不要json代码块标记)// 固定用户提示词.user(生成一篇关于 topic 的短文信息).call()// 自动结构化解析为实体.entity(ArticleDTO.class);}4.2 Consumer模板写法重点推荐支持 {key} 占位符模板 .param() 动态绑定参数无需手动new PromptTemplate兼顾简洁性与动态性是轻量级业务接口最优写法。Lambda表达式写法日常开发主流GetMapping(/structured/client-consumer)public ArticleDTO structuredClientConsumer(String topic) {return chatClient.prompt().system(禁止输出多余文字不要json代码块标记)// Lambda实现Consumer动态模板传参.user(spec - spec.text(生成一篇关于{topic}的短文信息).param(topic, topic)).call().entity(ArticleDTO.class);}匿名内部类写法原理学习参考Lambda语法糖脱糖后的原生写法兼容所有Java版本仅用于理解底层原理项目中统一使用Lambda写法GetMapping(/structured/client-anonymous)public ArticleDTO structuredClientAnonymous(String topic) {return chatClient.prompt().system(禁止输出多余文字不要json代码块标记)// 完整匿名内部类实现Consumer接口.user(new ConsumerChatClient.PromptUserSpec() {Overridepublic void accept(ChatClient.PromptUserSpec spec) {spec.text(生成一篇关于{topic}的短文信息).param(topic, topic);}}).call().entity(ArticleDTO.class);}Consumer核心原理Consumer是Java8函数式接口仅包含一个抽象方法支持Lambda简化写法FunctionalInterfacepublic interface ConsumerT {void accept(T t);}开发规范项目统一使用Lambda表达式代码简洁可读性高匿名内部类仅用于原理学习。五、两套API核心选型对比两套方案底层核心一致均基于Spring AI OutputParser仅封装层级不同可根据项目复杂度选型开发方式核心API优点局限适用场景底层原始APIChatModel JsonOutputParser高度可控支持外部文件模板、灵活组装多角色消息适配复杂提示词工程代码量偏多需手动处理解析逻辑中大型项目、提示词复杂、多角色消息编排场景高层流式DSLChatClient call().entity()代码极简内置结构化转换器开发效率极高外部txt模板编排不够直观小型业务接口、快速原型开发、简单AI功能底层原理chatClient.prompt().call().entity() 底层会自动创建JsonOutputParser与底层API本质一致。六、开发高频踩坑解决方案必看6.1 问题模型返回Markdown JSON代码块解析报错现象大模型返回内容携带json标记导致实体解析失败json{title:xxx,description:xxx,author:xxx}解决方案1优先在系统提示词中强制约束输出规则禁止使用markdown代码块包裹返回内容只输出纯净JSON字符串。解决方案2兜底工具封装公共文本清洗工具类兼容异常场景/*** 清洗大模型返回的Markdown JSON标记*/public static String cleanJsonMarkdown(String text) {return text.replaceAll(json, ).replaceAll(, ).trim();}6.2 问题字段缺失、格式错乱导致解析异常问题现象模型返回字段缺失、JSON格式错误抛出ParseException程序直接报错。解决方案所有结构化解析逻辑必须捕获异常打印原始返回文本方便快速定位问题try {return parser.parse(rawResult);} catch (ParseException e) {// 打印模型原始返回内容精准排查格式问题log.error(结构化输出解析失败原始返回{}, rawResult, e);throw new RuntimeException(模型返回格式异常请重试);}6.3 占位符大小写严格匹配模板中的占位符名称如{topic}与param、Map传入的key名称必须大小写完全一致否则占位符无法渲染导致提示词失效、输出结果异常。七、总结结构化输出的核心价值是标准化模型返回值实现AI结果与业务代码的无缝对接彻底告别手动文本清洗复杂项目、自定义提示词工程优先使用 ChatModel JsonOutputParser灵活性拉满简单接口、快速开发优先使用 ChatClient DSL Lambda模板传参代码简洁高效开发必须做好输出约束、异常捕获、文本兜底清洗保证接口稳定性。