Spring AI 基本组件详解 —— ChatClient、Prompt、Memory

发布于:2025-07-09 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、前言

本篇教程是《Java驱动AI革命:Spring AI八篇进阶指南——从架构基础到企业级智能系统实战》系列的第二篇,将手把手教你如何使用 Spring AI 的核心三剑客:ChatClientPromptTemplateMemory。它们是构建任何一个人工智能聊天应用的基础。

最重要的是,本篇博文将完全抛开网上各种教程对国外服务的依赖,全程使用免费、开源的本地大模型运行平台 Ollama 和国内优秀的 Qwen (通义千问) 模型。这意味着你不需要注册付费的API,就能在你自己的电脑上体验到大模型的神奇之处,是不是比现在流行的各种网课给力?话不多说,现在咱们就在本地局域网内,用Java编码免费把大模型玩起来。

你将学到:

  • 如何在本地安装和运行 Ollama。
  • 如何在 Ollama 中下载并运行 Qwen 模型。
  • 如何从零开始搭建一个 Spring Boot 项目,并集成 Spring AI。
  • 如何使用 ChatClient 发送你的第一个请求。
  • 如何使用 PromptTemplate 灵活地构建你的提问。
  • 如何使用 Memory 让你的AI记住之前的对话,实现多轮交流。

二、本地环境准备:Ollama 与 Qwen 模型

在使用 Spring AI 之前,我们首先需要在本地搭建一个大模型运行环境。这里我们选择 Ollama,它是一个非常方便的本地大模型运行平台。
具体也可参考之前写过的《Ollama 深度使用指南:在本地玩转大型语言模型》

1. 安装 Ollama
在这里插入图片描述

  • 下载 Ollama: 访问 Ollama 的官方网站:https://ollama.com/download
  • 选择你的操作系统: 根据你的电脑系统(Windows、macOS、Linux),下载对应的安装包。
  • 安装:
    • Windows/macOS: 双击下载的安装包,按照提示一步步完成安装。安装过程非常简单,就像安装普通软件一样。
    • Linux: 按照官网的命令行指示进行安装。

安装完成后,Ollama 会在后台默默运行,你通常不会看到一个单独的应用程序窗口,它作为一个服务在你的系统上运行。

2. 下载 Qwen 模型

Ollama 安装好后,我们就可以通过命令行下载 Qwen 模型了。Qwen 是阿里云开源的一系列优秀大模型。

  • 打开命令行/终端:

    • Windows:Win + R 键,输入 cmdpowershell,然后回车。
    • macOS/Linux: 打开“终端”应用程序。
  • 下载模型: 在命令行中输入以下命令并回车。这里我们以下载 qwen:7b 模型为例,7b 表示70亿参数,对电脑配置要求不高,适合初学者。

    ollama pull/run qwen:7b
    

    这个过程可能需要一些时间,因为它会从互联网上下载模型文件,文件通常比较大(几个GB)。请耐心等待下载完成。下载完成后,Ollama 服务就已经可以使用 qwen:7b 模型了!

    注意: Ollama 默认运行在 http://localhost:11434。如果你的电脑防火墙或网络设置有问题,可能需要检查。但通常情况下,安装后即可直接使用。
    在这里插入图片描述


三、Spring Boot 项目初始化与配置

现在本地大模型环境已经准备好了,我们来创建 Spring Boot 项目并集成 Spring AI。

1. 创建 Spring Boot 项目
在这里插入图片描述

