SpringAI5-智能聊天机器⼈
随着⼈⼯智能技术的快速发展和⼤模型开源趋势的兴起, 智能聊天机器⼈在客服、知识问答、⽣活助⼿等领域得到了⼴泛应⽤. Deepseek作为优秀的开源⼤模型框架, 为开发者提供了强⼤的基础能⼒. 当前市场上已有多个基于Deepseek的AI应⽤案例, 我们接下来模仿这些应⽤来实现⼀个智能聊天机器⼈, 提升⽤⼾交互体验.产品⽬标:• 提供流畅, ⾃然的对话体验• ⽀持多轮对话及上下⽂理解• 能够回答常⻅
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
1. 项⽬介绍
随着⼈⼯智能技术的快速发展和⼤模型开源趋势的兴起, 智能聊天机器⼈在客服、知识问答、⽣活助⼿等领域得到了⼴泛应⽤. Deepseek作为优秀的开源⼤模型框架, 为开发者提供了强⼤的基础能⼒. 当前市场上已有多个基于Deepseek的AI应⽤案例, 我们接下来模仿这些应⽤来实现⼀个智能聊天机器⼈, 提升⽤⼾交互体验.
产品⽬标:
• 提供流畅, ⾃然的对话体验
• ⽀持多轮对话及上下⽂理解
• 能够回答常⻅问题
• 记录和管理⽤⼾历史对话
核⼼功能
- 对话
• ⽀持⽤⼾与机器⼈进⾏⽂本对话
• 实时响应⽤⼾输⼊, 输出⾃然语⾔回复 - 多轮对话
• 能够理解和处理多轮对话, 保持上下⽂连续性
• ⽀持基于上下⽂的智能应答 - 历史记录
• ⾃动保存⽤⼾与机器⼈的对话历史
• ⽀持⽤⼾查看历史对话内容
界⾯设计
前端代码随资料提供


这个就是多轮会话
2. 搭建环境
创建项⽬: spring-chat-bot
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- deepseek-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
spring:
application:
name: spring-chat-bot
ai:
openai:
api-key: sk-af290165703c47a78a451bc62d28688e
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.7
logging:
pattern:
console: '%d{HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
file: '%d{HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
level:
org.springframework.ai.chat.client.advisor: debug
然后是前端的页面
前端一行都不写—》可以直接复制
3. 简单对话

@Configuration
public class CommonConfig {
@Bean
public ChatClient deepseekChatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
.defaultSystem("你是lyx,是由ck研发的一款智能AI助手,你很擅长医学研究,请以友好的态度来回答问题")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
}
@RestController
@RequestMapping("/chat")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient deepseekChatClient) {
this.chatClient = deepseekChatClient;
}
@RequestMapping(value = "/stream",produces = "text/html;chatset=utf-8")
public Flux<String> stream(String prompt){
return this.chatClient.prompt()
.user(prompt)
.stream()
.content();
}
}
构造函数中,就是获取的deepseekChatClient的bean

如果是本地部署的模型—》有思考过程的

我们发现对话是没有记忆的
4. 对话记忆
原因这两次请求之间没有关系
"⼤模型的对话记忆"这⼀概念, 指的是模型在与⽤⼾进⾏交互式对话过程中, 能够追踪、理解并利⽤先前对话上下⽂的能⼒. 此机制使得⼤模型不仅能够响应即时的输⼊请求, 还能基于之前的交流内容能够在对话中记住先前的对话内容, 并根据这些信息进⾏后续的响应
⼤模型本⾝是不具备记忆能⼒的, 要想让⼤模型记住之前的聊天内容, 需要把之前的聊天内容与新的提⽰词⼀起发给⼤模型.以 阿⾥百炼平台-通义千问Plus 进⾏演⽰

点击右边的模型调试




