前言

大家好,这里是程序员阿亮,从今天开始打算跟xdm一起学习一下SpringAI这个技术框架,并且带xdm完成一个基于SpringAI的RAG+Agent的项目,从0基础到手搓完整的Agent项目

不过,对于基础的Agent和RAG的知识点,xdm可以之前看我前俩期播客:

Agent理论

深入研究RAG

这一期,咱们先从介绍SpringAI与环境准备开始

一、SpringAI是什么?

1.1 介绍

根据官方文档:(https://spring.io/projects/spring-ai)的介绍,实际上SpringAI就是一个Spring 官方推出的 应用框架,专为人工智能工程化设计。它的目标是将 Spring 生态系统的设计原则(如可移植性、模块化、POJO 构建)应用于 AI 领域,简化企业级应用中集成大语言模型(LLM)的复杂度的框架

1.2 核心模块

实际上SpringAI的模块划分并不多,我打算从

├── Model(模型集成)← 
├── Vector Store(向量库)
├── Document Processing(文档处理)
├── Chat Memory(对话记忆)
├── RAG / Tools / MCP / Evaluation(扩展优化)
└── Agent(智能体)

这几个方面层层递进地进行详细讲解。

二、Model模块

2.1 模块定位

Model 模块是 Spring AI 的核心引擎,负责:

  1. 屏蔽差异:统一 OpenAI、Azure、Ollama、Anthropic 等不同厂商的 API 差异。
  2. 对话管理:处理 Prompt 构建、消息流转、响应解析。
  3. 能力扩展:支持流式输出、结构化数据、函数调用(Function Calling)。

2.2 如何配置

引入依赖

<dependencies>
    <!-- 核心 Starter -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
        <!-- 若使用 Ollama 则改为 spring-ai-starter-model-ollama -->
    </dependency>
    
    <!-- Web 支持(用于实战 Controller) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Lombok(简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
spring:
  ai:
    openai:
      # API Key 配置
      api-key: ${OPENAI_API_KEY} 
      # 基础 URL(若使用兼容 OpenAI 协议的国内模型可修改此处)
      base-url: https://api.openai.com/v1
      chat:
        options:
          # 默认模型
          model: gpt-4o
          # 默认温度值 (0.0 - 2.0)
          temperature: 0.7

还有就是进行基本的文件配置

三、Model模块核心API讲解

接下来我来讲解一下SpringAI的Model模块的核心API

3.1 ChatClient

ChatClient 是 Spring AI 1.0 引入的流式构建器模式高级 API,旨在简化调用链。

3.1.1 创建与注入

作用:获取客户端实例,支持多例构建。

@RestController
public class ModelController {

    // 1. 注入 Builder(Spring Boot 自动配置)
    private final ChatClient.Builder chatClientBuilder;

    // 2. 构造函数注入
    public ModelController(ChatClient.Builder chatClientBuilder) {
        this.chatClientBuilder = chatClientBuilder;
    }

    // 3. 构建默认客户端
    private ChatClient createClient() {
        return chatClientBuilder.build();
    }
}

简单说这个API就是把我们跟模型进行交互的底层给封装了,集成了非常丰富的功能

3.1.2 基础对话 API: prompt().call().content()

作用:发送文本提示,同步获取纯文本响应。

举个例子

@GetMapping("/basic")
public String basicChat() {
    // 1. 构建客户端
    ChatClient client = createClient();
    
    // 2. 构建 Prompt 并调用
    // .prompt(String): 设置用户消息
    // .call(): 执行同步请求
    // .content(): 提取响应中的文本内容
    String response = client
        .prompt("你好,请介绍下 Spring AI") 
        .call()
        .content();
    
    return response;
}

特别需要注意的是返回的结果的对象结构是:

ChatResponse (模型完整响应)
 ├─► List<Generation> getResults()    // 可能返回多个候选结果
 ├─► ChatResponseMetadata getMetadata() // 响应级元数据
 │
 └─► Generation (单次生成结果)
      ├─► AssistantMessage getOutput()  // 模型输出的消息内容
      ├─► ChatGenerationMetadata getMetadata() // 生成级元数据
      │
      └─► AssistantMessage (助手消息)
           ├─► String getText()          // 文本内容 你常用的
           ├─► Map<String,Object> getMetadata() // 消息级元数据
           ├─► List<ToolCall> getToolCalls()   // 函数调用请求
           ├─► List<Media> getMedia()          // 多模态内容
           └─► MessageType getMessageType()    // 消息类型: ASSISTANT

3.1.3 系统消息 API: system()

作用:设定 AI 的角色、行为准则或背景知识(System Message)。

@GetMapping("/system")
public String systemChat() {
    String response = createClient()
        .prompt("翻译这句话:Hello World")
        // .system(String): 设置系统指令,优先级高于用户消息
        .system("你是一个专业的翻译助手,只输出翻译结果,不要解释")
        .call()
        .content();
    return response;
}

这种系统提示词一般是我们写死在后端文件中,用于给模型进行角色调整、提供背景知识的工具,其实也是Prompt

3.1.4 多轮对话消息 API: messages()

API

作用:手动构建完整的消息历史(User/Assistant/System),用于精细控制上下文。

@GetMapping("/messages")
public String messagesChat() {
    // 1. 构建消息列表
    List<Message> messages = List.of(
        // SystemMessage: 系统指令
        new SystemMessage("你是一个数学助手"),
        // UserMessage: 用户输入
        new UserMessage("1+1 等于几?"),
        // AssistantMessage: 模拟历史回答(用于 Few-Shot 或上下文延续)
        new AssistantMessage("等于 2"),
        // 新一轮用户输入
        new UserMessage("那 2+2 呢?")
    );

    // 2. 传入消息列表
    String response = createClient()
        .messages(messages) 
        .call()
        .content();
    
    return response; // 预期输出:等于 4
}

三种Message的关系和区别:

三种消息角色对比

消息类型

对应角色

发送方

核心作用

典型内容

SystemMessage

system

开发者设定

设定人设/规则/边界,指导模型行为

"你是一个数学助手"、"请用中文回答"、"不要提供医疗建议"

UserMessage

user

终端用户

发起提问/提供输入,驱动对话流程

"1+1等于几?"、"解释一下红黑树"

AssistantMessage

assistant

模型回复

记录历史回复,构建多轮对话上下文

"等于2"、"红黑树是一种自平衡二叉搜索树..."

3.1.5 流式输出 API: stream()

作用:服务端发送事件(SSE),前端逐字显示,降低首字延迟。

就是咱们平时用ChatGPT不是一个一个chunk显示嘛,就是基于流式API去实现的

一般是基于SSE或者WebFlux

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat() {
    // 1. 调用 stream() 而非 call()
    // 2. 返回 Flux<ChatResponse> 流
    Flux<ChatResponse> responseFlux = createClient()
        .prompt("写一首关于春天的诗,每句一行")
        .stream();

    // 3. 转换流数据
    return responseFlux
        // .getResult(): 获取生成结果
        // .getOutput(): 获取消息内容
        // .getText(): 获取文本片段
        .map(response -> response.getResult().getOutput().getText());
}

大致链路就是:

用户请求 /stream
     │
     ▼
Spring Controller 方法执行
     │
     ▼
createClient().prompt(...).stream() 
     │
     ▼
[异步] 连接大模型流式接口(如 OpenAI /stream)
     │
     ▼
模型每生成一个 token → 返回一个 ChatResponse 片段
     │
     ▼
.map() 提取文本 → Flux<String> 发射一个字符串片段
     │
     ▼
Spring WebFlux 自动将 Flux<String> 序列化为 SSE 格式:
     │    data: 春
     │    data: 风
     │    data: 吹
     │    ...
     ▼
浏览器/客户端逐行接收并渲染 → 实现"打字机效果" 

3.1.6 结构化输出 API: entity()

作用:强制模型输出特定 JSON 格式,并自动反序列化为 Java Bean(POJO)。

// 定义目标结构
public record UserInfo(String name, int age, String city) {}

@GetMapping("/entity")
public UserInfo entityChat() {
    // 1. 调用 .entity(Class)
    // 底层会自动构建 Function Calling 或 JSON Mode 提示
    UserInfo info = createClient()
        .prompt("用户说:我叫张三,今年 25 岁,住在北京")
        .call()
        .entity(UserInfo.class); 
    
    return info; // 自动转换为 {name:"张三", age:25, city:"北京"}
}

这实际就是FunctionCalling,也是我们实现Tool调用的核心,也是我们Agent的核心部分之一

3.1.7 模型参数配置 API: options()

作用:动态覆盖全局配置,针对单次请求调整温度、模型类型等

@GetMapping("/options")
public String optionsChat() {
    // 1. 构建特定模型的 Options
    OpenAiOptions options = OpenAiOptions.builder()
        .model("gpt-4o-mini")      // 指定模型
        .temperature(0.2)          // 降低随机性,更严谨
        .maxTokens(100)            // 限制最大输出长度
        .build();

    String response = createClient()
        .prompt("解释量子力学")
        // .options(Object): 传入特定提供商的 Options 对象
        .options(options) 
        .call()
        .content();
    
    return response;
}

3.1.8 工具调用 API: tools() / toolNames()

作用:注册 Java 方法供模型调用,实现联网搜索、数据库查询等能力。

// 定义工具类
@Component
public class WeatherTool {
    @Tool(description = "查询城市天气") // @Tool 注解标记为可调用函数
    public String getWeather(String city) {
        return city + " 天气晴朗,25 度";
    }
}

// Controller 中使用
@GetMapping("/tools")
public String toolsChat(@Autowired WeatherTool weatherTool) {
    String response = createClient()
        .prompt("北京天气怎么样?")
        // 方式 1: 直接传入工具对象列表
        .tools(List.of(weatherTool)) 
        // 方式 2: 或者指定工具名称 .toolNames("getWeather")
        .call()
        .content();
    
    // 模型会自动识别意图,调用 getWeather("北京") 并整合结果
    return response; 
}

3.1.9 Advisors (切面顾问) API: advisors()

作用:在请求前后插入逻辑,如记忆管理、日志记录、安全过滤。

@GetMapping("/advisors")
public String advisorsChat(@Autowired ChatMemory chatMemory) {
    String response = createClient()
        .prompt("我叫小明,记住我的名字")
        // 添加记忆顾问,自动管理上下文
        .advisors(new MessageChatMemoryAdvisor(chatMemory)) 
        // 添加日志顾问
        .advisors(new SimpleLoggerAdvisor()) 
        .call()
        .content();
    return response;
}

3.2 ChatModel

ChatModel 是更底层的接口,ChatClient 内部其实是基于它构建的。当你需要完全控制请求/响应对象时使用。

3.2.1 ChatClient vs ChatModel 返回差异

调用方式

返回类型

适用场景

示例

chatClient.prompt().call().content()

String

快速获取文本

"等于 4"

chatClient.prompt().call().chatResponse()

ChatResponse

需要元数据/多结果

查看 token 用量

chatClient.prompt().call().entity(Class<T>)

T

结构化输出

ActorFilms 对象

chatModel.call(prompt)

ChatResponse

底层 API 直接调用

自定义集成

chatClient.prompt().stream().content()

Flux<String>

流式文本

打字机效果

chatClient.prompt().stream().chatResponse()

Flux<ChatResponse>

流式 + 元数据

实时显示 token 消耗

ChatClient 是 ChatModel 的高级封装ChatClient 提供了 fluent API、模板、Advisor 等增强功能,底层最终调用 ChatModel

特别需要注意的是返回的结果的对象结构是:

ChatResponse (模型完整响应)
 ├─► List<Generation> getResults()    // 可能返回多个候选结果
 ├─► ChatResponseMetadata getMetadata() // 响应级元数据
 │
 └─► Generation (单次生成结果)
      ├─► AssistantMessage getOutput()  // 模型输出的消息内容
      ├─► ChatGenerationMetadata getMetadata() // 生成级元数据
      │
      └─► AssistantMessage (助手消息)
           ├─► String getText()          // 文本内容 你常用的
           ├─► Map<String,Object> getMetadata() // 消息级元数据
           ├─► List<ToolCall> getToolCalls()   // 函数调用请求
           ├─► List<Media> getMedia()          // 多模态内容
           └─► MessageType getMessageType()    // 消息类型: ASSISTANT

3.2.2 注入与调用

@Autowired
private ChatModel chatModel; // 底层模型接口

@GetMapping("/model-low-level")
public String lowLevelChat() {
    // 1. 构建消息
    UserMessage userMessage = new UserMessage("你好");
    
    // 2. 构建 Prompt 对象
    Prompt prompt = new Prompt(userMessage);
    
    // 3. 调用 chat 方法
    ChatResponse response = chatModel.call(prompt);
    
    // 4. 解析结果
    return response.getResult().getOutput().getText();
}

3.2.3 流式调用 (Streaming)

@GetMapping("/model-stream")
public Flux<ChatResponse> modelStream() {
    Prompt prompt = new Prompt("写个故事");
    // 返回 Flux 流
    return chatModel.stream(prompt);
}

3.2.4 异步调用 (Async)

@GetMapping("/model-async")
public CompletableFuture<String> asyncChat() {
    Prompt prompt = new Prompt("异步任务");
    // 返回 CompletableFuture
    CompletableFuture<ChatResponse> future = chatModel.callAsync(prompt);
    
    return future.thenApply(resp -> 
        resp.getResult().getOutput().getText()
    );
}

四、基于ChatClient的实践练习

学了上述内容,大家完全可以接一个Deepseek或者其他厂商的model去做一个demo了!

这里给一个小实例:

场景:构建一个支持记忆工具调用(查订单)、结构化输出(提取用户意图)的客服系统。

4.1 项目结构

src/main/java
├── config/          # 配置类
├── model/           # POJO 定义
├── tools/           # 工具函数
└── controller/      # 接口入口

4.2 定义结构化数据 (POJO)

package com.example.demo.model;

import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * 用户意图识别结果
 * 用于将自然语言转换为业务可处理的对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserIntent {
    // 意图类型:QUERY_ORDER, COMPLAINT, CONSULT
    private String type;
    // 提取的关键参数,如订单号
    private String orderId;
    // 用户情绪分数 1-10
    private int sentimentScore;
}

4.3 定义业务工具 (Tools)

package com.example.demo.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class OrderService {

    /**
     * 模拟数据库查询订单
     * @Tool 描述至关重要,决定了 LLM 何时调用此函数
     */
    @Tool(description = "根据订单号查询订单状态和物流信息")
    public String queryOrderStatus(String orderId) {
        // 模拟业务逻辑
        if ("ORD123".equals(orderId)) {
            return "订单 ORD123 已发货,物流单号 SF123456";
        }
        return "未找到订单:" + orderId;
    }
}

4.4 控制器实现 (Controller)

package com.example.demo.controller;

import com.example.demo.model.UserIntent;
import com.example.demo.tools.OrderService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageChatMemoryAdvisor;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/cs")
public class CustomerServiceController {

    private final ChatClient chatClient;
    private final OrderService orderService;
    private final ChatMemory chatMemory;

    // 注入依赖
    public CustomerServiceController(ChatClient.Builder builder, 
                                     OrderService orderService,
                                     ChatMemory chatMemory) {
        this.orderService = orderService;
        this.chatMemory = chatMemory;
        // 构建默认客户端,预置系统指令
        this.chatClient = builder
            .defaultSystem("""
                你是电商智能客服「小蜜」。
                1. 先识别用户意图。
                2. 如果需要查订单,调用 queryOrderStatus 工具。
                3. 语气亲切专业。
                """)
            .build();
    }

    /**
     * 综合实战接口
     * 1. 记忆上下文
     * 2. 工具调用
     * 3. 意图识别
     */
    @PostMapping("/chat")
    public Map<String, Object> chat(@RequestParam String message,
                                    @RequestParam String sessionId) {
        
        // 1. 意图识别 (结构化输出)
        // 单独调用一次用于分类,实际生产中可优化为一步
        UserIntent intent = chatClient.prompt()
            .system("请分析用户输入的意图,返回 JSON 格式")
            .user(message)
            .call()
            .entity(UserIntent.class);

        // 2. 正式对话 (带记忆 + 工具)
        String reply = chatClient.prompt()
            .user(message)
            // 启用记忆顾问,传入会话 ID 实现多用户隔离
            .advisors(a -> a.param(MessageChatMemoryAdvisor.CONVERSATION_ID, sessionId))
            // 注册工具,模型可根据意图自动调用
            .tools(orderService) 
            .call()
            .content();

        // 3. 返回组合结果
        return Map.of(
            "reply", reply,
            "intent", intent.getType(),
            "orderId", intent.getOrderId(),
            "sentiment", intent.getSentimentScore()
        );
    }
}

那么急于以上的实验我们就学习到了ChatClient和ChatModel的实际使用了!

五、总结

实际上本文讲解了SpringAI与其第一个核心模块:Model模块,接下来会讲解的是:

├── Model(模型集成)
├── Vector Store(向量库)
├── Document Processing(文档处理)
├── Chat Memory(对话记忆)
├── RAG / Tools / MCP / Evaluation(扩展优化)
└── Agent(智能体)

等模块

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