Spaces:
Runtime error
Runtime error
File size: 11,860 Bytes
b30d305 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | """
能力检测器
"""
import asyncio
import json
from abc import ABC, abstractmethod
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
import httpx
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
from src.utils.config import ChannelConfig, CapabilityTestConfig, ConfigManager
from src.utils.logger import get_logger
from src.utils.exceptions import CapabilityDetectionError, NetworkError, AuthenticationError
class CapabilityStatus(Enum):
"""能力状态枚举"""
SUPPORTED = "supported"
NOT_SUPPORTED = "not_supported"
UNKNOWN = "unknown"
ERROR = "error"
@dataclass
class CapabilityResult:
"""能力检测结果"""
capability: str
status: CapabilityStatus
details: Optional[Dict[str, Any]] = None
error: Optional[str] = None
response_time: Optional[float] = None
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return asdict(self)
@dataclass
class ChannelCapabilities:
"""渠道能力信息"""
provider: str
base_url: str
models: List[str]
capabilities: Dict[str, CapabilityResult]
detection_time: str
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"provider": self.provider,
"base_url": self.base_url,
"models": self.models,
"capabilities": {k: v.to_dict() for k, v in self.capabilities.items()},
"detection_time": self.detection_time
}
class BaseCapabilityDetector(ABC):
"""基础能力检测器"""
def __init__(self, config: ChannelConfig):
self.config = config
self.logger = get_logger(f"detector.{config.provider}")
self.config_manager = ConfigManager()
self.console = Console()
self.target_model = None # 指定要检测的模型
self.debug_mode = False # 调试模式
@abstractmethod
async def detect_models(self) -> List[str]:
"""检测支持的模型"""
pass
@abstractmethod
async def test_capability(self, capability_config: CapabilityTestConfig) -> CapabilityResult:
"""测试单个能力"""
pass
async def detect_all_capabilities(self) -> ChannelCapabilities:
"""检测所有能力"""
from datetime import datetime
self.logger.info(f"Starting capability detection for {self.config.provider}")
# 检测模型(如果已设置目标模型则跳过)
if hasattr(self, 'target_model') and self.target_model:
models = [self.target_model]
self.logger.info(f"Using target model: {self.target_model}")
else:
try:
models = await self.detect_models()
self.logger.info(f"Detected {len(models)} models")
except Exception as e:
self.logger.error(f"Failed to detect models: {e}")
models = []
# 检测能力
capabilities = {}
capability_configs = self.config_manager.get_all_capabilities()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=self.console
) as progress:
task = progress.add_task("检测能力中...", total=len(capability_configs))
for name, config in capability_configs.items():
progress.update(task, description=f"检测 {config.description}")
try:
result = await self.test_capability(config)
capabilities[name] = result
self.logger.info(f"Capability {name}: {result.status.value}")
except Exception as e:
self.logger.error(f"Failed to test capability {name}: {e}")
capabilities[name] = CapabilityResult(
capability=name,
status=CapabilityStatus.ERROR,
error=str(e)
)
progress.advance(task)
return ChannelCapabilities(
provider=self.config.provider,
base_url=self.config.base_url,
models=models,
capabilities=capabilities,
detection_time=datetime.now().isoformat()
)
async def detect_selected_capabilities(self, selected_capabilities: List[str]) -> ChannelCapabilities:
"""检测选定的能力"""
from datetime import datetime
self.logger.info(f"Starting selected capability detection for {self.config.provider}")
# 检测模型(如果已设置目标模型则跳过)
if hasattr(self, 'target_model') and self.target_model:
models = [self.target_model]
self.logger.info(f"Using target model: {self.target_model}")
else:
try:
models = await self.detect_models()
self.logger.info(f"Detected {len(models)} models")
except Exception as e:
self.logger.error(f"Failed to detect models: {e}")
models = []
# 获取所有能力配置
all_capability_configs = self.config_manager.get_all_capabilities()
# 过滤选定的能力
capability_configs = {}
for cap_name in selected_capabilities:
if cap_name in all_capability_configs:
capability_configs[cap_name] = all_capability_configs[cap_name]
else:
self.logger.warning(f"Unknown capability: {cap_name}")
# 检测能力
capabilities = {}
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=self.console
) as progress:
task = progress.add_task("检测能力中...", total=len(capability_configs))
for name, config in capability_configs.items():
progress.update(task, description=f"检测 {config.description}")
try:
result = await self.test_capability(config)
capabilities[name] = result
self.logger.info(f"Capability {name}: {result.status.value}")
except Exception as e:
self.logger.error(f"Failed to test capability {name}: {e}")
capabilities[name] = CapabilityResult(
capability=name,
status=CapabilityStatus.ERROR,
error=str(e)
)
progress.advance(task)
return ChannelCapabilities(
provider=self.config.provider,
base_url=self.config.base_url,
models=models,
capabilities=capabilities,
detection_time=datetime.now().isoformat()
)
async def _make_request(
self,
method: str,
url: str,
data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
timeout: int = 30,
show_details: bool = False
) -> Tuple[int, Dict[str, Any]]:
"""发送HTTP请求"""
default_headers = {
"Content-Type": "application/json"
}
if headers:
default_headers.update(headers)
# 展示请求详情(使用实例的debug_mode或方法参数)
if show_details or getattr(self, 'debug_mode', False):
self._show_request_details(method, url, data, default_headers)
try:
async with httpx.AsyncClient(timeout=timeout) as client:
if method.upper() == "GET":
response = await client.get(url, headers=default_headers)
elif method.upper() == "POST":
response = await client.post(url, json=data, headers=default_headers)
else:
raise ValueError(f"Unsupported HTTP method: {method}")
# 尝试解析JSON响应
try:
response_data = response.json()
except json.JSONDecodeError:
response_data = {"text": response.text}
# 展示响应详情(使用实例的debug_mode或方法参数)
if show_details or getattr(self, 'debug_mode', False):
self._show_response_details(response.status_code, response_data, response.headers)
return response.status_code, response_data
except httpx.TimeoutException:
raise NetworkError(f"Request timeout for {url}")
except httpx.ConnectError:
raise NetworkError(f"Failed to connect to {url}")
except Exception as e:
raise NetworkError(f"Network error: {e}")
def _check_authentication_error(self, status_code: int, response_data: Dict[str, Any]) -> None:
"""检查认证错误"""
if status_code == 401:
raise AuthenticationError("Invalid API key or authentication failed")
elif status_code == 403:
raise AuthenticationError("Access forbidden - check API key permissions")
def _extract_error_message(self, response_data: Dict[str, Any]) -> str:
"""提取错误信息"""
# 常见的错误字段
error_fields = ["error", "message", "detail", "details"]
for field in error_fields:
if field in response_data:
error_info = response_data[field]
if isinstance(error_info, dict):
return error_info.get("message", str(error_info))
return str(error_info)
return "Unknown error"
def _show_request_details(self, method: str, url: str, data: Optional[Dict[str, Any]], headers: Dict[str, str]):
"""记录请求详情(仅在调试模式下)"""
if not getattr(self, 'debug_mode', False):
return
import json
# 只记录到日志文件,不在控制台显示
self.logger.debug(f"HTTP Request: {method.upper()} {url}")
if data:
from src.utils.security import safe_log_request
self.logger.debug(f"Request Body: {safe_log_request(data)}")
def _show_response_details(self, status_code: int, data: Dict[str, Any], headers):
"""记录响应详情(仅在调试模式下)"""
if not getattr(self, 'debug_mode', False):
return
import json
# 只记录到日志文件,不在控制台显示
self.logger.debug(f"HTTP Response: Status {status_code}")
if data:
from src.utils.security import safe_log_response
self.logger.debug(f"Response Body: {safe_log_response(data)}")
class CapabilityDetectorFactory:
"""能力检测器工厂"""
_detectors = {}
@classmethod
def register(cls, provider: str, detector_class):
"""注册检测器"""
cls._detectors[provider] = detector_class
@classmethod
def create(cls, config: ChannelConfig) -> BaseCapabilityDetector:
"""创建检测器"""
if config.provider not in cls._detectors:
raise ValueError(f"Unsupported provider: {config.provider}")
return cls._detectors[config.provider](config)
@classmethod
def get_supported_providers(cls) -> List[str]:
"""获取支持的提供商"""
return list(cls._detectors.keys()) |