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:
lintsinghua 2025-11-28 16:53:01 +08:00
parent 22c528acf1
commit 7091f891d1
5 changed files with 178 additions and 72 deletions

View File

@ -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="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连接测试失败: {str(e)}"
message=f"LLM连接测试失败: {error_msg}"
)

View File

@ -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
# 调用 LiteLLM
response = await litellm.acompletion(**kwargs)
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)

View File

@ -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, [])

View File

@ -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",
}

View File

@ -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 {