Spring AI 核心架构、抽象模型与四大核心组件设计精髓

一、Spring AI 概述

Spring AI 是 Spring 官方推出的 AI 应用开发框架,旨在简化 Java 开发者构建 AI 驱动应用的过程。它遵循 Spring 一贯的设计哲学:约定优于配置面向接口编程依赖注入,为开发者提供了一套统一、可扩展的 API,屏蔽了不同 AI 模型提供商之间的差异。

Spring AI 的核心价值在于:

  • 提供统一的编程模型,支持多种 AI 模型提供商(OpenAI、Anthropic、Google、阿里云、字节跳动等)

  • 提供丰富的抽象层,将 AI 能力与 Spring 生态无缝集成

  • 支持结构化输出、函数调用、RAG(检索增强生成)等高级特性

  • 提供可观测性、重试、限流等企业级功能

二、Spring AI 核心架构详解

Spring AI 采用分层架构设计,从下到上分为模型层、核心抽象层、服务层和集成层。

2.1 模型层

模型层是 Spring AI 的最底层,负责与具体的 AI 模型提供商进行通信。它包含了各个模型提供商的客户端实现,将原生 API 转换为 Spring AI 的统一抽象。

主要组件:

  • 聊天模型客户端(ChatModel)

  • 嵌入模型客户端(EmbeddingModel)

  • 图像生成模型客户端(ImageModel)

  • 语音转文本模型客户端(SpeechModel)

  • 文本转语音模型客户端(TextToSpeechModel)

2.2 核心抽象层

核心抽象层是 Spring AI 的灵魂,定义了一套与具体模型提供商无关的接口和抽象类。这一层确保了开发者可以在不修改业务代码的情况下,轻松切换不同的 AI 模型提供商。

核心抽象:

  • Model:所有 AI 模型的根接口

  • ChatModel:聊天模型的抽象

  • EmbeddingModel:嵌入模型的抽象

  • Prompt:AI 输入的抽象

  • ChatResponse:AI 输出的抽象

  • OutputParser:输出解析器的抽象

  • FunctionCallback:函数调用的抽象

2.3 服务层

服务层基于核心抽象层,提供了更高层次的服务和功能。它封装了常见的 AI 应用场景,简化了开发者的使用。

主要服务:

  • ChatClient:统一的聊天客户端

  • VectorStore:向量存储服务

  • RetrievalAugmentor:检索增强生成服务

  • FunctionCallingService:函数调用服务

2.4 集成层

集成层负责将 Spring AI 与 Spring 生态和其他第三方框架进行集成。

主要集成:

  • Spring Boot 自动配置

  • Spring Web 集成

  • Spring Data 集成

  • Spring Security 集成

  • Spring Cloud 集成

  • Swagger/OpenAPI 集成

三、Spring AI 核心抽象模型深度解析

Spring AI 的核心设计理念是抽象与解耦。通过定义清晰的接口和抽象类,Spring AI 将 AI 能力的使用与具体实现分离开来,使得代码更加灵活、可扩展和可测试。

3.1 核心抽象模型概览

3.2 Model 接口

Model 是所有 AI 模型的根接口,定义了 AI 模型最基本的能力:接收一个 Prompt 并返回一个 ChatResponse

复制代码
package org.springframework.ai.model;

import org.springframework.ai.prompt.Prompt;
import org.springframework.ai.model.ChatResponse;

/**
 * 所有AI模型的根接口
 * @author Spring AI Team
 */
public interface Model {
    /**
     * 调用AI模型
     * @param prompt 输入提示
     * @return 模型响应
     */
    ChatResponse call(Prompt prompt);
}

Model 接口的设计非常简洁,只包含一个 call 方法。这种设计使得所有 AI 模型都具有统一的调用方式,极大地提高了代码的一致性和可维护性。

3.3 Prompt 抽象

Prompt 是 AI 输入的抽象,它包含了发送给 AI 模型的所有信息。一个 Prompt 由多个 Message 组成,每个 Message 代表一次对话中的一条消息。

复制代码
package org.springframework.ai.prompt;

import org.springframework.ai.model.Message;
import java.util.List;
import java.util.Map;

/**
 * AI输入提示的抽象
 * @author Spring AI Team
 */
public class Prompt {
    private final List<Message> messages;
    private final Map<String, Object> options;

    public Prompt(List<Message> messages) {
        this(messages, Map.of());
    }

