本案例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 Structured Output 功能,实现将大模型输出转换为结构化数据(如 Java Bean、JSON、Map 和 List)。

1. 案例目标

我们将创建一个包含多个结构化输出功能的 Web 应用:

  1. Java Bean 输出 (/bean/*):将大模型输出转换为指定的 Java Bean 对象。
  2. JSON 格式输出 (/json/*):使用 DashScope 的 JSON 模式,确保输出为有效的 JSON 格式。
  3. 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: 10007

spring:
  application:
    name: spring-ai-alibaba-structured-example

  ai:
    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;
    }

    @Override
    public 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. 运行与测试

  1. 启动应用:运行你的 Spring Boot 主程序。
  2. 使用浏览器或 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/chatList

7. 实现思路与扩展建议

实现思路

本案例的核心思想是"结构化输出转换"。我们将大模型的非结构化文本输出转换为结构化数据对象,这使得:

  • 数据一致性高:通过预定义的结构,确保输出数据的一致性和可预测性。
  • 易于集成:结构化数据可以轻松集成到现有的业务系统中。
  • 类型安全:使用强类型的 Java 对象,减少运行时错误。

扩展建议

  • 自定义输出转换器:可以根据业务需求,实现自定义的输出转换器,支持更复杂的数据结构。
  • 输出验证:添加输出验证机制,确保转换后的数据符合业务规则。
  • 多格式支持:扩展支持更多输出格式,如 XML、YAML 等。
  • 错误处理:增强错误处理机制,当输出无法正确转换时提供有意义的错误信息。
  • 性能优化:对于大规模数据转换,可以考虑使用缓存或批量处理来提高性能。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