diff --git a/virtual-patient-common/src/main/java/com/supervision/config/RedisVectorStoreInit.java b/virtual-patient-common/src/main/java/com/supervision/config/RedisVectorStoreInit.java new file mode 100644 index 00000000..4b5b47b3 --- /dev/null +++ b/virtual-patient-common/src/main/java/com/supervision/config/RedisVectorStoreInit.java @@ -0,0 +1,27 @@ +package com.supervision.config; + +import org.springframework.ai.vectorstore.RedisVectorStore; +import org.springframework.util.Assert; + +public class RedisVectorStoreInit { + + public static RedisVectorStore redisVectorStoreInit(VectorEmbeddingClient vectorEmbeddingClient, RedisVectorProperties redisVectorProperties) { + Assert.notNull(redisVectorProperties.getUri(), "配置文件vector.redis.uri未找到"); + RedisVectorStore.RedisVectorStoreConfig config = RedisVectorStore.RedisVectorStoreConfig.builder() + .withURI(redisVectorProperties.getUri()) + .withPrefix(redisVectorProperties.getPrefix()) + .withIndexName(redisVectorProperties.getIndexName()) + // 定义搜索过滤器使用的元数据字段(!!!!!!!!千万重要,数据类型一定要用字符串,否则会导致查询不到!!!!!!!!) + .withMetadataFields( + // 问题的ID,如果type=1,则在library表,如果type=2,则在similar表 + RedisVectorStore.MetadataField.tag("questionId"), + //关联字典ID + RedisVectorStore.MetadataField.tag("dictId"), + // 标准问ID + RedisVectorStore.MetadataField.tag("libraryQuestionId"), + // 类型 1标准问 2相似问 3自定义 + RedisVectorStore.MetadataField.tag("type")) + .build(); + return new RedisVectorStore(config, vectorEmbeddingClient); + } +} diff --git a/virtual-patient-common/src/main/java/com/supervision/config/VectorSimilarityConfiguration.java b/virtual-patient-common/src/main/java/com/supervision/config/VectorSimilarityConfiguration.java index dc8e4cf4..269b54f6 100644 --- a/virtual-patient-common/src/main/java/com/supervision/config/VectorSimilarityConfiguration.java +++ b/virtual-patient-common/src/main/java/com/supervision/config/VectorSimilarityConfiguration.java @@ -18,25 +18,9 @@ public class VectorSimilarityConfiguration { return new VectorEmbeddingClient(embeddingProperties.getUrl()); } - @Bean + @Bean() @ConditionalOnProperty(prefix = "vector.redis", name = "uri") public RedisVectorStore redisVectorStore(VectorEmbeddingClient vectorEmbeddingClient, RedisVectorProperties redisVectorProperties) { - Assert.notNull(redisVectorProperties.getUri(), "配置文件vector.redis.uri未找到"); - RedisVectorStore.RedisVectorStoreConfig config = RedisVectorStore.RedisVectorStoreConfig.builder() - .withURI(redisVectorProperties.getUri()) - .withPrefix(redisVectorProperties.getPrefix()) - .withIndexName(redisVectorProperties.getIndexName()) - // 定义搜索过滤器使用的元数据字段(!!!!!!!!千万重要,数据类型一定要用字符串,否则会导致查询不到!!!!!!!!) - .withMetadataFields( - // 问题的ID,如果type=1,则在library表,如果type=2,则在similar表 - RedisVectorStore.MetadataField.tag("questionId"), - //关联字典ID - RedisVectorStore.MetadataField.tag("dictId"), - // 标准问ID - RedisVectorStore.MetadataField.tag("libraryQuestionId"), - // 类型 1标准问 2相似问 3自定义 - RedisVectorStore.MetadataField.tag("type")) - .build(); - return new RedisVectorStore(config, vectorEmbeddingClient); + return RedisVectorStoreInit.redisVectorStoreInit(vectorEmbeddingClient, redisVectorProperties); } } diff --git a/virtual-patient-common/src/main/java/com/supervision/domain/QaSimilarityQuestionAnswer.java b/virtual-patient-common/src/main/java/com/supervision/domain/QaSimilarityQuestionAnswer.java index 3570458f..e88df4d8 100644 --- a/virtual-patient-common/src/main/java/com/supervision/domain/QaSimilarityQuestionAnswer.java +++ b/virtual-patient-common/src/main/java/com/supervision/domain/QaSimilarityQuestionAnswer.java @@ -26,7 +26,7 @@ public class QaSimilarityQuestionAnswer { private String dictId; /** - * cosine余弦得分,一般0.5以上匹配度就比较高了 + * cosine余弦得分,一般0.6以上匹配度就比较高了 */ private Double matchScore; } diff --git a/virtual-patient-common/src/main/java/com/supervision/record/QuestionMetadata.java b/virtual-patient-common/src/main/java/com/supervision/record/QuestionMetadata.java new file mode 100644 index 00000000..d204625e --- /dev/null +++ b/virtual-patient-common/src/main/java/com/supervision/record/QuestionMetadata.java @@ -0,0 +1,13 @@ +package com.supervision.record; + +import java.util.Map; + +public record QuestionMetadata(String type, String libraryQuestionId, String questionId, Long dictId) { + + public Map toMap() { + return Map.of("type", type, + "libraryQuestionId", libraryQuestionId, + "questionId", questionId, + "dictId", String.valueOf(dictId)); + } +} diff --git a/virtual-patient-common/src/main/java/com/supervision/util/AiChatUtil.java b/virtual-patient-common/src/main/java/com/supervision/util/AiChatUtil.java index 299d93f9..2ca62c87 100644 --- a/virtual-patient-common/src/main/java/com/supervision/util/AiChatUtil.java +++ b/virtual-patient-common/src/main/java/com/supervision/util/AiChatUtil.java @@ -9,6 +9,7 @@ import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.ollama.OllamaChatClient; +import org.springframework.ai.ollama.api.OllamaOptions; import java.util.List; import java.util.Optional; @@ -30,6 +31,25 @@ public class AiChatUtil { public static Optional chat(String chat) { Prompt prompt = new Prompt(List.of(new UserMessage(chat))); Future submit = chatExecutor.submit(new ChatTask(chatClient, prompt)); + try { + return Optional.of(JSONUtil.parseObj(submit.get())); + } catch (ExecutionException | InterruptedException e) { + log.error("调用大模型生成失败", e); + } + return Optional.empty(); + } + + /** + * 单轮对话 + * + * @param chat 对话的内容 + * @return jsonObject + */ + public static Optional chatWithRandom(String chat, Integer seed) { + OllamaOptions ollamaOptions = new OllamaOptions(); + ollamaOptions.setSeed(seed); + Prompt prompt = new Prompt(List.of(new UserMessage(chat)), ollamaOptions); + Future submit = chatExecutor.submit(new ChatTask(chatClient, prompt)); try { return Optional.of(JSONUtil.parseObj(submit.get())); } catch (ExecutionException | InterruptedException e) { @@ -50,7 +70,7 @@ public class AiChatUtil { try { return Optional.of(JSONUtil.parseObj(submit.get())); } catch (ExecutionException | InterruptedException e) { - log.error("调用大模型生成失败"); + log.error("调用大模型生成失败", e); } return Optional.empty(); } @@ -90,7 +110,7 @@ public class AiChatUtil { String s = submit.get(); return Optional.ofNullable(JSONUtil.toBean(s, clazz)); } catch (ExecutionException | InterruptedException e) { - log.error("调用大模型生成失败"); + log.error("调用大模型生成失败", e); } return Optional.empty(); } diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/controller/customQuestion/CustomQuestionController.java b/virtual-patient-manage/src/main/java/com/supervision/manage/controller/customQuestion/CustomQuestionController.java new file mode 100644 index 00000000..0f3ad1f1 --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/controller/customQuestion/CustomQuestionController.java @@ -0,0 +1,42 @@ +package com.supervision.manage.controller.customQuestion; + +import com.supervision.manage.pojo.vo.AssessCustomQuestionReqVO; +import com.supervision.manage.pojo.vo.CustomQuestionMatchResVO; +import com.supervision.manage.pojo.vo.CustomQuestionSaveReqVO; +import com.supervision.manage.service.CustomQuestionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "自定义问题") +@RestController +@RequestMapping("customQuestion") +@RequiredArgsConstructor +public class CustomQuestionController { + + private final CustomQuestionService customQuestionService; + + @Operation(summary = "评估自定义问题") + @PostMapping("/assessCustomQuestion") + public CustomQuestionMatchResVO assessCustomQuestion(@RequestBody AssessCustomQuestionReqVO reqVO) { + return customQuestionService.assessCustomQuestion(reqVO); + } + + @Operation(summary = "生成相似问") + @PostMapping("/generateSimilarQuestion") + public List generateSimilarQuestion(@RequestBody AssessCustomQuestionReqVO reqVO) { + return customQuestionService.generateSimilarQuestion(reqVO); + } + + @Operation(summary = "保存自定义问题") + @PostMapping("saveCustomQuestion") + public void saveCustomQuestion(@RequestBody CustomQuestionSaveReqVO reqVO) { + customQuestionService.saveCustomQuestion(reqVO); + } +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/AssessCustomQuestionReqVO.java b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/AssessCustomQuestionReqVO.java new file mode 100644 index 00000000..0638fcba --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/AssessCustomQuestionReqVO.java @@ -0,0 +1,16 @@ +package com.supervision.manage.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class AssessCustomQuestionReqVO { + + @Schema(description = "自定义问题") + private String customQuestion; + + @Schema(description = "已选中的问题库id列表") + private List selectedQuestionLibraryIdList; +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/CustomQuestionMatchResVO.java b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/CustomQuestionMatchResVO.java new file mode 100644 index 00000000..7bac3227 --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/CustomQuestionMatchResVO.java @@ -0,0 +1,18 @@ +package com.supervision.manage.pojo.vo; + +import com.supervision.manage.service.impl.SimilarLibraryQuestionResVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class CustomQuestionMatchResVO { + + @Schema(description = "是否评估通过,true评估通过,false不通过") + private Boolean accessCustom; + + @Schema(description = "相似度高的标准问题列表") + private List similarLibraryQuestionList; + +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/CustomQuestionSaveReqVO.java b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/CustomQuestionSaveReqVO.java new file mode 100644 index 00000000..f07fcba4 --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/CustomQuestionSaveReqVO.java @@ -0,0 +1,25 @@ +package com.supervision.manage.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class CustomQuestionSaveReqVO { + + @Schema(description = "字典ID") + private Long dictId; + + @Schema(description = "用户自定义的问题") + private String customQuestion; + + @Schema(description = "用户配置的回答") + private String answer; + + @Schema(description = "自定义问题的相似问") + private List similarQuestionList; + + @Schema(description = "已选中的问题库id列表") + private List selectedQuestionLibraryIdList; +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/SaveCustomQuestionResVO.java b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/SaveCustomQuestionResVO.java new file mode 100644 index 00000000..1a5d4c1c --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/pojo/vo/SaveCustomQuestionResVO.java @@ -0,0 +1,16 @@ +package com.supervision.manage.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class SaveCustomQuestionResVO { + + @Schema(description = "保存结果,true保存成功,false保存失败") + private Boolean saveResult; + + @Schema(description = "保存失败,则返回保存失败的问题") + private List errorSimilarQuestionList; +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/service/CustomQuestionService.java b/virtual-patient-manage/src/main/java/com/supervision/manage/service/CustomQuestionService.java new file mode 100644 index 00000000..b16d460c --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/service/CustomQuestionService.java @@ -0,0 +1,17 @@ +package com.supervision.manage.service; + +import com.supervision.manage.pojo.vo.AssessCustomQuestionReqVO; +import com.supervision.manage.pojo.vo.CustomQuestionMatchResVO; +import com.supervision.manage.pojo.vo.CustomQuestionSaveReqVO; +import com.supervision.manage.pojo.vo.SaveCustomQuestionResVO; + +import java.util.List; + +public interface CustomQuestionService { + + CustomQuestionMatchResVO assessCustomQuestion(AssessCustomQuestionReqVO reqVO); + + List generateSimilarQuestion(AssessCustomQuestionReqVO reqVO); + + SaveCustomQuestionResVO saveCustomQuestion(CustomQuestionSaveReqVO reqVO); +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/CustomQuestionServiceImpl.java b/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/CustomQuestionServiceImpl.java new file mode 100644 index 00000000..72848c01 --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/CustomQuestionServiceImpl.java @@ -0,0 +1,258 @@ +package com.supervision.manage.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import com.supervision.config.RedisVectorProperties; +import com.supervision.config.RedisVectorStoreInit; +import com.supervision.config.VectorEmbeddingClient; +import com.supervision.domain.QaSimilarityQuestionAnswer; +import com.supervision.exception.BusinessException; +import com.supervision.manage.pojo.vo.AssessCustomQuestionReqVO; +import com.supervision.manage.pojo.vo.CustomQuestionMatchResVO; +import com.supervision.manage.pojo.vo.CustomQuestionSaveReqVO; +import com.supervision.manage.pojo.vo.SaveCustomQuestionResVO; +import com.supervision.manage.service.CustomQuestionService; +import com.supervision.model.AskTemplateQuestionLibrary; +import com.supervision.model.AskTemplateQuestionSimilarity; +import com.supervision.model.CommonDic; +import com.supervision.record.QuestionMetadata; +import com.supervision.service.AskTemplateQuestionLibraryService; +import com.supervision.service.AskTemplateQuestionSimilarityService; +import com.supervision.service.CommonDicService; +import com.supervision.util.AiChatUtil; +import com.supervision.util.SimilarityUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.RedisVectorStore; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CustomQuestionServiceImpl implements CustomQuestionService { + + private static final String prompt = """ + 根据用户输入的问题,生成相似的不同问法.要求最少生成20条,且保证和输入的问题相似 + 问题:{customQuestion} + 回复格式:{"similarList":["xx1","xx2"]} + """; + private final AskTemplateQuestionLibraryService askTemplateQuestionLibraryService; + + private final CommonDicService commonDicService; + + private final RedisVectorProperties redisVectorProperties; + + private final VectorEmbeddingClient vectorEmbeddingClient; + + private final AskTemplateQuestionSimilarityService askTemplateQuestionSimilarityService; + + private final RedisVectorStore redisVectorStore; + @Value("${customQuestionThreshold:0.6}") + private String customQuestionThreshold; + + @Override + public CustomQuestionMatchResVO assessCustomQuestion(AssessCustomQuestionReqVO reqVO) { + CustomQuestionMatchResVO resVO = new CustomQuestionMatchResVO(); + // 首先拿问题去向量库里面进行匹配,找到TOP的数据 + Assert.notBlank(reqVO.getCustomQuestion(), "问诊问题不能为空"); + if (CollUtil.isEmpty(reqVO.getSelectedQuestionLibraryIdList())) { + // 如果没有选择过任何问题,则直接允许添加 + resVO.setAccessCustom(true); + return resVO; + } + // 校验相似度 + List similarList = checkCustomQuestionSimilar(reqVO.getCustomQuestion(), reqVO.getSelectedQuestionLibraryIdList()); + if (CollUtil.isEmpty(similarList)) { + // 如果走这里,说明相似度比较低,可以允许 + resVO.setAccessCustom(true); + } else { + resVO.setAccessCustom(false); + resVO.setSimilarLibraryQuestionList(similarList); + } + return resVO; + } + + private List checkCustomQuestionSimilar(String customQuestion, List selectedQuestionLibraryIdList) { + // 只和已经选择的问题库进行比较 + List qaSimilarityQuestionAnswers = SimilarityUtil.talkRedisVectorWithScore(customQuestion, selectedQuestionLibraryIdList); + // 找到大于阈值的 + List overThresholdList = qaSimilarityQuestionAnswers.stream() + .filter(e -> e.getMatchScore() >= Double.parseDouble(customQuestionThreshold)).toList(); + List similarList = new ArrayList<>(); + if (CollUtil.isNotEmpty(overThresholdList)) { + // 如果不为空,说明有相似度比较高的,这时,获取他们的标准库ID + LinkedHashSet overLibraryIdSet = new LinkedHashSet<>(); + for (QaSimilarityQuestionAnswer similarityQuestionAnswer : overThresholdList) { + overLibraryIdSet.add(similarityQuestionAnswer.getLibraryQuestionId()); + } + // 然后获取标准库的相似问题以及关联的字典 + List libraryList = askTemplateQuestionLibraryService.lambdaQuery() + .in(AskTemplateQuestionLibrary::getId, overLibraryIdSet).list(); + List dictIdList = libraryList.stream().map(AskTemplateQuestionLibrary::getDictId).toList(); + List dictList = commonDicService.lambdaQuery().in(CommonDic::getId, dictIdList).list(); + Map dictMap = dictList.stream().collect(Collectors.toMap(CommonDic::getId, CommonDic::getNameZhPath)); + + for (AskTemplateQuestionLibrary library : libraryList) { + SimilarLibraryQuestionResVO similarNode = new SimilarLibraryQuestionResVO(); + similarNode.setLibraryQuestion(library.getStandardQuestion()); + similarNode.setDictPath(dictMap.get(library.getDictId())); + similarList.add(similarNode); + } + } + return similarList; + } + + public List generateSimilarQuestion(AssessCustomQuestionReqVO reqVO) { + log.info("开始调用大模型生成相似问题"); + Optional chat = AiChatUtil.chat(StrUtil.format(prompt, Map.of("customQuestion", reqVO.getCustomQuestion()))); + // 最终相似的列表 + log.info("调用大模型生成相似问题结束"); + if (chat.isPresent()) { + List similarList = chat.get().getBeanList("similarList", String.class); + return checkSimilarQuestion(reqVO.getCustomQuestion(), similarList, reqVO.getSelectedQuestionLibraryIdList()); + } + return new ArrayList<>(); + } + + /** + * 潜在bug: + * 这里可能有一个问题,如果先添加的自定义的相似问,再去选择一个标准问 + * 这时,我们是不对标准问下面的相似问,再和自定义问题以及对应的相似问进行一遍相似度校验的 + * 这时,如果自定义的相似问和标准问的相似问如果相似度很高,就会出现答非所问的情况 + * 2024.06.18 15:24和产品反馈了这个问题,答案是,先不管这个问题,等后面实际使用中出现了,再去想怎么解决 + * weixin聊天记录:避免限制配置,我们按医生提供的问题配置完后会去实际测试问诊,如果有答非所问的情况,会再人工删除 + * + * @param customQuestion 用户自定义的标注问题 + * @param similarQuestionList 大模型或用户手动填写的相似问 + * @param selectedQuestionList 用户已选择的标准问 + * @return 相似问 + */ + private List checkSimilarQuestion(String customQuestion, List similarQuestionList, List selectedQuestionList) { + List similarResultList = new ArrayList<>(); + // 定义一个新的向量库 + RedisVectorProperties newRedisVectorProperties = BeanUtil.copyProperties(redisVectorProperties, RedisVectorProperties.class); + // 设置一个新的前缀和indexName + String uuid = UUID.fastUUID().toString(); + newRedisVectorProperties.setPrefix("customQuestion:" + uuid + ":"); + String indexName = StrUtil.format("{}-{}", "customQuestion-index", uuid); + newRedisVectorProperties.setIndexName(indexName); + RedisVectorStore tempRedisVectorStore = RedisVectorStoreInit.redisVectorStoreInit(vectorEmbeddingClient, newRedisVectorProperties); + tempRedisVectorStore.afterPropertiesSet(); + // 先把用户自定义的标准问题加载进去,然后遍历相似问,去计算相似问和这个自定义的标准问题间的相似度.相似度高才允许被保留 + ArrayList tempVectorList = new ArrayList<>(); + List documents = new ArrayList<>(); + Document document = new Document(customQuestion); + documents.add(document); + tempVectorList.add(document.getId()); + tempRedisVectorStore.add(documents); + log.info("生成临时库以存储临时的标准问完成"); + try { + // 首先对生成的相似问和提交的标准问进行相似度比较,如果分数比较低,就忽略 + for (String similarQuestion : similarQuestionList) { + log.info("正在计算相似度:{}", similarQuestion); + List similaritySearchList = tempRedisVectorStore.similaritySearch(similarQuestion); + if (CollUtil.isNotEmpty(similaritySearchList)) { + // 如果相似度比较高,就添加进去 + Double similarityScore = SimilarityUtil.computeScore(similaritySearchList.get(0)); + log.info("问题:{}和自定义问题的相似度为:{}", similarQuestion, similarityScore); + if (similarityScore >= 0.6) { + // 如果没有选择任何问题,那么就直接过 + if (CollUtil.isNotEmpty(selectedQuestionList)) { + // 再过一遍已选择的问题,如果已选择的问题中存在相似度较高的,就不允许添加进来 + List qaSimilarityQuestionAnswers = SimilarityUtil.talkRedisVectorWithScore(similarQuestion, selectedQuestionList); + if (CollUtil.isNotEmpty(qaSimilarityQuestionAnswers)) { + // 如果标准库的数据比较低,才允许添加进来 + Double qaMatchScore = qaSimilarityQuestionAnswers.get(0).getMatchScore(); + log.info("问题:{}和标准问题库的相似度为:{},匹配度最高的标准问题库问题为:{}", similarQuestion, qaMatchScore, qaSimilarityQuestionAnswers.get(0).getMatchQuestion()); + if (qaMatchScore <= 0.6) { + similarResultList.add(similarQuestion); + } + } + } else { + similarResultList.add(similarQuestion); + } + + } + } + } + } catch (Exception e) { + log.info("计算自定义的标准问题的和相似问的相似度出现问题"); + throw e; + } finally { + // 最终要进行删除 + tempRedisVectorStore.delete(tempVectorList); + tempRedisVectorStore.getJedis().ftDropIndex(indexName); + } + return similarResultList; + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public SaveCustomQuestionResVO saveCustomQuestion(CustomQuestionSaveReqVO reqVO) { + // 首先对标准问进行校验,校验相似度比较低才能允许被提交 + List similarLibraryQuestionResVOS = checkCustomQuestionSimilar(reqVO.getCustomQuestion(), reqVO.getSelectedQuestionLibraryIdList()); + if (CollUtil.isNotEmpty(similarLibraryQuestionResVOS)) { + throw new BusinessException("相似度较高,已存在相似问诊问题,不支持添加,若要添加可联系运营人员!"); + } + List similarQuestionList = checkSimilarQuestion(reqVO.getCustomQuestion(), reqVO.getSimilarQuestionList(), reqVO.getSelectedQuestionLibraryIdList()); + // 校验,这时要校验哪些问题没有被添加进来,如果没有被添加进来,说明相似度较高,这时前端应该给于提示 + List errorSimilarQuestionList = new ArrayList<>(); + for (String pageSimilarQuestion : reqVO.getSimilarQuestionList()) { + // 如果不包含,说明相似度较高 + if (!similarQuestionList.contains(pageSimilarQuestion)) { + errorSimilarQuestionList.add(pageSimilarQuestion); + } + } + SaveCustomQuestionResVO saveCustomQuestionResVO = new SaveCustomQuestionResVO(); + if (CollUtil.isNotEmpty(errorSimilarQuestionList)) { + saveCustomQuestionResVO.setSaveResult(false); + saveCustomQuestionResVO.setErrorSimilarQuestionList(errorSimilarQuestionList); + return saveCustomQuestionResVO; + } + // 如果为空,说明符合要求,这时就进行保存 + AskTemplateQuestionLibrary askTemplateQuestionLibrary = new AskTemplateQuestionLibrary(); + askTemplateQuestionLibrary.setDictId(reqVO.getDictId()); + askTemplateQuestionLibrary.setStandardQuestion(reqVO.getCustomQuestion()); + askTemplateQuestionLibrary.setType(2); + askTemplateQuestionLibraryService.save(askTemplateQuestionLibrary); + // 保存相似问 + for (String similarQuestion : similarQuestionList) { + AskTemplateQuestionSimilarity askTemplateQuestionSimilarity = new AskTemplateQuestionSimilarity(); + askTemplateQuestionSimilarity.setLibraryId(askTemplateQuestionLibrary.getId()); + askTemplateQuestionSimilarity.setSimilarityQuestion(similarQuestion); + askTemplateQuestionSimilarityService.save(askTemplateQuestionSimilarity); + } + // 这时,需要将数据刷到向量库中 + String description = askTemplateQuestionLibrary.getStandardQuestion(); + Map questionVector = new QuestionMetadata("3", + askTemplateQuestionLibrary.getId(), askTemplateQuestionLibrary.getId(), + askTemplateQuestionLibrary.getDictId()).toMap(); + redisVectorStore.add(List.of(new Document(askTemplateQuestionLibrary.getId(), description, questionVector))); + + List similarityList = askTemplateQuestionSimilarityService.lambdaQuery() + .eq(AskTemplateQuestionSimilarity::getLibraryId, askTemplateQuestionLibrary.getId()).list(); + for (AskTemplateQuestionSimilarity askTemplateQuestionSimilarity : similarityList) { + Map questionVectorNest = new QuestionMetadata("2", + askTemplateQuestionSimilarity.getLibraryId(), askTemplateQuestionSimilarity.getId(), + askTemplateQuestionLibrary.getDictId()).toMap(); + redisVectorStore.add(List.of(new Document(askTemplateQuestionSimilarity.getId(), + askTemplateQuestionSimilarity.getSimilarityQuestion(), + questionVectorNest))); + } + saveCustomQuestionResVO.setSaveResult(true); + return saveCustomQuestionResVO; + } + + +} diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/QaKnowledgeManageServiceImpl.java b/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/QaKnowledgeManageServiceImpl.java index 6580d24f..fd109e61 100644 --- a/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/QaKnowledgeManageServiceImpl.java +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/QaKnowledgeManageServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import com.supervision.manage.service.QaKnowledgeManageService; import com.supervision.model.AskTemplateQuestionLibrary; import com.supervision.model.AskTemplateQuestionSimilarity; +import com.supervision.record.QuestionMetadata; import com.supervision.service.AskTemplateQuestionLibraryService; import com.supervision.service.AskTemplateQuestionSimilarityService; import lombok.RequiredArgsConstructor; @@ -28,14 +29,7 @@ public class QaKnowledgeManageServiceImpl implements QaKnowledgeManageService { private final RedisVectorStore redisVectorStore; - public record QuestionMetadata(String type, String libraryQuestionId, String questionId, Long dictId) { - public Map toMap(){ - return Map.of("type", type, - "libraryQuestionId", libraryQuestionId, - "questionId", questionId, - "dictId", String.valueOf(dictId)); - } - } + public void refreshQaKnowledge() { // 修改为手动刷新向量库 List list = askTemplateQuestionLibraryService.list(); diff --git a/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/SimilarLibraryQuestionResVO.java b/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/SimilarLibraryQuestionResVO.java new file mode 100644 index 00000000..968a8c4a --- /dev/null +++ b/virtual-patient-manage/src/main/java/com/supervision/manage/service/impl/SimilarLibraryQuestionResVO.java @@ -0,0 +1,11 @@ +package com.supervision.manage.service.impl; + +import lombok.Data; + +@Data +public class SimilarLibraryQuestionResVO { + + private String libraryQuestion; + + private String dictPath; +} diff --git a/virtual-patient-model/src/main/java/com/supervision/model/AskTemplateQuestionLibrary.java b/virtual-patient-model/src/main/java/com/supervision/model/AskTemplateQuestionLibrary.java index 980b5764..211f3758 100644 --- a/virtual-patient-model/src/main/java/com/supervision/model/AskTemplateQuestionLibrary.java +++ b/virtual-patient-model/src/main/java/com/supervision/model/AskTemplateQuestionLibrary.java @@ -38,11 +38,18 @@ public class AskTemplateQuestionLibrary implements Serializable { */ private String standardQuestion; + /** + * 问题类型 1标准问 2自定义问题 1.3.1新增 + */ + @Schema(description = "问题类型 1标准问 2自定义问题") + private Integer type; + /** * 等价于standardQuestion */ + @Deprecated @TableField(exist = false) - @Schema(description = "等价于standardQuestion v.13之后废弃",deprecated = true) + @Schema(description = "等价于standardQuestion v.13之后废弃", deprecated = true) private String description;