    public Prompt(List<Message> messages, Map<String, Object> options) {
        this.messages = messages;
        this.options = options;
    }

    public List<Message> getMessages() {
        return messages;
    }

    public Map<String, Object> getOptions() {
        return options;
    }
}

Message 接口定义了消息的基本属性:内容和角色。Spring AI 支持四种消息角色:

  • USER:用户消息

  • ASSISTANT:助手消息

  • SYSTEM:系统消息

  • FUNCTION:函数调用结果消息

3.4 ChatResponse 抽象

ChatResponse 是 AI 输出的抽象,它包含了 AI 模型返回的所有信息。一个 ChatResponse 由多个 Generation 组成,每个 Generation 代表模型生成的一个候选结果。

复制代码
package org.springframework.ai.model;

import java.util.List;
import java.util.Map;

/**
 * AI聊天响应的抽象
 * @author Spring AI Team
 */
public class ChatResponse {
    private final List<Generation> generations;
    private final Map<String, Object> metadata;

    public ChatResponse(List<Generation> generations) {
        this(generations, Map.of());
    }

    public ChatResponse(List<Generation> generations, Map<String, Object> metadata) {
        this.generations = generations;
        this.metadata = metadata;
    }

    public List<Generation> getGenerations() {
        return generations;
    }

    public Map<String, Object> getMetadata() {
        return metadata;
    }

    public Generation getResult() {
        return generations.get(0);
    }
}

Generation 类包含了生成的消息和相关的元数据,如完成原因、使用的令牌数等。

3.5 OutputParser 接口

OutputParser 是输出解析器的抽象,负责将 AI 模型返回的字符串转换为结构化的数据。这是 Spring AI 中非常重要的一个组件,它解决了 AI 模型输出格式不固定的问题。

复制代码
package org.springframework.ai.parser;

/**
 * 输出解析器接口
 * @param <T> 解析后的类型
 * @author Spring AI Team
 */
public interface OutputParser<T> {
    /**
     * 解析AI模型的输出
     * @param text AI模型返回的字符串
     * @return 解析后的结构化数据
     */
    T parse(String text);

    /**
     * 获取格式说明,用于提示AI模型按照指定格式输出
     * @return 格式说明字符串
     */
    String getFormat();
}

四、ChatClient:统一的 AI 交互入口

ChatClient 是 Spring AI 提供的统一聊天客户端,它封装了 ChatModel 的底层细节,提供了更加流畅和易用的 API。ChatClient 采用构建者模式设计,支持链式调用,极大地简化了 AI 交互的代码编写。

4.1 ChatClient 的设计理念

ChatClient 的设计理念是简单、灵活、可扩展。它提供了多种调用方式,满足不同场景的需求:

  • 同步调用:适用于简单的问答场景

  • 异步调用:适用于需要非阻塞处理的场景

  • 流式调用:适用于需要实时展示结果的场景

同时,ChatClient 支持丰富的配置选项,可以在调用时动态调整模型参数,如温度、最大令牌数等。

4.2 ChatClient 的核心 API

复制代码
package org.springframework.ai.chat.client;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 统一的聊天客户端接口
 * @author Spring AI Team
 */
public interface ChatClient {
    /**
     * 创建一个新的ChatClient构建器
     * @param chatModel 聊天模型
     * @return ChatClient构建器
     */
    static Builder builder(ChatModel chatModel) {
        return new DefaultChatClientBuilder(chatModel);
    }

    /**
     * 创建一个新的请求规范
     * @return 请求规范
     */
    RequestSpec prompt();

    /**
     * ChatClient构建器
     */
    interface Builder {
        Builder defaultSystem(String text);
        Builder defaultSystem(String text, Map<String, Object> variables);
        Builder defaultOptions(Consumer<ChatOptions.Builder> optionsConsumer);
        ChatClient build();
    }

    /**
     * 请求规范
     */
    interface RequestSpec {
        RequestSpec system(String text);
        RequestSpec system(String text, Map<String, Object> variables);
        RequestSpec user(String text);
        RequestSpec user(String text, Map<String, Object> variables);
        RequestSpec assistant(String text);
        RequestSpec options(Consumer<ChatOptions.Builder> optionsConsumer);
        ResponseSpec call();
        StreamResponseSpec stream();
    }

    /**
     * 同步响应规范
     */
    interface ResponseSpec {
        String content();
        <T> T entity(Class<T> type);
        ChatResponse chatResponse();
    }

