Spring AI实现对话记录(实战篇)
Spring AI实现对话记录
介绍:基于上篇Spring AI搭建知识库后,此篇基于知识库信息保存消息、对话记录
步骤一:消息、对话表设计
消息记录表
CREATE TABLE `ai_chat_message` (
`message_id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息id',
`conversation_id` bigint DEFAULT NULL COMMENT '对话id',
`reply_id` bigint DEFAULT NULL COMMENT '回复消息id',
`type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '消息类型',
`model` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '模型标识',
`model_id` bigint DEFAULT NULL COMMENT '模型id',
`content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '消息内容',
`use_context` char(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '是否携带上下文 0->是 1->否',
`tenant_id` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '租户id',
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
`create_by` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息记录表';
对话记录表
CREATE TABLE `ai_chat_conversation` (
`conversation_id` bigint NOT NULL AUTO_INCREMENT COMMENT '对话id',
`knowledge_ids` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '知识库ids',
`title` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '标题',
`user_id` bigint DEFAULT NULL COMMENT '用户id',
`model_id` bigint DEFAULT NULL COMMENT '模型id',
`model` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '模型标识',
`system_message` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '系统提示词',
`temperature` double DEFAULT NULL COMMENT '温度系数',
`max_tokens` int DEFAULT NULL COMMENT '单条回复的最大token数',
`max_contexts` int DEFAULT NULL COMMENT '上下文的最大消息数',
`tenant_id` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '租户id',
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
`create_by` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` char(1) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
PRIMARY KEY (`conversation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='对话记录表';
步骤二:代码生成
拷贝到对应目录即可
步骤三:接口编写
接口一:新建对话
[1]AiChatConversationController
/**
* 新建对话
*
* @param bo
* @return
*/
@PostMapping("/create")
public R<Long> createChatConversation(@Validated @RequestBody AiChatConversationCreateBo bo) {
return R.ok(aiChatConversationService.createChatConversation(bo));
}
[2]IAiChatConversationService
/**
* 新建对话
*
* @param bo
* @return
*/
Long createChatConversation(AiChatConversationCreateBo bo);
[3]AiChatConversationServiceImpl
/**
* 新建对话
*
* @param bo
* @return
*/
@Override
public Long createChatConversation(AiChatConversationCreateBo bo) {
AiModelVo aiModelVo = aiModelService.validateModel(bo.getModelId());
Assert.notNull(aiModelVo, "模型不能为空");
// 校验模型
validateChatModel(aiModelVo);
// 校验知识库
if (ObjectUtil.isNotEmpty(bo.getKnowledgeIds())) {
for (Long knowledgeId : bo.getKnowledgeIds()) {
aiKnowledgeService.validateKnowledgeExists(knowledgeId);
}
}
AiChatConversation aiChatConversation = new AiChatConversation();
aiChatConversation.setTitle("新对话");
aiChatConversation.setModelId(bo.getModelId());
String knowledgeIdsStr = Optional.ofNullable(bo.getKnowledgeIds())
.filter(ids -> !ids.isEmpty())
.map(ids -> ids.stream()
.map(String::valueOf)
.collect(Collectors.joining(",")))
.orElse(null);
aiChatConversation.setKnowledgeIds(knowledgeIdsStr);
aiChatConversation.setUserId(getLoginUser().getUserId());
aiChatConversation.setModel(aiModelVo.getModel());
aiChatConversation.setTemperature(aiModelVo.getTemperature());
aiChatConversation.setMaxTokens(aiModelVo.getMaxTokens());
aiChatConversation.setMaxContexts(aiModelVo.getMaxContexts());
baseMapper.insert(aiChatConversation);
return aiChatConversation.getConversationId();
}
接口二:获取我的对话
[1]AiChatConversationController
/**
* 获取我的会话
*
* @return
*/
@GetMapping("/my")
public R<List<AiChatConversationVo>> getMyConversationList() {
return R.ok(aiChatConversationService.getMyConversationList());
}
[2]IAiChatConversationService
/**
* 获取我的会话
*
* @return
*/
List<AiChatConversationVo> getMyConversationList();
[3]AiChatConversationServiceImpl
/**
* 获取我的会话
*
* @return
*/
@Override
public List<AiChatConversationVo> getMyConversationList() {
LoginUser loginUser = getLoginUser();
LambdaQueryWrapper<AiChatConversation> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AiChatConversation::getUserId, loginUser.getUserId());
return baseMapper.selectVoList(queryWrapper);
}
接口三:发送消息
[1]AiChatMessageController
/**
* 发送消息
*
* @param bo
* @return
*/
@PostMapping(value = "/sendMessage", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AiChatMessageSendVo> sendMessage(@Validated @RequestBody AiChatMessageSendBo bo) {
return aiChatMessageService.sendMessage(bo);
}
[2]IAiChatMessageService
/**
* 发送消息
*
* @param bo
* @return
*/
Flux<AiChatMessageSendVo> sendMessage(AiChatMessageSendBo bo);
[3]AiChatMessageServiceImpl
/**
* 发送消息
*
* @param bo
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Flux<AiChatMessageSendVo> sendMessage(AiChatMessageSendBo bo) {
AiChatConversationVo aiChatConversationVo = conversationService.validateChatConversationExists(bo.getConversationId());
// 校验用户是否合法
LoginUser loginUser = getLoginUser();
if (ObjUtil.notEqual(aiChatConversationVo.getUserId(), loginUser.getUserId())) {
throw new ServiceException("对话不存在");
}
// 获取历史消息
List<AiChatMessageVo> historyMessages = baseMapper.selectVoList(
new LambdaQueryWrapper<AiChatMessage>().eq(AiChatMessage::getConversationId, bo.getConversationId()));
// 校验模型
AiModelVo aiModelVo = modelService.validateModel(aiChatConversationVo.getModelId());
StreamingChatModel chatModel = modelService.getChatModel(aiModelVo.getModelId());
// 关联的切片
List<AiSearchKnowledgeSegmentBo> knowledgeSegments= relativeKnowledgeSegments(bo.getContent(), aiChatConversationVo);
// 用户消息保存
AiChatMessage userMessage = createChatMessage(bo.getConversationId(), null,
aiModelVo, loginUser.getUserId(), MessageType.USER, bo.getContent(), bo.getUseContext());
// 助手信息保存
AiChatMessage assistantMessage = createChatMessage(bo.getConversationId(), userMessage.getMessageId(),
aiModelVo, loginUser.getUserId(), MessageType.ASSISTANT, null, bo.getUseContext());
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(bo.getContent()));
// 流式返回
StringBuffer stringBuffer = new StringBuffer();
return responseFlux
.map(chunk -> {
String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
newContent = StrUtil.nullToDefault(newContent, "");
stringBuffer.append(newContent);
AiChatMessageSendVo aiChatMessageSendVo = new AiChatMessageSendVo();
aiChatMessageSendVo.setSend(buildMessageVo(userMessage.getMessageId(), userMessage.getContent(),
MessageType.USER.toString(), userMessage.getCreateTime()));
aiChatMessageSendVo.setReceive(buildMessageVo(assistantMessage.getMessageId(), newContent,
MessageType.ASSISTANT.toString(), assistantMessage.getCreateTime()));
log.info("返回对象: {}", JSONUtil.toJsonStr(aiChatMessageSendVo));
return aiChatMessageSendVo;
})
.doOnComplete(() -> {
assistantMessage.setContent(stringBuffer.toString());
baseMapper.updateById(assistantMessage);
})
.doOnError(throwable -> {
log.info("对话异常: {}", throwable.getMessage());
})
.onErrorResume(error -> Flux.error(new IllegalArgumentException("对话异常")));
}
eg:此时已经将用户提的内容与知识库挂钩,将关联的切片信息查出,大家可以自行修改,让AI基于知识库回答
其中AiSearchKnowledgeSegmentBo对象内容:
@Data
public class AiSearchKnowledgeSegmentBo {
/**
* 分片id
*/
private Long segmentId;
/**
* 文档id
*/
private Long documentId;
/**
* 知识库id
*/
private Long knowledgeId;
/**
* 内容
*/
private String content;
/**
* 字符数
*/
private Integer contentLength;
/**
* token数
*/
private Integer tokens;
/**
* 质量分数
*/
private Double score;
}
查询关联切片底层核心方法:
/**
* 根据知识库检索文档
*
* @param aiKnowledgeVo
* @param knowledgeId
* @param content
* @return
*/
private List<Document> vectorSearchByKnowledge(AiKnowledgeVo aiKnowledgeVo, String knowledgeId, String content) {
VectorStore vectorStore = getVectorStoreById(aiKnowledgeVo);
return vectorStore.similaritySearch(SearchRequest.builder()
.query(content)
.topK(aiKnowledgeVo.getTopK())
.similarityThreshold(aiKnowledgeVo.getSimilarityThreshold())
.filterExpression(new FilterExpressionBuilder()
.eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, knowledgeId)
.build())
.build());
}
VectorStore源码
步骤四:测试
接口一:新建对话
需要定义为聊天模型的ID,其中根据type字段区分,1代表聊天,2代表向量
知识库ID保持一致,其包含documents集合
文档集
验证
接口二:我的对话
接口三:修改对话
验证
接口四:发送消息
验证
eg:这个接口可以修改为基于知识库回答,考验大家的水平,可以自行调整,需要资料可以加入技术群获取
接口五:查询消息记录
接口六:删除单条消息
验证
至此,基于Spring AI实现消息、对话记录demo版结束啦,需要资料可以加技术群获取!
本人正在打造技术交流群,欢迎志同道合的朋友一起探讨,一起努力,通过自己的努力,在技术岗位这条道路上走得更远。QQ群号:925317809 备注:技术交流 即可通过!
加入技术群可以获取资料,含AI资料、Spring AI中文文档等,等你加入~
更多推荐
所有评论(0)