Spring AI 入门:手把手教你构建一个拥有“记忆”的恋爱助手

0 阅读6分钟

大家好,我是野人。作为一名AI新手,当你第一次接触Spring AI时,可能会被各种概念弄晕:什么是ChatClient?什么是Advisor?怎么让AI记住我之前说的话?
别担心,本文将通过一个生动的  “恋爱助手”  案例,带你一步步梳理这些核心概念,并最终实现一个支持多轮对话的AI应用。

1. 核心工具:ChatClient

在Spring AI的世界里,ChatClient是你与大模型对话的主要入口。你可以把它想象成一个智能电话,你对着它说话,它帮你接通并转告给真正的大模型(如通义千问、ChatGPT)。

那么,如何获得这个“电话”呢?主要有两种方式:

方式一:构造函数注入(推荐)

这种方式代码清晰,便于测试和维护。就像你去营业厅,明确告诉工作人员你需要什么配置的电话。

image.png

方式二:字段注入 + 手动构造

这种方式比较直接,通过@Autowired拿到模型,然后手动用模型去创建电话

image.png 新手提示:  推荐使用第一种方式(构造器注入),因为它更符合Spring的规范,并且在你后续需要添加更多功能(比如拦截器)时会更加灵活。

2. 增强工具:Advisors(顾问)

光有电话还不够,我们有时想在打电话前先检查一下信号(检查提示词),或者在挂电话后记录一下通话内容(记录日志)。Spring AI 的 Advisors(顾问)  就是用来做这个的。

你可以把 Advisor 想象成一个个  “拦截器” ,它们像洋葱一样层层包裹住真正的大模型。你的话(Prompt)要先经过这些顾问才能传给模型,模型的回答也要经过它们才能返回来。

image.png 执行流程拆解:

  1. 请求发起:你的问题(Prompt)被包装成一个AdvisedRequest
  2. 前置增强:请求依次经过 AroundAdvisor 链。每个顾问都可以查看或修改你的问题。
  3. 调用模型:经过所有顾问处理后,请求才真正发送给 ChatModel(大模型)。
  4. 后置增强:模型的返回结果被包装成AdvisedResponse,再次逆序经过顾问链。顾问们可以查看或修改这个结果。
  5. 返回结果:最终的结果(ChatResponse)返回给你。

3. 赋予AI“记忆”:ChatMemoryAdvisor

默认情况下,大模型是没有记忆的。你问“你好”,它回答“你好”;你接着问“我刚才说了什么?”,它就忘了。为了让AI能记住上下文(实现多轮对话),我们就需要用到 ChatMemoryAdvisor

Spring AI 提供了几种把“记忆”塞给AI的方式:

  • MessageChatMemoryAdvisor(推荐) :把历史对话以原始消息格式(数组)塞给AI。就像把聊天记录截图发给AI看,它理解得最准确。
  • PromptChatMemoryAdvisor:把历史对话转换成一段文字描述,放到系统提示词里。这会丢失一些格式信息。
  • VectorStoreChatMemoryAdvisor:用向量数据库存储海量历史,适合超长对话。

记忆存储在哪里?ChatMemory

顾问本身不负责存数据,它只是负责“取”和“放”。真正存数据的是 ChatMemory
Spring AI 提供了多种存储方案:

  • InMemoryChatMemory:存在内存里(程序重启就没了,适合测试)。
  • JdbcChatMemory:存在数据库里(永久保存)。

4. 实战:打造一个会记住你的恋爱助手

现在,我们把上面的知识都串起来,写一个真正的 LoveApp。这个助手不仅能以恋爱专家的身份回答你,还能记住你上一轮说了什么(多轮对话)。

项目需求:

  1. AI角色:深耕恋爱心理领域的专家。
  2. 功能:支持多轮对话,能根据不同的chatId区分不同用户的对话历史。

完整代码实现:

package com.swl.baoaiagent.app;

