1.概述
上篇文章利用SpringAI结合deepSeek开发了一个智能助手,但是没有实现会话记忆功能,如果缺少会话记忆,其实对话性能大打折扣,本文将实现会话记忆功能,实现真正的智能。
2.会话记忆功能实现
2.1 SpringAI相关概念说明
2.1.1 SpringAI Advisor
Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该拦截器,可以配置更复杂、可复用且易维护的AI组件。本文配置了ChatClient的Advisor 如下:
java
return ChatClient.builder(ollamaChatModel)
.defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小钢炮,请以小钢炮的身份和语气回答问题")
.defaultAdvisors(
//简单的日志记录器advisor,打印请求及大模型返回日志
new SimpleLoggerAdvisor(),
//对话记忆advisor,将每次对话内容进行存储
MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
2.1.2 ChatMemory
ChatMemory 抽象层支持实现多种记忆类型以满足不同场景需求。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。 ChatMemory 实现类可自主决定消息保留策略 --- 例如保留最近 N 条消息、按时间周期保留或基于 Token 总量限制保留。本文使用的是InMemoryChatMemoryRepository ,基于 ConcurrentHashMap 实现内存存储。ChatMemory支持存储在数据库中,支持PostgreSQL、MySQL / MariaDB、SQL Server、HSQLDB等类型数据库,大家根据需要自行选择。
2.2 代码实现
2.2.1 引入pom文件
java
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.eckey.lab</groupId>
<artifactId>ollama</artifactId>
<version>1.0.0</version>
<name>ollama-chat</name>
<properties>
<java.version>17</java.version>
<spring-ai.version>2.0.0-M2</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-starter-model-ollama</artifactId>
</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>
2.2.2 配置OllamaChatModel
java
@Bean
public ChatClient chatClient(OllamaChatModel ollamaChatModel, ChatMemory chatMemory) {
return ChatClient.builder(ollamaChatModel)
.defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小钢炮,请以小钢炮的身份和语气回答问题")
.defaultAdvisors(
new SimpleLoggerAdvisor(),
MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
@Bean
public ChatMemoryRepository chatMemoryRepository() {
return new InMemoryChatMemoryRepository();
}
@Bean
public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository)
.maxMessages(100).build();
}
2.2.3 配置文件编写
java
spring.application.name=ollama-chat
server.port=8080
//ollama服务器地址
spring.ai.ollama.base-url=http://127.0.0.1:11434
//模型版本号
spring.ai.ollama.chat.model=deepseek-r1:1.5b
//日志打印级别
logging.level.org.springframework.ai.chat.client.advisor=debug
2.2.4 编写controller
java
@RestController()
@CrossOrigin("*")
@RequestMapping("/ai/chat")
public class OllamaController {
@Resource
private ChatClient chatClient;
@Resource
private ChatHistoryRepository chatHistoryRepository;
@Resource
private ChatMemory chatMemory;
/**
* 同步响应请求,获取到大模型所有返回结果后,一次性返回前端
*/
@GetMapping("/call")
public String generate(@RequestParam("message") String message) {
return chatClient.prompt(message).call().content();
}
/**
* 流式响应
*/
@RequestMapping(value = "/stream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(String prompt, String chatId) {
chatHistoryRepository.save(ChatTypeEnums.CHAT.getType(), chatId);
return chatClient.prompt(prompt)
.advisors(a -> a.param(CONVERSATION_ID, chatId))
.stream().content();
}
/**
* 根据chatId本次对话所有信息,以固定对象MessageVO返回
*/
@GetMapping(value = "/history/{type}/{chatId}")
public List<MessageVO> getChatHistory(@PathVariable("chatId") String chatId) {
List<Message> messages = chatMemory.get(chatId);
return messages.stream().map(MessageVO::new).toList();
}
}
实体MessageVO对象信息如下:
java
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:
this.role = "user";
break;
case ASSISTANT:
this.role = "assistant";
break;
case SYSTEM:
this.role = "system";
break;
case TOOL:
this.role = "function";
break;
default:
this.role = "unknown";
}
this.content = message.getText();
}
}
2.2.5 定义ChatHistoryRepository接口并实现
java
public interface ChatHistoryRepository {
void save(String type, String chatId);
List<String> getChatIds(String type);
void delete(String type, String chatId);
}
@Slf4j
@Component
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {
private final Map<String, List<String>> chatHistory = new ConcurrentHashMap<>();
@Override
public void save(String type, String chatId) {
List<String> chatIds = chatHistory.computeIfAbsent(type, k -> new ArrayList<>());
if (chatIds.contains(chatId)) {
return;
}
chatIds.add(chatId);
log.info("chatIds :{}", JSON.toJSONString(chatIds));
//chatHistory.put(type, chatIds);
log.info("chatHistory :{}", JSON.toJSONString(chatHistory));
}
@Override
public List<String> getChatIds(String type) {
return chatHistory.getOrDefault(type, List.of());
}
@Override
public void delete(String type, String chatId) {
List<String> strings = chatHistory.get(type);
if (strings != null && strings.contains(chatId)) {
strings.remove(chatId);
}
log.info("chatHistory :{}", JSON.toJSONString(chatHistory));
}
}
2.2.5 测试验证
通过调用API进行测试,得到结果如下:


结合前端界面呈现如下,及时刷新页面也能重新获取对话信息进行呈现:

3.小结
1.本文结合SpringAI实现了智能助手会话记忆功能,帮助对话功能更完善;
2.SpringAI功能也在不断完善,接下来将结合Rag实现对文档的向量化存储与搜索。