增加向量化的库

pull/1/head
liu 11 months ago
parent 7b9fd6d21c
commit dcb2297821

@ -0,0 +1,14 @@
package com.supervision.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "embedding")
public class EmbeddingProperties {
private String url;
}

@ -0,0 +1,18 @@
package com.supervision.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "vector.redis")
public class RedisVectorProperties {
private String uri;
private String indexName;
private String prefix;
}

@ -3,25 +3,26 @@ package com.supervision.config;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.SpringProperties;
import org.springframework.util.Assert;
@Configuration
public class VectorSimilarityConfiguration {
@Bean
public VectorEmbeddingClient vectorEmbeddingClient() {
String embeddingUrl = SpringProperties.getProperty("embeddingUrl");
Assert.notNull(embeddingUrl, "配置文件embeddingUrl未找到");
return new VectorEmbeddingClient(embeddingUrl);
@SuppressWarnings("all")
public VectorEmbeddingClient vectorEmbeddingClient(EmbeddingProperties embeddingProperties) {
Assert.notNull(embeddingProperties.getUrl(), "配置文件embedding:url未找到");
return new VectorEmbeddingClient(embeddingProperties.getUrl());
}
@Bean
public RedisVectorStore redisVectorStore(VectorEmbeddingClient vectorEmbeddingClient) {
String property = SpringProperties.getProperty("spring.ai.vectorstore.redis.uri");
Assert.notNull(property, "配置文件spring.ai.vectorstore.redis.uri未找到");
@SuppressWarnings("all")
public RedisVectorStore redisVectorStore(VectorEmbeddingClient vectorEmbeddingClient, RedisVectorProperties redisVectorProperties) {
Assert.notNull(redisVectorProperties.getUri(), "配置文件vector.redis.uri未找到");
RedisVectorStore.RedisVectorStoreConfig config = RedisVectorStore.RedisVectorStoreConfig.builder()
.withURI(property)
.withURI(redisVectorProperties.getUri())
.withPrefix(redisVectorProperties.getPrefix())
.withIndexName(redisVectorProperties.getIndexName())
// 定义搜索过滤器使用的元数据字段
.withMetadataFields(
// 问题的ID

@ -1,6 +1,7 @@
package com.supervision.util;
import cn.hutool.core.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
@ -29,7 +30,7 @@ public class SpringBeanUtil implements ApplicationContextAware {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
public void setApplicationContext(@NotNull ApplicationContext applicationContext) {
SpringBeanUtil.applicationContext = applicationContext;
}

@ -1,6 +1,5 @@
package com.supervision.controller;
import com.supervision.pojo.vo.TalkResultResVO;
import com.supervision.pojo.vo.TalkVideoReqVO;
import com.supervision.pojo.vo.TalkVideoTtsResultResVO;
import com.supervision.service.AskService;
@ -10,8 +9,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Tag(name = "问诊")
@RestController
@RequestMapping("/ask/")
@ -27,12 +24,6 @@ public class AskController {
}
@Operation(summary = "使用本地视频的形式来做")
@PostMapping("/talkByVideo")
public TalkResultResVO talkByVideo(@RequestBody TalkVideoReqVO talkReqVO) throws IOException {
return askService.talkByVideo(talkReqVO);
}
@Operation(summary = "使用无声视频+语音转文字的形式来做")
@PostMapping("talkByVideoAndTts")
public TalkVideoTtsResultResVO talkByVideoAndTts(@RequestBody TalkVideoReqVO talkReqVO) {

@ -1,19 +0,0 @@
package com.supervision.feign;
import com.supervision.domain.GlobalResult;
import com.supervision.pojo.qaSimilarity.QaSimilarityQuestion;
import com.supervision.pojo.qaSimilarity.QaSimilarityQuestionAnswer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@FeignClient(name = "askQaSimilarityFeignClient", url = "${qaSimilarity}")
public interface AskQaSimilarityFeignClient {
@PostMapping("matchQuestion")
GlobalResult<List<QaSimilarityQuestionAnswer>> askQuestionSimilarityAnswer(@RequestBody QaSimilarityQuestion question);
}

@ -1,18 +1,13 @@
package com.supervision.service;
import com.supervision.pojo.vo.TalkResultResVO;
import com.supervision.pojo.vo.TalkVideoReqVO;
import com.supervision.pojo.vo.TalkVideoTtsResultResVO;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface AskService {
String receiveVoiceFile(MultipartFile file);
TalkResultResVO talkByVideo(TalkVideoReqVO talkReqVO) throws IOException;
TalkVideoTtsResultResVO talkByVideoAndTts(TalkVideoReqVO talkReqVO);

@ -1,30 +1,18 @@
package com.supervision.service.impl;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONUtil;
import com.supervision.domain.GlobalResult;
import com.supervision.exception.BusinessException;
import com.supervision.feign.AskQaSimilarityFeignClient;
import com.supervision.feign.RasaManageFeignClient;
import com.supervision.model.Process;
import com.supervision.model.*;
import com.supervision.pojo.qaSimilarity.QaSimilarityQuestion;
import com.supervision.pojo.qaSimilarity.QaSimilarityQuestionAnswer;
import com.supervision.pojo.vo.TalkResultResVO;
import com.supervision.pojo.vo.TalkVideoReqVO;
import com.supervision.pojo.vo.TalkVideoTtsResultResVO;
import com.supervision.service.*;
import com.supervision.util.AsrUtil;
import com.supervision.util.MinioUtil;
import com.supervision.util.TtsUtil;
import com.supervision.util.UserUtil;
import com.supervision.vo.rasa.RasaTalkVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
@ -33,7 +21,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@ -49,18 +36,6 @@ public class AskServiceImpl implements AskService {
private final AskPatientAnswerService askPatientAnswerService;
private final ConfigPhysicalToolService configPhysicalToolService;
private final ConfigAncillaryItemService configAncillaryItemService;
private final FileResourceService fileResourceService;
private final RasaManageFeignClient rasaManageFeignClient;
private final AskQaSimilarityFeignClient askQaSimilarityFeignClient;
private final CommonDicService commonDicService;
private final AiService aiService;
private final MedicalRecService medicalRecService;
@ -68,8 +43,8 @@ public class AskServiceImpl implements AskService {
private final DiagnosisAiRecordService diagnosisAiRecordService;
private final RedisVectorStore redisVectorStore;
@Value("${qaSimilarityThreshold:0.7}")
private String qaSimilarityThreshold;
@Value("${threshold:0.7}")
private String threshold;
@Override
public String receiveVoiceFile(MultipartFile file) {
@ -104,139 +79,6 @@ public class AskServiceImpl implements AskService {
record.insert();
}
private void setActionRelation(TalkResultResVO talkResultResVO) {
Integer type = talkResultResVO.getType();
String actionId = talkResultResVO.getActionId();
if (StrUtil.isEmpty(actionId) || null == type) {
return;
}
// 体格检查
if (type.equals(2)) {
ConfigPhysicalTool configPhysicalTool = configPhysicalToolService.getById(actionId);
if (null != configPhysicalTool) {
talkResultResVO.setItemName(configPhysicalTool.getToolName());
talkResultResVO.setItemImage(configPhysicalTool.getIconBase64());
talkResultResVO.setRequireLocation(configPhysicalTool.getRequireLocation());
// 生成树的时候,没有ID字段,所以用名称生成md5作为ID
talkResultResVO.setActionType(new MD5().digestHex16(configPhysicalTool.getType()));
}
}
// 辅助检查
if (type.equals(3)) {
ConfigAncillaryItem configAncillaryItem = configAncillaryItemService.getById(actionId);
if (null != configAncillaryItem) {
talkResultResVO.setItemName(configAncillaryItem.getItemName());
// 生成树的时候,没有ID字段,所以用名称生成md5作为ID
talkResultResVO.setActionType(new MD5().digestHex16(configAncillaryItem.getType()));
}
}
}
@Override
public TalkResultResVO talkByVideo(TalkVideoReqVO talkReqVO) {
// 根据processId找到对应的病人
Process process = Optional.ofNullable(processService.getById(talkReqVO.getProcessId())).orElseThrow(() -> new BusinessException("未找到诊疗进程"));
String talkResult = talkQaSimilarity(talkReqVO.getText(), UserUtil.getUser().getId());
// 调用rasa获取文字内容
//String rasaResult = talkRasa(talkReqVO.getText(), UserUtil.getUser().getId());
log.info("调用对话的回复是:{}", talkResult);
TalkResultResVO talkResultResVO = new TalkResultResVO();
// 如果rasa没有识别出来,则返回默认值
if (StrUtil.isBlank(talkResult)) {
AskPatientAnswer medicalRecErrorAnswer = getMedicalRecErrorAnswer(process.getMedicalRecId());
talkResultResVO.setVideoBase64(getAnswerVideoBase64OrDefault(medicalRecErrorAnswer.getAnswerResourceId()));
talkResultResVO.setAnswerMessage(medicalRecErrorAnswer.getAnswer());
saveQaRecord(talkReqVO.getProcessId(), "default", null, talkReqVO.getText(), null, medicalRecErrorAnswer.getAnswer());
talkResultResVO.setType(1);
return talkResultResVO;
}
// 这里校验,rasa回复的结果是不是action
// 这里设置的模板,对于action的动作全部是用ancillary_ | tool_进行标记,详情看生成rasa的yml的代码:RasaServiceImpl.generateDomain
// ancillary_ | tool_
if (talkResult.startsWith("ancillary_") || talkResult.startsWith("tool_")) {
log.info("呼出语句:{}", talkResult);
List<String> actionList = StrUtil.split(talkResult, '_');
if (actionList.size() > 1) {
// 在这里设置为动作
talkResultResVO.setActionId(actionList.get(1));
talkResultResVO.setType("ancillary".equals(actionList.get(0)) ? 3 : 2);
setActionRelation(talkResultResVO);
return talkResultResVO;
}
} else {
AskTemplateQuestionLibrary library = askTemplateQuestionLibraryService.getById(talkResult);
if (ObjectUtil.isEmpty(library)) {
log.info("{}:未从问题库中找到,回答未识别语句", talkResult);
AskPatientAnswer medicalRecErrorAnswer = getMedicalRecErrorAnswer(process.getMedicalRecId());
talkResultResVO.setVideoBase64(getAnswerVideoBase64OrDefault(medicalRecErrorAnswer.getAnswerResourceId()));
talkResultResVO.setAnswerMessage(medicalRecErrorAnswer.getAnswer());
saveQaRecord(talkReqVO.getProcessId(), "default", null, talkReqVO.getText(), null, medicalRecErrorAnswer.getAnswer());
} else {
AskPatientAnswer askPatientAnswer = askPatientAnswerService.lambdaQuery().eq(AskPatientAnswer::getMedicalId, process.getMedicalRecId())
.eq(AskPatientAnswer::getLibraryQuestionId, library.getId()).last("limit 1").one();
if (ObjectUtil.isNotEmpty(askPatientAnswer) && StrUtil.isNotEmpty(askPatientAnswer.getAnswerResourceId())) {
String resText = askPatientAnswer.getAnswer();
log.info("{}:找到了病历配置的回答语句:{},回答内容:{}", talkResult, askPatientAnswer.getId(), resText);
talkResultResVO.setVideoBase64(getAnswerVideoBase64OrDefault(askPatientAnswer.getAnswerResourceId()));
talkResultResVO.setAnswerMessage(askPatientAnswer.getAnswer());
// 保存记录
saveQaRecord(talkReqVO.getProcessId(), "patient", askPatientAnswer.getId(), talkReqVO.getText(), library, resText);
} else {
// 临时解决方案,回答没有,在问题库中内置这个回答
log.info("{}:病历配置,从AskPatientAnswer中未找到回答结果,回复 默认回复", talkResult);
AskPatientAnswer medicalRecDefaultAnswer = getMedicalRecDefaultAnswer(process.getMedicalRecId());
talkResultResVO.setVideoBase64(getAnswerVideoBase64OrDefault(medicalRecDefaultAnswer.getAnswerResourceId()));
talkResultResVO.setAnswerMessage(medicalRecDefaultAnswer.getAnswer());
saveQaRecord(talkReqVO.getProcessId(), "default", null, talkReqVO.getText(), library, medicalRecDefaultAnswer.getAnswer());
}
}
}
talkResultResVO.setType(1);
return talkResultResVO;
}
/**
* rasa,,
*/
@Deprecated
public String talkRasa(String question, String sessionId) {
RasaTalkVo rasaTalkVo = new RasaTalkVo();
rasaTalkVo.setQuestion(question);
rasaTalkVo.setSessionId(sessionId);
// 默认为1
rasaTalkVo.setModelId("1");
try {
GlobalResult<List<String>> talk = rasaManageFeignClient.talk(rasaTalkVo);
log.info("调用rasa对话返回结果:{}", talk);
if (talk.getCode() != 200 || CollUtil.isEmpty(talk.getData())) {
return null;
}
return CollUtil.getFirst(talk.getData());
} catch (Exception e) {
log.error("talkRasa error ", e);
}
return null;
}
public String talkQaSimilarity(String question, String sessionId) {
log.info("开始调用talkQaSimilarity,问题:{}", question);
try {
GlobalResult<List<QaSimilarityQuestionAnswer>> result = askQaSimilarityFeignClient.askQuestionSimilarityAnswer(new QaSimilarityQuestion(question));
log.info("调用talkQaSimilarity结束,问题:{},返回结果:{}", question, JSONUtil.toJsonStr(result));
return CollUtil.getFirst(result.getData()).getMatchQuestionCode();
} catch (Exception e) {
log.error("调用talkQaSimilarity error ", e);
return null;
}
}
public QaSimilarityQuestionAnswer talkRedisVectorWithScore(String question) {
log.info("开始调用talkQaSimilarity,问题:{}", question);
try {
@ -258,47 +100,6 @@ public class AskServiceImpl implements AskService {
}
}
private AskPatientAnswer getMedicalRecErrorAnswer(String medicalRecId) {
//Optional.ofNullable(medicalRecErrorAnswer).orElseGet(() ->new AskPatientAnswer()).getAnswer()
Assert.notEmpty(medicalRecId, "病历id不能为空");
CommonDic commonDic = commonDicService.lambdaQuery().eq(CommonDic::getGroupCode, "AQT").eq(CommonDic::getCode, "system_error").one();
Assert.notNull(commonDic, "字典未配置系统内置异常识别场景");
AskTemplateQuestionLibrary askTemplateQuestionLibrary = askTemplateQuestionLibraryService.lambdaQuery().eq(AskTemplateQuestionLibrary::getDictId, commonDic.getId()).one();
Assert.notNull(askTemplateQuestionLibrary, "知识库未配置系统内置异常识别场景");
return askPatientAnswerService.lambdaQuery().eq(AskPatientAnswer::getMedicalId, medicalRecId).eq(AskPatientAnswer::getLibraryQuestionId, askTemplateQuestionLibrary.getId()).one();
}
private AskPatientAnswer getMedicalRecDefaultAnswer(String medicalRecId) {
//Optional.ofNullable(medicalRecErrorAnswer).orElseGet(() ->new AskPatientAnswer()).getAnswer()
Assert.notEmpty(medicalRecId, "病历id不能为空");
CommonDic commonDic = commonDicService.lambdaQuery().eq(CommonDic::getGroupCode, "AQT").eq(CommonDic::getCode, "system_default").one();
Assert.notNull(commonDic, "字典未配置默认回答场景");
AskTemplateQuestionLibrary askTemplateQuestionLibrary = askTemplateQuestionLibraryService.lambdaQuery().eq(AskTemplateQuestionLibrary::getDictId, commonDic.getId()).one();
Assert.notNull(askTemplateQuestionLibrary, "知识库未配置系统内置异常识别场景");
return askPatientAnswerService.lambdaQuery().eq(AskPatientAnswer::getMedicalId, medicalRecId).eq(AskPatientAnswer::getLibraryQuestionId, askTemplateQuestionLibrary.getId()).one();
}
/**
* base64
*
* @param fileResourceId id
* @return base64
*/
private String getAnswerVideoBase64OrDefault(String fileResourceId) {
FileResource fileResource = fileResourceService.getById(fileResourceId);
try (InputStream inputStream = MinioUtil.download(fileResource.getMinioId())) {
return Base64Encoder.encode(IoUtil.readBytes(inputStream));
} catch (Exception e) {
log.error("获取视频失败", e);
throw new BusinessException("未找到回复视频");
}
}
/**
* 使+
@ -320,8 +121,8 @@ public class AskServiceImpl implements AskService {
saveAiRecord(process.getId(), talkReqVO.getText(), talkVideoTtsResultResVO.getAnswerMessage());
} else {
// 如果阈值过低,也走大模型
double threshold = Double.parseDouble(qaSimilarityThreshold);
if (qaSimilarityQuestionAnswer.getMatchScore() < threshold) {
double thresholdValue = Double.parseDouble(threshold);
if (qaSimilarityQuestionAnswer.getMatchScore() < thresholdValue) {
log.info("{}:匹配到的结果阈值过低,走大模型回答", qaSimilarityQuestionAnswer);
String talk = aiService.talk(talkReqVO.getText(), medicalRec.getMedicalRecordAi());
talkVideoTtsResultResVO.setAnswerMessage(talk);

@ -41,12 +41,6 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false
ai:
vectorstore:
redis:
uri: redis://:123456@192.168.10.137:6380
index: 1
prefix: 'vp:vector:'
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml

@ -5,8 +5,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.supervision.model.AskPatientAnswer;
import com.supervision.model.AskTemplateQuestionLibrary;
import com.supervision.model.FileResource;
import com.supervision.pojo.vo.TalkResultResVO;
import com.supervision.pojo.vo.TalkVideoReqVO;
import com.supervision.service.AskPatientAnswerService;
import com.supervision.service.AskService;
import com.supervision.service.AskTemplateQuestionLibraryService;
@ -99,19 +97,7 @@ public class VideoUpdateTest {
@Autowired
private AskService askService;
@Test
public void talkTest() throws Exception {
TalkVideoReqVO talkVideoReqVO = new TalkVideoReqVO();
talkVideoReqVO.setProcessId("1749312510591934462");
talkVideoReqVO.setText("你哪里不舒服");
TalkResultResVO talkResultResVO = askService.talkByVideo(talkVideoReqVO);
System.out.println(talkResultResVO.getAnswerMessage());
}
@Test
public void talkTest1() throws Exception {

Loading…
Cancel
Save