基于SpringBoot+多模型API的AI角色扮演系统实战开发
本文介绍了一个基于SpringBoot和多模型API的AI角色扮演系统开发实战。项目采用Java技术栈,集成通义千问和DeepSeek两个大模型API,实现角色管理、会话创建、多风格对话等功能。系统架构采用分层设计,核心亮点是通过策略模式+工厂模式实现模型动态切换,支持REST API和WebSocket双交互方式。数据库设计包含角色、会话、消息等核心表,并预设了苏格拉底和魔法学徒两个角色。项目展
基于SpringBoot+多模型API的AI角色扮演系统实战开发
前言
这是我接单完成的一个AI应用项目。客户需要开发一个类似豆包智能体的AI角色扮演系统,要求使用Java技术栈,支持多个大模型API切换,实现纯文本的角色扮演对话功能。
本文将详细记录整个开发过程,包括需求分析、架构设计、核心功能实现、问题解决等,希望能给同样在学习AI应用开发的朋友一些参考和启发。
项目技术栈:SpringBoot 3.2.0 + MySQL 8.0 + 通义千问API + DeepSeek API + WebSocket
一、项目背景与需求分析
1.1 项目来源
这个项目是我接的一个外包单子,客户提供了详细的需求文档(虽然现在已经过期),主要需求是开发一个AI角色扮演系统,类似于豆包智能体的功能。
1.2 核心需求
客户的需求非常明确:
| 需求类别 | 具体要求 |
|---|---|
| 技术栈 | Java + SpringBoot + MySQL |
| 开发范围 | 纯后端API开发,无需前端界面 |
| AI模型 | 集成通义千问和DeepSeek两个大模型 |
| 测试方式 | 使用Postman等工具测试,能测通即可 |
核心功能清单:
- 角色管理(预设苏格拉底和魔法学徒两个角色)
- 会话管理(支持创建会话并选择模型)
- 消息交互(REST API + WebSocket双模式)
- 多种对话风格(沉浸式、学术式、苏格拉底式)
- 模型选择功能(运行时动态切换)
1.3 客户提供的资源
客户提供了以下关键资源:
# 通义千问API Key
# DeepSeek API Key
此外还提供了:
- 预设角色数据(苏格拉底、魔法学徒)
- 数据库表结构要求
- 角色人设的YAML格式定义
二、系统架构设计
2.1 技术选型
经过综合考虑,我选择了以下技术栈:
| 技术分类 | 选型方案 | 选择理由 |
|---|---|---|
| 后端框架 | Spring Boot 3.2.0 | 最新稳定版,生态完善 |
| 数据库 | MySQL 8.0 | 客户指定,成熟稳定 |
| ORM框架 | Spring Data JPA + Hibernate | 简化数据库操作 |
| HTTP客户端 | WebFlux WebClient | 支持异步调用大模型API |
| 实时通信 | WebSocket | 支持流式对话 |
| 构建工具 | Maven | 依赖管理方便 |
| JDK版本 | Java 17 | 支持新特性如switch表达式 |
2.2 项目结构
采用经典的分层架构设计:
ai-roleplay/
├── src/main/java/com/example/airoleplay/
│ ├── controller/ # REST API控制器层
│ │ ├── CharacterController.java # 角色管理接口
│ │ ├── SessionController.java # 会话管理接口
│ │ └── HealthController.java # 健康检查接口
│ ├── service/ # 业务逻辑层
│ │ ├── CharacterService.java # 角色业务逻辑
│ │ ├── SessionService.java # 会话业务逻辑
│ │ ├── LlmService.java # LLM服务接口
│ │ ├── TongyiLlmService.java # 通义千问实现
│ │ ├── DeepSeekLlmService.java # DeepSeek实现
│ │ └── LlmServiceFactory.java # 工厂模式选择模型
│ ├── entity/ # 数据库实体层
│ │ ├── Character.java # 角色实体
│ │ ├── Session.java # 会话实体
│ │ ├── Message.java # 消息实体
│ │ └── Persona.java # 人设实体
│ ├── repository/ # 数据访问层
│ │ ├── CharacterRepository.java
│ │ ├── SessionRepository.java
│ │ └── MessageRepository.java
│ ├── dto/ # 数据传输对象
│ │ ├── ChatMessage.java
│ │ └── CreateSessionRequest.java
│ ├── config/ # 配置类
│ │ └── WebSocketConfig.java # WebSocket配置
│ └── websocket/ # WebSocket处理器
│ └── ChatWebSocketHandler.java # 聊天WebSocket处理
└── schema.sql # 数据库初始化脚本
2.3 系统架构图
三、核心功能实现
3.1 数据库设计
数据库设计是整个系统的基础,我按照客户需求文档设计了完整的表结构。
核心表结构
| 表名 | 说明 | 关键字段 |
|---|---|---|
| users | 用户表 | id, email, nickname |
| characters | 角色表 | id, name, brief, popularity |
| personas | 人设表 | character_id, persona_yaml, persona_json |
| sessions | 会话表 | id, character_id, mode, model_name |
| messages | 消息表 | session_id, role, text |
| citations | 引用表 | message_id, source, url |
| favorites | 收藏表 | user_id, session_id |
关键设计点
-
UUID主键:使用UUID作为主键,便于分布式扩展
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()) -
对话模式枚举:
sessions表中的mode字段支持三种模式mode ENUM('immersive', 'academic', 'socratic') DEFAULT 'immersive'immersive:沉浸式,AI完全进入角色academic:学术式,以学术讨论方式交流socratic:苏格拉底式,通过提问引导思考
-
模型选择字段:
model_name字段记录使用的AI模型model_name VARCHAR(50) DEFAULT 'tongyi'
预设数据
数据库初始化时插入了两个角色:
INSERT INTO characters (id, name, locale, tags, brief, popularity) VALUES
('char-socrates', '苏格拉底', 'zh-CN', '["哲学家", "古希腊", "智者"]',
'古希腊哲学家,以问答法著称', 100),
('char-wizard', '魔法学徒', 'zh-CN', '["魔法", "学生", "冒险"]',
'霍格沃茨的年轻魔法学徒', 85);
3.2 多模型适配器模式
这是项目的核心设计亮点。为了支持多个大模型的灵活切换,我采用了策略模式 + 工厂模式的组合设计。
设计思路
LLM服务接口定义
public interface LlmService {
String generateResponse(String text);
String generateResponse(String text, Session session);
void streamChat(String text, String sessionId, WebSocketSession webSocketSession);
}
工厂类实现模型选择
@Component
@RequiredArgsConstructor
public class LlmServiceFactory {
private final TongyiLlmService tongyiLlmService;
private final DeepSeekLlmService deepSeekLlmService;
public LlmService getService(String modelName) {
return switch (modelName.toLowerCase()) {
case "deepseek" -> deepSeekLlmService;
case "tongyi" -> tongyiLlmService;
default -> tongyiLlmService;
};
}
}
设计优势
这样设计带来了以下好处:
- ✅ 符合开闭原则:新增模型只需实现
LlmService接口,无需修改现有代码 - ✅ 运行时动态选择:根据会话配置动态选择模型,无需重启服务
- ✅ 代码解耦:每个模型的实现互不影响,便于维护
- ✅ 易于测试:可以轻松mock不同的LLM服务进行单元测试
3.3 通义千问API集成
通义千问使用阿里云的DashScope API,这是阿里云提供的大模型服务平台。
核心实现代码
private String callTongyiApi(String text) throws Exception {
Map<String, Object> request = new HashMap<>();
request.put("model", "qwen-turbo");
Map<String, Object> input = new HashMap<>();
input.put("messages", List.of(Map.of("role", "user", "content", text)));
request.put("input", input);
WebClient client = WebClient.builder()
.defaultHeader("Authorization", "Bearer " + apiKey)
.defaultHeader("Content-Type", "application/json")
.build();
String response = client.post()
.uri("https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation")
.bodyValue(request)
.retrieve()
.bodyToMono(String.class)
.block();
JsonNode node = objectMapper.readTree(response);
return node.path("output").path("choices").get(0)
.path("message").path("content").asText();
}
踩坑记录
⚠️ 坑点1:通义千问的响应格式有两种
- 旧版格式:
output.text- 新版格式:
output.choices[0].message.content- 解决方案:需要兼容处理两种格式
⚠️ 坑点2:错误处理方式不同
- 通义千问失败时检查
code字段- 而不是常见的
error字段
// 错误检查代码
if (node.has("code")) {
throw new RuntimeException("API调用失败: " + node.path("message").asText());
}
3.4 DeepSeek API集成
DeepSeek使用标准的OpenAI兼容接口,实现相对简单,这也是我选择它的原因之一。
核心实现代码
private String callDeepSeekApi(String text) throws Exception {
Map<String, Object> request = new HashMap<>();
request.put("model", "deepseek-chat");
request.put("messages", List.of(Map.of("role", "user", "content", text)));
request.put("stream", false);
WebClient client = WebClient.builder()
.defaultHeader("Authorization", "Bearer " + apiKey)
.defaultHeader("Content-Type", "application/json")
.build();
String response = client.post()
.uri("https://api.deepseek.com/chat/completions")
.bodyValue(request)
.retrieve()
.bodyToMono(String.class)
.block();
JsonNode node = objectMapper.readTree(response);
return node.path("choices").get(0)
.path("message").path("content").asText();
}
API对比
| 对比项 | 通义千问 | DeepSeek |
|---|---|---|
| 接口标准 | 阿里云自定义 | OpenAI兼容 |
| 请求格式 | input.messages |
messages |
| 响应格式 | output.choices |
choices |
| 流式输出 | incremental_output |
stream |
| 实现难度 | 中等 | 简单 |
3.5 角色扮演Prompt工程
这是项目的另一个核心技术点。为了让AI真正"扮演"角色而不是简单回答问题,我设计了动态Prompt构建逻辑。
Prompt构建策略
private String buildRolePlayPrompt(String userText, Session session) {
StringBuilder prompt = new StringBuilder();
// 1. 注入角色身份
characterService.getCharacterById(session.getCharacterId())
.ifPresent(character -> {
prompt.append("你现在要扮演角色:").append(character.getName()).append("\n");
prompt.append("角色描述:").append(character.getBrief()).append("\n");
});
// 2. 根据对话模式调整风格
switch (session.getMode()) {
case immersive:
prompt.append("请完全沉浸在这个角色中,用第一人称回答,保持角色的性格特点和说话方式。\n");
break;
case academic:
prompt.append("请以学术讨论的方式,结合角色的知识背景来回答问题。\n");
break;
case socratic:
prompt.append("请用苏格拉底式的问答方法,通过提问来引导思考。\n");
break;
}
prompt.append("\n用户问题:").append(userText);
return prompt.toString();
}
实际效果示例
场景1:苏格拉底 + socratic模式
用户:什么是正义?
AI:让我们先澄清概念。你认为正义是什么呢?是对所有人一视同仁,还是给予每个人应得的?
场景2:魔法学徒 + immersive模式
用户:请介绍一下魔法学院的生活
AI:这听起来像是一场冒险!在霍格沃茨,每天早上我都要赶去上魔药课,格兰芬多的同学们总是...
场景3:苏格拉底 + academic模式
用户:什么是真理?
AI:从哲学角度来看,真理是符合客观实际的认识。在古希腊哲学传统中,我们认为...
3.6 WebSocket实时通信
为了支持实时流式对话,我实现了WebSocket功能,让用户可以实时看到AI的回复过程。
WebSocket处理器实现
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
String sessionId = getSessionIdFromUri(session.getUri());
ChatMessage chatMessage = objectMapper.readValue(message.getPayload(), ChatMessage.class);
// 获取会话信息,确定使用哪个模型
Session chatSession = sessionService.getSessionById(sessionId).get();
LlmService llmService = llmServiceFactory.getService(chatSession.getModelName());
// 异步调用LLM,避免阻塞WebSocket线程
new Thread(() -> llmService.streamChat(chatMessage.getText(), sessionId, session)).start();
}
连接方式
# WebSocket连接URL
ws://localhost:8080/ws/chat?sessionId=<会话ID>
# 发送消息格式(JSON)
{
"text": "你好,请介绍一下你自己"
}
# 接收消息格式(JSON)
{
"delta": "我是苏格拉底...",
"done": false
}
技术要点
- 异步处理:使用新线程处理LLM调用,避免阻塞WebSocket主线程
- 会话验证:连接时验证sessionId的有效性
- 错误处理:捕获异常并记录日志
- 线程安全:使用
synchronized保证消息发送的线程安全
四、完整测试流程
4.1 环境准备
- 安装MySQL 8.0,创建数据库
- 执行
schema.sql初始化数据 - 修改
application.properties中的数据库连接信息 - 运行
AiRoleplayApplication.java启动服务
4.2 REST API测试(使用Postman)
测试1:获取角色列表
GET http://localhost:8080/api/v1/characters
预期结果:返回苏格拉底和魔法学徒两个角色
测试2:创建会话(通义千问)
POST http://localhost:8080/api/v1/sessions
Content-Type: application/json
{
"characterId": "char-socrates",
"mode": "immersive",
"modelName": "tongyi"
}
关键操作:复制返回的id字段,这是后续对话需要的sessionId
测试3:发送消息
POST http://localhost:8080/api/v1/sessions/{sessionId}/messages
Content-Type: application/json
{
"text": "你好,请介绍一下你自己"
}
实际效果:AI会以苏格拉底的口吻回答
测试4:获取历史消息
GET http://localhost:8080/api/v1/sessions/{sessionId}/messages
返回内容:该会话的所有对话记录
4.3 WebSocket测试
- 连接WebSocket:
ws://localhost:8080/ws/chat?sessionId={sessionId} - 发送JSON消息:
{
"text": "这是通过WebSocket发送的消息"
}
- 实时接收AI的流式回复
五、开发中遇到的问题与解决
5.1 问题1:AI回复中自报家门
现象:使用通义千问时,AI回复"我是通义千问…",没有进入角色
原因:创建会话时保存了modelName字段到数据库,但调用LLM服务时没有从数据库读取会话信息,导致Prompt没有正确构建
解决方案:
// 修改前
String response = llmServiceFactory.getService(session.getModelName())
.generateResponse(text);
// 修改后
String response = llmServiceFactory.getService(session.getModelName())
.generateResponse(text, session); // 传入完整会话对象
5.2 问题2:WebClient依赖缺失
现象:编译时找不到WebClient类
解决方案:在pom.xml中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
5.3 问题3:UUID生成策略问题
现象:JPA保存实体时ID为null
解决方案:使用Hibernate的UUID生成器
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
private String id;
六、项目亮点总结
6.1 技术亮点
- 架构设计:采用分层架构 + 策略模式,代码结构清晰,易于维护和扩展
- 多模型支持:通过工厂模式实现运行时动态切换AI模型
- Prompt工程:根据角色和对话模式动态构建Prompt,实现真实的角色扮演效果
- 双通信模式:同时支持REST API和WebSocket,满足不同场景需求
- 完整的数据持久化:所有对话记录都保存到数据库,支持历史查询
6.2 技术收获
通过这个项目,我深入学习了:
- Spring Boot 3.x的新特性和最佳实践
- 大模型API的调用方式和差异
- WebSocket在实时通信中的应用
- 设计模式在实际项目中的运用
- Prompt工程的基本技巧
七、项目交付
7.1 交付内容
最终交付内容:
- ✅ 完整的源代码(Maven项目)
- ✅ 数据库初始化脚本(schema.sql)
- ✅ 详细的测试文档(Word格式)
- ✅ API接口说明
- ✅ 运行环境配置说明
客户通过Postman测试所有接口后,确认功能完全符合需求,项目顺利验收。
7.2 后续优化方向
如果有时间,可以考虑以下优化:
- 添加用户认证和权限管理(JWT)
- 实现真正的流式输出(SSE或WebSocket分片传输)
- 添加对话上下文管理(目前每次调用都是独立的)
- 集成更多大模型
- 添加敏感词过滤和内容审核
- 实现对话摘要和关键词提取
八、相关资源
结语
这是我第一次独立完成AI应用的后端开发,从需求分析到架构设计,从编码实现到测试交付,整个过程让我对Spring Boot和大模型应用有了更深的理解。
项目的核心价值:
- 💡 掌握了多模型集成的设计模式
- 💡 学会了Prompt工程的实践技巧
- 💡 积累了真实的项目开发经验
- 💡 提升了问题分析和解决能力
希望这篇文章能帮助到正在学习AI应用开发的同学们!如果你有任何问题或建议,欢迎私信或者在评论区交流讨论!
关键词:SpringBoot AI角色扮演 通义千问 DeepSeek WebSocket 大模型应用 Prompt工程 设计模式
原创不易,如果觉得有帮助,请点赞👍收藏⭐关注💖支持一下!
更多推荐
所有评论(0)