优化模型实例创建逻辑,简化API密钥选择,更新阿里巴巴模型的最大Token设置,支持新的Qwen-VL模型,调整思考过程的处理逻辑,增强用户界面设置的动态效果。

This commit is contained in:
Zylan
2025-04-02 13:28:02 +08:00
parent d95b16c73c
commit 794cac0ca4
6 changed files with 218 additions and 77 deletions

51
app.py
View File

@@ -65,34 +65,21 @@ def handle_connect():
def handle_disconnect():
print('Client disconnected')
def create_model_instance(model_id, api_keys, settings):
"""创建模型实例并配置参数"""
def create_model_instance(model_id, settings, is_reasoning=False):
"""创建模型实例"""
# 提取API密钥
api_keys = settings.get('apiKeys', {})
# 获取模型信息
model_info = settings.get('modelInfo', {})
is_reasoning = model_info.get('isReasoning', False)
provider = model_info.get('provider', '').lower()
# 确定API密钥ID
# 确定需要哪个API密钥
api_key_id = None
if provider == 'anthropic':
if "claude" in model_id.lower() or "anthropic" in model_id.lower():
api_key_id = "AnthropicApiKey"
elif provider == 'openai':
elif any(keyword in model_id.lower() for keyword in ["gpt", "openai"]):
api_key_id = "OpenaiApiKey"
elif provider == 'deepseek':
elif "deepseek" in model_id.lower():
api_key_id = "DeepseekApiKey"
elif provider == 'alibaba':
elif "qvq" in model_id.lower() or "alibaba" in model_id.lower() or "qwen" in model_id.lower():
api_key_id = "AlibabaApiKey"
else:
# 根据模型名称
if "claude" in model_id.lower():
api_key_id = "AnthropicApiKey"
elif any(keyword in model_id.lower() for keyword in ["gpt", "openai"]):
api_key_id = "OpenaiApiKey"
elif "deepseek" in model_id.lower():
api_key_id = "DeepseekApiKey"
elif "qvq" in model_id.lower() or "alibaba" in model_id.lower():
api_key_id = "AlibabaApiKey"
api_key = api_keys.get(api_key_id)
if not api_key:
@@ -110,8 +97,10 @@ def create_model_instance(model_id, api_keys, settings):
language=settings.get('language', '中文')
)
# 设置最大输出Token
model_instance.max_tokens = max_tokens
# 设置最大输出Token,但不为阿里巴巴模型设置(它们有自己内部的处理逻辑)
is_alibaba_model = "qvq" in model_id.lower() or "alibaba" in model_id.lower() or "qwen" in model_id.lower()
if not is_alibaba_model:
model_instance.max_tokens = max_tokens
return model_instance
@@ -338,13 +327,16 @@ def handle_analyze_text(data):
# 获取模型和API密钥
model_id = settings.get('model', 'claude-3-7-sonnet-20250219')
api_keys = settings.get('apiKeys', {})
if not text:
socketio.emit('error', {'message': '文本内容不能为空'})
return
model_instance = create_model_instance(model_id, api_keys, settings)
# 获取模型信息,判断是否为推理模型
model_info = settings.get('modelInfo', {})
is_reasoning = model_info.get('isReasoning', False)
model_instance = create_model_instance(model_id, settings, is_reasoning)
# 将推理配置传递给模型
if reasoning_config:
@@ -383,13 +375,16 @@ def handle_analyze_image(data):
# 获取模型和API密钥
model_id = settings.get('model', 'claude-3-7-sonnet-20250219')
api_keys = settings.get('apiKeys', {})
if not image_data:
socketio.emit('error', {'message': '图像数据不能为空'})
return
model_instance = create_model_instance(model_id, api_keys, settings)
# 获取模型信息,判断是否为推理模型
model_info = settings.get('modelInfo', {})
is_reasoning = model_info.get('isReasoning', False)
model_instance = create_model_instance(model_id, settings, is_reasoning)
# 将推理配置传递给模型
if reasoning_config:

View File

