yzx преди 2 седмици
родител
ревизия
4defd9c02b
променени са 3 файла, в които са добавени 88 реда и са изтрити 16 реда
  1. 5 3
      sql-scripts/easycallcenter365.sql
  2. 58 0
      src/main/java/com/telerobot/fs/robot/RobotBase.java
  3. 25 13
      src/main/java/com/telerobot/fs/robot/RobotChat.java

+ 5 - 3
sql-scripts/easycallcenter365.sql

@@ -1706,6 +1706,8 @@ INSERT INTO `cc_params` VALUES ('1231318', '是否启用firewalld防护墙', 'fi
 INSERT INTO `cc_params` VALUES ('1231319', '呼入ip白名单(用于外部线路呼入)', 'fs-inbound-allow-ip-list', '192.168.67.110\r\n192.168.67.120', 'sys', '0', '1');
 INSERT INTO `cc_params` VALUES ('1231320', '分机注册的白名单 ', 'fs-register-allow-ip-list', '192.168.67.110\r\n192.168.67.120', 'sys', '0', '1');
 INSERT INTO `cc_params` VALUES ('1231321', '是否启用freeswitch的分机注册防护', 'fs-register-acl-enabled', 'true', 'sys', '0', '1');
+INSERT INTO `cc_params` VALUES ('1231330', '打断过滤: ASR结果最小文本长度,低于此长度视为噪声不打断', 'barge-in-min-text-length', '3', 'sys', '0', '1');
+INSERT INTO `cc_params` VALUES ('1231331', '打断过滤: 噪声/语气词列表(逗号分隔,完整匹配不打断)', 'barge-in-noise-words', '嗯,啊,哦,哎,唔,噢,呃,额,喂,嗨,哈,呀,嘛,嗯嗯,哈哈,呵呵,哎哎,啊啊', 'sys', '0', '1');
 
 -- ----------------------------
 -- Table structure for cc_tts_aliyun
@@ -8664,9 +8666,9 @@ CREATE TABLE `cc_call_round` (
   `round_no` int NOT NULL DEFAULT '0' COMMENT 'round number (1-based)',
   `asr_text` varchar(500) NOT NULL DEFAULT '' COMMENT 'ASR recognized text',
   `llm_text` varchar(500) NOT NULL DEFAULT '' COMMENT 'LLM response text',
-  `asr_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'ASR duration (ms)',
-  `llm_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'LLM duration (ms)',
-  `tts_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'TTS playback duration (ms)',
+  `asr_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'ASR recognition duration (ms), from begin-speaking to vad',
+  `llm_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'LLM call duration (ms)',
+  `tts_cost_ms` bigint NOT NULL DEFAULT '0' COMMENT 'TTS first-byte latency (ms), from TTS request to first audio',
   `create_time` bigint NOT NULL DEFAULT '0' COMMENT 'record create timestamp',
   PRIMARY KEY (`id`),
   KEY `idx_uuid` (`uuid`)

+ 58 - 0
src/main/java/com/telerobot/fs/robot/RobotBase.java

@@ -110,6 +110,20 @@ public abstract class RobotBase implements IEslEventListener {
             "max-consecutive-no-voice-number","3")
     );
 
+    /**
+     * 打断最小文本长度(低于此长度的ASR结果视为噪声/语气词,不触发打断)
+     */
+    protected final static int BARGE_IN_MIN_TEXT_LENGTH = Integer.parseInt(SystemConfig.getValue(
+            "barge-in-min-text-length", "3")
+    );
+
+    /**
+     * 打断噪声/语气词过滤列表(逗号分隔,匹配到的不触发打断)
+     */
+    protected final static String BARGE_IN_NOISE_WORDS = SystemConfig.getValue(
+            "barge-in-noise-words", "嗯,啊,哦,哎,唔,噢,呃,额,喂,嗨,哈,呀,嘛,嗯嗯,哈哈,呵呵,哎哎,啊啊")
+    );
+
     /**
      *  首次打断后,不响应,等待客户继续说话;
      */
@@ -181,6 +195,16 @@ public abstract class RobotBase implements IEslEventListener {
      */
     protected volatile long roundTtsCostMs;
 
+    /**
+     *  用户开始说话的时间戳(用于计算ASR耗时)
+     */
+    protected volatile long userSpeakStartTime;
+
+    /**
+     *  TTS首字音频到达时间戳(用于计算TTS耗时)
+     */
+    protected volatile long ttsFirstAudioTime;
+
     /**
      *  ASR等待开始时间戳
      */
@@ -849,4 +873,38 @@ public abstract class RobotBase implements IEslEventListener {
 
         return false;
     }
+
+    /**
+     * 检查ASR结果是否为噪声/语气词,不应触发打断。
+     * 规则:
+     * 1. 去除标点后文本长度 < BARGE_IN_MIN_TEXT_LENGTH → 视为噪声
+     * 2. 去除标点后文本完整匹配 BARGE_IN_NOISE_WORDS 中的某项 → 视为噪声
+     */
+    protected boolean isBargeInNoise(String asrText) {
+        if (asrText == null || asrText.isEmpty()) {
+            return true;
+        }
+        // 去除标点和空格
+        String words = asrText.replaceAll("[\\p{Punct}\\p{IsPunctuation}]", "").replace(" ", "").trim();
+        if (words.isEmpty()) {
+            return true;
+        }
+        // 规则1: 文本太短,大概率是噪声/语气词
+        if (words.length() < BARGE_IN_MIN_TEXT_LENGTH) {
+            logger.info("{} barge-in filtered: text too short ({} < {}), text={}",
+                    getTraceId(), words.length(), BARGE_IN_MIN_TEXT_LENGTH, words);
+            return true;
+        }
+        // 规则2: 完整匹配噪声词列表
+        if (BARGE_IN_NOISE_WORDS != null && !BARGE_IN_NOISE_WORDS.isEmpty()) {
+            for (String noiseWord : BARGE_IN_NOISE_WORDS.split(",")) {
+                if (!noiseWord.isEmpty() && words.equals(noiseWord)) {
+                    logger.info("{} barge-in filtered: matched noise word '{}', text={}",
+                            getTraceId(), noiseWord, words);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 }

+ 25 - 13
src/main/java/com/telerobot/fs/robot/RobotChat.java

@@ -259,8 +259,11 @@ public class RobotChat extends RobotBase {
             if(EventNames.PLAYBACK_START.equalsIgnoreCase(detail)) {
                 chatRobot.setTtsChannelState(TtsChannelState.OPENED);
                 chatRobot.flushTtsRequestQueue();
-                if (playbackStartTime <= 0) {
-                    playbackStartTime = System.currentTimeMillis();
+                if (ttsFirstAudioTime <= 0) {
+                    ttsFirstAudioTime = System.currentTimeMillis();
+                    if (playbackStartTime > 0) {
+                        roundTtsCostMs = ttsFirstAudioTime - playbackStartTime;
+                    }
                 }
                 long timeSpent = System.currentTimeMillis() - playbackStartTime;
                 logger.info("{} PLAYBACK_START event,  time cost = {} ms. ", getTraceId(), timeSpent);
@@ -426,16 +429,16 @@ public class RobotChat extends RobotBase {
                ttsChannelClosed = true;
                recvPlayBackEndEvent = true;
                playbackEndTime = System.currentTimeMillis();
-               if (playbackStartTime > 0) {
-                   roundTtsCostMs = playbackEndTime - playbackStartTime;
-               }
                releasePlayBackFinishedSignal();
            }
            if("Speech-Open".equalsIgnoreCase(event)){
                chatRobot.setTtsChannelState(TtsChannelState.OPENED);
                chatRobot.flushTtsRequestQueue();
-               if (playbackStartTime <= 0) {
-                   playbackStartTime = System.currentTimeMillis();
+               if (ttsFirstAudioTime <= 0) {
+                   ttsFirstAudioTime = System.currentTimeMillis();
+                   if (playbackStartTime > 0) {
+                       roundTtsCostMs = ttsFirstAudioTime - playbackStartTime;
+                   }
                }
                long timeSpent = System.currentTimeMillis() - playbackStartTime;
                logger.info("{} Speech-Open event,  time cost = {} ms. ", getTraceId(), timeSpent);
@@ -527,6 +530,9 @@ public class RobotChat extends RobotBase {
             }
 
             if ("middle".equalsIgnoreCase(speechEvent)) {
+                if (userSpeakStartTime <= 0) {
+                    userSpeakStartTime = System.currentTimeMillis();
+                }
                 logger.info("{}  ** asr-websocket, begin-speaking **  {}", getTraceId(), asrResponse);
                 logger.info("{} recv asr middle result event, recvPlayBackEndEvent={}, allowInterrupt={}, !checkSpeaking={}",
                         getTraceId(),
@@ -569,8 +575,8 @@ public class RobotChat extends RobotBase {
                 if (!StringUtil.isNullOrEmpty(asrResponse)) {
                     asrResultEx.add(asrResponse);
                     roundAsrText = asrResponse;
-                    if (asrWaitStartTime > 0) {
-                        roundAsrCostMs = System.currentTimeMillis() - asrWaitStartTime;
+                    if (userSpeakStartTime > 0) {
+                        roundAsrCostMs = System.currentTimeMillis() - userSpeakStartTime;
                     }
                     // #region debug-point xfyun-asr-no-response-asr-cache
                     logger.info("{} dbg_asr_event cached vad result, queueSize={}, response={}",
@@ -587,7 +593,11 @@ public class RobotChat extends RobotBase {
                     releasePlayBackFinishedSignal();
                     ThreadUtil.sleep(100);
                 } else if(chatRobot.getAccount().interruptFlag == 2 && !recvPlayBackEndEvent) {
-                    logger.info("{} interruptFlag=2 barge-in detected by vad event, interrupt current robot speech.", getTraceId());
+                    if (isBargeInNoise(asrResponse)) {
+                        logger.info("{} interruptFlag=2 barge-in skipped by noise filter, asrText={}", getTraceId(), asrResponse);
+                        return;
+                    }
+                    logger.info("{} interruptFlag=2 barge-in detected by vad event, interrupt current robot speech. asrText={}", getTraceId(), asrResponse);
                     interruptRobotSpeech();
                     releasePlayBackFinishedSignal();
                     ThreadUtil.sleep(100);
@@ -704,8 +714,8 @@ public class RobotChat extends RobotBase {
                         String tmpResult = URLDecoder.decode(speechResult,"utf-8").replace(" ","");
                         asrResultEx.add(tmpResult);
                         roundAsrText = tmpResult;
-                        if (asrWaitStartTime > 0) {
-                            roundAsrCostMs = System.currentTimeMillis() - asrWaitStartTime;
+                        if (userSpeakStartTime > 0) {
+                            roundAsrCostMs = System.currentTimeMillis() - userSpeakStartTime;
                         }
                         logger.info("{} kaldi asr response: {}",getTraceId(), tmpResult);
                     } catch (Throwable e) {
@@ -845,7 +855,9 @@ public class RobotChat extends RobotBase {
 
         // 识别开始时间
         Long startTime = System.currentTimeMillis();
-        playbackStartTime = 0; // will be set on Speech-Open or first TTS event
+        userSpeakStartTime = 0; // reset: will be set on middle/begin-speaking event
+        ttsFirstAudioTime = 0;  // reset: will be set on Speech-Open/PLAYBACK_START
+        playbackStartTime = startTime; // TTS request starts from LLM call time (streaming LLM may trigger TTS during call)
         LlmAiphoneRes aiphoneRes;
 
             try {