|
|
|
@ -5,10 +5,10 @@ import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import com.supervision.dto.roundAsk.EntityQuestionDTO;
|
|
|
|
|
import com.supervision.dto.roundAsk.ItemNodeDTO;
|
|
|
|
|
import com.supervision.dto.roundAsk.SessionParamDTO;
|
|
|
|
|
import com.supervision.enums.EntityQuestionEnum;
|
|
|
|
|
import com.supervision.enums.IdentifyIntentEnum;
|
|
|
|
|
import com.supervision.enums.RetireRoleEnum;
|
|
|
|
|
import com.supervision.exception.BusinessException;
|
|
|
|
|
import com.supervision.handler.gpt.AnswerQuestionHandler;
|
|
|
|
|
import com.supervision.handler.gpt.ConditionJudgeHandler;
|
|
|
|
@ -20,14 +20,14 @@ import com.supervision.handler.graph.FindItemNodeHandler;
|
|
|
|
|
import com.supervision.ngbatis.domain.tag.Condition;
|
|
|
|
|
import com.supervision.ngbatis.domain.tag.ItemLeaf;
|
|
|
|
|
import com.supervision.service.AskService;
|
|
|
|
|
import com.supervision.vo.RoleSetResVO;
|
|
|
|
|
import com.supervision.vo.RoundTalkReqVO;
|
|
|
|
|
import com.supervision.vo.RoundTalkResVO;
|
|
|
|
|
import com.supervision.vo.UserParamReqVO;
|
|
|
|
|
import com.supervision.vo.RoleSetReqVO;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
@ -59,7 +59,7 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
|
|
|
|
|
private static final String SESSION_PARAM = "KBQA:ASK:SESSION_PARAM:";
|
|
|
|
|
|
|
|
|
|
private static final String USER_PARAM = "KBQA:ASK:USER_PARAM:";
|
|
|
|
|
private static final String USER_PARAM = "KBQA:ASK:USER_ROLE:";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -79,6 +79,7 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
sessionParamDTO.setOriginalQuestion(roundTalkReqVO.getUserTalk());
|
|
|
|
|
sessionParamDTO.setSessionId(sessionId);
|
|
|
|
|
sessionParamDTO.setAlreadyMatchEntitySet(new HashSet<>());
|
|
|
|
|
sessionParamDTO.setTalkRecord(new HashMap<>());
|
|
|
|
|
redisTemplate.opsForValue().set(SESSION_PARAM + sessionId, sessionParamDTO);
|
|
|
|
|
} else {
|
|
|
|
|
sessionParamDTO = BeanUtil.toBean(cache, SessionParamDTO.class);
|
|
|
|
@ -92,9 +93,9 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
// 识别出来意图之后,再去判断是否识别过实体
|
|
|
|
|
if (CollUtil.isEmpty(sessionParamDTO.getEntityValueByExtract())) {
|
|
|
|
|
// 识别实体(先从图中获取所有的节点名称,然后识别)
|
|
|
|
|
List<String> allItemNode = findItemNodeHandler.findAllItemNode();
|
|
|
|
|
// List<String> allItemNode = findItemNodeHandler.findAllItemNode();
|
|
|
|
|
//List<String> extractValue = itemExtractHandler.itemExtractByPossibleItem(sessionParamDTO.getOriginalQuestion(), allItemNode);
|
|
|
|
|
// 换了另外一种匹配方式
|
|
|
|
|
// 识别问题中,用户可能问的业务是什么
|
|
|
|
|
String extractValue = itemExtractHandler.itemExtractBusiness(sessionParamDTO.getOriginalQuestion());
|
|
|
|
|
sessionParamDTO.setEntityValueByExtract(Collections.singletonList(extractValue));
|
|
|
|
|
// 根据提取的内容,开始在知识图谱中寻找节点(首先找叶子节点,如果叶子节点有数据,直接返回,如果叶子节点没数据,再去找分支节点)
|
|
|
|
@ -146,34 +147,10 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
}
|
|
|
|
|
// 判断待匹配的节点是不是只有一个了,如果有多个,就从路径中选择一个问题问前端
|
|
|
|
|
if (sessionParamDTO.getWaitMatchItemLeafMap().size() != 1) {
|
|
|
|
|
|
|
|
|
|
if (ObjectUtil.isNotEmpty(sessionParamDTO.getCurrentEntity())) {
|
|
|
|
|
// 如果当前对话实体不为空,说明当前问答就是上一个问题的回复,这个时候,就去GPT中进行匹配
|
|
|
|
|
// 遍历所有的path,找到回答
|
|
|
|
|
Map<String, List<List<Condition>>> conditionPathMap = sessionParamDTO.getConditionPathMap();
|
|
|
|
|
Set<String> possibleAnswerSet = new HashSet<>();
|
|
|
|
|
for (Map.Entry<String, List<List<Condition>>> entry : conditionPathMap.entrySet()) {
|
|
|
|
|
List<List<Condition>> conditionPath = entry.getValue();
|
|
|
|
|
// 遍历所有的路径,找到回答
|
|
|
|
|
for (List<Condition> conditions : conditionPath) {
|
|
|
|
|
// 遍历所有的条件,找到回答
|
|
|
|
|
for (Condition condition : conditions) {
|
|
|
|
|
// 如果当前对话实体和条件中的实体类型相同,就添加到possibleAnswerSet中
|
|
|
|
|
if (sessionParamDTO.getCurrentEntity().getCurrentEntityType().equals(condition.getEntityType())) {
|
|
|
|
|
possibleAnswerSet.add(condition.getCondition());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Set<String> judgeResultSet = conditionJudgeHandler.newConditionJudge(sessionParamDTO.getCurrentEntity().getCurrentQuestion(),
|
|
|
|
|
possibleAnswerSet,
|
|
|
|
|
roundTalkReqVO.getUserTalk(),
|
|
|
|
|
sessionParamDTO.getCurrentEntity().getCurrentEntityType());
|
|
|
|
|
// 筛选路径,如果某个路径的结果不在比较结果中,说明这个结果不对,排除这个路径
|
|
|
|
|
pathFilterByJudgeResult(sessionParamDTO.getCurrentEntity().getCurrentEntityType(), judgeResultSet, sessionParamDTO);
|
|
|
|
|
filterNotMatchNode(sessionParamDTO);
|
|
|
|
|
// 加到已匹配的实体类型,下次不再匹配
|
|
|
|
|
sessionParamDTO.getAlreadyMatchEntitySet().add(sessionParamDTO.getCurrentEntity().getCurrentEntityType());
|
|
|
|
|
redisTemplate.opsForValue().set(SESSION_PARAM + sessionId, sessionParamDTO);
|
|
|
|
|
// 如果当前对话实体不为空,说明当前问答就是上一个问题的回复,这个时候,就去GPT中进行匹配并排除路径
|
|
|
|
|
filterPath(sessionParamDTO, roundTalkReqVO.getUserTalk());
|
|
|
|
|
// 如果排除后只剩一个了,这时跳出多轮问答
|
|
|
|
|
if (sessionParamDTO.getWaitMatchItemLeafMap().size() == 1) {
|
|
|
|
|
sessionParamDTO.setMatchItemLeaf(sessionParamDTO.getWaitMatchItemLeafMap().values().iterator().next());
|
|
|
|
@ -202,6 +179,13 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
}
|
|
|
|
|
// 走到这里,说明就只有一个节点了,那么就可以进行下一步了
|
|
|
|
|
log.info("走到这里,说明找到了匹配的节点,开始根据用户的意图生成");
|
|
|
|
|
return afterMatchReturnAnswer(sessionParamDTO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 匹配完成之后,返回结果
|
|
|
|
|
*/
|
|
|
|
|
private RoundTalkResVO afterMatchReturnAnswer(SessionParamDTO sessionParamDTO) {
|
|
|
|
|
String intent = sessionParamDTO.getIntent();
|
|
|
|
|
ItemLeaf matchItemLeaf = sessionParamDTO.getMatchItemLeaf();
|
|
|
|
|
// 根据用户的意图找到对应的节点
|
|
|
|
@ -212,14 +196,54 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
// 根据意图和节点,找到对应的结果
|
|
|
|
|
List<String> itemDetail = findItemDetailHandler.findItemDetail(matchItemLeaf.getVid(), intentEnum.getTagType(), intentEnum.getEdgeType());
|
|
|
|
|
if (CollUtil.isEmpty(itemDetail)) {
|
|
|
|
|
return RoundTalkResVO.builder().sessionId(sessionId).replyQuestion("暂不支持该意图的问答").build();
|
|
|
|
|
return RoundTalkResVO.builder().sessionId(sessionParamDTO.getSessionId()).replyQuestion("暂不支持该意图的问答").build();
|
|
|
|
|
}
|
|
|
|
|
// 提交GPT,问问题的答案
|
|
|
|
|
String answer = answerQuestionHandler.answerQuestion(sessionParamDTO.getOriginalQuestion(), itemDetail);
|
|
|
|
|
String answer = answerQuestionHandler.answerQuestion(sessionParamDTO.getOriginalQuestion(), itemDetail, sessionParamDTO.getTalkRecord());
|
|
|
|
|
if (StrUtil.isBlank(answer)) {
|
|
|
|
|
return RoundTalkResVO.builder().sessionId(sessionId).replyQuestion("暂时还不会回答这个问题哦").build();
|
|
|
|
|
return RoundTalkResVO.builder().sessionId(sessionParamDTO.getSessionId()).replyQuestion("暂时还不会回答这个问题哦").build();
|
|
|
|
|
}
|
|
|
|
|
return RoundTalkResVO.builder().sessionId(sessionId).replyQuestion(answer).build();
|
|
|
|
|
// 清空问话实体
|
|
|
|
|
sessionParamDTO.setCurrentEntity(null);
|
|
|
|
|
redisTemplate.opsForValue().set(SESSION_PARAM + sessionParamDTO.getSessionId(), sessionParamDTO);
|
|
|
|
|
return RoundTalkResVO.builder().sessionId(sessionParamDTO.getSessionId()).replyQuestion(answer).build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void filterPath(SessionParamDTO sessionParamDTO, String userTalk) {
|
|
|
|
|
// 遍历所有的path,找到回答
|
|
|
|
|
Set<String> possibleAnswerSet = findPossibleAnswerSet(sessionParamDTO);
|
|
|
|
|
Set<String> judgeResultSet = conditionJudgeHandler.newConditionJudge(sessionParamDTO.getCurrentEntity().getCurrentQuestion(),
|
|
|
|
|
possibleAnswerSet,
|
|
|
|
|
userTalk,
|
|
|
|
|
sessionParamDTO.getCurrentEntity().getCurrentEntityType());
|
|
|
|
|
// 筛选路径,如果某个路径的结果不在比较结果中,说明这个结果不对,排除这个路径
|
|
|
|
|
pathFilterByJudgeResult(sessionParamDTO.getCurrentEntity().getCurrentEntityType(), judgeResultSet, sessionParamDTO);
|
|
|
|
|
filterNotMatchNode(sessionParamDTO);
|
|
|
|
|
// 加到已匹配的实体类型,下次不再匹配
|
|
|
|
|
sessionParamDTO.getAlreadyMatchEntitySet().add(sessionParamDTO.getCurrentEntity().getCurrentEntityType());
|
|
|
|
|
// 保存用户的对话记录
|
|
|
|
|
sessionParamDTO.getTalkRecord().put(sessionParamDTO.getCurrentEntity().getCurrentEntityType(), userTalk);
|
|
|
|
|
// 缓存到Redis中
|
|
|
|
|
redisTemplate.opsForValue().set(SESSION_PARAM + sessionParamDTO.getSessionId(), sessionParamDTO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Set<String> findPossibleAnswerSet(SessionParamDTO sessionParamDTO) {
|
|
|
|
|
Map<String, List<List<Condition>>> conditionPathMap = sessionParamDTO.getConditionPathMap();
|
|
|
|
|
Set<String> possibleAnswerSet = new HashSet<>();
|
|
|
|
|
for (Map.Entry<String, List<List<Condition>>> entry : conditionPathMap.entrySet()) {
|
|
|
|
|
List<List<Condition>> conditionPath = entry.getValue();
|
|
|
|
|
// 遍历所有的路径,找到回答
|
|
|
|
|
for (List<Condition> conditions : conditionPath) {
|
|
|
|
|
// 遍历所有的条件,找到回答
|
|
|
|
|
for (Condition condition : conditions) {
|
|
|
|
|
// 如果当前对话实体和条件中的实体类型相同,就添加到possibleAnswerSet中
|
|
|
|
|
if (sessionParamDTO.getCurrentEntity().getCurrentEntityType().equals(condition.getEntityType())) {
|
|
|
|
|
possibleAnswerSet.add(condition.getCondition());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return possibleAnswerSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, Integer> countCondition(SessionParamDTO sessionParamDTO) {
|
|
|
|
@ -260,12 +284,10 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemove) {
|
|
|
|
|
toRemove.add(conditions);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 移除所有需要移除的路径
|
|
|
|
|
conditionPath.removeAll(toRemove);
|
|
|
|
|
}
|
|
|
|
@ -296,22 +318,45 @@ public class AskServiceImpl implements AskService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void saveUserParam(UserParamReqVO paramReqVO) {
|
|
|
|
|
public RoundTalkResVO saveUserParam(RoleSetReqVO paramReqVO) {
|
|
|
|
|
// 缓存到Redis中
|
|
|
|
|
if (CollUtil.isNotEmpty(paramReqVO.getParamList()) && StrUtil.isNotBlank(paramReqVO.getSessionId())) {
|
|
|
|
|
redisTemplate.opsForValue().set(USER_PARAM + paramReqVO.getSessionId(), paramReqVO.getParamList());
|
|
|
|
|
if (CollUtil.isNotEmpty(paramReqVO.getParamMap()) && StrUtil.isNotBlank(paramReqVO.getSessionId())) {
|
|
|
|
|
redisTemplate.opsForValue().set(USER_PARAM + paramReqVO.getSessionId(), paramReqVO.getParamMap());
|
|
|
|
|
// 这里获取session进行筛选,将不匹配的路径移除掉
|
|
|
|
|
Object cache = redisTemplate.opsForValue().get(SESSION_PARAM + paramReqVO.getSessionId());
|
|
|
|
|
Optional.ofNullable(cache).orElseThrow(() -> new BusinessException("未找到的会话ID"));
|
|
|
|
|
SessionParamDTO sessionParamDTO = BeanUtil.toBean(cache, SessionParamDTO.class);
|
|
|
|
|
for (Map.Entry<String, String> entry : paramReqVO.getParamMap().entrySet()) {
|
|
|
|
|
String key = entry.getKey();
|
|
|
|
|
// 去枚举里面去找
|
|
|
|
|
RetireRoleEnum roleEnum = Arrays.stream(RetireRoleEnum.values()).filter(e -> e.getCode().equals(key)).findFirst().orElseThrow(() -> new BusinessException("未找到的参数"));
|
|
|
|
|
// 如果匹配的实体不为空,就去校验是否存在该实体一致的判断对象
|
|
|
|
|
if (null != roleEnum.getEntityEnum()) {
|
|
|
|
|
Map<String, Integer> entityCountMap = sessionParamDTO.getEntityCountMap();
|
|
|
|
|
// 如果包含,就去尝试排除路径
|
|
|
|
|
if (entityCountMap.containsKey(roleEnum.getEntityEnum().getEntityType())) {
|
|
|
|
|
filterPath(sessionParamDTO, entry.getValue());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 在这里,判断是不是只生一个匹配的了,如果只剩一个匹配的了,就直接回复了,不需要再问了
|
|
|
|
|
// 如果排除后只剩一个了,这时跳出多轮问答
|
|
|
|
|
if (sessionParamDTO.getWaitMatchItemLeafMap().size() == 1) {
|
|
|
|
|
sessionParamDTO.setMatchItemLeaf(sessionParamDTO.getWaitMatchItemLeafMap().values().iterator().next());
|
|
|
|
|
redisTemplate.opsForValue().set(SESSION_PARAM + paramReqVO.getSessionId(), sessionParamDTO);
|
|
|
|
|
return afterMatchReturnAnswer(sessionParamDTO);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Set<String> queryUserNeedParam(String sessionId) {
|
|
|
|
|
// 首先根据session获取需要判断的条件
|
|
|
|
|
Object sessionCache = redisTemplate.opsForValue().get(SESSION_PARAM + sessionId);
|
|
|
|
|
SessionParamDTO sessionParamDTO = BeanUtil.toBean(sessionCache, SessionParamDTO.class);
|
|
|
|
|
// 然后获取里面的数据
|
|
|
|
|
return sessionParamDTO.getEntityCountMap().keySet();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public RoleSetResVO queryUserNeedParam(String sessionId) {
|
|
|
|
|
// 暂时是写死的退休的事项列表,后面再调整优化根据事项修改逻辑
|
|
|
|
|
List<String> itemList = Arrays.stream(RetireRoleEnum.values()).map(RetireRoleEnum::getCode).collect(Collectors.toList());
|
|
|
|
|
RoleSetResVO roleSetResVO = new RoleSetResVO();
|
|
|
|
|
roleSetResVO.setSessionId(sessionId);
|
|
|
|
|
roleSetResVO.setParam(itemList);
|
|
|
|
|
return roleSetResVO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|