Преглед изворни кода

fix: 修改语音机器人逻辑

余尚辉 пре 3 месеци
родитељ
комит
263aed5df4
5 измењених фајлова са 344 додато и 66 уклоњено
  1. 197 0
      logs/flask.log
  2. 87 0
      src/core/callcenter/dao.py
  3. 1 1
      src/core/callcenter/esl/client.py
  4. 1 1
      src/core/voip/asr.py
  5. 58 64
      src/core/voip/bot.py

Разлика између датотеке није приказан због своје велике величине
+ 197 - 0
logs/flask.log


+ 87 - 0
src/core/callcenter/dao.py

@@ -365,3 +365,90 @@ class HumanServiceMap(db.Model):
             'update_time': self.update_time.isoformat() if self.update_time else None,
             'create_time': self.create_time.isoformat() if self.create_time else None,
         }
+
+
+
+class Whitelist(db.Model):
+    __tablename__ = 't_whitelist'
+    __table_args__ = {'comment': '白名单配置表'}
+
+    id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键')
+    phone = db.Column(db.String(20), nullable=False, comment='电话号码')
+    description = db.Column(db.String(255), nullable=True, comment='描述说明(备注)')
+    del_flag = db.Column(db.Boolean, nullable=False, default=False, comment='删除标志(0代表存在 2代表删除)')
+    revision = db.Column(db.Integer, nullable=True, comment='乐观锁')
+    create_by = db.Column(db.String(32), nullable=True, comment='创建人')
+    create_time = db.Column(db.DateTime, nullable=True, default=datetime.utcnow, comment='创建时间')
+    update_by = db.Column(db.String(32), nullable=True, comment='更新人')
+    update_time = db.Column(db.DateTime, nullable=True, onupdate=datetime.utcnow, comment='更新时间')
+    remark = db.Column(db.String(500), nullable=True, comment='备注')
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'phone': self.phone,
+            'description': self.description,
+            'del_flag': self.del_flag,
+            'revision': self.revision,
+            'create_by': self.create_by,
+            'create_time': self.create_time.isoformat() if self.create_time else None,
+            'update_by': self.update_by,
+            'update_time': self.update_time.isoformat() if self.update_time else None,
+            'remark': self.remark,
+        }
+
+
+class CallRecord(db.Model):
+    __tablename__ = 't_call_record'
+    __table_args__ = {'comment': '通话记录表'}
+
+    id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键')
+    session_id = db.Column(db.String(30), nullable=True, comment='sessionId')
+    type = db.Column(db.SmallInteger, nullable=True, comment='呼入分类(0白名单 1AI服务 2传统服务)')
+    user_id = db.Column(db.BigInteger, nullable=True, comment='客服ID')
+    user_name = db.Column(db.String(255), nullable=True, comment='客服名字')
+    service_category = db.Column(db.SmallInteger, nullable=False, comment='服务类型(0人工坐席 1机器人坐席 2机器人转人工)')
+    time_begin = db.Column(db.DateTime, nullable=True, comment='通话发起时间')
+    time_end = db.Column(db.DateTime, nullable=True, comment='通话结束时间')
+    times = db.Column(db.String(30), nullable=True, comment='通话时长(暂时按字符串接收)')
+    category = db.Column(db.SmallInteger, nullable=True, comment='通话类型(0呼入 1呼出)')
+    status = db.Column(db.SmallInteger, nullable=True, comment='通话状态(0未接听 1已接通)')
+    phone = db.Column(db.String(20), nullable=True, comment='电话号码')
+    bussiness_type = db.Column(db.String(50), nullable=True, comment='业务类型(创个返回字符串)')
+    url = db.Column(db.String(255), nullable=True, comment='录音的地址')
+    remark = db.Column(db.String(500), nullable=True, comment='备注')
+    has_parsed = db.Column(db.Boolean, nullable=False, default=False, comment='是否已转录音(0否 1是)')
+    parsed_voice_content = db.Column(db.Text, nullable=True, comment='通话录音内容')
+    del_flag = db.Column(db.Boolean, nullable=False, default=False, comment='删除标志(0代表存在 2代表删除)')
+    revision = db.Column(db.Integer, nullable=True, comment='乐观锁')
+    create_by = db.Column(db.String(32), nullable=True, default="admin", comment='创建人')
+    create_time = db.Column(db.DateTime, nullable=True, default=datetime.utcnow, comment='创建时间')
+    update_by = db.Column(db.String(32), nullable=True, default="admin", comment='更新人')
+    update_time = db.Column(db.DateTime, nullable=True, onupdate=datetime.utcnow, comment='更新时间')
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'session_id': self.session_id,
+            'type': self.type,
+            'user_id': self.user_id,
+            'user_name': self.user_name,
+            'service_category': self.service_category,
+            'time_begin': self.time_begin.isoformat() if self.time_begin else None,
+            'time_end': self.time_end.isoformat() if self.time_end else None,
+            'times': self.times,
+            'category': self.category,
+            'status': self.status,
+            'phone': self.phone,
+            'bussiness_type': self.bussiness_type,
+            'url': self.url,
+            'remark': self.remark,
+            'has_parsed': self.has_parsed,
+            'parsed_voice_content': self.parsed_voice_content,
+            'del_flag': self.del_flag,
+            'revision': self.revision,
+            'create_by': self.create_by,
+            'create_time': self.create_time.isoformat() if self.create_time else None,
+            'update_by': self.update_by,
+            'update_time': self.update_time.isoformat() if self.update_time else None,
+        }

