mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-09 01:46:38 +08:00
refactor(error): 统一异常处理和响应格式
这次提交重构了整个应用的异常处理机制,保证了处理方式的一致性,还能提供更详细的错误信息。 主要改动包括: - 修改了 `ApiClient`,现在抛出的异常会同时包含状态码和消息。这样上游服务就能传递准确的 HTTP 错误响应啦。 - 更新了所有服务层(`gemini`、`openai`、`vertex`、`embedding`),现在会捕获这些结构化的异常,不再从字符串里解析错误消息了。 - 增强了路由级别的错误处理,特别是针对流式端点,能正确捕获初始化错误,并返回结构化的 JSON 错误响应,而不是格式错误的 SSE 事件。 - 在所有 API 路由中添加了 `allowed_token` 的日志记录,方便追踪和调试授权问题。 - 还有一些常规的代码清理,比如调整了 import 顺序和格式化代码,提高了可读性和可维护性。
This commit is contained in:
@@ -365,13 +365,9 @@ class GeminiChatService:
|
||||
return self.response_handler.handle_response(response, model, stream=False)
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"Normal API call failed with error: {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
@@ -416,13 +412,9 @@ class GeminiChatService:
|
||||
return response
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"Count tokens API call failed with error: {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
@@ -470,7 +462,6 @@ class GeminiChatService:
|
||||
is_success = False
|
||||
status_code = None
|
||||
final_api_key = api_key
|
||||
last_error_msg = None
|
||||
|
||||
while retries < max_retries:
|
||||
request_datetime = datetime.datetime.now()
|
||||
@@ -509,16 +500,11 @@ class GeminiChatService:
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
last_error_msg = error_log_msg
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.warning(
|
||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
||||
)
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=current_attempt_key,
|
||||
@@ -539,11 +525,11 @@ class GeminiChatService:
|
||||
)
|
||||
else:
|
||||
logger.error(f"No valid API key available after {retries} retries.")
|
||||
break
|
||||
raise
|
||||
|
||||
if retries >= max_retries:
|
||||
logger.error(f"Max retries ({max_retries}) reached for streaming.")
|
||||
break
|
||||
raise
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
latency_ms = int((end_time - start_time) * 1000)
|
||||
@@ -555,26 +541,3 @@ class GeminiChatService:
|
||||
latency_ms=latency_ms,
|
||||
request_time=request_datetime,
|
||||
)
|
||||
|
||||
# Emit final error SSE event if all retries failed
|
||||
if not is_success:
|
||||
# 从错误消息中提取嵌套JSON
|
||||
parsed_error = None
|
||||
if last_error_msg:
|
||||
try:
|
||||
# 查找JSON起始位置
|
||||
json_start = last_error_msg.find('{')
|
||||
if json_start != -1:
|
||||
json_str = last_error_msg[json_start:]
|
||||
parsed_error = json.loads(json_str)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
error_data = {
|
||||
"error": {
|
||||
"code": parsed_error['error']['code'] if (parsed_error and 'error' in parsed_error and 'code' in parsed_error['error']) else (status_code or 500),
|
||||
"message": parsed_error['error']['message'] if (parsed_error and 'error' in parsed_error and 'message' in parsed_error['error']) else (last_error_msg or "Streaming failed"),
|
||||
"status": parsed_error['error']['status'] if (parsed_error and 'error' in parsed_error and 'status' in parsed_error['error']) else "INTERNAL"
|
||||
}
|
||||
}
|
||||
yield json.dumps(error_data, ensure_ascii=False)
|
||||
@@ -3,7 +3,6 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from typing import Any, AsyncGenerator, Dict, List, Optional, Union
|
||||
@@ -339,7 +338,8 @@ class OpenAIChatService:
|
||||
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"API call failed for model {model}: {error_log_msg}")
|
||||
|
||||
# 特别记录 max_tokens 相关的错误
|
||||
@@ -353,9 +353,6 @@ class OpenAIChatService:
|
||||
if "parts" in error_log_msg:
|
||||
logger.error("This is likely a response processing error")
|
||||
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
status_code = int(match.group(1)) if match else 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
model_name=model,
|
||||
@@ -540,20 +537,12 @@ class OpenAIChatService:
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.warning(
|
||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries} with key {current_attempt_key}"
|
||||
)
|
||||
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
if isinstance(e, asyncio.TimeoutError):
|
||||
status_code = 408
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=current_attempt_key,
|
||||
model_name=model,
|
||||
@@ -577,7 +566,7 @@ class OpenAIChatService:
|
||||
logger.error(
|
||||
f"No valid API key available after {retries} retries, ceasing attempts for this request."
|
||||
)
|
||||
break
|
||||
raise
|
||||
else:
|
||||
logger.error(
|
||||
"KeyManager not available, cannot switch API key. Ceasing attempts for this request."
|
||||
@@ -588,6 +577,7 @@ class OpenAIChatService:
|
||||
logger.error(
|
||||
f"Max retries ({max_retries}) reached for streaming model {model}."
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
latency_ms = int((end_time - start_time) * 1000)
|
||||
@@ -600,13 +590,6 @@ class OpenAIChatService:
|
||||
request_time=request_datetime,
|
||||
)
|
||||
|
||||
if not is_success:
|
||||
logger.error(
|
||||
f"Streaming failed permanently for model {model} after {retries} attempts."
|
||||
)
|
||||
yield f"data: {json.dumps({'error': f'Streaming failed after {retries} retries.'})}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
|
||||
async def create_image_chat_completion(
|
||||
self, request: ChatRequest, api_key: str
|
||||
) -> Union[Dict[str, Any], AsyncGenerator[str, None]]:
|
||||
@@ -665,9 +648,9 @@ class OpenAIChatService:
|
||||
yield "data: [DONE]\n\n"
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = f"Stream image completion failed for model {model}: {e}"
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(error_log_msg)
|
||||
status_code = 500
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
model_name=model,
|
||||
@@ -677,8 +660,7 @@ class OpenAIChatService:
|
||||
request_msg={"image_data_truncated": image_data[:1000]},
|
||||
request_datetime=request_datetime,
|
||||
)
|
||||
yield f"data: {json.dumps({'error': error_log_msg})}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
raise
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
latency_ms = int((end_time - start_time) * 1000)
|
||||
@@ -716,9 +698,9 @@ class OpenAIChatService:
|
||||
return result
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = f"Normal image completion failed for model {model}: {e}"
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(error_log_msg)
|
||||
status_code = 500
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
model_name=model,
|
||||
@@ -728,7 +710,7 @@ class OpenAIChatService:
|
||||
request_msg={"image_data_truncated": image_data[:1000]},
|
||||
request_datetime=request_datetime,
|
||||
)
|
||||
raise e
|
||||
raise
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
latency_ms = int((end_time - start_time) * 1000)
|
||||
@@ -742,4 +724,4 @@ class OpenAIChatService:
|
||||
status_code=status_code,
|
||||
latency_ms=latency_ms,
|
||||
request_time=request_datetime,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from typing import Any, AsyncGenerator, Dict, List
|
||||
|
||||
@@ -278,13 +277,9 @@ class GeminiChatService:
|
||||
return self.response_handler.handle_response(response, model, stream=False)
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"Normal API call failed with error: {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
@@ -356,15 +351,11 @@ class GeminiChatService:
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.warning(
|
||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
||||
)
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=current_attempt_key,
|
||||
@@ -385,11 +376,11 @@ class GeminiChatService:
|
||||
)
|
||||
else:
|
||||
logger.error(f"No valid API key available after {retries} retries.")
|
||||
break
|
||||
raise
|
||||
|
||||
if retries >= max_retries:
|
||||
logger.error(f"Max retries ({max_retries}) reached for streaming.")
|
||||
break
|
||||
raise
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
latency_ms = int((end_time - start_time) * 1000)
|
||||
@@ -400,4 +391,4 @@ class GeminiChatService:
|
||||
status_code=status_code,
|
||||
latency_ms=latency_ms,
|
||||
request_time=request_datetime,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
# app/services/chat/api_client.py
|
||||
|
||||
from typing import Dict, Any, AsyncGenerator, Optional
|
||||
import httpx
|
||||
import random
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from app.config.config import settings
|
||||
from app.log.logger import get_api_client_logger
|
||||
from app.core.constants import DEFAULT_TIMEOUT
|
||||
from app.log.logger import get_api_client_logger
|
||||
|
||||
logger = get_api_client_logger()
|
||||
|
||||
|
||||
class ApiClient(ABC):
|
||||
"""API客户端基类"""
|
||||
|
||||
@abstractmethod
|
||||
async def generate_content(self, payload: Dict[str, Any], model: str, api_key: str) -> Dict[str, Any]:
|
||||
async def generate_content(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def stream_generate_content(self, payload: Dict[str, Any], model: str, api_key: str) -> AsyncGenerator[str, None]:
|
||||
async def stream_generate_content(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> AsyncGenerator[str, None]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -50,7 +57,7 @@ class GeminiApiClient(ApiClient):
|
||||
async def get_models(self, api_key: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取可用的 Gemini 模型列表"""
|
||||
timeout = httpx.Timeout(timeout=5)
|
||||
|
||||
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -73,11 +80,13 @@ class GeminiApiClient(ApiClient):
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"请求模型列表失败: {e}")
|
||||
return None
|
||||
|
||||
async def generate_content(self, payload: Dict[str, Any], model: str, api_key: str) -> Dict[str, Any]:
|
||||
|
||||
async def generate_content(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
model = self._get_real_model(model)
|
||||
|
||||
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -85,42 +94,46 @@ class GeminiApiClient(ApiClient):
|
||||
else:
|
||||
proxy_to_use = random.choice(settings.PROXIES)
|
||||
logger.info(f"Using proxy for getting models: {proxy_to_use}")
|
||||
|
||||
|
||||
headers = self._prepare_headers()
|
||||
|
||||
|
||||
async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client:
|
||||
url = f"{self.base_url}/models/{model}:generateContent?key={api_key}"
|
||||
|
||||
|
||||
try:
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
|
||||
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
logger.error(f"API call failed - Status: {response.status_code}, Content: {error_content}")
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
|
||||
logger.error(
|
||||
f"API call failed - Status: {response.status_code}, Content: {error_content}"
|
||||
)
|
||||
raise Exception(response.status_code, error_content)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
|
||||
# 检查响应结构的基本信息
|
||||
if not response_data.get("candidates"):
|
||||
logger.warning("No candidates found in API response")
|
||||
|
||||
|
||||
return response_data
|
||||
|
||||
|
||||
except httpx.TimeoutException as e:
|
||||
logger.error(f"Request timeout: {e}")
|
||||
raise Exception(f"Request timeout: {e}")
|
||||
raise Exception(500, f"Request timeout: {e}")
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"Request error: {e}")
|
||||
raise Exception(f"Request error: {e}")
|
||||
raise Exception(500, f"Request error: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}")
|
||||
raise
|
||||
raise Exception(500, f"Unexpected error: {e}")
|
||||
|
||||
async def stream_generate_content(self, payload: Dict[str, Any], model: str, api_key: str) -> AsyncGenerator[str, None]:
|
||||
async def stream_generate_content(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> AsyncGenerator[str, None]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
model = self._get_real_model(model)
|
||||
|
||||
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -132,15 +145,19 @@ class GeminiApiClient(ApiClient):
|
||||
headers = self._prepare_headers()
|
||||
async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client:
|
||||
url = f"{self.base_url}/models/{model}:streamGenerateContent?alt=sse&key={api_key}"
|
||||
async with client.stream(method="POST", url=url, json=payload, headers=headers) as response:
|
||||
async with client.stream(
|
||||
method="POST", url=url, json=payload, headers=headers
|
||||
) as response:
|
||||
if response.status_code != 200:
|
||||
error_content = await response.aread()
|
||||
error_msg = error_content.decode("utf-8")
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_msg}")
|
||||
raise Exception(response.status_code, error_msg)
|
||||
async for line in response.aiter_lines():
|
||||
yield line
|
||||
|
||||
async def count_tokens(self, payload: Dict[str, Any], model: str, api_key: str) -> Dict[str, Any]:
|
||||
async def count_tokens(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
model = self._get_real_model(model)
|
||||
|
||||
@@ -158,14 +175,16 @@ class GeminiApiClient(ApiClient):
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
raise Exception(response.status_code, error_content)
|
||||
return response.json()
|
||||
|
||||
async def embed_content(self, payload: Dict[str, Any], model: str, api_key: str) -> Dict[str, Any]:
|
||||
async def embed_content(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
"""单一嵌入内容生成"""
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
model = self._get_real_model(model)
|
||||
|
||||
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -177,32 +196,36 @@ class GeminiApiClient(ApiClient):
|
||||
headers = self._prepare_headers()
|
||||
async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client:
|
||||
url = f"{self.base_url}/models/{model}:embedContent?key={api_key}"
|
||||
|
||||
|
||||
try:
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
|
||||
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
logger.error(f"Embedding API call failed - Status: {response.status_code}, Content: {error_content}")
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
|
||||
logger.error(
|
||||
f"Embedding API call failed - Status: {response.status_code}, Content: {error_content}"
|
||||
)
|
||||
raise Exception(response.status_code, error_content)
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
except httpx.TimeoutException as e:
|
||||
logger.error(f"Embedding request timeout: {e}")
|
||||
raise Exception(f"Request timeout: {e}")
|
||||
raise Exception(500, f"Request timeout: {e}")
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"Embedding request error: {e}")
|
||||
raise Exception(f"Request error: {e}")
|
||||
raise Exception(500, f"Request error: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected embedding error: {e}")
|
||||
raise
|
||||
raise Exception(500, f"Unexpected embedding error: {e}")
|
||||
|
||||
async def batch_embed_contents(self, payload: Dict[str, Any], model: str, api_key: str) -> Dict[str, Any]:
|
||||
async def batch_embed_contents(
|
||||
self, payload: Dict[str, Any], model: str, api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
"""批量嵌入内容生成"""
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
model = self._get_real_model(model)
|
||||
|
||||
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -214,26 +237,28 @@ class GeminiApiClient(ApiClient):
|
||||
headers = self._prepare_headers()
|
||||
async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client:
|
||||
url = f"{self.base_url}/models/{model}:batchEmbedContents?key={api_key}"
|
||||
|
||||
|
||||
try:
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
|
||||
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
logger.error(f"Batch embedding API call failed - Status: {response.status_code}, Content: {error_content}")
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
|
||||
logger.error(
|
||||
f"Batch embedding API call failed - Status: {response.status_code}, Content: {error_content}"
|
||||
)
|
||||
raise Exception(response.status_code, error_content)
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
except httpx.TimeoutException as e:
|
||||
logger.error(f"Batch embedding request timeout: {e}")
|
||||
raise Exception(f"Request timeout: {e}")
|
||||
raise Exception(500, f"Request timeout: {e}")
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"Batch embedding request error: {e}")
|
||||
raise Exception(f"Request error: {e}")
|
||||
raise Exception(500, f"Request error: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected batch embedding error: {e}")
|
||||
raise
|
||||
raise Exception(500, f"Unexpected batch embedding error: {e}")
|
||||
|
||||
|
||||
class OpenaiApiClient(ApiClient):
|
||||
@@ -242,7 +267,7 @@ class OpenaiApiClient(ApiClient):
|
||||
def __init__(self, base_url: str, timeout: int = DEFAULT_TIMEOUT):
|
||||
self.base_url = base_url
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
def _prepare_headers(self, api_key: str) -> Dict[str, str]:
|
||||
headers = {"Authorization": f"Bearer {api_key}"}
|
||||
if settings.CUSTOM_HEADERS:
|
||||
@@ -267,12 +292,16 @@ class OpenaiApiClient(ApiClient):
|
||||
response = await client.get(url, headers=headers)
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
raise Exception(response.status_code, error_content)
|
||||
return response.json()
|
||||
|
||||
async def generate_content(self, payload: Dict[str, Any], api_key: str) -> Dict[str, Any]:
|
||||
async def generate_content(
|
||||
self, payload: Dict[str, Any], api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
logger.info(f"settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: {settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY}")
|
||||
logger.info(
|
||||
f"settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: {settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY}"
|
||||
)
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -287,10 +316,12 @@ class OpenaiApiClient(ApiClient):
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
raise Exception(response.status_code, error_content)
|
||||
return response.json()
|
||||
|
||||
async def stream_generate_content(self, payload: Dict[str, Any], api_key: str) -> AsyncGenerator[str, None]:
|
||||
async def stream_generate_content(
|
||||
self, payload: Dict[str, Any], api_key: str
|
||||
) -> AsyncGenerator[str, None]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
@@ -303,17 +334,21 @@ class OpenaiApiClient(ApiClient):
|
||||
headers = self._prepare_headers(api_key)
|
||||
async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client:
|
||||
url = f"{self.base_url}/openai/chat/completions"
|
||||
async with client.stream(method="POST", url=url, json=payload, headers=headers) as response:
|
||||
async with client.stream(
|
||||
method="POST", url=url, json=payload, headers=headers
|
||||
) as response:
|
||||
if response.status_code != 200:
|
||||
error_content = await response.aread()
|
||||
error_msg = error_content.decode("utf-8")
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_msg}")
|
||||
raise Exception(response.status_code, error_msg)
|
||||
async for line in response.aiter_lines():
|
||||
yield line
|
||||
|
||||
async def create_embeddings(self, input: str, model: str, api_key: str) -> Dict[str, Any]:
|
||||
|
||||
async def create_embeddings(
|
||||
self, input: str, model: str, api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
|
||||
|
||||
proxy_to_use = None
|
||||
if settings.PROXIES:
|
||||
if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY:
|
||||
@@ -332,10 +367,12 @@ class OpenaiApiClient(ApiClient):
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
raise Exception(response.status_code, error_content)
|
||||
return response.json()
|
||||
|
||||
async def generate_images(self, payload: Dict[str, Any], api_key: str) -> Dict[str, Any]:
|
||||
|
||||
async def generate_images(
|
||||
self, payload: Dict[str, Any], api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
timeout = httpx.Timeout(self.timeout, read=self.timeout)
|
||||
|
||||
proxy_to_use = None
|
||||
@@ -352,5 +389,5 @@ class OpenaiApiClient(ApiClient):
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
error_content = response.text
|
||||
raise Exception(f"API call failed with status code {response.status_code}, {error_content}")
|
||||
return response.json()
|
||||
raise Exception(response.status_code, error_content)
|
||||
return response.json()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import datetime
|
||||
import re
|
||||
import time
|
||||
from typing import List, Union
|
||||
|
||||
@@ -56,13 +55,9 @@ class EmbeddingService:
|
||||
raise e
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
status_code = 500
|
||||
error_log_msg = f"Generic error: {e}"
|
||||
logger.error(f"Error creating embedding (Exception): {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", str(e))
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
raise e
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# app/service/embedding/gemini_embedding_service.py
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
@@ -69,13 +68,9 @@ class GeminiEmbeddingService:
|
||||
return response
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"Single embedding API call failed: {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
@@ -119,13 +114,9 @@ class GeminiEmbeddingService:
|
||||
return response
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"Batch embedding API call failed: {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from typing import Any, AsyncGenerator, Dict, Union
|
||||
|
||||
@@ -80,13 +78,9 @@ class OpenAICompatiableService:
|
||||
return response
|
||||
except Exception as e:
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.error(f"Normal API call failed with error: {error_log_msg}")
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=api_key,
|
||||
@@ -138,15 +132,11 @@ class OpenAICompatiableService:
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
is_success = False
|
||||
error_log_msg = str(e)
|
||||
status_code = e.args[0]
|
||||
error_log_msg = e.args[1]
|
||||
logger.warning(
|
||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
||||
)
|
||||
match = re.search(r"status code (\d+)", error_log_msg)
|
||||
if match:
|
||||
status_code = int(match.group(1))
|
||||
else:
|
||||
status_code = 500
|
||||
|
||||
await add_error_log(
|
||||
gemini_key=current_attempt_key,
|
||||
@@ -170,14 +160,14 @@ class OpenAICompatiableService:
|
||||
logger.error(
|
||||
f"No valid API key available after {retries} retries."
|
||||
)
|
||||
break
|
||||
raise
|
||||
else:
|
||||
logger.error("KeyManager not available for retry logic.")
|
||||
break
|
||||
|
||||
if retries >= max_retries:
|
||||
logger.error(f"Max retries ({max_retries}) reached for streaming.")
|
||||
break
|
||||
raise
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
latency_ms = int((end_time - start_time) * 1000)
|
||||
@@ -189,6 +179,3 @@ class OpenAICompatiableService:
|
||||
latency_ms=latency_ms,
|
||||
request_time=request_datetime,
|
||||
)
|
||||
if not is_success and retries >= max_retries:
|
||||
yield f"data: {json.dumps({'error': 'Streaming failed after retries'})}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
|
||||
Reference in New Issue
Block a user