    /**
     * 流式响应规范
     */
    interface StreamResponseSpec {
        Flux<String> content();
        Flux<ChatResponse> chatResponse();
    }
}

4.3 ChatClient 的使用示例

首先,我们需要在 pom.xml 中添加必要的依赖:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>spring-ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai-demo</name>
    <description>Spring AI Demo Project</description>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M1</spring-ai.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-dashscope-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.52</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.1.0-jre</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

然后,在 application.yml 中配置阿里云通义千问的 API Key:

复制代码
spring:
  ai:
    dashscope:
      api-key: your-api-key-here
      chat:
        options:
          model: qwen-turbo
          temperature: 0.7
          max-tokens: 1000
server:
  port: 8080
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs

接下来,创建一个配置类来配置 ChatClient

复制代码
package com.jam.demo.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Spring AI配置类
 * @author ken
 */
@Configuration
public class SpringAiConfig {
    /**
     * 配置ChatClient
     * @param dashScopeChatModel 通义千问聊天模型
     * @return ChatClient实例
     */
    @Bean
    public ChatClient chatClient(DashScopeChatModel dashScopeChatModel) {
        return ChatClient.builder(dashScopeChatModel)
                .defaultSystem("你是一个专业的Java技术助手,回答问题要准确、简洁、专业。")
                .build();
    }
}

创建一个简单的控制器来演示 ChatClient 的使用:

复制代码
package com.jam.demo.controller;

import com.jam.demo.dto.ChatRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

/**
 * 聊天控制器
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/api/chat")
@RequiredArgsConstructor
@Tag(name = "聊天接口", description = "AI聊天相关接口")
public class ChatController {
    private final ChatClient chatClient;

    /**
     * 同步聊天接口
     * @param request 聊天请求
     * @return AI响应
     */
    @PostMapping("/sync")
    @Operation(summary = "同步聊天", description = "发送消息并等待AI完整响应")
    public ResponseEntity<String> syncChat(@RequestBody ChatRequest request) {
        String response = chatClient.prompt()
                .user(request.getMessage())
                .call()
                .content();
        return ResponseEntity.ok(response);
    }

    /**
     * 流式聊天接口
     * @param request 聊天请求
     * @return AI流式响应
     */
    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @Operation(summary = "流式聊天", description = "发送消息并以SSE方式接收AI实时响应")
    public Flux<String> streamChat(@RequestBody ChatRequest request) {
        return chatClient.prompt()
                .user(request.getMessage())
                .stream()
                .content();
    }
}

创建对应的 DTO 类:

复制代码
package com.jam.demo.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * 聊天请求DTO
 * @author ken
 */
@Data
@Schema(description = "聊天请求")
public class ChatRequest {
    @Schema(description = "用户消息", example = "什么是Spring AI?")
    private String message;
}

最后,创建 Spring Boot 启动类:

复制代码
package com.jam.demo;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Spring AI Demo启动类
 * @author ken
 */
@SpringBootApplication
@OpenAPIDefinition(
        info = @Info(
                title = "Spring AI Demo API",
                version = "1.0",
                description = "Spring AI演示项目API文档"
        )
)
public class SpringAiDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAiDemoApplication.class, args);
    }
}

4.4 ChatClient 的高级用法

ChatClient 支持很多高级特性,如动态调整模型参数、传递变量、多轮对话等。

动态调整模型参数
复制代码
String response = chatClient.prompt()
        .user("写一首关于春天的诗")
        .options(options -> options
                .temperature(0.9)
                .maxTokens(500))
        .call()
        .content();
传递变量
复制代码
String response = chatClient.prompt()
        .user("请用{language}语言解释什么是{concept}", 
                Map.of("language", "中文", "concept", "依赖注入"))
        .call()
        .content();
多轮对话
复制代码
// 第一轮对话
String response1 = chatClient.prompt()
        .user("什么是Spring Boot?")
        .call()
        .content();

// 第二轮对话(需要包含历史消息)
String response2 = chatClient.prompt()
        .user("什么是Spring Boot?")
        .assistant(response1)
        .user("它和Spring Framework有什么区别?")
        .call()
        .content();

五、Prompt:AI 交互的语言桥梁

Prompt 是人类与 AI 模型沟通的语言,它直接决定了 AI 模型输出的质量。Spring AI 提供了强大的 Prompt 管理能力,支持模板化、变量替换、多消息组合等功能。

5.1 Prompt 的设计理念