如上图所示,推荐使用 Spring Initializr (https://start.spring.io/) 来快速创建项目,当然如果你是idea的旗舰版用户也可以本地快速创建。

  • 访问地址: 打开浏览器,访问 https://start.spring.io/
  • 项目设置:
    • Project: Maven Project (或 Gradle Project,这里我们用Maven)
    • Language: Java
    • Spring Boot: 选择最新的 3.5.3 版本,参考官网描述,最低3.4.x
    • Group: com.example (可以自定义,例如 com.yourcompany)
    • Artifact: spring-ai-ollama-demo (项目名称,可以自定义)
    • Java: 选择 17 或更高版本 (建议Java 17 LTS)
  • 添加依赖 (Dependencies):
    • 点击 “Add Dependencies…” 按钮
    • 搜索并添加:
      • Spring Web (可选,但通常用于构建Web应用,方便测试)
      • Lombok (可选,但非常推荐,可以大大简化Java代码)
      • Spring AI Ollama Starter (这个是关键!用于集成Ollama)
  • 生成项目: 点击 “Generate” 按钮,下载项目的压缩包。
  • 解压并导入IDE: 将下载的 spring-ai-ollama-demo.zip 解压到你喜欢的位置,然后使用你的Java集成开发环境 (IDE),例如 IntelliJ IDEA 或 Eclipse,导入这个Maven项目。

2. pom.xml 依赖配置

导入项目后,检查 pom.xml 文件,确保它包含了所有必要的依赖。
对于spring-ai版本选择,由于进化很快,这里咱用最新的1.0.0-M5来演示,参考如下图
在这里插入图片描述

下面是一个完整的 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.5.3</version> <relativePath/> </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-ai-ollama-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai-ollama-demo</name>
    <description>Demo project for Spring AI with Ollama</description>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M5</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-ollama-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

3. application.properties 配置

src/main/resources/ 目录下找到 application.properties 文件,并添加以下内容。这些配置告诉 Spring AI 如何连接到 Ollama 服务,以及使用哪个模型。

# Spring AI Ollama 配置
# Ollama 服务的地址,默认是 http://localhost:11434
spring.ai.ollama.chat.base-url=http://localhost:11434

# 指定要使用的模型名称,这里我们使用之前下载的qwen:7b
spring.ai.ollama.chat.options.model=qwen:7b

# 如果需要,可以调整温度参数(控制AI回答的创造性)
# spring.ai.ollama.chat.options.temperature=0.7

注意: 如果你的 Ollama 服务没有运行在 http://localhost:11434 (例如,你在WSL中运行或配置了其他端口),你需要修改 spring.ai.ollama.chat.base-url 为正确的地址。


四、ChatClient:对话的统一入口

ChatClient 是 Spring AI 提供的最核心接口,它充当了你与大模型对话的统一入口。无论你底层用的是 Ollama、OpenAI 还是其他模型,你都通过 ChatClient 来发送提问并接收回答。

1. 核心作用与基本使用

Spring AI 会根据你的配置(这里是 Ollama)自动为你创建一个 ChatClient 的 Bean,你只需要通过 Spring 的 @Autowired 注解就可以把它注入到你的代码中并使用。

现在,我们创建一个主要的 Spring Boot 应用程序类 SpringAiOllamaDemoApplication.java,并添加第一个使用 ChatClient 的示例。

文件路径: src/main/java/com/example/springaiollamademopackage/SpringAiOllamaDemoApplication.java (请将 package 名称替换为你的实际项目包名)

// src/main/java/com/example/springaiollamademodemo/SpringAiOllamaDemoApplication.java
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication // 这是一个Spring Boot应用
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        // Spring Boot 应用的启动入口
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    /**
     * 示例1: ChatClient 基本使用
     *
     * @param chatClient Spring 自动注入的 ChatClient 实例
     * @return CommandLineRunner,在应用启动后执行其run方法
     */
    @Bean
    public CommandLineRunner runChatClientBasic(ChatClient chatClient) {
        return args -> {
            System.out.println("--- ChatClient 基本使用:向大模型提问 ---");
            // 使用chatClient.prompt().user("你的问题").call().content() 发送问题并获取回答内容
            String responseContent = chatClient.prompt()
                    .user("解释下量子纠缠的原理")
                    .call()
                    .content(); // 获取大模型的回答内容
            System.out.println("大模型响应: " + responseContent);
            System.out.println("\n------------------------------------------------\n");
        };
    }
}

如何运行:

  1. 确保你的 Ollama 服务和 qwen:7b 模型正在运行。
  2. 在你的IDE中,找到 SpringAiOllamaDemoApplication 类,右键点击并选择 “Run ‘SpringAiOllamaDemoApplication.main()’” (或类似的选项)。
  3. 程序启动后,你会在IDE的控制台(Console)中看到大模型对“量子纠缠”的解释。

在这里插入图片描述

2. 进阶用法:封装多轮上下文 (设置角色)

ChatClient 允许你发送包含多条消息的请求,这对于给AI设置一个特定的“角色”或者提供更丰富的对话背景非常有用。你可以发送 system (系统) 消息和 user (用户) 消息。

SpringAiOllamaDemoApplication.java 中,继续添加一个 @Bean 方法:

// ... (保留SpringAiOllamaDemoApplication的main方法和之前的runChatClientBasic Bean)
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    // ... (保留 runChatClientBasic 方法)

    /**
     * 示例2: ChatClient 多轮上下文 (设置角色)
     * 通过系统消息为大模型设定一个角色,让它的回答更符合预期。
     *
     * @param chatClient Spring 自动注入的 ChatClient 实例
     * @return CommandLineRunner
     */
    @Bean
    public CommandLineRunner runChatClientWithContext(ChatClient chatClient) {
        return args -> {
            System.out.println("--- ChatClient 多轮上下文 (设置角色): 让大模型成为科普专家 ---");
            String responseContent = chatClient.prompt()
                    .system("你是一个科普专家,擅长用通俗易懂的语言解释科学原理。") // 系统消息:设定AI角色
                    .user("讲讲黑洞是怎么形成的?") // 用户消息:实际问题
                    .call()
                    .content();
            System.out.println("大模型(科普专家)响应: " + responseContent);
            System.out.println("\n------------------------------------------------\n");
        };
    }
}

再次运行 SpringAiOllamaDemoApplication,你会发现大模型会以“科普专家”的口吻来解释黑洞的形成。


在这里插入图片描述

五、PromptTemplate:灵活的提示模板引擎

在实际开发中,我们往往需要根据不同的输入动态地生成问题。例如,一个翻译应用需要翻译不同的文本,一个总结工具需要总结不同的文章。PromptTemplate 就是为此而生,它允许你定义带有占位符的模板,然后动态填充数据。

1. 为何需要 Prompt 模板?

想象一下,你每次都要手动拼接字符串来提问,当问题结构复杂、变量多时,代码会变得非常难以维护。PromptTemplate 就像一个填空题,你只需要填入变量的值,它就能帮你生成完整的提问。

2. 定义方式示例与使用

PromptTemplate 可以直接在代码中通过字符串定义,也可以更优雅地与 ChatClient 的链式调用结合。

SpringAiOllamaDemoApplication.java 中,继续添加一个 @Bean 方法:

// ... (保留SpringAiOllamaDemoApplication的main方法和之前的ChatClient Beans)
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    // ... (保留 runChatClientBasic 和 runChatClientWithContext 方法)

    /**
     * 示例3: PromptTemplate 使用
     * 通过占位符定义模板,动态填充变量来构建问题。
     *
     * @param chatClient Spring 自动注入的 ChatClient 实例
     * @return CommandLineRunner
     */
        @Bean
    public CommandLineRunner runPromptTemplate(ChatClient chatClient) {
        return args -> {
            System.out.println("--- PromptTemplate 使用:动态提问 ---");

            // 场景一:字符串模板示例 - 翻译助手
            String textToTranslate = "Hello, Spring AI is awesome!";

            // 创建模板并渲染变量
            PromptTemplate translationTemplate = new PromptTemplate("请帮我翻译这段内容:{{content}}");
            String translatedContent = chatClient.prompt()
                    .system("你是一名翻译助手,请帮助我翻译以下内容:")
                    .user(translationTemplate.render(Map.of("content", textToTranslate)))
                    .call()
                    .content();
            System.out.println("翻译结果: " + translatedContent);

            System.out.println("\n--- 另一个PromptTemplate示例:简历优化助手 ---");

            // 场景二:模拟结构化模板示例 - 简历优化助手
            String resumeContent = "我在一家大型互联网公司担任客户支持,负责日常用户咨询和故障排除。";
            PromptTemplate resumeTemplate = new PromptTemplate("请帮我优化这段内容:{{content}}");
            String optimizedResume = chatClient.prompt()
                    .system("你是一名专业的简历优化专家,请帮助我改进简历内容,使其更具吸引力。")
                    .user(resumeTemplate.render(Map.of("content", resumeContent)))
                    .call()
                    .content();
            System.out.println("优化后的简历内容: " + optimizedResume);

            System.out.println("\n------------------------------------------------\n");
        };
    }

运行项目,你会看到大模型分别完成了翻译和简历优化任务,且是根据你提供的文本动态生成的。

在这里插入图片描述


六、Memory:对话上下文与智能体的记忆机制

在真实的聊天场景中,AI 需要记住之前的对话,才能进行连贯的多轮交流。例如,你问完“北京天气如何?”后,接着问“那需要带伞吗?”,AI需要知道“它”指的是“北京的天气”。ChatMemory 就是用来解决这个问题的,它为 AI 提供了“记忆”能力。

1. 接入示例

Spring AI 提供了 InMemoryChatMemory (内存记忆) 作为默认实现,它会将对话历史存储在内存中。要让 ChatClient 拥有记忆能力,我们需要用到 Spring AI 提供的 MessageChatMemoryAdvisor

