|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- from flask_restful import Resource, reqparse
- from flask import jsonify,request
- from bridge.context import ContextType
- import requests,json
- from wechat import gewe_chat
- from voice.ali.ali_voice import AliVoice
- from common import utils,redis_helper,memory,kafka_helper
- from common.log import logger
- import openai
- import xml.etree.ElementTree as ET
-
-
- import os
-
- from voice import audio_convert
-
- class MessagesResource(Resource):
- def __init__(self):
- self.parser = reqparse.RequestParser()
-
-
- def post(self):
- msg = request.get_json()
- logger.info(f"收到微信回调消息: {msg}")
- type_name =msg.get("TypeName")
- app_id = msg.get("Appid")
- # token_id = "f828cb3c-1039-489f-b9ae-7494d1778a15"
- token_id=get_token_id_by_app_id(app_id)
-
- if token_id=="":
- logger.warning('找不到登录信息,不处理')
- return jsonify({"message": "收到微信回调消息"})
-
- if type_name=='AddMsg':
- wxid = msg.get("Wxid")
- msg_data = msg.get("Data")
- msg_type = msg_data.get("MsgType",None)
- from_wxid = msg_data["FromUserName"]["string"]
- to_wxid = msg_data["ToUserName"]["string"]
- handlers = {
- 1: handle_text,
- 3: handle_image,
- 34: handle_voice,
- 49: handle_xml,
- 37: handle_add_friend_notice
- }
-
- handler = handlers.get(msg_type)
- if handler:
- return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
- else:
- logger.warning(f"微信回调消息类型 {msg_type} 未处理")
- elif type_name=='ModContacts':
- '''
- 好友通过验证及好友资料变更的通知消息
- '''
- msg_data = msg.get("Data")
- handle_mod_contacts(token_id,app_id,msg_data)
-
- elif type_name=="Offline":
- '''
- 掉线通知
- '''
- elif type_name=="DelContacts":
- '''
- 删除好友通知
- 退出群聊
- '''
- else:
- logger.warning(f"未知消息类型")
-
- return jsonify({"message": "收到微信回调消息"})
-
-
- def handle_text(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
- '''
- 文本消息
- '''
- msg_content=msg_data["Content"]["string"]
-
- if wxid == from_wxid: #手动发送消息
- logger.info("Active message sending detected")
-
- gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
- callback_to_user=msg_data["ToUserName"]["string"]
-
- input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
- input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
- else:
-
- callback_to_user=msg_data["FromUserName"]["string"]
- hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
-
- prompt={"role": "user", "content": [{
- "type": "text",
- "text": msg_content
- }]}
- messages_to_send=get_messages_from_cache(hash_key, prompt)
- # 收到的对话
- input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
- input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
-
- cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
- if cache_data and cache_data.get('interactive'):
- messages_to_send=[{"role": "user", "content": msg_content}]
-
- res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
- reply_content=res["choices"][0]["message"]["content"]
-
- description = ''
- userSelectOptions = []
-
- if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
- for item in reply_content:
- if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
- params = item["interactive"]["params"]
- description = params.get("description")
- userSelectOptions = params.get("userSelectOptions", [])
- values_string = "\n".join(option["value"] for option in userSelectOptions)
-
- if description is not None:
- memory.USER_INTERACTIVE_CACHE[wxid] = {
- "interactive":True
- }
- reply_content=description + '------------------------------\n'+values_string
-
- elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
- memory.USER_INTERACTIVE_CACHE[wxid] = {
- "interactive":False
- }
- text=''
- for item in reply_content:
- if item["type"] == "text":
- text=item["text"]["content"]
- if text=='':
- # 去除上次上一轮对话再次请求
- cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
- cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
-
- if len(cache_messages) >= 3:
- cache_messages = cache_messages[:-3]
-
- redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
- messages_to_send=get_messages_from_cache(hash_key, prompt)
- res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
- reply_content=res["choices"][0]["message"]["content"]
- else:
- reply_content=text
- else:
- memory.USER_INTERACTIVE_CACHE[wxid] = {
- "interactive":False
- }
- reply_content=res["choices"][0]["message"]["content"]
-
- # print(f'token_id {token_id}')
- # print(f'app_id {app_id}')
- # print(f'touser: {callback_to_user}')
- # # print(f'towxuser:{towxuser}')
- # print(reply_content)
- gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
- get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
- # 回复的对话
- input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
- input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
-
- def handle_image(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
- '''
- 图片消息
- '''
- msg_content=msg_data["Content"]["string"]
-
- callback_to_user=from_wxid
- hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
- wx_img_url=gewe_chat.wxchat.download_image_msg(token_id,app_id,msg_content)
-
- oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
- oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
- oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
- oss_bucket_name="cow-agent"
- oss_prefix="cow"
-
- img_url=utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
-
- prompt={
- "role": "user",
- "content": [{
- "type": "image_url",
- "image_url": {"url": img_url}
- }]
- }
-
- get_messages_from_cache(hash_key, prompt)
- gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,'已经上传了图片,有什么可以为您服务')
- logger.info(f"上传图片 URL: {img_url}")
-
- wx_content_dialogue_message=[{"type": "image_url", "image_url": {"url": img_url}}]
- input_message=utils.dialogue_message(wxid,callback_to_user,wx_content_dialogue_message)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
-
- def handle_voice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
- '''
- 语音消息
- '''
- callback_to_user=from_wxid
- msg_content=msg_data["Content"]["string"]
- msg_id=msg_data["MsgId"]
-
-
- file_url=gewe_chat.wxchat.download_audio_msg(token_id,app_id,msg_id,msg_content)
- react_silk_path=utils.save_to_local_from_url(file_url)
- react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
- audio_convert.any_to_wav(react_silk_path,react_wav_path)
- react_voice_text=AliVoice().voiceToText(react_wav_path)
-
- os.remove(react_silk_path)
- os.remove(react_wav_path)
-
- hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
- messages=get_messages_from_cache(hash_key, {"role": "user", "content": react_voice_text})
- ai_res=fast_gpt_api(messages,f'{wxid}-{callback_to_user}')
- ai_res_content=ai_res["choices"][0]["message"]["content"]
- voice_during,voice_url=utils.wx_voice(ai_res_content)
-
- ret,ret_msg,res=gewe_chat.wxchat.post_voice(token_id,app_id,callback_to_user,voice_url,voice_during)
- # 删除临时文件
-
- if ret==200:
- get_messages_from_cache(hash_key, {"role": "assistant", "content": ai_res_content})
- logger.info((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
-
- # 构造对话消息并发送到 Kafka
- input_wx_content_dialogue_message = [{"type": "text", "text": ai_res_content}]
- input_message = utils.dialogue_message(wxid, callback_to_user, input_wx_content_dialogue_message)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s", input_message)
- else:
- logger.warning((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
-
- def handle_xml(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
- '''
- 处理xml
- '''
- msg_content_xml=msg_data["Content"]["string"]
- root = ET.fromstring(msg_content_xml)
- type_value = root.find(".//appmsg/type").text
- handlers = {
- 57: handle_xml_reference,
- }
- handler = handlers.get(type_value)
- if handler:
- return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
- else:
- print(f"xml消息 {type_value} 未解析")
-
- def handle_xml_reference(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
- '''
- 判断此类消息的逻辑:$.Data.MsgType=49 并且 解析$.Data.Content.string中的xml msg.appmsg.type=57
- '''
- callback_to_user=from_wxid
- hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
-
- msg_content= msg_data["PushContent"]
-
- prompt={"role": "user", "content": [{
- "type": "text",
- "text": msg_content
- }]}
-
- # 收到的对话
- messages_to_send=get_messages_from_cache(hash_key, prompt)
- input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
- input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
-
- # 回复的对话
- res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
- reply_content=res["choices"][0]["message"]["content"]
- input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
- input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
- get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
- gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
-
- def handle_add_friend_notice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
- '''
- 好友添加请求通知
- '''
- logger.info('好友添加请求通知')
-
- msg_content_xml=msg_data["Content"]["string"]
-
- root = ET.fromstring(msg_content_xml)
-
- msg_content = root.attrib.get('content', None)
- v3= root.attrib.get('encryptusername', None)
- v4= root.attrib.get('ticket', None)
- scene=root.attrib.get('scene', None)
-
-
- to_contact_wxid=root.attrib.get('fromusername', None)
- wxid=msg_data["ToUserName"]["string"]
-
- # 自动同意好友
- # print(v3)
- # print(v4)
- # print(scene)
- # print(msg_content)
- # 操作类型,2添加好友 3同意好友 4拒绝好友
- #option=2
- option=3
- reply_add_contact_contact="亲,我是你的美丽顾问"
- ret,ret_msg=gewe_chat.wxchat.add_contacts(token_id,app_id,scene,option,v3,v4,reply_add_contact_contact)
- if ret==200:
- logger.info('自动添加好友成功')
-
- # 好友发送的文字
- hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{to_contact_wxid}'
- prompt={"role": "user", "content": [{"type": "text","text": msg_content}]}
- messages_to_send=get_messages_from_cache(hash_key, prompt)
-
- input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
- input_message=utils.dialogue_message(to_contact_wxid,wxid,input_wx_content_dialogue_message)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
-
- callback_to_user=to_contact_wxid
- res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
- reply_content=res["choices"][0]["message"]["content"]
-
- #保存好友信息
- gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id, wxid,[to_contact_wxid])
-
- # 保存到缓存
- get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
-
- # 发送信息
- gewe_chat.wxchat.post_text(token_id,app_id, to_contact_wxid,reply_content)
-
- # 发送到kafka
- input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
- input_message=utils.dialogue_message(wxid,to_contact_wxid,input_wx_content_dialogue_message,True)
- kafka_helper.kafka_client.produce_message(input_message)
- logger.info("发送对话 %s",input_message)
-
- else:
- logger.warning("添加好友失败")
-
-
- def handle_mod_contacts(token_id,app_id,msg_data):
- '''
- 好友通过验证及好友资料变更的通知消息
- '''
- logger.info('好友通过验证及好友资料变更的通知消息')
-
- contact_wxid = msg_data["UserName"]["string"]
- keys=redis_helper.redis_helper.client.keys('__AI_OPS_WX__:LOGININFO:*')
-
- wxid=""
- for k in keys:
- key=k.decode('utf-8')
- hash_key=f"__AI_OPS_WX__:LOGININFO:{key}"
- cache_app_id=redis_helper.redis_helper.get_hash_field(hash_key,"appId")
- if app_id==cache_app_id:
- wxid=redis_helper.redis_helper.get_hash_field(hash_key,"wxid")
- break
-
- if wxid!="":
- hash_key=f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
- cache_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
- cache = json.loads(cache_str) if cache_str else []
- for c in cache:
- if c["userName"]==contact_wxid:
- c["nickName"] = msg_data["NickName"]
- c["snsBgImg"] = msg_data["SnsBgimgId"]
- c["smallHeadImgUrl"] = msg_data["SmallHeadImgUrl"]
- c["signature"]= msg_data["Signature"]
-
- # 更新缓存,如果有修改过的话
- if cache:
- updated_cache_str = json.dumps(cache)
- redis_helper.redis_helper.set_hash_field(hash_key, "data", updated_cache_str)
-
- def get_messages_from_cache(hash_key,object:object)->object:
- messages=redis_helper.redis_helper.get_hash(hash_key)
- wxid=hash_key.split(':')[-1]
- if not messages:
- messages=[{"role": "system", "content": ""}]
- messages.append(object)
- redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
- else:
- messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
- messages = json.loads(messages_str) if messages_str else []
- #判断是否含有图片
- last_message = messages[-1]
- content = last_message.get("content", [])
- if isinstance(content, list) and content:
- last_content_type = content[-1].get("type")
- if last_content_type == 'image_url':
- content.append(object['content'][0])
- messages[-1]['content']=content
- else:
- messages.append(object)
- else:
- messages.append(object)
- redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
- return messages
-
- def fast_gpt_api(messages:list,session_id:str):
- #api_key="sk-tdi7u0zuLsR0JpPMGBeFZxymOpL0zoFVafX8EEEvEakIDAGQ22NyQ6w"
- api_key="sk-uJDBdKmJVb2cmfldGOvlIY6Qx0AzqWMPD3lS1IzgQYzHNOXv9SKNI"
- api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
- headers = {
-
- "Content-Type": "application/json",
- "Authorization": f"Bearer {api_key}"
- }
- data={
- "model": "",
- "messages":messages,
- "chatId": session_id,
- "detail": True
- }
- print(json.dumps(data,ensure_ascii=False))
- logger.info("[CHATGPT] 请求={}".format(json.dumps(data, ensure_ascii=False)))
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600)
- response.raise_for_status()
- response_data = response.json()
- logger.info("[CHATGPT] 响应={}".format(json.dumps(response_data, separators=(',', ':'),ensure_ascii=False)))
- print(response_data)
- return response_data
-
-
- def get_token_id_by_app_id(app_id: str) -> str:
- # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
- cursor = 0
- while True:
- cursor, login_keys = redis_helper.redis_helper.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
-
- # 批量获取所有键的 hash 数据
- for k in login_keys:
- r = redis_helper.redis_helper.get_hash(k)
- if r.get("appId") == app_id:
- return r.get("tokenId", "")
-
- # 如果游标为 0,则表示扫描完成
- if cursor == 0:
- break
-
- return ""
|