Spring AI 的 Prompt 设计遵循以下原则:

  • 结构化:将 Prompt 分解为多个消息,每个消息有明确的角色

  • 可复用:支持模板化,将通用的 Prompt 逻辑提取为模板

  • 可扩展:支持自定义消息类型和模板引擎

  • 类型安全:提供类型安全的变量替换机制

5.2 Prompt 模板

Spring AI 内置了基于 Spring Expression Language (SpEL) 的模板引擎,支持变量替换、条件判断、循环等功能。

基本模板使用
复制代码
package com.jam.demo.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.prompt.PromptTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * Prompt服务
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PromptService {
    private final ChatClient chatClient;

    /**
     * 使用模板生成解释
     * @param concept 要解释的概念
     * @param language 解释使用的语言
     * @return 解释结果
     */
    public String explainConcept(String concept, String language) {
        PromptTemplate template = new PromptTemplate(
                "请用{language}语言详细解释什么是{concept}。" +
                "要求:1. 定义清晰准确;2. 举一个简单的例子;3. 说明它的主要用途。"
        );
        
        String promptText = template.render(Map.of(
                "concept", concept,
                "language", language
        ));
        
        return chatClient.prompt()
                .user(promptText)
                .call()
                .content();
    }
}
条件判断
复制代码
PromptTemplate template = new PromptTemplate(
        "请写一篇关于{topic}的文章。" +
        "#if(${includeCode})" +
        "文章中需要包含一个简单的代码示例。" +
        "#end"
);

String promptText = template.render(Map.of(
        "topic", "Java线程池",
        "includeCode", true
));
循环
复制代码
PromptTemplate template = new PromptTemplate(
        "请列出以下技术的主要特点:" +
        "#foreach($tech in $technologies)" +
        "- $tech" +
        "#end"
);

String promptText = template.render(Map.of(
        "technologies", List.of("Spring Boot", "Spring Cloud", "Spring AI")
));

5.3 外部 Prompt 模板文件

对于复杂的 Prompt,建议将其保存为外部文件,便于管理和维护。Spring AI 支持从类路径加载 Prompt 模板文件。

创建 src/main/resources/prompts/explain-concept.st 文件:

复制代码
请用{language}语言详细解释什么是{concept}。

要求:
1. 定义清晰准确
2. 举一个简单的例子
3. 说明它的主要用途
4. 字数控制在300字以内

然后在代码中加载并使用:

复制代码
import org.springframework.core.io.ClassPathResource;
import org.springframework.ai.prompt.PromptTemplate;

public String explainConceptFromFile(String concept, String language) {
    PromptTemplate template = new PromptTemplate(new ClassPathResource("prompts/explain-concept.st"));
    
    String promptText = template.render(Map.of(
            "concept", concept,
            "language", language
    ));
    
    return chatClient.prompt()
            .user(promptText)
            .call()
            .content();
}

5.4 Prompt 工程最佳实践

  1. 明确角色:在系统提示中明确 AI 的角色和任务

  2. 清晰指令:给出清晰、具体的指令,避免模糊不清的表述

  3. 格式要求:明确指定输出格式,便于后续处理

  4. 示例引导:提供示例,让 AI 更好地理解你的需求

  5. 分步思考:对于复杂问题,引导 AI 分步思考

  6. 限制输出:限制输出的长度和内容,避免无关信息

六、Model:模型能力的抽象与适配

Model 是 Spring AI 对 AI 模型能力的抽象,它定义了 AI 模型应该具备的基本接口。Spring AI 提供了多种 Model 实现,支持不同类型的 AI 模型和不同的模型提供商。

6.1 Model 的设计理念

Spring AI 的 Model 设计遵循以下原则:

  • 统一接口:所有同类型的模型都实现相同的接口

  • 最小接口:接口只定义最基本的方法,保持简洁

  • 可扩展:通过选项机制支持模型的特定功能

  • 适配模式:使用适配器模式将原生 API 转换为统一接口

6.2 ChatModel 详解

ChatModel 是聊天模型的抽象,它定义了聊天模型应该具备的基本能力:同步调用和流式调用。

复制代码
package org.springframework.ai.chat.model;

import org.springframework.ai.model.Model;
import org.springframework.ai.prompt.Prompt;
import reactor.core.publisher.Flux;

/**
 * 聊天模型接口
 * @author Spring AI Team
 */
public interface ChatModel extends Model {
    /**
     * 同步调用聊天模型
     * @param prompt 输入提示
     * @return 聊天响应
     */
    @Override
    ChatResponse call(Prompt prompt);

