langchain4j+mysql+历史记录
一、前言
在构建 AI 聊天应用的过程中,很多开发者往往会把"记忆"和"聊天记录"混为一谈。但实际上,这两者在系统中的角色完全不同。
在 LangChain4j 的体系中,"记忆(Memory)"更多是服务于大模型本身,用于拼接上下文、提升对话连贯性,通常依赖于 Redis 等高性能存储实现短期缓存。而"聊天记录"则是面向用户的功能,需要支持历史查询、分页展示,甚至多会话管理,这类数据更适合持久化存储在 MySQL 中。
因此,在一个完整的 AI 聊天系统中,我们往往需要同时设计两套存储体系:
一套用于服务模型的"短期记忆",另一套用于服务用户的"长期历史记录"。
本文将接上期来实现长期历史记录。
二、环境与依赖
环境上期有说到,与上次相似但需要mysql。
依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/langchain4j_db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
ChatHistory
@Data
public class ChatHistory {
@TableId(type = IdType.AUTO)
private Long id;
private String sessionId;
private String userId;
private String role;
private String content;
private LocalDateTime createTime;
}
ChatHistoryService
@Service
public class ChatHistoryService {
@Resource
private ChatHistoryMapper mapper;
public void save(String sessionId, String userId, String role, String content) {
ChatHistory chat = new ChatHistory();
chat.setSessionId(sessionId);
chat.setUserId(userId);
chat.setRole(role);
chat.setContent(content);
mapper.insert(chat);
}
}
ChatHistoryMapper
public interface ChatHistoryMapper extends BaseMapper<ChatHistory> {
}
AiController
@Resource
private AiCodeHelperService aiCodeHelperService;
@Resource
private ChatHistoryService chatHistoryService;
@GetMapping(value = "/chat", produces = "text/event-stream")
public Flux<ServerSentEvent<String>> chat(int memoryId, String message) {
if (message == null || message.isBlank()) {
return Flux.empty();
}
String sessionId = String.valueOf(memoryId);
String userId = "default";
// 存用户消息
chatHistoryService.save(sessionId, userId, "user", message);
StringBuilder fullAnswer = new StringBuilder();
return aiCodeHelperService.chatStream(memoryId, message)
.doOnNext(chunk -> fullAnswer.append(chunk))
.bufferUntil(chunk ->
chunk.endsWith("。") ||
chunk.endsWith("!") ||
chunk.endsWith("?") ||
chunk.endsWith("\n")
)
.map(list -> String.join("", list))
.map(sentence -> ServerSentEvent.<String>builder().data(sentence).build())
// 存AI回复
.doOnComplete(() -> {
chatHistoryService.save(
sessionId,
userId,
"assistant",
fullAnswer.toString()
);
});
}
Langchain4jApplication
@MapperScan("cn.langchain4j.ai.mapper")
创建数据库 langchain4j_db
CREATE TABLE chat_history (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64),
user_id VARCHAR(64),
role VARCHAR(20),
content TEXT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
到这里就完成了对聊天信息的存储,接下来做查询。
MybatisPlusConfig
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
ChatHistoryService
public Page<ChatHistory> page(String sessionId, int page, int size) {
Page<ChatHistory> pageParam = new Page<>(page, size);
LambdaQueryWrapper<ChatHistory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ChatHistory::getSessionId, sessionId)
.orderByDesc(ChatHistory::getCreateTime);
return mapper.selectPage(pageParam, wrapper);
}
AiController
@GetMapping("/history")
public Page<ChatHistory> history(
String sessionId,
int page,
int size) {
return chatHistoryService.page(sessionId, page, size);
}