【Java EE】【SpringAI】智能聊天机器人

目录

  • 一、项目介绍
    • [1.1 背景&⽬标](#1.1 背景&⽬标)
    • [1.2 核⼼功能](#1.2 核⼼功能)
  • 二、环境搭建
  • 三、简单对话
    • [3.1 接口定义](#3.1 接口定义)
    • [3.2 实现](#3.2 实现)
  • 四、对话记忆
    • [4.1 Chat Memory](#4.1 Chat Memory)
    • [4.2 修改接⼝](#4.2 修改接⼝)
    • [4.2 实现](#4.2 实现)
  • 四、对话历史
    • [4.1 存储会话](#4.1 存储会话)
    • [4.2 获取会话列表](#4.2 获取会话列表)
    • [4.3 获取会话记录](#4.3 获取会话记录)

一、项目介绍

1.1 背景&⽬标

模仿kimi简单实现⼀个智能聊天机器⼈,提升⽤⼾交互体验.

产品⽬标:

  • 提供流畅,⾃然的对话体验
  • ⽀持多轮对话及上下⽂理解
  • 能够回答常⻅问题
  • 记录和管理⽤⼾历史对话

1.2 核⼼功能

  • 对话

    • ⽀持⽤⼾与机器⼈进⾏⽂本对话
    • 实时响应⽤⼾输⼊,输出⾃然语⾔回复
  • 多轮对话

    • 能够理解和处理多轮对话,保持上下⽂连续性
    • ⽀持基于上下⽂的智能应答
  • 历史记录

    • ⾃动保存⽤⼾与机器⼈的对话历史
    • ⽀持⽤⼾查看历史对话内

二、环境搭建

创建项目并初始化

创建子项⽬:spring-chat-bot

pom:

xml 复制代码
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
			 <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>1.0.0-M6</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-M6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
    </dependencyManagement>

启动类

java 复制代码
package com.spring.chat.bot;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

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

添加配置⽂件

yml 复制代码
spring:
  application:
    name: spring-chat-bot
  ai:
    openai:
      api-key: sk-key
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat
          temperature: 0.7
logging:
  level:
    org.springframework.ai.chat.client.advisor: debug
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

三、简单对话

3.1 接口定义

java 复制代码
[请求]
GET/POST/chat/stream
[参数]
prompt(String): ⽤⼾输⼊的消息内容
[响应]
Flux流式返回的机器⼈回复内容(text/html;charset=utf-8)

3.2 实现

配置client:

java 复制代码
package com.spring.chat.bot.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CommonConfiguration {
    @Bean
    public ChatClient  ollamaChatClient(ChatClient.Builder client) {
        return client
                .defaultSystem("你是一个IKUN,名字叫鸽鸽damn,专门负责的聊天机器人,你可以回答用户的问题.")
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }
}

流式响应:

java 复制代码
package com.spring.chat.bot.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/chat")
@Slf4j
public class ChatController {
    @Autowired
    private final ChatClient chatClient;
    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    @RequestMapping(value = "/stream", produces = "text/html;charset=utf-8")
    public Flux<String> stream(String prompt){
        return chatClient.prompt()
                .user(prompt)
                .stream()
                .content();
    }
}

四、对话记忆

当前的简单对话,每一次发消息都是重新调接口,是一次新的对话,每次对话之间是没有什么联系的。

"⼤模型的对话记忆"这⼀概念,指的就是模型在与⽤⼾进⾏交互式对话过程中,能够追踪、理解并利⽤先前对话上下⽂的能⼒. 此机制使得⼤模型不仅能够响应即时的输⼊请求,还能基于之前的交流内容能够在对话中记住先前的对话内容,并根据这些信息进⾏后续的响应。

Spring AI ⻆⾊消息类型:

在对话系统(尤其是⼤语⾔模型应⽤)中,SystemMessage、UserMessage和AssistantMessage是三种核⼼⻆⾊消息类型,⽤于构建上下⽂感知的对话框架.

  • SystemMessage:系统消息,通常由系统设定或初始化时提供,⽤于设定对话的背景、⻆⾊、⾏为准则等.它不是⽤⼾或助⼿发出的,⽽是系统层⾯的指令或上下⽂信息
  • UserMessage:⽤⼾消息,即由⽤⼾输⼊的内容.在对话中,⽤⼾的问题或语句都属于此类.
  • AssistantMessage:助⼿消息,即由助⼿(通常是AI模型)⽣成并返回给⽤⼾的响应.

4.1 Chat Memory

⼤型语⾔模型 (LLM) 是⽆状态的,也就是它们不会保留有关以前交互的信息.当开发⼈员希望在多个交互中维护上下⽂或状态时,这可能是⼀个限制.为了解决这个问题,SpringAI提供了对话内存功能,定义了ChatMemory接⼝,允许开发⼈员在与LLM的多次交互中存储和检索信息.

java 复制代码
public interface ChatMemory {
	void add(String conversationId, List<Message> messages);
	
	List<Message> get(String conversationId, int lastN);
	
	void clear(String conversationId);
}
  1. add(String conversationId, List messages)
    说明:将单条或多条对话消息(如⽤⼾输⼊或AI回复)添加到指定会话的记忆库中
    参数:
  • conversationId: 区分不同会话的唯⼀标识
  • Messages:可包含UserMessage/AssistantMessage等类型
  1. List get(String conversationId, int lastN)
    说明:根据会话标识,获取历史消息
    参数:
  • conversationId: 会话唯⼀标识
  • lastN
    参数表⽰从指定会话中获取的最新消息数量
  1. clear(String conversationId)
    说明:清空指定会话的记忆存储
    参数:
  • conversationId: 会话唯⼀标识

Spring AI 会⾃动配置ChatMemory,开发⼈员可以直接在应⽤程序中使⽤这个bean,SpringAI提供了默认实现InMemoryChatMemory ,不需要开发⼈员显⽰的调⽤记录每⼀轮的对话历史.

默认情况下,ChatMemory使⽤内存来存储消息,开发⼈员可以根据⾃⼰的需求,去配置不同的存储库.

4.2 修改接⼝

java 复制代码
[请求]
GET/POST/chat/stream
[参数]
prompt(String): ⽤⼾输⼊的消息内容
chatId (String) : 会话标识ID, 由前端⽣成, 不重复, 新建会话时, 创建新的chatID
[响应]
Flux流式返回的机器⼈回复内容(text/html;charset=utf-8)

4.2 实现

在配置项中注入ChatMemory

java 复制代码
    @Bean
    public ChatMemory chatMemory(){
        return new InMemoryChatMemory();
    }

配置会话记忆:把ChatMemory注⼊到ChatClient

java 复制代码
    @Bean
    public ChatClient  ollamaChatClient(ChatClient.Builder client, ChatMemory chatMemory) {
        return client
                .defaultSystem("你是一个IKUN,名字叫鸽鸽damn,专门负责的聊天机器人,你可以回答用户的问题.")
                .defaultAdvisors(new SimpleLoggerAdvisor(), new MessageChatMemoryAdvisor(chatMemory))
                .build();
    }

向模型发送请求时,传递ChatId:

java 复制代码
    @RequestMapping(value = "/stream", produces = "text/html;charset=utf-8")
    public Flux<String> stream(String prompt, String chatId){
        return chatClient.prompt()
                .user(prompt)
                .advisors(spec->spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY,chatId))
                .stream()
                .content();
    }

http://127.0.0.1:8080/chat/stream?prompt=我是鸡哥&chatID=123456
http://127.0.0.1:8080/chat/stream?prompt=我是谁&chatID=123456

四、对话历史

ChatMemory提供了get接⼝,可以根据会话ID,获取会话记录.我们可以定义⼀个List,来存储会话ID的列表,然后再根据会话ID获取会话记录。

4.1 存储会话

定义ChatInfo 存储会话记录,存储会话的id和最后一次询问的内容的前15个字作为这个会话的标题展示。

java 复制代码
package com.spring.chat.bot.entity;
import lombok.Data;

@Data
public class ChatInfo {
    private String chatId;
    private String title;

    public ChatInfo(String chatId, String title) {
        this.chatId = chatId;
        this.title = title==null?"⽆标题 ": title.length()>15 ? title.substring(0,15):title;
    }
}

仿照ChatMemory接口来实现我们自己的接口,也提供添加(add)、获取(get)、删除(clear)会话的方法。

java 复制代码
package com.spring.chat.bot.repository;

import com.spring.chat.bot.entity.ChatInfo;
import java.util.List;

public interface ChatHistoryRepository {
    void add(String chatId, String title);
    List<ChatInfo> getChats();

    void clearByChatId(String chatId);
}

实现类:

java 复制代码
package com.spring.chat.bot.repository;

import com.spring.chat.bot.entity.ChatInfo;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class MemoryChatHistoryRepository implements ChatHistoryRepository {
    Map<String,String> chatHistoryMap = new LinkedHashMap<>();
    @Override
    public void add(String chatId, String title) {
        chatHistoryMap.put(chatId,title);
    }

    @Override
    public List<ChatInfo> getChats() {
        List<ChatInfo> chatInfos = chatHistoryMap.entrySet().stream()
                .map(entry -> new ChatInfo(entry.getKey(), entry.getValue()))
                .toList();
        return chatInfos;
    }

    @Override
    public void clearByChatId(String chatId) {
        chatHistoryMap.remove(chatId);
    }
}

存储会话: 在前面的简单对话实现中加:

java 复制代码
@RestController
@RequestMapping("/chat")
@Slf4j
public class ChatController {
    @Autowired
    private final ChatClient chatClient;
    @Autowired
    private ChatHistoryRepository memoryChatHistoryRepository;
    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    @RequestMapping(value = "/stream", produces = "text/html;charset=utf-8")
    public Flux<String> stream(String prompt, String chatId){
        memoryChatHistoryRepository.add(chatId,prompt);
        return chatClient.prompt()
                .user(prompt)
                .advisors(spec->spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY,chatId))
                .stream()
                .content();
    }
}

4.2 获取会话列表

获取会话列表:

java 复制代码
[请求]
GET/POST    
[参数]
⽆ /chat/getChatIds
[响应]
会话ID列表 List<ChatInfo>
java 复制代码
    @RequestMapping("/getChatIds")
    public List<ChatInfo> getChatIds(){
        return memoryChatHistoryRepository.getChats();
    }

4.3 获取会话记录

获取会话记录:根据会话ID,从ChatMemory中获取会话记录

将spring自带的Message进行转换,返回自己前端需要的东西。

java 复制代码
package com.spring.chat.bot.controller;

import lombok.Data;
import org.springframework.ai.chat.messages.Message;

@Data
public class MessageVO {
    private String role;
    private String content;
    public MessageVO(Message message) {
        switch (message.getMessageType()) {
            case USER -> {
                role = "user";
                break;
            }
            case ASSISTANT -> {
                role = "assistant";
                break;
            }
            case SYSTEM -> {
                role = "system";
                break;
            }
            case TOOL -> {
                role = "tool";
                break;
            }
        }
        this.content = message.getText();
    }
}

```java
[请求]
GET/POST    /chat/getChatHistory
[参数]
chatId (String) : 会话标识ID
[响应]
会话历史记录 List<MessageVO>
java 复制代码
    @Autowired
    private  ChatMemory chatMemory;
       
    @RequestMapping("/getChatHistory")
    List<MessageVO> getChatHistory(String chatId){
        List<Message> messages = chatMemory.get(chatId, 20);
        if (messages==null){
            return List.of();
        }
        return messages.stream().map(MessageVO::new).toList();
    }
复制代码
## 4.4 删除会话记录

```java
[请求]
GET/POST    /chat/deleteChat
[参数]
chatId (String) : 会话标识ID
[响应]
true/false
java 复制代码
    @RequestMapping("/deleteChat")
    public Boolean deleteChat(String chatId){
        try {
            chatMemory.clear(chatId);
            memoryChatHistoryRepository.clearByChatId(chatId);
        }catch (Exception e){
            return false;
        }
        return true;
    }
相关推荐
sthnyph2 小时前
Spring Framework 中文官方文档
java·后端·spring
带刺的坐椅2 小时前
Snack4 Json 流式解析与自动结构修复深度指南
java·llm·json·jsonpath
zb200641202 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
我命由我123452 小时前
Android 多进程开发 - FileDescriptor、Uri、AIDL 接口定义不能抛出异常
android·java·java-ee·kotlin·android studio·android-studio·android runtime
xyhuix2 小时前
Spring+Quartz实现定时任务的配置方法
java
分享牛2 小时前
Operaton入门到精通22-Operaton 2.0 升级指南:Spring Boot 4 核心变更详解
java·spring boot·后端
jinanmichael2 小时前
SpringBoot 如何调用 WebService 接口
java·spring boot·后端
深蓝轨迹2 小时前
吃透 Spring Boot dataSource与Starter
java·spring boot·笔记·后端
spring2997922 小时前
springboot和springframework版本依赖关系
java·spring boot·后端