文章目录

基于Spring AI实现多轮对话系统架构设计

前言

一、多轮对话系统核心架构

1.1 架构概览

1.2 Spring AI核心优势

二、ChatClient与多轮对话设计

2.1 ChatClient的特性与角色

2.2 实现多轮对话方法

三、Advisors拦截器机制

3.1 Advisors概念与工作原理

3.2 对话记忆Advisor详解

四、对话记忆实现方案

4.1 ChatMemory接口

4.2 内存存储实现

4.3 文件持久化存储

4.4 数据库持久化实现

五、自定义增强Advisor实现

5.1 日志记录Advisor

5.2 内容安全Advisor

5.3 推理增强Advisor

六、结构化输出与业务应用

6.1 结构化报告生成

6.2 完整对话系统整合

七、性能优化与最佳实践

7.1 对话记忆性能优化

7.2 多轮对话系统设计要点

总结


基于Spring AI实现多轮对话系统架构设计

前言

随着大型语言模型(LLM)的迅速发展,构建具有持久记忆和上下文感知能力的对话系统成为AI应用开发的关键。Spring AI框架提供了简洁高效的组件,帮助开发者快速实现这类功能。本文将深入探讨如何基于Spring AI打造多轮对话系统,包括架构设计、核心组件和实现方法。

一、多轮对话系统核心架构

1.1 架构概览

多轮对话系统的核心在于维护对话上下文,使AI能够理解历史交互内容,实现连贯对话。基于Spring AI的多轮对话系统架构可概括为:

核心组件包括:

  • ChatClient:对话客户端,处理与LLM的交互

  • Advisor:拦截器链,处理请求前后的增强逻辑

  • ChatMemory:对话记忆,存储历史消息

  • PromptTemplate:提示词模板,构建结构化提示

1.2 Spring AI核心优势

基于Spring AI框架开发多轮对话系统具有以下优势:

  1. 链式调用API(Fluent API):简洁直观的调用方式

  2. 动态参数绑定:支持模板变量,提高灵活性

  3. 灵活的响应格式:支持实体映射和流式输出

  4. 可插拔拦截器:通过Advisors机制轻松扩展功能

  5. 内置记忆管理:开箱即用的对话记忆组件

二、ChatClient与多轮对话设计

2.1 ChatClient的特性与角色

@Component
@Slf4j
public class LoveApp {
​
    private static final String SYSTEM_PROMPT = "**恋爱大师·情感导航员**  \n" + 
        "10年情感咨询经验,擅长亲密关系理论与沟通技巧...";
        
    private final ChatClient chatClient;
​
    public LoveApp(ChatModel dashscopeChatModel) {
        ChatMemory chatMemory = new InMemoryChatMemory();
        chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultSystem(SYSTEM_PROMPT)
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
                .build();
    }
}

ChatClient是Spring AI提供的核心对话客户端组件,负责维护与大语言模型的通信。它支持:

  • 链式调用:简化API调用流程

  • 动态参数注入:运行时传递控制参数

  • 多格式响应处理:文本、JSON、流式回复等

  • 拦截器机制:Advisors模式的扩展点

创建ChatClient示例:


2.2 实现多轮对话方法

通过ChatClient实现多轮对话的关键是正确配置对话记忆,并在每次交互中保持会话ID的一致性:

