ff
Browse files
app.py
CHANGED
|
@@ -6,14 +6,15 @@ import threading
|
|
| 6 |
import httpx
|
| 7 |
import logging
|
| 8 |
import gradio as gr
|
|
|
|
|
|
|
| 9 |
from openai import OpenAI
|
| 10 |
from Crypto.Cipher import AES
|
| 11 |
from Crypto.Util.Padding import unpad
|
| 12 |
-
from fastapi import Request
|
| 13 |
-
from fastapi.responses import PlainTextResponse
|
| 14 |
import xml.etree.ElementTree as ET
|
|
|
|
| 15 |
|
| 16 |
-
# โโ ๆฅๅฟ
|
| 17 |
logging.basicConfig(
|
| 18 |
level=logging.INFO,
|
| 19 |
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
@@ -29,11 +30,8 @@ WECOM_CORPID = os.getenv("CORPID", "").strip()
|
|
| 29 |
WECOM_SECRET = os.getenv("WECOM_SECRET", "").strip()
|
| 30 |
WECOM_AGENTID = os.getenv("WECOM_AGENTID", "1000003")
|
| 31 |
|
| 32 |
-
# โโ LLM
|
| 33 |
-
llm = OpenAI(
|
| 34 |
-
base_url="https://integrate.api.nvidia.com/v1",
|
| 35 |
-
api_key=NVIDIA_API_KEY,
|
| 36 |
-
)
|
| 37 |
|
| 38 |
SYSTEM_PROMPT = (
|
| 39 |
"You are Hermes, a helpful AI assistant. "
|
|
@@ -60,7 +58,7 @@ def chat_with_hermes(message: str, history: list) -> str:
|
|
| 60 |
log.info("[WEB] assistant: %s", reply)
|
| 61 |
return reply
|
| 62 |
|
| 63 |
-
# โโ ไผไธๅพฎไฟก
|
| 64 |
_wx_cache = {"token": "", "expires": 0}
|
| 65 |
_lock = threading.Lock()
|
| 66 |
|
|
@@ -74,20 +72,20 @@ def get_wecom_token() -> str:
|
|
| 74 |
params={"corpid": WECOM_CORPID, "corpsecret": WECOM_SECRET},
|
| 75 |
timeout=10,
|
| 76 |
).json()
|
| 77 |
-
log.info("[WECOM] get_token
|
| 78 |
_wx_cache["token"] = r.get("access_token", "")
|
| 79 |
_wx_cache["expires"] = time.time() + r.get("expires_in", 7200)
|
| 80 |
return _wx_cache["token"]
|
| 81 |
|
| 82 |
def send_wecom_message(to_user: str, content: str):
|
| 83 |
token = get_wecom_token()
|
| 84 |
-
payload = {"touser": to_user, "msgtype": "text",
|
| 85 |
-
"agentid": int(WECOM_AGENTID), "text": {"content": content}}
|
| 86 |
r = httpx.post(
|
| 87 |
f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}",
|
| 88 |
-
json=
|
|
|
|
|
|
|
| 89 |
).json()
|
| 90 |
-
log.info("[WECOM]
|
| 91 |
|
| 92 |
def decrypt_msg(encrypted: str) -> str:
|
| 93 |
aes_key = base64.b64decode(WECOM_AES_KEY + "=")
|
|
@@ -100,42 +98,37 @@ def verify_sig(timestamp: str, nonce: str, encrypt: str = "") -> str:
|
|
| 100 |
parts = [WECOM_TOKEN, timestamp, nonce] + ([encrypt] if encrypt else [])
|
| 101 |
return hashlib.sha1("".join(sorted(parts)).encode()).hexdigest()
|
| 102 |
|
| 103 |
-
# โโ
|
| 104 |
-
|
| 105 |
-
fn=chat_with_hermes,
|
| 106 |
-
title="๐ค Hermes Agent",
|
| 107 |
-
description="Powered by NVIDIA NIM ยท ไผไธๅพฎไฟกๅๆญฅๆฅๅ
ฅ",
|
| 108 |
-
)
|
| 109 |
|
| 110 |
-
|
| 111 |
-
@demo.app.get("/gateway/wecom")
|
| 112 |
async def wecom_verify(request: Request):
|
| 113 |
p = request.query_params
|
| 114 |
-
log.info("[WECOM] GET
|
| 115 |
try:
|
| 116 |
-
sig = verify_sig(p.get("timestamp",""), p.get("nonce",""), p.get("echostr",""))
|
| 117 |
-
expected = p.get("msg_signature","")
|
| 118 |
-
log.info("[WECOM]
|
| 119 |
if sig != expected:
|
| 120 |
return PlainTextResponse("")
|
| 121 |
-
plain = decrypt_msg(p.get("echostr",""))
|
| 122 |
-
log.info("[WECOM] verify ok
|
| 123 |
return PlainTextResponse(plain)
|
| 124 |
except Exception as e:
|
| 125 |
log.exception("[WECOM] verify error: %s", e)
|
| 126 |
return PlainTextResponse("")
|
| 127 |
|
| 128 |
-
@
|
| 129 |
async def wecom_message(request: Request):
|
| 130 |
p = request.query_params
|
| 131 |
log.info("[WECOM] POST params: %s", dict(p))
|
| 132 |
try:
|
| 133 |
body = await request.body()
|
| 134 |
-
log.info("[WECOM]
|
| 135 |
encrypt = ET.fromstring(body.decode()).findtext("Encrypt", "")
|
| 136 |
-
sig = verify_sig(p.get("timestamp",""), p.get("nonce",""), encrypt)
|
| 137 |
-
expected = p.get("msg_signature","")
|
| 138 |
-
log.info("[WECOM]
|
| 139 |
if sig != expected:
|
| 140 |
return PlainTextResponse("success")
|
| 141 |
root = ET.fromstring(decrypt_msg(encrypt))
|
|
@@ -144,12 +137,12 @@ async def wecom_message(request: Request):
|
|
| 144 |
log.info("[WECOM] msg_type=%s from=%s", msg_type, from_user)
|
| 145 |
if msg_type == "text":
|
| 146 |
content = root.findtext("Content", "").strip()
|
| 147 |
-
log.info("[WECOM]
|
| 148 |
if content:
|
| 149 |
def reply():
|
| 150 |
try:
|
| 151 |
answer = chat_with_hermes(content, [])
|
| 152 |
-
log.info("[WECOM] reply
|
| 153 |
send_wecom_message(from_user, answer)
|
| 154 |
except Exception as e:
|
| 155 |
log.exception("[WECOM] reply error: %s", e)
|
|
@@ -158,14 +151,17 @@ async def wecom_message(request: Request):
|
|
| 158 |
log.exception("[WECOM] message error: %s", e)
|
| 159 |
return PlainTextResponse("success")
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
# โโ ๅฏๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 162 |
if __name__ == "__main__":
|
| 163 |
-
log.info("
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
log.info("WECOM_SECRET set: %s", bool(WECOM_SECRET))
|
| 168 |
-
log.info("NVIDIA_API_KEY set: %s", bool(NVIDIA_API_KEY))
|
| 169 |
-
routes = [r.path for r in demo.app.routes]
|
| 170 |
-
log.info("Registered routes: %s", routes)
|
| 171 |
-
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 6 |
import httpx
|
| 7 |
import logging
|
| 8 |
import gradio as gr
|
| 9 |
+
from fastapi import FastAPI, Request
|
| 10 |
+
from fastapi.responses import PlainTextResponse
|
| 11 |
from openai import OpenAI
|
| 12 |
from Crypto.Cipher import AES
|
| 13 |
from Crypto.Util.Padding import unpad
|
|
|
|
|
|
|
| 14 |
import xml.etree.ElementTree as ET
|
| 15 |
+
import uvicorn
|
| 16 |
|
| 17 |
+
# โโ ๆฅๅฟ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 18 |
logging.basicConfig(
|
| 19 |
level=logging.INFO,
|
| 20 |
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
|
|
| 30 |
WECOM_SECRET = os.getenv("WECOM_SECRET", "").strip()
|
| 31 |
WECOM_AGENTID = os.getenv("WECOM_AGENTID", "1000003")
|
| 32 |
|
| 33 |
+
# โโ LLM โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 34 |
+
llm = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=NVIDIA_API_KEY)
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
SYSTEM_PROMPT = (
|
| 37 |
"You are Hermes, a helpful AI assistant. "
|
|
|
|
| 58 |
log.info("[WEB] assistant: %s", reply)
|
| 59 |
return reply
|
| 60 |
|
| 61 |
+
# โโ ไผไธๅพฎไฟก โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 62 |
_wx_cache = {"token": "", "expires": 0}
|
| 63 |
_lock = threading.Lock()
|
| 64 |
|
|
|
|
| 72 |
params={"corpid": WECOM_CORPID, "corpsecret": WECOM_SECRET},
|
| 73 |
timeout=10,
|
| 74 |
).json()
|
| 75 |
+
log.info("[WECOM] get_token: %s", r)
|
| 76 |
_wx_cache["token"] = r.get("access_token", "")
|
| 77 |
_wx_cache["expires"] = time.time() + r.get("expires_in", 7200)
|
| 78 |
return _wx_cache["token"]
|
| 79 |
|
| 80 |
def send_wecom_message(to_user: str, content: str):
|
| 81 |
token = get_wecom_token()
|
|
|
|
|
|
|
| 82 |
r = httpx.post(
|
| 83 |
f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}",
|
| 84 |
+
json={"touser": to_user, "msgtype": "text",
|
| 85 |
+
"agentid": int(WECOM_AGENTID), "text": {"content": content}},
|
| 86 |
+
timeout=10,
|
| 87 |
).json()
|
| 88 |
+
log.info("[WECOM] send to=%s result=%s", to_user, r)
|
| 89 |
|
| 90 |
def decrypt_msg(encrypted: str) -> str:
|
| 91 |
aes_key = base64.b64decode(WECOM_AES_KEY + "=")
|
|
|
|
| 98 |
parts = [WECOM_TOKEN, timestamp, nonce] + ([encrypt] if encrypt else [])
|
| 99 |
return hashlib.sha1("".join(sorted(parts)).encode()).hexdigest()
|
| 100 |
|
| 101 |
+
# โโ FastAPI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 102 |
+
fastapi_app = FastAPI()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
+
@fastapi_app.get("/gateway/wecom")
|
|
|
|
| 105 |
async def wecom_verify(request: Request):
|
| 106 |
p = request.query_params
|
| 107 |
+
log.info("[WECOM] GET params: %s", dict(p))
|
| 108 |
try:
|
| 109 |
+
sig = verify_sig(p.get("timestamp", ""), p.get("nonce", ""), p.get("echostr", ""))
|
| 110 |
+
expected = p.get("msg_signature", "")
|
| 111 |
+
log.info("[WECOM] sig=%s expected=%s match=%s", sig, expected, sig == expected)
|
| 112 |
if sig != expected:
|
| 113 |
return PlainTextResponse("")
|
| 114 |
+
plain = decrypt_msg(p.get("echostr", ""))
|
| 115 |
+
log.info("[WECOM] verify ok plain=%s", plain)
|
| 116 |
return PlainTextResponse(plain)
|
| 117 |
except Exception as e:
|
| 118 |
log.exception("[WECOM] verify error: %s", e)
|
| 119 |
return PlainTextResponse("")
|
| 120 |
|
| 121 |
+
@fastapi_app.post("/gateway/wecom")
|
| 122 |
async def wecom_message(request: Request):
|
| 123 |
p = request.query_params
|
| 124 |
log.info("[WECOM] POST params: %s", dict(p))
|
| 125 |
try:
|
| 126 |
body = await request.body()
|
| 127 |
+
log.info("[WECOM] body: %s", body.decode())
|
| 128 |
encrypt = ET.fromstring(body.decode()).findtext("Encrypt", "")
|
| 129 |
+
sig = verify_sig(p.get("timestamp", ""), p.get("nonce", ""), encrypt)
|
| 130 |
+
expected = p.get("msg_signature", "")
|
| 131 |
+
log.info("[WECOM] sig=%s expected=%s match=%s", sig, expected, sig == expected)
|
| 132 |
if sig != expected:
|
| 133 |
return PlainTextResponse("success")
|
| 134 |
root = ET.fromstring(decrypt_msg(encrypt))
|
|
|
|
| 137 |
log.info("[WECOM] msg_type=%s from=%s", msg_type, from_user)
|
| 138 |
if msg_type == "text":
|
| 139 |
content = root.findtext("Content", "").strip()
|
| 140 |
+
log.info("[WECOM] content=%s", content)
|
| 141 |
if content:
|
| 142 |
def reply():
|
| 143 |
try:
|
| 144 |
answer = chat_with_hermes(content, [])
|
| 145 |
+
log.info("[WECOM] reply=%s", answer)
|
| 146 |
send_wecom_message(from_user, answer)
|
| 147 |
except Exception as e:
|
| 148 |
log.exception("[WECOM] reply error: %s", e)
|
|
|
|
| 151 |
log.exception("[WECOM] message error: %s", e)
|
| 152 |
return PlainTextResponse("success")
|
| 153 |
|
| 154 |
+
# โโ Gradio ๆ่ฝฝๅฐ FastAPI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 155 |
+
demo = gr.ChatInterface(
|
| 156 |
+
fn=chat_with_hermes,
|
| 157 |
+
title="๐ค Hermes Agent",
|
| 158 |
+
description="Powered by NVIDIA NIM ยท ไผไธๅพฎไฟกๅๆญฅๆฅๅ
ฅ",
|
| 159 |
+
)
|
| 160 |
+
app = gr.mount_gradio_app(fastapi_app, demo, path="/")
|
| 161 |
+
|
| 162 |
# โโ ๅฏๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 163 |
if __name__ == "__main__":
|
| 164 |
+
log.info("WECOM_TOKEN=%s AES_KEY=%s CORPID=%s SECRET=%s NVIDIA=%s",
|
| 165 |
+
bool(WECOM_TOKEN), bool(WECOM_AES_KEY),
|
| 166 |
+
bool(WECOM_CORPID), bool(WECOM_SECRET), bool(NVIDIA_API_KEY))
|
| 167 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|