截至 2024 年 5 月 2 日,Spring AI 旧版 OutputParser、BeanOutputParser、 ListOutputParser 和MapOutputParser 类已弃用,推荐改用新版 StructuredOutputConverter、BeanOutputConverter、ListOutputConverter 和 MapOutputConverter 实现。如下图:
后者可直接替换前者并提供相同功能,此次变更主要出于命名规范化考虑(实际并无解析操作),同时也为对齐 Spring 的org.springframework.core.convert.converter 包并引入增强功能。
大语言模型生成结构化输出的能力对依赖可靠解析结果的下游应用至关重要,例如,一个请假应用程序需要从用户输入的信息中提取请假信息,并且使用 JSON 格式返回,如果返回的 JSON 格式非法,将直接导致应用不能继续下去。由此,开发者希望快速、准确的将 AI 模型返回结果转换为 JSON、XML 或 Java Class 等数据类型,以便传递给其他应用函数和方法。
Spring AI 结构化输出转换器(Structured Output Converter)帮助将 LLM(大语言模型)的输出转为结构化格式。如下图所示,该方案围绕 LLM 文本补全端点运作:
使用通用补全 API 从大语言模型(LLM)生成结构化输出时,输入输出的处理至关重要,需谨慎对待,原因如下:
输入处理的重要性:在调用 LLM 前,需通过结构化输出转换器向提示词追加格式指令,为模型生成预期输出结构提供明确指导,这些指令如同蓝图,引导模型响应符合指定格式。若输入的提示词或格式指令不清晰、不准确,模型可能无法理解要求,从而生成不符合预期的输出。
输出处理的必要性:LLM 调用后,转换器需将模型的原始文本输出转换为结构化类型实例,包括解析原始文本并映射为 JSON、XML 或领域特定数据结构等。但 AI 模型并不保证按请求返回结构化输出,可能因无法理解提示或生成所需结构而输出非结构化内容,因此需实现验证机制确保模型输出符合预期。
📢注意事项:
(1)StructuredOutputConverter 会尽力将模型输出转换为结构化格式,但 AI 模型并不保证按请求返回结构化输出(可能无法理解提示词或生成所需结构),建议实现验证机制以确保模型输出符合预期。
(2)StructuredOutputConverter 不用于大语言模型(LLM)的工具调用,因为此功能默认情况下本身就会提供结构化输出。
StructuredOutputConverter 接口允许从基于文本的 AI 模型输出中获取结构化结果,例如映射到 Java 类或值数组。其接口定义为:
package org.springframework.ai.converter; import org.springframework.core.convert.converter.Converter; public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider { }
该接口继承了 Spring 的 Converter<String, T> 和 FormatProvider 接口,其中 Converter 接口定义如下:
package org.springframework.core.convert.converter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * 类型转换的核心接口,将源类型对象转换为目标类型对象 * 实现类需实现线程安全的转换逻辑 * * @param <S> 源类型 * @param <T> 目标类型 */ @FunctionalInterface public interface Converter<S, T> { /** * 将源对象转换为目标类型 * * @param source 源对象,可为 null * @return 转换后的目标对象,可为 null * @throws IllegalArgumentException 当源对象无法转换为目标类型时抛出 */ @Nullable T convert(S source); /** * 返回一个组合转换器,先执行当前转换器,再执行 after 转换器 * * @param <U> 最终结果类型 * @param after 后续要应用的转换器,不能为 null * @return 组合转换器实例 * @throws IllegalArgumentException 当 after 为 null 时抛出 */ default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) { Assert.notNull(after, "'after' Converter must not be null"); return (s) -> { // 执行当前转换器 T initialResult = this.convert(s); // 仅当前转换结果非空时执行后续转换器 return initialResult != null ? after.convert(initialResult) : null; }; } }
FormatProvider 接口定义如下:
package org.springframework.ai.converter; /** * 格式提供者接口,用于标识支持格式信息获取的组件 * 实现类通常用于提供数据格式描述,如JSON、XML等 */ public interface FormatProvider { /** * 获取格式描述字符串 * * @return 格式描述 */ String getFormat(); }
下面是 MapOutputConverter 类中的 getFormat() 方法:
public class MapOutputConverter extends AbstractMessageOutputConverter<Map<String, Object>> { //... public String getFormat() { String raw = "Your response should be in JSON format.\nThe data structure for the JSON should match this Java class: %s\nDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\nRemove the ```json markdown surrounding the output including the trailing \"```\".\n"; return String.format(raw, HashMap.class.getName()); } }
下图展示了使用结构化输出 API 时的数据流:
FormatProvider 向 AI 模型提供特定格式指南,使其生成可被 Converter 转换为目标类型 T 的文本输出。以下是此类格式指令的示例:
Your response should be in JSON format. The data structure for the JSON should match this Java class: java.util.HashMap Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
翻译成中文:
你的响应应以JSON格式呈现。 JSON的数据结构应与此Java类匹配:java.util.HashMap 不要包含任何解释,仅按照此格式提供符合RFC8259标准的JSON响应,不得有偏差。
格式指令通常通过 PromptTemplate 追加到用户输入末尾,如下所示:
StructuredOutputConverter outputConverter = ... String userInputTemplate = """ ... user text input .... {format} """; // 包含 "format" 占位符的用户输入。 Prompt prompt = new Prompt( new PromptTemplate( this.userInputTemplate, // 将 "format" 占位符替换为转换器的格式指令 Map.of(..., "format", outputConverter.getFormat()) ).createMessage());
而,Converter<String, T> 负责将模型的文本输出转换为指定类型 T 的实例。
目前 Spring AI 提供了以下 StructuredOutputConverter 接口实现类:
它们之间的类图如下:
该抽象类仅提供预配置的 GenericConversionService 用于将 LLM 输出转换为目标格式,未提供默认 FormatProvider 实现。源码如下:
package org.springframework.ai.converter; import org.springframework.core.convert.support.DefaultConversionService; public abstract class AbstractConversionServiceOutputConverter<T> implements StructuredOutputConverter<T> { private final DefaultConversionService conversionService; public AbstractConversionServiceOutputConverter(DefaultConversionService conversionService) { this.conversionService = conversionService; } public DefaultConversionService getConversionService() { return this.conversionService; } }
其中,DefaultConversionService 继承了 GenericConversionService,定义如下:
public class DefaultConversionService extends GenericConversionService { //... }
该抽象类提供预配置的 MessageConverter 用于将 LLM 输出转换为目标格式,未提供默认 FormatProvider 实现。源码如下:
package org.springframework.ai.converter; import org.springframework.messaging.converter.MessageConverter; public abstract class AbstractMessageOutputConverter<T> implements StructuredOutputConverter<T> { private MessageConverter messageConverter; public AbstractMessageOutputConverter(MessageConverter messageConverter) { this.messageConverter = messageConverter; } public MessageConverter getMessageConverter() { return this.messageConverter; } }
该类通过指定的 Java 类(例如 Bean)或 ParameterizedTypeReference,该转换器(Converter)使用 FormatProvider 实现指导 AI 模型生成符合 DRAFT_2020_12、JSON Schema 的响应(基于指定 Java 类生成),随后用 ObjectMapper 将 JSON 输出反序列化为目标类的 Java 对象实例。源码:
/** * 将JSON格式输出转换为Java Bean的结构化输出转换器 * * @param <T> 目标Bean类型 */ public class BeanOutputConverter<T> implements StructuredOutputConverter<T> { private final Logger logger; /** 目标Bean的Type类型信息,用于泛型类型解析 */ private final Type type; /** 用于JSON与Java对象转换的Jackson ObjectMapper */ private final ObjectMapper objectMapper; /** 生成的JSON Schema字符串,用于提示LLM生成符合结构的JSON */ private String jsonSchema; //... }
该类扩展了 AbstractMessageOutputConverter 的功能,通过一个 FormatProvider 实现,引导 AI 模型生成符合 RFC8259 标准的 JSON 响应。此外,它还包含一个转换器实现,该实现利用提供的MessageConverter 将 JSON 有效负载转换为 java.util.Map<String, Object> 实例。
源码如下:
package org.springframework.ai.converter; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import org.springframework.lang.NonNull; import org.springframework.messaging.Message; import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.support.MessageBuilder; /** * 将JSON格式文本转换为Map<String, Object>的输出转换器 * 支持处理带```json```标记的markdown格式JSON文本 */ public class MapOutputConverter extends AbstractMessageOutputConverter<Map<String, Object>> { /** * 使用Jackson JSON消息转换器初始化 */ public MapOutputConverter() { super(new MappingJackson2MessageConverter()); } /** * 将JSON格式字符串转换为Map对象 * * @param text 待转换的JSON文本,可为markdown格式(带```json```标记) * @return 转换后的Map对象 * @throws IllegalArgumentException 当文本无法解析为JSON时抛出 */ @Override public Map<String, Object> convert(@NonNull String text) { // 移除markdown代码块标记(如果存在) if (text.startsWith("```json") && text.endsWith("```")) { text = text.substring(7, text.length() - 3); } // 构建消息对象并使用Jackson转换器进行解析 Message<?> message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF_8)).build(); return (Map<String, Object>) getMessageConverter().fromMessage(message, HashMap.class); } /** * 获取格式描述,用于提示模型输出符合要求的JSON格式 * * @return 格式描述字符串,包含Java类信息和JSON规范要求 */ @Override public String getFormat() { String template = "Your response should be in JSON format.\n" + "The data structure for the JSON should match this Java class: %s\n" + "Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\n" + "Remove the ```json markdown surrounding the output including the trailing \"```\".\n"; return String.format(template, HashMap.class.getName()); } }
该类继承了 AbstractConversionServiceOutputConverter,包含专为逗号分隔列表输出定制的 FormatProvider 实现。该转换器利用提供的 ConversionService 将模型文本输出转换为 java.util.List。
源码如下:
package org.springframework.ai.converter; import java.util.List; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.lang.NonNull; /** * 将逗号分隔的字符串转换为List<String>的输出转换器 * 支持将"a,b,c"格式的文本转换为对应的字符串列表 */ public class ListOutputConverter extends AbstractConversionServiceOutputConverter<List<String>> { /** * 使用默认转换服务初始化转换器 */ public ListOutputConverter() { this(new DefaultConversionService()); } /** * 使用自定义转换服务初始化转换器 * * @param defaultConversionService 自定义转换服务实例 */ public ListOutputConverter(DefaultConversionService defaultConversionService) { super(defaultConversionService); } /** * 获取格式描述,用于提示模型输出符合要求的逗号分隔值列表 * * @return 格式描述字符串,包含示例格式说明 */ @Override public String getFormat() { return "Respond with only a list of comma-separated values, without any leading or trailing text.\nExample format: foo, bar, baz\n"; } /** * 将逗号分隔的字符串转换为List<String> * * @param text 待转换的字符串,格式应为"value1,value2,value3" * @return 转换后的字符串列表 * @throws IllegalArgumentException 当转换失败时抛出 */ @Override public List<String> convert(@NonNull String text) { return (List<String>) getConversionService().convert(text, List.class); } }
以下 将提供使用现有转换器生成结构化输出的指南。
以下示例展示如何使用 BeanOutputConverter 生成演员作品表,先使用高阶 Fluent 式 ChatClient API 应用 BeanOutputConverter 的方式:
package com.hxstrive.springai.springai_openai.converter1.controller; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController class MyController { @Autowired private ChatModel chatModel; // 定义 Record record ActorsFilms(String actor, List<String> movies) { } // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt() // 日志Advisor,输出请求/响应日志 .advisors(new SimpleLoggerAdvisor()) // 用户提示词 .user(u -> u.text("生成{actor}的5部电影作品表。") .param("actor", "李连杰")) // 调用实体大模型 .call() // 将结果转换成实体 .entity(ActorsFilms.class); return JSONObject.toJSONString(actorsFilms, JSONWriter.Feature.PrettyFormat); } }
运行应用,效果如下图:
后端输出日志如下:
2025-07-16T20:39:01.151+08:00 DEBUG 33492 --- [springai_demo1] [nio-8080-exec-1] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: ChatClientRequest[prompt=Prompt{messages=[UserMessage{content='生成李连杰的5部电影作品表。', properties={messageType=USER}, messageType=USER}], modelOptions=OpenAiChatOptions: {"streamUsage":false,"model":"gpt-3.5-turbo","temperature":0.7}}, context={spring.ai.chat.client.output.format=Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in your response. Remove the ```json markdown from the output. Here is the JSON Schema instance your output must adhere to: ```{ "$schema" : "https://json-schema.org/draft/2020-12/schema", "type" : "object", "properties" : { "actor" : { "type" : "string" }, "movies" : { "type" : "array", "items" : { "type" : "string" } } }, "additionalProperties" : false }``` }]
仔细观察上述输出日志,modelOptions 选项中多出了很多关于输出格式的说明,如“spring.ai.chat.client.output.format=Your response should be in JSON format....”。
除了通过 Fluent 式,还可以直接使用底层 ChatModel API,例如:
package com.hxstrive.springai.springai_openai.converter2.controller; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController class MyController { @Autowired private ChatModel chatModel; // 定义 Record @JsonPropertyOrder({"movies", "actor"}) record ActorsFilms(String actor, List<String> movies) { } // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { // 创建转换器 BeanOutputConverter BeanOutputConverter<ActorsFilms> beanOutputConverter = new BeanOutputConverter<>(ActorsFilms.class); // 提示词 PromptTemplate promptTemplate = new PromptTemplate(""" 生成{actor}的5部电影作品表。 {format} """); promptTemplate.add("actor", "李连杰"); promptTemplate.add("format", beanOutputConverter.getFormat()); // 调用大模型 ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors(new SimpleLoggerAdvisor()) .build(); String result = chatClient.prompt(promptTemplate.create()).call().content(); // 使用 BeanOutputConverter 对结果进行转换操作 ActorsFilms actorsFilms = beanOutputConverter.convert(result); return JSONObject.toJSONString(actorsFilms, JSONWriter.Feature.PrettyFormat); } }
运行示例,输出效果如下图:
BeanOutputConverter 通过 @JsonPropertyOrder 注解支持自定义 JSON Schema 中的属性顺序,该注解允许指定属性在模式中的精确出现顺序(无视 class 或 record 中的声明顺序)。
例如,确保 ActorsFilms record 中的属性特定顺序:
@RestController class MyController { @Autowired private ChatModel chatModel; // 定义 Record @JsonPropertyOrder({"movies", "actor"}) record ActorsFilms(String actor, List<String> movies) { } // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { //... } }
注意,该注解同时适用于 record 和常规 Java 类。
使用 ParameterizedTypeReference 构造函数指定更复杂的目标类结构。例如,表示演员及其作品表的列表:
package com.hxstrive.springai.springai_openai.converter3.controller; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController class MyController { @Autowired private ChatModel chatModel; // 定义 Record record ActorsFilms(String actor, List<String> movies) { } // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt() .advisors(new SimpleLoggerAdvisor()) .user("生成成龙和洪金宝五部电影的作品表。") .call() .entity(new ParameterizedTypeReference<List<ActorsFilms>>() {}); return JSONObject.toJSONString(actorsFilms, JSONWriter.Feature.PrettyFormat); } }
运行示例,效果如下图:
查看输出日志如下:
2025-07-16T20:51:55.011+08:00 DEBUG 35080 --- [springai_demo1] [nio-8080-exec-1] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: ChatClientRequest[prompt=Prompt{messages=[UserMessage{content='生成成龙和洪金宝五部电影的作品表。', properties={messageType=USER}, messageType=USER}], modelOptions=OpenAiChatOptions: {"streamUsage":false,"model":"gpt-3.5-turbo","temperature":0.7}}, context={spring.ai.chat.client.output.format=Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in your response. Remove the ```json markdown from the output. Here is the JSON Schema instance your output must adhere to: ```{ "$schema" : "https://json-schema.org/draft/2020-12/schema", "type" : "array", "items" : { "type" : "object", "properties" : { "actor" : { "type" : "string" }, "movies" : { "type" : "array", "items" : { "type" : "string" } } }, "additionalProperties" : false } }``` }]
或直接使用底层 ChatModel API:
package com.hxstrive.springai.springai_openai.converter4.controller; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController class MyController { @Autowired private ChatModel chatModel; // 定义 Record record ActorsFilms(String actor, List<String> movies) { } // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { BeanOutputConverter<List<ActorsFilms>> outputConverter = new BeanOutputConverter<>( new ParameterizedTypeReference<List<ActorsFilms>>() { }); // 提示词模板 PromptTemplate promptTemplate = new PromptTemplate(""" 生成成龙和洪金宝五部电影的作品表。 {format} """); promptTemplate.add("format", outputConverter.getFormat()); // 调用大模型 ChatClient chatClient = ChatClient.builder(chatModel).defaultAdvisors(new SimpleLoggerAdvisor()).build(); String result = chatClient.prompt(promptTemplate.create()).call().content(); // 手动转换 List<ActorsFilms> actorsFilms = outputConverter.convert(result); return JSONObject.toJSONString(actorsFilms, JSONWriter.Feature.PrettyFormat); } }
以下片段展示如何使用 MapOutputConverter 将模型输出转换为 Map 中的数值列表:
package com.hxstrive.springai.springai_openai.converter5.controller; import com.alibaba.fastjson2.JSONObject; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController class MyController { @Autowired private ChatModel chatModel; // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { Map<String, Object> result = ChatClient.create(chatModel).prompt() .advisors(new SimpleLoggerAdvisor()) .user(u -> u.text("给我一个{subject}的列表") .param("subject", "键名“numbers”下从1到9的数字数组")) .call() .entity(new ParameterizedTypeReference<Map<String, Object>>() {}); return JSONObject.toJSONString(result); } }
运行程序,效果如下图:
查看输出日志:
2025-07-16T20:55:01.820+08:00 DEBUG 27008 --- [springai_demo1] [nio-8080-exec-1] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: ChatClientRequest[prompt=Prompt{messages=[UserMessage{content='给我一个键名“numbers”下从1到9的数字数组的列表', properties={messageType=USER}, messageType=USER}], modelOptions=OpenAiChatOptions: {"streamUsage":false,"model":"gpt-3.5-turbo","temperature":0.7}}, context={spring.ai.chat.client.output.format=Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in your response. Remove the ```json markdown from the output. Here is the JSON Schema instance your output must adhere to: ```{ "$schema" : "https://json-schema.org/draft/2020-12/schema", "type" : "object", "additionalProperties" : false }``` }]
或直接使用底层 ChatModel API:
package com.hxstrive.springai.springai_openai.converter6.controller; import com.alibaba.fastjson2.JSONObject; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.MapOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController class MyController { @Autowired private ChatModel chatModel; // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { MapOutputConverter mapOutputConverter = new MapOutputConverter(); PromptTemplate promptTemplate = new PromptTemplate(""" 给我一个{subject}的列表 {format} """); promptTemplate.add("format", mapOutputConverter.getFormat()); promptTemplate.add("subject", "键名“numbers”下从1到9的数字数组"); ChatClient chatClient = ChatClient.builder(chatModel).defaultAdvisors(new SimpleLoggerAdvisor()).build(); String result = chatClient.prompt(promptTemplate.create()).call().content(); Map<String, Object> map = mapOutputConverter.convert(result); return JSONObject.toJSONString(map); } }
以下片段展示如何使用 ListOutputConverter 将模型输出转换为冰淇淋口味列表:
package com.hxstrive.springai.springai_openai.converter7.controller; import com.alibaba.fastjson2.JSONObject; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController class MyController { @Autowired private ChatModel chatModel; // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { List<String> flavors = ChatClient.create(chatModel).prompt() .user(u -> u.text("列出五种 {subject}") .param("subject", "冰淇淋口味")) .call() .entity(new ListOutputConverter(new DefaultConversionService())); return JSONObject.toJSONString(flavors); } }
运行程序,效果如下图:
查询输出日志:
2025-07-16T20:58:22.521+08:00 DEBUG 23360 --- [springai_demo1] [nio-8080-exec-2] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: ChatClientRequest[prompt=Prompt{messages=[UserMessage{content='列出五种 冰淇淋口味', properties={messageType=USER}, messageType=USER}], modelOptions=OpenAiChatOptions: {"streamUsage":false,"model":"gpt-3.5-turbo","temperature":0.7}}, context={spring.ai.chat.client.output.format=Respond with only a list of comma-separated values, without any leading or trailing text. Example format: foo, bar, baz }]
或直接使用底层 ChatModel API:
package com.hxstrive.springai.springai_openai.converter8.controller; import com.alibaba.fastjson2.JSONObject; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController class MyController { @Autowired private ChatModel chatModel; // http://localhost:8080/ai @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") public String ai() { ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService()); PromptTemplate promptTemplate = new PromptTemplate(""" 列出五种 {subject} {format} """); promptTemplate.add("subject", "冰淇淋口味"); promptTemplate.add("format", listOutputConverter.getFormat()); ChatClient chatClient = ChatClient.builder(chatModel).defaultAdvisors(new SimpleLoggerAdvisor()).build(); String result = chatClient.prompt(promptTemplate.create()).call().content(); List<String> list = listOutputConverter.convert(result); return JSONObject.toJSONString(list); } }
以下 AI 模型已测试支持 List、Map 和 Bean 结构化输出:
OpenAI
Anthropic Claude 3
Azure OpenAI
Mistral AI
Ollama
Vertex AI Gemini
部分 AI 模型提供专用配置选项以生成结构化(通常为 JSON)输出。
OpenAI 结构化输出确保模型生成严格符合所提供 JSON Schema 的响应。可选模式包括:保证生成有效 JSON 的 JSON_OBJECT,或通过 spring.ai.openai.chat.options.responseFormat 配置、确保响应匹配指定 schema 的 JSON_SCHEMA。
Azure OpenAI 提供 spring.ai.azure.openai.chat.options.responseFormat 选项指定模型输出格式。设置为 { "type": "json_object" } 可启用 JSON 模式,确保模型生成的消息为有效 JSON。
Ollama 提供 spring.ai.ollama.chat.options.format 选项指定响应格式,当前仅接受 JSON 值。
Mistral AI 提供 spring.ai.mistralai.chat.options.responseFormat 选项指定响应格式,设置为 { "type": "json_object" } 可启用 JSON 模式,确保模型生成的消息为有效 JSON。
关于更多结构化输出知识,请参考官方文档。