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.

244 lines
11KB

  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. """处理微信回调的POST请求"""
  18. msg = request.get_json()
  19. if 'Data' not in msg:
  20. return jsonify({"message": "无效的数据格式"}), 400 # 如果数据格式不正确,返回错误信息
  21. msg_data = msg.get("Data")
  22. msg_type = msg_data.get("MsgType")
  23. # 根据不同的消息类型调用对应的处理函数
  24. if msg_type == 1: # 文字消息
  25. return self.handle_text_message(msg)
  26. elif msg_type == 3: # 图片消息
  27. return self.handle_image_message(msg)
  28. elif msg_type == 34: # 语音消息
  29. return self.handle_voice_message(msg)
  30. elif msg_type == 49: # 引用消息
  31. return self.handle_replied_message(msg)
  32. return jsonify({"message": "不支持的消息类型"}), 400 # 如果消息类型不支持,返回错误信息
  33. def handle_text_message(self, msg):
  34. """处理文字消息"""
  35. msg_data = msg["Data"]
  36. wxid = msg["Wxid"]
  37. hash_key = f"__AI_OPS_WX__:MESSAGES:{wxid}"
  38. app_id = msg["Appid"]
  39. callback_to_user = msg_data["FromUserName"]["string"]
  40. msg_content = msg_data["Content"]["string"]
  41. # 构造 GPT 请求的 prompt
  42. prompt = {"role": "user", "content": [{"type": "text", "text": msg_content}]}
  43. messages_to_send = self.get_messages_from_cache(hash_key, prompt)
  44. # 将消息发送到 Kafka 中
  45. self.send_to_kafka(wxid, callback_to_user, msg_content)
  46. # 调用 GPT API 获取回复
  47. res = self.fast_gpt_api(messages_to_send, wxid)
  48. reply_content = self.process_reply_content(res, wxid)
  49. # 将 GPT 回复的内容发送到微信
  50. gewe_chat.wxchat.post_text(msg["TokenId"], app_id, callback_to_user, reply_content)
  51. self.get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  52. return jsonify({"message": "文字消息处理成功"})
  53. def handle_image_message(self, msg):
  54. """处理图片消息"""
  55. msg_data = msg["Data"]
  56. wxid = msg["Wxid"]
  57. hash_key = f"__AI_OPS_WX__:MESSAGES:{wxid}"
  58. app_id = msg["Appid"]
  59. callback_to_user = msg_data["FromUserName"]["string"]
  60. img_url = msg_data["Content"]["string"]
  61. # 图片下载并上传至 OSS
  62. wx_img_url = gewe_chat.wxchat.download_image_msg(msg["TokenId"], app_id, img_url)
  63. # img_url = self.upload_image_to_oss(wx_img_url)
  64. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  65. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  66. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  67. oss_bucket_name="cow-agent"
  68. oss_prefix="cow"
  69. img_url=utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
  70. # 发送确认消息
  71. gewe_chat.wxchat.post_text(msg["TokenId"], app_id, callback_to_user, '已经上传了图片,有什么可以为您服务')
  72. # 构造消息并发送到 Kafka
  73. wx_content_dialogue_message = [{"type": "image_url", "image_url": {"url": img_url}}]
  74. self.send_to_kafka(wxid, callback_to_user, wx_content_dialogue_message)
  75. return jsonify({"message": "图片消息处理成功"})
  76. def handle_voice_message(self, msg):
  77. """处理语音消息"""
  78. msg_data = msg["Data"]
  79. wxid = msg["Wxid"]
  80. hash_key = f"__AI_OPS_WX__:MESSAGES:{wxid}"
  81. app_id = msg["Appid"]
  82. callback_to_user = msg_data["FromUserName"]["string"]
  83. msg_id = msg_data["MsgId"]
  84. msg_content = msg_data["Content"]["string"]
  85. # 下载语音文件并转为文本
  86. file_url = gewe_chat.wxchat.download_audio_msg(msg["TokenId"], app_id, msg_id, msg_content)
  87. react_silk_path = utils.save_to_local_from_url(file_url)
  88. react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
  89. audio_convert.any_to_wav(react_silk_path, react_wav_path)
  90. react_voice_text = AliVoice().voiceToText(react_wav_path)
  91. # 发送到 GPT 获取回复
  92. messages_to_send = self.get_messages_from_cache(hash_key, {"role": "user", "content": react_voice_text})
  93. ai_res = self.fast_gpt_api(messages_to_send, wxid)
  94. ai_res_content = ai_res["choices"][0]["message"]["content"]
  95. # 将 GPT 的文本转换成语音并上传到 OSS
  96. reply_text_voice = AliVoice().textToVoice(ai_res_content)
  97. reply_text_voice_path = os.path.join(os.getcwd(), reply_text_voice)
  98. reply_silk_path = os.path.splitext(reply_text_voice_path)[0] + ".silk"
  99. audio_convert.any_to_sil(reply_text_voice_path, reply_silk_path)
  100. file_url = self.upload_audio_to_oss(reply_silk_path)
  101. # 发送语音回复
  102. res = gewe_chat.wxchat.post_voice(msg["TokenId"], app_id, callback_to_user, file_url, int(reply_silk_during))
  103. # 删除临时文件
  104. self.delete_temp_files([react_silk_path, react_wav_path, reply_text_voice_path, reply_silk_path])
  105. return jsonify({"message": "语音消息处理成功"})
  106. def handle_replied_message(self, msg):
  107. """处理引用消息"""
  108. msg_data = msg["Data"]
  109. wxid = msg["Wxid"]
  110. hash_key = f"__AI_OPS_WX__:MESSAGES:{wxid}"
  111. app_id = msg["Appid"]
  112. callback_to_user = msg_data["FromUserName"]["string"]
  113. msg_content_xml = msg_data["Content"]["string"]
  114. # 解析 XML,获取引用消息
  115. root = ET.fromstring(msg_content_xml)
  116. type_value = root.find(".//appmsg/type").text
  117. if type_value == '57': # 如果是引用消息
  118. prompt = {"role": "user", "content": [{"type": "text", "text": msg_content_xml}]}
  119. messages_to_send = self.get_messages_from_cache(hash_key, prompt)
  120. # 发送到 Kafka
  121. self.send_to_kafka(wxid, callback_to_user, msg_content_xml)
  122. # 获取 GPT 回复并发送
  123. res = self.fast_gpt_api(messages_to_send, wxid)
  124. reply_content = res["choices"][0]["message"]["content"]
  125. gewe_chat.wxchat.post_text(msg["TokenId"], app_id, callback_to_user, reply_content)
  126. return jsonify({"message": "引用消息处理成功"})
  127. def get_messages_from_cache(self, hash_key, object: object) -> object:
  128. """从缓存中获取消息并更新缓存"""
  129. messages = redis_helper.redis_helper.get_hash(hash_key)
  130. if not messages:
  131. messages = [{"role": "system", "content": ""}]
  132. messages.append(object)
  133. redis_helper.redis_helper.set_hash(hash_key, {"data": json.dumps(messages, ensure_ascii=False)}, 3600)
  134. else:
  135. messages_str = redis_helper.redis_helper.get_hash_field(hash_key, "data")
  136. messages = json.loads(messages_str) if messages_str else []
  137. last_message = messages[-1]
  138. content = last_message.get("content", [])
  139. if isinstance(content, list) and content:
  140. last_content_type = content[-1].get("type")
  141. if last_content_type == 'image_url':
  142. content.append(object['content'][0])
  143. messages[-1]['content'] = content
  144. else:
  145. messages.append(object)
  146. else:
  147. messages.append(object)
  148. redis_helper.redis_helper.set_hash(hash_key, {"data": json.dumps(messages, ensure_ascii=False)}, 3600)
  149. return messages
  150. def send_to_kafka(self, wxid, callback_to_user, msg_content):
  151. """将消息发送到 Kafka"""
  152. input_wx_content_dialogue_message = [{"type": "text", "text": msg_content}]
  153. input_message = utils.dialogue_message(callback_to_user, wxid, input_wx_content_dialogue_message)
  154. kafka_helper.kafka_client.produce_message(input_message)
  155. logger.info("发送对话 %s", input_message)
  156. def fast_gpt_api(self, messages: list, session_id: str):
  157. """调用 GPT API 获取回复"""
  158. api_key = "sk-tdi7u0zuLsR0JpPMGBeFZxymOpL0zoFVafX8EEEvEakIDAGQ22NyQ6w"
  159. api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
  160. headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
  161. data = {"model": "", "messages": messages, "chatId": session_id, "detail": True}
  162. try:
  163. response = requests.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600)
  164. response.raise_for_status()
  165. return response.json()
  166. except requests.exceptions.RequestException as e:
  167. logger.error(f"GPT API 请求失败: {e}")
  168. return {"error": "API 请求失败"}
  169. def process_reply_content(self, res, wxid):
  170. """处理 GPT 回复内容"""
  171. reply_content = res["choices"][0]["message"]["content"]
  172. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  173. return self.process_interactive_reply(reply_content, wxid)
  174. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  175. return self.process_text_reply(reply_content, wxid)
  176. else:
  177. return reply_content
  178. def process_interactive_reply(self, reply_content, wxid):
  179. """处理交互式回复"""
  180. description = ''
  181. user_select_options = []
  182. for item in reply_content:
  183. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  184. params = item["interactive"]["params"]
  185. description = params.get("description")
  186. user_select_options = params.get("userSelectOptions", [])
  187. values_string = "\n".join(option["value"] for option in user_select_options)
  188. if description:
  189. memory.USER_INTERACTIVE_CACHE[wxid] = {"interactive": True}
  190. return f"{description}\n------------------------------\n{values_string}"
  191. return reply_content
  192. def process_text_reply(self, reply_content, wxid):
  193. """处理文本回复"""
  194. memory.USER_INTERACTIVE_CACHE[wxid] = {"interactive": False}
  195. text = next((item["text"]["content"] for item in reply_content if item["type"] == "text"), '')
  196. if not text:
  197. messages_to_send = self.get_messages_from_cache(f"__AI_OPS_WX__:MESSAGES:{wxid}", {"role": "user", "content": text})
  198. res = self.fast_gpt_api(messages_to_send, wxid)
  199. return res["choices"][0]["message"]["content"]
  200. return text