mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-08 01:06:37 +08:00
feat(日志): 添加数据库日志记录并增强API重试/错误处理
- 为 Gemini 聊天(流式/非流式)、OpenAI 图像聊天(流式/非流式)和 embedding 服务的 API 调用实现全面的数据库日志记录。日志包括请求详情、成功/失败状态、状态码、延迟和错误消息。 - 重构 Gemini 流式聊天服务 (`stream_generate_content`) 以整合使用 `KeyManager` 的重试逻辑,与非流式实现保持一致,包括失败时的 API 密钥切换。 - 增强重试处理器 (`RetryHandler`) 的日志记录,以提高密钥切换和失败场景下的清晰度。 - 确保 `api_key` 正确传递给 OpenAI 图像聊天完成。 - 改进 embedding 服务中的错误处理,区分 `APIStatusError` 和通用异常,并将错误记录到数据库。 - 为 embedding 服务日志添加请求负载截断。 - 修复 Gemini `_build_payload` 中使用正确的 `model` 变量获取 `THINKING_BUDGET_MAP` 的错误。 - 移除 `ImageCreateService` 中未使用的 `paid_key` 类变量。
This commit is contained in:
@@ -23,21 +23,26 @@ class RetryHandler:
|
|||||||
last_exception = None
|
last_exception = None
|
||||||
|
|
||||||
for attempt in range(self.max_retries):
|
for attempt in range(self.max_retries):
|
||||||
|
retries = attempt + 1
|
||||||
try:
|
try:
|
||||||
return await func(*args, **kwargs)
|
return await func(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_exception = e
|
last_exception = e
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"API call failed with error: {str(e)}. Attempt {attempt + 1} of {self.max_retries}"
|
f"API call failed with error: {str(e)}. Attempt {retries} of {self.max_retries}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 从函数参数中获取 key_manager
|
# 从函数参数中获取 key_manager
|
||||||
key_manager = kwargs.get("key_manager")
|
key_manager = kwargs.get("key_manager")
|
||||||
if key_manager:
|
if key_manager:
|
||||||
old_key = kwargs.get(self.key_arg)
|
old_key = kwargs.get(self.key_arg)
|
||||||
new_key = await key_manager.handle_api_failure(old_key, attempt)
|
new_key = await key_manager.handle_api_failure(old_key, retries)
|
||||||
kwargs[self.key_arg] = new_key
|
if new_key:
|
||||||
logger.info(f"Switched to new API key: {new_key}")
|
kwargs[self.key_arg] = new_key
|
||||||
|
logger.info(f"Switched to new API key: {new_key}")
|
||||||
|
else:
|
||||||
|
logger.error(f"No valid API key available after {retries} retries.")
|
||||||
|
break
|
||||||
|
|
||||||
logger.error(
|
logger.error(
|
||||||
f"All retry attempts failed, raising final exception: {str(last_exception)}"
|
f"All retry attempts failed, raising final exception: {str(last_exception)}"
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ async def generate_content(
|
|||||||
request: GeminiRequest,
|
request: GeminiRequest,
|
||||||
_=Depends(security_service.verify_key_or_goog_api_key),
|
_=Depends(security_service.verify_key_or_goog_api_key),
|
||||||
api_key: str = Depends(get_next_working_key),
|
api_key: str = Depends(get_next_working_key),
|
||||||
|
key_manager: KeyManager = Depends(get_key_manager),
|
||||||
chat_service: GeminiChatService = Depends(get_chat_service)
|
chat_service: GeminiChatService = Depends(get_chat_service)
|
||||||
):
|
):
|
||||||
"""非流式生成内容"""
|
"""非流式生成内容"""
|
||||||
@@ -140,6 +141,7 @@ async def stream_generate_content(
|
|||||||
request: GeminiRequest,
|
request: GeminiRequest,
|
||||||
_=Depends(security_service.verify_key_or_goog_api_key),
|
_=Depends(security_service.verify_key_or_goog_api_key),
|
||||||
api_key: str = Depends(get_next_working_key),
|
api_key: str = Depends(get_next_working_key),
|
||||||
|
key_manager: KeyManager = Depends(get_key_manager),
|
||||||
chat_service: GeminiChatService = Depends(get_chat_service)
|
chat_service: GeminiChatService = Depends(get_chat_service)
|
||||||
):
|
):
|
||||||
"""流式生成内容"""
|
"""流式生成内容"""
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async def chat_completion(
|
|||||||
try:
|
try:
|
||||||
# 如果model是imagen3,使用paid_key
|
# 如果model是imagen3,使用paid_key
|
||||||
if request.model == f"{settings.CREATE_IMAGE_MODEL}-chat":
|
if request.model == f"{settings.CREATE_IMAGE_MODEL}-chat":
|
||||||
response = await chat_service.create_image_chat_completion(request=request)
|
response = await chat_service.create_image_chat_completion(request, api_key)
|
||||||
else:
|
else:
|
||||||
response = await chat_service.create_chat_completion(request, api_key)
|
response = await chat_service.create_chat_completion(request, api_key)
|
||||||
# 处理流式响应
|
# 处理流式响应
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
|||||||
if model.endswith("-non-thinking"):
|
if model.endswith("-non-thinking"):
|
||||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||||
if model in settings.THINKING_BUDGET_MAP:
|
if model in settings.THINKING_BUDGET_MAP:
|
||||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": settings.THINKING_BUDGET_MAP.get(request.model,1000)}
|
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": settings.THINKING_BUDGET_MAP.get(model,1000)}
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@@ -162,10 +162,6 @@ class GeminiChatService:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self.api_client.generate_content(payload, model, api_key)
|
response = await self.api_client.generate_content(payload, model, api_key)
|
||||||
# Assuming success if no exception is raised and response is received
|
|
||||||
# The actual status code might be within the response structure or headers,
|
|
||||||
# but api_client doesn't seem to expose it directly here.
|
|
||||||
# We'll assume 200 for success if no exception.
|
|
||||||
is_success = True
|
is_success = True
|
||||||
status_code = 200 # Assume 200 on success
|
status_code = 200 # Assume 200 on success
|
||||||
return self.response_handler.handle_response(response, model, stream=False)
|
return self.response_handler.handle_response(response, model, stream=False)
|
||||||
@@ -184,7 +180,7 @@ class GeminiChatService:
|
|||||||
await add_error_log(
|
await add_error_log(
|
||||||
gemini_key=api_key,
|
gemini_key=api_key,
|
||||||
model_name=model,
|
model_name=model,
|
||||||
error_type="gemini_chat_service",
|
error_type="gemini-chat-non-stream",
|
||||||
error_log=error_log_msg,
|
error_log=error_log_msg,
|
||||||
error_code=status_code,
|
error_code=status_code,
|
||||||
request_msg=payload
|
request_msg=payload
|
||||||
@@ -210,96 +206,90 @@ class GeminiChatService:
|
|||||||
retries = 0
|
retries = 0
|
||||||
max_retries = settings.MAX_RETRIES
|
max_retries = settings.MAX_RETRIES
|
||||||
payload = _build_payload(model, request)
|
payload = _build_payload(model, request)
|
||||||
start_time = time.perf_counter() # Record start time before loop
|
|
||||||
request_datetime = datetime.datetime.now()
|
|
||||||
is_success = False
|
is_success = False
|
||||||
status_code = None
|
status_code = None
|
||||||
final_api_key = api_key # Store the initial key
|
final_api_key = api_key
|
||||||
|
|
||||||
try:
|
while retries < max_retries:
|
||||||
while retries < max_retries:
|
request_datetime = datetime.datetime.now()
|
||||||
current_attempt_key = api_key # Key used for this attempt
|
start_time = time.perf_counter()
|
||||||
final_api_key = current_attempt_key # Update final key used
|
current_attempt_key = api_key
|
||||||
try:
|
final_api_key = current_attempt_key # Update final key used
|
||||||
async for line in self.api_client.stream_generate_content(
|
try:
|
||||||
payload, model, current_attempt_key
|
async for line in self.api_client.stream_generate_content(
|
||||||
):
|
payload, model, current_attempt_key
|
||||||
# print(line)
|
):
|
||||||
if line.startswith("data:"):
|
# print(line)
|
||||||
line = line[6:]
|
if line.startswith("data:"):
|
||||||
response_data = self.response_handler.handle_response(
|
line = line[6:]
|
||||||
json.loads(line), model, stream=True
|
response_data = self.response_handler.handle_response(
|
||||||
)
|
json.loads(line), model, stream=True
|
||||||
text = self._extract_text_from_response(response_data)
|
|
||||||
# 如果有文本内容,且开启了流式输出优化器,则使用流式输出优化器处理
|
|
||||||
if text and settings.STREAM_OPTIMIZER_ENABLED:
|
|
||||||
# 使用流式输出优化器处理文本输出
|
|
||||||
async for (
|
|
||||||
optimized_chunk
|
|
||||||
) in gemini_optimizer.optimize_stream_output(
|
|
||||||
text,
|
|
||||||
lambda t: self._create_char_response(response_data, t),
|
|
||||||
lambda c: "data: " + json.dumps(c) + "\n\n",
|
|
||||||
):
|
|
||||||
yield optimized_chunk
|
|
||||||
else:
|
|
||||||
# 如果没有文本内容(如工具调用等),整块输出
|
|
||||||
yield "data: " + json.dumps(response_data) + "\n\n"
|
|
||||||
logger.info("Streaming completed successfully")
|
|
||||||
is_success = True
|
|
||||||
status_code = 200 # Assume 200 on success
|
|
||||||
break # Exit loop on success
|
|
||||||
except Exception as e:
|
|
||||||
retries += 1
|
|
||||||
is_success = False # Mark as failed for this attempt
|
|
||||||
error_log_msg = str(e)
|
|
||||||
logger.warning(
|
|
||||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
|
||||||
)
|
|
||||||
# Parse error code for logging
|
|
||||||
match = re.search(r"status code (\d+)", error_log_msg)
|
|
||||||
if match:
|
|
||||||
status_code = int(match.group(1))
|
|
||||||
else:
|
|
||||||
status_code = 500 # Default if parsing fails
|
|
||||||
|
|
||||||
# Log error to error log table
|
|
||||||
await add_error_log(
|
|
||||||
gemini_key=current_attempt_key, # Log key used for this failed attempt
|
|
||||||
model_name=model,
|
|
||||||
error_log=error_log_msg,
|
|
||||||
error_code=status_code,
|
|
||||||
request_msg=payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attempt to switch API Key
|
|
||||||
api_key = await self.key_manager.handle_api_failure(current_attempt_key, retries)
|
|
||||||
if api_key:
|
|
||||||
logger.info(f"Switched to new API key: {api_key}")
|
|
||||||
else: # No more keys or retries exceeded by handle_api_failure logic
|
|
||||||
logger.error(f"No valid API key available after {retries} retries.")
|
|
||||||
break # Exit loop if no key available
|
|
||||||
|
|
||||||
if retries >= max_retries:
|
|
||||||
logger.error(
|
|
||||||
f"Max retries ({max_retries}) reached for streaming."
|
|
||||||
)
|
)
|
||||||
break # Exit loop after max retries
|
text = self._extract_text_from_response(response_data)
|
||||||
finally:
|
# 如果有文本内容,且开启了流式输出优化器,则使用流式输出优化器处理
|
||||||
# Log the final outcome of the streaming request
|
if text and settings.STREAM_OPTIMIZER_ENABLED:
|
||||||
end_time = time.perf_counter()
|
# 使用流式输出优化器处理文本输出
|
||||||
latency_ms = int((end_time - start_time) * 1000)
|
async for (
|
||||||
await add_request_log(
|
optimized_chunk
|
||||||
model_name=model,
|
) in gemini_optimizer.optimize_stream_output(
|
||||||
api_key=final_api_key, # Log the last key used
|
text,
|
||||||
is_success=is_success, # Log the final success status
|
lambda t: self._create_char_response(response_data, t),
|
||||||
status_code=status_code, # Log the last known status code
|
lambda c: "data: " + json.dumps(c) + "\n\n",
|
||||||
latency_ms=latency_ms, # Log total time including retries
|
):
|
||||||
request_time=request_datetime
|
yield optimized_chunk
|
||||||
)
|
else:
|
||||||
# If the loop finished due to failure, ensure an exception is raised if not already handled
|
# 如果没有文本内容(如工具调用等),整块输出
|
||||||
if not is_success and retries >= max_retries:
|
yield "data: " + json.dumps(response_data) + "\n\n"
|
||||||
# We need to raise an exception here if the loop exited due to max retries failure
|
logger.info("Streaming completed successfully")
|
||||||
# However, the original code structure doesn't explicitly raise here after the loop.
|
is_success = True
|
||||||
# For now, we just log. Consider raising HTTPException if needed.
|
status_code = 200
|
||||||
pass
|
break
|
||||||
|
except Exception as e:
|
||||||
|
retries += 1
|
||||||
|
is_success = False
|
||||||
|
error_log_msg = str(e)
|
||||||
|
logger.warning(
|
||||||
|
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
||||||
|
)
|
||||||
|
# Parse error code for logging
|
||||||
|
match = re.search(r"status code (\d+)", error_log_msg)
|
||||||
|
if match:
|
||||||
|
status_code = int(match.group(1))
|
||||||
|
else:
|
||||||
|
status_code = 500
|
||||||
|
|
||||||
|
# Log error to error log table
|
||||||
|
await add_error_log(
|
||||||
|
gemini_key=current_attempt_key, # Log key used for this failed attempt
|
||||||
|
model_name=model,
|
||||||
|
error_type="gemini-chat-stream",
|
||||||
|
error_log=error_log_msg,
|
||||||
|
error_code=status_code,
|
||||||
|
request_msg=payload
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attempt to switch API Key
|
||||||
|
api_key = await self.key_manager.handle_api_failure(current_attempt_key, retries)
|
||||||
|
if api_key:
|
||||||
|
logger.info(f"Switched to new API key: {api_key}")
|
||||||
|
else: # No more keys or retries exceeded by handle_api_failure logic
|
||||||
|
logger.error(f"No valid API key available after {retries} retries.")
|
||||||
|
break # Exit loop if no key available
|
||||||
|
|
||||||
|
if retries >= max_retries:
|
||||||
|
logger.error(
|
||||||
|
f"Max retries ({max_retries}) reached for streaming."
|
||||||
|
)
|
||||||
|
break # Exit loop after max retries
|
||||||
|
finally:
|
||||||
|
# Log the final outcome of the streaming request
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
latency_ms = int((end_time - start_time) * 1000)
|
||||||
|
await add_request_log(
|
||||||
|
model_name=model,
|
||||||
|
api_key=final_api_key, # Log the last key used
|
||||||
|
is_success=is_success, # Log the final success status
|
||||||
|
status_code=status_code, # Log the last known status code
|
||||||
|
latency_ms=latency_ms, # Log total time including retries
|
||||||
|
request_time=request_datetime
|
||||||
|
)
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ class OpenAIChatService:
|
|||||||
await add_error_log(
|
await add_error_log(
|
||||||
gemini_key=api_key, # Note: Parameter name is gemini_key in add_error_log
|
gemini_key=api_key, # Note: Parameter name is gemini_key in add_error_log
|
||||||
model_name=model,
|
model_name=model,
|
||||||
error_type="openai_chat_service", # Indicate service type
|
error_type="openai-chat-non-stream",
|
||||||
error_log=error_log_msg,
|
error_log=error_log_msg,
|
||||||
error_code=status_code,
|
error_code=status_code,
|
||||||
request_msg=payload
|
request_msg=payload
|
||||||
@@ -247,118 +247,117 @@ class OpenAIChatService:
|
|||||||
"""处理流式聊天完成,添加重试逻辑"""
|
"""处理流式聊天完成,添加重试逻辑"""
|
||||||
retries = 0
|
retries = 0
|
||||||
max_retries = settings.MAX_RETRIES
|
max_retries = settings.MAX_RETRIES
|
||||||
start_time = time.perf_counter() # Record start time before loop
|
|
||||||
request_datetime = datetime.datetime.now()
|
|
||||||
is_success = False
|
is_success = False
|
||||||
status_code = None
|
status_code = None
|
||||||
final_api_key = api_key # Store the initial key
|
final_api_key = api_key
|
||||||
|
|
||||||
try:
|
while retries < max_retries:
|
||||||
while retries < max_retries:
|
start_time = time.perf_counter()
|
||||||
current_attempt_key = api_key # Key used for this attempt
|
request_datetime = datetime.datetime.now()
|
||||||
final_api_key = current_attempt_key # Update final key used
|
current_attempt_key = api_key
|
||||||
try:
|
final_api_key = current_attempt_key
|
||||||
tool_call_flag = False
|
try:
|
||||||
async for line in self.api_client.stream_generate_content(
|
tool_call_flag = False
|
||||||
payload, model, current_attempt_key
|
async for line in self.api_client.stream_generate_content(
|
||||||
):
|
payload, model, current_attempt_key
|
||||||
print(line)
|
):
|
||||||
if line.startswith("data:"):
|
if line.startswith("data:"):
|
||||||
chunk = json.loads(line[6:])
|
chunk = json.loads(line[6:])
|
||||||
openai_chunk = self.response_handler.handle_response(
|
openai_chunk = self.response_handler.handle_response(
|
||||||
chunk, model, stream=True, finish_reason=None
|
chunk, model, stream=True, finish_reason=None
|
||||||
)
|
|
||||||
if openai_chunk:
|
|
||||||
# 提取文本内容
|
|
||||||
text = self._extract_text_from_openai_chunk(openai_chunk)
|
|
||||||
if text and settings.STREAM_OPTIMIZER_ENABLED:
|
|
||||||
# 使用流式输出优化器处理文本输出
|
|
||||||
async for (
|
|
||||||
optimized_chunk
|
|
||||||
) in openai_optimizer.optimize_stream_output(
|
|
||||||
text,
|
|
||||||
lambda t: self._create_char_openai_chunk(
|
|
||||||
openai_chunk, t
|
|
||||||
),
|
|
||||||
lambda c: f"data: {json.dumps(c)}\n\n",
|
|
||||||
):
|
|
||||||
yield optimized_chunk
|
|
||||||
else:
|
|
||||||
# 如果没有文本内容(如工具调用等),整块输出
|
|
||||||
if "tool_calls" in json.dumps(openai_chunk):
|
|
||||||
tool_call_flag = True
|
|
||||||
yield f"data: {json.dumps(openai_chunk)}\n\n"
|
|
||||||
if tool_call_flag:
|
|
||||||
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='tool_calls'))}\n\n"
|
|
||||||
else:
|
|
||||||
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='stop'))}\n\n"
|
|
||||||
yield "data: [DONE]\n\n"
|
|
||||||
logger.info("Streaming completed successfully")
|
|
||||||
is_success = True
|
|
||||||
status_code = 200 # Assume 200 on success
|
|
||||||
break # 成功后退出循环
|
|
||||||
except Exception as e:
|
|
||||||
retries += 1
|
|
||||||
is_success = False # Mark as failed for this attempt
|
|
||||||
error_log_msg = str(e)
|
|
||||||
logger.warning(
|
|
||||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
|
||||||
)
|
|
||||||
# Parse error code for logging
|
|
||||||
match = re.search(r"status code (\d+)", error_log_msg)
|
|
||||||
if match:
|
|
||||||
status_code = int(match.group(1))
|
|
||||||
else:
|
|
||||||
status_code = 500 # Default if parsing fails
|
|
||||||
|
|
||||||
# Log error to error log table
|
|
||||||
await add_error_log(
|
|
||||||
gemini_key=current_attempt_key, # Note: Parameter name is gemini_key
|
|
||||||
model_name=model,
|
|
||||||
error_type="openai_chat_service", # Indicate service type
|
|
||||||
error_log=error_log_msg,
|
|
||||||
error_code=status_code,
|
|
||||||
request_msg=payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attempt to switch API Key
|
|
||||||
# Ensure key_manager is available (might need adjustment if not always passed)
|
|
||||||
if self.key_manager:
|
|
||||||
api_key = await self.key_manager.handle_api_failure(current_attempt_key, retries)
|
|
||||||
if api_key:
|
|
||||||
logger.info(f"Switched to new API key: {api_key}")
|
|
||||||
else:
|
|
||||||
logger.error(f"No valid API key available after {retries} retries.")
|
|
||||||
break # Exit loop if no key available
|
|
||||||
else:
|
|
||||||
logger.error("KeyManager not available for retry logic.")
|
|
||||||
break # Exit loop if key manager is missing
|
|
||||||
|
|
||||||
if retries >= max_retries:
|
|
||||||
logger.error(
|
|
||||||
f"Max retries ({max_retries}) reached for streaming."
|
|
||||||
)
|
)
|
||||||
break # Exit loop after max retries
|
if openai_chunk:
|
||||||
finally:
|
# 提取文本内容
|
||||||
# Log the final outcome of the streaming request
|
text = self._extract_text_from_openai_chunk(openai_chunk)
|
||||||
end_time = time.perf_counter()
|
if text and settings.STREAM_OPTIMIZER_ENABLED:
|
||||||
latency_ms = int((end_time - start_time) * 1000)
|
# 使用流式输出优化器处理文本输出
|
||||||
await add_request_log(
|
async for (
|
||||||
model_name=model,
|
optimized_chunk
|
||||||
api_key=final_api_key, # Log the last key used
|
) in openai_optimizer.optimize_stream_output(
|
||||||
is_success=is_success, # Log the final success status
|
text,
|
||||||
status_code=status_code, # Log the last known status code
|
lambda t: self._create_char_openai_chunk(
|
||||||
latency_ms=latency_ms, # Log total time including retries
|
openai_chunk, t
|
||||||
request_time=request_datetime
|
),
|
||||||
)
|
lambda c: f"data: {json.dumps(c)}\n\n",
|
||||||
# If the loop finished due to failure, yield error and DONE
|
):
|
||||||
if not is_success and retries >= max_retries:
|
yield optimized_chunk
|
||||||
yield f"data: {json.dumps({'error': 'Streaming failed after retries'})}\n\n"
|
else:
|
||||||
yield "data: [DONE]\n\n"
|
# 如果没有文本内容(如工具调用等),整块输出
|
||||||
|
if "tool_calls" in json.dumps(openai_chunk):
|
||||||
|
tool_call_flag = True
|
||||||
|
yield f"data: {json.dumps(openai_chunk)}\n\n"
|
||||||
|
if tool_call_flag:
|
||||||
|
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='tool_calls'))}\n\n"
|
||||||
|
else:
|
||||||
|
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='stop'))}\n\n"
|
||||||
|
yield "data: [DONE]\n\n"
|
||||||
|
logger.info("Streaming completed successfully")
|
||||||
|
is_success = True
|
||||||
|
status_code = 200 # Assume 200 on success
|
||||||
|
break # 成功后退出循环
|
||||||
|
except Exception as e:
|
||||||
|
retries += 1
|
||||||
|
is_success = False
|
||||||
|
error_log_msg = str(e)
|
||||||
|
logger.warning(
|
||||||
|
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
|
||||||
|
)
|
||||||
|
# Parse error code for logging
|
||||||
|
match = re.search(r"status code (\d+)", error_log_msg)
|
||||||
|
if match:
|
||||||
|
status_code = int(match.group(1))
|
||||||
|
else:
|
||||||
|
status_code = 500 # Default if parsing fails
|
||||||
|
|
||||||
|
# Log error to error log table
|
||||||
|
await add_error_log(
|
||||||
|
gemini_key=current_attempt_key,
|
||||||
|
model_name=model,
|
||||||
|
error_type="openai-chat-stream",
|
||||||
|
error_log=error_log_msg,
|
||||||
|
error_code=status_code,
|
||||||
|
request_msg=payload
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attempt to switch API Key
|
||||||
|
# Ensure key_manager is available (might need adjustment if not always passed)
|
||||||
|
if self.key_manager:
|
||||||
|
api_key = await self.key_manager.handle_api_failure(current_attempt_key, retries)
|
||||||
|
if api_key:
|
||||||
|
logger.info(f"Switched to new API key: {api_key}")
|
||||||
|
else:
|
||||||
|
logger.error(f"No valid API key available after {retries} retries.")
|
||||||
|
break # Exit loop if no key available
|
||||||
|
else:
|
||||||
|
logger.error("KeyManager not available for retry logic.")
|
||||||
|
break # Exit loop if key manager is missing
|
||||||
|
|
||||||
|
if retries >= max_retries:
|
||||||
|
logger.error(
|
||||||
|
f"Max retries ({max_retries}) reached for streaming."
|
||||||
|
)
|
||||||
|
break # Exit loop after max retries
|
||||||
|
finally:
|
||||||
|
# Log the final outcome of the streaming request
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
latency_ms = int((end_time - start_time) * 1000)
|
||||||
|
await add_request_log(
|
||||||
|
model_name=model,
|
||||||
|
api_key=final_api_key, # Log the last key used
|
||||||
|
is_success=is_success, # Log the final success status
|
||||||
|
status_code=status_code, # Log the last known status code
|
||||||
|
latency_ms=latency_ms, # Log total time including retries
|
||||||
|
request_time=request_datetime
|
||||||
|
)
|
||||||
|
# If the loop finished due to failure, yield error and DONE
|
||||||
|
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"
|
||||||
|
|
||||||
async def create_image_chat_completion(
|
async def create_image_chat_completion(
|
||||||
self,
|
self,
|
||||||
request: ChatRequest,
|
request: ChatRequest,
|
||||||
|
api_key: str
|
||||||
) -> Union[Dict[str, Any], AsyncGenerator[str, None]]:
|
) -> Union[Dict[str, Any], AsyncGenerator[str, None]]:
|
||||||
|
|
||||||
image_generate_request = ImageGenerationRequest()
|
image_generate_request = ImageGenerationRequest()
|
||||||
@@ -368,41 +367,120 @@ class OpenAIChatService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if request.stream:
|
if request.stream:
|
||||||
return self._handle_stream_image_completion(request.model, image_res)
|
return self._handle_stream_image_completion(request.model, image_res, api_key)
|
||||||
else:
|
else:
|
||||||
return self._handle_normal_image_completion(request.model, image_res)
|
return await self._handle_normal_image_completion(request.model, image_res, api_key)
|
||||||
|
|
||||||
async def _handle_stream_image_completion(
|
async def _handle_stream_image_completion(
|
||||||
self, model: str, image_data: str
|
self, model: str, image_data: str, api_key:str
|
||||||
) -> AsyncGenerator[str, None]:
|
) -> AsyncGenerator[str, None]:
|
||||||
if image_data:
|
logger.info(f"Starting stream image completion for model: {model}")
|
||||||
openai_chunk = self.response_handler.handle_image_chat_response(
|
start_time = time.perf_counter()
|
||||||
image_data, model, stream=True, finish_reason=None
|
request_datetime = datetime.datetime.now() # Although not used for DB log here
|
||||||
|
is_success = False
|
||||||
|
status_code = None # Although not used for DB log here
|
||||||
|
|
||||||
|
try:
|
||||||
|
if image_data:
|
||||||
|
openai_chunk = self.response_handler.handle_image_chat_response(
|
||||||
|
image_data, model, stream=True, finish_reason=None
|
||||||
|
)
|
||||||
|
if openai_chunk:
|
||||||
|
# 提取文本内容
|
||||||
|
text = self._extract_text_from_openai_chunk(openai_chunk)
|
||||||
|
if text:
|
||||||
|
# 使用流式输出优化器处理文本输出
|
||||||
|
async for (
|
||||||
|
optimized_chunk
|
||||||
|
) in openai_optimizer.optimize_stream_output(
|
||||||
|
text,
|
||||||
|
lambda t: self._create_char_openai_chunk(openai_chunk, t),
|
||||||
|
lambda c: f"data: {json.dumps(c)}\n\n",
|
||||||
|
):
|
||||||
|
yield optimized_chunk
|
||||||
|
else:
|
||||||
|
# 如果没有文本内容(如图片URL等),整块输出
|
||||||
|
yield f"data: {json.dumps(openai_chunk)}\n\n"
|
||||||
|
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='stop'))}\n\n"
|
||||||
|
logger.info(f"Stream image completion finished successfully for model: {model}")
|
||||||
|
is_success = True
|
||||||
|
status_code = 200
|
||||||
|
yield "data: [DONE]\n\n"
|
||||||
|
except Exception as e:
|
||||||
|
is_success = False
|
||||||
|
error_log_msg = f"Stream image completion failed for model {model}: {e}"
|
||||||
|
logger.error(error_log_msg)
|
||||||
|
status_code = 500 # Default error code
|
||||||
|
# Call add_error_log using the passed api_key
|
||||||
|
await add_error_log(
|
||||||
|
gemini_key=api_key,
|
||||||
|
model_name=model,
|
||||||
|
error_type="openai-image-stream", # Specific error type
|
||||||
|
error_log=error_log_msg,
|
||||||
|
error_code=status_code,
|
||||||
|
request_msg={"image_data_truncated": image_data[:1000]} # Log truncated data
|
||||||
|
)
|
||||||
|
yield f"data: {json.dumps({'error': error_log_msg})}\n\n" # Send error to client
|
||||||
|
yield "data: [DONE]\n\n" # Still need DONE message
|
||||||
|
# Re-raising might break the stream, decide if needed
|
||||||
|
finally:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
latency_ms = int((end_time - start_time) * 1000)
|
||||||
|
logger.info(f"Stream image completion for model {model} took {latency_ms} ms. Success: {is_success}")
|
||||||
|
# Call add_request_log using the passed api_key
|
||||||
|
await add_request_log(
|
||||||
|
model_name=model,
|
||||||
|
api_key=api_key,
|
||||||
|
is_success=is_success,
|
||||||
|
status_code=status_code,
|
||||||
|
latency_ms=latency_ms,
|
||||||
|
request_time=request_datetime
|
||||||
)
|
)
|
||||||
if openai_chunk:
|
|
||||||
# 提取文本内容
|
|
||||||
text = self._extract_text_from_openai_chunk(openai_chunk)
|
|
||||||
if text:
|
|
||||||
# 使用流式输出优化器处理文本输出
|
|
||||||
async for (
|
|
||||||
optimized_chunk
|
|
||||||
) in openai_optimizer.optimize_stream_output(
|
|
||||||
text,
|
|
||||||
lambda t: self._create_char_openai_chunk(openai_chunk, t),
|
|
||||||
lambda c: f"data: {json.dumps(c)}\n\n",
|
|
||||||
):
|
|
||||||
yield optimized_chunk
|
|
||||||
else:
|
|
||||||
# 如果没有文本内容(如图片URL等),整块输出
|
|
||||||
yield f"data: {json.dumps(openai_chunk)}\n\n"
|
|
||||||
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='stop'))}\n\n"
|
|
||||||
yield "data: [DONE]\n\n"
|
|
||||||
logger.info("Image chat streaming completed successfully")
|
|
||||||
|
|
||||||
def _handle_normal_image_completion(
|
async def _handle_normal_image_completion(
|
||||||
self, model: str, image_data: str
|
self, model: str, image_data: str, api_key: str # Add api_key parameter
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
|
logger.info(f"Starting normal image completion for model: {model}")
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
request_datetime = datetime.datetime.now() # Although not used for DB log here
|
||||||
|
is_success = False
|
||||||
|
status_code = None # Although not used for DB log here
|
||||||
|
result = None
|
||||||
|
|
||||||
return self.response_handler.handle_image_chat_response(
|
try:
|
||||||
image_data, model, stream=False, finish_reason="stop"
|
result = self.response_handler.handle_image_chat_response(
|
||||||
)
|
image_data, model, stream=False, finish_reason="stop"
|
||||||
|
)
|
||||||
|
logger.info(f"Normal image completion finished successfully for model: {model}")
|
||||||
|
is_success = True
|
||||||
|
status_code = 200
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
is_success = False
|
||||||
|
error_log_msg = f"Normal image completion failed for model {model}: {e}"
|
||||||
|
logger.error(error_log_msg)
|
||||||
|
status_code = 500 # Default error code
|
||||||
|
# Call add_error_log using the passed api_key
|
||||||
|
await add_error_log(
|
||||||
|
gemini_key=api_key,
|
||||||
|
model_name=model,
|
||||||
|
error_type="openai-image-non-stream", # Specific error type
|
||||||
|
error_log=error_log_msg,
|
||||||
|
error_code=status_code,
|
||||||
|
request_msg={"image_data_truncated": image_data[:1000]} # Log truncated data
|
||||||
|
)
|
||||||
|
# Re-raise the exception so the caller knows about the failure
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
latency_ms = int((end_time - start_time) * 1000)
|
||||||
|
logger.info(f"Normal image completion for model {model} took {latency_ms} ms. Success: {is_success}")
|
||||||
|
# Call add_request_log using the passed api_key
|
||||||
|
await add_request_log(
|
||||||
|
model_name=model,
|
||||||
|
api_key=api_key,
|
||||||
|
is_success=is_success,
|
||||||
|
status_code=status_code,
|
||||||
|
latency_ms=latency_ms,
|
||||||
|
request_time=request_datetime
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
import re # For potential status code parsing from generic errors
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
import openai
|
import openai
|
||||||
|
from openai import APIStatusError # Import specific error type
|
||||||
from openai.types import CreateEmbeddingResponse
|
from openai.types import CreateEmbeddingResponse
|
||||||
|
|
||||||
from app.config.config import settings
|
from app.config.config import settings
|
||||||
from app.log.logger import get_embeddings_logger
|
from app.log.logger import get_embeddings_logger
|
||||||
|
from app.database.services import add_error_log, add_request_log # Import DB logging functions
|
||||||
|
|
||||||
logger = get_embeddings_logger()
|
logger = get_embeddings_logger()
|
||||||
|
|
||||||
@@ -13,11 +19,64 @@ class EmbeddingService:
|
|||||||
async def create_embedding(
|
async def create_embedding(
|
||||||
self, input_text: Union[str, List[str]], model: str, api_key: str
|
self, input_text: Union[str, List[str]], model: str, api_key: str
|
||||||
) -> CreateEmbeddingResponse:
|
) -> CreateEmbeddingResponse:
|
||||||
"""Create embeddings using OpenAI API"""
|
"""Create embeddings using OpenAI API with database logging"""
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
request_datetime = datetime.datetime.now()
|
||||||
|
is_success = False
|
||||||
|
status_code = None
|
||||||
|
response = None
|
||||||
|
error_log_msg = ""
|
||||||
|
# Prepare request message for logging (truncate if list or long string)
|
||||||
|
if isinstance(input_text, list):
|
||||||
|
request_msg_log = {"input_truncated": [str(item)[:100] + "..." if len(str(item)) > 100 else str(item) for item in input_text[:5]]}
|
||||||
|
if len(input_text) > 5:
|
||||||
|
request_msg_log["input_truncated"].append("...")
|
||||||
|
else:
|
||||||
|
request_msg_log = {"input_truncated": input_text[:1000] + "..." if len(input_text) > 1000 else input_text}
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = openai.OpenAI(api_key=api_key, base_url=settings.BASE_URL)
|
client = openai.OpenAI(api_key=api_key, base_url=settings.BASE_URL)
|
||||||
response = client.embeddings.create(input=input_text, model=model)
|
response = client.embeddings.create(input=input_text, model=model)
|
||||||
|
is_success = True
|
||||||
|
status_code = 200 # Assume 200 OK on success
|
||||||
return response
|
return response
|
||||||
|
except APIStatusError as e:
|
||||||
|
is_success = False
|
||||||
|
status_code = e.status_code
|
||||||
|
error_log_msg = f"OpenAI API error: {e}"
|
||||||
|
logger.error(f"Error creating embedding (APIStatusError): {error_log_msg}")
|
||||||
|
raise e # Re-raise the specific error
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating embedding: {str(e)}")
|
is_success = False
|
||||||
raise
|
error_log_msg = f"Generic error: {e}"
|
||||||
|
logger.error(f"Error creating embedding (Exception): {error_log_msg}")
|
||||||
|
# Try to parse status code from generic error (less reliable)
|
||||||
|
match = re.search(r"status code (\d+)", str(e))
|
||||||
|
if match:
|
||||||
|
status_code = int(match.group(1))
|
||||||
|
else:
|
||||||
|
status_code = 500 # Default if parsing fails
|
||||||
|
raise e # Re-raise the generic error
|
||||||
|
finally:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
latency_ms = int((end_time - start_time) * 1000)
|
||||||
|
if not is_success:
|
||||||
|
# Log error to database if it failed
|
||||||
|
await add_error_log(
|
||||||
|
gemini_key=api_key, # Using gemini_key parameter name for consistency
|
||||||
|
model_name=model,
|
||||||
|
error_type="openai-embedding",
|
||||||
|
error_log=error_log_msg,
|
||||||
|
error_code=status_code,
|
||||||
|
request_msg=request_msg_log
|
||||||
|
)
|
||||||
|
# Log request outcome to database regardless of success/failure
|
||||||
|
await add_request_log(
|
||||||
|
model_name=model,
|
||||||
|
api_key=api_key,
|
||||||
|
is_success=is_success,
|
||||||
|
status_code=status_code,
|
||||||
|
latency_ms=latency_ms,
|
||||||
|
request_time=request_datetime
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ logger = get_image_create_logger()
|
|||||||
class ImageCreateService:
|
class ImageCreateService:
|
||||||
def __init__(self, aspect_ratio="1:1"):
|
def __init__(self, aspect_ratio="1:1"):
|
||||||
self.image_model = settings.CREATE_IMAGE_MODEL
|
self.image_model = settings.CREATE_IMAGE_MODEL
|
||||||
self.paid_key = settings.PAID_KEY
|
|
||||||
self.aspect_ratio = aspect_ratio
|
self.aspect_ratio = aspect_ratio
|
||||||
|
|
||||||
def parse_prompt_parameters(self, prompt: str) -> tuple:
|
def parse_prompt_parameters(self, prompt: str) -> tuple:
|
||||||
@@ -53,7 +52,7 @@ class ImageCreateService:
|
|||||||
return prompt, n, aspect_ratio
|
return prompt, n, aspect_ratio
|
||||||
|
|
||||||
def generate_images(self, request: ImageGenerationRequest):
|
def generate_images(self, request: ImageGenerationRequest):
|
||||||
client = genai.Client(api_key=self.paid_key)
|
client = genai.Client(api_key=settings.PAID_KEY)
|
||||||
|
|
||||||
if request.size == "1024x1024":
|
if request.size == "1024x1024":
|
||||||
self.aspect_ratio = "1:1"
|
self.aspect_ratio = "1:1"
|
||||||
|
|||||||
Reference in New Issue
Block a user