You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

419 lines
17KB

  1. from flask_restful import Resource, reqparse
  2. from flask import jsonify,request
  3. from bridge.context import ContextType
  4. import requests,json
  5. from wechat import gewe_chat
  6. from voice.ali.ali_voice import AliVoice
  7. from common import utils,redis_helper,memory,kafka_helper
  8. from common.log import logger
  9. import openai
  10. import xml.etree.ElementTree as ET
  11. import os
  12. from voice import audio_convert
  13. class MessagesResource(Resource):
  14. def __init__(self):
  15. self.parser = reqparse.RequestParser()
  16. def post(self):
  17. msg = request.get_json()
  18. logger.info(f"Received message: {msg}")
  19. # if msg and 'Data' not in msg:
  20. # logger.warning(f"未知消息")
  21. # return jsonify({"message": "未知消息"})
  22. type_name =msg.get("TypeName")
  23. app_id = msg.get("Appid")
  24. token_id = "f828cb3c-1039-489f-b9ae-7494d1778a15"
  25. if type_name=='AddMsg':
  26. wxid = msg.get("Wxid")
  27. msg_data = msg.get("Data")
  28. msg_type = msg_data.get("MsgType",None)
  29. from_wxid = msg_data["FromUserName"]["string"]
  30. to_wxid = msg_data["ToUserName"]["string"]
  31. message_hash_key = f"__AI_OPS_WX__:MESSAGES:{wxid}"
  32. handlers = {
  33. 1: handle_text,
  34. 3: handle_image,
  35. 34: handle_voice,
  36. 49: handle_xml,
  37. 37: handle_add_friend_info
  38. }
  39. handler = handlers.get(msg_type)
  40. if handler:
  41. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, message_hash_key)
  42. else:
  43. logger.warning(f"消息类型{msg_type}未处理")
  44. elif type_name=='ModContacts':
  45. '''
  46. 好友通过验证及好友资料变更的通知消息
  47. '''
  48. msg_data = msg.get("Data")
  49. handle_mod_contacts(token_id,app_id,msg_data)
  50. elif type_name=="Offline":
  51. '''
  52. 掉线通知
  53. '''
  54. elif type_name=="DelContacts":
  55. '''
  56. 删除好友通知
  57. 退出群聊
  58. '''
  59. else:
  60. logger.warning(f"不知道消息类型")
  61. return jsonify({"message": "Unsupported message type"})
  62. def handle_text(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key):
  63. msg_content=msg_data["Content"]["string"]
  64. if wxid == from_wxid: #主动发送消息
  65. logger.info("Active message sending detected")
  66. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
  67. callback_to_user=msg_data["ToUserName"]["string"]
  68. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  69. input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  70. kafka_helper.kafka_client.produce_message(input_message)
  71. logger.info("发送对话 %s",input_message)
  72. else:
  73. callback_to_user=msg_data["FromUserName"]["string"]
  74. prompt={"role": "user", "content": [{
  75. "type": "text",
  76. "text": msg_content
  77. }]}
  78. messages_to_send=get_messages_from_cache(hash_key, prompt)
  79. # 收到的对话
  80. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  81. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  82. kafka_helper.kafka_client.produce_message(input_message)
  83. logger.info("发送对话 %s",input_message)
  84. cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  85. if cache_data and cache_data.get('interactive'):
  86. messages_to_send=[{"role": "user", "content": msg_content}]
  87. res=fast_gpt_api(messages_to_send,wxid)
  88. reply_content=res["choices"][0]["message"]["content"]
  89. description = ''
  90. userSelectOptions = []
  91. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  92. for item in reply_content:
  93. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  94. params = item["interactive"]["params"]
  95. description = params.get("description")
  96. userSelectOptions = params.get("userSelectOptions", [])
  97. values_string = "\n".join(option["value"] for option in userSelectOptions)
  98. if description is not None:
  99. memory.USER_INTERACTIVE_CACHE[wxid] = {
  100. "interactive":True
  101. }
  102. reply_content=description + '------------------------------\n'+values_string
  103. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  104. memory.USER_INTERACTIVE_CACHE[wxid] = {
  105. "interactive":False
  106. }
  107. text=''
  108. for item in reply_content:
  109. if item["type"] == "text":
  110. text=item["text"]["content"]
  111. if text=='':
  112. # 去除上次上一轮对话再次请求
  113. cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  114. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  115. if len(cache_messages) >= 3:
  116. cache_messages = cache_messages[:-3]
  117. redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  118. messages_to_send=get_messages_from_cache(hash_key, prompt)
  119. res=fast_gpt_api(messages_to_send,wxid)
  120. reply_content=res["choices"][0]["message"]["content"]
  121. else:
  122. reply_content=text
  123. else:
  124. memory.USER_INTERACTIVE_CACHE[wxid] = {
  125. "interactive":False
  126. }
  127. reply_content=res["choices"][0]["message"]["content"]
  128. # print(f'token_id {token_id}')
  129. # print(f'app_id {app_id}')
  130. # print(f'touser: {callback_to_user}')
  131. # # print(f'towxuser:{towxuser}')
  132. # print(reply_content)
  133. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  134. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  135. # 回复的对话
  136. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  137. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  138. kafka_helper.kafka_client.produce_message(input_message)
  139. logger.info("发送对话 %s",input_message)
  140. def handle_image(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key):
  141. msg_content=msg_data["Content"]["string"]
  142. callback_to_user=from_wxid
  143. hash_key = f"__AI_OPS_WX__:MESSAGES:{wxid}"
  144. wx_img_url=gewe_chat.wxchat.download_image_msg(token_id,app_id,msg_content)
  145. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  146. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  147. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  148. oss_bucket_name="cow-agent"
  149. oss_prefix="cow"
  150. img_url=utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
  151. prompt={
  152. "role": "user",
  153. "content": [{
  154. "type": "image_url",
  155. "image_url": {"url": img_url}
  156. }]
  157. }
  158. get_messages_from_cache(hash_key, prompt)
  159. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,'已经上传了图片,有什么可以为您服务')
  160. logger.debug(f"Uploaded image URL: {img_url}")
  161. wx_content_dialogue_message=[{"type": "image_url", "image_url": {"url": img_url}}]
  162. input_message=utils.dialogue_message(wxid,callback_to_user,wx_content_dialogue_message)
  163. kafka_helper.kafka_client.produce_message(input_message)
  164. logger.info("发送对话 %s",input_message)
  165. def handle_voice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key):
  166. callback_to_user=from_wxid
  167. msg_content=msg_data["Content"]["string"]
  168. msg_id=msg_data["MsgId"]
  169. file_url=gewe_chat.wxchat.download_audio_msg(token_id,app_id,msg_id,msg_content)
  170. react_silk_path=utils.save_to_local_from_url(file_url)
  171. react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
  172. audio_convert.any_to_wav(react_silk_path,react_wav_path)
  173. react_voice_text=AliVoice().voiceToText(react_wav_path)
  174. messages=get_messages_from_cache(hash_key, {"role": "user", "content": react_voice_text})
  175. ai_res=fast_gpt_api(messages,wxid)
  176. ai_res_content=ai_res["choices"][0]["message"]["content"]
  177. reply_text_voice=AliVoice().textToVoice(ai_res_content)
  178. reply_text_voice_path=os.path.join(os.getcwd(), reply_text_voice)
  179. reply_silk_path = os.path.splitext(reply_text_voice_path)[0] + ".silk"
  180. reply_silk_during=audio_convert.any_to_sil(reply_text_voice_path,reply_silk_path)
  181. # print(int(reply_silk_during))
  182. # print(reply_silk_path)
  183. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  184. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  185. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  186. oss_bucket_name="cow-agent"
  187. oss_prefix="cow"
  188. file_path=reply_silk_path
  189. file_url = utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, file_path, oss_prefix)
  190. print(file_url)
  191. res=gewe_chat.wxchat.post_voice(token_id,app_id,callback_to_user,file_url,int(reply_silk_during))
  192. # 删除临时文件
  193. os.remove(react_silk_path)
  194. os.remove(react_wav_path)
  195. os.remove(reply_text_voice_path)
  196. os.remove(reply_silk_path)
  197. get_messages_from_cache(hash_key, {"role": "assistant", "content": ai_res_content})
  198. def handle_xml(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key):
  199. msg_content_xml=msg_data["Content"]["string"]
  200. root = ET.fromstring(msg_content_xml)
  201. type_value = root.find(".//appmsg/type").text
  202. handlers = {
  203. 57: handle_xml_reference,
  204. }
  205. handler = handlers.get(type_value)
  206. if handler:
  207. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key)
  208. else:
  209. print(f"xml消息 {type_value} 未解析")
  210. def handle_xml_reference(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key):
  211. '''
  212. 判断此类消息的逻辑:$.Data.MsgType=49 并且 解析$.Data.Content.string中的xml msg.appmsg.type=57
  213. '''
  214. callback_to_user=from_wxid
  215. msg_content= msg_data["PushContent"]
  216. prompt={"role": "user", "content": [{
  217. "type": "text",
  218. "text": msg_content
  219. }]}
  220. # 收到的对话
  221. messages_to_send=get_messages_from_cache(hash_key, prompt)
  222. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  223. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  224. kafka_helper.kafka_client.produce_message(input_message)
  225. logger.info("发送对话 %s",input_message)
  226. # 回复的对话
  227. res=fast_gpt_api(messages_to_send,wxid)
  228. reply_content=res["choices"][0]["message"]["content"]
  229. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  230. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  231. kafka_helper.kafka_client.produce_message(input_message)
  232. logger.info("发送对话 %s",input_message)
  233. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  234. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  235. def handle_add_friend_info(token_id,app_id, wxid,msg_data,from_wxid, to_wxid, hash_key):
  236. '''
  237. 好友添加请求通知
  238. '''
  239. msg_content_xml=msg_data["Content"]["string"]
  240. root = ET.fromstring(msg_content_xml)
  241. msg_content = root.attrib.get('content', None)
  242. message_hash_key = f"__AI_OPS_WX__:MESSAGES:{msg_data["ToUserName"]["string"]}"
  243. prompt={"role": "user", "content": [{"type": "text","text": msg_content}]}
  244. messages_to_send=get_messages_from_cache(message_hash_key, prompt)
  245. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  246. input_message=utils.dialogue_message(wxid,msg_data["ToUserName"]["string"],input_wx_content_dialogue_message)
  247. kafka_helper.kafka_client.produce_message(input_message)
  248. logger.info("发送对话 %s",input_message)
  249. # # 更改通信方向
  250. to_wxid=wxid
  251. wxid=msg_data["ToUserName"]["string"]
  252. res=fast_gpt_api(messages_to_send,wxid)
  253. reply_content=res["choices"][0]["message"]["content"]
  254. #保存好友信息
  255. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id, wxid,[to_wxid])
  256. # 保存到缓存
  257. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  258. # 发送信息
  259. gewe_chat.wxchat.post_text(token_id,app_id, to_wxid,reply_content)
  260. # 发送到kafka
  261. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  262. input_message=utils.dialogue_message(wxid,to_wxid,input_wx_content_dialogue_message,True)
  263. kafka_helper.kafka_client.produce_message(input_message)
  264. logger.info("发送对话 %s",input_message)
  265. def handle_mod_contacts(token_id,app_id,msg_data):
  266. '''
  267. 好友通过验证及好友资料变更的通知消息
  268. '''
  269. contact_wxid = msg_data["UserName"]["string"]
  270. keys=redis_helper.redis_helper.client.keys('__AI_OPS_WX__:LOGININFO:*')
  271. wxid=""
  272. for k in keys:
  273. # 将字节字符串转换为普通字符串
  274. # key_set.add(key.decode('utf-8'))
  275. key=k.decode('utf-8')
  276. hash_key=f"__AI_OPS_WX__:LOGININFO:{key}"
  277. cache_app_id=redis_helper.redis_helper.get_hash_field(hash_key,"appId")
  278. if app_id==cache_app_id:
  279. wxid=redis_helper.redis_helper.get_hash_field(hash_key,"wxid")
  280. break
  281. if wxid!="":
  282. hash_key=f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  283. cache_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  284. cache = json.loads(cache_str) if cache_str else []
  285. for c in cache:
  286. if c["userName"]==contact_wxid:
  287. c["nickName"] = msg_data["NickName"]
  288. c["snsBgImg"] = msg_data["SnsBgimgId"]
  289. c["smallHeadImgUrl"] = msg_data["SmallHeadImgUrl"]
  290. # 更新缓存,如果有修改过的话
  291. if cache:
  292. updated_cache_str = json.dumps(cache)
  293. redis_helper.redis_helper.set_hash_field(hash_key, "data", updated_cache_str)
  294. def get_messages_from_cache(hash_key,object:object)->object:
  295. messages=redis_helper.redis_helper.get_hash(hash_key)
  296. wxid=hash_key.split(':')[-1]
  297. if not messages:
  298. messages=[{"role": "system", "content": ""}]
  299. messages.append(object)
  300. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  301. else:
  302. messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  303. messages = json.loads(messages_str) if messages_str else []
  304. #判断是否含有图片
  305. last_message = messages[-1]
  306. # print(last_message)
  307. # print('~~~~~~~~~~')
  308. # 获取 content 并判断其是否为列表
  309. content = last_message.get("content", [])
  310. if isinstance(content, list) and content:
  311. last_content_type = content[-1].get("type")
  312. if last_content_type == 'image_url':
  313. content.append(object['content'][0])
  314. messages[-1]['content']=content
  315. else:
  316. messages.append(object)
  317. else:
  318. messages.append(object)
  319. # messages.append({"role": "user", "content": msg_content})
  320. #messages.append(object)
  321. # print(messages)
  322. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  323. return messages
  324. def fast_gpt_api(messages:list,session_id:str):
  325. #api_key="sk-tdi7u0zuLsR0JpPMGBeFZxymOpL0zoFVafX8EEEvEakIDAGQ22NyQ6w"
  326. api_key="sk-uJDBdKmJVb2cmfldGOvlIY6Qx0AzqWMPD3lS1IzgQYzHNOXv9SKNI"
  327. api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
  328. headers = {
  329. "Content-Type": "application/json",
  330. "Authorization": f"Bearer {api_key}"
  331. }
  332. data={
  333. "model": "",
  334. "messages":messages,
  335. "chatId": session_id,
  336. "detail": True
  337. }
  338. print(json.dumps(data,ensure_ascii=False))
  339. logger.info("[CHATGPT] 请求={}".format(json.dumps(data, ensure_ascii=False)))
  340. response = requests.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600)
  341. response.raise_for_status()
  342. response_data = response.json()
  343. logger.info("[CHATGPT] 响应={}".format(json.dumps(response_data, separators=(',', ':'),ensure_ascii=False)))
  344. print(response_data)
  345. return response_data