Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/allen-xxa_comfynexus.json
T

1 line
38 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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"}}}}