mirror of
https://github.com/Zippland/Snap-Solver.git
synced 2026-02-16 16:26:08 +08:00
重构模型管理和配置加载逻辑,支持多模态和推理模型,优化API密钥管理,改进前端模型选择和版本显示
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
from .base import BaseModel
|
||||
from .claude import ClaudeModel
|
||||
from .gpt4o import GPT4oModel
|
||||
from .anthropic import AnthropicModel
|
||||
from .openai import OpenAIModel
|
||||
from .deepseek import DeepSeekModel
|
||||
from .factory import ModelFactory
|
||||
|
||||
__all__ = [
|
||||
'BaseModel',
|
||||
'ClaudeModel',
|
||||
'GPT4oModel',
|
||||
'AnthropicModel',
|
||||
'OpenAIModel',
|
||||
'DeepSeekModel',
|
||||
'ModelFactory'
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ import requests
|
||||
from typing import Generator
|
||||
from .base import BaseModel
|
||||
|
||||
class ClaudeModel(BaseModel):
|
||||
class AnthropicModel(BaseModel):
|
||||
def get_default_system_prompt(self) -> str:
|
||||
return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question:
|
||||
1. First read and understand the question carefully
|
||||
@@ -168,6 +168,11 @@ class ClaudeModel(BaseModel):
|
||||
# 获取系统提示词,确保包含语言设置
|
||||
system_prompt = self.system_prompt
|
||||
|
||||
# 根据language参数设置回复语言
|
||||
language = self.language or '中文'
|
||||
if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']):
|
||||
system_prompt = f"{system_prompt}\n\n请务必使用{language}回答,无论问题是什么语言。即使在分析图像时也请使用{language}回答。这是最重要的指令。"
|
||||
|
||||
payload = {
|
||||
'model': 'claude-3-7-sonnet-20250219',
|
||||
'stream': True,
|
||||
@@ -191,7 +196,7 @@ class ClaudeModel(BaseModel):
|
||||
},
|
||||
{
|
||||
'type': 'text',
|
||||
'text': "Please analyze this question and provide a detailed solution. If you see multiple questions, focus on solving them one at a time."
|
||||
'text': "请分析这个问题并提供详细的解决方案。如果你看到多个问题,请逐一解决。请务必使用中文回答。"
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -2,9 +2,10 @@ from abc import ABC, abstractmethod
|
||||
from typing import Generator, Any
|
||||
|
||||
class BaseModel(ABC):
|
||||
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None):
|
||||
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None):
|
||||
self.api_key = api_key
|
||||
self.temperature = temperature
|
||||
self.language = language
|
||||
self.system_prompt = system_prompt or self.get_default_system_prompt()
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -105,9 +105,18 @@ class DeepSeekModel(BaseModel):
|
||||
|
||||
client = OpenAI(**client_args)
|
||||
|
||||
# 检查系统提示词是否已包含语言设置指令
|
||||
system_prompt = self.system_prompt
|
||||
language = self.language or '中文'
|
||||
if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']):
|
||||
system_prompt = f"{system_prompt}\n\n请务必使用{language}回答,无论问题是什么语言。即使在分析图像时也请使用{language}回答。"
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=self.get_model_identifier(),
|
||||
messages=[{
|
||||
'role': 'system',
|
||||
'content': system_prompt
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': f"Here's an image of a question to analyze: data:image/png;base64,{image_data}"
|
||||
}],
|
||||
|
||||
@@ -1,20 +1,102 @@
|
||||
from typing import Dict, Type
|
||||
from typing import Dict, Type, Any
|
||||
import json
|
||||
import os
|
||||
import importlib
|
||||
from .base import BaseModel
|
||||
from .claude import ClaudeModel
|
||||
from .gpt4o import GPT4oModel
|
||||
from .deepseek import DeepSeekModel
|
||||
from .mathpix import MathpixModel
|
||||
from .mathpix import MathpixModel # MathpixModel仍然需要直接导入,因为它是特殊工具
|
||||
|
||||
class ModelFactory:
|
||||
_models: Dict[str, Type[BaseModel]] = {
|
||||
'claude-3-7-sonnet-20250219': ClaudeModel,
|
||||
'gpt-4o-2024-11-20': GPT4oModel,
|
||||
'deepseek-reasoner': DeepSeekModel,
|
||||
'mathpix': MathpixModel
|
||||
}
|
||||
# 模型基本信息,包含类型和特性
|
||||
_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) -> BaseModel:
|
||||
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.
|
||||
|
||||
@@ -23,6 +105,7 @@ class ModelFactory:
|
||||
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
|
||||
@@ -30,28 +113,86 @@ class ModelFactory:
|
||||
Raises:
|
||||
ValueError: If the model_name is not recognized
|
||||
"""
|
||||
model_class = cls._models.get(model_name)
|
||||
if not model_class:
|
||||
model_info = cls._models.get(model_name)
|
||||
if not model_info:
|
||||
raise ValueError(f"Unknown model: {model_name}")
|
||||
|
||||
return model_class(
|
||||
api_key=api_key,
|
||||
temperature=temperature,
|
||||
system_prompt=system_prompt
|
||||
)
|
||||
model_class = model_info['class']
|
||||
|
||||
# 对于Mathpix模型,不传递language参数
|
||||
if model_name == 'mathpix':
|
||||
return model_class(
|
||||
api_key=api_key,
|
||||
temperature=temperature,
|
||||
system_prompt=system_prompt
|
||||
)
|
||||
else:
|
||||
# 对于其他模型,传递所有参数
|
||||
return model_class(
|
||||
api_key=api_key,
|
||||
temperature=temperature,
|
||||
system_prompt=system_prompt,
|
||||
language=language
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_available_models(cls) -> list[str]:
|
||||
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 list(cls._models.keys())
|
||||
return [model_id for model_id in cls._models.keys()
|
||||
if not cls._models[model_id].get('is_ocr_only', False)]
|
||||
|
||||
@classmethod
|
||||
def register_model(cls, model_name: str, model_class: Type[BaseModel]) -> None:
|
||||
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] = model_class
|
||||
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 ''
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class MathpixModel(BaseModel):
|
||||
Raises:
|
||||
ValueError: If the API key format is invalid
|
||||
"""
|
||||
# 只传递必需的参数,不传递language参数
|
||||
super().__init__(api_key, temperature, system_prompt)
|
||||
try:
|
||||
self.app_id, self.app_key = api_key.split(':')
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Generator, Dict, Optional
|
||||
from openai import OpenAI
|
||||
from .base import BaseModel
|
||||
|
||||
class GPT4oModel(BaseModel):
|
||||
class OpenAIModel(BaseModel):
|
||||
def get_default_system_prompt(self) -> str:
|
||||
return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question:
|
||||
1. First read and understand the question carefully
|
||||
@@ -126,11 +126,17 @@ class GPT4oModel(BaseModel):
|
||||
# Initialize OpenAI client
|
||||
client = OpenAI(api_key=self.api_key)
|
||||
|
||||
# 检查系统提示词是否已包含语言设置指令
|
||||
system_prompt = self.system_prompt
|
||||
language = self.language or '中文'
|
||||
if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']):
|
||||
system_prompt = f"{system_prompt}\n\n请务必使用{language}回答,无论问题是什么语言。即使在分析图像时也请使用{language}回答。"
|
||||
|
||||
# Prepare messages with image
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": self.system_prompt
|
||||
"content": system_prompt
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
Reference in New Issue
Block a user