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

java 复制代码
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 代表一次对话中的一条消息。

typescript 复制代码
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 代表模型生成的一个候选结果。

typescript 复制代码
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 模型输出格式不固定的问题。

java 复制代码
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

arduino 复制代码
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 复制代码
<?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:

yaml 复制代码
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

kotlin 复制代码
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 的使用:

kotlin 复制代码
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 类:

kotlin 复制代码
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 启动类:

typescript 复制代码
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 支持很多高级特性,如动态调整模型参数、传递变量、多轮对话等。

动态调整模型参数

scss 复制代码
String response = chatClient.prompt()
        .user("写一首关于春天的诗")
        .options(options -> options
                .temperature(0.9)
                .maxTokens(500))
        .call()
        .content();

传递变量

javascript 复制代码
String response = chatClient.prompt()
        .user("请用{language}语言解释什么是{concept}", 
                Map.of("language", "中文", "concept", "依赖注入"))
        .call()
        .content();

多轮对话

scss 复制代码
// 第一轮对话
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) 的模板引擎,支持变量替换、条件判断、循环等功能。

基本模板使用

typescript 复制代码
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();
    }
}

条件判断

arduino 复制代码
PromptTemplate template = new PromptTemplate(
        "请写一篇关于{topic}的文章。" +
        "#if(${includeCode})" +
        "文章中需要包含一个简单的代码示例。" +
        "#end"
);

String promptText = template.render(Map.of(
        "topic", "Java线程池",
        "includeCode", true
));

循环

dart 复制代码
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 文件:

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

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

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

arduino 复制代码
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 是聊天模型的抽象,它定义了聊天模型应该具备的基本能力:同步调用和流式调用。

java 复制代码
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:存在惩罚
scss 复制代码
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 支持多种模型提供商,你可以在同一个应用中同时使用多个模型。

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

xml 复制代码
<!-- 字节跳动豆包 -->
<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>

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

yaml 复制代码
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

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

typescript 复制代码
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:

arduino 复制代码
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 输出:

typescript 复制代码
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 使用示例

typescript 复制代码
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。

typescript 复制代码
/**
 * 使用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。

typescript 复制代码
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:

typescript 复制代码
/**
 * 获取节日日期
 * @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 数据库设计

创建书籍信息表:

sql 复制代码
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='书籍信息表';

插入测试数据:

sql 复制代码
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

创建书籍实体类:

kotlin 复制代码
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 接口:

java 复制代码
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 服务层实现

创建智能图书服务:

typescript 复制代码
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:

typescript 复制代码
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 控制器实现

kotlin 复制代码
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 构建智能应用。

相关推荐
bug制造者阿杜2 小时前
Claude Code 前端页面设计 Skills 排名与特点分析
ai编程
Java小白笔记2 小时前
Claude Code 实战方法论
ai编程
bug制造者阿杜3 小时前
Claude Code 命令速查大全
ai编程
donglianyou3 小时前
大模型提示词工程Prompt
人工智能·prompt·ai编程·大模型应用开发
带娃的IT创业者3 小时前
Claude Code Routines:如何让AI编程助手实现全自动工作流?
agent·ai编程·ai编程助手·claude code·自动化工作流·routines
AlianNiew4 小时前
从零开发一个 MCP 服务器 + OpenCode Skill:让 AI 学会审查你的代码
ai编程·mcp
三木檾4 小时前
LLM 应用开发的底层逻辑:模型只是一个无状态函数
llm·ai编程
花间相见4 小时前
【AI私人家庭医生day01】—— 项目介绍
大数据·linux·人工智能·python·flask·conda·ai编程
vivo互联网技术4 小时前
OpenClaw 落地到生产实际应用的一种可能的路径
人工智能·agent·ai编程