mirror of
https://github.com/linyqh/NarratoAI.git
synced 2025-12-11 02:12:50 +00:00
feat(llm): 添加gemini-2.5-flash支持并增强API调用可靠性
添加对gemini-2.5-flash模型的支持并更新示例配置 实现模型验证的严格/宽松模式配置 为API调用添加重试机制和超时配置 增加对更多HTTP错误状态码的处理
This commit is contained in:
parent
062d317261
commit
864ebea1be
@ -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)
|
||||
|
||||
|
||||
@ -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": [],
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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:
|
||||
"""解析文本生成响应"""
|
||||
|
||||
@ -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 (硅基流动)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user