+ 1 - 1
src/core/callcenter/esl/client.py

@@ -111,7 +111,7 @@ class InboundClient:
         event_name = EslEventUtil.getEventName(e)
         coreUUID = EslEventUtil.getCoreUuid(e)
         address = self.host + ':' + self.port
-        self.logger.info("process_esl_event.event_name=%s,coreUUID=%s", event_name, coreUUID)
+        # self.logger.info("process_esl_event.event_name=%s,coreUUID=%s", event_name, coreUUID)
         try:
             if event_name in self.handler_table:
                 items = self.handler_table.get(event_name)

+ 1 - 1
src/core/voip/asr.py

@@ -100,7 +100,7 @@ class TestSt:
             on_close=self.test_on_close,
             callback_args=[self.__id]
         )
-        self.sr.__setattr__("max_sentence_silence", 2000)
+        self.sr.__setattr__("max_sentence_silence", 1200)
         self.sr.start(
             aformat="pcm",
             enable_intermediate_result=True,

+ 58 - 64
src/core/voip/bot.py

@@ -18,6 +18,7 @@ import requests
 from src.core.callcenter.api import BotChatRequest,ChatMessage
 
 from src.core import singleton_keys
+from src.core.callcenter.snowflake import Snowflake
 
 calls = {}
 # recording_file = '/code/src/core/voip/incoming_call.wav'
@@ -70,26 +71,32 @@ class MyAudioMediaPort(pj.AudioMediaPort):
         # print("Received audio frame:", frame.buf, frame.size)
         if self.asr:  # 如果ASR实例存在,则发送音频数据
             self.asr.send_audio(frame.buf)
-
-        current_time = time.time() # 实时当前时间
         try:
             asr_text = self.get_asr_text()
             play_complete = self.call.is_play_complete()
-            if asr_text and not play_complete:
-                self.user_asr_texts.append(asr_text)
-            if asr_text and play_complete:
-                self.call.cur_player_file = None
-                self.user_asr_texts.append(asr_text)
-                if self.call.inputType == '1.0' and current_time - self.call.inputLongStart > 35:
-                    self.user_asr_texts.append(self.call.digit)
+            current_time = time.time() # 实时当前时间
+            if self.call.inputType == '1.0':
+                time_difference = int(current_time - self.call.inputLongStart)
+                # print('current_time - self.call.inputLongStart:',time_difference > 35, self.call.txtLock , play_complete)
+                if int(current_time - self.call.inputLongStart) > 35 and play_complete and not self.call.txtLock:
+                    self.user_asr_texts.append(f"DTMF({self.call.digit})DTMF")
                     user_asr_text = asr_text if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
+                    self.user_asr_texts.clear()
                     self.call.chat(user_asr_text)
-                else:
+                    # print("测试超长", user_asr_text)
+                elif asr_text:
+                    self.user_asr_texts.append(asr_text)
+                if time_difference > int(self.call.wait_time):
+                    self.call.reset_wait_time()
+            else:
+                if asr_text and not play_complete:
+                    self.user_asr_texts.append(asr_text)
+                if asr_text and play_complete:
+                    self.user_asr_texts.append(asr_text)
                     user_asr_text = asr_text if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
                     self.user_asr_texts.clear()
                     self.call.chat(user_asr_text)
 
-            # print(f'onFrameReceived:self.wait_time={self.call.wait_time}, self.call.digit ={self.call.digit},asr_text:{asr_text},play_complete:{play_complete},self.call.inputType:{self.call.inputType}')
             if self.call.wait_time and self.call.wait_time != "0" and play_complete and not asr_text:
                 self.call.wait_time_check(current_time, self.call.wait_time)
 
@@ -99,17 +106,13 @@ class MyAudioMediaPort(pj.AudioMediaPort):
                 self.call.cur_player_file, self.call.wait_time, self.call.inputType,self.call.action, self.call.node_id = self.get_player_file()
                 # 重置播放完成标志和超时计时器,确保新的播放从头开始计时
                 self.call.reset_wait_time()
+                self.call.txtLock = False
                 self.call.send_bot_speaker(self.call.cur_player_file)
                 #播放完毕执行的动作
                 self.call.say_end_action(self.call.action)
         except:
             pass
 
-    # def is_play_complete(self):
-    #     if self.call.cur_player_file:
-    #         player_id = murmur3_32(self.call.cur_player_file)
-    #         return self.call.player_complete_dict.get(player_id)
-
     def get_asr_text(self):
         try:
             asr_text = self.call.user_asr_text_queue.get(block=False)
@@ -123,8 +126,6 @@ class MyAudioMediaPort(pj.AudioMediaPort):
             message = self.call.message_queue.get(block=False)
             player_file = [item.voice_url for item in message.contents if item.content_type == 'voice']
             print('get_player_file:', player_file,message.wait_time, message.inputType, message.action, message.node_id)
-            if message.inputType== '1.0':
-                self.call.inputLongStart = time.time()
             return player_file, message.wait_time, message.inputType, message.action, message.node_id
         except Exception as e:
             traceback.print_exc()
@@ -182,7 +183,10 @@ class MyCall(pj.Call):
         self.player = None
         self.asr = None
 
-        print("mycall acc:", call_id)
+        self.snowflake = Snowflake(worker_id=1, data_center_id=1)
+        self.session_id = 'S' + str(self.snowflake.next_id())
+
+        print("self.session_id:", self.session_id)
         # self.scripts = build_demo_script()
         self.user_asr_text_queue = queue.Queue(maxsize=100)
         self.message_queue = queue.Queue(maxsize=3)
@@ -207,7 +211,7 @@ class MyCall(pj.Call):
         self.play_complete_flag = False  # 倒计时开始标志
 
         self.txtLock = False
-        self.inputLongStart = 0  #长按键开始时间
+        self.inputLongStart = time.time()    #长按键开始时间
 
     def wait_time_check(self, current_time, wait_time):
         try:
@@ -217,17 +221,11 @@ class MyCall(pj.Call):
             if not self.play_complete_flag:
                 self.play_complete_flag = True
                 self.play_start_time = current_time
-                print(f"开始计时: {self.play_start_time}")
-
-            # 播放完成后,检查是否超时
+                # print(f"开始计时: {self.play_start_time}")
             elapsed_time = current_time - self.play_start_time
-            print(f"当前时间: {current_time}, 已过时间: {elapsed_time}, 最大等待时间: {wait_time}")
-
             if elapsed_time >= wait_time:
-                print("ASR输入超时")
-                self.user_asr_text_queue.put("ASR408error")
-                self.reset_wait_time()
-
+                # self.user_asr_text_queue.put("ASR408error")
+                self.chat('ASR408error')
         except ValueError as e:
             print(f"无效的等待时间参数: {wait_time}, 错误: {e}")
             self.reset_wait_time()
@@ -235,7 +233,6 @@ class MyCall(pj.Call):
     def reset_wait_time(self):
         self.play_complete_flag = False  # 重置播放完成标志
         self.play_start_time = None  # 重置开始计时时间
-        print("计时器已重置")
     def get_phone(self):
         import re
         call_info = self.getInfo()
@@ -250,24 +247,22 @@ class MyCall(pj.Call):
             player_id = murmur3_32(self.cur_player_file)
             return self.player_complete_dict.get(player_id)
     def onDtmfDigit(self, prm):
-        if not self.is_play_complete() or self.txtLock:   # 判断是否播放完成 否则不记录用户说的内容
-            return
-        digit = prm.digit
-        self.reset_wait_time()
-        # 假设为超长类型按键 把用户输入的按键进行拼接 如果为# 则把用户输入所有按键放入队列并发送文本机器人
-        # 如果为非正常按键服务 输入以后直接发送文本机器人
-        if self.inputType == '1.0':
-            if digit != '#':
-                self.digit += digit
+        if self.is_play_complete() and not self.txtLock:   # 判断是否播放完成 否则不记录用户说的内容
+            digit = prm.digit
+            self.reset_wait_time()
+            # 假设为超长类型按键 把用户输入的按键进行拼接 如果为# 则把用户输入所有按键放入队列并发送文本机器人
+            # 如果为非正常按键服务 输入以后直接发送文本机器人
+            if self.inputType == '1.0':
+                if digit != '#':
+                    self.digit += digit
+                elif digit == '#':
+                    # self.user_asr_text_queue.put(f"DTMF({self.digit})DTMF")
+                    self.chat(f"DTMF({self.digit})DTMF")
             else:
-                self.user_asr_text_queue.put(f"DTMF({self.digit})DTMF")
-        else:
-            print(f"Received DTMF digit: {digit}")
-            self.user_asr_text_queue.put(f"DTMF({digit})DTMF")
+                self.user_asr_text_queue.put(f"DTMF({digit})DTMF")
 
     def onCallState(self, prm):
         call_info = self.getInfo()
-        # print("Call state text: ", dir(call_info))
         print("Call state: %s, call id: %s, callcallIdString: %s ", call_info.state, call_info.id, call_info.callIdString)
 
         # pj.PJSIP_INV_STATE_NULL
@@ -293,7 +288,7 @@ class MyCall(pj.Call):
 
     def onCallMediaState(self, prm):
         call_info = self.getInfo()
-        print("Call Media state: ", call_info.stateText)
+        # print("Call Media state: ", call_info.stateText)
         for media in call_info.media:
             if media.type == pj.PJMEDIA_TYPE_AUDIO and \
                (media.status == pj.PJSUA_CALL_MEDIA_ACTIVE):
@@ -316,7 +311,6 @@ class MyCall(pj.Call):
             return
         player_id = murmur3_32(player_file)
         self.player_complete_dict[player_id] = False
-        self.txtLock = False
         # print('self.player_complete_dict[player_id]D:', player_id, player_file, self.player_complete_dict[player_id])
         print(f"[DEBUG] Sending bot speaker, player_file: {player_file}, player_id: {player_id}")
         self.player = MyAudioMediaPlayer(player_id, self.aud_med, on_complete=self.on_media_player_complete)
@@ -325,24 +319,23 @@ class MyCall(pj.Call):
         self.player.startTransmit(self.aud_med)
 
     def on_receiver_asr_result(self, message, *args):
-        if not self.is_play_complete() or self.txtLock:   # 判断是否播放完成 否则不记录用户说的内容
-            print('机器人播放过程asr返回内容:',message)
-            return
-        self.reset_wait_time()
-        message = json.loads(message)
-        if message["header"]["status"] == 20000000:
-            # 获取 result 内容
-            result = message["payload"]["result"]
-            print("asr返回内容Result:", result)
-            self.user_asr_text_queue.put(result)
-        else:
-            print(f"Status is not {message['header']['status']}")
+        if self.is_play_complete() and not self.txtLock:   # 判断是否播放完成 否则不记录用户说的内容
+            self.reset_wait_time()
+            message = json.loads(message)
+            if message["header"]["status"] == 20000000:
+                # 获取 result 内容
+                result = message["payload"]["result"]
+                print("asr返回内容Result:", result)
+                self.user_asr_text_queue.put(result)
+            else:
+                print(f"Status is not {message['header']['status']}")
 
 
 
     def on_media_player_complete(self, player_id):
         print('player complete')
         self.player_complete_dict[player_id] = True
+        self.inputLongStart = time.time()
 
     def bot_say_hello(self):
         # print('bot_say_hello, come in ')
@@ -369,13 +362,11 @@ class ToTextBotAgent:
         if not user_asr_text:
             print("ASR文本为空,终止执行。")
             return
-
         self.call_agent = call_agent
         self.request_data = BotChatRequest(
             nodeId=self.call_agent.node_id,
             userId=self.call_agent.call_phone,
-            sessionId= self.call_agent.callIdString,
-            # sessionId="23",
+            sessionId= self.call_agent.session_id,
             recordId="",
             taskId="10001",
             asrText=user_asr_text,
@@ -418,7 +409,10 @@ class ToTextBotAgent:
 
 # 模拟接口请求返回
     def test_request(self, params: BotChatRequest):
+        if self.call_agent.txtLock:
+            return
         print("test_request::params=", params)
+        self.call_agent.txtLock = True
         response_data = {
             "node_id": "1.0",
             "contents": [],
@@ -427,7 +421,7 @@ class ToTextBotAgent:
                 "action_code": "normal",
                 "action_content": "正常通话"
             },
-            "input_type": "0"
+            "inputType": "0"
         }
 
         print("asrText:", params.asrText)
@@ -438,7 +432,7 @@ class ToTextBotAgent:
                 "voice_url": '/code/src/core/voip/scripts/1_00.wav',
                 "voice_content": "五一北京到上海的高铁票还有吗?"
             })
-            response_data['inputType'] = '1'
+            response_data['inputType'] = '1.0'
         elif params.asrText == 'ASR408error':                          #超时执行
             response_data['contents'].append({
                 "content_type": "voice",
@@ -446,7 +440,7 @@ class ToTextBotAgent:
                 "voice_url": '/code/src/core/voip/scripts/4_00.wav',
                 "voice_content": "waitTime超时"
             })
-        elif "DTMF" in params.asrText and self.call_agent.inputType =='1':   #长按键超30s执行
+        elif "DTMF" in params.asrText and self.call_agent.inputType =='1.0':   #长按键超30s执行
             response_data['contents'].append({
                 "content_type": "voice",
                 "content": "",

Неке датотеке нису приказане због велике количине промена