Gem2a / app /core /gemini_client_manager.py
misonL's picture
Feat: Adapt Gemini chat history and improve session cleanup
a43b63a
Raw
History Blame Contribute Delete
7.98 kB
# app/core/gemini_client_manager.py
import os # 导入 os 模块用于读取环境变量
import itertools # 导入 itertools 模块用于循环 cookie
import logging
from typing import Dict, Any, List, Optional, Tuple
from gemini_webapi import GeminiClient
import gemini_webapi.exceptions # 导入 gemini_webapi 的异常模块
import asyncio # 导入 asyncio 模块用于异步操作
from app.core.session_manager import chat_sessions # 导入 chat_sessions
logger = logging.getLogger(__name__)
# 新的环境变量读取方式
GEMINI_PSID_COOKIES_STR = os.getenv("GEMINI_PSID_COOKIES", "")
GEMINI_PSIDTS_COOKIES_STR = os.getenv("GEMINI_PSIDTS_COOKIES", "")
raw_psid_cookies = [c.strip() for c in GEMINI_PSID_COOKIES_STR.split(",") if c.strip()]
raw_psidts_cookies = [
c.strip() for c in GEMINI_PSIDTS_COOKIES_STR.split(",") if c.strip()
]
gemini_cookie_pairs: List[Tuple[str, str]] = []
if raw_psid_cookies and raw_psidts_cookies:
if len(raw_psid_cookies) == len(raw_psidts_cookies):
valid_pairs = []
for sid, sidts in zip(raw_psid_cookies, raw_psidts_cookies):
if sid and sidts: # Both must be non-empty
valid_pairs.append((sid, sidts))
else:
logger.warning(
f"检测到无效的 Cookie 对 (一个或两个值为空): SID='{sid}', SIDTS='{sidts}'. 此对将被忽略。"
)
gemini_cookie_pairs = valid_pairs
if gemini_cookie_pairs:
logger.info(
f"成功加载并验证 {len(gemini_cookie_pairs)} 组 Gemini Cookie 对。"
)
else:
logger.warning(
"所有提供的 Cookie 对均无效 (一个或两个值为空) 或 GEMINI_PSID_COOKIES / GEMINI_PSIDTS_COOKIES 列表长度匹配但内容无效。"
)
else:
logger.warning(
"GEMINI_PSID_COOKIES 和 GEMINI_PSIDTS_COOKIES 环境变量中的 Cookie 数量不匹配。请检查配置。"
)
else:
if not raw_psid_cookies and not raw_psidts_cookies:
logger.warning(
"GEMINI_PSID_COOKIES 和 GEMINI_PSIDTS_COOKIES 环境变量均未设置或为空。Gemini API 调用可能会失败。"
)
elif not raw_psid_cookies:
logger.warning(
"GEMINI_PSID_COOKIES 环境变量未设置或为空,但 GEMINI_PSIDTS_COOKIES 可能已设置。Cookie 对不完整或无效。"
)
else: # not raw_psidts_cookies
logger.warning(
"GEMINI_PSIDTS_COOKIES 环境变量未设置或为空,但 GEMINI_PSID_COOKIES 可能已设置。Cookie 对不完整或无效。"
)
# 全局变量,用于存储 cookie 轮询器和客户端实例
cookie_cycler = itertools.cycle(gemini_cookie_pairs) if gemini_cookie_pairs else None
gemini_clients: Dict[str, GeminiClient] = {} # 存储 {auth_token: GeminiClient实例}
def reload_gemini_cookies():
"""
重新加载 Gemini Cookies 环境变量并重新初始化 cookie 轮询器。
此函数应在需要刷新 cookies 时调用。
"""
global gemini_cookie_pairs, cookie_cycler, gemini_clients # 声明为全局变量以便修改
logger.info("开始重新加载 Gemini Cookies...")
# 重新读取环境变量
new_psid_cookies_str = os.getenv("GEMINI_PSID_COOKIES", "")
new_psidts_cookies_str = os.getenv("GEMINI_PSIDTS_COOKIES", "")
new_raw_psid_cookies = [c.strip() for c in new_psid_cookies_str.split(",") if c.strip()]
new_raw_psidts_cookies = [c.strip() for c in new_psidts_cookies_str.split(",") if c.strip()]
new_gemini_cookie_pairs = []
if new_raw_psid_cookies and new_raw_psidts_cookies:
if len(new_raw_psid_cookies) == len(new_raw_psidts_cookies):
valid_pairs = []
for sid, sidts in zip(new_raw_psid_cookies, new_raw_psidts_cookies):
if sid and sidts:
valid_pairs.append((sid, sidts))
else:
logger.warning(
f"重新加载时检测到无效的 Cookie 对 (一个或两个值为空): SID='{sid}', SIDTS='{sidts}'. 此对将被忽略。"
)
new_gemini_cookie_pairs = valid_pairs
if new_gemini_cookie_pairs:
logger.info(
f"成功重新加载并验证 {len(new_gemini_cookie_pairs)} 组 Gemini Cookie 对。"
)
else:
logger.warning(
"重新加载时所有提供的 Cookie 对均无效或列表长度匹配但内容无效。"
)
else:
logger.warning(
"重新加载时 GEMINI_PSID_COOKIES 和 GEMINI_PSIDTS_COOKIES 环境变量中的 Cookie 数量不匹配。请检查配置。"
)
else:
logger.warning(
"重新加载时 GEMINI_PSID_COOKIES 和 GEMINI_PSIDTS_COOKIES 环境变量均未设置或为空。Gemini API 调用可能会失败。"
)
gemini_cookie_pairs = new_gemini_cookie_pairs
cookie_cycler = itertools.cycle(gemini_cookie_pairs) if gemini_cookie_pairs else None
# 清空所有现有的 GeminiClient 实例,强制下次请求时重新初始化
gemini_clients.clear()
logger.info("所有现有 GeminiClient 实例已清空,将在下次请求时重新初始化。")
# 遍历所有会话,清空其 _chat_instance,强制重新初始化聊天实例
for auth_token in chat_sessions:
for session_id, session in chat_sessions[auth_token].items():
if session._chat_instance:
session._chat_instance = None
logger.info(f"会话 {session_id} 的聊天实例已清空。")
logger.info("Gemini Cookies 重新加载完成。")
def get_next_gemini_cookie_tuple() -> Optional[Tuple[str, str]]:
"""获取下一组可用的 Gemini Cookie (SID, SIDTS) 元组。"""
if not cookie_cycler:
logger.error(
"Gemini Cookie 轮询器未初始化或为空。请检查 GEMINI_PSID_COOKIES 和 GEMINI_PSIDTS_COOKIES 环境变量。"
)
return None
try:
return next(cookie_cycler)
except StopIteration:
logger.error(
"Gemini Cookie 轮询器意外耗尽。这可能表明初始化的 gemini_cookie_pairs 为空或所有元素均无效。"
)
return None
except Exception as e:
logger.error(f"获取下一组 Gemini Cookie 时发生意外错误: {e}", exc_info=True)
return None
async def get_gemini_client(auth_token: str) -> GeminiClient:
"""
获取或初始化与给定认证令牌关联的 GeminiClient 实例。
如果客户端不存在,则创建一个新的。
"""
client = gemini_clients.get(auth_token)
if client:
logger.info(f"认证令牌 {auth_token} 复用现有 GeminiClient 实例。")
return client
else:
logger.info(f"认证令牌 {auth_token} 创建新的 GeminiClient 实例。")
cookie_tuple = get_next_gemini_cookie_tuple()
if not cookie_tuple:
logger.error("未配置有效的 Gemini Cookies。请检查环境变量。")
raise Exception("服务端未配置有效的 Gemini Cookies,无法连接到 Gemini API。")
sid, sidts = cookie_tuple
try:
client = GeminiClient(sid, sidts)
await client.init(timeout=30) # 设置初始化超时时间
gemini_clients[auth_token] = client # 存储新的客户端实例
logger.info(f"认证令牌 {auth_token} 成功初始化并存储 GeminiClient。")
return client
except Exception as client_init_error:
logger.error(f"认证令牌 {auth_token} 初始化 GeminiClient 失败。")
raise Exception("初始化 Gemini 客户端失败。请检查 GEMINI_PSID_COOKIES 和 GEMINI_PSIDTS_COOKIES 是否有效。")
# 在应用启动时调用一次,确保初始加载
reload_gemini_cookies()