为了更好的结构化,我们创建一个新的配置类 ChatMemoryConfig.java 来定义带有记忆功能的 ChatClient

文件路径: src/main/java/com/example/springaiollamademodemo/ChatMemoryConfig.java (请将 package 名称替换为你的实际项目包名)

// src/main/java/com/example/springaiollamademodemo/ChatMemoryConfig.java
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 这是一个配置类
public class ChatMemoryConfig {

    /**
     * 定义一个 InMemoryChatMemory Bean。
     * 这是对话历史的存储介质,默认将历史存储在内存中。
     * @return InMemoryChatMemory 实例
     */
    @Bean
    public ChatMemory chatMemory() {
        return new InMemoryChatMemory();
    }

    /**
     * 定义一个带有记忆功能的 ChatClient。
     * 通过 ChatClient.Builder 添加 MessageChatMemoryAdvisor 来实现记忆功能。
     * 给这个 Bean 一个特定的名称 "chatClientWithMemory",以免与默认的 ChatClient 冲突。
     *
     * @param chatClientBuilder Spring AI 自动配置的 ChatClient.Builder
     * @param chatMemory 我们上面定义的 ChatMemory 实例
     * @return 带有记忆功能的 ChatClient
     */
    @Bean("chatClientWithMemory")
    public ChatClient chatClientWithMemory(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {
        return chatClientBuilder
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) // 添加记忆顾问
                .build();
    }
}

现在,回到 SpringAiOllamaDemoApplication.java,添加一个使用这个带有记忆功能的 ChatClient 的示例。

// ... (保留SpringAiOllamaDemoApplication的main方法和所有之前的Beans)
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier; // 导入Qualifier注解

@SpringBootApplication
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    // ... (保留 runChatClientBasic, runChatClientWithContext, runPromptTemplate 方法)

    /**
     * 示例4: ChatClient 与 Memory 结合 (多轮对话)
     * 使用带有记忆功能的 ChatClient 进行多轮对话,AI会记住之前的上下文。
     *
     * @param chatClientWithMemory 通过 @Qualifier 注解注入我们定义的带有记忆功能的 ChatClient
     * @return CommandLineRunner
     */
    @Bean
    public CommandLineRunner runChatClientWithMemory(@Qualifier("chatClientWithMemory") ChatClient chatClientWithMemory) {
        return args -> {
            System.out.println("--- ChatClient 与 Memory 结合:实现多轮对话 ---");

            System.out.println("用户: 今天天气怎么样?");
            String response1 = chatClientWithMemory.prompt()
                    .user("今天天气怎么样?")
                    .call()
                    .content();
            System.out.println("大模型响应: " + response1);

            // 第二次提问:不直接提及地点,看大模型是否能记住上下文
            System.out.println("用户: 那我需要带伞吗?");
            String response2 = chatClientWithMemory.prompt()
                    .user("那我需要带伞吗?")
                    .call()
                    .content();
            System.out.println("大模型响应: " + response2);

            System.out.println("\n------------------------------------------------\n");
        };
    }
}

在这里插入图片描述

重新运行 SpringAiOllamaDemoApplication,你会发现大模型在回答第二个问题时,能够根据第一个问题的上下文(“今天天气怎样?”)来判断是否需要带伞。

2. 使用标识:通过 ConversationId 管理多会话

在实际应用中,你可能需要为不同的用户或不同的对话主题维护独立的记忆。Spring AI 允许你通过 conversationId 来实现这一点。同一个 ChatMemory 实例可以管理多个 conversationId 对应的对话历史。

我们可以在 ChatClient.Builder 中通过 conversationId() 方法来指定当前的会话ID。

SpringAiOllamaDemoApplication.java 中,继续添加一个 @Bean 方法:

// ... (保留SpringAiOllamaDemoApplication的main方法和所有之前的Beans)
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired; // 导入Autowired
import org.springframework.beans.factory.annotation.Qualifier; // 导入Qualifier

