feat(llm): 添加gemini-2.5-flash支持并增强API调用可靠性

添加对gemini-2.5-flash模型的支持并更新示例配置
实现模型验证的严格/宽松模式配置
为API调用添加重试机制和超时配置
增加对更多HTTP错误状态码的处理
This commit is contained in:
linyq 2025-08-03 20:06:14 +08:00
parent 062d317261
commit 864ebea1be
5 changed files with 181 additions and 45 deletions

View File

@ -57,14 +57,33 @@ class BaseLLMProvider(ABC):
"""验证配置参数"""
if not self.api_key:
raise ConfigurationError("API密钥不能为空", "api_key")
if not self.model_name:
raise ConfigurationError("模型名称不能为空", "model_name")
if self.model_name not in self.supported_models:
from .exceptions import ModelNotSupportedError
raise ModelNotSupportedError(self.model_name, self.provider_name)
# 检查模型支持情况
self._validate_model_support()
def _validate_model_support(self):
"""验证模型支持情况"""
from app.config import config
from .exceptions import ModelNotSupportedError
from loguru import logger
# 获取模型验证模式配置
strict_model_validation = config.app.get('strict_model_validation', True)
if self.model_name not in self.supported_models:
if strict_model_validation:
# 严格模式:抛出异常
raise ModelNotSupportedError(self.model_name, self.provider_name)
else:
# 宽松模式:仅记录警告
logger.warning(
f"模型 {self.model_name} 未在供应商 {self.provider_name} 的预定义支持列表中,"
f"但已启用宽松验证模式。支持的模型列表: {self.supported_models}"
)
def _initialize(self):
"""初始化提供商特定设置,子类可重写"""
pass
@ -77,11 +96,15 @@ class BaseLLMProvider(ABC):
def _handle_api_error(self, status_code: int, response_text: str) -> LLMServiceError:
"""处理API错误返回适当的异常"""
from .exceptions import APICallError, RateLimitError, AuthenticationError
if status_code == 401:
return AuthenticationError()
elif status_code == 429:
return RateLimitError()
elif status_code in [502, 503, 504]:
return APICallError(f"服务器错误 HTTP {status_code}", status_code, response_text)
elif status_code == 524:
return APICallError(f"服务器处理超时 HTTP {status_code}", status_code, response_text)
else:
return APICallError(f"HTTP {status_code}", status_code, response_text)

View File

