您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

messages_resource.py 18KB

3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
3 个月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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"收到微信回调消息: {msg}")
  19. type_name =msg.get("TypeName")
  20. app_id = msg.get("Appid")
  21. # token_id = "f828cb3c-1039-489f-b9ae-7494d1778a15"
  22. token_id=get_token_id_by_app_id(app_id)
  23. if token_id=="":
  24. logger.warning('找不到登录信息,不处理')
  25. return jsonify({"message": "收到微信回调消息"})
  26. if type_name=='AddMsg':
  27. wxid = msg.get("Wxid")
  28. msg_data = msg.get("Data")
  29. msg_type = msg_data.get("MsgType",None)
  30. from_wxid = msg_data["FromUserName"]["string"]
  31. to_wxid = msg_data["ToUserName"]["string"]
  32. handlers = {
  33. 1: handle_text,
  34. 3: handle_image,
  35. 34: handle_voice,
  36. 49: handle_xml,
  37. 37: handle_add_friend_notice
  38. }
  39. handler = handlers.get(msg_type)
  40. if handler:
  41. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  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": "收到微信回调消息"})
  62. def handle_text(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  63. '''
  64. 文本消息
  65. '''
  66. msg_content=msg_data["Content"]["string"]
  67. if wxid == from_wxid: #手动发送消息
  68. logger.info("Active message sending detected")
  69. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
  70. callback_to_user=msg_data["ToUserName"]["string"]
  71. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  72. input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  73. kafka_helper.kafka_client.produce_message(input_message)
  74. logger.info("发送对话 %s",input_message)
  75. else:
  76. callback_to_user=msg_data["FromUserName"]["string"]
  77. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  78. prompt={"role": "user", "content": [{
  79. "type": "text",
  80. "text": msg_content
  81. }]}
  82. messages_to_send=get_messages_from_cache(hash_key, prompt)
  83. # 收到的对话
  84. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  85. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  86. kafka_helper.kafka_client.produce_message(input_message)
  87. logger.info("发送对话 %s",input_message)
  88. cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  89. if cache_data and cache_data.get('interactive'):
  90. messages_to_send=[{"role": "user", "content": msg_content}]
  91. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  92. reply_content=res["choices"][0]["message"]["content"]
  93. description = ''
  94. userSelectOptions = []
  95. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  96. for item in reply_content:
  97. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  98. params = item["interactive"]["params"]
  99. description = params.get("description")
  100. userSelectOptions = params.get("userSelectOptions", [])
  101. values_string = "\n".join(option["value"] for option in userSelectOptions)
  102. if description is not None:
  103. memory.USER_INTERACTIVE_CACHE[wxid] = {
  104. "interactive":True
  105. }
  106. reply_content=description + '------------------------------\n'+values_string
  107. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  108. memory.USER_INTERACTIVE_CACHE[wxid] = {
  109. "interactive":False
  110. }
  111. text=''
  112. for item in reply_content:
  113. if item["type"] == "text":
  114. text=item["text"]["content"]
  115. if text=='':
  116. # 去除上次上一轮对话再次请求
  117. cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  118. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  119. if len(cache_messages) >= 3:
  120. cache_messages = cache_messages[:-3]
  121. redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  122. messages_to_send=get_messages_from_cache(hash_key, prompt)
  123. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  124. reply_content=res["choices"][0]["message"]["content"]
  125. else:
  126. reply_content=text
  127. else:
  128. memory.USER_INTERACTIVE_CACHE[wxid] = {
  129. "interactive":False
  130. }
  131. reply_content=res["choices"][0]["message"]["content"]
  132. # print(f'token_id {token_id}')
  133. # print(f'app_id {app_id}')
  134. # print(f'touser: {callback_to_user}')
  135. # # print(f'towxuser:{towxuser}')
  136. # print(reply_content)
  137. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  138. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  139. # 回复的对话
  140. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  141. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  142. kafka_helper.kafka_client.produce_message(input_message)
  143. logger.info("发送对话 %s",input_message)
  144. def handle_image(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  145. '''
  146. 图片消息
  147. '''
  148. msg_content=msg_data["Content"]["string"]
  149. callback_to_user=from_wxid
  150. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  151. wx_img_url=gewe_chat.wxchat.download_image_msg(token_id,app_id,msg_content)
  152. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  153. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  154. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  155. oss_bucket_name="cow-agent"
  156. oss_prefix="cow"
  157. img_url=utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
  158. prompt={
  159. "role": "user",
  160. "content": [{
  161. "type": "image_url",
  162. "image_url": {"url": img_url}
  163. }]
  164. }
  165. get_messages_from_cache(hash_key, prompt)
  166. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,'已经上传了图片,有什么可以为您服务')
  167. logger.info(f"上传图片 URL: {img_url}")
  168. wx_content_dialogue_message=[{"type": "image_url", "image_url": {"url": img_url}}]
  169. input_message=utils.dialogue_message(wxid,callback_to_user,wx_content_dialogue_message)
  170. kafka_helper.kafka_client.produce_message(input_message)
  171. logger.info("发送对话 %s",input_message)
  172. def handle_voice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  173. '''
  174. 语音消息
  175. '''
  176. callback_to_user=from_wxid
  177. msg_content=msg_data["Content"]["string"]
  178. msg_id=msg_data["MsgId"]
  179. file_url=gewe_chat.wxchat.download_audio_msg(token_id,app_id,msg_id,msg_content)
  180. react_silk_path=utils.save_to_local_from_url(file_url)
  181. react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
  182. audio_convert.any_to_wav(react_silk_path,react_wav_path)
  183. react_voice_text=AliVoice().voiceToText(react_wav_path)
  184. os.remove(react_silk_path)
  185. os.remove(react_wav_path)
  186. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  187. messages=get_messages_from_cache(hash_key, {"role": "user", "content": react_voice_text})
  188. ai_res=fast_gpt_api(messages,f'{wxid}-{callback_to_user}')
  189. ai_res_content=ai_res["choices"][0]["message"]["content"]
  190. voice_during,voice_url=utils.wx_voice(ai_res_content)
  191. ret,ret_msg,res=gewe_chat.wxchat.post_voice(token_id,app_id,callback_to_user,voice_url,voice_during)
  192. # 删除临时文件
  193. if ret==200:
  194. get_messages_from_cache(hash_key, {"role": "assistant", "content": ai_res_content})
  195. logger.info((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  196. # 构造对话消息并发送到 Kafka
  197. input_wx_content_dialogue_message = [{"type": "text", "text": ai_res_content}]
  198. input_message = utils.dialogue_message(wxid, callback_to_user, input_wx_content_dialogue_message)
  199. kafka_helper.kafka_client.produce_message(input_message)
  200. logger.info("发送对话 %s", input_message)
  201. else:
  202. logger.warning((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  203. def handle_xml(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  204. '''
  205. 处理xml
  206. '''
  207. msg_content_xml=msg_data["Content"]["string"]
  208. root = ET.fromstring(msg_content_xml)
  209. type_value = root.find(".//appmsg/type").text
  210. handlers = {
  211. 57: handle_xml_reference,
  212. }
  213. handler = handlers.get(type_value)
  214. if handler:
  215. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  216. else:
  217. print(f"xml消息 {type_value} 未解析")
  218. def handle_xml_reference(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  219. '''
  220. 判断此类消息的逻辑:$.Data.MsgType=49 并且 解析$.Data.Content.string中的xml msg.appmsg.type=57
  221. '''
  222. callback_to_user=from_wxid
  223. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  224. msg_content= msg_data["PushContent"]
  225. prompt={"role": "user", "content": [{
  226. "type": "text",
  227. "text": msg_content
  228. }]}
  229. # 收到的对话
  230. messages_to_send=get_messages_from_cache(hash_key, prompt)
  231. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  232. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  233. kafka_helper.kafka_client.produce_message(input_message)
  234. logger.info("发送对话 %s",input_message)
  235. # 回复的对话
  236. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  237. reply_content=res["choices"][0]["message"]["content"]
  238. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  239. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  240. kafka_helper.kafka_client.produce_message(input_message)
  241. logger.info("发送对话 %s",input_message)
  242. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  243. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  244. def handle_add_friend_notice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  245. '''
  246. 好友添加请求通知
  247. '''
  248. logger.info('好友添加请求通知')
  249. msg_content_xml=msg_data["Content"]["string"]
  250. root = ET.fromstring(msg_content_xml)
  251. msg_content = root.attrib.get('content', None)
  252. v3= root.attrib.get('encryptusername', None)
  253. v4= root.attrib.get('ticket', None)
  254. scene=root.attrib.get('scene', None)
  255. to_contact_wxid=root.attrib.get('fromusername', None)
  256. wxid=msg_data["ToUserName"]["string"]
  257. # 自动同意好友
  258. # print(v3)
  259. # print(v4)
  260. # print(scene)
  261. # print(msg_content)
  262. # 操作类型,2添加好友 3同意好友 4拒绝好友
  263. #option=2
  264. option=3
  265. reply_add_contact_contact="亲,我是你的美丽顾问"
  266. ret,ret_msg=gewe_chat.wxchat.add_contacts(token_id,app_id,scene,option,v3,v4,reply_add_contact_contact)
  267. if ret==200:
  268. logger.info('自动添加好友成功')
  269. # 好友发送的文字
  270. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{to_contact_wxid}'
  271. prompt={"role": "user", "content": [{"type": "text","text": msg_content}]}
  272. messages_to_send=get_messages_from_cache(hash_key, prompt)
  273. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  274. input_message=utils.dialogue_message(to_contact_wxid,wxid,input_wx_content_dialogue_message)
  275. kafka_helper.kafka_client.produce_message(input_message)
  276. logger.info("发送对话 %s",input_message)
  277. callback_to_user=to_contact_wxid
  278. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  279. reply_content=res["choices"][0]["message"]["content"]
  280. #保存好友信息
  281. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id, wxid,[to_contact_wxid])
  282. # 保存到缓存
  283. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  284. # 发送信息
  285. gewe_chat.wxchat.post_text(token_id,app_id, to_contact_wxid,reply_content)
  286. # 发送到kafka
  287. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  288. input_message=utils.dialogue_message(wxid,to_contact_wxid,input_wx_content_dialogue_message,True)
  289. kafka_helper.kafka_client.produce_message(input_message)
  290. logger.info("发送对话 %s",input_message)
  291. else:
  292. logger.warning("添加好友失败")
  293. def handle_mod_contacts(token_id,app_id,msg_data):
  294. '''
  295. 好友通过验证及好友资料变更的通知消息
  296. '''
  297. logger.info('好友通过验证及好友资料变更的通知消息')
  298. contact_wxid = msg_data["UserName"]["string"]
  299. keys=redis_helper.redis_helper.client.keys('__AI_OPS_WX__:LOGININFO:*')
  300. wxid=""
  301. for k in keys:
  302. key=k.decode('utf-8')
  303. hash_key=f"__AI_OPS_WX__:LOGININFO:{key}"
  304. cache_app_id=redis_helper.redis_helper.get_hash_field(hash_key,"appId")
  305. if app_id==cache_app_id:
  306. wxid=redis_helper.redis_helper.get_hash_field(hash_key,"wxid")
  307. break
  308. if wxid!="":
  309. hash_key=f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  310. cache_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  311. cache = json.loads(cache_str) if cache_str else []
  312. for c in cache:
  313. if c["userName"]==contact_wxid:
  314. c["nickName"] = msg_data["NickName"]
  315. c["snsBgImg"] = msg_data["SnsBgimgId"]
  316. c["smallHeadImgUrl"] = msg_data["SmallHeadImgUrl"]
  317. c["signature"]= msg_data["Signature"]
  318. # 更新缓存,如果有修改过的话
  319. if cache:
  320. updated_cache_str = json.dumps(cache)
  321. redis_helper.redis_helper.set_hash_field(hash_key, "data", updated_cache_str)
  322. def get_messages_from_cache(hash_key,object:object)->object:
  323. messages=redis_helper.redis_helper.get_hash(hash_key)
  324. wxid=hash_key.split(':')[-1]
  325. if not messages:
  326. messages=[{"role": "system", "content": ""}]
  327. messages.append(object)
  328. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  329. else:
  330. messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  331. messages = json.loads(messages_str) if messages_str else []
  332. #判断是否含有图片
  333. last_message = messages[-1]
  334. content = last_message.get("content", [])
  335. if isinstance(content, list) and content:
  336. last_content_type = content[-1].get("type")
  337. if last_content_type == 'image_url':
  338. content.append(object['content'][0])
  339. messages[-1]['content']=content
  340. else:
  341. messages.append(object)
  342. else:
  343. messages.append(object)
  344. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  345. return messages
  346. def fast_gpt_api(messages:list,session_id:str):
  347. #api_key="sk-tdi7u0zuLsR0JpPMGBeFZxymOpL0zoFVafX8EEEvEakIDAGQ22NyQ6w"
  348. api_key="sk-uJDBdKmJVb2cmfldGOvlIY6Qx0AzqWMPD3lS1IzgQYzHNOXv9SKNI"
  349. api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
  350. headers = {
  351. "Content-Type": "application/json",
  352. "Authorization": f"Bearer {api_key}"
  353. }
  354. data={
  355. "model": "",
  356. "messages":messages,
  357. "chatId": session_id,
  358. "detail": True
  359. }
  360. print(json.dumps(data,ensure_ascii=False))
  361. logger.info("[CHATGPT] 请求={}".format(json.dumps(data, ensure_ascii=False)))
  362. response = requests.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600)
  363. response.raise_for_status()
  364. response_data = response.json()
  365. logger.info("[CHATGPT] 响应={}".format(json.dumps(response_data, separators=(',', ':'),ensure_ascii=False)))
  366. print(response_data)
  367. return response_data
  368. def get_token_id_by_app_id(app_id: str) -> str:
  369. # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
  370. cursor = 0
  371. while True:
  372. cursor, login_keys = redis_helper.redis_helper.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  373. # 批量获取所有键的 hash 数据
  374. for k in login_keys:
  375. r = redis_helper.redis_helper.get_hash(k)
  376. if r.get("appId") == app_id:
  377. return r.get("tokenId", "")
  378. # 如果游标为 0,则表示扫描完成
  379. if cursor == 0:
  380. break
  381. return ""