Commit 51e0cbe

benny-dou <60535774+benny-dou@users.noreply.github.com>
2025-03-10 10:51:55
feat(gpt): add `/qwen` and `/doubao` commands
1 parent 82656c2
src/llm/contexts.py
@@ -71,7 +71,7 @@ async def single_context(client: Client, message: Message) -> dict:
     def clean_text(text: str) -> str:
         if not text:
             return ""
-        for prefix in [PREFIX.GPT, "/gpt", "/gemini", "/ds"]:
+        for prefix in [PREFIX.GPT, "/gpt", "/gemini", "/ds", "/qwen", "/doubao"]:
             text = text.removeprefix(prefix).strip()
         # remove bot tips
         text = re.sub(rf"(.*?){BOT_TIPS}\)", "", text, flags=re.DOTALL).strip()
src/llm/gpt.py
@@ -19,11 +19,12 @@ HELP = f"""🤖**GPT对话**
 `{PREFIX.GPT}` 命令当前模型:
 - 文本模型: **{GPT.TEXT_MODEL_NAME}**
 - 图片模型: **{GPT.IMAGE_MODEL_NAME}**
-- 视频模型(暂时禁用): **{GPT.VIDEO_MODEL_NAME}**
 
 `/gpt` 命令强制使用: **{GPT.OPENAI_MODEL_NAME}**
 `/gemini` 命令强制使用: **{GPT.GEMINI_MODEL_NAME}**
 `/ds` 命令强制使用: **{GPT.DEEPSEEK_MODEL_NAME}**
+`/qwen` 命令强制使用: **{GPT.QWEN_MODEL_NAME}**
+`/doubao` 命令强制使用: **{GPT.DOUBAO_MODEL_NAME}**
 
 使用说明:
 1. 在 `{PREFIX.GPT}` 后接提示词即可与GPT对话
@@ -34,7 +35,7 @@ HELP = f"""🤖**GPT对话**
 
 def is_gpt_conversation(message: Message) -> bool:
     info = parse_msg(message)
-    if startswith_prefix(info["text"], prefix=[PREFIX.GPT, "/gpt", "/gemini", "/ds"]):
+    if startswith_prefix(info["text"], prefix=[PREFIX.GPT, "/gpt", "/gemini", "/ds", "/qwen", "/doubao"]):
         return True
     # is replying to gpt-bot response message?
     if not message.reply_to_message:
@@ -56,27 +57,35 @@ async def gpt_response(client: Client, message: Message, **kwargs):
     # ruff: noqa: RET502, RET503
     info = parse_msg(message)
     # send docs if message == "/ai", without reply
-    if equal_prefix(info["text"], prefix=[PREFIX.GPT, "/gpt", "/gemini", "/ds"]) and not message.reply_to_message:
+    if equal_prefix(info["text"], prefix=[PREFIX.GPT, "/gpt", "/gemini", "/ds", "/qwen", "/doubao"]) and not message.reply_to_message:
         await send2tg(client, message, texts=HELP, **kwargs)
         return
 
     if not is_gpt_conversation(message):
         return
 
-    # /gpt = OpenAI, /gemini = Gemini, /ds = DeepSeek
+    # /gpt = OpenAI, /gemini = Gemini, /ds = DeepSeek, /qwen = Qwen, /doubao = Doubao
     force_model = "N/A"
     if startswith_prefix(info["text"], prefix=["/gpt"]):
         force_model = GPT.OPENAI_MODEL
         if not GPT.OPENAI_API_KEY:
-            return await send2tg(client, message, texts=f"⚠️GPT暂时禁用, 请尝试其他命令\n\n{HELP}", **kwargs)
+            return await send2tg(client, message, texts=f"⚠️GPT未配置API Key, 请尝试其他命令\n\n{HELP}", **kwargs)
     elif startswith_prefix(info["text"], prefix=["/gemini"]):
         force_model = GPT.GEMINI_MODEL
         if not GPT.GEMINI_API_KEY:
-            return await send2tg(client, message, texts=f"⚠️Gemini暂时禁用, 请尝试其他命令\n\n{HELP}", **kwargs)
+            return await send2tg(client, message, texts=f"⚠️Gemini未配置API Key, 请尝试其他命令\n\n{HELP}", **kwargs)
     elif startswith_prefix(info["text"], prefix=["/ds"]):
         force_model = GPT.DEEPSEEK_MODEL
         if not GPT.DEEPSEEK_API_KEY:
-            return await send2tg(client, message, texts=f"⚠️DeepSeek暂时禁用, 请尝试其他命令\n\n{HELP}", **kwargs)
+            return await send2tg(client, message, texts=f"⚠️DeepSeek未配置API Key, 请尝试其他命令\n\n{HELP}", **kwargs)
+    elif startswith_prefix(info["text"], prefix=["/qwen"]):
+        force_model = GPT.QWEN_MODEL
+        if not GPT.QWEN_API_KEY:
+            return await send2tg(client, message, texts=f"⚠️通义千问未配置API Key, 请尝试其他命令\n\n{HELP}", **kwargs)
+    elif startswith_prefix(info["text"], prefix=["/doubao"]):
+        force_model = GPT.DOUBAO_MODEL
+        if not GPT.DOUBAO_API_KEY:
+            return await send2tg(client, message, texts=f"⚠️豆包未配置API Key, 请尝试其他命令\n\n{HELP}", **kwargs)
 
     # cache media_group message, only process once
     if media_group_id := message.media_group_id:
src/llm/models.py
@@ -72,6 +72,14 @@ def get_model_config_with_contexts(model_type: str, contexts: list[dict], force_
         client["api_key"] = GPT.DEEPSEEK_API_KEY
         client["base_url"] = GPT.DEEPSEEK_BASE_URL
         model_name = GPT.DEEPSEEK_MODEL_NAME
+    elif force_model == GPT.QWEN_MODEL:
+        client["api_key"] = GPT.QWEN_API_KEY
+        client["base_url"] = GPT.QWEN_BASE_URL
+        model_name = GPT.QWEN_MODEL_NAME
+    elif force_model == GPT.DOUBAO_MODEL:
+        client["api_key"] = GPT.DOUBAO_API_KEY
+        client["base_url"] = GPT.DOUBAO_BASE_URL
+        model_name = GPT.DOUBAO_MODEL_NAME
     elif force_model == GPT.SUMMARY_MODEL:
         client["api_key"] = GPT.SUMMARY_API_KEY
         client["base_url"] = GPT.SUMMARY_BASE_URL
src/config.py
@@ -183,6 +183,17 @@ class GPT:  # see `llm/README.md`
     DEEPSEEK_MODEL_NAME = os.getenv("GPT_DEEPSEEK_MODEL_NAME", "DeepSeek-R1")
     DEEPSEEK_API_KEY = os.getenv("GPT_DEEPSEEK_API_KEY", "")
     DEEPSEEK_BASE_URL = os.getenv("GPT_DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")
+    # /qwen command
+    QWEN_MODEL = os.getenv("GPT_QWEN_MODEL", "qwen-vl-max")
+    QWEN_MODEL_NAME = os.getenv("GPT_QWEN_MODEL_NAME", "Qwen-VL-Max")
+    QWEN_API_KEY = os.getenv("GPT_QWEN_API_KEY", "")
+    QWEN_BASE_URL = os.getenv("GPT_QWEN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
+    # /doubao command
+    DOUBAO_MODEL = os.getenv("GPT_DOUBAO_MODEL", "doubao-1-5-vision-pro-32k-250115")
+    DOUBAO_MODEL_NAME = os.getenv("GPT_DOUBAO_MODEL_NAME", "豆包-1.5-Pro")
+    DOUBAO_API_KEY = os.getenv("GPT_DOUBAO_API_KEY", "")
+    DOUBAO_BASE_URL = os.getenv("GPT_DOUBAO_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
+
     # /summary command
     SUMMARY_MODEL = os.getenv("GPT_SUMMARY_MODEL", "gpt-4o")
     SUMMARY_MODEL_NAME = os.getenv("GPT_SUMMARY_MODEL_NAME", "GPT-4o")
src/handler.py
@@ -78,7 +78,7 @@ async def handle_utilities(
     """
     kwargs |= {"target_chat": target_chat, "reply_msg_id": reply_msg_id, "show_progress": show_progress, "detail_progress": detail_progress}
     if ai:
-        await gpt_response(client, message, **kwargs)  # /ai /gpt /gemini /ds
+        await gpt_response(client, message, **kwargs)  # /ai /gpt /gemini /ds /qwen /doubao
     if asr:
         await voice_to_text(client, message, **kwargs)  # /asr
     if audio:
@@ -273,7 +273,7 @@ def get_social_media_help(chat_id: int | str, ctype: str, prefixes: list[str] |
         msg += "\n🅱️哔哩哔哩"
         msg += "\n🆕和所有yt-dlp支持的链接\n"
     if permission["ai"]:
-        msg += f"\n🤖**GPT对话**: `{PREFIX.GPT} /gpt /gemini /ds` + 提示词"
+        msg += f"\n🤖**GPT对话**: `{PREFIX.GPT} /gpt /gemini /ds /qwen /doubao` + 提示词"
     if permission["asr"]:
         msg += f"\n🗣**语音转文字**: `{PREFIX.ASR}` 回复语音消息"
     if permission["audio"]:
@@ -286,7 +286,7 @@ def get_social_media_help(chat_id: int | str, ctype: str, prefixes: list[str] |
         msg += f"\n💵**查询价格**: `{PREFIX.PRICE}` + Symbol"
     if permission["subtitle"]:
         msg += f"\n📃**提取字幕**: `{PREFIX.SUBTITLE}` + 油管链接 (或回复油管链接)"
-    if permission["summary"] and permission["ai"]:  # summary is dependent on ai
+    if permission["summary"] and permission["ai"]:  # summary depends on ai
         msg += f"\n🤖**总结历史**: `{PREFIX.AI_SUMMARY} #N` AI总结最近N条对话历史"
     if permission["wget"]:
         msg += f"\n⏬**下载文件**: `{PREFIX.WGET}` + URL"