refactor: 重构错误处理并优化路由与服务结构

主要变更:
- 新增 `app/handler/error_handler.py`,引入 `handle_route_errors` 异步上下文管理器,用于统一处理路由中的错误和日志记录。
- 在 `openai_routes` 和 `openai_compatiable_routes` 中应用 `handle_route_errors`,移除冗余的 try-except 块,简化路由逻辑。
- 将 `OpenAICompatiableService` 移动到 `app/service/openai_compatiable/` 目录下。
- 将 `StatsService` 移动到 `app/service/stats/` 目录下,并更新相关导入路径。
- 修复 `response_handler` 中处理 Gemini API 响应时 `inlineData` 字段的错误(原为 `inline_data`)。
- 修复 `openai_routes` 和 `openai_compatiable_routes` 中处理图像生成聊天(如 imagen3-chat)时未正确使用付费 API key 的问题。
- 在 `requirements.txt` 中将 `httpx` 更改为 `httpx[socks]`,以增加 SOCKS 代理支持。
This commit is contained in:
snaily
2025-05-02 01:20:05 +08:00
parent 7c9b721164
commit 2072f54ca1
11 changed files with 216 additions and 204 deletions
+32
View File
@@ -0,0 +1,32 @@
from contextlib import asynccontextmanager
from fastapi import HTTPException
import logging
@asynccontextmanager
async def handle_route_errors(logger: logging.Logger, operation_name: str, success_message: str = None, failure_message: str = None):
"""
一个异步上下文管理器,用于统一处理 FastAPI 路由中的常见错误和日志记录。
Args:
logger: 用于记录日志的 Logger 实例。
operation_name: 操作的名称,用于日志记录和错误详情。
success_message: 操作成功时记录的自定义消息 (可选)。
failure_message: 操作失败时记录的自定义消息 (可选)。
"""
default_success_msg = f"{operation_name} request successful"
default_failure_msg = f"{operation_name} request failed"
logger.info("-" * 50 + operation_name + "-" * 50)
try:
yield
logger.info(success_message or default_success_msg)
except HTTPException as http_exc:
# 如果已经是 HTTPException,直接重新抛出,保留原始状态码和详情
logger.error(f"{failure_message or default_failure_msg}: {http_exc.detail} (Status: {http_exc.status_code})")
raise http_exc
except Exception as e:
# 对于其他所有异常,记录错误并抛出标准的 500 错误
logger.error(f"{failure_message or default_failure_msg}: {str(e)}")
raise HTTPException(
status_code=500, detail=f"Internal server error during {operation_name}"
) from e
+3 -3
View File
@@ -172,7 +172,7 @@ def _extract_result(
text = _format_execution_result(parts[0]["executableCodeResult"]) text = _format_execution_result(parts[0]["executableCodeResult"])
elif "codeExecutionResult" in parts[0]: elif "codeExecutionResult" in parts[0]:
text = _format_execution_result(parts[0]["codeExecutionResult"]) text = _format_execution_result(parts[0]["codeExecutionResult"])
elif "inline_data" in parts[0]: elif "inlineData" in parts[0]:
text = _extract_image_data(parts[0]) text = _extract_image_data(parts[0])
else: else:
text = "" text = ""
@@ -203,7 +203,7 @@ def _extract_result(
for part in candidate["content"]["parts"]: for part in candidate["content"]["parts"]:
if "text" in part: if "text" in part:
text += part["text"] text += part["text"]
elif "inline_data" in part: elif "inlineData" in part:
text += _extract_image_data(part) text += _extract_image_data(part)
text = _add_search_link_text(model, candidate, text) text = _add_search_link_text(model, candidate, text)
@@ -233,7 +233,7 @@ def _extract_image_data(part: dict) -> str:
) )
current_date = time.strftime("%Y/%m/%d") current_date = time.strftime("%Y/%m/%d")
filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png" filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png"
base64_data = part["inline_data"]["data"] base64_data = part["inlineData"]["data"]
# 将base64_data转成bytes数组 # 将base64_data转成bytes数组
bytes_data = base64.b64decode(base64_data) bytes_data = base64.b64decode(base64_data)
upload_response = image_uploader.upload(bytes_data, filename) upload_response = image_uploader.upload(bytes_data, filename)
+75 -78
View File
@@ -1,15 +1,16 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse, JSONResponse from fastapi.responses import StreamingResponse, JSONResponse
from copy import deepcopy from copy import deepcopy
import asyncio
from app.config.config import settings from app.config.config import settings
from app.log.logger import get_gemini_logger from app.log.logger import get_gemini_logger
from app.core.security import SecurityService from app.core.security import SecurityService
import asyncio # 导入 asyncio
from app.domain.gemini_models import GeminiContent, GeminiRequest, ResetSelectedKeysRequest, VerifySelectedKeysRequest # 添加导入 from app.domain.gemini_models import GeminiContent, GeminiRequest, ResetSelectedKeysRequest, VerifySelectedKeysRequest # 添加导入
from app.service.chat.gemini_chat_service import GeminiChatService from app.service.chat.gemini_chat_service import GeminiChatService
from app.service.key.key_manager import KeyManager, get_key_manager_instance from app.service.key.key_manager import KeyManager, get_key_manager_instance
from app.service.model.model_service import ModelService from app.service.model.model_service import ModelService
from app.handler.retry_handler import RetryHandler from app.handler.retry_handler import RetryHandler
from app.handler.error_handler import handle_route_errors
from app.core.constants import API_VERSION from app.core.constants import API_VERSION
# 路由设置 # 路由设置
@@ -43,62 +44,62 @@ async def list_models(
_=Depends(security_service.verify_key_or_goog_api_key), _=Depends(security_service.verify_key_or_goog_api_key),
key_manager: KeyManager = Depends(get_key_manager) key_manager: KeyManager = Depends(get_key_manager)
): ):
"""获取可用的Gemini模型列表""" """获取可用的 Gemini 模型列表,并根据配置添加衍生模型(搜索、图像、非思考)。"""
logger.info("-" * 50 + "list_gemini_models" + "-" * 50) operation_name = "list_gemini_models"
# 注意:此路由的错误处理相对复杂,涉及模型查找和修改,
# 使用通用错误处理可能隐藏部分逻辑错误。暂时保留原有结构,
# 但如果需要更统一的处理,可以将内部逻辑封装并应用 handle_route_errors。
# 这里仅添加日志分隔符。
logger.info("-" * 50 + operation_name + "-" * 50)
logger.info("Handling Gemini models list request") logger.info("Handling Gemini models list request")
api_key = await key_manager.get_first_valid_key() try:
logger.info(f"Using API key: {api_key}") api_key = await key_manager.get_first_valid_key()
if not api_key:
models_json = model_service.get_gemini_models(api_key) raise HTTPException(status_code=503, detail="No valid API keys available to fetch models.")
model_mapping = {x.get("name", "").split("/", maxsplit=1)[1]: x for x in models_json["models"]} logger.info(f"Using API key: {api_key}")
# 添加搜索模型 # 假设 get_gemini_models 是同步的,如果不是需要 await
if settings.SEARCH_MODELS: models_data = model_service.get_gemini_models(api_key)
for name in settings.SEARCH_MODELS: if not models_data or "models" not in models_data:
model = model_mapping.get(name) raise HTTPException(status_code=500, detail="Failed to fetch base models list.")
models_json = deepcopy(models_data) # 操作副本以防修改原始缓存
model_mapping = {x.get("name", "").split("/", maxsplit=1)[-1]: x for x in models_json.get("models", [])}
def add_derived_model(base_name, suffix, display_suffix):
model = model_mapping.get(base_name)
if not model: if not model:
continue logger.warning(f"Base model '{base_name}' not found for derived model '{suffix}'.")
return
item = deepcopy(model) item = deepcopy(model)
item["name"] = f"models/{name}-search" item["name"] = f"models/{base_name}{suffix}"
display_name = f'{item.get("displayName")} For Search' display_name = f'{item.get("displayName", base_name)}{display_suffix}'
item["displayName"] = display_name item["displayName"] = display_name
item["description"] = display_name item["description"] = display_name # 使用 display_name 作为描述
models_json["models"].append(item) models_json["models"].append(item)
# 添加图像生成模型 # 添加衍生模型
if settings.IMAGE_MODELS: if settings.SEARCH_MODELS:
for name in settings.IMAGE_MODELS: for name in settings.SEARCH_MODELS:
model = model_mapping.get(name) add_derived_model(name, "-search", " For Search")
if not model: if settings.IMAGE_MODELS:
continue for name in settings.IMAGE_MODELS:
add_derived_model(name, "-image", " For Image")
item = deepcopy(model) if settings.THINKING_MODELS:
item["name"] = f"models/{name}-image" for name in settings.THINKING_MODELS:
display_name = f'{item.get("displayName")} For Image' add_derived_model(name, "-non-thinking", " Non Thinking")
item["displayName"] = display_name
item["description"] = display_name logger.info("Gemini models list request successful")
return models_json
models_json["models"].append(item) except HTTPException as http_exc:
# 重新抛出已知的 HTTP 异常
# 添加思考模型的非思考版本 raise http_exc
if settings.THINKING_MODELS: except Exception as e:
for name in settings.THINKING_MODELS: logger.error(f"Error getting Gemini models list: {str(e)}")
model = model_mapping.get(name) raise HTTPException(
if not model: status_code=500, detail="Internal server error while fetching Gemini models list"
continue ) from e
item = deepcopy(model)
item["name"] = f"models/{name}-non-thinking"
display_name = f'{item.get("displayName")} Non Thinking'
item["displayName"] = display_name
item["description"] = display_name
models_json["models"].append(item)
return models_json
@router.post("/models/{model_name}:generateContent") @router.post("/models/{model_name}:generateContent")
@@ -112,25 +113,22 @@ async def generate_content(
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
chat_service: GeminiChatService = Depends(get_chat_service) chat_service: GeminiChatService = Depends(get_chat_service)
): ):
"""非流式生成内容""" """处理 Gemini 非流式内容生成请求。"""
logger.info("-" * 50 + "gemini_generate_content" + "-" * 50) operation_name = "gemini_generate_content"
logger.info(f"Handling Gemini content generation request for model: {model_name}") async with handle_route_errors(logger, operation_name, failure_message="Content generation failed"):
logger.debug(f"Request: \n{request.model_dump_json(indent=2)}") logger.info(f"Handling Gemini content generation request for model: {model_name}")
logger.info(f"Using API key: {api_key}") logger.debug(f"Request: \n{request.model_dump_json(indent=2)}")
logger.info(f"Using API key: {api_key}")
if not model_service.check_model_support(model_name):
raise HTTPException(status_code=400, detail=f"Model {model_name} is not supported") if not model_service.check_model_support(model_name):
raise HTTPException(status_code=400, detail=f"Model {model_name} is not supported")
try:
response = await chat_service.generate_content( response = await chat_service.generate_content(
model=model_name, model=model_name,
request=request, request=request,
api_key=api_key api_key=api_key
) )
return response return response
except Exception as e:
logger.error(f"Chat completion failed after retries: {str(e)}")
raise HTTPException(status_code=500, detail="Chat completion failed") from e
@router.post("/models/{model_name}:streamGenerateContent") @router.post("/models/{model_name}:streamGenerateContent")
@@ -144,25 +142,24 @@ async def stream_generate_content(
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
chat_service: GeminiChatService = Depends(get_chat_service) chat_service: GeminiChatService = Depends(get_chat_service)
): ):
"""流式生成内容""" """处理 Gemini 流式内容生成请求。"""
logger.info("-" * 50 + "gemini_stream_generate_content" + "-" * 50) operation_name = "gemini_stream_generate_content"
logger.info(f"Handling Gemini streaming content generation for model: {model_name}") # 流式请求的成功/失败日志在流处理中更复杂,这里仅用上下文管理器处理启动错误
logger.debug(f"Request: \n{request.model_dump_json(indent=2)}") async with handle_route_errors(logger, operation_name, failure_message="Streaming request initiation failed"):
logger.info(f"Using API key: {api_key}") logger.info(f"Handling Gemini streaming content generation for model: {model_name}")
logger.debug(f"Request: \n{request.model_dump_json(indent=2)}")
if not model_service.check_model_support(model_name): logger.info(f"Using API key: {api_key}")
raise HTTPException(status_code=400, detail=f"Model {model_name} is not supported")
if not model_service.check_model_support(model_name):
try: raise HTTPException(status_code=400, detail=f"Model {model_name} is not supported")
response_stream = chat_service.stream_generate_content( response_stream = chat_service.stream_generate_content(
model=model_name, model=model_name,
request=request, request=request,
api_key=api_key api_key=api_key
) )
# 注意:流本身的错误需要在服务层或流迭代中处理,这里只返回流响应
return StreamingResponse(response_stream, media_type="text/event-stream") return StreamingResponse(response_stream, media_type="text/event-stream")
except Exception as e:
logger.error(f"Streaming request failed: {str(e)}")
raise HTTPException(status_code=500, detail="Streaming request failed") from e
@router.post("/reset-all-fail-counts") @router.post("/reset-all-fail-counts")
async def reset_all_key_fail_counts(key_type: str = None, key_manager: KeyManager = Depends(get_key_manager)): async def reset_all_key_fail_counts(key_type: str = None, key_manager: KeyManager = Depends(get_key_manager)):
+47 -56
View File
@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from app.config.config import settings from app.config.config import settings
@@ -9,9 +9,10 @@ from app.domain.openai_models import (
ImageGenerationRequest, ImageGenerationRequest,
) )
from app.handler.retry_handler import RetryHandler from app.handler.retry_handler import RetryHandler
from app.handler.error_handler import handle_route_errors
from app.log.logger import get_openai_compatible_logger from app.log.logger import get_openai_compatible_logger
from app.service.key.key_manager import KeyManager, get_key_manager_instance from app.service.key.key_manager import KeyManager, get_key_manager_instance
from app.service.openai_compatiable_service import OpenAICompatiableService from app.service.openai_compatiable.openai_compatiable_service import OpenAICompatiableService
router = APIRouter() router = APIRouter()
@@ -41,17 +42,13 @@ async def list_models(
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
openai_service: OpenAICompatiableService = Depends(get_openai_service), openai_service: OpenAICompatiableService = Depends(get_openai_service),
): ):
logger.info("-" * 50 + "list_models" + "-" * 50) """获取可用模型列表。"""
logger.info("Handling models list request") operation_name = "list_models"
api_key = await key_manager.get_first_valid_key() async with handle_route_errors(logger, operation_name):
logger.info(f"Using API key: {api_key}") logger.info("Handling models list request")
try: api_key = await key_manager.get_first_valid_key()
logger.info(f"Using API key: {api_key}")
return await openai_service.get_models(api_key) return await openai_service.get_models(api_key)
except Exception as e:
logger.error(f"Error getting models list: {str(e)}")
raise HTTPException(
status_code=500, detail="Internal server error while fetching models list"
) from e
@router.post("/openai/v1/chat/completions") @router.post("/openai/v1/chat/completions")
@@ -63,28 +60,32 @@ async def chat_completion(
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
openai_service: OpenAICompatiableService = Depends(get_openai_service), openai_service: OpenAICompatiableService = Depends(get_openai_service),
): ):
# 如果model是imagen3,使用paid_key """处理聊天补全请求,支持流式响应和特定模型切换。"""
if request.model == f"{settings.CREATE_IMAGE_MODEL}-chat": operation_name = "chat_completion"
api_key = await key_manager.get_paid_key() # 检查是否为图像生成相关的聊天模型,如果是,则使用付费密钥
logger.info("-" * 50 + "chat_completion" + "-" * 50) is_image_chat = request.model == f"{settings.CREATE_IMAGE_MODEL}-chat"
logger.info(f"Handling chat completion request for model: {request.model}") current_api_key = api_key # 保存原始key(可能是普通key
logger.debug(f"Request: \n{request.model_dump_json(indent=2)}") if is_image_chat:
logger.info(f"Using API key: {api_key}") current_api_key = await key_manager.get_paid_key() # 获取付费密钥
try: async with handle_route_errors(logger, operation_name):
# 如果model是imagen3,使用paid_key logger.info(f"Handling chat completion request for model: {request.model}")
if request.model == f"{settings.CREATE_IMAGE_MODEL}-chat": logger.debug(f"Request: \n{request.model_dump_json(indent=2)}")
response = await openai_service.create_image_chat_completion(request, api_key) logger.info(f"Using API key: {current_api_key}") # 使用 current_api_key
if is_image_chat:
# 图像生成聊天,调用特定服务,不处理流式
response = await openai_service.create_image_chat_completion(request, current_api_key)
return response # 直接返回结果
else: else:
response = await openai_service.create_chat_completion(request, api_key) # 普通聊天补全
# 处理流式响应 response = await openai_service.create_chat_completion(request, current_api_key)
if request.stream: # 处理流式响应
return StreamingResponse(response, media_type="text/event-stream") if request.stream:
logger.info("Chat completion request successful") # 假设 openai_service.create_chat_completion 在流式时返回异步生成器
return response return StreamingResponse(response, media_type="text/event-stream")
except Exception as e: # 非流式直接返回结果
logger.error(f"Chat completion failed after retries: {str(e)}") return response
raise HTTPException(status_code=500, detail="Chat completion failed") from e
@router.post("/openai/v1/images/generations") @router.post("/openai/v1/images/generations")
@@ -93,19 +94,13 @@ async def generate_image(
_=Depends(security_service.verify_authorization), _=Depends(security_service.verify_authorization),
openai_service: OpenAICompatiableService = Depends(get_openai_service), openai_service: OpenAICompatiableService = Depends(get_openai_service),
): ):
logger.info("-" * 50 + "generate_image" + "-" * 50) """处理图像生成请求。"""
logger.info(f"Handling image generation request for prompt: {request.prompt}") operation_name = "generate_image"
request.model = settings.CREATE_IMAGE_MODEL async with handle_route_errors(logger, operation_name):
logger.info(f"Handling image generation request for prompt: {request.prompt}")
try: # 强制使用配置的模型,确保请求中包含正确的模型信息
response = await openai_service.generate_images(request) request.model = settings.CREATE_IMAGE_MODEL
logger.info("Image generation request successful") return await openai_service.generate_images(request)
return response
except Exception as e:
logger.error(f"Image generation request failed: {str(e)}")
raise HTTPException(
status_code=500, detail="Image generation request failed"
) from e
@router.post("/openai/v1/embeddings") @router.post("/openai/v1/embeddings")
@@ -115,16 +110,12 @@ async def embedding(
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
openai_service: OpenAICompatiableService = Depends(get_openai_service), openai_service: OpenAICompatiableService = Depends(get_openai_service),
): ):
logger.info("-" * 50 + "embedding" + "-" * 50) """处理文本嵌入请求。"""
logger.info(f"Handling embedding request for model: {request.model}") operation_name = "embedding"
api_key = await key_manager.get_next_working_key() async with handle_route_errors(logger, operation_name):
logger.info(f"Using API key: {api_key}") logger.info(f"Handling embedding request for model: {request.model}")
try: api_key = await key_manager.get_next_working_key()
response = await openai_service.create_embeddings( logger.info(f"Using API key: {api_key}")
return await openai_service.create_embeddings(
input_text=request.input, model=request.model, api_key=api_key input_text=request.input, model=request.model, api_key=api_key
) )
logger.info("Embedding request successful")
return response
except Exception as e:
logger.error(f"Embedding request failed: {str(e)}")
raise HTTPException(status_code=500, detail="Embedding request failed") from e
+54 -62
View File
@@ -9,6 +9,7 @@ from app.domain.openai_models import (
ImageGenerationRequest, ImageGenerationRequest,
) )
from app.handler.retry_handler import RetryHandler from app.handler.retry_handler import RetryHandler
from app.handler.error_handler import handle_route_errors # 导入共享错误处理器
from app.log.logger import get_openai_logger from app.log.logger import get_openai_logger
from app.service.chat.openai_chat_service import OpenAIChatService from app.service.chat.openai_chat_service import OpenAIChatService
from app.service.embedding.embedding_service import EmbeddingService from app.service.embedding.embedding_service import EmbeddingService
@@ -47,17 +48,15 @@ async def list_models(
_=Depends(security_service.verify_authorization), _=Depends(security_service.verify_authorization),
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
): ):
logger.info("-" * 50 + "list_models" + "-" * 50) """获取可用的 OpenAI 模型列表 (兼容 Gemini 和 OpenAI)。"""
logger.info("Handling models list request") operation_name = "list_models"
api_key = await key_manager.get_first_valid_key() async with handle_route_errors(logger, operation_name):
logger.info(f"Using API key: {api_key}") logger.info("Handling models list request")
try: api_key = await key_manager.get_first_valid_key()
logger.info(f"Using API key: {api_key}")
# 注意:这里假设 model_service.get_gemini_openai_models 是同步函数
# 如果它是异步的,需要 await
return model_service.get_gemini_openai_models(api_key) return model_service.get_gemini_openai_models(api_key)
except Exception as e:
logger.error(f"Error getting models list: {str(e)}")
raise HTTPException(
status_code=500, detail="Internal server error while fetching models list"
) from e
@router.post("/v1/chat/completions") @router.post("/v1/chat/completions")
@@ -70,33 +69,38 @@ async def chat_completion(
key_manager: KeyManager = Depends(get_key_manager), # 保留 key_manager 用于获取 paid_key key_manager: KeyManager = Depends(get_key_manager), # 保留 key_manager 用于获取 paid_key
chat_service: OpenAIChatService = Depends(get_openai_chat_service), chat_service: OpenAIChatService = Depends(get_openai_chat_service),
): ):
# 如果model是imagen3,使用paid_key """处理 OpenAI 聊天补全请求,支持流式响应和特定模型切换。"""
if request.model == f"{settings.CREATE_IMAGE_MODEL}-chat": operation_name = "chat_completion"
api_key = await key_manager.get_paid_key() # 检查是否为图像生成相关的聊天模型
logger.info("-" * 50 + "chat_completion" + "-" * 50) is_image_chat = request.model == f"{settings.CREATE_IMAGE_MODEL}-chat"
logger.info(f"Handling chat completion request for model: {request.model}") current_api_key = api_key # 保存原始 key
logger.debug(f"Request: \n{request.model_dump_json(indent=2)}") if is_image_chat:
logger.info(f"Using API key: {api_key}") current_api_key = await key_manager.get_paid_key() # 获取付费密钥
if not model_service.check_model_support(request.model): async with handle_route_errors(logger, operation_name):
raise HTTPException( logger.info(f"Handling chat completion request for model: {request.model}")
status_code=400, detail=f"Model {request.model} is not supported" logger.debug(f"Request: \n{request.model_dump_json(indent=2)}")
) logger.info(f"Using API key: {current_api_key}")
try: # 检查模型支持性应在错误处理块内,以便捕获并记录错误
# 如果model是imagen3,使用paid_key if not model_service.check_model_support(request.model):
if request.model == f"{settings.CREATE_IMAGE_MODEL}-chat": # 使用 HTTPException,会被 handle_route_errors 捕获并记录
response = await chat_service.create_image_chat_completion(request, api_key) raise HTTPException(
status_code=400, detail=f"Model {request.model} is not supported"
)
if is_image_chat:
# 图像生成聊天
response = await chat_service.create_image_chat_completion(request, current_api_key)
return response # 直接返回,不处理流式
else: else:
response = await chat_service.create_chat_completion(request, api_key) # 普通聊天补全
# 处理流式响应 response = await chat_service.create_chat_completion(request, current_api_key)
if request.stream: # 处理流式响应
return StreamingResponse(response, media_type="text/event-stream") if request.stream:
logger.info("Chat completion request successful") return StreamingResponse(response, media_type="text/event-stream")
return response # 非流式直接返回结果
except Exception as e: return response
logger.error(f"Chat completion failed after retries: {str(e)}")
raise HTTPException(status_code=500, detail="Chat completion failed") from e
@router.post("/v1/images/generations") @router.post("/v1/images/generations")
@@ -105,18 +109,14 @@ async def generate_image(
request: ImageGenerationRequest, request: ImageGenerationRequest,
_=Depends(security_service.verify_authorization), _=Depends(security_service.verify_authorization),
): ):
logger.info("-" * 50 + "generate_image" + "-" * 50) """处理 OpenAI 图像生成请求。"""
logger.info(f"Handling image generation request for prompt: {request.prompt}") operation_name = "generate_image"
async with handle_route_errors(logger, operation_name):
try: logger.info(f"Handling image generation request for prompt: {request.prompt}")
# 注意:这里假设 image_create_service.generate_images 是同步函数
# 如果它是异步的,需要 await
response = image_create_service.generate_images(request) response = image_create_service.generate_images(request)
logger.info("Image generation request successful")
return response return response
except Exception as e:
logger.error(f"Image generation request failed: {str(e)}")
raise HTTPException(
status_code=500, detail="Image generation request failed"
) from e
@router.post("/v1/embeddings") @router.post("/v1/embeddings")
@@ -126,19 +126,16 @@ async def embedding(
_=Depends(security_service.verify_authorization), _=Depends(security_service.verify_authorization),
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
): ):
logger.info("-" * 50 + "embedding" + "-" * 50) """处理 OpenAI 文本嵌入请求。"""
logger.info(f"Handling embedding request for model: {request.model}") operation_name = "embedding"
api_key = await key_manager.get_next_working_key() async with handle_route_errors(logger, operation_name):
logger.info(f"Using API key: {api_key}") logger.info(f"Handling embedding request for model: {request.model}")
try: api_key = await key_manager.get_next_working_key()
logger.info(f"Using API key: {api_key}")
response = await embedding_service.create_embedding( response = await embedding_service.create_embedding(
input_text=request.input, model=request.model, api_key=api_key input_text=request.input, model=request.model, api_key=api_key
) )
logger.info("Embedding request successful")
return response return response
except Exception as e:
logger.error(f"Embedding request failed: {str(e)}")
raise HTTPException(status_code=500, detail="Embedding request failed") from e
@router.get("/v1/keys/list") @router.get("/v1/keys/list")
@@ -147,10 +144,10 @@ async def get_keys_list(
_=Depends(security_service.verify_auth_token), _=Depends(security_service.verify_auth_token),
key_manager: KeyManager = Depends(get_key_manager), key_manager: KeyManager = Depends(get_key_manager),
): ):
"""获取有效和无效的API key列表""" """获取有效和无效的API key列表 (需要管理 Token 认证)。"""
logger.info("-" * 50 + "get_keys_list" + "-" * 50) operation_name = "get_keys_list"
logger.info("Handling keys list request") async with handle_route_errors(logger, operation_name):
try: logger.info("Handling keys list request")
keys_status = await key_manager.get_keys_by_status() keys_status = await key_manager.get_keys_by_status()
return { return {
"status": "success", "status": "success",
@@ -160,8 +157,3 @@ async def get_keys_list(
}, },
"total": len(keys_status["valid_keys"]) + len(keys_status["invalid_keys"]), "total": len(keys_status["valid_keys"]) + len(keys_status["invalid_keys"]),
} }
except Exception as e:
logger.error(f"Error getting keys list: {str(e)}")
raise HTTPException(
status_code=500, detail="Internal server error while fetching keys list"
) from e
+1 -1
View File
@@ -10,7 +10,7 @@ from app.core.security import verify_auth_token
from app.log.logger import get_routes_logger from app.log.logger import get_routes_logger
from app.router import error_log_routes, gemini_routes, openai_routes, config_routes, scheduler_routes, stats_routes, version_routes, openai_compatiable_routes from app.router import error_log_routes, gemini_routes, openai_routes, config_routes, scheduler_routes, stats_routes, version_routes, openai_compatiable_routes
from app.service.key.key_manager import get_key_manager_instance from app.service.key.key_manager import get_key_manager_instance
from app.service.stats_service import StatsService from app.service.stats.stats_service import StatsService
logger = get_routes_logger() logger = get_routes_logger()
+1 -1
View File
@@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request
from starlette import status from starlette import status
from app.core.security import verify_auth_token from app.core.security import verify_auth_token
from app.service.stats_service import StatsService from app.service.stats.stats_service import StatsService
from app.log.logger import get_stats_logger from app.log.logger import get_stats_logger
logger = get_stats_logger() logger = get_stats_logger()
+1
View File
@@ -272,6 +272,7 @@ class OpenAIChatService:
async for line in self.api_client.stream_generate_content( async for line in self.api_client.stream_generate_content(
payload, model, current_attempt_key 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(
+2 -3
View File
@@ -1,5 +1,5 @@
fastapi fastapi
httpx httpx[socks]
openai openai
pydantic pydantic
pydantic_settings pydantic_settings
@@ -16,6 +16,5 @@ sqlalchemy
aiomysql aiomysql
databases databases
python-dotenv python-dotenv
apscheduler # 添加定时任务库 apscheduler
packaging packaging