public String doChat(String message, String chatId) {
    ChatResponse response = chatClient.prompt()
            .user(message)
            .advisors(spec -> spec
                    .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
            .call()
            .chatResponse();
    String content = response.getResult().getOutput().getText();
    log.info("content: {}", content);
    return content;
}

这段代码的关键点在于:

  • 用户消息注入:通过.user(message)添加当前用户问题

  • 会话标识:通过CHAT_MEMORY_CONVERSATION_ID_KEY参数维护会话一致性

  • 上下文长度:通过CHAT_MEMORY_RETRIEVE_SIZE_KEY控制历史消息检索数量

  • 响应处理:获取并处理模型回复

三、Advisors拦截器机制

3.1 Advisors概念与工作原理

Advisors是Spring AI中基于责任链模式实现的拦截器机制,可以在调用大模型前后执行增强逻辑。其核心特性包括:

  • 责任链模式:多个拦截器按顺序执行

  • 顺序控制:通过getOrder()方法控制执行顺序

  • 前置/后置处理:可在请求发送前和响应接收后进行处理

  • 可扩展性:通过实现接口自定义拦截器

常用内置Advisor:

  • MessageChatMemoryAdvisor:维护对话历史

  • QuestionAnswerAdvisor:知识库检索增强

3.2 对话记忆Advisor详解

负责维护对话上下文的拦截器主要有两种:

  • MessageChatMemoryAdvisor

    • 保留消息的角色(用户/助手/系统)

    • 将历史消息作为独立实体注入

    • 维护完整对话结构

  • PromptChatMemoryAdvisor

    • 将历史对话合并为文本

    • 作为系统提示的一部分注入

    • 可能丢失消息边界信息

在多轮对话系统中,通常选择MessageChatMemoryAdvisor以保留更多上下文信息:

ChatMemory chatMemory = new InMemoryChatMemory();
chatClient = ChatClient.builder(dashscopeChatModel)
        .defaultSystem(SYSTEM_PROMPT)
        .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
        .build();

四、对话记忆实现方案

4.1 ChatMemory接口

ChatMemory是Spring AI提供的抽象接口,定义了对话记忆的核心操作:

public interface ChatMemory {
    void add(String conversationId, List<Message> messages);
    List<Message> get(String conversationId, int lastN);
    void clear(String conversationId);
}

这三个方法构成了对话记忆的基本功能:

  • 添加:保存新的对话消息

  • 获取:检索特定会话的历史消息

  • 清空:删除特定会话的所有记录

4.2 内存存储实现

最简单的实现是使用InMemoryChatMemory,适用于开发测试或短期会话:

ChatMemory chatMemory = new InMemoryChatMemory();

内存实现的优缺点:

  • 优点:速度快,配置简单

  • 缺点:服务重启数据丢失,不适合生产环境

4.3 文件持久化存储

对于需要跨服务重启保存对话的场景,可以实现基于文件的持久化:

@Slf4j
public class FileBasedChatMemory implements ChatMemory {
​
    private final String baseDir;
    private static final Kryo kryo;
​
    static {
        kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
    }
​
    public FileBasedChatMemory(String dir) {
        this.baseDir = dir;
        new File(dir).mkdirs();
    }
​
    @Override
    public void add(String conversationId, List<Message> messages) {
        var existingMessages = getOrCreateConversation(conversationId);
        existingMessages.addAll(messages);
        saveConversation(conversationId, existingMessages);
    }
​
    @Override
    public List<Message> get(String conversationId, int lastN) {
        var allMessages = getOrCreateConversation(conversationId);
        return allMessages.stream()
                .skip(Math.max(0, allMessages.size() - lastN))
                .toList();
    }
​
    // 其他辅助方法...
}

文件存储的优缺点:

  • 优点:简单易实现,无需额外服务

  • 缺点:并发性能有限,不适合高并发场景

4.4 数据库持久化实现

对于生产环境,尤其是多用户系统,数据库存储是最佳选择。以MySQL为例:

@Component
@Slf4j
public class MySQLChatMemory implements ChatMemory {
​
    private final JdbcTemplate jdbcTemplate;
    private final JSONConfig jsonConfig;
​
    public MySQLChatMemory(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.jsonConfig = new JSONConfig().setIgnoreNullValue(true);
        log.info("初始化MySQL对话记忆");
    }
​
    @Override
    @Transactional
    public void add(String conversationId, List<Message> messages) {
        // 获取当前最大序号
        Integer maxOrder = getMaxOrder(conversationId).orElse(0);
        int nextOrder = maxOrder + 1;
​
        // 批量插入消息
        String insertSql = "INSERT INTO chatmemory (...) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
        jdbcTemplate.batchUpdate(insertSql, messages, messages.size(), (ps, message) -> {
            // 设置参数...
        });
    }
​
    // 其他实现方法...
}

对于更复杂的场景,可以集成ORM框架如MyBatis-Plus:

@Component
@Slf4j
public class MybatisPlusChatMemory implements ChatMemory {
​
    private final ChatMemoryService chatMemoryService;
​
    public MybatisPlusChatMemory(ChatMemoryService chatMemoryService) {
        this.chatMemoryService = chatMemoryService;
        log.info("初始化Mybatis-Plus对话记忆");
    }
​
    @Override
    public void add(String conversationId, List<Message> messages) {
        chatMemoryService.addMessages(conversationId, messages);
    }
​
    @Override
    public List<Message> get(String conversationId, int lastN) {
        return chatMemoryService.getMessages(conversationId, lastN);
    }
​
    @Override
    public void clear(String conversationId) {
        chatMemoryService.clearMessages(conversationId);
    }
}

五、自定义增强Advisor实现

5.1 日志记录Advisor

记录对话内容的自定义Advisor实现:

@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
​
    @Override
    public String getName() {
        return getClass().getSimpleName();
    }
​
    @Override
    public int getOrder() {
        return 0; // 执行顺序
    }
​
    // 请求前打印用户输入
    private AdvisedRequest before(AdvisedRequest request) {
        log.info("AI Request: {}", request.userText());
        return request;
    }
​
    // 响应后打印 AI 输出
    private void observeAfter(AdvisedResponse response) {
        log.info("AI Response: {}", 
            response.response().getResult().getOutput().getText());
    }
​
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest req, CallAroundAdvisorChain chain) {
        req = before(req);
        AdvisedResponse res = chain.nextAroundCall(req);
        observeAfter(res);
        return res;
    }
​
    // 流式调用处理
    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest req, 
                                             StreamAroundAdvisorChain chain) {
        req = before(req);
        Flux<AdvisedResponse> res = chain.nextAroundStream(req);
        return new MessageAggregator().aggregateAdvisedResponse(res, this::observeAfter);
    }
}

5.2 内容安全Advisor

检测违禁词的自定义Advisor实现:


5.3 推理增强Advisor

@Slf4j
public class ProhibitedWordAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
​
    private static final String DEFAULT_PROHIBITED_WORDS_FILE = "prohibited-words.txt";
    private final List<String> prohibitedWords;