import lombok.extern.slf4j.Slf4j;
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.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.stereotype.Component;

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

@Component
@Slf4j
public class LoveApp {

    // 对话客户端
    private ChatClient chatClient;

    // 系统预设:告诉AI它该扮演什么角色
    private static final String SYSTEM_PROMPT = "扮演深耕恋爱心理领域的专家。开场向用户表明身份,告知用户可倾诉恋爱难题。围绕单身、恋爱、已婚三种状态提问:单身状态询问社交圈拓展及追求心仪对象的困扰;恋爱状态询问沟通、习惯差异引发的矛盾;已婚状态询问家庭责任与亲属关系处理的问题。引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。";

    /**
     * 构造函数:初始化AI助手
     * @param dashscopeChatModel Spring注入的通义千问模型
     */
    public LoveApp(ChatModel dashscopeChatModel) {
        // 1. 创建记忆存储器(这里先用内存存储,方便测试)
        ChatMemory chatMemory = new InMemoryChatMemory();

        // 2. 构建ChatClient,并装配上“记忆顾问”
        this.chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultSystem(SYSTEM_PROMPT)          // 设置角色
                .defaultAdvisors(
                        // 使用链式构造器创建记忆顾问
                        MessageChatMemoryAdvisor.builder(chatMemory).build()
                )
                .build();
    }

    /**
     * AI 基础对话方法(支持多轮对话)
     * @param message 用户输入的消息
     * @param chatId 会话ID(用于区分不同用户的对话)
     * @return AI 的回复内容
     */
    public String doChat(String message, String chatId){
        // 发起对话
        ChatResponse chatResponse = chatClient.prompt()
                .user(message)  // 用户说:我最近和女朋友吵架了
                .advisors(spec -> spec
                        // 告诉顾问:这是谁的对话(张三还是李四)
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        // 告诉顾问:取最近几条历史记录?(这里取最近10条,后进先出)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .call()         // 拨打电话
                .chatResponse(); // 等待回复

        // 解析回复
        String content = chatResponse.getResult().getOutput().getText();
        log.info("AI回复: {}", content);
        return content;
    }
}

代码讲解

  1. 构造函数

    • 我们通过构造器传入了 ChatModel(大模型)。
    • 新建了一个 InMemoryChatMemory 作为临时记忆仓库。
    • 使用 ChatClient.builder(...) 进行链式配置:设置系统提示词 (SYSTEM_PROMPT),并装配默认顾问 (MessageChatMemoryAdvisor)。
  2. doChat 方法

    • 关键点1:chatId:这是一个非常重要的参数。它就像一个文件夹标签,告诉程序这个对话是属于“用户A”还是“用户B”。如果没有它,所有人的对话就会混在一起。

    • 关键点2:advisors 配置:我们在调用prompt()时,通过advisors动态指定了两个参数:

      • CHAT_MEMORY_CONVERSATION_ID_KEY:指定本次对话属于哪个会话ID。
      • CHAT_MEMORY_RETRIEVE_SIZE_KEY:指定从记忆中取回多少条最近的对话。这里设置为10,意味着AI会看到最近10轮(用户和AI一来一回算一轮)的聊天记录。

5. 总结

通过这个简单的恋爱助手例子,你应该对Spring AI有了一个初步的认识:

  • ChatClient 是你的AI应用的核心API。
  • Advisors 是强大的拦截器机制,用于横切关注点(如记忆、审核、日志)。
  • ChatMemoryAdvisor + ChatMemory 是实现多轮对话记忆的黄金搭档。Advisor负责处理逻辑,ChatMemory负责存储数据。

现在,你可以试着运行这个程序,连续问它几个关于恋爱的问题,比如“我喜欢一个女孩但不敢表白怎么办?”,然后接着问“你刚才建议我第一步做什么?”,你会发现,AI已经记住了你们的对话内容。

开源文档mubaodian/bao-ai-agent: AI超级智能体