|
@@ -12,10 +12,14 @@ import pjsua2 as pj
|
|
from enum import Enum
|
|
from enum import Enum
|
|
from src.core.voip.constant import *
|
|
from src.core.voip.constant import *
|
|
|
|
|
|
|
|
+import requests
|
|
|
|
+from src.core.callcenter.model import BotChatRequest, ChatResponse
|
|
|
|
+
|
|
|
|
+
|
|
calls = {}
|
|
calls = {}
|
|
# recording_file = '/code/src/core/voip/incoming_call.wav'
|
|
# 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):
|
|
class BotStatus(Enum):
|
|
# 等待用户讲话,未超时
|
|
# 等待用户讲话,未超时
|
|
@@ -68,12 +72,15 @@ class MyAudioMediaPort(pj.AudioMediaPort):
|
|
if asr_text and not play_complete:
|
|
if asr_text and not play_complete:
|
|
self.user_asr_texts.append(asr_text)
|
|
self.user_asr_texts.append(asr_text)
|
|
if asr_text and play_complete:
|
|
if asr_text and play_complete:
|
|
|
|
+ self.cur_player_file = None
|
|
self.user_asr_texts.append(asr_text)
|
|
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)
|
|
user_asr_text = asr_text if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
|
|
self.user_asr_texts.clear()
|
|
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()
|
|
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):
|
|
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)
|
|
print('onFrameReceived:player_queue_size=', player_queue_size, 'play_complete=', play_complete)
|
|
self.cur_player_file = self.get_player_file()
|
|
self.cur_player_file = self.get_player_file()
|
|
@@ -154,6 +161,7 @@ class MyCall(pj.Call):
|
|
self.player = None
|
|
self.player = None
|
|
self.asr = None
|
|
self.asr = None
|
|
|
|
|
|
|
|
+
|
|
self.scripts = build_demo_script()
|
|
self.scripts = build_demo_script()
|
|
self.user_asr_text_queue = queue.Queue(maxsize=100)
|
|
self.user_asr_text_queue = queue.Queue(maxsize=100)
|
|
self.player_queue = queue.Queue(maxsize=3)
|
|
self.player_queue = queue.Queue(maxsize=3)
|
|
@@ -181,9 +189,9 @@ class MyCall(pj.Call):
|
|
# pj.PJSIP_INV_STATE_CONFIRMED
|
|
# pj.PJSIP_INV_STATE_CONFIRMED
|
|
# pj.PJSIP_INV_STATE_DISCONNECTED
|
|
# 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:
|
|
if call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
|
|
print("通话结束")
|
|
print("通话结束")
|
|
@@ -201,7 +209,7 @@ class MyCall(pj.Call):
|
|
try:
|
|
try:
|
|
# 建立双向通道
|
|
# 建立双向通道
|
|
self.receive_user_speaker()
|
|
self.receive_user_speaker()
|
|
- self.bot_say_hello()
|
|
|
|
|
|
+ # self.bot_say_hello()
|
|
except Exception as e:
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
traceback.print_exc()
|
|
|
|
|
|
@@ -220,7 +228,7 @@ class MyCall(pj.Call):
|
|
self.player.startTransmit(self.aud_med)
|
|
self.player.startTransmit(self.aud_med)
|
|
|
|
|
|
def on_receiver_asr_result(self, message, *args):
|
|
def on_receiver_asr_result(self, message, *args):
|
|
- print(message)
|
|
|
|
|
|
+ print('asr返回内容:',message)
|
|
self.user_asr_text_queue.put(message)
|
|
self.user_asr_text_queue.put(message)
|
|
|
|
|
|
def on_media_player_complete(self, player_id):
|
|
def on_media_player_complete(self, player_id):
|
|
@@ -229,15 +237,131 @@ class MyCall(pj.Call):
|
|
|
|
|
|
def bot_say_hello(self):
|
|
def bot_say_hello(self):
|
|
print('bot_say_hello, come in ')
|
|
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 调用文本机器人接口
|
|
# 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:
|
|
class BotAgent:
|
|
|
|
|
|
@@ -259,8 +383,8 @@ class BotAgent:
|
|
ep_cfg.uaConfig.maxCalls = 10
|
|
ep_cfg.uaConfig.maxCalls = 10
|
|
ep_cfg.uaConfig.maxAccounts = 10
|
|
ep_cfg.uaConfig.maxAccounts = 10
|
|
ep_cfg.medConfig.noVad = True
|
|
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.libCreate()
|
|
self.ep.libInit(ep_cfg)
|
|
self.ep.libInit(ep_cfg)
|
|
|
|
|
|
@@ -285,9 +409,9 @@ class BotAgent:
|
|
acfg.regConfig.retryIntervalSec = 10 # 重试间隔时间(秒)
|
|
acfg.regConfig.retryIntervalSec = 10 # 重试间隔时间(秒)
|
|
acfg.regConfig.firstRetryIntervalSec = 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.turnUsername = "username"
|
|
# acfg.natConfig.turnPassword = "password"
|
|
# acfg.natConfig.turnPassword = "password"
|
|
|
|
|