@ -213,7 +213,8 @@ class LLMConfigValidator:
"确保所有API密钥都已正确配置",
"建议为每个提供商配置base_url以提高稳定性",
"定期检查模型名称是否为最新版本",
"建议配置多个提供商作为备用方案"
"建议配置多个提供商作为备用方案",
"如果使用新发布的模型遇到MODEL_NOT_SUPPORTED错误可以设置 strict_model_validation = false 启用宽松验证模式"
]
}
@ -252,8 +253,8 @@ class LLMConfigValidator:
"""获取示例模型名称"""
examples = {
"gemini": {
"vision": ["gemini-2.0-flash-lite", "gemini-2.0-flash"],
"text": ["gemini-2.0-flash", "gemini-1.5-pro"]
"vision": ["gemini-2.5-flash", "gemini-2.0-flash-lite", "gemini-2.0-flash"],
"text": ["gemini-2.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"]
},
"openai": {
"vision": [],

View File

@ -27,6 +27,7 @@ class GeminiOpenAIVisionProvider(VisionModelProvider):
@property
def supported_models(self) -> List[str]:
return [
"gemini-2.5-flash",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"gemini-1.5-pro",
@ -137,6 +138,7 @@ class GeminiOpenAITextProvider(TextModelProvider):
@property
def supported_models(self) -> List[str]:
return [
"gemini-2.5-flash",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"gemini-1.5-pro",

View File

@ -27,6 +27,7 @@ class GeminiVisionProvider(VisionModelProvider):
@property
def supported_models(self) -> List[str]:
return [
"gemini-2.5-flash",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"gemini-1.5-pro",
@ -136,25 +137,72 @@ class GeminiVisionProvider(VisionModelProvider):
return base64.b64encode(img_bytes).decode('utf-8')
async def _make_api_call(self, payload: Dict[str, Any]) -> Dict[str, Any]:
"""执行原生Gemini API调用"""
"""执行原生Gemini API调用包含重试机制"""
from app.config import config
url = f"{self.base_url}/models/{self.model_name}:generateContent?key={self.api_key}"
response = await asyncio.to_thread(
requests.post,
url,
json=payload,
headers={
"Content-Type": "application/json",
"User-Agent": "NarratoAI/1.0"
},
timeout=120
)
if response.status_code != 200:
error = self._handle_api_error(response.status_code, response.text)
raise error
return response.json()
max_retries = config.app.get('llm_max_retries', 3)
base_timeout = config.app.get('llm_vision_timeout', 120)
for attempt in range(max_retries):
try:
# 根据尝试次数调整超时时间
timeout = base_timeout * (attempt + 1)
logger.debug(f"Gemini API调用尝试 {attempt + 1}/{max_retries},超时设置: {timeout}")
response = await asyncio.to_thread(
requests.post,
url,
json=payload,
headers={
"Content-Type": "application/json",
"User-Agent": "NarratoAI/1.0"
},
timeout=timeout
)
if response.status_code == 200:
return response.json()
# 处理特定的错误状态码
if response.status_code == 429:
# 速率限制,等待后重试
wait_time = 30 * (attempt + 1)
logger.warning(f"Gemini API速率限制等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
elif response.status_code in [502, 503, 504, 524]:
# 服务器错误或超时,可以重试
if attempt < max_retries - 1:
wait_time = 10 * (attempt + 1)
logger.warning(f"Gemini API服务器错误 {response.status_code},等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
# 其他错误,直接抛出
error = self._handle_api_error(response.status_code, response.text)
raise error
except requests.exceptions.Timeout:
if attempt < max_retries - 1:
wait_time = 15 * (attempt + 1)
logger.warning(f"Gemini API请求超时等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
else:
raise APICallError("Gemini API请求超时已达到最大重试次数")
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1:
wait_time = 10 * (attempt + 1)
logger.warning(f"Gemini API网络错误: {str(e)},等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
else:
raise APICallError(f"Gemini API网络错误: {str(e)}")
# 如果所有重试都失败了
raise APICallError("Gemini API调用失败已达到最大重试次数")
def _parse_vision_response(self, response_data: Dict[str, Any]) -> str:
"""解析视觉分析响应"""
@ -192,6 +240,7 @@ class GeminiTextProvider(TextModelProvider):
@property
def supported_models(self) -> List[str]:
return [
"gemini-2.5-flash",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"gemini-1.5-pro",
@ -278,25 +327,72 @@ class GeminiTextProvider(TextModelProvider):
return self._parse_text_response(response_data)
async def _make_api_call(self, payload: Dict[str, Any]) -> Dict[str, Any]:
"""执行原生Gemini API调用"""
"""执行原生Gemini API调用包含重试机制"""
from app.config import config
url = f"{self.base_url}/models/{self.model_name}:generateContent?key={self.api_key}"
response = await asyncio.to_thread(
requests.post,
url,
json=payload,
headers={
"Content-Type": "application/json",
"User-Agent": "NarratoAI/1.0"
},
timeout=120
)
if response.status_code != 200:
error = self._handle_api_error(response.status_code, response.text)
raise error
return response.json()
max_retries = config.app.get('llm_max_retries', 3)
base_timeout = config.app.get('llm_text_timeout', 180) # 文本生成任务使用更长的基础超时时间
for attempt in range(max_retries):
try:
# 根据尝试次数调整超时时间
timeout = base_timeout * (attempt + 1)
logger.debug(f"Gemini文本API调用尝试 {attempt + 1}/{max_retries},超时设置: {timeout}")
response = await asyncio.to_thread(
requests.post,
url,
json=payload,
headers={
"Content-Type": "application/json",
"User-Agent": "NarratoAI/1.0"
},
timeout=timeout
)
if response.status_code == 200:
return response.json()
# 处理特定的错误状态码
if response.status_code == 429:
# 速率限制,等待后重试
wait_time = 30 * (attempt + 1)
logger.warning(f"Gemini API速率限制等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
elif response.status_code in [502, 503, 504, 524]:
# 服务器错误或超时,可以重试
if attempt < max_retries - 1:
wait_time = 15 * (attempt + 1)
logger.warning(f"Gemini API服务器错误 {response.status_code},等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
# 其他错误,直接抛出
error = self._handle_api_error(response.status_code, response.text)
raise error
except requests.exceptions.Timeout:
if attempt < max_retries - 1:
wait_time = 20 * (attempt + 1)
logger.warning(f"Gemini文本API请求超时等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
else:
raise APICallError("Gemini文本API请求超时已达到最大重试次数")
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1:
wait_time = 15 * (attempt + 1)
logger.warning(f"Gemini文本API网络错误: {str(e)},等待 {wait_time} 秒后重试")
await asyncio.sleep(wait_time)
continue
else:
raise APICallError(f"Gemini文本API网络错误: {str(e)}")
# 如果所有重试都失败了
raise APICallError("Gemini文本API调用失败已达到最大重试次数")
def _parse_text_response(self, response_data: Dict[str, Any]) -> str:
"""解析文本生成响应"""

View File

@ -1,5 +1,19 @@
[app]
project_version="0.6.8"
# 模型验证模式配置
# true: 严格模式,只允许使用预定义支持列表中的模型(默认)
# false: 宽松模式,允许使用任何模型名称,仅记录警告
strict_model_validation = true
# LLM API 超时配置(秒)
# 视觉模型基础超时时间
llm_vision_timeout = 120
# 文本模型基础超时时间(解说文案生成等复杂任务需要更长时间)
llm_text_timeout = 180
# API 重试次数
llm_max_retries = 3
# 支持视频理解的大模型提供商
# gemini (谷歌, 需要 VPN)
# siliconflow (硅基流动)