Эх сурвалжийг харах

feat: 模拟调用机器人接口

余尚辉 6 сар өмнө
parent
commit
b7ee74b4d2

+ 9 - 10
src/core/callcenter/model.py

@@ -467,7 +467,7 @@ class RouteGateway:
 
 
 #机器人外呼
-class OutboundRobotRequestParams:
+class BotChatRequest:
     def __init__(self, node_id=None, user_id=None, session_id=None, record_id=None,
                  task_id=None, event_type=None, asr_text=None, key_input=None):
         self.node_id = node_id  # 节点id
@@ -477,7 +477,6 @@ class OutboundRobotRequestParams:
         self.task_id = task_id  # 机器人任务id
         self.event_type = event_type  # 1:电话接通,2:用户语音asr结果上传,3:用户按键输入,4:用户挂断电话,5:读取外呼结果(可在外呼过程中调用,不一定在结束之后),6:用户不说话超时,7:TTS或录音文件播放完毕或被打断,8:ASR错误导致挂断电话,9:TTS错误导致挂断电话,10:其它系统错误导致挂断电话
         self.asr_text = asr_text  # asr识别的文本
-        self.key_input = key_input  # 用户按键输入
 
     def to_json_string(self):
         return json.dumps(self.__dict__, ensure_ascii=False)
@@ -494,7 +493,7 @@ class OutboundRobotRequestParams:
 
 
 
-class OutboundRobotResponseAction:
+class ChatAction:
     def __init__(self, action_code=None, action_content=None):
         self.action_code = action_code         # normal:正常通话;hang:挂断;transfer:转人工
         self.action_content = action_content   # 动作内容
@@ -510,7 +509,7 @@ class OutboundRobotResponseAction:
         )
 
 
-class OutboundRobotResponseContent:
+class ChatContent:
     def __init__(self, content_type=None, content=None, voice_url=None, voice_content=None):
         self.content_type = content_type   # 播放类型
         self.content = content             # 播放内容
@@ -530,7 +529,7 @@ class OutboundRobotResponseContent:
         )
 
 
-class OutboundRobotResponseResponseData:
+class ChatMessage:
     def __init__(self, node_id=None, contents=None, interruptable=None, wait_time=None,
                  action=None, talk_time_out=None):
         self.node_id = node_id   # 节点id
@@ -552,8 +551,8 @@ class OutboundRobotResponseResponseData:
 
     @classmethod
     def from_json(cls, json_data):
-        contents = [OutboundRobotResponseContent.from_json(item) for item in json_data.get("contents", [])]
-        action = OutboundRobotResponseAction.from_json(json_data.get("action", {})) if json_data.get("action") else None
+        contents = [ChatContent.from_json(item) for item in json_data.get("contents", [])]
+        action = ChatAction.from_json(json_data.get("action", {})) if json_data.get("action") else None
         return cls(
             node_id=json_data.get("node_id"),
             contents=contents,
@@ -564,9 +563,9 @@ class OutboundRobotResponseResponseData:
         )
 
 
-class OutboundRobotResponseResponse:
+class ChatResponse:
     def __init__(self, data=None, message=None, code=None):
-        self.data = data if data is not None else OutboundRobotResponseResponseData()
+        self.data = data if data is not None else ChatMessage()
         self.message = message
         self.code = code
 
@@ -580,7 +579,7 @@ class OutboundRobotResponseResponse:
     @classmethod
     def from_json(cls, json_string):
         data = json.loads(json_string)
-        response_data = OutboundRobotResponseResponseData.from_json(data.get("data", {}))
+        response_data = ChatMessage.from_json(data.get("data", {}))
         return cls(
             data=response_data,
             message=data.get("message"),

+ 143 - 19
src/core/voip/bot.py

@@ -12,10 +12,14 @@ import pjsua2 as pj
 from enum import Enum
 from src.core.voip.constant import *
 
+import requests
+from src.core.callcenter.model import BotChatRequest, ChatResponse
+
+
 calls = {}
 # recording_file = '/code/src/core/voip/incoming_call.wav'
-# player_file = '/code/src/core/voip/test222.wav'
-
+player_file1 = '/code/src/core/voip/test111.wav'
+player_file2 = '/code/src/core/voip/test222.wav'
 
 class BotStatus(Enum):
     # 等待用户讲话,未超时
@@ -68,12 +72,15 @@ class MyAudioMediaPort(pj.AudioMediaPort):
             if asr_text and not play_complete:
                 self.user_asr_texts.append(asr_text)
             if asr_text and play_complete:
+                self.cur_player_file = None
                 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)