    /**
     * 流式调用聊天模型
     * @param prompt 输入提示
     * @return 流式聊天响应
     */
    Flux<ChatResponse> stream(Prompt prompt);
}

6.3 模型选项

Spring AI 通过 ChatOptions 类来配置模型的参数。不同的模型提供商可能有不同的参数,但 Spring AI 定义了一组通用的参数,确保在不同模型之间的兼容性。

通用参数:

  • model:模型名称

  • temperature:温度,控制输出的随机性

  • maxTokens:最大生成令牌数

  • topP:核采样参数

  • frequencyPenalty:频率惩罚

  • presencePenalty:存在惩罚

    import org.springframework.ai.chat.model.ChatOptions;

    ChatOptions options = ChatOptions.builder()
    .model("qwen-turbo")
    .temperature(0.7)
    .maxTokens(1000)
    .topP(0.9)
    .build();

6.4 多模型支持

Spring AI 支持多种模型提供商,你可以在同一个应用中同时使用多个模型。

首先,添加不同模型提供商的依赖:

复制代码
<!-- 字节跳动豆包 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-doubao-spring-boot-starter</artifactId>
</dependency>

<!-- OpenAI -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

然后,在配置文件中配置多个模型:

复制代码
spring:
  ai:
    dashscope:
      api-key: your-dashscope-api-key
      chat:
        options:
          model: qwen-turbo
    doubao:
      api-key: your-doubao-api-key
      chat:
        options:
          model: doubao-pro
    openai:
      api-key: your-openai-api-key
      chat:
        options:
          model: gpt-3.5-turbo

最后,在代码中使用不同的模型:

复制代码
package com.jam.demo.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.doubao.chat.DoubaoChatModel;
import org.springframework.ai.openai.chat.OpenAiChatModel;
import org.springframework.stereotype.Service;

/**
 * 多模型服务
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MultiModelService {
    private final DashScopeChatModel dashScopeChatModel;
    private final DoubaoChatModel doubaoChatModel;
    private final OpenAiChatModel openAiChatModel;

    /**
     * 使用通义千问聊天
     * @param message 用户消息
     * @return AI响应
     */
    public String chatWithQwen(String message) {
        return ChatClient.create(dashScopeChatModel)
                .prompt()
                .user(message)
                .call()
                .content();
    }

    /**
     * 使用豆包聊天
     * @param message 用户消息
     * @return AI响应
     */
    public String chatWithDoubao(String message) {
        return ChatClient.create(doubaoChatModel)
                .prompt()
                .user(message)
                .call()
                .content();
    }

    /**
     * 使用GPT-3.5聊天
     * @param message 用户消息
     * @return AI响应
     */
    public String chatWithGpt35(String message) {
        return ChatClient.create(openAiChatModel)
                .prompt()
                .user(message)
                .call()
                .content();
    }
}

七、OutputParser:结构化输出的关键

OutputParser 是 Spring AI 中非常重要的一个组件,它负责将 AI 模型返回的非结构化字符串转换为结构化的数据。这使得开发者可以像调用普通方法一样调用 AI 模型,获取类型安全的返回值。

7.1 OutputParser 的设计理念

Spring AI 的 OutputParser 设计遵循以下原则:

  • 类型安全:提供类型安全的解析结果

  • 自动格式提示:自动生成格式说明,提示 AI 模型按照指定格式输出

  • 容错处理:提供容错机制,处理 AI 模型输出格式不规范的情况

  • 可扩展:支持自定义输出解析器

7.2 内置 OutputParser

Spring AI 提供了多种内置的 OutputParser,满足常见的需求:

  • BeanOutputParser:将输出解析为 Java Bean

  • ListOutputParser:将输出解析为列表

  • MapOutputParser:将输出解析为 Map

  • BooleanOutputParser:将输出解析为布尔值

  • IntegerOutputParser:将输出解析为整数

  • DoubleOutputParser:将输出解析为浮点数

BeanOutputParser 使用示例

首先,定义一个 Java Bean:

复制代码
package com.jam.demo.dto;

import lombok.Data;

/**
 * 书籍信息DTO
 * @author ken
 */
@Data
public class Book {
    private String title;
    private String author;
    private String isbn;
    private double price;
    private String description;
}

然后,使用 BeanOutputParser 解析 AI 输出:

复制代码
package com.jam.demo.service;

import com.jam.demo.dto.Book;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.stereotype.Service;

