feat(llm): enhance LLM connection testing with improved error handling and adapter instantiation
- Bypass LLMFactory cache during connection tests to ensure fresh API calls with latest configuration - Directly instantiate native adapters (Baidu, Minimax, Doubao) and LiteLLM adapter based on provider type - Add comprehensive error handling in LiteLLM adapter with specific exception catching for authentication, rate limiting, and connection errors - Implement user-friendly error messages for common failure scenarios (invalid API key, authentication failure, timeout, connection issues) - Add response validation to detect and report empty API responses - Disable LiteLLM internal caching to guarantee actual API calls during testing - Update available models list with 2025 latest models across all providers (Gemini, OpenAI, Claude, Qwen, DeepSeek, etc.) - Improve error message clarity and debugging information in config endpoint
This commit is contained in:
parent
22c528acf1
commit
7091f891d1
|
|
@ -239,7 +239,8 @@ async def test_llm_connection(
|
|||
current_user: User = Depends(deps.get_current_user),
|
||||
) -> Any:
|
||||
"""测试LLM连接是否正常"""
|
||||
from app.services.llm.factory import LLMFactory
|
||||
from app.services.llm.factory import LLMFactory, NATIVE_ONLY_PROVIDERS
|
||||
from app.services.llm.adapters import LiteLLMAdapter, BaiduAdapter, MinimaxAdapter, DoubaoAdapter
|
||||
from app.services.llm.types import LLMConfig, LLMProvider, LLMRequest, LLMMessage, DEFAULT_MODELS
|
||||
|
||||
try:
|
||||
|
|
@ -278,8 +279,16 @@ async def test_llm_connection(
|
|||
max_tokens=50, # 测试使用较少的token
|
||||
)
|
||||
|
||||
# 创建适配器并测试
|
||||
adapter = LLMFactory.create_adapter(config)
|
||||
# 直接创建新的适配器实例(不使用缓存),确保使用最新的配置
|
||||
if provider in NATIVE_ONLY_PROVIDERS:
|
||||
native_adapter_map = {
|
||||
LLMProvider.BAIDU: BaiduAdapter,
|
||||
LLMProvider.MINIMAX: MinimaxAdapter,
|
||||
LLMProvider.DOUBAO: DoubaoAdapter,
|
||||
}
|
||||
adapter = native_adapter_map[provider](config)
|
||||
else:
|
||||
adapter = LiteLLMAdapter(config)
|
||||
|
||||
test_request = LLMRequest(
|
||||
messages=[
|
||||
|
|
@ -290,6 +299,13 @@ async def test_llm_connection(
|
|||
|
||||
response = await adapter.complete(test_request)
|
||||
|
||||
# 验证响应内容
|
||||
if not response or not response.content:
|
||||
return LLMTestResponse(
|
||||
success=False,
|
||||
message="LLM 返回空响应,请检查 API Key 和配置"
|
||||
)
|
||||
|
||||
return LLMTestResponse(
|
||||
success=True,
|
||||
message="LLM连接测试成功",
|
||||
|
|
@ -298,9 +314,32 @@ async def test_llm_connection(
|
|||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
# 提供更友好的错误信息
|
||||
if "401" in error_msg or "invalid_api_key" in error_msg.lower() or "incorrect api key" in error_msg.lower():
|
||||
return LLMTestResponse(
|
||||
success=False,
|
||||
message=f"LLM连接测试失败: {str(e)}"
|
||||
message="API Key 无效或已过期,请检查后重试"
|
||||
)
|
||||
elif "authentication" in error_msg.lower():
|
||||
return LLMTestResponse(
|
||||
success=False,
|
||||
message="认证失败,请检查 API Key 是否正确"
|
||||
)
|
||||
elif "timeout" in error_msg.lower():
|
||||
return LLMTestResponse(
|
||||
success=False,
|
||||
message="连接超时,请检查网络或 API 地址是否正确"
|
||||
)
|
||||
elif "connection" in error_msg.lower():
|
||||
return LLMTestResponse(
|
||||
success=False,
|
||||
message="无法连接到 API 服务,请检查网络或 API 地址"
|
||||
)
|
||||
|
||||
return LLMTestResponse(
|
||||
success=False,
|
||||
message=f"LLM连接测试失败: {error_msg}"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ class LiteLLMAdapter(BaseLLMAdapter):
|
|||
"""发送请求到 LiteLLM"""
|
||||
import litellm
|
||||
|
||||
# 禁用 LiteLLM 的缓存,确保每次都实际调用 API
|
||||
litellm.cache = None
|
||||
|
||||
# 构建消息
|
||||
messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
|
||||
|
||||
|
|
@ -130,10 +133,30 @@ class LiteLLMAdapter(BaseLLMAdapter):
|
|||
kwargs["frequency_penalty"] = self.config.frequency_penalty
|
||||
kwargs["presence_penalty"] = self.config.presence_penalty
|
||||
|
||||
try:
|
||||
# 调用 LiteLLM
|
||||
response = await litellm.acompletion(**kwargs)
|
||||
except litellm.exceptions.AuthenticationError as e:
|
||||
raise LLMError(f"API Key 无效或已过期: {str(e)}", self.config.provider, 401)
|
||||
except litellm.exceptions.RateLimitError as e:
|
||||
raise LLMError(f"API 调用频率超限: {str(e)}", self.config.provider, 429)
|
||||
except litellm.exceptions.APIConnectionError as e:
|
||||
raise LLMError(f"无法连接到 API 服务: {str(e)}", self.config.provider)
|
||||
except litellm.exceptions.APIError as e:
|
||||
raise LLMError(f"API 错误: {str(e)}", self.config.provider, getattr(e, 'status_code', None))
|
||||
except Exception as e:
|
||||
# 捕获其他异常并重新抛出
|
||||
error_msg = str(e)
|
||||
if "invalid_api_key" in error_msg.lower() or "incorrect api key" in error_msg.lower():
|
||||
raise LLMError(f"API Key 无效: {error_msg}", self.config.provider, 401)
|
||||
elif "authentication" in error_msg.lower():
|
||||
raise LLMError(f"认证失败: {error_msg}", self.config.provider, 401)
|
||||
raise
|
||||
|
||||
# 解析响应
|
||||
if not response:
|
||||
raise LLMError("API 返回空响应", self.config.provider)
|
||||
|
||||
choice = response.choices[0] if response.choices else None
|
||||
if not choice:
|
||||
raise LLMError("API响应格式异常: 缺少choices字段", self.config.provider)
|
||||
|
|
|
|||
|
|
@ -102,73 +102,117 @@ class LLMFactory:
|
|||
|
||||
@classmethod
|
||||
def get_available_models(cls, provider: LLMProvider) -> List[str]:
|
||||
"""获取提供商的可用模型列表"""
|
||||
"""获取提供商的可用模型列表 (2025年最新)"""
|
||||
models = {
|
||||
LLMProvider.GEMINI: [
|
||||
"gemini-3-pro",
|
||||
"gemini-3.0-deep-think",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-pro",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-flash-live-api",
|
||||
"veo-3.1",
|
||||
"veo-3.1-fast",
|
||||
],
|
||||
LLMProvider.OPENAI: [
|
||||
"gpt-5",
|
||||
"gpt-5.1",
|
||||
"gpt-5.1-instant",
|
||||
"gpt-5.1-codex-max",
|
||||
"gpt-4o",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4",
|
||||
"gpt-3.5-turbo",
|
||||
"o1-preview",
|
||||
"o1-mini",
|
||||
"gpt-4.5",
|
||||
"o4-mini",
|
||||
"o3",
|
||||
"o3-mini",
|
||||
"gpt-oss-120b",
|
||||
"gpt-oss-20b",
|
||||
],
|
||||
LLMProvider.CLAUDE: [
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-20240307",
|
||||
"claude-opus-4.5",
|
||||
"claude-sonnet-4.5",
|
||||
"claude-haiku-4.5",
|
||||
"claude-sonnet-4",
|
||||
"claude-opus-4",
|
||||
"claude-3.7-sonnet",
|
||||
"claude-3.5-sonnet",
|
||||
"claude-3.5-haiku",
|
||||
"claude-3-opus",
|
||||
],
|
||||
LLMProvider.QWEN: [
|
||||
"qwen-turbo",
|
||||
"qwen-plus",
|
||||
"qwen-max",
|
||||
"qwen-max-longcontext",
|
||||
"qwen3-max-instruct",
|
||||
"qwen3-235b-a22b",
|
||||
"qwen3-turbo",
|
||||
"qwen3-32b",
|
||||
"qwen3-4b",
|
||||
"qwen3-embedding-8b",
|
||||
"qwen-image",
|
||||
"qwen-vl",
|
||||
"qwen-audio",
|
||||
],
|
||||
LLMProvider.DEEPSEEK: [
|
||||
"deepseek-v3.1-terminus",
|
||||
"deepseek-r1-70b",
|
||||
"deepseek-r1-zero",
|
||||
"deepseek-v3.2-exp",
|
||||
"deepseek-chat",
|
||||
"deepseek-coder",
|
||||
"deepseek-reasoner",
|
||||
"deepseek-ocr",
|
||||
],
|
||||
LLMProvider.ZHIPU: [
|
||||
"glm-4.6",
|
||||
"glm-4.6-reap-218b",
|
||||
"glm-4.5",
|
||||
"glm-4.5v",
|
||||
"glm-4.5-air-106b",
|
||||
"glm-4-flash",
|
||||
"glm-4",
|
||||
"glm-4-plus",
|
||||
"glm-4-long",
|
||||
"glm-4v-flash",
|
||||
"glm-4.1v-thinking",
|
||||
],
|
||||
LLMProvider.MOONSHOT: [
|
||||
"moonshot-v1-8k",
|
||||
"moonshot-v1-32k",
|
||||
"moonshot-v1-128k",
|
||||
"kimi-k2",
|
||||
"kimi-k2-thinking",
|
||||
"kimi-k2-instruct-0905",
|
||||
"kimi-k1.5",
|
||||
"kimi-vl",
|
||||
"kimi-dev-72b",
|
||||
"kimi-researcher",
|
||||
"kimi-linear",
|
||||
],
|
||||
LLMProvider.BAIDU: [
|
||||
"ERNIE-3.5-8K",
|
||||
"ERNIE-4.0-8K",
|
||||
"ERNIE-Speed-8K",
|
||||
"ernie-4.5",
|
||||
"ernie-4.5-21b-a3b-thinking",
|
||||
"ernie-4.0-8k",
|
||||
"ernie-3.5-8k",
|
||||
"ernie-vl",
|
||||
],
|
||||
LLMProvider.MINIMAX: [
|
||||
"abab6.5-chat",
|
||||
"abab6.5s-chat",
|
||||
"abab5.5-chat",
|
||||
"minimax-m2",
|
||||
"minimax-01-text",
|
||||
"minimax-01-vl",
|
||||
"minimax-m1",
|
||||
"speech-2.6",
|
||||
"hailuo-02",
|
||||
"music-1.5",
|
||||
],
|
||||
LLMProvider.DOUBAO: [
|
||||
"doubao-pro-32k",
|
||||
"doubao-pro-128k",
|
||||
"doubao-lite-32k",
|
||||
"doubao-1.6-pro",
|
||||
"doubao-1.5-pro",
|
||||
"doubao-seed-code",
|
||||
"doubao-seed-1.6",
|
||||
"doubao-vision-language",
|
||||
],
|
||||
LLMProvider.OLLAMA: [
|
||||
"llama3",
|
||||
"llama3.1",
|
||||
"llama3.2",
|
||||
"codellama",
|
||||
"mistral",
|
||||
"deepseek-coder-v2",
|
||||
"qwen2.5-coder",
|
||||
"llama3.3-70b",
|
||||
"qwen3-8b",
|
||||
"gemma3-27b",
|
||||
"dolphin-3.0-llama3.1-8b",
|
||||
"cogito-v1",
|
||||
"deepseek-r1",
|
||||
"gpt-oss-120b",
|
||||
"llama3.1-405b",
|
||||
"mistral-nemo",
|
||||
"phi-3",
|
||||
],
|
||||
}
|
||||
return models.get(provider, [])
|
||||
|
|
|
|||
|
|
@ -87,19 +87,19 @@ class LLMError(Exception):
|
|||
self.original_error = original_error
|
||||
|
||||
|
||||
# 各平台默认模型
|
||||
# 各平台默认模型 (2025年最新推荐)
|
||||
DEFAULT_MODELS: Dict[LLMProvider, str] = {
|
||||
LLMProvider.GEMINI: "gemini-2.5-flash",
|
||||
LLMProvider.OPENAI: "gpt-4o-mini",
|
||||
LLMProvider.CLAUDE: "claude-3-5-sonnet-20241022",
|
||||
LLMProvider.QWEN: "qwen-turbo",
|
||||
LLMProvider.DEEPSEEK: "deepseek-chat",
|
||||
LLMProvider.ZHIPU: "glm-4-flash",
|
||||
LLMProvider.MOONSHOT: "moonshot-v1-8k",
|
||||
LLMProvider.BAIDU: "ERNIE-3.5-8K",
|
||||
LLMProvider.MINIMAX: "abab6.5-chat",
|
||||
LLMProvider.DOUBAO: "doubao-pro-32k",
|
||||
LLMProvider.OLLAMA: "llama3",
|
||||
LLMProvider.GEMINI: "gemini-3-pro",
|
||||
LLMProvider.OPENAI: "gpt-5",
|
||||
LLMProvider.CLAUDE: "claude-sonnet-4.5",
|
||||
LLMProvider.QWEN: "qwen3-max-instruct",
|
||||
LLMProvider.DEEPSEEK: "deepseek-v3.1-terminus",
|
||||
LLMProvider.ZHIPU: "glm-4.6",
|
||||
LLMProvider.MOONSHOT: "kimi-k2",
|
||||
LLMProvider.BAIDU: "ernie-4.5",
|
||||
LLMProvider.MINIMAX: "minimax-m2",
|
||||
LLMProvider.DOUBAO: "doubao-1.6-pro",
|
||||
LLMProvider.OLLAMA: "llama3.3-70b",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,25 +11,25 @@ import {
|
|||
import { toast } from "sonner";
|
||||
import { api } from "@/shared/api/database";
|
||||
|
||||
// LLM 提供商配置 - 简化分类
|
||||
// LLM 提供商配置 - 2025年最新
|
||||
const LLM_PROVIDERS = [
|
||||
{ value: 'openai', label: 'OpenAI GPT', icon: '🟢', category: 'litellm', hint: 'gpt-4o, gpt-4o-mini 等' },
|
||||
{ value: 'claude', label: 'Anthropic Claude', icon: '🟣', category: 'litellm', hint: 'claude-3.5-sonnet 等' },
|
||||
{ value: 'gemini', label: 'Google Gemini', icon: '🔵', category: 'litellm', hint: 'gemini-1.5-flash 等' },
|
||||
{ value: 'deepseek', label: 'DeepSeek', icon: '🔷', category: 'litellm', hint: 'deepseek-chat, deepseek-coder' },
|
||||
{ value: 'qwen', label: '通义千问', icon: '🟠', category: 'litellm', hint: 'qwen-turbo, qwen-max 等' },
|
||||
{ value: 'zhipu', label: '智谱AI (GLM)', icon: '🔴', category: 'litellm', hint: 'glm-4-flash, glm-4 等' },
|
||||
{ value: 'moonshot', label: 'Moonshot (Kimi)', icon: '🌙', category: 'litellm', hint: 'moonshot-v1-8k 等' },
|
||||
{ value: 'ollama', label: 'Ollama 本地', icon: '🖥️', category: 'litellm', hint: 'llama3, codellama 等' },
|
||||
{ value: 'baidu', label: '百度文心', icon: '📘', category: 'native', hint: 'ERNIE-3.5-8K (需要 API_KEY:SECRET_KEY)' },
|
||||
{ value: 'minimax', label: 'MiniMax', icon: '⚡', category: 'native', hint: 'abab6.5-chat 等' },
|
||||
{ value: 'doubao', label: '字节豆包', icon: '🎯', category: 'native', hint: 'doubao-pro-32k 等' },
|
||||
{ value: 'openai', label: 'OpenAI GPT', icon: '🟢', category: 'litellm', hint: 'gpt-5, gpt-5-mini, o3 等' },
|
||||
{ value: 'claude', label: 'Anthropic Claude', icon: '🟣', category: 'litellm', hint: 'claude-sonnet-4.5, claude-opus-4 等' },
|
||||
{ value: 'gemini', label: 'Google Gemini', icon: '🔵', category: 'litellm', hint: 'gemini-3-pro, gemini-3-flash 等' },
|
||||
{ value: 'deepseek', label: 'DeepSeek', icon: '🔷', category: 'litellm', hint: 'deepseek-v3.1-terminus, deepseek-v3 等' },
|
||||
{ value: 'qwen', label: '通义千问', icon: '🟠', category: 'litellm', hint: 'qwen3-max-instruct, qwen3-plus 等' },
|
||||
{ value: 'zhipu', label: '智谱AI (GLM)', icon: '🔴', category: 'litellm', hint: 'glm-4.6, glm-4.5-flash 等' },
|
||||
{ value: 'moonshot', label: 'Moonshot (Kimi)', icon: '🌙', category: 'litellm', hint: 'kimi-k2, kimi-k1.5 等' },
|
||||
{ value: 'ollama', label: 'Ollama 本地', icon: '🖥️', category: 'litellm', hint: 'llama3.3-70b, qwen3-8b 等' },
|
||||
{ value: 'baidu', label: '百度文心', icon: '📘', category: 'native', hint: 'ernie-4.5 (需要 API_KEY:SECRET_KEY)' },
|
||||
{ value: 'minimax', label: 'MiniMax', icon: '⚡', category: 'native', hint: 'minimax-m2, minimax-m1 等' },
|
||||
{ value: 'doubao', label: '字节豆包', icon: '🎯', category: 'native', hint: 'doubao-1.6-pro, doubao-1.5-pro 等' },
|
||||
];
|
||||
|
||||
const DEFAULT_MODELS: Record<string, string> = {
|
||||
openai: 'gpt-4o-mini', claude: 'claude-3-5-sonnet-20241022', gemini: 'gemini-2.5-flash',
|
||||
deepseek: 'deepseek-chat', qwen: 'qwen-turbo', zhipu: 'glm-4-flash', moonshot: 'moonshot-v1-8k',
|
||||
ollama: 'llama3', baidu: 'ERNIE-3.5-8K', minimax: 'abab6.5-chat', doubao: 'doubao-pro-32k',
|
||||
openai: 'gpt-5', claude: 'claude-sonnet-4.5', gemini: 'gemini-3-pro',
|
||||
deepseek: 'deepseek-v3.1-terminus', qwen: 'qwen3-max-instruct', zhipu: 'glm-4.6', moonshot: 'kimi-k2',
|
||||
ollama: 'llama3.3-70b', baidu: 'ernie-4.5', minimax: 'minimax-m2', doubao: 'doubao-1.6-pro',
|
||||
};
|
||||
|
||||
interface SystemConfigData {
|
||||
|
|
|
|||
Loading…
Reference in New Issue