一、由于业务场景需要,面试分为两种模式:数智人播报面试,语音合成面试,后续会陆续衍生出其他形式的面试
所以针对面试行为进行统一整合
二、
定义面试模式枚举
/**
* 模式面试枚举
*/
public enum InterviewModeEnum {
/**
* 数智人面试模式
*/
VIRTUAL_HUMAN_INTERVIEW("virtualHumanInterview", "数智人面试模式"),
/**
* 文本播报面试模式
*/
TEXT_SPEECH_INTERVIEW("textSpeechInterview", "文本播报面试模式");
private String value;
private String description;
InterviewModeEnum(String value, String description) {
this.value = value;
this.description = description;
}
public String getValue() {
return value;
}
public String getDescription() {
return description;
}
/**
* 获取枚举对象
*
* @param value 枚举值
* @return 枚举
*/
public static InterviewModeEnum getEnum(String value) {
return getEnumMap().get(value);
}
/**
* 获取值与枚举对象Map
*
* @return {@code Map<String, StatisticDimEnum>} 值与枚举对象Map
*/
public static Map<String, InterviewModeEnum> getEnumMap() {
return Arrays.stream(InterviewModeEnum.values())
.collect(Collectors.toMap(InterviewModeEnum::getValue, InterviewModeEnum -> InterviewModeEnum));
}
}
定义面试相关上下文进行参数传递
/**
* 面试上下文
*/
@Data
public class InterviewContext {
/**
* 面试模式
*
* @see InterviewModeEnum
*/
private InterviewModeEnum interviewMode;
/**
* 用户id
*/
private Integer userId;
/**
* 用户Token
*/
private String userToken;
/**
* 用户回答
*/
private String userAnswer;
/**
* 版本
*/
private Integer version;
/**
* asr DTO
*/
private TencentConnectConfigDTO asrConnectConfigDTO;
/**
* 问题DTO
*/
private AiInterviewQuestionDTO questionDTO;
}
数智人模式面试上下文
/**
* 数智人面试上下文
*/
@Data
public class VirtualHumanInterviewContext extends InterviewContext {
/**
*
*/
private VhNewSessionResultDTO newSessionResultDTO;
/**
*
*/
private InterviewStatusDTO interviewStatusDTO;
}
文本播报模式面试上下文
/**
* 文本播报面试上下文
*/
@Data
public class TextSpeechInterviewContext extends InterviewContext {
/**
* tts DTO
*/
private TencentConnectConfigDTO ttsConnectConfigDTO;
/**
* 问题id
*/
private Integer questionId;
/**
* 总结集合
*/
List<InterviewShowContentItemVO> summaryContents;
}
三、定义面试相关操作接口
public interface InterviewManager<T extends InterviewContext> {
/**
* 创建面试房间
*
* @param context
*/
void createInterviewRoom(T context);
/**
* 开始面试
*
* @param context
*/
void startInterview(T context);
/**
* 检查面试状态
* @param context
*/
void checkInterviewStatus(T context);
/**
* 面试对话
*
* @param context
*/
void talk(T context);
/**
* 离开面试房间
*
* @param context
*/
void leaveInterviewRoom(T context);
/**
* 面试总结
* @param context
*/
void summaryInterview(T context);
}
定义面试接口方法模板
**
* 面试模板
*/
@Slf4j
public abstract class InterviewManagerTemplate<T extends InterviewContext> implements InterviewManager<T> {
@Override
public void createInterviewRoom(T context) {
//执行创建面试间
toCreateInterviewRoom(context);
}
@Override
public void startInterview(T context) {
toStartInterview(context);
}
@Override
public void checkInterviewStatus(T context) {
toCheckInterviewStatus(context);
}
@Override
public void talk(T context) {
toTalk(context);
}
@Override
public void leaveInterviewRoom(T context) {
toLeaveInterviewRoom(context);
}
@Override
public void summaryInterview(T context) {
toSummaryInterview(context);
}
/**
* 执行创建面时间
*
* @param context
*/
protected abstract void toCreateInterviewRoom(T context);
/**
* 执行开始面试
*
* @param context
*/
protected abstract void toStartInterview(T context);
/**
* 执行对话
*
* @param context
*/
protected abstract void toTalk(T context);
/**
* 执行检查状态
*
* @param context
*/
protected abstract void toCheckInterviewStatus(T context);
/**
* 执行离开面试间
*
* @param context
*/
protected abstract void toLeaveInterviewRoom(T context);
/**
* 执行面试总结
*
* @param context
*/
protected abstract void toSummaryInterview(T context);
}
定义抽象面试公共基类
/**
* 抽象公用基类
*/
@Slf4j
public abstract class AbstractInterviewManager<T extends InterviewContext> extends InterviewManagerTemplate<T> {
@Autowired
private BotUtil botUtil;
@Autowired
private InterviewSessionManager interviewSessionManager;
@Autowired
private AiInterviewAssembler aiInterviewAssembler;
@Autowired
private AiInterviewService aiInterviewService;
@Autowired
private InterviewStatusManager interviewStatusManager;
@Autowired
private AiInterviewQuestHandler aiInterviewQuestHandler;
/**
* 面试botId
*/
@Value("${ai-bot.interview-bot-id}")
private String BOT_ID;
/**
* 面试数值人prompt
*/
@Value("${ai-bot.interview-prompt-id}")
private Integer PROMPT_ID;
@Override
protected void toSummaryInterview(T context) {
log.info("AbstractInterviewManager.toSummaryInterview-->context:{}", context);
}
@Override
protected void toCheckInterviewStatus(T context) {
log.info("AbstractInterviewManager.toCheckInterviewStatus-->context:{}", context);
}
/**
* 初始化会话bot
*/
protected String initChatBot(Integer userId, AiInterviewQuestionDTO questionDTO) {
log.info("AbstractInterviewManager.initChatBot-->userId:{},questionDTO:{}", userId, questionDTO);
String chatId = null;
try {
JSONObject params = new JSONObject();
params.put(TencentConstants.QUESTION, questionDTO.getQuestion());
List<QuestionInfo> otherQuestion = questionDTO.getOtherQuestion();
if (CollectionUtils.isNotEmpty(otherQuestion)) {
StringBuilder sb = new StringBuilder();
for (int i = Constants.ONE; i <= otherQuestion.size(); i++) {
sb.append("问题");
sb.append(i);
sb.append(StringPool.COLON);
sb.append(otherQuestion.get(i - Constants.ONE).getQuestion());
sb.append(Constants.SEPETR);
}
params.put(TencentConstants.QUESTION_B, sb.toString());
}
log.info("AbstractInterviewManager.initChatBot-->params:{}", params);
chatId = botUtil.changePrompt(BOT_ID, userId, PROMPT_ID, params);
} catch (Exception e) {
log.error("AbstractInterviewManager.initChatBot-->", e);
}
log.info("AbstractInterviewManager.initChatBot-->chatId:{}", chatId);
return chatId;
}
/**
* 发送会话到bot
*
* @param userId
* @param content
* @param token
* @return
*/
protected String sendAnswerToBot(AiInterviewQuestionDTO questionDTO, Integer userId, String content, String token) {
String botMessage = null;
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
try {
String chatId = sessionDTO.getBotChatId();
log.info("AbstractInterviewManager.sendAnswerToBot-->start!content:{}", content);
//调用gtp回答内容
botMessage = botUtil.getBotContent(userId, BOT_ID, content, token, chatId);
log.info("AbstractInterviewManager.sendAnswerToBot-->end!botMessage:{}", botMessage);
} catch (Exception e) {
log.error("AbstractInterviewManager.sendAnswerToBot-->", e);
}
//数智人的回答表示追问
this.logRecord(questionDTO.getResumeId(), userId, questionDTO.getQuestionId(), AiInterviewChatRoleEnum.GPT.getCode(), AiInterviewChatTypeEnum.PROBE.getCode(), botMessage);
return botMessage;
}
/**
* getAndCheckInterviewSession
*
* @param userId
* @return
*/
protected InterviewSessionDTO getAndCheckInterviewSession(Integer userId) {
InterviewSessionDTO interviewSessionDTO = interviewSessionManager.get(userId);
if (ObjectUtils.isEmpty(interviewSessionDTO)) {
log.error("AbstractInterviewManager.getAndCheckInterviewSession-->interviewSessionDTO is empty!");
throw new BusinessException(HttpResultEnum.PRODUCT_STATE_ERROR);
}
log.info("AbstractInterviewManager.getAndCheckInterviewSession-->interviewSessionDTO:{}", interviewSessionDTO);
return interviewSessionDTO;
}
/**
* getInterviewStatus
*
* @param userId
* @return
*/
protected InterviewStatusDTO getInterviewStatus(Integer userId) {
InterviewStatusDTO interviewStatusDTO = interviewStatusManager.get(userId);
if (ObjectUtils.isEmpty(interviewStatusDTO)) {
log.error("AbstractInterviewManager.getInterviewStatus-->interviewStatusDTO is empty!");
throw new BusinessException(HttpResultEnum.PRODUCT_STATE_ERROR);
}
return interviewStatusDTO;
}
/**
* submitCreateGptReport
*
* @param userId
* @param userToken
* @param questionId
* @param isFinish
*/
protected void submitCreateGptReport(Integer userId, String userToken, Integer questionId, Boolean isFinish, Integer version) {
try {
log.info("AbstractInterviewManager.submitCreateGptReport-->userId:{},questionId:{},isFinish:{},version:{}", userId, questionId, isFinish, version);
//生成报告
aiInterviewQuestHandler.createGptReport(userId, userToken, questionId, isFinish, version);
} catch (Exception e) {
log.error("AbstractInterviewManager.submitCreateGptReport-->", e);
}
}
/**
* getQuestionShowContentItemVO
*
* @param resumeId
* @param questionInfo
* @return
*/
protected InterviewShowContentItemVO getQuestionShowContentItemVO(Integer resumeId, QuestionInfo questionInfo) {
InterviewShowContentItemVO contentItemVO = null;
Integer questionId = questionInfo.getQuestionId();
List<AiInterviewRecord> suggestionList = aiInterviewService.getSuggestion(resumeId, questionId);
if (CollectionUtils.isEmpty(suggestionList)) {
log.warn("AbstractInterviewManager.getQuestionShowContentItemVO-->suggestionDTOS is empty!");
return contentItemVO;
}
String suggestion = null;
String advantage = null;
String summary = null;
for (AiInterviewRecord suggestionItem : suggestionList) {
AiInterviewChatTypeEnum chatType = AiInterviewChatTypeEnum.parse(suggestionItem.getChatType());
switch (chatType) {
case SUGGESTION:
suggestion = suggestionItem.getTextContent();
break;
case ADVANTAGE:
advantage = suggestionItem.getTextContent();
break;
case SUMMARY:
summary = suggestionItem.getTextContent();
break;
}
}
contentItemVO = aiInterviewAssembler.buildInterviewShowContentItemVO(questionId, questionInfo.getQuestion(), suggestion, advantage, summary);
log.info("AbstractInterviewManager.getQuestionShowContentItemVO-->contentItemVO:{}", contentItemVO);
return contentItemVO;
}
/**
* 尝试获取用户问题
*
* @param userId
* @return
*/
protected AiInterviewQuestionDTO tryQueryUserGptQuestion(Integer userId) {
AiInterviewQuestionDTO questionDTO = null;
Integer times = 0;
try {
do {
questionDTO = aiInterviewService.queryUserGptQuestion(userId);
if (ObjectUtils.isNotEmpty(questionDTO) || times > 8) {
log.info("AbstractInterviewManager.tryQueryUserGptQuestion-->questionDTO:{},times:{}", questionDTO, times);
break;
}
Thread.sleep(1000);
times++;
} while (true);
} catch (Exception e) {
log.error("AbstractInterviewManager.tryQueryUserGptQuestion-->", e);
}
return questionDTO;
}
/**
* 会话记录
*
* @param interviewId
* @param userId
* @param questionId
* @param chatRole
* @param chatType
* @param textContent
*/
protected void logRecord(Integer interviewId, Integer userId, Integer questionId, Integer chatRole, Integer chatType, String textContent) {
try {
AiInterviewRecordRequest recordRequest = aiInterviewAssembler.buildAiInterviewRecordRequest(interviewId, userId, questionId, chatRole, chatType, textContent);
log.info("AbstractInterviewManager.logRecord-->recordRequest:{}", recordRequest);
aiInterviewService.record(recordRequest);
} catch (Exception e) {
log.error("AbstractInterviewManager.talkRecord-->", e);
}
}
}
具体面试模式实现类
数智人模式执行器
/**
* 数智人面试执行器
*/
@Slf4j
@Component("virtualHumanInterviewActuator")
public class VirtualHumanInterviewActuator extends AbstractInterviewManager<VirtualHumanInterviewContext> {
@Autowired
private VirtualHumanPerformer virtualHumanPerformer;
@Autowired
private AutomaticSpeechRecognitionPerformer automaticSpeechRecognitionPerformer;
@Autowired
private InterviewStatusManager interviewStatusManager;
@Autowired
private InterviewSessionManager interviewSessionManager;
@Autowired
private InterviewSessionUserRelaManger interviewSessionUserRelaManger;
@Autowired
private InterviewShowContentManager interviewShowContentManager;
@Autowired
private AiInterviewAssembler aiInterviewAssembler;
@Autowired
private AiInterviewService aiInterviewService;
@Autowired
private Executor interviewSchedulerExecutor;
@Autowired
private InterviewStatusChecker answerOverChecker;
@Autowired
private InterviewStatusChecker interviewOverChecker;
@Override
protected void toCreateInterviewRoom(VirtualHumanInterviewContext context) {
Integer userId = context.getUserId();
VhNewSessionResultDTO newSessionResultDTO = virtualHumanPerformer.newSession();
if (ObjectUtils.isEmpty(newSessionResultDTO)) {
log.warn("VirtualHumanInterviewActuator.toCreateInterviewRoom-->newSessionResultDTO is empty!");
newSessionResultDTO = new VhNewSessionResultDTO();
}
String sessionId = newSessionResultDTO.getSessionId();
//websocket监听数智人
virtualHumanPerformer.commandChannelWebsocket(sessionId);
InterviewSessionDTO interviewSessionDTO = aiInterviewAssembler.buildInterviewSessionDTO(userId, context.getInterviewMode().getValue(), sessionId);
//维护用户会话缓存
interviewSessionManager.update(userId, interviewSessionDTO);
//维护会话与用户的关系缓存
interviewSessionUserRelaManger.update(sessionId, userId);
TencentConnectConfigDTO connectConfigDTO = automaticSpeechRecognitionPerformer.generalAsrWebSocketUrl();
context.setNewSessionResultDTO(newSessionResultDTO);
context.setAsrConnectConfigDTO(connectConfigDTO);
}
@Override
protected void toStartInterview(VirtualHumanInterviewContext context) {
Integer userId = context.getUserId();
//step 1/init interviewStatus
InterviewStatusDTO interviewStatusDTO = aiInterviewAssembler.buildInterviewStatusDTO(userId, false, true, true, false, false);
interviewStatusManager.update(userId, interviewStatusDTO);
//初始化展示内容信息
InterviewShowContentVO contentVO = aiInterviewAssembler.buildInterviewShowContentVO(userId, Lists.newLinkedList(), Boolean.FALSE);
interviewShowContentManager.update(userId, contentVO);
//开始会话
this.startVhSession(userId);
//发送开场白
this.sendCommandToVh(userId, TencentConstants.WELCOME_WORD);
AiInterviewQuestionDTO questionDTO = this.askNextQuestion(userId, context.getUserToken(), true, context.getVersion());
context.setQuestionDTO(questionDTO);
}
@Override
protected void toTalk(VirtualHumanInterviewContext context) {
Integer userId = context.getUserId();
//获取用户回答
String userAnswer = context.getUserAnswer();
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
AiInterviewQuestionDTO sessionQuestionDTO = ObjectUtils.isEmpty(sessionDTO.getQuestionDTO()) ? new AiInterviewQuestionDTO() : sessionDTO.getQuestionDTO();
Integer chatType = AiInterviewChatTypeEnum.DIALOGUE.getCode();
//为空表示为追问回答
if (ObjectUtils.isEmpty(sessionQuestionDTO.getIsProbe())) {
chatType = AiInterviewChatTypeEnum.PROBE.getCode();
}
//记录用户对话
this.logRecord(sessionQuestionDTO.getResumeId(), userId, sessionQuestionDTO.getQuestionId(), AiInterviewChatRoleEnum.USER.getCode(), chatType, userAnswer);
if (BooleanUtils.isTrue(sessionQuestionDTO.getIsProbe())) {
//需要追问
//调用gtp回答内容
InterviewStatusDTO enterStatus = this.getInterviewStatus(userId);
enterStatus.setIsGtpAnswerOver(false);
interviewStatusManager.update(userId, enterStatus);
String botMessage = this.sendAnswerToBot(sessionQuestionDTO, userId, userAnswer, context.getUserToken());
InterviewStatusDTO exitStatus = this.getInterviewStatus(userId);
exitStatus.setIsGtpAnswerOver(true);
interviewStatusManager.update(userId, exitStatus);
//发送数智人播报,数智人播报需去除特殊符号
this.sendCommandToVh(userId, new String(botMessage));
//追问结束
sessionQuestionDTO.setQuestion(botMessage);
//维护为null,用户回答后区分为追问回答
sessionQuestionDTO.setIsProbe(null);
sessionDTO.setQuestionDTO(sessionQuestionDTO);
interviewSessionManager.update(userId, sessionDTO);
context.setQuestionDTO(sessionQuestionDTO);
} else {
//下一个问题
AiInterviewQuestionDTO questionDTO = this.askNextQuestion(userId, context.getUserToken(), false, context.getVersion());
context.setQuestionDTO(questionDTO);
}
}
@Override
protected void toCheckInterviewStatus(VirtualHumanInterviewContext context) {
Integer userId = context.getUserId();
InterviewStatusDTO interviewStatusDTO = this.getInterviewStatus(userId);
try {
Boolean isInterviewOver = interviewOverChecker.check(userId);
Boolean isAnswerOver = answerOverChecker.check(userId);
interviewStatusDTO.setIsAnswerOver(isAnswerOver);
//系统验证结果设置
interviewStatusDTO.setIsInterviewOver(isInterviewOver);
} catch (Exception e) {
log.error("VirtualHumanInterviewActuator.checkInterviewStatus-->", e);
}
context.setInterviewStatusDTO(interviewStatusDTO);
}
@Override
protected void toLeaveInterviewRoom(VirtualHumanInterviewContext context) {
this.releaseSource(context.getUserId());
}
/**
* 发送数智人开始说话
*
* @param message
*/
private void sendCommandToVh(Integer userId, String message) {
if (ObjectUtils.isEmpty(message)) {
log.warn("VirtualHumanInterviewActuator.sendCommandToVh-->message is empty!");
return;
}
message = message.replaceAll(TencentConstants.QUESTION_END_TAG, "");
message = message.replaceAll(TencentConstants.QUESTION_LINE_TAG, "");
message = message.replaceAll(TencentConstants.QUESTION_LINE_END_TAG, "");
virtualHumanPerformer.trySendCommand(userId, message);
}
/**
* 开启会话
*
* @param userId
*/
private void startVhSession(Integer userId) {
InterviewSessionDTO interviewSessionDTO = this.getAndCheckInterviewSession(userId);
virtualHumanPerformer.startSession(interviewSessionDTO.getVhSessionId());
}
/**
* 提问下一个问题
*
* @param userId
* @param userToken
* @param isFirst 是否未第一次提问
* @return
*/
private AiInterviewQuestionDTO askNextQuestion(Integer userId, String userToken, Boolean isFirst, Integer version) {
AiInterviewQuestionDTO questionDTO = aiInterviewService.queryUserGptQuestion(userId);
log.info("VirtualHumanInterviewActuator.askNextQuestion-->userId:{},isFirst:{},questionDTO:{},version:{}", userId, isFirst, questionDTO, version);
if (isFirst) {
//获取简历id对应面试id
Integer resumeId = ObjectUtils.isNotEmpty(questionDTO) ? questionDTO.getResumeId() : null;
this.logRecord(resumeId, userId, null, null, AiInterviewChatTypeEnum.BEGIN.getCode(), StringUtils.EMPTY);
}
String question = null;
Integer resumeId = null;
Boolean isFinish = false;
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
AiInterviewQuestionDTO lastQuestionDTO = sessionDTO.getQuestionDTO();
if (ObjectUtils.isNotEmpty(questionDTO)) {
//维护上下文参数信息
question = questionDTO.getQuestion();
resumeId = questionDTO.getResumeId();
//记录问题
this.logRecord(questionDTO.getResumeId(), userId, questionDTO.getQuestionId(), AiInterviewChatRoleEnum.GPT.getCode(), AiInterviewChatTypeEnum.DIALOGUE.getCode(), question);
sessionDTO.setQuestionDTO(questionDTO);
if (BooleanUtils.isTrue(questionDTO.getIsProbe())) {
//初始化bot信息
String chatId = this.initChatBot(userId, questionDTO);
sessionDTO.setBotChatId(chatId);
}
interviewSessionManager.update(userId, sessionDTO);
} else {
isFinish = true;
}
if (ObjectUtils.isEmpty(resumeId) && ObjectUtils.isNotEmpty(lastQuestionDTO)) {
resumeId = lastQuestionDTO.getResumeId();
}
if (isFinish) {
//面试结束,更新对应状态
InterviewStatusDTO interviewStatusDTO = this.getInterviewStatus(userId);
interviewStatusDTO.setIsGptFinish(true);
log.info("VirtualHumanInterviewActuator.askNextQuestion-->interviewStatusDTO:{}", interviewStatusDTO);
interviewStatusManager.update(userId, interviewStatusDTO);
//结束日志保存
this.logRecord(resumeId, userId, null, null, AiInterviewChatTypeEnum.END.getCode(), StringUtils.EMPTY);
}
if (ObjectUtils.isNotEmpty(question)) {
//发送播报
this.sendCommandToVh(userId, question);
}
if (ObjectUtils.isNotEmpty(lastQuestionDTO)) {
this.submitCreateGptReport(userId, userToken, lastQuestionDTO.getQuestionId(), isFinish, version);
}
//进行总结
//问题结束,需要进行面试总结
if (isFinish) {
interviewSchedulerExecutor.execute(() -> {
log.info("VirtualHumanInterviewActuator.askNextQuestion-->interviewSchedulerExecutor userId:{}", userId);
summaryInterview(userId);
});
}
return questionDTO;
}
/**
* 进行面试总结
*
* @param userId
*/
private void summaryInterview(Integer userId) {
log.info("VirtualHumanInterviewActuator.summaryInterview-->userId:{}", userId);
try {
//发送面试播报引言
this.sendCommandToVh(userId, TencentConstants.SUMMARY_FOREWORD);
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
//获取简历id
Integer resumeId = ObjectUtils.isEmpty(sessionDTO.getQuestionDTO()) ? -1 : sessionDTO.getQuestionDTO().getResumeId();
List<QuestionInfo> questionInfos = aiInterviewService.queryAllQuestionByResumeId(resumeId);
if (CollectionUtils.isNotEmpty(questionInfos)) {
questionInfos.forEach(questionInfo -> {
InterviewShowContentItemVO contentItemVO = getQuestionShowContentItemVO(resumeId, questionInfo);
if (ObjectUtils.isEmpty(contentItemVO)) {
return;
}
InterviewShowContentVO contentVO = interviewShowContentManager.get(userId);
contentVO.setIsLast(Boolean.FALSE);
LinkedList<InterviewShowContentItemVO> contents = contentVO.getContents();
if (ObjectUtils.isEmpty(contents)) {
contents = Lists.newLinkedList();
}
contents.add(contentItemVO);
contentVO.setContents(contents);
log.info("VirtualHumanInterviewActuator.summaryInterview-->contentVO:{}", contentVO);
//存放展示内容信息
interviewShowContentManager.update(userId, contentVO);
this.sendCommandToVh(userId, contentItemVO.getPlayContent());
});
}
} catch (Exception e) {
log.error("VirtualHumanInterviewActuator.summaryInterview-->", e);
}
//维护展示内容信息
InterviewShowContentVO contentVO = interviewShowContentManager.get(userId);
contentVO.setIsLast(Boolean.TRUE);
log.info("VirtualHumanInterviewActuator.summaryInterview-->contentVO:{}", contentVO);
interviewShowContentManager.update(userId, contentVO);
InterviewStatusDTO interviewStatusDTO = this.getInterviewStatus(userId);
interviewStatusDTO.setIsSummaryOver(true);
interviewStatusManager.update(userId, interviewStatusDTO);
//面试结束
String content = TencentConstants.COMPLETION_WORD;
//发送播报
this.sendCommandToVh(userId, content);
}
/**
* 释放资源
*
* @param userId
*/
private void releaseSource(Integer userId) {
log.info("VirtualHumanInterviewActuator.releaseSource-->userId:{}", userId);
try {
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
String vhSessionId = sessionDTO.getVhSessionId();
//关闭数智人会话
virtualHumanPerformer.closeCommandChannelWebsocket(vhSessionId);
virtualHumanPerformer.closeSession(vhSessionId);
interviewSessionManager.remove(userId);
interviewStatusManager.remove(userId);
interviewSessionUserRelaManger.remove(vhSessionId);
} catch (Exception e) {
log.error("VirtualHumanInterviewActuator.releaseSource-->", e);
}
}
}
文本播报模式执行器
/**
* 文本播报面试执行器
*/
@Slf4j
@Component("textSpeechInterviewActuator")
public class TextSpeechInterviewActuator extends AbstractInterviewManager<TextSpeechInterviewContext> {
@Autowired
private TextToSpeechPerformer textToSpeechPerformer;
@Autowired
private AutomaticSpeechRecognitionPerformer automaticSpeechRecognitionPerformer;
@Autowired
private AiInterviewAssembler aiInterviewAssembler;
@Autowired
private InterviewSessionManager interviewSessionManager;
@Autowired
private AiInterviewService aiInterviewService;
@Override
protected void toCreateInterviewRoom(TextSpeechInterviewContext context) {
Integer userId = context.getUserId();
TencentConnectConfigDTO ttsConnectConfigDTO = textToSpeechPerformer.generalTtsWebSocketUrl();
TencentConnectConfigDTO asrConnectConfigDTO = automaticSpeechRecognitionPerformer.generalAsrWebSocketUrl();
context.setAsrConnectConfigDTO(asrConnectConfigDTO);
context.setTtsConnectConfigDTO(ttsConnectConfigDTO);
InterviewSessionDTO interviewSessionDTO = aiInterviewAssembler.buildInterviewSessionDTO(userId, context.getInterviewMode().getValue(), null);
//维护用户会话缓存
interviewSessionManager.update(userId, interviewSessionDTO);
}
@Override
protected void toStartInterview(TextSpeechInterviewContext context) {
Integer userId = context.getUserId();
AiInterviewQuestionDTO questionDTO = this.getNextQuestion(userId, context.getUserToken(), true, context.getVersion());
context.setQuestionDTO(questionDTO);
}
@Override
protected void toTalk(TextSpeechInterviewContext context) {
log.info("TextSpeechInterviewActuator.toTalk-->context:{}", context);
Integer userId = context.getUserId();
//获取用户回答
String userAnswer = context.getUserAnswer();
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
AiInterviewQuestionDTO sessionQuestionDTO = ObjectUtils.isEmpty(sessionDTO.getQuestionDTO()) ? new AiInterviewQuestionDTO() : sessionDTO.getQuestionDTO();
Integer chatType = AiInterviewChatTypeEnum.DIALOGUE.getCode();
//为空表示为追问回答
if (ObjectUtils.isEmpty(sessionQuestionDTO.getIsProbe())) {
chatType = AiInterviewChatTypeEnum.PROBE.getCode();
}
//记录用户对话
this.logRecord(sessionQuestionDTO.getResumeId(), userId, sessionQuestionDTO.getQuestionId(), AiInterviewChatRoleEnum.USER.getCode(), chatType, userAnswer);
if (BooleanUtils.isTrue(sessionQuestionDTO.getIsProbe())) {
//需要追问
//调用gtp回答内容
String botMessage = this.sendAnswerToBot(sessionQuestionDTO, userId, userAnswer, context.getUserToken());
//追问结束
sessionQuestionDTO.setQuestion(botMessage);
//维护为null,用户回答后区分为追问回答
sessionQuestionDTO.setIsProbe(null);
sessionDTO.setQuestionDTO(sessionQuestionDTO);
interviewSessionManager.update(userId, sessionDTO);
context.setQuestionDTO(sessionQuestionDTO);
} else {
//下一个问题
AiInterviewQuestionDTO questionDTO = this.getNextQuestion(userId, context.getUserToken(), false, context.getVersion());
context.setQuestionDTO(questionDTO);
}
}
@Override
protected void toLeaveInterviewRoom(TextSpeechInterviewContext context) {
this.releaseSource(context.getUserId());
}
@Override
protected void toSummaryInterview(TextSpeechInterviewContext context) {
log.info("TextSpeechInterviewActuator.toSummaryInterview-->context:{}", context);
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(context.getUserId());
//获取简历id
Integer resumeId = ObjectUtils.isEmpty(sessionDTO.getQuestionDTO()) ? -1 : sessionDTO.getQuestionDTO().getResumeId();
List<QuestionInfo> questionInfos = aiInterviewService.queryAllQuestionByResumeId(resumeId);
if (CollectionUtils.isEmpty(questionInfos)) {
log.warn("TextSpeechInterviewActuator.toSummaryInterview-->questionInfos is empty!");
return;
}
Integer questionId = context.getQuestionId();
if (ObjectUtils.isNotEmpty(questionId)) {
questionInfos.removeIf(temp -> !Objects.equals(temp.getQuestionId(), questionId));
}
if (CollectionUtils.isEmpty(questionInfos)) {
log.warn("TextSpeechInterviewActuator.toSummaryInterview--> remove after questionInfos is empty!questionId:{}", questionId);
return;
}
List<InterviewShowContentItemVO> summaryContents = Lists.newArrayList();
questionInfos.forEach(questionInfo -> {
InterviewShowContentItemVO contentItemVO = getQuestionShowContentItemVO(resumeId, questionInfo);
if (ObjectUtils.isEmpty(contentItemVO)) {
log.warn("TextSpeechInterviewActuator.toSummaryInterview-->contentItemVO is empty!");
return;
}
summaryContents.add(contentItemVO);
});
context.setSummaryContents(summaryContents);
}
/**
* @param userId
* @param isFirst
* @return
*/
private AiInterviewQuestionDTO getNextQuestion(Integer userId, String userToken, Boolean isFirst, Integer version) {
AiInterviewQuestionDTO questionDTO = aiInterviewService.queryUserGptQuestion(userId);
log.info("TextSpeechInterviewActuator.getNextQuestion-->userId:{},isFirst:{},questionDTO:{},version:{}", userId, isFirst, questionDTO, version);
if (isFirst) {
//获取简历id对应面试id
Integer resumeId = ObjectUtils.isNotEmpty(questionDTO) ? questionDTO.getResumeId() : null;
this.logRecord(resumeId, userId, null, null, AiInterviewChatTypeEnum.BEGIN.getCode(), StringUtils.EMPTY);
}
Integer resumeId = null;
Boolean isFinish = false;
InterviewSessionDTO sessionDTO = this.getAndCheckInterviewSession(userId);
AiInterviewQuestionDTO lastQuestionDTO = sessionDTO.getQuestionDTO();
if (ObjectUtils.isNotEmpty(questionDTO)) {
resumeId = questionDTO.getResumeId();
//维护上下文参数信息
//记录问题
this.logRecord(questionDTO.getResumeId(), userId, questionDTO.getQuestionId(), AiInterviewChatRoleEnum.GPT.getCode(), AiInterviewChatTypeEnum.DIALOGUE.getCode(), questionDTO.getQuestion());
sessionDTO.setQuestionDTO(questionDTO);
if (BooleanUtils.isTrue(questionDTO.getIsProbe())) {
//初始化bot信息
String chatId = this.initChatBot(userId, questionDTO);
sessionDTO.setBotChatId(chatId);
}
interviewSessionManager.update(userId, sessionDTO);
} else {
isFinish = true;
}
if (ObjectUtils.isEmpty(resumeId) && ObjectUtils.isNotEmpty(lastQuestionDTO)) {
resumeId = lastQuestionDTO.getResumeId();
}
if (isFinish) {
//结束日志保存
this.logRecord(resumeId, userId, null, null, AiInterviewChatTypeEnum.END.getCode(), StringUtils.EMPTY);
}
if (ObjectUtils.isNotEmpty(lastQuestionDTO)) {
this.submitCreateGptReport(userId, userToken, lastQuestionDTO.getQuestionId(), isFinish, version);
}
return questionDTO;
}
/**
* 释放资源
*
* @param userId
*/
private void releaseSource(Integer userId) {
log.info("TextSpeechInterviewActuator.releaseSource-->userId:{}", userId);
try {
interviewSessionManager.remove(userId);
} catch (Exception e) {
log.error("TextSpeechInterviewActuator.releaseSource-->", e);
}
}
}
统一面试入口执行器
/**
* 面试入口执行器
*/
@Slf4j
@Component
public class InterviewActuator implements InterviewManager<InterviewContext> {
private Map<InterviewModeEnum, InterviewManager> actuators;
@Autowired
public InterviewActuator(Map<InterviewModeEnum, InterviewManager> actuators) {
this.actuators = actuators;
}
@Override
public void createInterviewRoom(InterviewContext context) {
this.getActuator(context).createInterviewRoom(context);
}
@Override
public void startInterview(InterviewContext context) {
this.getActuator(context).startInterview(context);
}
@Override
public void checkInterviewStatus(InterviewContext context) {
this.getActuator(context).checkInterviewStatus(context);
}
@Override
public void talk(InterviewContext context) {
this.getActuator(context).talk(context);
}
@Override
public void leaveInterviewRoom(InterviewContext context) {
this.getActuator(context).leaveInterviewRoom(context);
}
@Override
public void summaryInterview(InterviewContext context) {
this.getActuator(context).summaryInterview(context);
}
/**
* @param context
* @return
*/
private InterviewManager getActuator(InterviewContext context) {
if (Objects.isNull(context) || Objects.isNull(context.getInterviewMode()) || !actuators.containsKey(context.getInterviewMode())) {
log.error("InterviewActuator.getActuator-->context:{}", context);
throw new BusinessException(HttpResultEnum.BUSINESS_ERROR);
}
return actuators.get(context.getInterviewMode());
}
}
面试模式配置类
/**
* 面试配置类
*/
@Configuration
public class InterviewConfig {
@Bean
public Map<InterviewModeEnum, InterviewManager> actuators(InterviewManager virtualHumanInterviewActuator,
InterviewManager textSpeechInterviewActuator) {
return new ImmutableMap.Builder<InterviewModeEnum, InterviewManager>()
.put(InterviewModeEnum.VIRTUAL_HUMAN_INTERVIEW, virtualHumanInterviewActuator)
.put(InterviewModeEnum.TEXT_SPEECH_INTERVIEW, textSpeechInterviewActuator)
.build();
}
}
调用示例,面试对话
@Override
public InterviewQuestionVO receiveAsrMessage(InterviewAsrMessageRequest messageRequest) {
log.info("AiInterviewFacadeImpl.receiveAsrMessage-->messageRequest:{}", messageRequest);
if (ObjectUtils.isEmpty(messageRequest) || ObjectUtils.isEmpty(messageRequest.getMessage())) {
log.warn("AiInterviewFacadeImpl.receiveAsrMessage-->messageRequest is empty!");
return null;
}
Integer userId = this.getAndCheckUserId();
InterviewModeEnum interviewMode = this.getInterviewMode(null);
InterviewContext context = aiInterviewAssembler.buildInterviewContext(interviewMode, userId, messageRequest.getUserToken(), messageRequest.getMessage(), messageRequest.getVersion());
interviewActuator.talk(context);
return aiInterviewAssembler.convertToInterviewQuestionVO(context.getQuestionDTO());
}