+                self.call.chat("2",user_asr_text)
 
             player_queue_size = self.call.player_queue.qsize()
+            print(player_queue_size, self.cur_player_file, play_complete)
+
             if (player_queue_size > 0 and not self.cur_player_file) or (player_queue_size > 0 and play_complete):
                 print('onFrameReceived:player_queue_size=', player_queue_size, 'play_complete=', play_complete)
                 self.cur_player_file = self.get_player_file()
@@ -154,6 +161,7 @@ class MyCall(pj.Call):
         self.player = None
         self.asr = None
 
+
         self.scripts = build_demo_script()
         self.user_asr_text_queue = queue.Queue(maxsize=100)
         self.player_queue = queue.Queue(maxsize=3)
@@ -181,9 +189,9 @@ class MyCall(pj.Call):
         # pj.PJSIP_INV_STATE_CONFIRMED
         # pj.PJSIP_INV_STATE_DISCONNECTED
 
-        # if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
-        #     # 当呼叫状态为已确认(即接通)
-        #     self.bot_say_hello()
+        if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
+            # 当呼叫状态为已确认(即接通)
+            self.bot_say_hello()
 
         if call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
             print("通话结束")
@@ -201,7 +209,7 @@ class MyCall(pj.Call):
                 try:
                     # 建立双向通道
                     self.receive_user_speaker()
-                    self.bot_say_hello()
+                    # self.bot_say_hello()
                 except Exception as e:
                     traceback.print_exc()
 
@@ -220,7 +228,7 @@ class MyCall(pj.Call):
         self.player.startTransmit(self.aud_med)
 
     def on_receiver_asr_result(self, message, *args):
-        print(message)
+        print('asr返回内容:',message)
         self.user_asr_text_queue.put(message)
 
     def on_media_player_complete(self, player_id):
@@ -229,15 +237,131 @@ class MyCall(pj.Call):
 
     def bot_say_hello(self):
         print('bot_say_hello, come in ')
-        self.chat(user_asr_text="SAY_HELLO")
+        self.chat('1', user_asr_text="SAY_HELLO")
 
-    def chat(self, user_asr_text=None):
+    def chat(self,event_type, user_asr_text=None):
         # TODO 调用文本机器人接口
-        message = {'player_file': self.scripts.get()}
-        player = message.get('player_file')
-        print('chat::player_file=', player)
-        self.player_queue.put(player)
+        # message = {'player_file': self.scripts.get()}
+        # player = message.get('player_file')
+        # print('chat::player_file=', player)
+        # self.player_queue.put(player)
+        # 调用文本机器人接口
+        ToTextBotAgent(event_type,user_asr_text,self)
+
+class ToTextBotAgent:
+    def __init__(self, event_type, user_asr_text, call_agent):
+        if not user_asr_text:
+            print("ASR文本为空,终止执行。")
+            return
 
