介绍:基于上篇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中文文档等,等你加入~

Logo

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

更多推荐