@SpringBootApplication
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    // ... (保留 runChatClientBasic, runChatClientWithContext, runPromptTemplate, runChatClientWithMemory 方法)

    @Autowired // 注入 ChatClient.Builder 以便我们构建不同配置的ChatClient
    private ChatClient.Builder chatClientBuilder;

    @Autowired // 注入我们之前定义的 ChatMemory 实例
    private ChatMemory chatMemory;

    /**
     * 示例5: 通过 ConversationId 管理多会话
     * 为不同的用户或场景设置独立的会话ID,即使使用同一个 ChatMemory 实例,也能保持对话独立。
     *
     * @return CommandLineRunner
     */
    @Bean
    public CommandLineRunner runChatClientWithSpecificConversationId() {
        return args -> {
            System.out.println("--- ChatClient 与特定 ConversationId 结合 ---");

            String userId1 = "user-alice-session-001"; // 模拟第一个用户的会话ID
            ChatClient aliceChatClient = chatClientBuilder
                    .chatMemory(chatMemory) // 使用同一个 ChatMemory 实例
                    .conversationId(userId1) // 指定会话ID
                    .build();

            System.out.println("Alice (会话ID: " + userId1 + "): 中国的首都是哪里?");
            aliceChatClient.prompt()
                    .user("中国e的首都是哪里?")
                    .call()
                    .content(); // 发送问题,会话历史存入 'user-alice-session-001'

            System.out.println("Alice (会话ID: " + userId1 + "): 它的简称是什么?");
            String responseAlice = aliceChatClient.prompt()
                    .user("它的简称是什么?")
                    .call()
                    .content();
            System.out.println("大模型响应 (Alice): " + responseAlice); // 大模型能识别“它”指代的是北京

            System.out.println("\n--- 切换到另一个用户的会话 ---");

            String userId2 = "user-bob-session-002"; // 模拟第二个用户的会话ID
            ChatClient bobChatClient = chatClientBuilder
                    .chatMemory(chatMemory) // 同样使用同一个 ChatMemory 实例
                    .conversationId(userId2) // 指定另一个会话ID
                    .build();

            System.out.println("Bob (会话ID: " + userId2 + "): 世界上最高的山峰是哪座?");
            bobChatClient.prompt()
                    .user("世界上最高的山峰是哪座?")
                    .call()
                    .content();

            System.out.println("Bob (会话ID: " + userId2 + "): 它的海拔是多少?");
            String responseBob = bobChatClient.prompt()
                    .user("它的海拔是多少?")
                    .call()
                    .content();
            System.out.println("大模型响应 (Bob): " + responseBob); // 大模型能识别“它”指代的是珠穆朗玛峰

            System.out.println("\n------------------------------------------------\n");
        };
    }
}

在这里插入图片描述

运行项目,你会看到两个独立的对话过程,即使是同一个 ChatMemory 实例,由于 conversationId 不同,大模型也能正确地记住各自的上下文。

3. 查看对话历史 (通过注入 ChatMemory 实例)

如果你想在代码中直接查看某个 conversationId 下的对话历史,可以注入 ChatMemory Bean 并调用其内部方法。请注意:ChatMemory 接口本身在最新版本中主要用于内部集成。如果你需要获取特定ID的历史,可能需要对 InMemoryChatMemory 进行向下转型,或者自定义一个 ChatMemory 实现来提供更方便的查询方法。

下面我们提供一个用于演示如何查看历史的示例,但请记住,直接操作 InMemoryChatMemory 的内部结构可能不是最推荐的公共API用法,更推荐在业务逻辑层封装一个服务来管理和查询记忆。

// ... (保留SpringAiOllamaDemoApplication的main方法和所有之前的Beans)
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory; // 导入 InMemoryChatMemory
import org.springframework.ai.chat.messages.Message; // 导入 Message 类
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.List;
import java.util.Map; // 导入 Map

@SpringBootApplication
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    // ... (保留所有之前的 ChatClient 和 CommandLineRunner Beans)

    @Autowired
    private ChatMemory chatMemory; // 注入我们定义的 ChatMemory 实例

    /**
     * 示例6: 查看对话历史
     * 演示如何从 ChatMemory 中获取特定会话的对话历史。
     * 注意:这里为了演示直接获取 InMemoryChatMemory 的内部结构,实际生产中推荐封装为服务。
     *
     * @return CommandLineRunner
     */
    @Bean
    public CommandLineRunner viewChatHistoryFixed() { // 修改方法名以避免冲突
        return args -> {
            System.out.println("--- 查看对话历史 (最新修复版) ---");

            // 我们尝试查询示例5中Alice使用的固定会话ID
            String userIdToRetrieve = "fixed-alice-session-001";
            System.out.println("--- 尝试查看会话ID: " + userIdToRetrieve + " 的对话历史 ---");

            // 直接通过 ChatMemory 接口的 get(String conversationId) 方法获取消息列表
            List<Message> messagesForUser = chatMemory.get(userIdToRetrieve);

            if (messagesForUser != null && !messagesForUser.isEmpty()) {
                System.out.println("会话ID: " + userIdToRetrieve + " 的对话历史:");
                for (Message message : messagesForUser) {
                    // 打印消息类型(如 USER, AI, SYSTEM)和内容
                    System.out.println("  " + message.getMessageType().name() + ": " + message.getContent());
                }
            } else {
                System.out.println("会话ID: " + userIdToRetrieve + " 没有找到对话历史或历史为空。");
                System.out.println("请确保在运行此示例之前,`runChatClientWithSpecificConversationIdFixed` 方法已经运行,并且向此会话ID写入了消息。");
            }

            System.out.println("\n------------------------------------------------\n");
        };
    }