+        self.call_agent = call_agent
+        self.request_data = BotChatRequest(
+            node_id="1",
+            user_id="139311",
+            session_id="1",
+            record_id="2",
+            task_id="ceshi",
+            event_type=event_type,  # 传入不同的 event_type 来测试不同的响应
+            asr_text=user_asr_text
+        )
+        # 发送请求并处理响应
+        self.test_request(self.request_data)
+
+    def to_hang(self, action_content):
+        user_part = self.call_agent.user_part_pool.get()
+        self.call_agent.hangup(user_part, action_content)
+
+    def transfer_people(self):
+        print('todo 转人工')
+
+    def handling_release(self, response: ChatResponse):
+        print('handling_release', response.data)
+        if response:
+            data = response.data
+            action = data.action
+            action_code = action.action_code
+            print(f"ActionCode: {action_code}", data)
+            if action_code == 'hang':  # 挂断
+                action_content = action.action_content
+                self.to_hang(action_content)
+            elif action_code == 'transfer':  # 转人工
+                self.transfer_people()
+            elif action_code == 'normal':  # 正常通话
+                contents = data.contents
+                for content in contents:
+                    if content.content_type == 'voice' and content.voice_url:
+                        self.call_agent.player_queue.put(content.voice_url)
+                # 等待几秒以后重新调用文本机器人接口
+                wait_time = data.wait_time
+                print(f"成功获取响应: ActionCode: {action_code}, WaitTime: {wait_time}")
+        else:
+            print("文本机器人接口调用失败")
+
+    def to_quest(self, request: BotChatRequest):
+        # 将实体类转换为JSON字符串
+        headers = {'Content-Type': 'application/json'}
+        request_json = request.to_json_string()
+        # 发送POST请求
+        try:
+            response = requests.post('http://example.com/api', data=request_json, headers=headers)  # 使用占位URL
+            if response.status_code == 200:
+                # 成功,解析响应JSON
+                response_data = response.json()
+                return ChatResponse.from_json(json.dumps(response_data))  # 转换为JSON字符串并解析
+            else:
+                # 错误处理
+                print(f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}")
+                return None
+        except requests.RequestException as e:
+            traceback.print_exc()
+            return None
+
+# 模拟接口请求返回
+    def test_request(self, params: BotChatRequest):
+        print("test_request::params=", params)
+        response_data = {
+            "node_id": "1.0",
+            "contents": [],
+            "interruptable": False,
+            "wait_time": "6",
+            "action": {
+                "action_code": "normal",
+                "action_content": "正常通话"
+            },
+            "talk_time_out": ""
+        }
+
+        print("params.event_type:", params.event_type)
+        if params.event_type == '1':
+            response_data['contents'].append({
+                "content_type": "voice",
+                "content": "",
+                "voice_url": '/code/src/core/voip/test111.wav',
+                "voice_content": "五一北京到上海的高铁票还有吗?"
+            })
+        elif params.event_type == '2':
+            response_data['contents'].append({
+                "content_type": "voice",
+                "content": "",
+                "voice_url": '/code/src/core/voip/test222.wav',
+                "voice_content": "测试第二个录音文件"
+            })
+
+        response = {
+            "data": response_data,
+            "message": "success",
+            "code": 200
+        }
+        try:
+            # 转换为JSON字符串并解析
+            response_json = json.dumps(response)
+            parsed_response = ChatResponse.from_json(response_json)
+            print(f"Response in test_request: {parsed_response}")  # 确认response
+            self.handling_release(parsed_response)
+        except Exception as e:
+            print(f"Error in test_request: {e}")
+            traceback.print_exc()  # 打印完整的错误信息
+            return None
 
 class BotAgent:
 
@@ -259,8 +383,8 @@ class BotAgent:
         ep_cfg.uaConfig.maxCalls = 10
         ep_cfg.uaConfig.maxAccounts = 10
         ep_cfg.medConfig.noVad = True
-        ep_cfg.logConfig.level = 5
-        ep_cfg.logConfig.consoleLevel = 5
+        ep_cfg.logConfig.level = 4
+        ep_cfg.logConfig.consoleLevel = 4
         self.ep.libCreate()
         self.ep.libInit(ep_cfg)
 
@@ -285,9 +409,9 @@ class BotAgent:
             acfg.regConfig.retryIntervalSec = 10  # 重试间隔时间(秒)
             acfg.regConfig.firstRetryIntervalSec = 10  # 首次重试间隔时间(秒)
 
-            # acfg.natConfig.iceEnabled = True
-            # acfg.natConfig.turnEnabled = True
-            # acfg.natConfig.turnServer = "stun:192.168.100.159:3478"
+            acfg.natConfig.iceEnabled = True
+            acfg.natConfig.turnEnabled = True
+            acfg.natConfig.turnServer = "stun:192.168.100.159:3478"
             # acfg.natConfig.turnUsername = "username"
             # acfg.natConfig.turnPassword = "password"