|
|
@@ -3,10 +3,7 @@ package com.telerobot.fs.robot;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.telerobot.fs.acd.AcdSqlQueue;
|
|
|
-import com.telerobot.fs.config.AppContextProvider;
|
|
|
-import com.telerobot.fs.config.SystemConfig;
|
|
|
-import com.telerobot.fs.config.ThreadLocalTraceId;
|
|
|
-import com.telerobot.fs.config.UuidGenerator;
|
|
|
+import com.telerobot.fs.config.*;
|
|
|
import com.telerobot.fs.entity.bo.InboundDetail;
|
|
|
import com.telerobot.fs.entity.bo.LlmConsumer;
|
|
|
import com.telerobot.fs.entity.dao.LlmKb;
|
|
|
@@ -15,11 +12,9 @@ import com.telerobot.fs.entity.dto.LlmAiphoneRes;
|
|
|
import com.telerobot.fs.entity.dto.llm.AccountBaseEntity;
|
|
|
import com.telerobot.fs.entity.po.CdrDetail;
|
|
|
import com.telerobot.fs.entity.po.HangupCause;
|
|
|
-import com.telerobot.fs.entity.pojo.AsrProvider;
|
|
|
-import com.telerobot.fs.entity.pojo.LlmToolRequest;
|
|
|
-import com.telerobot.fs.entity.pojo.SpeechResultEntity;
|
|
|
-import com.telerobot.fs.entity.pojo.TtsProvider;
|
|
|
+import com.telerobot.fs.entity.pojo.*;
|
|
|
import com.telerobot.fs.global.CdrPush;
|
|
|
+import com.telerobot.fs.robot.impl.LocalWebApiTest;
|
|
|
import com.telerobot.fs.service.InboundDetailService;
|
|
|
import com.telerobot.fs.service.SysService;
|
|
|
import com.telerobot.fs.tts.aliyun.AliyunTTSWebApi;
|
|
|
@@ -79,6 +74,9 @@ public class RobotChat extends RobotBase {
|
|
|
chatRobot.setCallDetail(callDetail);
|
|
|
chatRobot.setTtsProvider(llmAccountInfo.voiceSource);
|
|
|
chatRobot.setTtsVoiceName(llmAccountInfo.voiceCode);
|
|
|
+ if(chatRobot instanceof LocalWebApiTest){
|
|
|
+ ((LocalWebApiTest)chatRobot).makeMockData();
|
|
|
+ }
|
|
|
|
|
|
if(getAllowInterrupt() && ASR_TYPE_MRCP.equalsIgnoreCase(this.getAsrModelType())){
|
|
|
logger.error("{} `robot-speech-interrupt-allowed` is not effective in the mrcp speech recognition mode.", uuid);
|
|
|
@@ -117,7 +115,7 @@ public class RobotChat extends RobotBase {
|
|
|
// In the outbound call scenario, solve the problem that the first few words of the first sentence
|
|
|
// cannot be heard clearly, because it takes about 2 seconds for the customer to transfer from
|
|
|
// the receiver to the headphones after answering the call.
|
|
|
- ThreadUtil.sleep(200);
|
|
|
+ ThreadUtil.sleep(1500);
|
|
|
if (isHangup) {
|
|
|
return;
|
|
|
}
|
|
|
@@ -126,6 +124,16 @@ public class RobotChat extends RobotBase {
|
|
|
String ttsProvider = chatRobot.getAccount().voiceSource;
|
|
|
String asrProvider = chatRobot.getAccount().asrProvider;
|
|
|
|
|
|
+ if(StringUtils.isEmpty(ttsProvider)){
|
|
|
+ logger.warn("{} No TTS voice is currently configured for the robot. Sound playback will be handled by playing pre-synthesized TTS files.",
|
|
|
+ getTraceId());
|
|
|
+ }
|
|
|
+ if(StringUtils.isEmpty(asrProvider)){
|
|
|
+ logger.error("{} asrProvider cant not be null, please check your speech-to-text configuration. ", getTraceId());
|
|
|
+ hangupAndCloseConn("asrProvider-can-not-be-null");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if(ttsProvider.equalsIgnoreCase(TtsProvider.ALIYUN) || asrProvider.equalsIgnoreCase(AsrProvider.ALIYUN)) {
|
|
|
if((!AliyunTTSWebApi.setAliyunTokenToFreeSWITCH(uuid))) {
|
|
|
String errMsg = "AliyunTTSWebApi getToken error!";
|
|
|
@@ -141,17 +149,34 @@ public class RobotChat extends RobotBase {
|
|
|
}
|
|
|
|
|
|
if(ttsProvider.equalsIgnoreCase(TtsProvider.DOUBAO)) {
|
|
|
- logger.info("{} Current tts provider is doubao!", getTraceId());
|
|
|
+ String ttsModels = chatRobot.getAccount().ttsModels;
|
|
|
+ logger.info("{} Current tts provider is doubao, set ttsModels={}", getTraceId(), ttsModels);
|
|
|
}
|
|
|
-
|
|
|
if(ttsProvider.equalsIgnoreCase(TtsProvider.MICROSOFT)) {
|
|
|
logger.info("{} Current tts provider is microsoft!", getTraceId());
|
|
|
}
|
|
|
-
|
|
|
if(ttsProvider.equalsIgnoreCase(TtsProvider.CHINA_TELECOM)) {
|
|
|
logger.info("{} Current tts provider is china_telecom!", getTraceId());
|
|
|
}
|
|
|
|
|
|
+ // set tts_models、asr_models、asr_language_code、tts_language_code
|
|
|
+ EslConnectionUtil.sendExecuteCommand("set",
|
|
|
+ "ecc365_tts_models=" + chatRobot.getAccount().ttsModels,
|
|
|
+ uuid
|
|
|
+ );
|
|
|
+ EslConnectionUtil.sendExecuteCommand("set",
|
|
|
+ "ecc365_asr_models=" + chatRobot.getAccount().asrModels,
|
|
|
+ uuid
|
|
|
+ );
|
|
|
+ EslConnectionUtil.sendExecuteCommand("set",
|
|
|
+ "ecc365_asr_language_code=" + chatRobot.getAccount().asrLanguageCode,
|
|
|
+ uuid
|
|
|
+ );
|
|
|
+ EslConnectionUtil.sendExecuteCommand("set",
|
|
|
+ "ecc365_tts_language_code=" + chatRobot.getAccount().ttsLanguageCode,
|
|
|
+ uuid
|
|
|
+ );
|
|
|
+
|
|
|
EslMessage apiResponseMsg = EslConnectionUtil.sendSyncApiCommand(
|
|
|
"uuid_exists",
|
|
|
uniqueID,
|
|
|
@@ -198,27 +223,37 @@ public class RobotChat extends RobotBase {
|
|
|
logger.info("{} PLAYBACK_START event, time cost = {} ms. ", getTraceId(), timeSpent);
|
|
|
}
|
|
|
|
|
|
- if(playbackFilePath != null && playbackFilePath.endsWith(LLM_WAIT_WAV_FILE)){
|
|
|
- releasePlayBackStartSignalForLlmConcurrency();
|
|
|
- logger.info("{} recv PLAYBACK_START event for wav file {}. ", getTraceId(), playbackFilePath);
|
|
|
+ if(StringUtils.isNotEmpty(playbackFilePath)){
|
|
|
+ if(playbackFilePath.endsWith(LLM_WAIT_WAV_FILE)) {
|
|
|
+ releasePlayBackStartSignalForLlmConcurrency();
|
|
|
+ logger.info("{} recv PLAYBACK_START event for wav file {}. ", getTraceId(), playbackFilePath);
|
|
|
+ }else{
|
|
|
+ logger.info("{} The currently playing file is {} . ", getTraceId(), playbackFilePath);
|
|
|
+ }
|
|
|
}
|
|
|
}else if(EventNames.CHANNEL_PARK.equalsIgnoreCase(eventName))
|
|
|
{
|
|
|
logger.info("{} recv CHANNEL_PARK event. ", uuid);
|
|
|
}
|
|
|
else if (EventNames.PLAYBACK_STOP.equalsIgnoreCase(eventName)) {
|
|
|
- if(playbackFilePath != null && playbackFilePath.endsWith(LLM_WAIT_WAV_FILE)){
|
|
|
- releasePlayBackStoppedSignalForLlmConcurrency();
|
|
|
- logger.info("{} recv PLAYBACK_STOP event for wav file {}. ", getTraceId(), playbackFilePath);
|
|
|
- return;
|
|
|
+ if(StringUtils.isNotEmpty(playbackFilePath)) {
|
|
|
+ if (playbackFilePath.endsWith(LLM_WAIT_WAV_FILE)) {
|
|
|
+ releasePlayBackStoppedSignalForLlmConcurrency();
|
|
|
+ logger.info("{} recv PLAYBACK_STOP event for wav file {}. ", getTraceId(), playbackFilePath);
|
|
|
+ }else {
|
|
|
+ logger.info("{} The playback of the file {} has completed. ", getTraceId(), playbackFilePath);
|
|
|
+ recvPlayBackEndEvent = true;
|
|
|
+ playbackEndTime = System.currentTimeMillis();
|
|
|
+ releasePlayBackFinishedSignal();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if(EventNames.PLAYBACK_STOP.equalsIgnoreCase(detail)) {
|
|
|
+ logger.info("{} ************ streaming tts playback finished.", getTraceId());
|
|
|
chatRobot.setTtsChannelState(TtsChannelState.CLOSED);
|
|
|
recvPlayBackEndEvent = true;
|
|
|
playbackEndTime = System.currentTimeMillis();
|
|
|
releasePlayBackFinishedSignal();
|
|
|
- logger.info("{} streaming tts playback finished.", getTraceId());
|
|
|
}
|
|
|
|
|
|
if(recvHangupSignal){
|
|
|
@@ -248,6 +283,9 @@ public class RobotChat extends RobotBase {
|
|
|
interruptRobotSpeech();
|
|
|
releasePlayBackFinishedSignal();
|
|
|
ThreadUtil.sleep(100);
|
|
|
+ if(StringUtils.isEmpty(chatRobot.getAccount().voiceSource)) {
|
|
|
+ ttsChannelClosed = true;
|
|
|
+ }
|
|
|
// wait for tts closed
|
|
|
int step = 50;
|
|
|
int maxWaitMills = 2000;
|
|
|
@@ -322,13 +360,14 @@ public class RobotChat extends RobotBase {
|
|
|
chatRobot.setTtsChannelState(TtsChannelState.CLOSED);
|
|
|
logger.info("{} TtsChannelClosed = true.", getTraceId());
|
|
|
ttsChannelClosed = true;
|
|
|
+ releasePlayBackFinishedSignal();
|
|
|
}
|
|
|
if("Speech-Open".equalsIgnoreCase(event)){
|
|
|
- chatRobot.setTtsChannelState(TtsChannelState.OPENED);
|
|
|
- chatRobot.flushTtsRequestQueue();
|
|
|
- long timeSpent = System.currentTimeMillis() - playbackStartTime;
|
|
|
- logger.info("{} Speech-Open event, time cost = {} ms. ", getTraceId(), timeSpent);
|
|
|
- }
|
|
|
+ chatRobot.setTtsChannelState(TtsChannelState.OPENED);
|
|
|
+ chatRobot.flushTtsRequestQueue();
|
|
|
+ long timeSpent = System.currentTimeMillis() - playbackStartTime;
|
|
|
+ logger.info("{} Speech-Open event, time cost = {} ms. ", getTraceId(), timeSpent);
|
|
|
+ }
|
|
|
|
|
|
if ("NetworkError".equalsIgnoreCase(event)) {
|
|
|
CommonUtils.setHangupCauseDetail(
|
|
|
@@ -387,7 +426,8 @@ public class RobotChat extends RobotBase {
|
|
|
);
|
|
|
if (recvPlayBackEndEvent || getAllowInterrupt()) {
|
|
|
if (!interactiveParam.checkInSpeaking()) {
|
|
|
- synchronized (getTraceId().intern()) {
|
|
|
+ String lockerKey = String.format("%s%s", uuid, "checkInSpeaking");
|
|
|
+ synchronized (lockerKey.intern()) {
|
|
|
if (!interactiveParam.checkInSpeaking()) {
|
|
|
interactiveParam.setInSpeaking(true);
|
|
|
// Main thread awakened to extend customer speaking time beyond 6 seconds.
|
|
|
@@ -435,7 +475,8 @@ public class RobotChat extends RobotBase {
|
|
|
}
|
|
|
|
|
|
private boolean setTransferState() {
|
|
|
- synchronized (uuid.intern()) {
|
|
|
+ String lockerKey = String.format("%s%s", uuid, "setTransferState");
|
|
|
+ synchronized (lockerKey.intern()) {
|
|
|
if (transferToAgent) {
|
|
|
logger.info("{} transferring to a human operator is already being handled. skip...", getTraceId());
|
|
|
return false;
|
|
|
@@ -576,6 +617,7 @@ public class RobotChat extends RobotBase {
|
|
|
public void backgroundJobResultReceived(String addr, EslEvent event) {
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
* interactWithRobot
|
|
|
**/
|
|
|
@@ -612,8 +654,7 @@ public class RobotChat extends RobotBase {
|
|
|
if (counter > MAX_CONSECUTIVE_NO_VOICE_NUMBER) {
|
|
|
logger.info("{} There has been no sound for {} consecutive times. Play hangupTips and then hangup call.",
|
|
|
getTraceId(), MAX_CONSECUTIVE_NO_VOICE_NUMBER);
|
|
|
- chatRobot.sendTtsRequest(chatRobot.getAccount().hangupTips);
|
|
|
- chatRobot.closeTts();
|
|
|
+ playSound(chatRobot.getAccount().hangupTips);
|
|
|
recvHangupSignal = true;
|
|
|
return;
|
|
|
}
|
|
|
@@ -641,8 +682,7 @@ public class RobotChat extends RobotBase {
|
|
|
if (aiphoneRes == null || aiphoneRes.getStatus_code() == 0) {
|
|
|
String tips = SystemConfig.getValue("llm-max-try-fail-tips", "");
|
|
|
if (!StringUtils.isEmpty(tips)) {
|
|
|
- chatRobot.sendTtsRequest(tips);
|
|
|
- chatRobot.closeTts();
|
|
|
+ playSound(tips);
|
|
|
} else {
|
|
|
CommonUtils.setHangupCauseDetail(
|
|
|
callDetail,
|
|
|
@@ -650,8 +690,8 @@ public class RobotChat extends RobotBase {
|
|
|
String.format("The large model failed to access successfully despite more than %d connection attempts.", LLM_MAX_TRY)
|
|
|
);
|
|
|
hangupAndCloseConn("reach-llm-max-try-error");
|
|
|
- return;
|
|
|
}
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
talkRound.increment();
|
|
|
@@ -660,8 +700,8 @@ public class RobotChat extends RobotBase {
|
|
|
getTraceId(), spentCost, JSON.toJSONString(aiphoneRes)
|
|
|
);
|
|
|
|
|
|
- if(aiphoneRes != null && aiphoneRes.getStatus_code() == 1) {
|
|
|
|
|
|
+ if(aiphoneRes.getStatus_code() == 1) {
|
|
|
ttsChannelClosed = false;
|
|
|
String body = aiphoneRes.getBody();
|
|
|
if(!StringUtils.isEmpty(body)){
|
|
|
@@ -704,17 +744,22 @@ public class RobotChat extends RobotBase {
|
|
|
if(!setTransferState()){
|
|
|
return;
|
|
|
}
|
|
|
- doTransferToManualAgent(body);
|
|
|
+ doTransferToManualAgent(aiphoneRes);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (aiphoneRes.getClose_phone() == 1) {
|
|
|
if(StringUtils.isEmpty(body)){
|
|
|
- chatRobot.sendTtsRequest(chatRobot.getAccount().hangupTips);
|
|
|
+ playSound(chatRobot.getAccount().hangupTips);
|
|
|
+ }else{
|
|
|
+ playResponse(aiphoneRes);
|
|
|
}
|
|
|
- chatRobot.closeTts();
|
|
|
waitForPlayBackFinished(11000);
|
|
|
long startTimeTick = System.currentTimeMillis();
|
|
|
+ if(StringUtils.isEmpty(chatRobot.getAccount().voiceSource)){
|
|
|
+ ttsChannelClosed = true;
|
|
|
+ ThreadUtil.sleep(2000);
|
|
|
+ }
|
|
|
while (!ttsChannelClosed && !isHangup) {
|
|
|
ThreadUtil.sleep(1000);
|
|
|
if(System.currentTimeMillis() - startTimeTick > 11000){
|
|
|
@@ -724,6 +769,9 @@ public class RobotChat extends RobotBase {
|
|
|
hangupAndCloseConn("system-hangup");
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ // play wav file
|
|
|
+ playResponse(aiphoneRes);
|
|
|
}
|
|
|
|
|
|
} catch (Throwable e) {
|
|
|
@@ -740,7 +788,7 @@ public class RobotChat extends RobotBase {
|
|
|
}
|
|
|
|
|
|
|
|
|
- if(aiphoneRes != null && aiphoneRes.getIfcan_interrupt() == 1) {
|
|
|
+ if(aiphoneRes.getIfcan_interrupt() == 1) {
|
|
|
interactiveParam.setAllowInterrupt(1);
|
|
|
logger.info("{} allowSpeechInterrupt={}", getTraceId(), 1);
|
|
|
}
|
|
|
@@ -766,12 +814,29 @@ public class RobotChat extends RobotBase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private synchronized void doTransferToManualAgent(String audioTipsText){
|
|
|
- if(transferToAgentExecuted){
|
|
|
- logger.warn("{} The call transfer to a human agent has already been processed.", getTraceId());
|
|
|
- return;
|
|
|
+ private void playResponse(LlmAiphoneRes aiphoneRes){
|
|
|
+ String ttsFilePathList = aiphoneRes.getTtsFilePathList();
|
|
|
+ if(!StringUtils.isEmpty(ttsFilePathList)){
|
|
|
+ TtsFileInfo ttsFileInfo = AudioUtils.joinTtsFiles(ttsFilePathList);
|
|
|
+ logger.info("{} try to play wav file for text {}.", getTraceId(), aiphoneRes.getBody());
|
|
|
+ startPlayback(ttsFileInfo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void doTransferToManualAgent(LlmAiphoneRes aiphoneRes){
|
|
|
+ String audioTipsText = null;
|
|
|
+ if(aiphoneRes != null) {
|
|
|
+ audioTipsText = aiphoneRes.getBody();
|
|
|
}
|
|
|
- transferToAgentExecuted = true;
|
|
|
+ String lockerKey = String.format("%s%s", uuid, "doTransferToManualAgent");
|
|
|
+ synchronized (lockerKey.intern()) {
|
|
|
+ if (transferToAgentExecuted) {
|
|
|
+ logger.warn("{} The call transfer to a human agent has already been processed.", getTraceId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ transferToAgentExecuted = true;
|
|
|
+ }
|
|
|
+
|
|
|
callDetail.setChatContent(chatRobot.getDialogues());
|
|
|
// Replace the prompt words for manual transfer in the text with blank spaces.
|
|
|
String transferToTel = "";
|
|
|
@@ -786,7 +851,7 @@ public class RobotChat extends RobotBase {
|
|
|
for (String match : matches) {
|
|
|
audioTipsText = audioTipsText.replace(match, "");
|
|
|
|
|
|
- List<String> tmp = RegExp.GetMatchFromStringByRegExp(match, "\\d{7,12}");
|
|
|
+ List<String> tmp = RegExp.GetMatchFromStringByRegExp(match, "\\d{1,12}");
|
|
|
transferToTel = tmp.get(0);
|
|
|
logger.info("{} Successfully retrieved transferToTel number {}", uuid, transferToTel);
|
|
|
|
|
|
@@ -797,13 +862,17 @@ public class RobotChat extends RobotBase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if(StringUtils.isEmpty(audioTipsText)){
|
|
|
+ if(StringUtils.isEmpty(audioTipsText) || audioTipsText.equalsIgnoreCase(LlmToolRequest.TRANSFER_TO_AGENT)){
|
|
|
String tips = chatRobot.getAccount().transferToAgentTips;
|
|
|
logger.info("{} Try to play tts transferToAgentTips {}", getTraceId(), tips);
|
|
|
- chatRobot.sendTtsRequest(tips);
|
|
|
- chatRobot.closeTts();
|
|
|
- waitForPlayBackFinished(6000);
|
|
|
+ if(!StringUtils.isEmpty(tips)) {
|
|
|
+ playSound(tips);
|
|
|
+ waitForPlayBackFinished(9000);
|
|
|
+ }
|
|
|
// wait for tips playback finished
|
|
|
+ }else {
|
|
|
+ playResponse(aiphoneRes);
|
|
|
+ waitForPlayBackFinished(9000);
|
|
|
}
|
|
|
|
|
|
// stop_asr 的顺序很重要,需要放在播放tts之后,否则不起作用;会被uuid_break清空指令;
|
|
|
@@ -863,7 +932,8 @@ public class RobotChat extends RobotBase {
|
|
|
long startWaitTimeMills = System.currentTimeMillis();
|
|
|
logger.info("{} wait for customer speaking ...", getTraceId());
|
|
|
|
|
|
- acquire(7000);
|
|
|
+ Integer maxWaitTimeCustomerSpeaking = Integer.parseInt(SystemConfig.getValue("max-wait-time-customer-speaking", "7000")) ;
|
|
|
+ acquire(maxWaitTimeCustomerSpeaking);
|
|
|
|
|
|
logger.info("{} wait for customer speaking, time passed = {}ms. ...",
|
|
|
getTraceId(),
|