diff --git a/app/api/admin/manage.py b/app/api/admin/manage.py index 87d02ed..a6876c2 100644 --- a/app/api/admin/manage.py +++ b/app/api/admin/manage.py @@ -252,7 +252,7 @@ async def admin_login(request: LoginRequest) -> LoginResponse: @router.post("/api/logout") -async def admin_logout(authenticated: bool = Depends(verify_admin_session), +async def admin_logout(_: bool = Depends(verify_admin_session), authorization: Optional[str] = Header(None)) -> Dict[str, Any]: """ 管理员登出 @@ -279,7 +279,7 @@ async def admin_logout(authenticated: bool = Depends(verify_admin_session), @router.get("/api/tokens", response_model=TokenListResponse) -async def list_tokens(authenticated: bool = Depends(verify_admin_session)) -> TokenListResponse: +async def list_tokens(_: bool = Depends(verify_admin_session)) -> TokenListResponse: """ 获取所有Token列表 @@ -337,7 +337,7 @@ async def list_tokens(authenticated: bool = Depends(verify_admin_session)) -> To @router.post("/api/tokens/add") async def add_tokens(request: AddTokensRequest, - authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: + _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: """ 批量添加Token @@ -372,7 +372,7 @@ async def add_tokens(request: AddTokensRequest, @router.post("/api/tokens/delete") async def delete_tokens(request: DeleteTokensRequest, - authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: + _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: """ 批量删除Token @@ -406,7 +406,7 @@ async def delete_tokens(request: DeleteTokensRequest, @router.get("/api/settings") -async def get_settings(authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: +async def get_settings(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: """获取全局配置""" try: logger.debug("[Admin] 获取全局配置") @@ -429,7 +429,7 @@ class UpdateSettingsRequest(BaseModel): @router.post("/api/settings") -async def update_settings(request: UpdateSettingsRequest, authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: +async def update_settings(request: UpdateSettingsRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: """更新全局配置""" try: import toml @@ -484,7 +484,7 @@ def _format_size(size_bytes: int) -> str: @router.get("/api/cache/size") -async def get_cache_size(authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: +async def get_cache_size(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: """获取缓存大小""" try: logger.debug("[Admin] 开始获取缓存大小") @@ -525,8 +525,10 @@ async def get_cache_size(authenticated: bool = Depends(verify_admin_session)) -> @router.post("/api/cache/clear") -async def clear_cache(authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: - """清理缓存 - 删除所有临时文件""" +async def clear_cache(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: + """清理缓存 + + 删除所有临时文件""" try: logger.debug("[Admin] 开始清理缓存") @@ -578,7 +580,7 @@ async def clear_cache(authenticated: bool = Depends(verify_admin_session)) -> Di @router.get("/api/stats") -async def get_stats(authenticated: bool = Depends(verify_admin_session)) -> Dict[str, Any]: +async def get_stats(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: """ 获取统计信息 diff --git a/app/api/v1/chat.py b/app/api/v1/chat.py index 7c2c61d..9e612a8 100644 --- a/app/api/v1/chat.py +++ b/app/api/v1/chat.py @@ -22,21 +22,21 @@ router = APIRouter(prefix="/chat", tags=["聊天"]) @router.post("/completions", response_model=None) async def chat_completions( request: OpenAIChatRequest, - authenticated: Optional[str] = Depends(auth_manager.verify) + _: Optional[str] = Depends(auth_manager.verify) ): """ 创建聊天补全 - + 兼容OpenAI聊天API的端点,支持流式和非流式响应。 - + Args: request: OpenAI格式的聊天请求 - authenticated: 认证状态(由依赖注入) - + _: 认证依赖(自动验证) + Returns: OpenAIChatCompletionResponse: 非流式响应 StreamingResponse: 流式响应 - + Raises: HTTPException: 当请求处理失败时 """ @@ -74,7 +74,7 @@ async def chat_completions( } ) except Exception as e: - logger.error(f"[Chat] 聊天请求处理失败: {str(e)}", exc_info=True) + logger.error(f"[Chat] 聊天请求处理失败: {str(e)}") raise HTTPException( status_code=500, detail={ diff --git a/app/api/v1/models.py b/app/api/v1/models.py index f5f1a5a..5c18513 100644 --- a/app/api/v1/models.py +++ b/app/api/v1/models.py @@ -19,15 +19,15 @@ router = APIRouter(tags=["模型"]) @router.get("/models") -async def list_models(authenticated: Optional[str] = Depends(auth_manager.verify)) -> Dict[str, Any]: +async def list_models(_: Optional[str] = Depends(auth_manager.verify)) -> Dict[str, Any]: """ 获取可用模型列表 - + 返回 OpenAI 兼容的模型列表格式,包含系统支持的所有 Grok 模型的详细信息。 - + Args: - authenticated: 认证状态(由依赖注入) - + _: 认证依赖(自动验证) + Returns: Dict[str, Any]: 包含模型列表的响应数据 """ @@ -85,14 +85,14 @@ async def list_models(authenticated: Optional[str] = Depends(auth_manager.verify @router.get("/models/{model_id}") -async def get_model(model_id: str, authenticated: Optional[str] = Depends(auth_manager.verify)) -> Dict[str, Any]: +async def get_model(model_id: str, _: Optional[str] = Depends(auth_manager.verify)) -> Dict[str, Any]: """ 获取特定模型信息 - + Args: model_id (str): 模型ID - authenticated: 认证状态(由依赖注入) - + _: 认证依赖(自动验证) + Returns: Dict[str, Any]: 模型详细信息 """ diff --git a/app/models/openai_schema.py b/app/models/openai_schema.py index 9012d87..37b6eeb 100644 --- a/app/models/openai_schema.py +++ b/app/models/openai_schema.py @@ -15,8 +15,8 @@ class OpenAIChatRequest(BaseModel): max_tokens: Optional[int] = Field(None, ge=1, le=100000, description="最大Token数") top_p: Optional[float] = Field(1.0, ge=0, le=1, description="采样参数") - @field_validator('messages') @classmethod + @field_validator('messages') def validate_messages(cls, v): """验证消息格式""" if not v: @@ -49,8 +49,8 @@ class OpenAIChatRequest(BaseModel): return v - @field_validator('model') @classmethod + @field_validator('model') def validate_model(cls, v): """验证模型名称""" allowed_models = [ @@ -68,16 +68,16 @@ class OpenAIChatCompletionMessage(BaseModel): """聊天完成消息""" role: str = Field(..., description="角色") content: str = Field(..., description="消息内容") - reference_id: Optional[str] = Field(None, description="参考ID") - annotations: Optional[List[str]] = Field(None, description="注释") + reference_id: Optional[str] = Field(default=None, description="参考ID") + annotations: Optional[List[str]] = Field(default=None, description="注释") class OpenAIChatCompletionChoice(BaseModel): """聊天完成选项""" index: int = Field(..., description="选项索引") message: OpenAIChatCompletionMessage = Field(..., description="响应消息") - logprobs: Optional[float] = Field(None, description="对数概率") - finish_reason: str = Field("stop", description="完成原因") + logprobs: Optional[float] = Field(default=None, description="对数概率") + finish_reason: str = Field(default="stop", description="完成原因") class OpenAIChatCompletionResponse(BaseModel): @@ -108,8 +108,8 @@ class OpenAIChatCompletionChunkChoice(BaseModel): class OpenAIChatCompletionChunkResponse(BaseModel): """流式聊天完成响应""" id: str = Field(..., description="响应ID") - object: str = Field("chat.completion.chunk", description="对象类型") + object: str = Field(default="chat.completion.chunk", description="对象类型") created: int = Field(..., description="创建时间戳") model: str = Field(..., description="使用的模型") - system_fingerprint: Optional[str] = Field(None, description="系统指纹") + system_fingerprint: Optional[str] = Field(default=None, description="系统指纹") choices: List[OpenAIChatCompletionChunkChoice] = Field(..., description="响应选项") \ No newline at end of file diff --git a/app/services/grok/cache.py b/app/services/grok/cache.py index 28408c8..5dcbfb1 100644 --- a/app/services/grok/cache.py +++ b/app/services/grok/cache.py @@ -14,12 +14,7 @@ class CacheService: """缓存服务基类""" def __init__(self, cache_type: str): - """ - 初始化缓存服务 - - Args: - cache_type: 缓存类型,'image' 或 'video' - """ + """初始化缓存服务""" self.cache_type = cache_type self.cache_dir = Path(f"data/temp/{cache_type}") self.cache_dir.mkdir(parents=True, exist_ok=True) @@ -27,7 +22,6 @@ class CacheService: @staticmethod def _get_cache_filename(file_path: str) -> str: """将文件路径转换为缓存文件名""" - # 移除开头的斜杠并替换所有斜杠为短横线 filename = file_path.lstrip('/').replace('/', '-') return filename @@ -75,7 +69,7 @@ class CacheService: # 代理配置 proxy_url = setting.grok_config.get("proxy_url") - proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None + proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else {} async with AsyncSession() as session: logger.debug(f"[{self.cache_type.upper()}Cache] 开始下载: {file_url}") @@ -115,12 +109,12 @@ class CacheService: async def cleanup_cache(self): """清理缓存目录,确保不超过配置的大小限制""" try: - # 获取配置的最大缓存大小(MB) + # 获取配置的最大缓存大小 config_key = f"{self.cache_type}_cache_max_size_mb" max_size_mb = setting.global_config.get(config_key, 500) max_size_bytes = max_size_mb * 1024 * 1024 - # 获取所有缓存文件及其大小和修改时间 + # 获取缓存大小和修改时间 files = [] total_size = 0 @@ -138,10 +132,10 @@ class CacheService: logger.info(f"[{self.cache_type.upper()}Cache] 缓存大小 {total_size / 1024 / 1024:.2f}MB 超过限制 {max_size_mb}MB,开始清理") - # 按修改时间排序(最旧的在前) + # 按修改时间排序 files.sort(key=lambda x: x[2]) - # 删除最旧的文件直到总大小低于限制 + # 删除文件直到总大小低于限制 for file_path, size, _ in files: if total_size <= max_size_bytes: break @@ -170,7 +164,7 @@ class ImageCacheService(CacheService): auth_token: 认证令牌 Returns: - 缓存文件路径,下载失败返回 None + 缓存文件路径,下载失败返回 None` """ return await self.download_file(image_path, auth_token, timeout=30.0) diff --git a/app/services/grok/client.py b/app/services/grok/client.py index 3fec279..9d4d935 100644 --- a/app/services/grok/client.py +++ b/app/services/grok/client.py @@ -148,18 +148,23 @@ class GrokClient: try: # 构建请求头和代理 headers = GrokClient._build_headers(auth_token) - proxies = GrokClient._get_proxy() + proxy_config = GrokClient._get_proxy() + + # 构建请求参数 + request_kwargs = { + "headers": headers, + "data": json.dumps(payload), + "impersonate": IMPERSONATE_BROWSER, + "timeout": REQUEST_TIMEOUT, + "stream": True, + "proxies": proxy_config if proxy_config else None + } # 在线程池中执行同步HTTP请求,避免阻塞事件循环 response = await asyncio.to_thread( curl_requests.post, GROK_API_ENDPOINT, - headers=headers, - data=json.dumps(payload), - impersonate=IMPERSONATE_BROWSER, - timeout=REQUEST_TIMEOUT, - stream=True, - **proxies + **request_kwargs ) logger.debug(f"[Client] API响应状态码: {response.status_code}") @@ -204,9 +209,9 @@ class GrokClient: try: error_data = response.json() error_message = str(error_data) - except Exception: + except Exception as e: error_data = response.text - error_message = error_data[:200] if error_data else "未知错误" + error_message = error_data[:200] if error_data else e # 记录Token失败 asyncio.create_task(token_manager.record_failure(auth_token, response.status_code, error_message)) diff --git a/app/services/grok/processer.py b/app/services/grok/processer.py index 525af81..7d66084 100644 --- a/app/services/grok/processer.py +++ b/app/services/grok/processer.py @@ -3,7 +3,7 @@ import json import uuid import time -from typing import Iterator +from typing import AsyncGenerator from app.core.config import setting from app.core.exception import GrokApiException @@ -43,7 +43,7 @@ class GrokResponseProcessor: # 提取响应数据 grok_resp = data.get("result", {}).get("response", {}) - # 检查视频生成响应 + # 提取视频数据 if video_resp := grok_resp.get("streamingVideoGenerationResponse"): if video_url := video_resp.get("videoUrl"): logger.debug(f"[Processor] 检测到视频生成: {video_url}") @@ -98,7 +98,7 @@ class GrokResponseProcessor: model_name = model_response.get("model") content = model_response.get("message", "") - # 添加生成的图片 + # 提取图片数据 if images := model_response.get("generatedImageUrls"): for img in images: try: @@ -114,7 +114,7 @@ class GrokResponseProcessor: logger.warning(f"[Processor] 缓存图片失败: {e}") content += f"\n![Generated Image](https://assets.grok.com/{img})" - # 返回OpenAI格式响应 + # 返回 OpenAI 响应格式 result = OpenAIChatCompletionResponse( id=f"chatcmpl-{uuid.uuid4()}", object="chat.completion", @@ -143,22 +143,19 @@ class GrokResponseProcessor: response.close() @staticmethod - async def process_stream(response, auth_token: str) -> Iterator[str]: + async def process_stream(response, auth_token: str) -> AsyncGenerator[str, None]: """处理流式响应""" + # 流式生成状态 is_image = False is_thinking = False thinking_finished = False chunk_index = 0 model = None - filtered_tags = setting.grok_config.get("filtered_tags", "").split(",") - - # 视频生成相关状态 - is_video = False - video_url = None + filtered_tags = setting.grok_config.get("filtered_tags", "").split(",") video_progress_started = False last_video_progress = -1 - def make_chunk(content: str, finish: str = None): + def make_chunk(chunk_content: str, finish: str = None): """生成OpenAI格式的响应块""" chunk_data = OpenAIChatCompletionChunkResponse( id=f"chatcmpl-{uuid.uuid4()}", @@ -168,8 +165,8 @@ class GrokResponseProcessor: index=chunk_index, delta=OpenAIChatCompletionChunkMessage( role="assistant", - content=content - ) if content else {}, + content=chunk_content + ) if chunk_content else {}, finish_reason=finish )] ).model_dump() @@ -204,27 +201,40 @@ class GrokResponseProcessor: if m := user_resp.get("model"): model = m - # 检查视频生成响应 + # 提取视频数据 if video_resp := grok_resp.get("streamingVideoGenerationResponse"): - is_video = True progress = video_resp.get("progress", 0) if progress > last_video_progress: last_video_progress = progress - # 首次显示进度时添加 + # 添加 标签 if not video_progress_started: content = f"视频已生成{progress}%\n" video_progress_started = True elif progress < 100: content = f"视频已生成{progress}%\n" else: - # 进度100%时关闭think标签 + # 进度100%时关闭 标签并立即处理视频 content = f"视频已生成{progress}%\n" - # 保存视频URL + + # 立即下载并缓存视频 if v_url := video_resp.get("videoUrl"): - video_url = v_url - logger.debug(f"[Processor] 视频生成完成: {video_url}") + logger.debug(f"[Processor] 视频生成完成: {v_url}") + full_video_url = f"https://assets.grok.com/{v_url}" + + try: + cache_path = await video_cache_service.download_video(f"/{v_url}", auth_token) + if cache_path: + video_path = v_url.replace('/', '-') + base_url = setting.global_config.get("base_url", "") + local_video_url = f"{base_url}/images/{video_path}" if base_url else f"/images/{video_path}" + content += f'' + else: + content += f'' + except Exception as e: + logger.warning(f"[Processor] 缓存视频失败: {e}") + content += f'' yield make_chunk(content) chunk_index += 1 @@ -238,16 +248,16 @@ class GrokResponseProcessor: # 获取token token = grok_resp.get("token", "") - # 图片模式 + # 提取图片数据 if is_image: if model_resp := grok_resp.get("modelResponse"): # 生成图片链接并缓存 content = "" for img in model_resp.get("generatedImageUrls", []): try: - # 异步下载并缓存图片 + # 缓存图片 await image_cache_service.download_image(f"/{img}", auth_token) - # 使用本地缓存路径 + # 本地图片路径 img_path = img.replace('/', '-') base_url = setting.global_config.get("base_url", "") img_url = f"{base_url}/images/{img_path}" if base_url else f"/images/{img_path}" @@ -261,7 +271,7 @@ class GrokResponseProcessor: yield make_chunk(token) chunk_index += 1 - # 对话模式 + # 提取对话数据 else: # 过滤 list 格式的 token if isinstance(token, list): @@ -275,7 +285,7 @@ class GrokResponseProcessor: current_is_thinking = grok_resp.get("isThinking", False) message_tag = grok_resp.get("messageTag") - # 跳过后续的 thinking + # 跳过后续的 标签 if thinking_finished and current_is_thinking: continue @@ -283,7 +293,7 @@ class GrokResponseProcessor: if grok_resp.get("toolUsageCardId"): if web_search := grok_resp.get("webSearchResults"): if current_is_thinking: - # 添加搜索结果到 token + # 封装搜索结果 for result in web_search.get("results", []): title = result.get("title", "") url = result.get("url", "") @@ -323,27 +333,8 @@ class GrokResponseProcessor: logger.warning(f"[Processor] 处理chunk出错: {e}") continue - # 处理视频生成完成后的输出 - if is_video and video_url: - full_video_url = f"https://assets.grok.com/{video_url}" - try: - # 下载并缓存视频 - cache_path = await video_cache_service.download_video(f"/{video_url}", auth_token) - if cache_path: - video_path = video_url.replace('/', '-') - base_url = setting.global_config.get("base_url", "") - local_video_url = f"{base_url}/images/{video_path}" if base_url else f"/images/{video_path}" - content = f'' - else: - content = f'' - except Exception as e: - logger.warning(f"[Processor] 缓存视频失败: {e}") - content = f'' - - yield make_chunk(content, "stop") - else: - # 发送结束块 - yield make_chunk("", "stop") + # 发送结束块 + yield make_chunk("", "stop") # 发送流结束标记 yield "data: [DONE]\n\n"