@@ -43,7 +43,7 @@
"provider": "openai",
"supportsMultimodal": false,
"isReasoning": true,
"version": "2025-01-31",
"version": "latest",
"description": "OpenAI的o3-mini模型支持图像理解和思考过程"
},
"deepseek-chat": {
@@ -51,7 +51,7 @@
"provider": "deepseek",
"supportsMultimodal": false,
"isReasoning": false,
"version": "2025-01",
"version": "latest",
"description": "DeepSeek最新大模型671B MoE模型支持60 tokens/秒的高速生成"
},
"deepseek-reasoner": {
@@ -69,6 +69,14 @@
"isReasoning": true,
"version": "2025-03-25",
"description": "阿里巴巴通义千问-QVQ-Max版本支持图像理解和思考过程"
},
"qwen-vl-max-latest": {
"name": "Qwen-VL-MAX",
"provider": "alibaba",
"supportsMultimodal": true,
"isReasoning": false,
"version": "latest",
"description": "阿里通义千问VL-MAX模型视觉理解能力最强支持图像理解和复杂任务"
}
}
}

View File

@@ -4,15 +4,54 @@ from openai import OpenAI
from .base import BaseModel
class AlibabaModel(BaseModel):
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None, model_name: str = None):
self.model_name = model_name or "QVQ-Max-2025-03-25" # 默认使用QVQ-Max模型
# 在super().__init__之前设置model_name这样get_default_system_prompt能使用它
super().__init__(api_key, temperature, system_prompt, language)
def get_default_system_prompt(self) -> str:
return """你是一位专业的问题分析与解答助手。当看到一个问题图片时,请:
1. 仔细阅读并理解问题
2. 分析问题的关键组成部分
3. 提供清晰的、逐步的解决方案
4. 如果相关,解释涉及的概念或理论
5. 如果有多种解决方法,先解释最高效的方法"""
"""根据模型名称返回不同的默认系统提示词"""
# 检查是否是通义千问VL模型
if self.model_name and "qwen-vl" in self.model_name:
return """你是通义千问VL视觉语言助手擅长图像理解、文字识别、内容分析和创作。请根据用户提供的图像
1. 仔细阅读并理解问题
2. 分析问题的关键组成部分
3. 提供清晰的、逐步的解决方案
4. 如果相关,解释涉及的概念或理论
5. 如果有多种解决方法,先解释最高效的方法"""
else:
# QVQ模型使用原先的提示词
return """你是一位专业的问题分析与解答助手。当看到一个问题图片时,请:
1. 仔细阅读并理解问题
2. 分析问题的关键组成部分
3. 提供清晰的、逐步的解决方案
4. 如果相关,解释涉及的概念或理论
5. 如果有多种解决方法,先解释最高效的方法"""
def get_model_identifier(self) -> str:
"""根据模型名称返回对应的模型标识符"""
# 直接映射模型ID到DashScope API使用的标识符
model_mapping = {
"QVQ-Max-2025-03-25": "qvq-max",
"qwen-vl-max-latest": "qwen-vl-max", # 修正为正确的API标识符
}
# 从模型映射表中获取模型标识符,如果不存在则使用默认值
model_id = model_mapping.get(self.model_name)
if model_id:
return model_id
# 如果没有精确匹配,检查是否包含特定前缀
if self.model_name and "qwen-vl" in self.model_name:
if "max" in self.model_name:
return "qwen-vl-max"
elif "plus" in self.model_name:
return "qwen-vl-plus"
elif "lite" in self.model_name:
return "qwen-vl-lite"
return "qwen-vl-max" # 默认使用最强版本
# 最后的默认值
return "qvq-max"
def analyze_text(self, text: str, proxies: dict = None) -> Generator[dict, None, None]:
@@ -59,7 +98,7 @@ class AlibabaModel(BaseModel):
messages=messages,
temperature=self.temperature,
stream=True,
max_tokens=self.max_tokens if hasattr(self, 'max_tokens') and self.max_tokens else 4000
max_tokens=self._get_max_tokens()
)
# 记录思考过程和回答
@@ -67,14 +106,17 @@ class AlibabaModel(BaseModel):
answer_content = ""
is_answering = False
# 检查是否为通义千问VL模型不支持reasoning_content
is_qwen_vl = "qwen-vl" in self.get_model_identifier()
for chunk in response:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
# 处理思考过程
if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
# 处理思考过程仅适用于QVQ模型
if not is_qwen_vl and hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
reasoning_content += delta.reasoning_content
# 思考过程作为一个独立的内容发送
yield {
@@ -84,7 +126,7 @@ class AlibabaModel(BaseModel):
}
elif delta.content != "":
# 判断是否开始回答(从思考过程切换到回答)
if not is_answering:
if not is_answering and not is_qwen_vl:
is_answering = True
# 发送完整的思考过程
if reasoning_content:
@@ -126,7 +168,7 @@ class AlibabaModel(BaseModel):
}
def analyze_image(self, image_data: str, proxies: dict = None) -> Generator[dict, None, None]:
"""Stream QVQ-Max's response for image analysis"""
"""Stream model's response for image analysis"""
try:
# Initial status
yield {"status": "started", "content": ""}
@@ -186,7 +228,7 @@ class AlibabaModel(BaseModel):
messages=messages,
temperature=self.temperature,
stream=True,
max_tokens=self.max_tokens if hasattr(self, 'max_tokens') and self.max_tokens else 4000
max_tokens=self._get_max_tokens()
)
# 记录思考过程和回答
@@ -194,14 +236,17 @@ class AlibabaModel(BaseModel):
answer_content = ""
is_answering = False
# 检查是否为通义千问VL模型不支持reasoning_content
is_qwen_vl = "qwen-vl" in self.get_model_identifier()
for chunk in response:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
# 处理思考过程
if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
# 处理思考过程仅适用于QVQ模型
if not is_qwen_vl and hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
reasoning_content += delta.reasoning_content
# 思考过程作为一个独立的内容发送
yield {
@@ -211,7 +256,7 @@ class AlibabaModel(BaseModel):
}
elif delta.content != "":
# 判断是否开始回答(从思考过程切换到回答)
if not is_answering:
if not is_answering and not is_qwen_vl:
is_answering = True
# 发送完整的思考过程
if reasoning_content:
@@ -250,4 +295,12 @@ class AlibabaModel(BaseModel):
yield {
"status": "error",
"error": str(e)
}
}
def _get_max_tokens(self) -> int:
"""根据模型类型返回合适的max_tokens值"""
# 检查是否为通义千问VL模型
if "qwen-vl" in self.get_model_identifier():
return 2000 # 通义千问VL模型最大支持2048留一些余量
# QVQ模型或其他模型
return self.max_tokens if hasattr(self, 'max_tokens') and self.max_tokens else 4000