/**
 * 输出解析服务
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OutputParserService {
    private final ChatClient chatClient;

    /**
     * 获取书籍信息
     * @param bookName 书籍名称
     * @return 书籍信息
     */
    public Book getBookInfo(String bookName) {
        BeanOutputParser<Book> parser = new BeanOutputParser<>(Book.class);
        
        String response = chatClient.prompt()
                .user("请提供关于《{bookName}》的详细信息。{format}", 
                        Map.of("bookName", bookName, "format", parser.getFormat()))
                .call()
                .content();
        
        return parser.parse(response);
    }
}
ListOutputParser 使用示例
复制代码
import org.springframework.ai.parser.ListOutputParser;

/**
 * 获取技术栈列表
 * @param domain 领域
 * @return 技术栈列表
 */
public List<String> getTechStack(String domain) {
    ListOutputParser parser = new ListOutputParser();
    
    String response = chatClient.prompt()
            .user("请列出{domain}领域常用的10个技术栈。{format}", 
                    Map.of("domain", domain, "format", parser.getFormat()))
            .call()
            .content();
    
    return parser.parse(response);
}

7.3 ChatClient 集成 OutputParser

ChatClient 提供了 entity 方法,可以直接将 AI 输出解析为指定类型的对象,无需手动创建 OutputParser。

复制代码
/**
 * 使用ChatClient直接解析为Java Bean
 * @param bookName 书籍名称
 * @return 书籍信息
 */
public Book getBookInfoWithChatClient(String bookName) {
    return chatClient.prompt()
            .user("请提供关于《{bookName}》的详细信息。", Map.of("bookName", bookName))
            .call()
            .entity(Book.class);
}

7.4 自定义 OutputParser

如果内置的 OutputParser 不能满足你的需求,你可以自定义 OutputParser。

复制代码
package com.jam.demo.parser;

import com.alibaba.fastjson2.JSON;
import org.springframework.ai.parser.OutputParser;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * 日期输出解析器
 * @author ken
 */
public class LocalDateOutputParser implements OutputParser<LocalDate> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate parse(String text) {
        if (!StringUtils.hasText(text)) {
            return null;
        }
        
        // 尝试直接解析
        try {
            return LocalDate.parse(text.trim(), FORMATTER);
        } catch (Exception e) {
            // 尝试从JSON中提取
            try {
                String dateStr = JSON.parseObject(text).getString("date");
                return LocalDate.parse(dateStr.trim(), FORMATTER);
            } catch (Exception ex) {
                throw new IllegalArgumentException("无法解析日期: " + text, ex);
            }
        }
    }

    @Override
    public String getFormat() {
        return "请按照yyyy-MM-dd格式输出日期。如果需要返回多个信息,请将日期放在date字段中,使用JSON格式输出。";
    }
}

使用自定义 OutputParser:

复制代码
/**
 * 获取节日日期
 * @param festival 节日名称
 * @return 节日日期
 */
public LocalDate getFestivalDate(String festival) {
    LocalDateOutputParser parser = new LocalDateOutputParser();
    
    String response = chatClient.prompt()
            .user("请告诉我{festival}是哪一天?{format}", 
                    Map.of("festival", festival, "format", parser.getFormat()))
            .call()
            .content();
    
    return parser.parse(response);
}

八、综合实战案例

下面我们将通过一个完整的案例,展示如何使用 Spring AI 构建一个智能问答系统,支持结构化输出和多轮对话。

8.1 需求分析

我们要构建一个智能图书问答系统,具备以下功能:

  1. 根据书名查询书籍基本信息

  2. 根据书名推荐相似书籍

  3. 根据作者查询其所有作品

  4. 多轮对话功能

8.2 数据库设计

创建书籍信息表:

复制代码
CREATE TABLE book (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL COMMENT '书名',
    author VARCHAR(255) NOT NULL COMMENT '作者',
    isbn VARCHAR(20) UNIQUE COMMENT 'ISBN',
    price DECIMAL(10,2) COMMENT '价格',
    description TEXT COMMENT '简介',
    publish_date DATE COMMENT '出版日期',
    category VARCHAR(100) COMMENT '分类',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='书籍信息表';

插入测试数据:

复制代码
INSERT INTO book (title, author, isbn, price, description, publish_date, category) VALUES
('Java编程思想', 'Bruce Eckel', '9787111213826', 108.00, 'Java领域经典著作,全面介绍了Java语言的特性和编程思想。', '2007-06-01', '计算机/编程'),
('深入理解Java虚拟机', '周志明', '9787111641247', 129.00, '深入解析Java虚拟机的工作原理和实现细节。', '2019-12-01', '计算机/编程'),
('Spring实战', 'Craig Walls', '9787115547620', 89.00, '全面介绍Spring框架的核心概念和使用方法。', '2020-07-01', '计算机/编程'),
('Spring Boot实战', '丁雪丰', '9787115453686', 69.00, '详细讲解Spring Boot的使用和最佳实践。', '2016-05-01', '计算机/编程'),
('设计模式:可复用面向对象软件的基础', 'Erich Gamma', '9787111094043', 59.00, '设计模式领域的经典著作,介绍了23种设计模式。', '2000-09-01', '计算机/编程');

8.3 实体类和 Mapper

创建书籍实体类:

复制代码
package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * 书籍实体类
 * @author ken
 */
@Data
@TableName("book")
public class Book {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String title;
    private String author;
    private String isbn;
    private BigDecimal price;
    private String description;
    private LocalDate publishDate;
    private String category;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

创建 Mapper 接口:

复制代码
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.Book;
import org.apache.ibatis.annotations.Mapper;

/**
 * 书籍Mapper
 * @author ken
 */
@Mapper
public interface BookMapper extends BaseMapper<Book> {
}

8.4 服务层实现

创建智能图书服务:

复制代码
package com.jam.demo.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.dto.BookRecommendation;
import com.jam.demo.entity.Book;
import com.jam.demo.mapper.BookMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;

/**
 * 智能图书服务
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SmartBookService {
    private final ChatClient chatClient;
    private final BookMapper bookMapper;

    /**
     * 根据书名查询书籍信息
     * @param title 书名
     * @return 书籍信息
     */
    public Book getBookByTitle(String title) {
        if (!StringUtils.hasText(title)) {
            return null;
        }
        
        LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(Book::getTitle, title);
        List<Book> books = bookMapper.selectList(wrapper);
        
        if (CollectionUtils.isEmpty(books)) {
            // 数据库中没有,使用AI生成
            return chatClient.prompt()
                    .user("请提供关于《{title}》的详细信息。", Map.of("title", title))
                    .call()
                    .entity(Book.class);
        }
        
        return books.get(0);
    }

    /**
     * 推荐相似书籍
     * @param title 书名
     * @return 推荐书籍列表
     */
    public List<BookRecommendation> recommendSimilarBooks(String title) {
        Book book = getBookByTitle(title);
        if (book == null) {
            return List.of();
        }
        
        return chatClient.prompt()
                .user("根据以下书籍信息,推荐5本相似的书籍:\n" +
                        "书名:{title}\n" +
                        "作者:{author}\n" +
                        "分类:{category}\n" +
                        "简介:{description}\n" +
                        "请按照以下格式返回:\n" +
                        "1. 书名 - 作者 - 推荐理由\n" +
                        "2. 书名 - 作者 - 推荐理由\n" +
                        "...",
                        Map.of(
                                "title", book.getTitle(),
                                "author", book.getAuthor(),
                                "category", book.getCategory(),
                                "description", book.getDescription()
                        ))
                .call()
                .entity(new org.springframework.core.ParameterizedTypeReference<List<BookRecommendation>>() {});
    }

    /**
     * 根据作者查询作品
     * @param author 作者
     * @return 作品列表
     */
    public List<String> getBooksByAuthor(String author) {
        if (!StringUtils.hasText(author)) {
            return List.of();
        }
        
        LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Book::getAuthor, author);
        List<Book> books = bookMapper.selectList(wrapper);
        
        if (CollectionUtils.isEmpty(books)) {
            // 数据库中没有,使用AI生成
            return chatClient.prompt()
                    .user("请列出{author}的主要作品。", Map.of("author", author))
                    .call()
                    .entity(new org.springframework.core.ParameterizedTypeReference<List<String>>() {});
        }
        
        return books.stream()
                .map(Book::getTitle)
                .toList();
    }
}

创建推荐书籍 DTO:

复制代码
package com.jam.demo.dto;

import lombok.Data;

/**
 * 书籍推荐DTO
 * @author ken
 */
@Data
public class BookRecommendation {
    private String title;
    private String author;
    private String reason;
}

8.5 控制器实现

复制代码
package com.jam.demo.controller;

import com.jam.demo.dto.BookRecommendation;
import com.jam.demo.entity.Book;
import com.jam.demo.service.SmartBookService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
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;

