mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
feat: register qianfan model provider
This commit is contained in:
@@ -14,6 +14,7 @@ from bridge.reply import Reply, ReplyType
|
|||||||
from common import const
|
from common import const
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
from common.utils import expand_path
|
from common.utils import expand_path
|
||||||
|
from config import conf
|
||||||
from models.openai_compatible_bot import OpenAICompatibleBot
|
from models.openai_compatible_bot import OpenAICompatibleBot
|
||||||
|
|
||||||
|
|
||||||
@@ -68,6 +69,7 @@ class AgentLLMModel(LLMModel):
|
|||||||
_MODEL_BOT_TYPE_MAP = {
|
_MODEL_BOT_TYPE_MAP = {
|
||||||
"wenxin": const.BAIDU, "wenxin-4": const.BAIDU,
|
"wenxin": const.BAIDU, "wenxin-4": const.BAIDU,
|
||||||
"xunfei": const.XUNFEI, const.QWEN: const.QWEN_DASHSCOPE,
|
"xunfei": const.XUNFEI, const.QWEN: const.QWEN_DASHSCOPE,
|
||||||
|
const.QIANFAN: const.QIANFAN,
|
||||||
const.MODELSCOPE: const.MODELSCOPE,
|
const.MODELSCOPE: const.MODELSCOPE,
|
||||||
}
|
}
|
||||||
_MODEL_PREFIX_MAP = [
|
_MODEL_PREFIX_MAP = [
|
||||||
@@ -75,10 +77,10 @@ class AgentLLMModel(LLMModel):
|
|||||||
("gemini", const.GEMINI), ("glm", const.ZHIPU_AI), ("claude", const.CLAUDEAPI),
|
("gemini", const.GEMINI), ("glm", const.ZHIPU_AI), ("claude", const.CLAUDEAPI),
|
||||||
("moonshot", const.MOONSHOT), ("kimi", const.MOONSHOT),
|
("moonshot", const.MOONSHOT), ("kimi", const.MOONSHOT),
|
||||||
("doubao", const.DOUBAO), ("deepseek", const.DEEPSEEK),
|
("doubao", const.DOUBAO), ("deepseek", const.DEEPSEEK),
|
||||||
|
("ernie", const.QIANFAN),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, bridge: Bridge, bot_type: str = "chat"):
|
def __init__(self, bridge: Bridge, bot_type: str = "chat"):
|
||||||
from config import conf
|
|
||||||
super().__init__(model=conf().get("model", const.GPT_41))
|
super().__init__(model=conf().get("model", const.GPT_41))
|
||||||
self.bridge = bridge
|
self.bridge = bridge
|
||||||
self.bot_type = bot_type
|
self.bot_type = bot_type
|
||||||
@@ -87,7 +89,6 @@ class AgentLLMModel(LLMModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self):
|
def model(self):
|
||||||
from config import conf
|
|
||||||
return conf().get("model", const.GPT_41)
|
return conf().get("model", const.GPT_41)
|
||||||
|
|
||||||
@model.setter
|
@model.setter
|
||||||
@@ -96,8 +97,6 @@ class AgentLLMModel(LLMModel):
|
|||||||
|
|
||||||
def _resolve_bot_type(self, model_name: str) -> str:
|
def _resolve_bot_type(self, model_name: str) -> str:
|
||||||
"""Resolve bot type from model name, matching Bridge.__init__ logic."""
|
"""Resolve bot type from model name, matching Bridge.__init__ logic."""
|
||||||
from config import conf
|
|
||||||
|
|
||||||
if conf().get("use_linkai", False) and conf().get("linkai_api_key"):
|
if conf().get("use_linkai", False) and conf().get("linkai_api_key"):
|
||||||
return const.LINKAI
|
return const.LINKAI
|
||||||
# Support custom bot type configuration
|
# Support custom bot type configuration
|
||||||
@@ -117,8 +116,9 @@ class AgentLLMModel(LLMModel):
|
|||||||
return const.MOONSHOT
|
return const.MOONSHOT
|
||||||
if conf().get("bot_type") == "modelscope":
|
if conf().get("bot_type") == "modelscope":
|
||||||
return const.MODELSCOPE
|
return const.MODELSCOPE
|
||||||
|
lowered_model = model_name.lower()
|
||||||
for prefix, btype in self._MODEL_PREFIX_MAP:
|
for prefix, btype in self._MODEL_PREFIX_MAP:
|
||||||
if model_name.startswith(prefix):
|
if lowered_model.startswith(prefix):
|
||||||
return btype
|
return btype
|
||||||
return const.OPENAI
|
return const.OPENAI
|
||||||
|
|
||||||
@@ -746,4 +746,4 @@ class AgentBridge:
|
|||||||
agent.tools = [t for t in agent.tools if t.name != "web_search"]
|
agent.tools = [t for t in agent.tools if t.name != "web_search"]
|
||||||
logger.info("[AgentBridge] web_search tool removed (API key no longer available)")
|
logger.info("[AgentBridge] web_search tool removed (API key no longer available)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[AgentBridge] Failed to refresh conditional tools: {e}")
|
logger.debug(f"[AgentBridge] Failed to refresh conditional tools: {e}")
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ class Bridge(object):
|
|||||||
if model_type and model_type.startswith("deepseek"):
|
if model_type and model_type.startswith("deepseek"):
|
||||||
self.btype["chat"] = const.DEEPSEEK
|
self.btype["chat"] = const.DEEPSEEK
|
||||||
|
|
||||||
|
if model_type and isinstance(model_type, str):
|
||||||
|
lowered_model_type = model_type.lower()
|
||||||
|
if lowered_model_type == const.QIANFAN or lowered_model_type.startswith("ernie"):
|
||||||
|
self.btype["chat"] = const.QIANFAN
|
||||||
|
|
||||||
if model_type in [const.MODELSCOPE]:
|
if model_type in [const.MODELSCOPE]:
|
||||||
self.btype["chat"] = const.MODELSCOPE
|
self.btype["chat"] = const.MODELSCOPE
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ OPEN_AI = "openAI"
|
|||||||
OPENAI = "openai"
|
OPENAI = "openai"
|
||||||
CHATGPT = "chatGPT" # legacy alias for OPENAI, kept for backward compatibility
|
CHATGPT = "chatGPT" # legacy alias for OPENAI, kept for backward compatibility
|
||||||
BAIDU = "baidu"
|
BAIDU = "baidu"
|
||||||
|
QIANFAN = "qianfan"
|
||||||
XUNFEI = "xunfei"
|
XUNFEI = "xunfei"
|
||||||
CHATGPTONAZURE = "chatGPTOnAzure"
|
CHATGPTONAZURE = "chatGPTOnAzure"
|
||||||
LINKAI = "linkai"
|
LINKAI = "linkai"
|
||||||
@@ -85,6 +86,12 @@ DEEPSEEK_REASONER = "deepseek-reasoner" # DeepSeek-R1模型
|
|||||||
DEEPSEEK_V4_FLASH = "deepseek-v4-flash" # DeepSeek V4 Flash - 默认推荐 (思考模式 + 工具调用)
|
DEEPSEEK_V4_FLASH = "deepseek-v4-flash" # DeepSeek V4 Flash - 默认推荐 (思考模式 + 工具调用)
|
||||||
DEEPSEEK_V4_PRO = "deepseek-v4-pro" # DeepSeek V4 Pro - 复杂任务更强 (思考模式 + 工具调用)
|
DEEPSEEK_V4_PRO = "deepseek-v4-pro" # DeepSeek V4 Pro - 复杂任务更强 (思考模式 + 工具调用)
|
||||||
|
|
||||||
|
# Baidu Qianfan / ERNIE
|
||||||
|
ERNIE_45_TURBO_128K = "ernie-4.5-turbo-128k"
|
||||||
|
ERNIE_45_TURBO_32K = "ernie-4.5-turbo-32k"
|
||||||
|
ERNIE_X1_TURBO_32K = "ernie-x1-turbo-32k"
|
||||||
|
ERNIE_4_TURBO_8K = "ERNIE-4.0-Turbo-8K"
|
||||||
|
|
||||||
# Qwen (通义千问 - 阿里云 DashScope)
|
# Qwen (通义千问 - 阿里云 DashScope)
|
||||||
QWEN_TURBO = "qwen-turbo"
|
QWEN_TURBO = "qwen-turbo"
|
||||||
QWEN_PLUS = "qwen-plus"
|
QWEN_PLUS = "qwen-plus"
|
||||||
@@ -159,6 +166,9 @@ MODEL_LIST = [
|
|||||||
# DeepSeek
|
# DeepSeek
|
||||||
DEEPSEEK_V4_FLASH, DEEPSEEK_V4_PRO, DEEPSEEK_CHAT, DEEPSEEK_REASONER,
|
DEEPSEEK_V4_FLASH, DEEPSEEK_V4_PRO, DEEPSEEK_CHAT, DEEPSEEK_REASONER,
|
||||||
|
|
||||||
|
# Baidu Qianfan / ERNIE
|
||||||
|
QIANFAN, ERNIE_45_TURBO_128K, ERNIE_45_TURBO_32K, ERNIE_X1_TURBO_32K, ERNIE_4_TURBO_8K,
|
||||||
|
|
||||||
# MiniMax
|
# MiniMax
|
||||||
MiniMax, MINIMAX_M2_7, MINIMAX_M2_7_HIGHSPEED, MINIMAX_M2_5, MINIMAX_M2_1, MINIMAX_M2_1_LIGHTNING, MINIMAX_M2, MINIMAX_ABAB6_5,
|
MiniMax, MINIMAX_M2_7, MINIMAX_M2_7_HIGHSPEED, MINIMAX_M2_5, MINIMAX_M2_1, MINIMAX_M2_1_LIGHTNING, MINIMAX_M2, MINIMAX_ABAB6_5,
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"model": "deepseek-v4-flash",
|
"model": "deepseek-v4-flash",
|
||||||
"deepseek_api_key": "",
|
"deepseek_api_key": "",
|
||||||
"deepseek_api_base": "https://api.deepseek.com/v1",
|
"deepseek_api_base": "https://api.deepseek.com/v1",
|
||||||
|
"qianfan_api_key": "",
|
||||||
|
"qianfan_api_base": "https://qianfan.baidubce.com/v2",
|
||||||
"minimax_api_key": "",
|
"minimax_api_key": "",
|
||||||
"zhipu_ai_api_key": "",
|
"zhipu_ai_api_key": "",
|
||||||
"ark_api_key": "",
|
"ark_api_key": "",
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ available_setting = {
|
|||||||
"baidu_wenxin_api_key": "", # Baidu api key
|
"baidu_wenxin_api_key": "", # Baidu api key
|
||||||
"baidu_wenxin_secret_key": "", # Baidu secret key
|
"baidu_wenxin_secret_key": "", # Baidu secret key
|
||||||
"baidu_wenxin_prompt_enabled": False, # Enable prompt if you are using ernie character model
|
"baidu_wenxin_prompt_enabled": False, # Enable prompt if you are using ernie character model
|
||||||
|
# Baidu Qianfan / ERNIE OpenAI-compatible API
|
||||||
|
"qianfan_api_key": "", # Baidu Qianfan API key in bce-v3 format
|
||||||
|
"qianfan_api_base": "https://qianfan.baidubce.com/v2", # Qianfan OpenAI-compatible API base
|
||||||
# 讯飞星火API
|
# 讯飞星火API
|
||||||
"xunfei_app_id": "", # 讯飞应用ID
|
"xunfei_app_id": "", # 讯飞应用ID
|
||||||
"xunfei_api_key": "", # 讯飞 API key
|
"xunfei_api_key": "", # 讯飞 API key
|
||||||
@@ -386,6 +389,8 @@ def load_config():
|
|||||||
"minimax_api_base": "MINIMAX_API_BASE",
|
"minimax_api_base": "MINIMAX_API_BASE",
|
||||||
"deepseek_api_key": "DEEPSEEK_API_KEY",
|
"deepseek_api_key": "DEEPSEEK_API_KEY",
|
||||||
"deepseek_api_base": "DEEPSEEK_API_BASE",
|
"deepseek_api_base": "DEEPSEEK_API_BASE",
|
||||||
|
"qianfan_api_key": "QIANFAN_API_KEY",
|
||||||
|
"qianfan_api_base": "QIANFAN_API_BASE",
|
||||||
"zhipu_ai_api_key": "ZHIPU_AI_API_KEY",
|
"zhipu_ai_api_key": "ZHIPU_AI_API_KEY",
|
||||||
"zhipu_ai_api_base": "ZHIPU_AI_API_BASE",
|
"zhipu_ai_api_base": "ZHIPU_AI_API_BASE",
|
||||||
"moonshot_api_key": "MOONSHOT_API_KEY",
|
"moonshot_api_key": "MOONSHOT_API_KEY",
|
||||||
|
|||||||
@@ -42,6 +42,49 @@ CLI_ONLY_COMMANDS = {"start", "stop", "restart"}
|
|||||||
CHAT_ONLY_COMMANDS = set() # context is allowed in both, but behaves differently
|
CHAT_ONLY_COMMANDS = set() # context is allowed in both, but behaves differently
|
||||||
|
|
||||||
|
|
||||||
|
_COW_CLI_SET_PLUGIN_PATH = plugins.instance.current_plugin_path is None
|
||||||
|
if _COW_CLI_SET_PLUGIN_PATH:
|
||||||
|
plugins.instance.current_plugin_path = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class CowCli:
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_bot_type_for_model(model_name: str) -> str:
|
||||||
|
"""Resolve bot_type from model name, reusing AgentBridge mapping."""
|
||||||
|
from common import const
|
||||||
|
_EXACT = {
|
||||||
|
"wenxin": const.BAIDU, "wenxin-4": const.BAIDU,
|
||||||
|
"xunfei": const.XUNFEI, const.QWEN: const.QWEN_DASHSCOPE,
|
||||||
|
const.QIANFAN: const.QIANFAN,
|
||||||
|
const.MODELSCOPE: const.MODELSCOPE,
|
||||||
|
const.MOONSHOT: const.MOONSHOT,
|
||||||
|
"moonshot-v1-8k": const.MOONSHOT, "moonshot-v1-32k": const.MOONSHOT,
|
||||||
|
"moonshot-v1-128k": const.MOONSHOT,
|
||||||
|
}
|
||||||
|
_PREFIX = [
|
||||||
|
("qwen", const.QWEN_DASHSCOPE), ("qwq", const.QWEN_DASHSCOPE),
|
||||||
|
("qvq", const.QWEN_DASHSCOPE),
|
||||||
|
("gemini", const.GEMINI), ("glm", const.ZHIPU_AI),
|
||||||
|
("claude", const.CLAUDEAPI),
|
||||||
|
("moonshot", const.MOONSHOT), ("kimi", const.MOONSHOT),
|
||||||
|
("doubao", const.DOUBAO), ("deepseek", const.DEEPSEEK),
|
||||||
|
("ernie", const.QIANFAN),
|
||||||
|
]
|
||||||
|
if not model_name:
|
||||||
|
return const.OPENAI
|
||||||
|
if model_name in _EXACT:
|
||||||
|
return _EXACT[model_name]
|
||||||
|
if model_name.lower().startswith("minimax") or model_name in ["abab6.5-chat"]:
|
||||||
|
return const.MiniMax
|
||||||
|
if model_name in [const.QWEN_TURBO, const.QWEN_PLUS, const.QWEN_MAX]:
|
||||||
|
return const.QWEN_DASHSCOPE
|
||||||
|
lowered_model = model_name.lower()
|
||||||
|
for prefix, btype in _PREFIX:
|
||||||
|
if lowered_model.startswith(prefix):
|
||||||
|
return btype
|
||||||
|
return const.OPENAI
|
||||||
|
|
||||||
|
|
||||||
@plugins.register(
|
@plugins.register(
|
||||||
name="cow_cli",
|
name="cow_cli",
|
||||||
desc="Handle cow/slash commands in chat messages",
|
desc="Handle cow/slash commands in chat messages",
|
||||||
@@ -428,36 +471,7 @@ class CowCliPlugin(Plugin):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolve_bot_type_for_model(model_name: str) -> str:
|
def _resolve_bot_type_for_model(model_name: str) -> str:
|
||||||
"""Resolve bot_type from model name, reusing AgentBridge mapping."""
|
return CowCli._resolve_bot_type_for_model(model_name)
|
||||||
from common import const
|
|
||||||
_EXACT = {
|
|
||||||
"wenxin": const.BAIDU, "wenxin-4": const.BAIDU,
|
|
||||||
"xunfei": const.XUNFEI, const.QWEN: const.QWEN_DASHSCOPE,
|
|
||||||
const.MODELSCOPE: const.MODELSCOPE,
|
|
||||||
const.MOONSHOT: const.MOONSHOT,
|
|
||||||
"moonshot-v1-8k": const.MOONSHOT, "moonshot-v1-32k": const.MOONSHOT,
|
|
||||||
"moonshot-v1-128k": const.MOONSHOT,
|
|
||||||
}
|
|
||||||
_PREFIX = [
|
|
||||||
("qwen", const.QWEN_DASHSCOPE), ("qwq", const.QWEN_DASHSCOPE),
|
|
||||||
("qvq", const.QWEN_DASHSCOPE),
|
|
||||||
("gemini", const.GEMINI), ("glm", const.ZHIPU_AI),
|
|
||||||
("claude", const.CLAUDEAPI),
|
|
||||||
("moonshot", const.MOONSHOT), ("kimi", const.MOONSHOT),
|
|
||||||
("doubao", const.DOUBAO), ("deepseek", const.DEEPSEEK),
|
|
||||||
]
|
|
||||||
if not model_name:
|
|
||||||
return const.OPENAI
|
|
||||||
if model_name in _EXACT:
|
|
||||||
return _EXACT[model_name]
|
|
||||||
if model_name.lower().startswith("minimax") or model_name in ["abab6.5-chat"]:
|
|
||||||
return const.MiniMax
|
|
||||||
if model_name in [const.QWEN_TURBO, const.QWEN_PLUS, const.QWEN_MAX]:
|
|
||||||
return const.QWEN_DASHSCOPE
|
|
||||||
for prefix, btype in _PREFIX:
|
|
||||||
if model_name.startswith(prefix):
|
|
||||||
return btype
|
|
||||||
return const.OPENAI
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# install-browser (shared logic with cow install-browser CLI)
|
# install-browser (shared logic with cow install-browser CLI)
|
||||||
@@ -1172,3 +1186,7 @@ class CowCliPlugin(Plugin):
|
|||||||
|
|
||||||
def get_help_text(self, **kwargs):
|
def get_help_text(self, **kwargs):
|
||||||
return "在对话中使用 /help 或 cow help 查看可用命令"
|
return "在对话中使用 /help 或 cow help 查看可用命令"
|
||||||
|
|
||||||
|
|
||||||
|
if _COW_CLI_SET_PLUGIN_PATH:
|
||||||
|
plugins.instance.current_plugin_path = None
|
||||||
|
|||||||
70
tests/test_qianfan_provider.py
Normal file
70
tests/test_qianfan_provider.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# encoding:utf-8
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
|
||||||
|
class TestQianfanConstantsAndRouting(unittest.TestCase):
|
||||||
|
def test_qianfan_provider_constant_defined(self):
|
||||||
|
from common import const
|
||||||
|
|
||||||
|
self.assertEqual(const.QIANFAN, "qianfan")
|
||||||
|
|
||||||
|
def test_ernie_constants_are_in_model_list(self):
|
||||||
|
from common import const
|
||||||
|
|
||||||
|
self.assertEqual(const.ERNIE_45_TURBO_128K, "ernie-4.5-turbo-128k")
|
||||||
|
self.assertEqual(const.ERNIE_45_TURBO_32K, "ernie-4.5-turbo-32k")
|
||||||
|
self.assertEqual(const.ERNIE_X1_TURBO_32K, "ernie-x1-turbo-32k")
|
||||||
|
self.assertIn(const.QIANFAN, const.MODEL_LIST)
|
||||||
|
self.assertIn(const.ERNIE_45_TURBO_128K, const.MODEL_LIST)
|
||||||
|
self.assertIn(const.ERNIE_45_TURBO_32K, const.MODEL_LIST)
|
||||||
|
self.assertIn(const.ERNIE_X1_TURBO_32K, const.MODEL_LIST)
|
||||||
|
|
||||||
|
def test_qianfan_config_keys_are_available(self):
|
||||||
|
import config
|
||||||
|
|
||||||
|
self.assertIn("qianfan_api_key", config.available_setting)
|
||||||
|
self.assertIn("qianfan_api_base", config.available_setting)
|
||||||
|
|
||||||
|
def test_agent_bridge_routes_ernie_models_to_qianfan(self):
|
||||||
|
from bridge.agent_bridge import AgentLLMModel
|
||||||
|
from common import const
|
||||||
|
|
||||||
|
model = AgentLLMModel.__new__(AgentLLMModel)
|
||||||
|
fake_conf = MagicMock()
|
||||||
|
fake_conf.get.side_effect = lambda key, default=None: {
|
||||||
|
"use_linkai": False,
|
||||||
|
"linkai_api_key": "",
|
||||||
|
"bot_type": "",
|
||||||
|
}.get(key, default)
|
||||||
|
|
||||||
|
with patch("bridge.agent_bridge.conf", return_value=fake_conf):
|
||||||
|
self.assertEqual(
|
||||||
|
AgentLLMModel._resolve_bot_type(model, "ernie-4.5-turbo-128k"),
|
||||||
|
const.QIANFAN,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
AgentLLMModel._resolve_bot_type(model, "qianfan"),
|
||||||
|
const.QIANFAN,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cow_cli_routes_ernie_models_to_qianfan(self):
|
||||||
|
from common import const
|
||||||
|
from plugins.cow_cli.cow_cli import CowCli
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
CowCli._resolve_bot_type_for_model("ernie-4.5-turbo-128k"),
|
||||||
|
const.QIANFAN,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
CowCli._resolve_bot_type_for_model("qianfan"),
|
||||||
|
const.QIANFAN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user