{"repo": "allen-xxa/comfynexus", "files": {"source-code/backend/src/core/ai/providers/factory.py": {"632eb35e73cd32d57b9708f8e48b9efb884f2f7e": {"t": 1785674273.5365443, "n": 4728, "h": "3303ff30a4139200", "c": "\"\"\"\nProviderFactory 工厂类\n\n负责根据配置创建对应的 AI Provider 实例。\n使用工厂模式实现 Provider 的动态创建和注册。\n\"\"\"\n\nfrom typing import Dict, Any, Type\nfrom .base_provider import BaseProvider\nfrom .openai_provider import OpenAIProvider\nfrom .spark_provider import SparkProvider\nfrom .volcengine_provider import VolcengineProvider\nfrom .zhipu_provider import ZhipuProvider\nfrom .ollama_provider import OllamaProvider\n\n\nclass ProviderFactory:\n \"\"\"AI Provider 工厂类\n \n 负责创建和管理不同类型的 AI Provider 实例。\n 支持动态注册新的 Provider 类型。\n \"\"\"\n \n # Provider 注册表:provider_name -> Provider 类\n _providers: Dict[str, Type[BaseProvider]] = {\n 'openai': OpenAIProvider,\n 'xflow': OpenAIProvider, # XFlow 兼容 OpenAI API\n 'iflow': OpenAIProvider, # iFlow 兼容 OpenAI API\n 'custom-openai': OpenAIProvider, # 通用 OpenAI API\n 'spark': SparkProvider,\n 'volcengine': VolcengineProvider,\n 'zhipu': ZhipuProvider,\n 'ollama': OllamaProvider,\n }\n \n # Provider 默认 Base URL 配置\n _default_base_urls: Dict[str, str] = {\n 'openai': 'https://api.openai.com/v1',\n 'xflow': 'https://api.xflow.cc/v1',\n 'iflow': 'https://apis.iflow.cn/v1',\n 'volcengine': 'https://ark.cn-beijing.volces.com/api/v3',\n 'zhipu': 'https://open.bigmodel.cn/api/paas/v4',\n 'ollama': 'http://localhost:11434',\n }\n \n @classmethod\n def create(cls, provider_name: str, config: Dict[str, Any], use_proxy: bool = False) -> BaseProvider:\n \"\"\"创建 Provider 实例\n \n Args:\n provider_name: Provider 名称(openai/spark/volcengine/zhipu/ollama)\n config: Provider 配置字典\n {\n \"api_key\": str,\n \"base_url\": str, # 可选\n \"model\": str,\n \"temperature\": float,\n \"max_tokens\": int,\n ...\n }\n use_proxy: 是否使用系统代理(默认 False)\n \n Returns:\n BaseProvider: Provider 实例\n \n Raises:\n ValueError: 当 provider_name 不存在时抛出\n \n Example:\n config = {\n \"api_key\": \"sk-xxx\",\n \"model\": \"gpt-4o\",\n \"temperature\": 0.7\n }\n provider = ProviderFactory.create(\"openai\", config, use_proxy=True)\n \"\"\"\n # 检查 provider_name 是否有效\n if provider_name not in cls._providers:\n available = ', '.join(cls._providers.keys())\n raise ValueError(\n f\"未知的 Provider: '{provider_name}'. \"\n f\"可用的 Provider: {available}\"\n )\n \n # 复制配置,避免修改原始配置\n config = config.copy()\n \n # 如果 base_url 为空且该 provider 有默认 URL,则设置默认值\n if not config.get('base_url') and provider_name in cls._default_base_urls:\n config['base_url'] = cls._default_base_urls[provider_name]\n \n # 获取 Provider 类\n provider_class = cls._providers[provider_name]\n \n # 创建并返回实例(传递 use_proxy 参数)\n return provider_class(config, use_proxy=use_proxy)\n \n @classmethod\n def register(cls, provider_name: str, provider_class: Type[BaseProvider]) -> None:\n \"\"\"注册新的 Provider 类型\n \n Args:\n provider_name: Provider 名称\n provider_class: Provider 类(必须继承自 BaseProvider)\n \n Raises:\n TypeError: 当 provider_class 不是 BaseProvider 的子类时抛出\n \n Example:\n class CustomProvider(BaseProvider):\n ...\n \n ProviderFactory.register(\"custom\", CustomProvider)\n \"\"\"\n # 验证 provider_class 是 BaseProvider 的子类\n if not issubclass(provider_class, BaseProvider):\n raise TypeError(\n f\"Provider 类必须继承自 BaseProvider, \"\n f\"但得到: {provider_class.__name__}\"\n )\n \n # 注册到注册表\n cls._providers[provider_name] = provider_class\n \n @classmethod\n def get_available_providers(cls) -> list[str]:\n \"\"\"获取所有已注册的 Provider 名称列表\n \n Returns:\n Provider 名称列表\n \n Example:\n providers = ProviderFactory.get_available_providers()\n # ['openai', 'spark', 'volcengine', ...]\n \"\"\"\n return list(cls._providers.keys())\n \n @classmethod\n def is_provider_available(cls, provider_name: str) -> bool:\n \"\"\"检查指定的 Provider 是否可用\n \n Args:\n provider_name: Provider 名称\n \n Returns:\n 是否可用\n \n Example:\n if ProviderFactory.is_provider_available(\"openai\"):\n provider = ProviderFactory.create(\"openai\", config)\n \"\"\"\n return provider_name in cls._providers\n"}, "98c7f1ce95484824f2c8d92ea2e4b94403ea2bf0": {"t": 1785846887.4427633, "n": 4728, "h": "3303ff30a4139200", "c": "\"\"\"\nProviderFactory 工厂类\n\n负责根据配置创建对应的 AI Provider 实例。\n使用工厂模式实现 Provider 的动态创建和注册。\n\"\"\"\n\nfrom typing import Dict, Any, Type\nfrom .base_provider import BaseProvider\nfrom .openai_provider import OpenAIProvider\nfrom .spark_provider import SparkProvider\nfrom .volcengine_provider import VolcengineProvider\nfrom .zhipu_provider import ZhipuProvider\nfrom .ollama_provider import OllamaProvider\n\n\nclass ProviderFactory:\n \"\"\"AI Provider 工厂类\n \n 负责创建和管理不同类型的 AI Provider 实例。\n 支持动态注册新的 Provider 类型。\n \"\"\"\n \n # Provider 注册表:provider_name -> Provider 类\n _providers: Dict[str, Type[BaseProvider]] = {\n 'openai': OpenAIProvider,\n 'xflow': OpenAIProvider, # XFlow 兼容 OpenAI API\n 'iflow': OpenAIProvider, # iFlow 兼容 OpenAI API\n 'custom-openai': OpenAIProvider, # 通用 OpenAI API\n 'spark': SparkProvider,\n 'volcengine': VolcengineProvider,\n 'zhipu': ZhipuProvider,\n 'ollama': OllamaProvider,\n }\n \n # Provider 默认 Base URL 配置\n _default_base_urls: Dict[str, str] = {\n 'openai': 'https://api.openai.com/v1',\n 'xflow': 'https://api.xflow.cc/v1',\n 'iflow': 'https://apis.iflow.cn/v1',\n 'volcengine': 'https://ark.cn-beijing.volces.com/api/v3',\n 'zhipu': 'https://open.bigmodel.cn/api/paas/v4',\n 'ollama': 'http://localhost:11434',\n }\n \n @classmethod\n def create(cls, provider_name: str, config: Dict[str, Any], use_proxy: bool = False) -> BaseProvider:\n \"\"\"创建 Provider 实例\n \n Args:\n provider_name: Provider 名称(openai/spark/volcengine/zhipu/ollama)\n config: Provider 配置字典\n {\n \"api_key\": str,\n \"base_url\": str, # 可选\n \"model\": str,\n \"temperature\": float,\n \"max_tokens\": int,\n ...\n }\n use_proxy: 是否使用系统代理(默认 False)\n \n Returns:\n BaseProvider: Provider 实例\n \n Raises:\n ValueError: 当 provider_name 不存在时抛出\n \n Example:\n config = {\n \"api_key\": \"sk-xxx\",\n \"model\": \"gpt-4o\",\n \"temperature\": 0.7\n }\n provider = ProviderFactory.create(\"openai\", config, use_proxy=True)\n \"\"\"\n # 检查 provider_name 是否有效\n if provider_name not in cls._providers:\n available = ', '.join(cls._providers.keys())\n raise ValueError(\n f\"未知的 Provider: '{provider_name}'. \"\n f\"可用的 Provider: {available}\"\n )\n \n # 复制配置,避免修改原始配置\n config = config.copy()\n \n # 如果 base_url 为空且该 provider 有默认 URL,则设置默认值\n if not config.get('base_url') and provider_name in cls._default_base_urls:\n config['base_url'] = cls._default_base_urls[provider_name]\n \n # 获取 Provider 类\n provider_class = cls._providers[provider_name]\n \n # 创建并返回实例(传递 use_proxy 参数)\n return provider_class(config, use_proxy=use_proxy)\n \n @classmethod\n def register(cls, provider_name: str, provider_class: Type[BaseProvider]) -> None:\n \"\"\"注册新的 Provider 类型\n \n Args:\n provider_name: Provider 名称\n provider_class: Provider 类(必须继承自 BaseProvider)\n \n Raises:\n TypeError: 当 provider_class 不是 BaseProvider 的子类时抛出\n \n Example:\n class CustomProvider(BaseProvider):\n ...\n \n ProviderFactory.register(\"custom\", CustomProvider)\n \"\"\"\n # 验证 provider_class 是 BaseProvider 的子类\n if not issubclass(provider_class, BaseProvider):\n raise TypeError(\n f\"Provider 类必须继承自 BaseProvider, \"\n f\"但得到: {provider_class.__name__}\"\n )\n \n # 注册到注册表\n cls._providers[provider_name] = provider_class\n \n @classmethod\n def get_available_providers(cls) -> list[str]:\n \"\"\"获取所有已注册的 Provider 名称列表\n \n Returns:\n Provider 名称列表\n \n Example:\n providers = ProviderFactory.get_available_providers()\n # ['openai', 'spark', 'volcengine', ...]\n \"\"\"\n return list(cls._providers.keys())\n \n @classmethod\n def is_provider_available(cls, provider_name: str) -> bool:\n \"\"\"检查指定的 Provider 是否可用\n \n Args:\n provider_name: Provider 名称\n \n Returns:\n 是否可用\n \n Example:\n if ProviderFactory.is_provider_available(\"openai\"):\n provider = ProviderFactory.create(\"openai\", config)\n \"\"\"\n return provider_name in cls._providers\n"}}, "source-code/backend/src/core/ai/providers/spark_provider.py": {"3decbadd64cf6ac14dbc3bd7c48bd14042aaac05": {"t": 1785809854.275647, "n": 11701, "h": "29d4e02bc0120df6", "c": "\"\"\"\nSparkProvider 实现(讯飞星火)\n\n提供讯飞星火 API 的流式聊天功能,支持:\n- WebSocket 流式响应\n- 连接测试\n- 模型列表获取\n- 错误处理和超时控制\n\n讯飞星火 API 文档:https://www.xfyun.cn/doc/spark/Web.html\n\"\"\"\n\nimport asyncio\nimport time\nimport json\nimport hmac\nimport hashlib\nimport base64\nfrom typing import AsyncIterator, Dict, Any, List, Tuple\nfrom urllib.parse import urlencode, urlparse\nfrom datetime import datetime, timezone\nimport logging\n\ntry:\n import websockets\nexcept ImportError:\n websockets = None\n\nfrom .base_provider import BaseProvider, StreamChunk\nfrom .network_utils import get_websocket_proxy\nfrom ._deep_thinking_prompt import DEEP_THINKING_PROMPT\n\nlogger = logging.getLogger(__name__)\n\n\nclass SparkProvider(BaseProvider):\n \"\"\"讯飞星火 Provider 实现类\n \n 讯飞星火使用 WebSocket 协议进行通信,需要特殊的鉴权方式。\n \"\"\"\n \n # 模型版本对应的 API 地址\n MODEL_URLS = {\n 'v1.5': 'wss://spark-api.xf-yun.com/v1.1/chat',\n 'v2.0': 'wss://spark-api.xf-yun.com/v2.1/chat',\n 'v3.0': 'wss://spark-api.xf-yun.com/v3.1/chat',\n 'v3.5': 'wss://spark-api.xf-yun.com/v3.5/chat',\n 'v4.0': 'wss://spark-api.xf-yun.com/v4.0/chat',\n }\n \n # 模型版本对应的 domain\n MODEL_DOMAINS = {\n 'v1.5': 'general',\n 'v2.0': 'generalv2',\n 'v3.0': 'generalv3',\n 'v3.5': 'generalv3.5',\n 'v4.0': 'generalv4',\n }\n \n def __init__(self, config: Dict[str, Any], use_proxy: bool = False):\n \"\"\"\n 初始化讯飞星火 Provider\n \n Args:\n config: 配置字典\n {\n \"app_id\": str, # 应用 ID(必需)\n \"api_key\": str, # API Key(必需)\n \"api_secret\": str, # API Secret(必需)\n \"model\": str, # 模型版本(必需,如 v3.5)\n \"temperature\": float, # 温度参数(可选,默认 0.5)\n \"max_tokens\": int, # 最大 Token 数(可选,默认 2048)\n \"timeout\": int, # 超时时间(秒,可选,默认 30)\n }\n use_proxy: 是否使用系统代理\n \"\"\"\n super().__init__(config, use_proxy=use_proxy)\n \n # 检查 websockets 库是否可用\n if websockets is None:\n raise ImportError(\n \"SparkProvider 需要 websockets 库。\"\n \"请运行: pip install websockets\"\n )\n \n self.app_id = config.get('app_id', '')\n self.api_key = config.get('api_key', '')\n self.api_secret = config.get('api_secret', '')\n self.timeout = config.get('timeout', 300) # 默认 5 分钟\n \n # 验证必需参数\n if not self.app_id:\n raise ValueError(\"讯飞星火 Provider 需要提供 app_id\")\n if not self.api_key:\n raise ValueError(\"讯飞星火 Provider 需要提供 api_key\")\n if not self.api_secret:\n raise ValueError(\"讯飞星火 Provider 需要提供 api_secret\")\n \n # 验证模型版本\n model = self.get_model()\n if model not in self.MODEL_URLS:\n available = ', '.join(self.MODEL_URLS.keys())\n raise ValueError(\n f\"不支持的讯飞星火模型: '{model}'. \"\n f\"可用模型: {available}\"\n )\n \n def _generate_auth_url(self, base_url: str) -> str:\n \"\"\"\n 生成带鉴权参数的 WebSocket URL\n \n 讯飞星火使用 HMAC-SHA256 签名进行鉴权。\n \n Args:\n base_url: 基础 WebSocket URL\n \n Returns:\n 带鉴权参数的完整 URL\n \"\"\"\n # 解析 URL\n parsed = urlparse(base_url)\n host = parsed.netloc\n path = parsed.path\n \n # 生成 RFC1123 格式的时间戳\n now = datetime.now(timezone.utc)\n date = now.strftime('%a, %d %b %Y %H:%M:%S GMT')\n \n # 构建签名原文\n signature_origin = f\"host: {host}\\ndate: {date}\\nGET {path} HTTP/1.1\"\n \n # 使用 HMAC-SHA256 生成签名\n signature = hmac.new(\n self.api_secret.encode('utf-8'),\n signature_origin.encode('utf-8'),\n hashlib.sha256\n ).digest()\n \n # Base64 编码签名\n signature_base64 = base64.b64encode(signature).decode('utf-8')\n \n # 构建 authorization 字符串\n authorization_origin = (\n f'api_key=\"{self.api_key}\", '\n f'algorithm=\"hmac-sha256\", '\n f'headers=\"host date request-line\", '\n f'signature=\"{signature_base64}\"'\n )\n authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode('utf-8')\n \n # 构建完整 URL\n auth_params = {\n 'authorization': authorization,\n 'date': date,\n 'host': host\n }\n \n return f\"{base_url}?{urlencode(auth_params)}\"\n \n def _build_request_body(\n self, \n messages: List[Dict[str, str]], \n system_prompt: str = None,\n deep_thinking: bool = False\n ) -> dict:\n \"\"\"\n 构建请求体\n \n Args:\n messages: 消息列表\n system_prompt: 系统提示词(可选)\n deep_thinking: 是否启用深度思考(可选)\n \n Returns:\n 请求体字典\n \"\"\"\n # 获取模型对应的 domain\n model = self.get_model()\n domain = self.MODEL_DOMAINS.get(model, 'general')\n \n # 构建消息列表\n request_messages = []\n \n # 讯飞星火不支持 system role,需要将 system prompt 转换为 user 消息\n if system_prompt:\n request_messages.append({\n \"role\": \"user\",\n \"content\": system_prompt\n })\n \n # 如果启用深度思考,添加深度思考提示词\n if deep_thinking:\n request_messages.append({\n \"role\": \"user\",\n \"content\": DEEP_THINKING_PROMPT\n })\n \n # 添加对话消息\n request_messages.extend(messages)\n \n # 构建请求体\n request_body = {\n \"header\": {\n \"app_id\": self.app_id,\n \"uid\": \"user\"\n },\n \"parameter\": {\n \"chat\": {\n \"domain\": domain,\n \"temperature\": self.get_temperature(),\n }\n },\n \"payload\": {\n \"message\": {\n \"text\": request_messages\n }\n }\n }\n \n # 只有在设置了 max_tokens 时才添加到请求体\n max_tokens = self.get_max_tokens()\n if max_tokens is not None:\n request_body[\"parameter\"][\"chat\"][\"max_tokens\"] = max_tokens\n \n return request_body\n \n async def chat_stream(\n self, \n messages: List[Dict[str, str]], \n system_prompt: str = None,\n deep_thinking: bool = False\n ) -> AsyncIterator[StreamChunk]:\n \"\"\"\n 流式聊天\n \n Args:\n messages: 消息列表\n [\n {\"role\": \"user\", \"content\": \"你好\"},\n {\"role\": \"assistant\", \"content\": \"你好!有什么可以帮助你的吗?\"},\n ...\n ]\n system_prompt: 系统提示词(可选)\n deep_thinking: 是否启用深度思考(可选,默认 False)\n \n Yields:\n StreamChunk: 流式响应数据结构,包含 content 和 finish_reason\n \n Raises:\n Exception: WebSocket 连接或通信错误\n \"\"\"\n # 获取模型对应的 WebSocket URL\n model = self.get_model()\n base_url = self.MODEL_URLS[model]\n \n # 生成鉴权 URL\n auth_url = self._generate_auth_url(base_url)\n \n # 构建请求体\n request_body = self._build_request_body(messages, system_prompt, deep_thinking)\n \n # 获取代理配置\n proxy = get_websocket_proxy()\n \n try:\n # 建立 WebSocket 连接\n connect_kwargs = {\n 'ping_interval': None,\n 'close_timeout': self.timeout\n }\n \n # 如果有代理配置,添加代理参数\n if proxy:\n logger.debug(f\"[讯飞星火] 使用代理: {proxy}\")\n # 注意:websockets 库的代理支持可能有限\n # 某些版本可能不支持 proxy 参数\n try:\n connect_kwargs['proxy'] = proxy\n except TypeError:\n logger.warning(\"[讯飞星火] 当前 websockets 版本不支持代理参数\")\n \n async with websockets.connect(auth_url, **connect_kwargs) as websocket:\n # 发送请求\n await websocket.send(json.dumps(request_body, ensure_ascii=False))\n \n # 接收响应\n while True:\n try:\n # 设置接收超时\n response_text = await asyncio.wait_for(\n websocket.recv(),\n timeout=self.timeout\n )\n \n # 解析响应\n response = json.loads(response_text)\n \n # 检查错误\n header = response.get('header', {})\n code = header.get('code', 0)\n \n if code != 0:\n message = header.get('message', '未知错误')\n raise ValueError(f\"讯飞星火 API 错误 ({code}): {message}\")\n \n # 提取内容\n payload = response.get('payload', {})\n choices = payload.get('choices', {})\n text_list = choices.get('text', [])\n \n for text_item in text_list:\n content = text_item.get('content', '')\n if content:\n yield StreamChunk(content=content)\n \n # 检查是否结束\n status = choices.get('status', 0)\n if status == 2: # 2 表示结束\n yield StreamChunk(content=\"\", finish_reason=\"stop\")\n break\n \n except asyncio.TimeoutError:\n raise asyncio.TimeoutError(f\"讯飞星火 API 接收超时({self.timeout}秒)\")\n \n except websockets.exceptions.WebSocketException as e:\n raise Exception(f\"讯飞星火 WebSocket 错误: {str(e)}\")\n \n except Exception as e:\n if isinstance(e, (asyncio.TimeoutError, ValueError)):\n raise\n raise Exception(f\"讯飞星火 API 错误: {str(e)}\")\n \n async def test_connection(self) -> Tuple[bool, str, float]:\n \"\"\"\n 测试连接\n \n Returns:\n (success, message, latency)\n - success: 是否成功\n - message: 结果消息\n - latency: 延迟(毫秒)\n \"\"\"\n start_time = time.time()\n \n try:\n # 发送一个简单的测试消息\n test_messages = [\n {\"role\": \"user\", \"content\": \"你好\"}\n ]\n \n # 尝试获取第一个响应片段\n response_received = False\n async for chunk in self.chat_stream(test_messages):\n if chunk:\n response_received = True\n break\n \n latency = int((time.time() - start_time) * 1000) # 转换为毫秒(整数)\n \n if response_received:\n return True, \"连接成功\", latency\n else:\n return False, \"未收到响应\", latency\n \n except asyncio.TimeoutError:\n latency = int((time.time() - start_time) * 1000)\n return False, \"连接超时\", latency\n \n except Exception as e:\n latency = int((time.time() - start_time) * 1000)\n return False, f\"连接错误: {str(e)}\", latency\n \n\n async def get_available_models_async(self) -> List[str]:\n \"\"\"\n 异步获取可用模型列表\n \n Returns:\n 模型名称列表\n \n Note:\n 讯飞星火的模型列表是固定的,由 MODEL_URLS 定义。\n \"\"\"\n # 讯飞星火的模型列表是固定的,返回固定列表\n return list(self.MODEL_URLS.keys())\n"}, "98c7f1ce95484824f2c8d92ea2e4b94403ea2bf0": {"t": 1785809896.8773384, "n": 11701, "h": "29d4e02bc0120df6", "c": "\"\"\"\nSparkProvider 实现(讯飞星火)\n\n提供讯飞星火 API 的流式聊天功能,支持:\n- WebSocket 流式响应\n- 连接测试\n- 模型列表获取\n- 错误处理和超时控制\n\n讯飞星火 API 文档:https://www.xfyun.cn/doc/spark/Web.html\n\"\"\"\n\nimport asyncio\nimport time\nimport json\nimport hmac\nimport hashlib\nimport base64\nfrom typing import AsyncIterator, Dict, Any, List, Tuple\nfrom urllib.parse import urlencode, urlparse\nfrom datetime import datetime, timezone\nimport logging\n\ntry:\n import websockets\nexcept ImportError:\n websockets = None\n\nfrom .base_provider import BaseProvider, StreamChunk\nfrom .network_utils import get_websocket_proxy\nfrom ._deep_thinking_prompt import DEEP_THINKING_PROMPT\n\nlogger = logging.getLogger(__name__)\n\n\nclass SparkProvider(BaseProvider):\n \"\"\"讯飞星火 Provider 实现类\n \n 讯飞星火使用 WebSocket 协议进行通信,需要特殊的鉴权方式。\n \"\"\"\n \n # 模型版本对应的 API 地址\n MODEL_URLS = {\n 'v1.5': 'wss://spark-api.xf-yun.com/v1.1/chat',\n 'v2.0': 'wss://spark-api.xf-yun.com/v2.1/chat',\n 'v3.0': 'wss://spark-api.xf-yun.com/v3.1/chat',\n 'v3.5': 'wss://spark-api.xf-yun.com/v3.5/chat',\n 'v4.0': 'wss://spark-api.xf-yun.com/v4.0/chat',\n }\n \n # 模型版本对应的 domain\n MODEL_DOMAINS = {\n 'v1.5': 'general',\n 'v2.0': 'generalv2',\n 'v3.0': 'generalv3',\n 'v3.5': 'generalv3.5',\n 'v4.0': 'generalv4',\n }\n \n def __init__(self, config: Dict[str, Any], use_proxy: bool = False):\n \"\"\"\n 初始化讯飞星火 Provider\n \n Args:\n config: 配置字典\n {\n \"app_id\": str, # 应用 ID(必需)\n \"api_key\": str, # API Key(必需)\n \"api_secret\": str, # API Secret(必需)\n \"model\": str, # 模型版本(必需,如 v3.5)\n \"temperature\": float, # 温度参数(可选,默认 0.5)\n \"max_tokens\": int, # 最大 Token 数(可选,默认 2048)\n \"timeout\": int, # 超时时间(秒,可选,默认 30)\n }\n use_proxy: 是否使用系统代理\n \"\"\"\n super().__init__(config, use_proxy=use_proxy)\n \n # 检查 websockets 库是否可用\n if websockets is None:\n raise ImportError(\n \"SparkProvider 需要 websockets 库。\"\n \"请运行: pip install websockets\"\n )\n \n self.app_id = config.get('app_id', '')\n self.api_key = config.get('api_key', '')\n self.api_secret = config.get('api_secret', '')\n self.timeout = config.get('timeout', 300) # 默认 5 分钟\n \n # 验证必需参数\n if not self.app_id:\n raise ValueError(\"讯飞星火 Provider 需要提供 app_id\")\n if not self.api_key:\n raise ValueError(\"讯飞星火 Provider 需要提供 api_key\")\n if not self.api_secret:\n raise ValueError(\"讯飞星火 Provider 需要提供 api_secret\")\n \n # 验证模型版本\n model = self.get_model()\n if model not in self.MODEL_URLS:\n available = ', '.join(self.MODEL_URLS.keys())\n raise ValueError(\n f\"不支持的讯飞星火模型: '{model}'. \"\n f\"可用模型: {available}\"\n )\n \n def _generate_auth_url(self, base_url: str) -> str:\n \"\"\"\n 生成带鉴权参数的 WebSocket URL\n \n 讯飞星火使用 HMAC-SHA256 签名进行鉴权。\n \n Args:\n base_url: 基础 WebSocket URL\n \n Returns:\n 带鉴权参数的完整 URL\n \"\"\"\n # 解析 URL\n parsed = urlparse(base_url)\n host = parsed.netloc\n path = parsed.path\n \n # 生成 RFC1123 格式的时间戳\n now = datetime.now(timezone.utc)\n date = now.strftime('%a, %d %b %Y %H:%M:%S GMT')\n \n # 构建签名原文\n signature_origin = f\"host: {host}\\ndate: {date}\\nGET {path} HTTP/1.1\"\n \n # 使用 HMAC-SHA256 生成签名\n signature = hmac.new(\n self.api_secret.encode('utf-8'),\n signature_origin.encode('utf-8'),\n hashlib.sha256\n ).digest()\n \n # Base64 编码签名\n signature_base64 = base64.b64encode(signature).decode('utf-8')\n \n # 构建 authorization 字符串\n authorization_origin = (\n f'api_key=\"{self.api_key}\", '\n f'algorithm=\"hmac-sha256\", '\n f'headers=\"host date request-line\", '\n f'signature=\"{signature_base64}\"'\n )\n authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode('utf-8')\n \n # 构建完整 URL\n auth_params = {\n 'authorization': authorization,\n 'date': date,\n 'host': host\n }\n \n return f\"{base_url}?{urlencode(auth_params)}\"\n \n def _build_request_body(\n self, \n messages: List[Dict[str, str]], \n system_prompt: str = None,\n deep_thinking: bool = False\n ) -> dict:\n \"\"\"\n 构建请求体\n \n Args:\n messages: 消息列表\n system_prompt: 系统提示词(可选)\n deep_thinking: 是否启用深度思考(可选)\n \n Returns:\n 请求体字典\n \"\"\"\n # 获取模型对应的 domain\n model = self.get_model()\n domain = self.MODEL_DOMAINS.get(model, 'general')\n \n # 构建消息列表\n request_messages = []\n \n # 讯飞星火不支持 system role,需要将 system prompt 转换为 user 消息\n if system_prompt:\n request_messages.append({\n \"role\": \"user\",\n \"content\": system_prompt\n })\n \n # 如果启用深度思考,添加深度思考提示词\n if deep_thinking:\n request_messages.append({\n \"role\": \"user\",\n \"content\": DEEP_THINKING_PROMPT\n })\n \n # 添加对话消息\n request_messages.extend(messages)\n \n # 构建请求体\n request_body = {\n \"header\": {\n \"app_id\": self.app_id,\n \"uid\": \"user\"\n },\n \"parameter\": {\n \"chat\": {\n \"domain\": domain,\n \"temperature\": self.get_temperature(),\n }\n },\n \"payload\": {\n \"message\": {\n \"text\": request_messages\n }\n }\n }\n \n # 只有在设置了 max_tokens 时才添加到请求体\n max_tokens = self.get_max_tokens()\n if max_tokens is not None:\n request_body[\"parameter\"][\"chat\"][\"max_tokens\"] = max_tokens\n \n return request_body\n \n async def chat_stream(\n self, \n messages: List[Dict[str, str]], \n system_prompt: str = None,\n deep_thinking: bool = False\n ) -> AsyncIterator[StreamChunk]:\n \"\"\"\n 流式聊天\n \n Args:\n messages: 消息列表\n [\n {\"role\": \"user\", \"content\": \"你好\"},\n {\"role\": \"assistant\", \"content\": \"你好!有什么可以帮助你的吗?\"},\n ...\n ]\n system_prompt: 系统提示词(可选)\n deep_thinking: 是否启用深度思考(可选,默认 False)\n \n Yields:\n StreamChunk: 流式响应数据结构,包含 content 和 finish_reason\n \n Raises:\n Exception: WebSocket 连接或通信错误\n \"\"\"\n # 获取模型对应的 WebSocket URL\n model = self.get_model()\n base_url = self.MODEL_URLS[model]\n \n # 生成鉴权 URL\n auth_url = self._generate_auth_url(base_url)\n \n # 构建请求体\n request_body = self._build_request_body(messages, system_prompt, deep_thinking)\n \n # 获取代理配置\n proxy = get_websocket_proxy()\n \n try:\n # 建立 WebSocket 连接\n connect_kwargs = {\n 'ping_interval': None,\n 'close_timeout': self.timeout\n }\n \n # 如果有代理配置,添加代理参数\n if proxy:\n logger.debug(f\"[讯飞星火] 使用代理: {proxy}\")\n # 注意:websockets 库的代理支持可能有限\n # 某些版本可能不支持 proxy 参数\n try:\n connect_kwargs['proxy'] = proxy\n except TypeError:\n logger.warning(\"[讯飞星火] 当前 websockets 版本不支持代理参数\")\n \n async with websockets.connect(auth_url, **connect_kwargs) as websocket:\n # 发送请求\n await websocket.send(json.dumps(request_body, ensure_ascii=False))\n \n # 接收响应\n while True:\n try:\n # 设置接收超时\n response_text = await asyncio.wait_for(\n websocket.recv(),\n timeout=self.timeout\n )\n \n # 解析响应\n response = json.loads(response_text)\n \n # 检查错误\n header = response.get('header', {})\n code = header.get('code', 0)\n \n if code != 0:\n message = header.get('message', '未知错误')\n raise ValueError(f\"讯飞星火 API 错误 ({code}): {message}\")\n \n # 提取内容\n payload = response.get('payload', {})\n choices = payload.get('choices', {})\n text_list = choices.get('text', [])\n \n for text_item in text_list:\n content = text_item.get('content', '')\n if content:\n yield StreamChunk(content=content)\n \n # 检查是否结束\n status = choices.get('status', 0)\n if status == 2: # 2 表示结束\n yield StreamChunk(content=\"\", finish_reason=\"stop\")\n break\n \n except asyncio.TimeoutError:\n raise asyncio.TimeoutError(f\"讯飞星火 API 接收超时({self.timeout}秒)\")\n \n except websockets.exceptions.WebSocketException as e:\n raise Exception(f\"讯飞星火 WebSocket 错误: {str(e)}\")\n \n except Exception as e:\n if isinstance(e, (asyncio.TimeoutError, ValueError)):\n raise\n raise Exception(f\"讯飞星火 API 错误: {str(e)}\")\n \n async def test_connection(self) -> Tuple[bool, str, float]:\n \"\"\"\n 测试连接\n \n Returns:\n (success, message, latency)\n - success: 是否成功\n - message: 结果消息\n - latency: 延迟(毫秒)\n \"\"\"\n start_time = time.time()\n \n try:\n # 发送一个简单的测试消息\n test_messages = [\n {\"role\": \"user\", \"content\": \"你好\"}\n ]\n \n # 尝试获取第一个响应片段\n response_received = False\n async for chunk in self.chat_stream(test_messages):\n if chunk:\n response_received = True\n break\n \n latency = int((time.time() - start_time) * 1000) # 转换为毫秒(整数)\n \n if response_received:\n return True, \"连接成功\", latency\n else:\n return False, \"未收到响应\", latency\n \n except asyncio.TimeoutError:\n latency = int((time.time() - start_time) * 1000)\n return False, \"连接超时\", latency\n \n except Exception as e:\n latency = int((time.time() - start_time) * 1000)\n return False, f\"连接错误: {str(e)}\", latency\n \n\n async def get_available_models_async(self) -> List[str]:\n \"\"\"\n 异步获取可用模型列表\n \n Returns:\n 模型名称列表\n \n Note:\n 讯飞星火的模型列表是固定的,由 MODEL_URLS 定义。\n \"\"\"\n # 讯飞星火的模型列表是固定的,返回固定列表\n return list(self.MODEL_URLS.keys())\n"}}}}