Merge remote-tracking branch 'origin/main'

main
xueqingkun 11 months ago
commit 55eee1978d

@ -3,9 +3,10 @@ package com.supervision.controller;
import cn.hutool.core.util.StrUtil;
import com.supervision.exception.BusinessException;
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 io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -30,15 +31,15 @@ public class AskController {
return askService.roundTalk(roundTalkReqVO);
}
@ApiOperation("多轮对话中用户手动填写参数")
@ApiOperation("多轮对话中用户手动填写参数,可能直接返回结果")
@PostMapping("saveUserParam")
public void saveUserParam(@RequestBody UserParamReqVO paramReqVO) {
askService.saveUserParam(paramReqVO);
public RoundTalkResVO saveUserParam(@RequestBody RoleSetReqVO paramReqVO) {
return askService.saveUserParam(paramReqVO);
}
@ApiOperation("查询多轮对话中用户需要填写的参数")
@GetMapping("queryUserNeedParam")
public Set<String> queryUserNeedParam(String sessionId) {
public RoleSetResVO queryUserNeedParam(String sessionId) {
return askService.queryUserNeedParam(sessionId);
}
}

@ -59,9 +59,14 @@ public class SessionParamDTO {
private Map<String, Integer> entityCountMap;
/**
* ()
* (,,)
*/
private EntityQuestionDTO currentEntity;
/**
* ,key,value
*/
private Map<String,String> talkRecord;
}

@ -0,0 +1,39 @@
package com.supervision.enums;
public enum RetireRoleEnum {
sex("sex", "性别", null),
job("job", "职业", null),
age("age", "年龄", EntityQuestionEnum.),
residentLocation("residentLocation", "户口所在地", EntityQuestionEnum.),
offsiteSocialSecurityInfo("offsiteSocialSecurityInfo", "异地社保转入情况", null),
lastInsuredPlace("lastInsuredPlace", "最后参保地", null),
pensionPaymentPeriod("pensionPaymentPeriod", "养老缴费年限", EntityQuestionEnum.);
private final String code;
private final String name;
/**
*
*/
private final EntityQuestionEnum entityEnum;
RetireRoleEnum(String code, String name, EntityQuestionEnum entityEnum) {
this.code = code;
this.name = name;
this.entityEnum = entityEnum;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public EntityQuestionEnum getEntityEnum() {
return entityEnum;
}
}

@ -10,24 +10,32 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
@Component
public class AnswerQuestionHandler {
public String answerQuestion(String question, List<String> detailList) {
public String answerQuestion(String question, List<String> detailList, Map<String, String> talkRecord) {
List<MessageDTO> messageList = new ArrayList<>();
messageList.add(new MessageDTO("system", "现在你是一个政务事项领域的问答大模型.\n" +
"我现在给一些政务文件的内容,再给你一个问题,请根据给你的文件内容,针对性的对问题进行解答.\n" +
"请严格按照文件内容进行回答,不要有文件内容之外的理解.\n" +
"除了解答的内容,什么其他的都不要说.\n" +
"如果你从文件内容中,没有提取到回答,你就回复:我暂时还不会这个问题哦!"));
"现在有一个问题,根据这个问题我查到了政策内容,并提供了用户问询过程中的对话记录,可供参考;\n" +
"请你根据政策内容回答这个问题,请不要有文件内容之外的内容或加入你自己的话"));
messageList.add(new MessageDTO("assistant", "好的"));
messageList.add(new MessageDTO("user", StrUtil.format("政务文件内容:[{}]", CollUtil.join(detailList, ";"))));
messageList.add(new MessageDTO("assistant", "继续"));
messageList.add(new MessageDTO("user", StrUtil.format("问题:{}", question)));
messageList.add(new MessageDTO("assistant", "继续"));
messageList.add(new MessageDTO("user", StrUtil.format("现在你可以回答了")));
if (CollUtil.isNotEmpty(talkRecord)) {
List<String> record = new ArrayList<>();
for (Map.Entry<String, String> entry : talkRecord.entrySet()) {
record.add("询问用户:" + entry.getKey());
record.add("用户回答:" + entry.getValue());
}
messageList.add(new MessageDTO("user", "下面是用户回答过程中的对话记录,可供参考,并提供更精确的回答:{" + CollUtil.join(record, ";") + "}"));
messageList.add(new MessageDTO("assistant", "继续"));
}
messageList.add(new MessageDTO("user", StrUtil.format("现在你可以回答了,如果你从文件内容中,没有提取到回答,你就回复:我暂时还不会这个问题哦!")));
log.info("answerQuestion的prompt是:{}", JSONUtil.toJsonStr(messageList));
String answer = AiUtil.chatByMessage(messageList);
log.info("answerQuestion的答案是:{}", answer);

@ -1,18 +1,17 @@
package com.supervision.service;
import com.supervision.vo.RoleSetResVO;
import com.supervision.vo.RoundTalkReqVO;
import com.supervision.vo.RoundTalkResVO;
import com.supervision.vo.UserParamReqVO;
import org.springframework.web.bind.annotation.RequestBody;
import com.supervision.vo.RoleSetReqVO;
import java.util.List;
import java.util.Set;
public interface AskService {
RoundTalkResVO roundTalk(RoundTalkReqVO roundTalkReqVO);
void saveUserParam(UserParamReqVO paramReqVO);
RoundTalkResVO saveUserParam(RoleSetReqVO paramReqVO);
Set<String> queryUserNeedParam(String sessionId);
RoleSetResVO queryUserNeedParam(String sessionId);
}

@ -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;
}
}

@ -0,0 +1,20 @@
package com.supervision.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class RoleSetReqVO {
private String sessionId;
/**
* ,keycode,value
*/
private Map<String, String> paramMap;
}

@ -0,0 +1,16 @@
package com.supervision.vo;
import lombok.Data;
import java.util.List;
@Data
public class RoleSetResVO {
private String sessionId;
private List<String> param;
}

@ -1,15 +0,0 @@
package com.supervision.vo;
import com.supervision.dto.roundAsk.UserParamNode;
import lombok.Data;
import java.util.List;
@Data
public class UserParamReqVO {
private String sessionId;
List<UserParamNode> paramList;
}
Loading…
Cancel
Save