
基于Spring AI实现多轮对话系统架构设计
基于SpringAI框架的多轮对话系统设计,涵盖ChatClient、Advisor、ChatMemory等核心组件,探讨链式调用、动态参数绑定、拦截器机制及多种记忆存储方案,助力构建高效、可扩展的对话系统。
文章目录
基于Spring AI实现多轮对话系统架构设计
前言
随着大型语言模型(LLM)的迅速发展,构建具有持久记忆和上下文感知能力的对话系统成为AI应用开发的关键。Spring AI框架提供了简洁高效的组件,帮助开发者快速实现这类功能。本文将深入探讨如何基于Spring AI打造多轮对话系统,包括架构设计、核心组件和实现方法。
一、多轮对话系统核心架构
1.1 架构概览
多轮对话系统的核心在于维护对话上下文,使AI能够理解历史交互内容,实现连贯对话。基于Spring AI的多轮对话系统架构可概括为:
核心组件包括:
-
ChatClient:对话客户端,处理与LLM的交互
-
Advisor:拦截器链,处理请求前后的增强逻辑
-
ChatMemory:对话记忆,存储历史消息
-
PromptTemplate:提示词模板,构建结构化提示
1.2 Spring AI核心优势
基于Spring AI框架开发多轮对话系统具有以下优势:
-
链式调用API(Fluent API):简洁直观的调用方式
-
动态参数绑定:支持模板变量,提高灵活性
-
灵活的响应格式:支持实体映射和流式输出
-
可插拔拦截器:通过Advisors机制轻松扩展功能
-
内置记忆管理:开箱即用的对话记忆组件
二、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 对话记忆性能优化
在高并发场景下,对话记忆的存取可能成为性能瓶颈,几个优化策略:
-
分级缓存:
-
热点会话使用内存缓存
-
冷数据使用数据库存储
-
采用 Redis 等分布式缓存
-
-
异步写入:
-
响应用户后再异步保存对话历史
-
使用消息队列减轻数据库压力
-
-
记忆裁剪:
-
定期清理过期会话数据
-
只保留最近N轮对话
-
实现智能摘要压缩历史内容
-
7.2 多轮对话系统设计要点
构建生产级多轮对话系统的关键考量:
-
会话标识与隔离:
-
使用稳定唯一的会话标识
-
避免跨会话数据泄露
-
支持多设备同步
-
-
上下文窗口管理:
-
根据模型能力调整窗口大小
-
实现滑动窗口减少token成本
-
处理长对话的上下文压缩
-
-
异常处理:
-
模型超时与重试机制
-
会话恢复能力
-
敏感内容过滤
-
-
监控与评估:
-
对话质量监控指标
-
性能监控
-
A/B测试框架
-
总结
基于Spring AI框架构建多轮对话系统,关键在于正确设计对话记忆机制和合理组织Advisor拦截器链。通过灵活选择不同的持久化方案,可以满足从开发测试到大规模生产环境的不同需求。
最后我叫 lenyan~ 也会持续学习更进 AI知识。让我们共进 AI 大时代。
作者:lenyan GitHub:lenyanjgk (lenyanjgk) · GitHub CSDN:lenyan~-CSDN博客
觉得有用的话可以点点赞 (/ω\),支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每周都会不定时更新哦 >人< 。
更多推荐
所有评论(0)