​
    public ProhibitedWordAdvisor() {
        this.prohibitedWords = loadProhibitedWordsFromFile(DEFAULT_PROHIBITED_WORDS_FILE);
        log.info("初始化违禁词Advisor,违禁词数量: {}", prohibitedWords.size());
    }
​
    private AdvisedRequest checkRequest(AdvisedRequest request) {
        String userText = request.userText();
        if (containsProhibitedWord(userText)) {
            log.warn("检测到违禁词在用户输入中: {}", userText);
            throw new ProhibitedWordException("用户输入包含违禁词");
        }
        return request;
    }
​
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, 
                                     CallAroundAdvisorChain chain) {
        return chain.nextAroundCall(checkRequest(advisedRequest));
    }
​
    // 其他辅助方法...
}

提高模型推理能力的自定义Advisor:

public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
​
    private AdvisedRequest before(AdvisedRequest advisedRequest) {
        Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
        advisedUserParams.put("re2_input_query", advisedRequest.userText());
​
        return AdvisedRequest.from(advisedRequest)
                .userText("""
                        {re2_input_query}
                        Read the question again: {re2_input_query}
                        """)
                .userParams(advisedUserParams)
                .build();
    }
​
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, 
                                     CallAroundAdvisorChain chain) {
        return chain.nextAroundCall(this.before(advisedRequest));
    }
​
    // 其他方法实现...
}

六、结构化输出与业务应用

6.1 结构化报告生成

Spring AI支持将模型输出直接映射为Java对象,实现结构化数据处理:

public LoveReport doChatWithReport(String message, String chatId) {
    LoveReport loveReport = chatClient.prompt()
        .system(SYSTEM_PROMPT + "每次对话后都要生成恋爱结果,标题为{用户名}的恋爱报告,内容为建议列表")
        .user(message)
        .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
        .call().entity(LoveReport.class);
    log.info("loveReport: {}", loveReport);
    return loveReport;
}

这种方式可以直接将模型生成的内容解析为Java对象,便于后续业务处理。

6.2 完整对话系统整合

将所有组件整合的多轮对话系统示例:

@Component
@Slf4j
public class EnhancedLoveApp {
​
    private static final String SYSTEM_PROMPT = "**恋爱大师·情感导航员**...";
    private final ChatClient chatClient;
​
    public EnhancedLoveApp(ChatModel dashscopeChatModel, 
                          ChatMemoryService chatMemoryService) {
        // 使用MyBatis-Plus持久化对话记忆
        ChatMemory chatMemory = new MybatisPlusChatMemory(chatMemoryService);
        
        chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultSystem(SYSTEM_PROMPT)
                .defaultAdvisors(
                        // 对话记忆能力
                        new MessageChatMemoryAdvisor(chatMemory),
                        // 记录日志
                        new MyLoggerAdvisor(),
                        // 违禁词检测
                        new ProhibitedWordAdvisor(),
                        // 复读强化阅读能力
                        new ReReadingAdvisor()
                )
                .build();
    }
​
    public String doChat(String message, String chatId) {
        return chatClient.prompt()
                .user(message)
                .advisors(spec -> spec
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .call()
                .chatResponse()
                .getResult().getOutput().getText();
    }
    
    // 其他业务方法...
}

七、性能优化与最佳实践

7.1 对话记忆性能优化

在高并发场景下,对话记忆的存取可能成为性能瓶颈,几个优化策略:

  1. 分级缓存

    • 热点会话使用内存缓存

    • 冷数据使用数据库存储

    • 采用 Redis 等分布式缓存

  2. 异步写入

    • 响应用户后再异步保存对话历史

    • 使用消息队列减轻数据库压力

  3. 记忆裁剪

    • 定期清理过期会话数据

    • 只保留最近N轮对话

    • 实现智能摘要压缩历史内容

7.2 多轮对话系统设计要点

构建生产级多轮对话系统的关键考量:

  1. 会话标识与隔离

    • 使用稳定唯一的会话标识

    • 避免跨会话数据泄露

    • 支持多设备同步

  2. 上下文窗口管理

    • 根据模型能力调整窗口大小

    • 实现滑动窗口减少token成本

    • 处理长对话的上下文压缩

  3. 异常处理

    • 模型超时与重试机制

    • 会话恢复能力

    • 敏感内容过滤

  4. 监控与评估

    • 对话质量监控指标

    • 性能监控

    • A/B测试框架

总结

基于Spring AI框架构建多轮对话系统,关键在于正确设计对话记忆机制和合理组织Advisor拦截器链。通过灵活选择不同的持久化方案,可以满足从开发测试到大规模生产环境的不同需求。


最后我叫 lenyan~ 也会持续学习更进 AI知识。让我们共进 AI 大时代。

 作者:lenyan GitHub:lenyanjgk (lenyanjgk) · GitHub CSDN:lenyan~-CSDN博客 

觉得有用的话可以点点赞 (/ω\),支持一下。

如果愿意的话关注一下。会对你有更多的帮助。

每周都会不定时更新哦 >人< 。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