005-Spring AI Alibaba Structured Output 功能完整案例

本案例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 Structured Output 功能,实现将大模型输出转换为结构化数据(如 Java Bean、JSON、Map 和 List)。
1. 案例目标
我们将创建一个包含多个结构化输出功能的 Web 应用:
- Java Bean 输出 (
/bean/*):将大模型输出转换为指定的 Java Bean 对象。 - JSON 格式输出 (
/json/*):使用 DashScope 的 JSON 模式,确保输出为有效的 JSON 格式。 - Map 和 List 输出 (
/map-list/*):将大模型输出转换为 Map 或 List 集合。
2. 技术栈与核心依赖
- Spring Boot 3.x
- Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
- Maven (项目构建工具)
在 pom.xml 中,你需要引入以下核心依赖:
<dependencies><!-- Spring AI Alibaba 核心启动器,集成 DashScope --><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId></dependency><!-- Spring Web 用于构建 RESTful API --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>3. 项目配置
在 src/main/resources/application.yml 文件中,配置你的 DashScope API Key 和响应格式。
server:port: 10007spring:application:name: spring-ai-alibaba-structured-exampleai:dashscope:api-key: ${AI_DASHSCOPE_API_KEY}chat:response-format: json重要提示:请将 AI_DASHSCOPE_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。配置中的 response-format: json 表示默认使用 JSON 格式响应。
4. 定义实体类
首先,我们定义一个用于接收结构化输出的实体类 BeanEntity:
package com.alibaba.cloud.ai.example.outparser.entity;import com.fasterxml.jackson.annotation.JsonPropertyOrder;@JsonPropertyOrder({"title", "date", "author", "content"}) // 指定属性的顺序
public class BeanEntity {private String title;private String author;private String date;private String content;public BeanEntity() {}// Getter 和 Setter 方法public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public String getDate() {return date;}public void setDate(String date) {this.date = date;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}@Overridepublic String toString() {return "StreamToBeanEntity{" +"title='" + title + '\'' +", author='" + author + '\'' +", date='" + date + '\'' +", content='" + content + '\'' +'}';}
}5. 编写控制器代码
5.1 BeanController.java - Java Bean 输出
实现将大模型输出转换为 Java Bean 对象的功能。
package com.alibaba.cloud.ai.example.outparser.controller;import com.alibaba.cloud.ai.example.outparser.entity.BeanEntity;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
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.converter.BeanOutputConverter;
import org.springframework.ai.template.st.StTemplateRenderer;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.util.Map;
import java.util.Objects;@RestController
@RequestMapping("/bean")
public class BeanController {private static final Logger log = LoggerFactory.getLogger(BeanController.class);private final ChatClient chatClient;private final ChatModel chatModel;private final BeanOutputConverter<BeanEntity> converter;private final String format;public BeanController(ChatClient.Builder builder, ChatModel chatModel) {this.chatModel = chatModel;this.converter = new BeanOutputConverter<>(new ParameterizedTypeReference<BeanEntity>() {});this.format = converter.getFormat();log.info("format: {}", format);this.chatClient = builder.build();}@GetMapping("/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "以影子为作者,写一篇200字左右的有关人工智能诗篇") String query) {String result = chatClient.prompt(query).call().content();log.info("result: {}", result);assert result != null;try {BeanEntity convert = converter.convert(result);log.info("反序列成功,convert: {}", convert);} catch (Exception e) {log.error("反序列化失败");}return result;}@GetMapping("/chat-format")public BeanEntity simpleChatFormat(@RequestParam(value = "query", defaultValue = "以影子为作者,写一篇200字左右的有关人工智能诗篇") String query) {return chatClient.prompt(query).call().entity(BeanEntity.class);}@GetMapping("/chat-model-format")public String chatModel(@RequestParam(value = "query", defaultValue = "以影子为作者,写一篇200字左右的有关人工智能诗篇") String query) {String template = query + "{format}";PromptTemplate promptTemplate = PromptTemplate.builder().template(template).variables(Map.of("format", format)).renderer(StTemplateRenderer.builder().build()).build();Prompt prompt = promptTemplate.create();String result = chatModel.call(prompt).getResult().getOutput().getText();log.info("result: {}", result);assert result != null;try {BeanEntity convert = converter.convert(result);log.info("反序列成功,convert: {}", convert);} catch (Exception e) {log.error("反序列化失败");}return result;}/*** @return {@link BeanEntity}*/@GetMapping("/play")public BeanEntity simpleChat(HttpServletResponse response) {Flux<String> flux = this.chatClient.prompt().user(u -> u.text("""requirement: 请用大概 120 字,作者为 牧生 ,为计算机的发展历史写一首现代诗;format: 以纯文本输出 json,请不要包含任何多余的文字——包括 markdown 格式;outputExample: {"title": {title},"author": {author},"date": {date},"content": {content}};""")).stream().content();String result = String.join("\n", Objects.requireNonNull(flux.collectList().block())).replaceAll("\\n", "").replaceAll("\\s+", " ").replaceAll("\"\\s*:", "\":").replaceAll(":\\s*\"", ":\"");log.info("LLMs 响应的 json 数据为:{}", result);return converter.convert(result);}
}5.2 JsonController.java - JSON 格式输出
实现使用 DashScope 的 JSON 模式,确保输出为有效的 JSON 格式。
package com.alibaba.cloud.ai.example.outparser.controller;import com.alibaba.cloud.ai.dashscope.api.DashScopeResponseFormat;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/json")
public class JsonController {private final ChatClient chatClient;private final DashScopeResponseFormat responseFormat;public JsonController(ChatClient.Builder builder) {// AI模型内置支持JSON模式DashScopeResponseFormat responseFormat = new DashScopeResponseFormat();responseFormat.setType(DashScopeResponseFormat.Type.JSON_OBJECT);this.responseFormat = responseFormat;this.chatClient = builder.build();}@GetMapping("/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "请以JSON格式介绍你自己") String query) {return chatClient.prompt(query).call().content();}@GetMapping("/chat-format")public String simpleChatFormat(@RequestParam(value = "query", defaultValue = "请以JSON格式介绍你自己") String query) {return chatClient.prompt(query).options(DashScopeChatOptions.builder().withTopP(0.7).withResponseFormat(responseFormat).build()).call().content();}
}5.3 MapListController.java - Map 和 List 输出
实现将大模型输出转换为 Map 或 List 集合的功能。
package com.alibaba.cloud.ai.example.outparser.controller;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.ChatClientAttributes;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/map-list")
public class MapListController {private static final Logger log = LoggerFactory.getLogger(BeanController.class);private final ChatClient chatClient;private final MapOutputConverter mapConverter;private final ListOutputConverter listConverter;public MapListController(ChatClient.Builder builder) {// map转换器this.mapConverter = new MapOutputConverter();// list转换器this.listConverter = new ListOutputConverter(new DefaultConversionService());this.chatClient = builder.build();}@GetMapping("/chatMap")public Map<String, Object> chatMap(@RequestParam(value = "query", defaultValue = "请为我描述下影子的特性") String query) {return chatClient.prompt(query).advisors(a -> a.param(ChatClientAttributes.OUTPUT_FORMAT.getKey(), mapConverter.getFormat())).call().entity(mapConverter);}@GetMapping("/chatList")public List<String> chatList(@RequestParam(value = "query", defaultValue = "请为我描述下影子的特性") String query) {return chatClient.prompt(query).advisors(a -> a.param(ChatClientAttributes.OUTPUT_FORMAT.getKey(), listConverter.getFormat())).call().entity(listConverter);}
}6. 运行与测试
- 启动应用:运行你的 Spring Boot 主程序。
- 使用浏览器或 API 工具(如 Postman, curl)进行测试。
测试 1:Java Bean 输出
访问以下 URL,测试基本对话功能:
GET http://localhost:10007/bean/chat访问以下 URL,测试格式化输出为 Java Bean:
GET http://localhost:10007/bean/chat-format访问以下 URL,测试使用 ChatModel 格式化输出:
GET http://localhost:10007/bean/chat-model-format访问以下 URL,测试流式输出并转换为 Bean:
GET http://localhost:10007/bean/play测试 2:JSON 格式输出
访问以下 URL,测试基本 JSON 输出:
GET http://localhost:10007/json/chat访问以下 URL,测试使用 DashScope 的 JSON 模式:
GET http://localhost:10007/json/chat-format测试 3:Map 和 List 输出
访问以下 URL,测试输出为 Map:
GET http://localhost:10007/map-list/chatMap访问以下 URL,测试输出为 List:
GET http://localhost:10007/map-list/chatList7. 实现思路与扩展建议
实现思路
本案例的核心思想是"结构化输出转换"。我们将大模型的非结构化文本输出转换为结构化数据对象,这使得:
- 数据一致性高:通过预定义的结构,确保输出数据的一致性和可预测性。
- 易于集成:结构化数据可以轻松集成到现有的业务系统中。
- 类型安全:使用强类型的 Java 对象,减少运行时错误。
扩展建议
- 自定义输出转换器:可以根据业务需求,实现自定义的输出转换器,支持更复杂的数据结构。
- 输出验证:添加输出验证机制,确保转换后的数据符合业务规则。
- 多格式支持:扩展支持更多输出格式,如 XML、YAML 等。
- 错误处理:增强错误处理机制,当输出无法正确转换时提供有意义的错误信息。
- 性能优化:对于大规模数据转换,可以考虑使用缓存或批量处理来提高性能。
