feat: compatible with Windows system
This commit is contained in:
1 parent
7ea8b7c19a
commit
8fb9d04ff6
4 files changed
+126
-88
No files matched your search
+91
-75
@@ -196,86 +196,102 @@ class GrokClient:
|
||||
if not token:
|
||||
raise GrokApiException("认证令牌缺失", "NO_AUTH_TOKEN")
|
||||
|
||||
# 403重试配置
|
||||
max_retries = 5
|
||||
retry_count = 0
|
||||
# 外层重试:可配置状态码(401/429等)
|
||||
retry_codes = setting.grok_config.get("retry_status_codes", [401, 429])
|
||||
MAX_OUTER_RETRY = 3
|
||||
|
||||
while retry_count <= max_retries:
|
||||
try:
|
||||
# 构建请求
|
||||
headers = GrokClient._build_headers(token)
|
||||
if model == "grok-imagine-0.9":
|
||||
file_attachments = payload.get("fileAttachments", [])
|
||||
ref_id = post_id or (file_attachments[0] if file_attachments else "")
|
||||
if ref_id:
|
||||
headers["Referer"] = f"https://grok.com/imagine/{ref_id}"
|
||||
|
||||
# 异步获取代理
|
||||
from app.core.proxy_pool import proxy_pool
|
||||
|
||||
# 如果是重试且使用代理池,强制刷新代理
|
||||
if retry_count > 0 and proxy_pool._enabled:
|
||||
logger.info(f"[Client] 403重试 {retry_count}/{max_retries},刷新代理...")
|
||||
proxy = await proxy_pool.force_refresh()
|
||||
else:
|
||||
proxy = await setting.get_proxy_async("service")
|
||||
|
||||
proxies = {"http": proxy, "https": proxy} if proxy else None
|
||||
|
||||
# 执行请求
|
||||
response = await asyncio.to_thread(
|
||||
curl_requests.post,
|
||||
API_ENDPOINT,
|
||||
headers=headers,
|
||||
data=orjson.dumps(payload),
|
||||
impersonate=BROWSER,
|
||||
timeout=TIMEOUT,
|
||||
stream=True,
|
||||
proxies=proxies
|
||||
)
|
||||
|
||||
# 内层403重试:仅当有代理池时触发
|
||||
if response.status_code == 403 and proxy_pool._enabled:
|
||||
retry_count += 1
|
||||
for outer_retry in range(MAX_OUTER_RETRY + 1): # +1确保实际重试3次
|
||||
# 内层重试:403代理池重试
|
||||
max_403_retries = 5
|
||||
retry_403_count = 0
|
||||
|
||||
while retry_403_count <= max_403_retries:
|
||||
try:
|
||||
# 构建请求
|
||||
headers = GrokClient._build_headers(token)
|
||||
if model == "grok-imagine-0.9":
|
||||
file_attachments = payload.get("fileAttachments", [])
|
||||
ref_id = post_id or (file_attachments[0] if file_attachments else "")
|
||||
if ref_id:
|
||||
headers["Referer"] = f"https://grok.com/imagine/{ref_id}"
|
||||
|
||||
if retry_count <= max_retries:
|
||||
logger.warning(f"[Client] 遇到403错误,正在重试 ({retry_count}/{max_retries})...")
|
||||
await asyncio.sleep(0.5)
|
||||
continue
|
||||
# 异步获取代理
|
||||
from app.core.proxy_pool import proxy_pool
|
||||
|
||||
# 内层重试全部失败
|
||||
logger.error(f"[Client] 403错误,已重试{retry_count-1}次,放弃")
|
||||
|
||||
# 检查响应状态
|
||||
if response.status_code != 200:
|
||||
GrokClient._handle_error(response, token)
|
||||
|
||||
# 成功 - 重置失败计数
|
||||
asyncio.create_task(token_manager.reset_failure(token))
|
||||
|
||||
# 如果是重试成功,记录日志
|
||||
if retry_count > 0:
|
||||
logger.info(f"[Client] 重试成功!第{retry_count}次重试后请求成功")
|
||||
|
||||
# 处理响应
|
||||
result = (GrokResponseProcessor.process_stream(response, token) if stream
|
||||
else await GrokResponseProcessor.process_normal(response, token, model))
|
||||
|
||||
asyncio.create_task(GrokClient._update_limits(token, model))
|
||||
return result
|
||||
|
||||
except curl_requests.RequestsError as e:
|
||||
logger.error(f"[Client] 网络错误: {e}")
|
||||
raise GrokApiException(f"网络错误: {e}", "NETWORK_ERROR") from e
|
||||
except GrokApiException:
|
||||
# 重新抛出GrokApiException(包括403错误)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"[Client] 请求错误: {e}")
|
||||
raise GrokApiException(f"请求错误: {e}", "REQUEST_ERROR") from e
|
||||
# 如果是403重试且使用代理池,强制刷新代理
|
||||
if retry_403_count > 0 and proxy_pool._enabled:
|
||||
logger.info(f"[Client] 403重试 {retry_403_count}/{max_403_retries},刷新代理...")
|
||||
proxy = await proxy_pool.force_refresh()
|
||||
else:
|
||||
proxy = await setting.get_proxy_async("service")
|
||||
|
||||
proxies = {"http": proxy, "https": proxy} if proxy else None
|
||||
|
||||
# 执行请求
|
||||
response = await asyncio.to_thread(
|
||||
curl_requests.post,
|
||||
API_ENDPOINT,
|
||||
headers=headers,
|
||||
data=orjson.dumps(payload),
|
||||
impersonate=BROWSER,
|
||||
timeout=TIMEOUT,
|
||||
stream=True,
|
||||
proxies=proxies
|
||||
)
|
||||
|
||||
# 内层403重试:仅当有代理池时触发
|
||||
if response.status_code == 403 and proxy_pool._enabled:
|
||||
retry_403_count += 1
|
||||
|
||||
if retry_403_count <= max_403_retries:
|
||||
logger.warning(f"[Client] 遇到403错误,正在重试 ({retry_403_count}/{max_403_retries})...")
|
||||
await asyncio.sleep(0.5)
|
||||
continue
|
||||
|
||||
# 内层重试全部失败
|
||||
logger.error(f"[Client] 403错误,已重试{retry_403_count-1}次,放弃")
|
||||
|
||||
# 检查可配置状态码错误 - 外层重试
|
||||
if response.status_code in retry_codes:
|
||||
if outer_retry < MAX_OUTER_RETRY:
|
||||
delay = (outer_retry + 1) * 0.1 # 渐进延迟:0.1s, 0.2s, 0.3s
|
||||
logger.warning(f"[Client] 遇到{response.status_code}错误,外层重试 ({outer_retry+1}/{MAX_OUTER_RETRY}),等待{delay}s...")
|
||||
await asyncio.sleep(delay)
|
||||
break # 跳出内层循环,进入外层重试
|
||||
else:
|
||||
logger.error(f"[Client] {response.status_code}错误,已重试{outer_retry}次,放弃")
|
||||
GrokClient._handle_error(response, token)
|
||||
|
||||
# 检查响应状态
|
||||
if response.status_code != 200:
|
||||
GrokClient._handle_error(response, token)
|
||||
|
||||
# 成功 - 重置失败计数
|
||||
asyncio.create_task(token_manager.reset_failure(token))
|
||||
|
||||
# 如果是重试成功,记录日志
|
||||
if outer_retry > 0 or retry_403_count > 0:
|
||||
logger.info(f"[Client] 重试成功!")
|
||||
|
||||
# 处理响应
|
||||
result = (GrokResponseProcessor.process_stream(response, token) if stream
|
||||
else await GrokResponseProcessor.process_normal(response, token, model))
|
||||
|
||||
asyncio.create_task(GrokClient._update_limits(token, model))
|
||||
return result
|
||||
|
||||
except curl_requests.RequestsError as e:
|
||||
logger.error(f"[Client] 网络错误: {e}")
|
||||
raise GrokApiException(f"网络错误: {e}", "NETWORK_ERROR") from e
|
||||
except GrokApiException:
|
||||
# 重新抛出GrokApiException(包括403错误)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"[Client] 请求错误: {e}")
|
||||
raise GrokApiException(f"请求错误: {e}", "REQUEST_ERROR") from e
|
||||
|
||||
# 理论上不应该到这里,但以防万一
|
||||
raise GrokApiException("403错误:已达到最大重试次数", "MAX_RETRIES_EXCEEDED")
|
||||
raise GrokApiException("请求失败:已达到最大重试次数", "MAX_RETRIES_EXCEEDED")
|
||||
|
||||
@staticmethod
|
||||
def _build_headers(token: str) -> Dict[str, str]:
|
||||
|
||||
@@ -109,7 +109,7 @@ class GrokTokenManager:
|
||||
from app.core.config import setting
|
||||
|
||||
interval = setting.global_config.get("batch_save_interval", 1.0)
|
||||
logger.info(f"[Token] 批量保存任务已启动,间隔: {interval}s")
|
||||
logger.info(f"[Token] 存储任务已启动,间隔: {interval}s")
|
||||
|
||||
while not self._shutdown:
|
||||
await asyncio.sleep(interval)
|
||||
@@ -118,15 +118,15 @@ class GrokTokenManager:
|
||||
try:
|
||||
await self._save_data()
|
||||
self._save_pending = False
|
||||
logger.debug("[Token] 批量保存完成")
|
||||
logger.debug("[Token] 存储完成")
|
||||
except Exception as e:
|
||||
logger.error(f"[Token] 批量保存失败: {e}")
|
||||
logger.error(f"[Token] 存储失败: {e}")
|
||||
|
||||
async def start_batch_save(self) -> None:
|
||||
"""启动批量保存任务"""
|
||||
if self._save_task is None:
|
||||
self._save_task = asyncio.create_task(self._batch_save_worker())
|
||||
logger.info("[Token] 批量保存任务已创建")
|
||||
logger.info("[Token] 存储任务已创建")
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""关闭并刷新所有待保存数据"""
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
"""FastAPI应用主入口"""
|
||||
"""Grok2API"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from app.core.logger import logger
|
||||
from app.core.exception import register_exception_handlers
|
||||
@@ -12,11 +17,20 @@ from app.api.v1.chat import router as chat_router
|
||||
from app.api.v1.models import router as models_router
|
||||
from app.api.v1.images import router as images_router
|
||||
from app.api.admin.manage import router as admin_router
|
||||
|
||||
# 导入MCP服务器(认证配置在server.py中完成)
|
||||
from app.services.mcp import mcp
|
||||
|
||||
# 创建MCP的FastAPI应用实例
|
||||
# 0. 兼容性检测
|
||||
try:
|
||||
if sys.platform != 'win32':
|
||||
import uvloop
|
||||
uvloop.install()
|
||||
logger.info("[Grok2API] 启用uvloop高性能事件循环")
|
||||
else:
|
||||
logger.info("[Grok2API] Windows系统,使用默认asyncio事件循环")
|
||||
except ImportError:
|
||||
logger.info("[Grok2API] uvloop未安装,使用默认asyncio事件循环")
|
||||
|
||||
# 1. 创建MCP的FastAPI应用实例
|
||||
# 使用流式HTTP传输,支持高效的双向流式通信
|
||||
mcp_app = mcp.http_app(stateless_http=True, transport="streamable-http")
|
||||
|
||||
@@ -133,7 +147,7 @@ if __name__ == "__main__":
|
||||
import uvicorn
|
||||
import os
|
||||
|
||||
# 从环境变量读取 worker 数量,默认为 1
|
||||
# 读取 worker 数量,默认为 1
|
||||
workers = int(os.getenv("WORKERS", "1"))
|
||||
|
||||
# 提示多进程模式
|
||||
@@ -143,10 +157,19 @@ if __name__ == "__main__":
|
||||
f"建议使用 Redis/MySQL 存储以获得最佳性能。"
|
||||
)
|
||||
|
||||
# 确定事件循环类型
|
||||
loop_type = "auto"
|
||||
if workers == 1 and sys.platform != 'win32':
|
||||
try:
|
||||
import uvloop
|
||||
loop_type = "uvloop"
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
uvicorn.run(
|
||||
"main:app", # 使用字符串以支持多worker
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8001,
|
||||
workers=workers,
|
||||
loop="uvloop" if workers == 1 else "auto" # 单进程用uvloop,多进程用auto
|
||||
loop=loop_type
|
||||
)
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
|
||||
toml==0.10.2
|
||||
fastapi==0.119.0
|
||||
uvicorn==0.37.0
|
||||
uvloop==0.21.0
|
||||
uvloop==0.21.0; sys_platform != 'win32'
|
||||
python-dotenv==1.1.1
|
||||
curl_cffi==0.13.0
|
||||
requests==2.32.5
|
||||
|
||||
Reference in new issue
Block a user