View File

@@ -126,8 +126,8 @@ class ModelFactory:
temperature=temperature,
system_prompt=system_prompt
)
# 对于DeepSeek模型传递model_name参数
elif "deepseek" in model_name.lower():
else:
# 对于所有其他模型传递model_name参数
return model_class(
api_key=api_key,
temperature=temperature,
@@ -135,14 +135,6 @@ class ModelFactory:
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]]:

View File

@@ -4,7 +4,12 @@ class SnapSolver {
// 初始化属性
this.socket = null;
this.socketId = null;
this.isProcessing = false;
this.cropper = null;
this.autoScrollInterval = null;
this.capturedImage = null; // 存储截图的base64数据
this.userThinkingExpanded = false; // 用户思考过程展开状态偏好
this.originalImage = null;
this.croppedImage = null;
this.extractedContent = '';
@@ -384,14 +389,21 @@ class SnapSolver {
// 添加打字动画效果
this.thinkingContent.classList.add('thinking-typing');
// 默认为折叠状态
this.thinkingContent.classList.add('collapsed');
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
// 更新切换按钮图标
const toggleIcon = document.querySelector('#thinkingToggle .toggle-btn i');
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
}
break;
@@ -411,14 +423,21 @@ class SnapSolver {
// 添加打字动画效果
this.thinkingContent.classList.add('thinking-typing');
// 默认为折叠状态
this.thinkingContent.classList.add('collapsed');
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
// 更新切换按钮图标
const toggleIcon = document.querySelector('#thinkingToggle .toggle-btn i');
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
}
break;
@@ -438,10 +457,21 @@ class SnapSolver {
// 移除打字动画
this.thinkingContent.classList.remove('thinking-typing');
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
// 确保图标正确显示
const toggleIcon = this.thinkingToggle.querySelector('.toggle-btn i');
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
}
break;
@@ -460,6 +490,23 @@ class SnapSolver {
// 移除打字动画
this.thinkingContent.classList.remove('thinking-typing');
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
// 确保图标正确显示
const toggleIcon = this.thinkingToggle.querySelector('.toggle-btn i');
if (toggleIcon) {
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
}
break;
@@ -502,11 +549,21 @@ class SnapSolver {
if (hasThinkingContent) {
// 有思考内容,显示思考组件
this.thinkingSection.classList.remove('hidden');
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.add('collapsed');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
const toggleBtn = document.querySelector('#thinkingToggle .toggle-btn i');
if (toggleBtn) {
toggleBtn.className = 'fas fa-chevron-down';
toggleBtn.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
// 简化提示信息
@@ -580,16 +637,20 @@ class SnapSolver {
// 使用setElementContent方法处理Markdown
this.setElementContent(this.thinkingContent, data.thinking);
// 记住用户的展开/折叠状态
const wasExpanded = this.thinkingContent.classList.contains('expanded');
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.remove('collapsed');
// 如果之前没有设置状态,默认为折叠
if (!wasExpanded && !this.thinkingContent.classList.contains('collapsed')) {
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
const toggleIcon = this.thinkingToggle.querySelector('.toggle-btn i');
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
}
}
const toggleIcon = this.thinkingToggle.querySelector('.toggle-btn i');
if (toggleIcon) {
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
});
@@ -604,10 +665,21 @@ class SnapSolver {
// 使用setElementContent方法处理Markdown
this.setElementContent(this.thinkingContent, data.thinking);
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
// 确保图标正确显示
const toggleIcon = this.thinkingToggle.querySelector('.toggle-btn i');
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
});
@@ -633,12 +705,20 @@ class SnapSolver {
// 使用setElementContent方法处理Markdown
this.setElementContent(this.thinkingContent, data.thinking);
// 确保初始状态为折叠
// 根据用户偏好设置展开/折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.add('collapsed');
this.thinkingContent.classList.remove('collapsed');
if (this.userThinkingExpanded) {
this.thinkingContent.classList.add('expanded');
} else {
this.thinkingContent.classList.add('collapsed');
}
const toggleIcon = this.thinkingToggle.querySelector('.toggle-btn i');
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
toggleIcon.className = this.userThinkingExpanded ?
'fas fa-chevron-up' : 'fas fa-chevron-down';
}
// 弹出提示
@@ -1079,6 +1159,8 @@ class SnapSolver {
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-down';
}
// 更新用户偏好
this.userThinkingExpanded = false;
} else {
console.log('展开思考内容');
// 添加展开状态
@@ -1086,6 +1168,8 @@ class SnapSolver {
if (toggleIcon) {
toggleIcon.className = 'fas fa-chevron-up';
}
// 更新用户偏好
this.userThinkingExpanded = true;
// 当展开思考内容时,确保代码高亮生效
if (window.hljs) {

View File

@@ -393,6 +393,15 @@ class SettingsManager {
this.reasoningDepthSelect.value === 'extended';
this.thinkBudgetGroup.style.display = showThinkBudget ? 'block' : 'none';
}
// 控制最大Token设置的显示
// 阿里巴巴模型不支持自定义Token设置
const maxTokensGroup = this.maxTokensInput ? this.maxTokensInput.closest('.setting-group') : null;
if (maxTokensGroup) {
// 如果是阿里巴巴模型隐藏Token设置
const isAlibabaModel = modelInfo.provider === 'alibaba';
maxTokensGroup.style.display = isAlibabaModel ? 'none' : 'block';
}
}
saveSettings() {