发现返回值就在assistant中
4.1 Spring AI ⻆⾊消息类型
在对话系统 (尤其是⼤语⾔模型应⽤) 中, SystemMessage、UserMessage 和 AssistantMessage 是三种核⼼⻆⾊消息类型, ⽤于构建上下⽂感知的对话框架.
AssistantMessage 是助手消息,一般是模型返回给用户的消息
SystemMessage: 系统消息, 通常由系统设定或初始化时提供, ⽤于设定对话的背景、⻆⾊、⾏为准则等. 它不是⽤⼾或助⼿发出的, ⽽是系统层⾯的指令或上下⽂信息. 例如, 在对话开始前, 系统消息可以设定助⼿的⻆⾊:"你是⼀个乐于助⼈的助⼿, ⽤中⽂回答问题. "比如defaultSystem
• UserMessage: ⽤⼾消息, 即由⽤⼾输⼊的内容. 在对话中, ⽤⼾的问题或语句都属于此类.
• AssistantMessage: 助⼿消息, 即由助⼿ (通常是AI模型) ⽣成并返回给⽤⼾的响应.
例如, ⼀个对话如下:
SystemMessage: "你是⼀个翻译助⼿, 将⽤⼾的中⽂翻译成英⽂. "
UserMessage: "你好, 今天天⽓真好. "
AssistantMessage: “Hello, the weather is really nice today.”
当模型⽣成回复时, 它会看到整个对话历史 (包括系统消息、之前的⽤⼾消息和助⼿消息) , 从⽽⽣成连贯且符合上下⽂的回复.
在技术实现上, 不同框架或库可能会采⽤不同的命名, 但核⼼思想⼀致. 例如, 在OpenAI的API中, 消息以⻆⾊ (role) 字段区分, 包括"system"、“user”、“assistant”.
在多轮对话中, AssistantMessage 与 UserMessage 交替排列形成时序链, 使模型能通过注意⼒机制理解当前问题的前置语境 (短期记忆)

发现正常的模型都会把原来模型输出的内容放在assistant中
删除UserMessage 和 AssistantMessage , 发现⼤模型不具备记忆能⼒
4.2 Chat Memory
⼤型语⾔模型 (LLM) 是⽆状态的, 也就是它们不会保留有关以前交互的信息. 当开发⼈员希望在多个交互中维护上下⽂或状态时, 这可能是⼀个限制. 为了解决这个问题, Spring AI 提供了对话内存功能, 定义了ChatMemory接⼝, 允许开发⼈员在与 LLM 的多次交互中存储和检索信息.

这个就是Chat Memory
add的意思是,第一个参数是每一个会话的id,就是向一个会话添加一个消息
get意思就是向以前的会话id,获取消息记录
clear就是清楚会话记录
add(String conversationId, List<Message> messages)
说明: 将单条或多条对话消息 (如⽤⼾输⼊或AI回复) 添加到指定会话的记忆库中
参数:
◦ conversationId: 区分不同会话的唯⼀标识
◦ Messages: 可包含 UserMessage/AssistantMessage 等类型
. List<Message> get(String conversationId, int lastN)
说明: 根据会话标识, 获取历史消息
参数:
◦ conversationId: 会话唯⼀标识
◦ lastN 参数表⽰从指定会话中获取的最新消息数量
clear(String conversationId)
说明: 清空指定会话的记忆存储
参数:
◦ conversationId: 会话唯⼀标识
Spring AI 会⾃动配置ChatMemory , 开发⼈员可以直接在应⽤程序中使⽤这个 bean, Spring AI 提供了默认实现 InMemoryChatMemory , 不需要开发⼈员显⽰的调⽤记录每⼀轮的对话历史.
默认情况下, ChatMemory 使⽤内存来存储消息, —》服务重启的话就消失了,开发⼈员可以根据⾃⼰的需求, 去配置不同的存储库,⽐如Cassandra、JDBC 或 Neo4j.
关于Chat Memory, Spring AI 和Spring AI alibaba都有实现, 感兴趣可以参考: 聊天记忆, 对话记忆


会话的id我们交给前端生成了
4.3 修改接口

@Configuration
public class CommonConfig {
@Bean
public ChatMemory chatMemory(){
return new InMemoryChatMemory();//创建一个ChatMemory,然后把它添加给defaultAdvisors
}
@Bean
public ChatClient deepseekChatClient(ChatClient.Builder chatClientBuilder,ChatMemory chatMemory) {
return chatClientBuilder
.defaultSystem("你是lyx,是由ck研发的一款智能AI助手,你很擅长医学研究,请以友好的态度来回答问题")
.defaultAdvisors(new SimpleLoggerAdvisor(),new MessageChatMemoryAdvisor(chatMemory))
.build();
}
}
@RequestMapping(value = "/stream",produces = "text/html;chatset=utf-8")
public Flux<String> stream(String prompt,String chatId){//chatId是会话id,由前端生成
return this.chatClient.prompt()
.user(prompt)
.advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.stream()
.content();
}

这样就成功了
5. 对话历史

可以查询历史消息,历史会话列表,根据会话ID,返回历史消息,然后可以删除历史会话
内存ChatMemory来存储,存储会话ID,会话标题
每个会话有很多个消息
@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;
}
}

历史会话列表,就是返回List了
内存来存储会话记录,删除会话记录,查询会话记录
public interface ChatHistoryRepository {
// 内存来存储会话记录,删除会话记录,查询会话记录
void save(String chatId,String title);
void clearByChatId(String chatId);//清楚历史会话
List<ChatInfo> getChats();//返回历史会话
}
然后是实现类
还有一点就是我们的stream接口,不管是哪个会话,都是调用的这个接口,所以还要判断这次会话的id是不是最新的,还是以前已经存储过的
@Repository
public class MemoryChatHistoryRepository implements ChatHistoryRepository{
private Map<String,String> chatInfos=new LinkedHashMap<>();
// 保存会话记录,如果是新会话,则新增,如果会话已经存在,则更新--->Map的put操作,但是Map是无序的----》LinkedHashMap
@Override
public void save(String chatId, String title) {
chatInfos.put(chatId,title);
}
@Override
public void clearByChatId(String chatId) {
chatInfos.remove(chatId);
}
@Override
public List<ChatInfo> getChats() {
return chatInfos.entrySet().stream()
.map(entry->new ChatInfo(entry.getKey(),entry.getValue()))
.toList();
}
}
5.1 存储会话
@Autowired
private ChatHistoryRepository memoryChatHistoryRepository;
@RequestMapping(value = "/stream",produces = "text/html;chatset=utf-8")
public Flux<String> stream(String prompt,String chatId){//chatId是会话id,由前端生成
log.info("chatId:{},prompt:{}",chatId,prompt);
memoryChatHistoryRepository.save(chatId,prompt);
return this.chatClient.prompt()
.user(prompt)
.advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.stream()
.content();
}
5.2 获取会话列表

@RequestMapping("/getChatIds")
public List<ChatInfo> getChatIds(){
return memoryChatHistoryRepository.getChats();
}
5.3 获取会话记录
就是点击会话列表的时候,要返回会话记录给我
每个会话的详细记录都是在ChatMemory中存储的
直接调用ChatMemory的接口就可以了

我们发现ChatMemory就有一个get接口

Message有MessageType这个方法
就有这几个类型
用户消息,AI生成消息,就这两种
Message继承了Content

然后就是对于不同的消息,前端渲染的格式不同
@Data
public class MessageVO {
private String role;//消息类型
private String content;//消息内容
public MessageVO(Message message) {
switch (message.getMessageType()){
case USER -> {this.role = "user";break;}
case ASSISTANT -> {this.role = "assistant";break;}
case SYSTEM -> {this.role = "system";break;}
case TOOL -> {this.role = "tool";break;}
}
this.content = message.getText();
}
}

@Autowired
private ChatMemory chatMemory;
//获取会话记录
@RequestMapping("/getChatHistory")
public List<MessageVO> getChatHistory(String chatId){
log.info("获取会话记录,chatId:{}",chatId);
List<Message> messages = chatMemory.get(chatId,20);//获取20条
return messages.stream().map(MessageVO::new).toList();
}
5.4 删除会话记录
会话及其消息都要删除掉
就是要从列表记录,和Chatmemory中同时删除
@RequestMapping("/deleteChat")
public Boolean deleteChat(String chatId){
log.info("删除会话,chatId:{}",chatId);
try {
memoryChatHistoryRepository.clearByChatId(chatId);
chatMemory.clear(chatId);
}catch (Exception e){
log.error("删除会话失败,chat:{}",chatId);
return false;
}
return true;
}
因为我们的数据都是存储在内存中的,所以服务重启,会话消息就会消失掉
先手动弄四个会话
刷新

会话记录已经存储下来了

会话记录都在

可以删除
扩展:这个对话,不区分用户,所有人的会话都在这个地方
总结
更多推荐
所有评论(0)