bot.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. #!/usr/bin/env python3
  2. # encoding:utf-8
  3. import ctypes
  4. import os
  5. import time
  6. import json
  7. import wave
  8. import queue
  9. import threading
  10. import traceback
  11. import sys
  12. from random import randint
  13. import pjsua2 as pj
  14. from enum import Enum
  15. from datetime import datetime
  16. from multiprocessing import Process
  17. from apscheduler.schedulers.background import BackgroundScheduler
  18. from src.core.callcenter import registry
  19. from src.core.callcenter.cache import Cache
  20. from src.core.callcenter.enumeration import HangupDir
  21. from src.core.datasource import SIP_SERVER, SERVE_HOST
  22. from src.core.voip.constant import *
  23. import requests
  24. from src.core.callcenter.api import BotChatRequest,ChatMessage
  25. from src.core import singleton_keys
  26. from src.core.callcenter.snowflake import Snowflake
  27. from src.core.callcenter.data_handler import *
  28. calls = {}
  29. # recording_file = '/code/src/core/voip/incoming_call.wav'
  30. # player_file1 = '/code/src/core/voip/test111.wav'
  31. # player_file2 = '/code/src/core/voip/test222.wav'
  32. from src.core.voip.asr import TestSt, XfAsr
  33. class BotStatus(Enum):
  34. # 等待用户讲话,未超时
  35. wait_speaking_not_timeout = 1
  36. # 等待用户讲话,已超时但用户仍在持续讲话
  37. wait_speaking_timeout_user_speaking = 2
  38. # 机器人讲话过程中
  39. speaking = 3
  40. # 等待用户讲话结束
  41. wait_user_speaking_end = 4
  42. # 等待大模型输出
  43. wait_gpt_return = 5
  44. class UserStatus(Enum):
  45. # ASR返回值为sentenceBegin后将状态置为Speaking
  46. speaking = 1
  47. # ASR返回值为sentenceEnd后,将状态置为Silence
  48. silence = 2
  49. class MyAudioMediaPort(pj.AudioMediaPort):
  50. def __init__(self, call, audio_media=None, asr=None):
  51. pj.AudioMediaPort.__init__(self)
  52. # # 打开一个 .pcm 文件来保存音频流(可选:保存为 .wav)
  53. # self.wav = wave.open(f"{recording_file}", "wb")
  54. # self.wav.setnchannels(1)
  55. # self.wav.setsampwidth(2) # 假设每个样本是 16 位(2 字节)
  56. # self.wav.setframerate(16000)
  57. print("MyAudioMediaPort::come in, debugger")
  58. self.call = call
  59. self.audio_media = audio_media
  60. self.asr = asr
  61. self.first = True
  62. self.user_asr_texts = []
  63. def onFrameRequested(self, frame):
  64. print("Request audio frame:", frame)
  65. def onFrameReceived(self, frame):
  66. # self.wav.writeframes(bytes(frame.buf))
  67. if self.asr: # 如果ASR实例存在,则发送音频数据
  68. if self.first:
  69. self.first = False
  70. self.call.logger.warn("Received audio frame: %s, %s, %s", self.call.session_id,frame.buf,frame.size)
  71. self.asr.send_audio(frame.buf)
  72. try:
  73. asr_text = self.get_asr_text()
  74. play_complete = self.call.is_play_complete()
  75. current_time = time.time() # 实时当前时间
  76. if self.call.inputType == '1.0':
  77. time_difference = int(current_time - self.call.inputLongStart)
  78. # print('current_time - self.call.inputLongStart:',time_difference > 35, self.call.txtLock , play_complete)
  79. if time_difference > 35 and play_complete:
  80. self.user_asr_texts.append(f"DTMF({self.call.digit})DTMF")
  81. user_asr_text = self.user_asr_texts[0] if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
  82. self.user_asr_texts.clear()
  83. self.call.chat(user_asr_text)
  84. # print("测试超长", user_asr_text)
  85. elif asr_text:
  86. self.user_asr_texts.append(asr_text)
  87. user_asr_text = self.user_asr_texts[0] if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
  88. self.user_asr_texts.clear()
  89. self.call.chat(user_asr_text)
  90. if time_difference > int(self.call.wait_time):
  91. self.call.reset_wait_time()
  92. else:
  93. if asr_text and not play_complete:
  94. self.user_asr_texts.append(asr_text)
  95. if (asr_text and play_complete) or (play_complete and self.user_asr_texts):
  96. if asr_text:
  97. self.user_asr_texts.append(asr_text)
  98. user_asr_text = self.user_asr_texts[0] if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
  99. self.user_asr_texts.clear()
  100. self.call.chat(user_asr_text)
  101. if self.call.wait_time and self.call.wait_time != "0" and play_complete and not asr_text:
  102. self.call.wait_time_check(current_time, self.call.wait_time)
  103. message_queue_size = self.call.message_queue.qsize()
  104. if (message_queue_size > 0 and not self.call.cur_player_file) or (message_queue_size > 0 and play_complete):
  105. # self.call.logger.info('onFrameReceived:message_queue_size=', message_queue_size, 'play_complete=', play_complete, asr_text)
  106. self.call.cur_player_file, self.call.wait_time, self.call.inputType,self.call.action, self.call.node_id = self.get_player_file()
  107. # 重置播放完成标志和超时计时器,确保新的播放从头开始计时
  108. self.call.reset_wait_time()
  109. self.call.send_bot_speaker(self.call.cur_player_file)
  110. except:
  111. pass
  112. def get_asr_text(self):
  113. try:
  114. asr_text = self.call.user_asr_text_queue.get(block=False)
  115. self.call.logger.info('get_asr_text: %s', asr_text)
  116. return asr_text
  117. except:
  118. pass
  119. def get_player_file(self):
  120. try:
  121. message = self.call.message_queue.get(block=False)
  122. player_file = [item.voice_url for item in message.contents if item.content_type == 'voice']
  123. return player_file, message.wait_time, message.inputType, message.action, message.node_id
  124. except Exception as e:
  125. traceback.print_exc()
  126. class MyAudioMediaPlayer(pj.AudioMediaPlayer):
  127. def __init__(self, player_id, sink, on_complete=None):
  128. pj.AudioMediaPlayer.__init__(self)
  129. self.player_id = player_id
  130. self.sink = sink
  131. self.on_complete = on_complete
  132. def onEof2(self):
  133. # self.stopTransmit(self.sink)
  134. if self.on_complete:
  135. self.on_complete(self.player_id)
  136. # Subclass to extend the Account and get notifications etc.
  137. class Account(pj.Account):
  138. def __init__(self, agent, user_part, **kwargs):
  139. pj.Account.__init__(self)
  140. self.agent = agent
  141. self.user_part = user_part
  142. self.calls = {}
  143. self.kwargs = kwargs
  144. def onRegState(self, prm):
  145. ai = self.getInfo()
  146. print("***OnRegState: " + prm.reason, ai.id,ai.isDefault,ai.uri,ai.regIsConfigured,ai.regIsActive,ai.regExpiresSec,ai.regStatus,ai.regStatusText,ai.regLastErr,ai.onlineStatus,ai.onlineStatusText)
  147. def onIncomingCall(self, prm):
  148. # self.agent.logger.info("daviddebugger::onIncomingCall::%s ", prm.callId)
  149. ai = self.getInfo()
  150. print("***onIncomingCall: ", prm.callId, ai.id,ai.isDefault,ai.uri,ai.regIsConfigured,ai.regIsActive,ai.regExpiresSec,ai.regStatus,ai.regStatusText,ai.regLastErr,ai.onlineStatus,ai.onlineStatusText)
  151. call = MyCall(self.agent, self, self.user_part, prm.callId, **self.kwargs)
  152. call_op_param = pj.CallOpParam(True)
  153. call_op_param.statusCode = pj.PJSIP_SC_OK
  154. call.answer(call_op_param)
  155. self.calls[prm.callId] = call
  156. class MyCall(pj.Call):
  157. def __init__(self, agent, acc, user_part, call_id, **kwargs):
  158. pj.Call.__init__(self, acc, call_id)
  159. self.agent = agent
  160. self.logger = agent.logger
  161. self.user_part = user_part
  162. self.call_id = call_id
  163. self.kwargs = kwargs
  164. self.audio_media = None
  165. self.audio_port = None
  166. self.audio_player = None
  167. self.asr = None
  168. self.session_id = kwargs.get('variable_sip_h_P-LIBRA-CallId')
  169. self.device_id = kwargs.get('variable_sip_h_P-LIBRA-DeviceId')
  170. self.call_phone = kwargs.get("Caller-Caller-ID-Number")
  171. self.logger.info("daviddebugger::self.session_id:%s, self.call_phone:%s", self.session_id,self.call_phone)
  172. self.taskId = "10001"
  173. self.user_asr_text_queue = queue.Queue(maxsize=100)
  174. self.message_queue = queue.Queue(maxsize=3)
  175. self.player_complete_dict = {}
  176. self.wait_time = None
  177. self.inputType = None #记录按键类型 1为长按键类型
  178. self.digit = '' # 存储长按键内容
  179. self.action = None
  180. self.node_id= 'start'
  181. self.cur_player_file = None #当前播放的文件
  182. # self.asr = TestSt(self.session_id, logger=self.logger, message_receiver=self.on_receiver_asr_result) # 创建ASR实例
  183. self.asr = XfAsr(self.session_id, logger=self.logger, message_receiver=self.on_receiver_asr_result) # 创建ASR实例
  184. self.asr.start() # 启动ASR线程
  185. self.start_time = time.time() # 当前机器人对话开始时间
  186. # 超时设置
  187. self.play_start_time = time.time() # 倒计时开始时间
  188. self.play_complete_flag = False # 倒计时开始标志
  189. self.txtLock = False
  190. self.inputLongStart = time.time() #长按键开始时间
  191. self.inter_action_total = 0
  192. self.statistics_lock = False
  193. def wait_time_check(self, current_time, wait_time):
  194. try:
  195. # 确保 wait_time 是整数类型
  196. wait_time = int(wait_time)
  197. # 如果播放尚未完成,重置标志并返回
  198. if not self.play_complete_flag:
  199. self.play_complete_flag = True
  200. self.play_start_time = current_time
  201. # print(f"开始计时: {self.play_start_time}")
  202. elapsed_time = int(current_time - self.play_start_time)
  203. # print(f"当前时间: {current_time}, 已过时间: {elapsed_time}, 最大等待时间: {wait_time}")
  204. if elapsed_time > wait_time:
  205. # self.user_asr_text_queue.put("ASR408error")
  206. self.chat('ASR408error')
  207. registry.BOT_ASR_408.labels(self.taskId).inc()
  208. self.reset_wait_time()
  209. except ValueError as e:
  210. self.logger.info(f"无效的等待时间参数: {wait_time}, 错误: {e}")
  211. self.reset_wait_time()
  212. def reset_wait_time(self):
  213. self.play_complete_flag = False # 重置播放完成标志
  214. self.play_start_time = None # 重置开始计时时间
  215. def is_play_complete(self): #语音机器人是否播放结束
  216. if self.cur_player_file:
  217. player_id = murmur3_32(self.cur_player_file)
  218. return self.player_complete_dict.get(player_id)
  219. def onDtmfDigit(self, prm):
  220. # 判断是否播放完成 否则不记录用户说的内容
  221. if not self.is_play_complete():
  222. return
  223. digit = prm.digit
  224. self.reset_wait_time()
  225. # 假设为超长类型按键 把用户输入的按键进行拼接 如果为# 则把用户输入所有按键放入队列并发送文本机器人
  226. # 如果为非正常按键服务 输入以后直接发送文本机器人
  227. if self.inputType == '1.0':
  228. if digit != '#':
  229. self.digit += digit
  230. elif digit == '#':
  231. # self.user_asr_text_queue.put(f"DTMF({self.digit})DTMF")
  232. self.chat(f"DTMF({self.digit})DTMF")
  233. else:
  234. self.user_asr_text_queue.put(f"DTMF({digit})DTMF")
  235. def onCallState(self, prm):
  236. call_info = self.getInfo()
  237. # pj.PJSIP_INV_STATE_NULL
  238. # pj.PJSIP_INV_STATE_CALLING
  239. # pj.PJSIP_INV_STATE_INCOMING
  240. # pj.PJSIP_INV_STATE_EARLY
  241. # pj.PJSIP_INV_STATE_CONNECTING
  242. # pj.PJSIP_INV_STATE_CONFIRMED
  243. # pj.PJSIP_INV_STATE_DISCONNECTED
  244. self.logger.info("daviddebugger::onCallState::[%s,%s] %s", pj.PJSIP_INV_STATE_CONFIRMED, pj.PJSIP_INV_STATE_DISCONNECTED, call_info.state)
  245. if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
  246. # 当呼叫状态为已确认(即接通)
  247. self.bot_say_hello()
  248. if call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
  249. self.logger.info("通话结束:%s", self.user_part)
  250. self.release()
  251. def onCallMediaState(self, prm):
  252. call_info = self.getInfo()
  253. # print("Call Media state: ", call_info.stateText)
  254. for media in call_info.media:
  255. if media.type == pj.PJMEDIA_TYPE_AUDIO and \
  256. (media.status == pj.PJSUA_CALL_MEDIA_ACTIVE):
  257. self.logger.info("Call Media state 111: %s", call_info.stateText)
  258. self.audio_media = self.getAudioMedia(media.index)
  259. try:
  260. # 建立双向通道
  261. self.receive_user_speaker()
  262. # self.bot_say_hello()
  263. except Exception as e:
  264. traceback.print_exc()
  265. def receive_user_speaker(self):
  266. self.audio_port = MyAudioMediaPort(self, self.audio_media, self.asr)
  267. self.audio_port.createPort("Incoming Call Port", build_audio_format())
  268. self.audio_media.startTransmit(self.audio_port)
  269. def send_bot_speaker(self, player_file):
  270. if not player_file :
  271. return
  272. if not self.isActive():
  273. return
  274. player_id = murmur3_32(player_file)
  275. self.player_complete_dict[player_id] = False
  276. for f in player_file:
  277. if not os.path.isfile(f):
  278. self.logger.info(f"Sending bot speaker, not exists, player_file: {player_file}, player_id: {player_id}, isActive: {self.isActive()}")
  279. return
  280. key = murmur3_32(str(datetime.now().timestamp()))
  281. self.agent.call_players[key] = [datetime.now().timestamp()]
  282. # print('self.player_complete_dict[player_id]D:', player_id, player_file, self.player_complete_dict[player_id])
  283. self.logger.info(f"Sending bot speaker, 111, player_file: {player_file}, player_id: {player_id}, isActive: {self.isActive()}")
  284. self.audio_player = MyAudioMediaPlayer(player_id, self.audio_media, on_complete=self.on_media_player_complete)
  285. if len(player_file) == 1:
  286. self.logger.info(f"Sending bot speaker, 222, player_file: {player_file}, player_id: {player_id}, isActive: {self.isActive()}")
  287. self.audio_player.createPlayer(player_file[0], pj.PJMEDIA_FILE_NO_LOOP)
  288. else:
  289. self.logger.info(f"Sending bot speaker, 333, player_file: {player_file}, player_id: {player_id}, isActive: {self.isActive()}")
  290. self.audio_player.createPlaylist(player_file, f'my_hello_playlist{player_id}', pj.PJMEDIA_FILE_NO_LOOP)
  291. self.logger.info(f"Sending bot speaker, 444, player_file: {player_file}, player_id: {player_id}, isActive: {self.isActive()}")
  292. self.audio_player.startTransmit(self.audio_media)
  293. self.agent.call_players[key].append(datetime.now().timestamp())
  294. def on_receiver_asr_result(self, message, *args):
  295. # 判断是否播放完成 否则不记录用户说的内容
  296. if not self.is_play_complete():
  297. return
  298. # self.logger.info("on_receiver_asr_result:message: %s", message)
  299. if message["name"] == "SentenceEnd":
  300. self.user_asr_text_queue.put(message["result"])
  301. elif message["name"] == "TranscriptionResultChanged":
  302. self.reset_wait_time()
  303. elif message["name"] == "TranscriptionResultError":
  304. pass
  305. # message = json.loads(message)
  306. # if message["header"]["status"] == 20000000:
  307. # if message["header"]["name"] == "SentenceEnd":
  308. # result = message["payload"]["result"]
  309. # # self.logger.info("asr返回内容Result:%s", result)
  310. # self.user_asr_text_queue.put(result)
  311. # elif message["header"]["name"] == "TranscriptionResultChanged":
  312. # self.reset_wait_time()
  313. # else:
  314. # self.logger.info(f"Status is not {message['header']['status']}")
  315. # registry.ASR_ERRORS.labels(message['header']['status']).inc()
  316. def on_media_player_complete(self, player_id):
  317. self.logger.info('player complete')
  318. self.player_complete_dict[player_id] = True
  319. self.digit = ''
  320. self.inputLongStart = time.time()
  321. #播放完毕执行的动作
  322. self.say_end_action(self.action)
  323. def bot_say_hello(self):
  324. self.chat(user_asr_text="start")
  325. def chat(self, user_asr_text=None):
  326. # 调用文本机器人接口
  327. ToTextBotAgent(user_asr_text,self)
  328. def say_end_action(self, action):
  329. self.logger.info('handling_release %s', action.action_code)
  330. action_code = action.action_code
  331. if action_code == 'hang': # 挂断
  332. self.agent.hangup(self.user_part)
  333. self.end_statistics()
  334. # 更新通话记录
  335. self.agent.dataHandleServer.update_record(self.session_id, hangup_dir=HangupDir.ROBOT_HANGUP.code)
  336. elif action_code == 'transfer': # 转人工
  337. self.agent.transfer(user_part=self.user_part, call_id=self.session_id, device_id=self.device_id)
  338. self.end_statistics()
  339. #更新通话记录
  340. self.agent.dataHandleServer.update_record(self.session_id, service_category=2)
  341. def end_statistics(self):
  342. if not self.statistics_lock:
  343. self.statistics_lock = True
  344. self.logger.info(f"self.inter_action_total:{self.inter_action_total}")
  345. latency = (time.time() - self.start_time)
  346. registry.BOT_CALL_DURATION.labels(self.taskId).observe(latency)
  347. registry.BOT_INTERACTION_ROUNDS.labels(self.taskId).observe(self.inter_action_total)
  348. def release(self):
  349. self.logger.info('liuwei::debugger::release:: come in ')
  350. if self.audio_player:
  351. try:
  352. self.audio_player.stopTransmit(self.audio_media)
  353. print("Success to stopTransmit:")
  354. except pj.Error as e:
  355. print("Failed to stopTransmit:", e)
  356. del self.audio_player
  357. self.audio_player = None # 或调用播放器停止方法
  358. if self.audio_port:
  359. try:
  360. self.audio_media.stopTransmit(self.audio_port)
  361. print("Success to stopTransmit:")
  362. except pj.Error as e:
  363. print("Failed to stopTransmit:", e)
  364. del self.audio_port
  365. self.audio_port = None # 或调用相关销毁方法
  366. if self.audio_media:
  367. # self.audio_media.stopTransmit()
  368. del self.audio_media
  369. self.audio_media = None
  370. self.asr.close()
  371. # 远程挂机之后要将分机号回收
  372. self.agent.hangup(self.user_part)
  373. # self.agent.release(self.user_part)
  374. self.end_statistics()
  375. class ToTextBotAgent:
  376. def __init__(self, user_asr_text, call_agent):
  377. if not user_asr_text or (call_agent.action and call_agent.action.action_code != 'normal'):
  378. # print("ASR文本为空,终止执行。")
  379. return
  380. self.call_agent = call_agent
  381. self.request_data = BotChatRequest(
  382. nodeId=self.call_agent.node_id,
  383. userId=self.call_agent.call_phone,
  384. sessionId= self.call_agent.session_id,
  385. recordId="",
  386. taskId=self.call_agent.taskId,
  387. asrText=user_asr_text,
  388. ext= None
  389. )
  390. self.call_agent.logger.info("user_asr_text发送结果: %s", user_asr_text)
  391. if user_asr_text != 'ASR408error':
  392. self.call_agent.inter_action_total += 1
  393. # 发送请求并处理响应
  394. self.to_request(self.request_data)
  395. # self.to_quest(self.request_data)
  396. def to_request(self, request: BotChatRequest, try_count = 3):
  397. start_time = time.time()
  398. request_data = request.to_json_string()
  399. response_data = None
  400. try:
  401. message = None
  402. url = f"http://{SERVE_HOST}:40072/botservice"
  403. headers = {"Content-Type": "application/json"}
  404. while try_count > 0:
  405. once_start = time.time()
  406. try:
  407. response = requests.post(url, data=request_data, headers=headers, timeout=3)
  408. if response and response.ok:
  409. response_data = response.json()
  410. if "data" in response_data and response_data["code"] == 0:
  411. data = response_data["data"]
  412. message = ChatMessage.from_json(data)
  413. self.call_agent.message_queue.put(message)
  414. break
  415. else:
  416. self.call_agent.logger.info(f"to_request::failed, sessionId={request.sessionId}, response_data:{response_data}")
  417. else:
  418. self.call_agent.logger.info(f"to_request::请求失败,sessionId:{request.sessionId}, 状态码: {response.status_code if response else None}, 响应内容: {response.text if response else None}")
  419. except Exception as e:
  420. traceback.print_exc()
  421. self.call_agent.logger.error(f"to_request::exception, TaskId={request.taskId}, sessionId={request.sessionId}, 请求发生异常: {e}")
  422. finally:
  423. try_count = try_count - 1
  424. latency = (time.time() - once_start)
  425. registry.BOT_REQUEST_ONCE_LATENCY.labels(request.taskId).observe(latency)
  426. if not message:
  427. massage = self.get_default_response()
  428. self.call_agent.message_queue.put(massage)
  429. finally:
  430. latency = (time.time() - start_time)
  431. registry.BOT_REQUEST_COUNT.inc()
  432. registry.BOT_REQUEST_LATENCY.labels(request.taskId).observe(latency)
  433. self.call_agent.logger.info(f"to_request sessionId={self.call_agent.session_id}, timeCost={latency}, request:{request_data}, response:{response_data if response_data else None}")
  434. # def to_quest(self, request: BotChatRequest, try_count = 3):
  435. # start_time = time.time()
  436. # request_data = request.to_json_string()
  437. # response = None
  438. # try:
  439. # url = f"http://{SERVE_HOST}:40072/botservice"
  440. # # payload = request.to_json_string()
  441. # # self.call_agent.logger.info(f"请求数据:{request_data},url:{url}")
  442. # with requests.Session() as session:
  443. # message = None
  444. # # try:
  445. # session.headers.update({'Content-Type': 'application/json'})
  446. # while try_count > 0:
  447. # once_start = time.time()
  448. # try:
  449. # response = session.post(url=url, json=request_data, timeout=3)
  450. # # response = requests.post(url=url, json=json.loads(request_data), headers=headers, timeout=10) # 使用占位URL
  451. # # self.call_agent.logger.info("to_request come in , try_count=%s", try_count)
  452. # if response.status_code == 200:
  453. # response_data = response.json()
  454. # if "data" in response_data and response_data["code"]==0:
  455. # data = response_data["data"]
  456. # message = ChatMessage.from_json(data)
  457. # self.call_agent.message_queue.put(message)
  458. # break
  459. # else:
  460. # self.call_agent.logger.info(f"to_request::sessionId:{request.sessionId}, 响应中没有 'data' 字段")
  461. # else:
  462. # self.call_agent.logger.info(f"to_request::请求失败,sessionId:{request.sessionId}, 状态码: {response.status_code}, 响应内容: {response.text}")
  463. # except Exception as e:
  464. # traceback.print_exc()
  465. # self.call_agent.logger.error(f"to_request::TaskId={request.taskId}, sessionId={request.sessionId}, 请求发生异常: {e}, URL: {url}")
  466. # finally:
  467. # try_count = try_count - 1
  468. # latency = (time.time() - once_start)
  469. # registry.BOT_REQUEST_ONCE_LATENCY.labels(request.taskId).observe(latency)
  470. #
  471. # self.call_agent.logger.info(f"to_request::sessionId:{request.sessionId}, message:{message.to_json_string() if message else None}")
  472. # if not message:
  473. # message = self.get_default_response()
  474. # self.call_agent.message_queue.put(message)
  475. # # finally:
  476. # # session.close()
  477. # finally:
  478. # latency = (time.time() - start_time)
  479. # registry.BOT_REQUEST_COUNT.inc()
  480. # registry.BOT_REQUEST_LATENCY.labels(request.taskId).observe(latency)
  481. # self.call_agent.logger.info(f"to_request::sessionId={ self.call_agent.session_id}, timeCost={latency}, request:{request_data}, response:{response.text if response else None}")
  482. def get_default_response(self):
  483. response= {
  484. "node_id": "99.00",
  485. "contents": [
  486. {
  487. "content_type": "voice",
  488. "content": "正在为您转接人工服务,请稍后",
  489. "voice_url": "/root/aibot/dm/voice/transfer.wav",
  490. "voice_content": ""
  491. }
  492. ],
  493. "wait_time": 1,
  494. "action": {
  495. "action_code": "transfer",
  496. "action_content": "转人工"
  497. },
  498. "inputType": "0"
  499. }
  500. parsed_response = ChatMessage.from_json(response)
  501. return parsed_response
  502. @singleton_keys
  503. class BotAgent:
  504. def __init__(self, app, user_part_range=range(1001, 1011), host=SIP_SERVER, port="5060", password="slibra@#123456"):
  505. self.app = app
  506. self.logger = app.logger
  507. self.user_part_range, self.host, self.port, self.password = user_part_range, host, port, password
  508. self.user_part_pool = queue.Queue(maxsize=len(user_part_range))
  509. self.accounts = {}
  510. self.calls = {}
  511. self.call_players={}
  512. self.ep = pj.Endpoint()
  513. self.daemon_stopping = False
  514. self.is_stopping = False
  515. self.counter = 0
  516. self.acd_service = None
  517. self.cache = Cache(app)
  518. self.dataHandleServer = DataHandleServer(app)
  519. self.pjsua_thread = None
  520. self._start()
  521. # threading.Thread(target=self.main_thread_daemon).start()
  522. self.daemon_job_scheduler = BackgroundScheduler()
  523. self.daemon_job_scheduler.add_job(self._main_thread_daemon, 'interval', seconds=1, max_instances=1, name='bot_agent_daemon')
  524. self.daemon_job_scheduler.start()
  525. class AsyncJob(pj.PendingJob):
  526. def __init__(self, agent):
  527. self.agent = agent
  528. super().__init__()
  529. agent.logger.warn("Job created id: %s", id(self))
  530. def execute(self, is_pending):
  531. self.agent.logger.warn("Executing job value: %s", is_pending)
  532. time.sleep(1)
  533. self.agent.ep.utilAddPendingJob(self)
  534. def __del__(self):
  535. self.agent.logger.warn("Job deleted id:%s", id(self))
  536. def _add_new_job(self):
  537. self.logger.warn("Creating job 1")
  538. job = self.AsyncJob(self)
  539. self.logger.warn("Adding job 1")
  540. self.ep.utilAddPendingJob(job)
  541. def _create_pjsua2(self, timeout_sec=86400):
  542. start_time = time.time()
  543. try:
  544. self.cache.set_register_per_hours(expire=timeout_sec - (60*3))
  545. # Create and initialize the library
  546. ep_cfg = build_ep_config()
  547. self.ep.libCreate()
  548. self.ep.libInit(ep_cfg)
  549. aud_dev_mgr = self.ep.audDevManager()
  550. aud_dev_mgr.setNullDev() # 使用虚拟音频设备(如果没有实际设备)
  551. # Set up media configuration, particularly jitter buffer
  552. media_cfg = build_media_config()
  553. self.ep.medConfig = media_cfg # Apply media config to endpoint
  554. # Create SIP transport. Error handling sample is shown
  555. sipTpConfig = build_sip_transport_config()
  556. self.ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, sipTpConfig)
  557. # Start the library
  558. self.ep.libStart()
  559. self._add_new_job()
  560. for user_part in self.user_part_range:
  561. acfg = build_account_config(self.host, self.port, user_part, self.password, timeout_sec)
  562. # Create the account
  563. acc = Account(self, user_part=user_part)
  564. acc.create(acfg)
  565. self.user_part_pool.put(user_part)
  566. self.accounts[user_part] = acc
  567. except:
  568. traceback.print_exc()
  569. finally:
  570. latency = time.time() - start_time
  571. registry.BOT_CREATE_ACCOUNT_LATENCY.observe(latency)
  572. self.logger.info("create pjsua latency: %.3fs", latency)
  573. while not self.is_stopping:
  574. registry.BOT_AGENT_LIVES.set(self.user_part_pool.qsize())
  575. self.ep.libHandleEvents(200)
  576. self.call_players.clear()
  577. self.accounts.clear()
  578. self.calls.clear()
  579. # Destroy the library
  580. self.ep.libDestroy()
  581. self.logger.info("create pjsua already shutdown")
  582. def _main_thread_daemon(self):
  583. # while not self.daemon_stopping:
  584. _lock = self._play_complete_degree_check()
  585. if _lock:
  586. self.logger.error("daviddebugger::play time greater than 60s, will restart")
  587. self.restart()
  588. return
  589. _lock = self.cache.get_pjsua_thread_lock()
  590. if _lock:
  591. self.cache.del_pjsua_thread_lock()
  592. self.logger.error("daviddebugger::thread is lock, will restart")
  593. self.restart()
  594. return
  595. _lock = self.cache.lock_register_per_hours()
  596. if not _lock and len(self.accounts) == len(self.user_part_range):
  597. self.logger.error("daviddebugger::register expire, will restart")
  598. self.restart()
  599. return
  600. # time.sleep(0.1)
  601. def _play_complete_degree_check(self):
  602. for k, v in list(self.call_players.items()):
  603. if len(v) == 2:
  604. self.call_players.pop(k)
  605. continue
  606. if len(v) == 1:
  607. sec = datetime.now().timestamp() - v[0]
  608. self.logger.info("daviddebugger::play_complete_degree_check, sec=%s", sec)
  609. if sec > 10:
  610. return True
  611. return False
  612. def transfer(self, user_part, call_id, device_id, service_id='00000000000000000'):
  613. if self.acd_service:
  614. self.acd_service.transfer_to_agent(call_id, device_id, service_id, hold=True)
  615. # sip_headers = {'P-LIBRA-HangUpReason': 'transferToAgent', 'P-LIBRA-ServiceId': service_id}
  616. try_count = 30
  617. while try_count >0:
  618. if self.cache.get_after_play_hold_music(call_id):
  619. self.hangup(user_part)
  620. break
  621. try_count = try_count - 1
  622. time.sleep(0.1)
  623. def hangup(self, user_part, reason="NORMAL_CLEARING", **sip_headers):
  624. call_op_param = pj.CallOpParam(True)
  625. call_op_param.statusCode = pj.PJSIP_SC_OK
  626. call_op_param.reason = reason
  627. call_op_param.txOption = pj.SipTxOption()
  628. sip_header_vector = pj.SipHeaderVector()
  629. for k, v in sip_headers.items():
  630. _sip_header = pj.SipHeader()
  631. _sip_header.hName = str(k)
  632. _sip_header.hValue = str(v)
  633. self.logger.info('hangup, header_name=%s, header_value=%s'%(k, v))
  634. sip_header_vector.push_back(_sip_header)
  635. call_op_param.txOption.headers = sip_header_vector
  636. try:
  637. acc = self.accounts.get(user_part)
  638. if acc:
  639. for k, v in list(acc.calls.items()):
  640. self.logger.info('hangup, call_idx=%s, call_active=%s'%(k, v.isActive()))
  641. if v.isActive():
  642. v.hangup(call_op_param)
  643. acc.calls.clear()
  644. except:
  645. traceback.print_exc()
  646. finally:
  647. # 机器人主动挂机回收分机号
  648. self.release(user_part)
  649. def register(self, **kwargs):
  650. self.logger.info('register,come in, pool.size :%d', self.user_part_pool.qsize())
  651. user_part = self.user_part_pool.get()
  652. acc = self.accounts.get(user_part)
  653. self.logger.info('register, user_part :%d, pool.size :%d', user_part, self.user_part_pool.qsize())
  654. if acc:
  655. self.logger.info('register==========> %s', acc.getId())
  656. # ps = pj.PresenceStatus()
  657. # ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
  658. # ps.activity = pj.PJRPID_ACTIVITY_AWAY
  659. # ps.note = "Away"
  660. # acc.setOnlineStatus(ps)
  661. # acc.setRegistration(renew=True)
  662. acc.kwargs = kwargs
  663. return user_part
  664. def unregister(self, user_part):
  665. acc = self.accounts.get(user_part)
  666. if acc:
  667. acc.setRegistration(renew=False)
  668. # 用户远程挂机回收分机号
  669. self.release(user_part)
  670. def release(self, user_part):
  671. if not user_part:
  672. self.logger.info("release, user_part is None")
  673. return
  674. def element_in_queue(q, element):
  675. with q.mutex: # 确保线程安全
  676. for item in list(q.queue): # 将队列转换为列表进行遍历
  677. if item == element:
  678. return True
  679. return False
  680. if element_in_queue(self.user_part_pool, user_part):
  681. self.logger.info("release, already exists, user_part :%d, pool.size :%d", user_part, self.user_part_pool.qsize())
  682. return
  683. self.user_part_pool.put(user_part)
  684. self.logger.info("release, user_part :%d, pool.size :%d", user_part, self.user_part_pool.qsize())
  685. def _start(self):
  686. self.is_stopping = False
  687. self.counter += 1
  688. self.pjsua_thread = threading.Thread(name=f"PJSUA-THREAD-{self.counter}", target=self._create_pjsua2, daemon=True)
  689. self.pjsua_thread.start()
  690. self.logger.info("bot agent starting ...")
  691. def restart(self):
  692. self.destroy()
  693. self.logger.info('restart, 22222')
  694. self._start()
  695. # threading.Thread(target=self.create_pjsua2, daemon=True).start()
  696. def destroy(self):
  697. self.is_stopping = True
  698. self.logger.info("destroy, come in 11111")
  699. try:
  700. while not self.user_part_pool.empty():
  701. self.user_part_pool.get_nowait()
  702. except:
  703. pass
  704. self.logger.info("destroy, come in 22222")
  705. # self.call_players.clear()
  706. # self.accounts.clear()
  707. # self.calls.clear()
  708. # # Destroy the library
  709. # self.ep.libDestroy()
  710. time.sleep(1)
  711. self.logger.info("destroy, come in 33333")
  712. if not self.pjsua_thread.is_alive():
  713. self.logger.info("destroy, pre thread already stopped")
  714. return
  715. ident = self.pjsua_thread.ident
  716. thread_id = ctypes.pythonapi.PyThreadState_SetAsyncExc
  717. res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
  718. ctypes.c_long(ident), ctypes.py_object(SystemExit)
  719. )
  720. self.logger.info("destroy, ident=%s, thread_id=%s, res=%s", ident, thread_id, res)
  721. # if res == 0:
  722. # raise ValueError("Invalid thread ID")
  723. # elif res > 1:
  724. # # 如果多次调用,需要复位
  725. # ctypes.pythonapi.PyThreadState_SetAsyncExc(ident, 0)
  726. # raise SystemError("PyThreadState_SetAsyncExc failed")
  727. def __del__(self):
  728. self.destroy()
  729. self.daemon_stopping = True
  730. # if __name__ == '__main__':
  731. # import logging
  732. # logger = logging.getLogger('voip')
  733. # bot = BotAgent(logger)