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

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: Spring AI 是 Spring 官方推出的 Java AI 应用开发框架,遵循“约定优于配置”等核心理念,提供统一 API 屏蔽多模型差异,支持 OpenAI、通义千问等厂商,集成 RAG、函数调用、结构化输出及企业级可观测性能力。

一、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 构建智能应用。

目录
相关文章
|
3月前
|
人工智能 Java API
Spring AI 与 Spring AI Alibaba怎么选?
Spring AI是Spring官方推出的AI应用开发框架,提供模型无关的抽象层;Spring AI Alibaba是其阿里云扩展,深度整合通义千问等国产AI服务,在中文支持、国内访问性能和成本上更具优势。两者API兼容,可混合使用。
1813 3
|
人工智能 JavaScript Java
【SpringAIAlibaba新手村系列】(1)初识 Spring AI Alibaba 框架
本文介绍了SpringAIAlibaba框架的基本概念和使用方法。作为Spring官方AI框架的阿里云实现版本,它简化了Java开发者调用AI模型的过程。文章详细讲解了核心概念如ChatModel、ChatClient,以及阿里云百炼平台的功能。通过HelloWorld项目示例,展示了如何配置APIKey、编写控制层代码,实现普通调用和流式输出两种AI交互方式。重点阐述了SpringAI与SpringAIAlibaba的关系,以及自动配置机制的工作原理,帮助开发者快速上手这一框架。
6785 8
|
Java 应用服务中间件 数据库连接
面试官:SpringBoot如何优雅停机?
面试官:SpringBoot如何优雅停机?
1113 0
|
3月前
|
存储 人工智能 Java
告别 AI 对话 “失忆”!Spring AI 聊天记忆底层原理与全场景落地实战
Spring AI提供优雅的聊天记忆解决方案,彻底解决大模型“失忆”痛点。其分层架构支持内存/MySQL等多存储,通过ChatMemory、ChatMemoryRepository和ChatMemoryAdvisor三大组件,实现会话隔离、消息有序、窗口可控,开箱即用,低侵入、高扩展。
780 13
告别 AI 对话 “失忆”!Spring AI 聊天记忆底层原理与全场景落地实战
|
4月前
|
人工智能 前端开发 Java
【SpringAIAlibaba新手村系列】(4)流式输出与响应式编程
本文围绕 Spring AI 中的流式输出与响应式编程展开,重点解释了传统一次性响应与流式返回的差异,以及 Flux 在异步数据流中的核心作用。文章结合 ChatModel.stream() 与 ChatClient 的多种代码示例,说明如何实现 AI 内容的边生成边返回,并帮助读者理解流式调用在用户体验、性能和长文本场景中的实际价值。
1477 4
【SpringAIAlibaba新手村系列】(4)流式输出与响应式编程
|
3月前
|
人工智能 Java 定位技术
【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成
本章从 MCP Server 视角出发,说明如何将本地天气查询能力整理并暴露为标准化工具服务。内容涵盖 @Tool、ToolCallbackProvider、MethodToolCallbackProvider 的作用,以及 Streamable-HTTP 协议下服务端的能力注册与对外提供逻辑。
758 13
|
3月前
|
人工智能 监控 前端开发
Harness Agent:2026 年 Java AI Agent 开发的终极框架
Harness Agent是2026年专为Java生态打造的AI Agent终极框架,原生Java、深度集成Spring Boot,轻量(仅1MB)、企业级(监控/限流/安全),支持工具调用、记忆管理、多Agent协作等完整能力,学习成本低,开箱即用。
3935 2
Harness Agent:2026 年 Java AI Agent 开发的终极框架
|
3月前
|
人工智能 JSON 决策智能
AI Agent 深潜:六大核心模块的设计本质与 Java 实现
AI Agent不是大模型API的简单封装,而是具备自主决策、闭环执行与迭代优化能力的完整智能系统。其核心由六大协同模块构成:规划(任务调度中枢)、记忆(经验沉淀载体)、工具使用(外部交互触手)、行动(落地执行手脚)、反思(自我迭代大脑)和多智能体协作(团队协同体系),共同支撑复杂任务的端到端可靠执行。
445 4
|
人工智能 NoSQL Java
【SpringAIAlibaba新手村系列】(8)持久化会话与 Redis 内存管理
本文详解 Spring AI 的会话记忆机制,从内存版 MemorySaver 到 Redis 版 RedisSaver,实现 AI 对话的上下文连续性。文章以 ReactAgent 为核心,讲解如何通过 threadId 管理会话线程,并将 Agent 状态持久化到 Redis 中。
1373 5