在这里插入图片描述

运行项目,你将看到指定会话ID的对话历史被打印出来。


七、实战案例:构建多轮问答机器人

结合以上所有组件,我们可以轻松构建一个能够进行多轮对话的问答机器人。

SpringAiOllamaDemoApplication.java 中,继续添加最后一个 @Bean 方法:

// ... (保留SpringAiOllamaDemoApplication的main方法和所有之前的Beans)
package com.example.springaiollamademodemo; // 请替换为你的实际项目包名

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Qualifier; // 导入Qualifier

@SpringBootApplication
public class SpringAiOllamaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAiOllamaDemoApplication.class, args);
    }

    // ... (保留所有之前的 ChatClient 和 CommandLineRunner Beans)

    /**
     * 示例7: 实战案例:构建多轮问答机器人
     * 这是一个完整的例子,展示了如何利用带记忆的 ChatClient 进行连续的、有上下文的对话。
     *
     * @param chatClientWithMemory 带有记忆功能的 ChatClient
     * @return CommandLineRunner
     */
    @Bean
    public CommandLineRunner runMultiTurnBot(@Qualifier("chatClientWithMemory") ChatClient chatClientWithMemory) {
        return args -> {
            System.out.println("--- 实战案例:构建多轮问答机器人 ---");
            System.out.println("机器人: 你好,我是基于Qwen的问答机器人,有什么可以帮助你的?");

            // 第一次对话
            String userQuestion1 = "最近有什么好看的科幻电影吗?";
            System.out.println("用户: " + userQuestion1);
            String botResponse1 = chatClientWithMemory.prompt()
                    .user(userQuestion1)
                    .call()
                    .content();
            System.out.println("机器人: " + botResponse1);

            // 第二次对话,利用上下文
            String userQuestion2 = "那其中有没有关于时间旅行的?";
            System.out.println("用户: " + userQuestion2);
            String botResponse2 = chatClientWithMemory.prompt()
                    .user(userQuestion2)
                    .call()
                    .content();
            System.out.println("机器人: " + botResponse2);

            // 第三次对话,继续利用上下文
            String userQuestion3 = "它的导演是谁?";
            System.out.println("用户: " + userQuestion3);
            String botResponse3 = chatClientWithMemory.prompt()
                    .user(userQuestion3)
                    .call()
                    .content();
            System.out.println("机器人: " + botResponse3);

            System.out.println("\n------------------------------------------------\n");
        };
    }
}

在这里插入图片描述

再次运行 SpringAiOllamaDemoApplication,你将看到一个完整的、多轮的对话过程,大模型能够基于之前的聊天内容,给出连贯且符合上下文的回答。


八、总结

组件 作用 场景
ChatClient 与 LLM 模型对话的统一接口 任何交互类任务,发送请求并处理响应
PromptTemplate 组织 Prompt 模板与变量填充 多语言翻译、自动回复、Agent 构建,动态生成Prompt
Memory 保存上下文历史,提供会话记忆 多轮对话、RAG、智能 Agent,维持对话连贯性

恭喜你!通过本篇教程,你不仅学会了 Spring AI 的核心组件,还成功在本地搭建了 Ollama 和 Qwen 模型,并用它们运行了你的第一个 AI 应用。这为你探索人工智能的世界打下了坚实的基础。


完整项目截图:

在这里插入图片描述

资料

参考

《Ollama 深度使用指南:在本地玩转大型语言模型》
本文代码:https://gitcode.com/superfreeman/spring-ai-demo
系列教程:《Java驱动AI革命:Spring AI八篇进阶指南——从架构基础到企业级智能系统实战》
在这里插入图片描述