/**
 * 智能图书控制器
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/api/book")
@RequiredArgsConstructor
@Tag(name = "智能图书接口", description = "智能图书问答相关接口")
public class SmartBookController {
    private final SmartBookService smartBookService;

    /**
     * 根据书名查询书籍信息
     * @param title 书名
     * @return 书籍信息
     */
    @GetMapping("/info")
    @Operation(summary = "查询书籍信息", description = "根据书名查询书籍的详细信息")
    public ResponseEntity<Book> getBookInfo(@RequestParam String title) {
        Book book = smartBookService.getBookByTitle(title);
        return ResponseEntity.ok(book);
    }

    /**
     * 推荐相似书籍
     * @param title 书名
     * @return 推荐书籍列表
     */
    @GetMapping("/recommend")
    @Operation(summary = "推荐相似书籍", description = "根据书名推荐相似的书籍")
    public ResponseEntity<List<BookRecommendation>> recommendBooks(@RequestParam String title) {
        List<BookRecommendation> recommendations = smartBookService.recommendSimilarBooks(title);
        return ResponseEntity.ok(recommendations);
    }

    /**
     * 根据作者查询作品
     * @param author 作者
     * @return 作品列表
     */
    @GetMapping("/author")
    @Operation(summary = "查询作者作品", description = "根据作者查询其主要作品")
    public ResponseEntity<List<String>> getBooksByAuthor(@RequestParam String author) {
        List<String> books = smartBookService.getBooksByAuthor(author);
        return ResponseEntity.ok(books);
    }
}

九、最佳实践与常见陷阱

9.1 最佳实践

  1. 使用 ChatClient 而非直接使用 ChatModelChatClient 提供了更加流畅和易用的 API,并且内置了很多有用的功能

  2. 将 Prompt 模板化:将通用的 Prompt 逻辑提取为模板,便于管理和维护

  3. 使用结构化输出 :尽可能使用 OutputParser 将 AI 输出转换为结构化数据,提高代码的可维护性

  4. 合理设置模型参数:根据不同的场景调整模型参数,如温度、最大令牌数等

  5. 添加错误处理:AI 模型调用可能会失败,需要添加适当的错误处理逻辑

  6. 添加限流和重试:为了防止 API 调用超限,添加限流和重试机制

  7. 记录日志:记录 AI 模型的输入和输出,便于问题排查和调试

9.2 常见陷阱

  1. Prompt 不清晰:模糊不清的 Prompt 会导致 AI 输出质量下降

  2. 过度依赖 AI:不要将所有逻辑都交给 AI 处理,关键逻辑应该由代码实现

  3. 忽略安全问题:AI 模型可能会生成有害内容,需要添加内容安全检查

  4. 没有处理格式错误:AI 模型可能不会严格按照指定格式输出,需要添加容错处理

  5. 没有考虑成本:AI 模型调用是有成本的,需要合理控制调用次数和令牌数

  6. 硬编码 API Key:不要将 API Key 硬编码在代码中,应该使用配置文件或环境变量

十、总结与展望

Spring AI 为 Java 开发者提供了一套强大而灵活的 AI 应用开发框架。它通过清晰的抽象和统一的 API,极大地降低了 AI 应用开发的门槛。本文详细解析了 Spring AI 的核心架构、核心抽象模型以及四大核心组件的设计理念和使用方法,并通过一个完整的实战案例展示了如何使用 Spring AI 构建智能应用。

相关推荐
LXMXHJ1 小时前
spring+
spring
Java成神之路-2 小时前
零基础入门:动态代理与 Spring AOP 核心知识点总结
spring·代理模式
算.子2 小时前
【Spring AI 实战】五、RAG 核心原理:为什么需要检索增强生成?
java·人工智能·spring
Java面试题总结2 小时前
Spring AI 核心架构、抽象模型与四大核心组件设计精髓
人工智能·spring·架构
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【20】MessagesAgentHook 、MessagesModelHook 相关实现类
java·人工智能·spring
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【15】工具执行拦截器(ToolInterceptor)
java·人工智能·spring
希望永不加班4 小时前
Spring AOP 核心概念:切面、通知、切点、织入
java·数据库·后端·mysql·spring
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【17】模型拦截器(ModelInterceptor)
java·人工智能·spring
Flittly4 小时前
【SpringSecurity新手村系列】(1)初识安全框架
java·spring boot·安全·spring·安全架构