from typing import Dict, Type, Any import json import os import importlib from .base import BaseModel from .mathpix import MathpixModel # MathpixModel仍然需要直接导入,因为它是特殊工具 class ModelFactory: # 模型基本信息,包含类型和特性 _models: Dict[str, Dict[str, Any]] = {} _class_map: Dict[str, Type[BaseModel]] = {} @classmethod def initialize(cls): """从配置文件加载模型信息""" try: config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'models.json') with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) # 加载提供商信息和类映射 providers = config.get('providers', {}) for provider_id, provider_info in providers.items(): class_name = provider_info.get('class_name') if class_name: # 从当前包动态导入模型类 module = importlib.import_module(f'.{provider_id.lower()}', package=__package__) cls._class_map[provider_id] = getattr(module, class_name) # 加载模型信息 for model_id, model_info in config.get('models', {}).items(): provider_id = model_info.get('provider') if provider_id and provider_id in cls._class_map: cls._models[model_id] = { 'class': cls._class_map[provider_id], 'is_multimodal': model_info.get('supportsMultimodal', False), 'is_reasoning': model_info.get('isReasoning', False), 'display_name': model_info.get('name', model_id), 'description': model_info.get('description', '') } # 添加Mathpix模型(特殊工具模型) cls._models['mathpix'] = { 'class': MathpixModel, 'is_multimodal': True, 'is_reasoning': False, 'display_name': 'Mathpix OCR', 'description': '文本提取工具,适用于数学公式和文本', 'is_ocr_only': True } print(f"已从配置加载 {len(cls._models)} 个模型") except Exception as e: print(f"加载模型配置失败: {str(e)}") cls._initialize_defaults() @classmethod def _initialize_defaults(cls): """初始化默认模型(当配置加载失败时)""" print("使用默认模型配置") # 导入所有模型类作为备份 from .anthropic import AnthropicModel from .openai import OpenAIModel from .deepseek import DeepSeekModel cls._models = { 'claude-3-7-sonnet-20250219': { 'class': AnthropicModel, 'is_multimodal': True, 'is_reasoning': True, 'display_name': 'Claude 3.7 Sonnet', 'description': '强大的Claude 3.7 Sonnet模型,支持图像理解和思考过程' }, 'gpt-4o-2024-11-20': { 'class': OpenAIModel, 'is_multimodal': True, 'is_reasoning': False, 'display_name': 'GPT-4o', 'description': 'OpenAI的GPT-4o模型,支持图像理解' }, 'deepseek-reasoner': { 'class': DeepSeekModel, 'is_multimodal': False, 'is_reasoning': True, 'display_name': 'DeepSeek Reasoner', 'description': 'DeepSeek推理模型,提供详细思考过程(仅支持文本)' }, 'mathpix': { 'class': MathpixModel, 'is_multimodal': True, 'is_reasoning': False, 'display_name': 'Mathpix OCR', 'description': '文本提取工具,适用于数学公式和文本', 'is_ocr_only': True } } @classmethod def create_model(cls, model_name: str, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None) -> BaseModel: """ Create and return an instance of the specified model. Args: model_name: The identifier of the model to create api_key: The API key for the model temperature: Optional temperature parameter for response generation system_prompt: Optional custom system prompt language: Optional language preference for responses Returns: An instance of the specified model Raises: ValueError: If the model_name is not recognized """ model_info = cls._models.get(model_name) if not model_info: raise ValueError(f"Unknown model: {model_name}") model_class = model_info['class'] # 对于Mathpix模型,不传递language参数 if model_name == 'mathpix': return model_class( api_key=api_key, temperature=temperature, system_prompt=system_prompt ) # 对于DeepSeek模型,传递model_name参数 elif "deepseek" in model_name.lower(): return model_class( api_key=api_key, temperature=temperature, system_prompt=system_prompt, language=language, model_name=model_name ) else: # 对于其他模型,传递所有参数 return model_class( api_key=api_key, temperature=temperature, system_prompt=system_prompt, language=language ) @classmethod def get_available_models(cls) -> list[Dict[str, Any]]: """Return a list of available models with their information""" models_info = [] for model_id, info in cls._models.items(): # 跳过仅OCR工具模型 if info.get('is_ocr_only', False): continue models_info.append({ 'id': model_id, 'display_name': info.get('display_name', model_id), 'description': info.get('description', ''), 'is_multimodal': info.get('is_multimodal', False), 'is_reasoning': info.get('is_reasoning', False) }) return models_info @classmethod def get_model_ids(cls) -> list[str]: """Return a list of available model identifiers""" return [model_id for model_id in cls._models.keys() if not cls._models[model_id].get('is_ocr_only', False)] @classmethod def is_multimodal(cls, model_name: str) -> bool: """判断模型是否支持多模态输入""" return cls._models.get(model_name, {}).get('is_multimodal', False) @classmethod def is_reasoning(cls, model_name: str) -> bool: """判断模型是否为推理模型""" return cls._models.get(model_name, {}).get('is_reasoning', False) @classmethod def get_model_display_name(cls, model_name: str) -> str: """获取模型的显示名称""" return cls._models.get(model_name, {}).get('display_name', model_name) @classmethod def register_model(cls, model_name: str, model_class: Type[BaseModel], is_multimodal: bool = False, is_reasoning: bool = False, display_name: str = None, description: str = None) -> None: """ Register a new model type with the factory. Args: model_name: The identifier for the model model_class: The model class to register is_multimodal: Whether the model supports image input is_reasoning: Whether the model provides reasoning process display_name: Human-readable name for the model description: Description of the model """ cls._models[model_name] = { 'class': model_class, 'is_multimodal': is_multimodal, 'is_reasoning': is_reasoning, 'display_name': display_name or model_name, 'description': description or '' }