langchain4j+mysql+历史记录

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);
}
相关推荐
Jim6001 天前
【吃透 MySQL InnoDB连载】第 1 章・解密线上数据库高频故障
mysql
GreatSQL1 天前
gt-checksum v4.0.0 新功能解读系列文章(4):SSL 加密连接——数据校验传输安全再升级
mysql
云技纵横4 天前
唯一索引 INSERT 死锁实战:5 秒复现交叉插入的 S 锁循环等待
sql·mysql
沉默王二4 天前
面试官:RAG 不用向量数据库,用 MySQL 硬扛?我:100 万向量不是很轻松?
mysql·面试·ai编程
小猿姐5 天前
MySQL Top 10 热点问题 AI 运维实战:从内核诊断到云原生运维
mysql·云原生·aiops
云技纵横5 天前
Gap Lock 死锁实战:5 秒在本地复现 MySQL 间隙锁死锁
后端·mysql
无响应de神5 天前
三、用户与权限管理
数据库·mysql
摇滚侠6 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql