Spaces:
Running
Running
Upload 11 files
Browse files- .env.example +21 -56
- .gitignore +1 -0
- README.md +1 -1
- main.py +202 -27
- requirements.txt +2 -1
.env.example
CHANGED
|
@@ -2,69 +2,34 @@
|
|
| 2 |
# Gemini Business2API 配置示例
|
| 3 |
# ============================================
|
| 4 |
|
| 5 |
-
#
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
# API 访问密钥(可选,用于保护 API 端点)
|
| 9 |
-
# API_KEY=your-secret-api-key-here
|
| 10 |
|
| 11 |
-
#
|
| 12 |
# 如果设置了,端点将为: /{PATH_PREFIX}/, /{PATH_PREFIX}/v1/
|
| 13 |
# 如果未设置,端点将为: /, /v1/
|
| 14 |
# PATH_PREFIX=your-random-path-prefix
|
| 15 |
|
| 16 |
-
# 管理员密钥(必需,用于登录管理面板)
|
| 17 |
-
ADMIN_KEY=your-admin-secret-key
|
| 18 |
-
|
| 19 |
-
# Session加密密钥(可选,自动生成)
|
| 20 |
-
# SESSION_SECRET_KEY=your-session-secret-key
|
| 21 |
-
|
| 22 |
-
# Session过期时间(可选,单位:小时,默认24小时)
|
| 23 |
-
# SESSION_EXPIRE_HOURS=24
|
| 24 |
-
|
| 25 |
-
# 服务器完整 URL(可选,用于反代公开日志和图片 URL)
|
| 26 |
-
# BASE_URL=https://your-domain.com
|
| 27 |
-
|
| 28 |
# ============================================
|
| 29 |
-
#
|
|
|
|
|
|
|
| 30 |
# ============================================
|
| 31 |
|
| 32 |
-
# 新会话创建最多尝试账户数(默认:5)
|
| 33 |
-
# MAX_NEW_SESSION_TRIES=5
|
| 34 |
-
|
| 35 |
-
# 请求失败最多重试次数(默认:3)
|
| 36 |
-
# MAX_REQUEST_RETRIES=3
|
| 37 |
-
|
| 38 |
-
# 每次重试找账户的最大尝试次数(默认:5)
|
| 39 |
-
# MAX_ACCOUNT_SWITCH_TRIES=5
|
| 40 |
-
|
| 41 |
-
# 账户连续失败阈值(默认:3次)
|
| 42 |
-
# ACCOUNT_FAILURE_THRESHOLD=3
|
| 43 |
-
|
| 44 |
-
# 429限流错误冷却时间,单位秒(默认:600秒=10分钟)
|
| 45 |
-
# RATE_LIMIT_COOLDOWN_SECONDS=600
|
| 46 |
-
|
| 47 |
-
# 会话缓存过期时间,单位秒(默认:3600秒=1小时)
|
| 48 |
-
# SESSION_CACHE_TTL_SECONDS=3600
|
| 49 |
-
|
| 50 |
-
# ============================================
|
| 51 |
-
# 公开展示配置(可选)
|
| 52 |
# ============================================
|
| 53 |
-
#
|
| 54 |
-
# LOGO_URL=https://your-domain.com/logo.png
|
| 55 |
-
|
| 56 |
-
# 开始对话链接(公开,为空则不显示)
|
| 57 |
-
# CHAT_URL=https://your-chat-app.com
|
| 58 |
-
|
| 59 |
-
# 模型名称(公开,默认:gemini-business)
|
| 60 |
-
# MODEL_NAME=gemini-business
|
| 61 |
-
|
| 62 |
# ============================================
|
| 63 |
-
#
|
| 64 |
-
#
|
| 65 |
-
#
|
| 66 |
-
#
|
| 67 |
-
#
|
| 68 |
-
#
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
# Gemini Business2API 配置示例
|
| 3 |
# ============================================
|
| 4 |
|
| 5 |
+
# 管理员密钥(必需,用于登录管理面板)
|
| 6 |
+
ADMIN_KEY=your-admin-secret-key
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
# 路径前缀(可选,用于隐藏端点路径,提高安全性)
|
| 9 |
# 如果设置了,端点将为: /{PATH_PREFIX}/, /{PATH_PREFIX}/v1/
|
| 10 |
# 如果未设置,端点将为: /, /v1/
|
| 11 |
# PATH_PREFIX=your-random-path-prefix
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
# ============================================
|
| 14 |
+
# 其他配置请在管理面板的"系统设置"中配置
|
| 15 |
+
# 包括:API密钥、代理、图片生成、重试策略等
|
| 16 |
+
# 配置保存在 data/settings.yaml
|
| 17 |
# ============================================
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
# ============================================
|
| 20 |
+
# 账户配置
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# ============================================
|
| 22 |
+
# 账户配置保存在 accounts.json 文件中
|
| 23 |
+
# 首次启动时会自动创建空配置
|
| 24 |
+
# 请在管理面板中添加账户,或直接编辑 accounts.json
|
| 25 |
+
#
|
| 26 |
+
# 账户配置格式示例:
|
| 27 |
+
# [
|
| 28 |
+
# {
|
| 29 |
+
# "id": "account_1",
|
| 30 |
+
# "secure_c_ses": "your-cookie-here",
|
| 31 |
+
# "csesidx": "your-idx",
|
| 32 |
+
# "config_id": "your-config",
|
| 33 |
+
# "expires_at": "2025-12-31 23:59:59"
|
| 34 |
+
# }
|
| 35 |
+
# ]
|
.gitignore
CHANGED
|
@@ -36,6 +36,7 @@ ENV/
|
|
| 36 |
.env
|
| 37 |
*.log
|
| 38 |
data/stats.json
|
|
|
|
| 39 |
accounts.json
|
| 40 |
|
| 41 |
# Generated files
|
|
|
|
| 36 |
.env
|
| 37 |
*.log
|
| 38 |
data/stats.json
|
| 39 |
+
data/settings.yaml
|
| 40 |
accounts.json
|
| 41 |
|
| 42 |
# Generated files
|
README.md
CHANGED
|
@@ -624,7 +624,7 @@ Deno.serve(handler);
|
|
| 624 |
### 7. API_KEY 和 ADMIN_KEY 的区别?
|
| 625 |
|
| 626 |
- **API_KEY**: 保护聊天接口 (`/v1/chat/completions`)
|
| 627 |
-
- **ADMIN_KEY**: 保护管理面板 (`/
|
| 628 |
|
| 629 |
可以设置相同的值,也可以分开
|
| 630 |
|
|
|
|
| 624 |
### 7. API_KEY 和 ADMIN_KEY 的区别?
|
| 625 |
|
| 626 |
- **API_KEY**: 保护聊天接口 (`/v1/chat/completions`)
|
| 627 |
+
- **ADMIN_KEY**: 保护管理面板 (`/` 或 `/{PATH_PREFIX}`)
|
| 628 |
|
| 629 |
可以设置相同的值,也可以分开
|
| 630 |
|
main.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
-
import json, time, os, asyncio, uuid, ssl, re
|
| 2 |
from datetime import datetime, timezone, timedelta
|
| 3 |
from typing import List, Optional, Union, Dict, Any
|
|
|
|
| 4 |
import logging
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
|
|
@@ -118,20 +119,87 @@ memory_handler.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s | %(m
|
|
| 118 |
logger.addHandler(memory_handler)
|
| 119 |
|
| 120 |
load_dotenv()
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
TIMEOUT_SECONDS = 600
|
| 124 |
-
API_KEY
|
| 125 |
-
PATH_PREFIX
|
| 126 |
-
ADMIN_KEY
|
| 127 |
-
BASE_URL
|
| 128 |
-
SESSION_SECRET_KEY = os.getenv("SESSION_SECRET_KEY", generate_session_secret()) #
|
| 129 |
-
SESSION_EXPIRE_HOURS =
|
| 130 |
|
| 131 |
# ---------- 公开展示配置 ----------
|
| 132 |
-
LOGO_URL
|
| 133 |
-
CHAT_URL
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
# ---------- 图片存储配置 ----------
|
| 137 |
if os.path.exists("/data"):
|
|
@@ -139,13 +207,13 @@ if os.path.exists("/data"):
|
|
| 139 |
else:
|
| 140 |
IMAGE_DIR = "./data/images" # 本地持久化存储
|
| 141 |
|
| 142 |
-
# ----------
|
| 143 |
-
MAX_NEW_SESSION_TRIES =
|
| 144 |
-
MAX_REQUEST_RETRIES =
|
| 145 |
-
MAX_ACCOUNT_SWITCH_TRIES =
|
| 146 |
-
ACCOUNT_FAILURE_THRESHOLD =
|
| 147 |
-
RATE_LIMIT_COOLDOWN_SECONDS =
|
| 148 |
-
SESSION_CACHE_TTL_SECONDS =
|
| 149 |
|
| 150 |
# ---------- 模型映射配置 ----------
|
| 151 |
MODEL_MAPPING = {
|
|
@@ -552,7 +620,7 @@ async def home(request: Request):
|
|
| 552 |
else:
|
| 553 |
# 未设置PATH_PREFIX(公开模式),根据登录状态重定向
|
| 554 |
if is_logged_in(request):
|
| 555 |
-
return
|
| 556 |
else:
|
| 557 |
return RedirectResponse(url="/login", status_code=302)
|
| 558 |
|
|
@@ -736,6 +804,98 @@ async def admin_enable_account(request: Request, account_id: str):
|
|
| 736 |
logger.error(f"[CONFIG] 启用账户失败: {str(e)}")
|
| 737 |
raise HTTPException(500, f"启用失败: {str(e)}")
|
| 738 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
@app.get("/admin/log")
|
| 740 |
@require_login()
|
| 741 |
async def admin_get_logs(
|
|
@@ -861,6 +1021,16 @@ if PATH_PREFIX:
|
|
| 861 |
async def admin_logs_html_route_prefixed(request: Request):
|
| 862 |
return await admin_logs_html_route(request=request)
|
| 863 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 864 |
# ---------- API端点(API Key认证) ----------
|
| 865 |
|
| 866 |
@app.get("/v1/models")
|
|
@@ -943,7 +1113,7 @@ async def chat_impl(
|
|
| 943 |
request.state.model = req.model
|
| 944 |
|
| 945 |
# 3. 生成会话指纹,获取Session锁(防止同一对话的并发请求冲突)
|
| 946 |
-
conv_key = get_conversation_key([m.
|
| 947 |
session_lock = await multi_account_mgr.acquire_session_lock(conv_key)
|
| 948 |
|
| 949 |
# 4. 在锁的保护下检查缓存和处理Session(保证同一对话的请求串行化)
|
|
@@ -1286,6 +1456,16 @@ async def stream_chat_generator(session: str, text_content: str, file_ids: List[
|
|
| 1286 |
jwt = await account_manager.get_jwt(request_id)
|
| 1287 |
headers = get_common_headers(jwt, USER_AGENT)
|
| 1288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1289 |
body = {
|
| 1290 |
"configId": account_manager.config.config_id,
|
| 1291 |
"additionalParams": {"token": "-"},
|
|
@@ -1295,12 +1475,7 @@ async def stream_chat_generator(session: str, text_content: str, file_ids: List[
|
|
| 1295 |
"filter": "",
|
| 1296 |
"fileIds": file_ids, # 注入文件 ID
|
| 1297 |
"answerGenerationMode": "NORMAL",
|
| 1298 |
-
"toolsSpec":
|
| 1299 |
-
"webGroundingSpec": {},
|
| 1300 |
-
"toolRegistry": "default_tool_registry",
|
| 1301 |
-
"imageGenerationSpec": {},
|
| 1302 |
-
"videoGenerationSpec": {}
|
| 1303 |
-
},
|
| 1304 |
"languageCode": "zh-CN",
|
| 1305 |
"userMetadata": {"timeZone": "Asia/Shanghai"},
|
| 1306 |
"assistSkippingMode": "REQUEST_ASSIST"
|
|
|
|
| 1 |
+
import json, time, os, asyncio, uuid, ssl, re, yaml
|
| 2 |
from datetime import datetime, timezone, timedelta
|
| 3 |
from typing import List, Optional, Union, Dict, Any
|
| 4 |
+
from pathlib import Path
|
| 5 |
import logging
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
|
|
|
| 119 |
logger.addHandler(memory_handler)
|
| 120 |
|
| 121 |
load_dotenv()
|
| 122 |
+
|
| 123 |
+
# ---------- YAML 配置系统 ----------
|
| 124 |
+
SETTINGS_FILE = "data/settings.yaml"
|
| 125 |
+
|
| 126 |
+
# 默认配置
|
| 127 |
+
DEFAULT_SETTINGS = {
|
| 128 |
+
"basic": {
|
| 129 |
+
"api_key": "",
|
| 130 |
+
"base_url": "",
|
| 131 |
+
"proxy": ""
|
| 132 |
+
},
|
| 133 |
+
"image_generation": {
|
| 134 |
+
"enabled": True,
|
| 135 |
+
"supported_models": ["gemini-3-pro-preview"],
|
| 136 |
+
"last_updated": None
|
| 137 |
+
},
|
| 138 |
+
"retry": {
|
| 139 |
+
"max_new_session_tries": 5,
|
| 140 |
+
"max_request_retries": 3,
|
| 141 |
+
"max_account_switch_tries": 5,
|
| 142 |
+
"account_failure_threshold": 3,
|
| 143 |
+
"rate_limit_cooldown_seconds": 600,
|
| 144 |
+
"session_cache_ttl_seconds": 3600
|
| 145 |
+
},
|
| 146 |
+
"public_display": {
|
| 147 |
+
"logo_url": "",
|
| 148 |
+
"chat_url": ""
|
| 149 |
+
},
|
| 150 |
+
"session": {
|
| 151 |
+
"expire_hours": 24
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
def load_settings() -> dict:
|
| 156 |
+
"""加载 YAML 配置"""
|
| 157 |
+
Path("data").mkdir(exist_ok=True)
|
| 158 |
+
if Path(SETTINGS_FILE).exists():
|
| 159 |
+
try:
|
| 160 |
+
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
| 161 |
+
settings = yaml.safe_load(f) or {}
|
| 162 |
+
# 合并默认配置(确保新增配置项有默认值)
|
| 163 |
+
for key, value in DEFAULT_SETTINGS.items():
|
| 164 |
+
if key not in settings:
|
| 165 |
+
settings[key] = value
|
| 166 |
+
elif isinstance(value, dict):
|
| 167 |
+
for k, v in value.items():
|
| 168 |
+
if k not in settings[key]:
|
| 169 |
+
settings[key][k] = v
|
| 170 |
+
return settings
|
| 171 |
+
except Exception as e:
|
| 172 |
+
print(f"[WARN] 加载配置文件失败: {e},使用默认配置")
|
| 173 |
+
# 创建默认配置文件
|
| 174 |
+
save_settings(DEFAULT_SETTINGS)
|
| 175 |
+
return DEFAULT_SETTINGS.copy()
|
| 176 |
+
|
| 177 |
+
def save_settings(settings: dict):
|
| 178 |
+
"""保存 YAML 配置"""
|
| 179 |
+
Path("data").mkdir(exist_ok=True)
|
| 180 |
+
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
| 181 |
+
yaml.dump(settings, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
| 182 |
+
|
| 183 |
+
# 加载配置
|
| 184 |
+
settings = load_settings()
|
| 185 |
+
|
| 186 |
+
# ---------- 配置(从 settings.yaml 读取)----------
|
| 187 |
+
PROXY = settings["basic"]["proxy"]
|
| 188 |
TIMEOUT_SECONDS = 600
|
| 189 |
+
API_KEY = settings["basic"]["api_key"]
|
| 190 |
+
PATH_PREFIX = os.getenv("PATH_PREFIX", "") # 路径前缀保留环境变量(影响路由注册)
|
| 191 |
+
ADMIN_KEY = os.getenv("ADMIN_KEY", "") # 管理员密钥保留环境变量(安全相关)
|
| 192 |
+
BASE_URL = settings["basic"]["base_url"]
|
| 193 |
+
SESSION_SECRET_KEY = os.getenv("SESSION_SECRET_KEY", generate_session_secret()) # 自动生成
|
| 194 |
+
SESSION_EXPIRE_HOURS = settings["session"]["expire_hours"]
|
| 195 |
|
| 196 |
# ---------- 公开展示配置 ----------
|
| 197 |
+
LOGO_URL = settings["public_display"]["logo_url"]
|
| 198 |
+
CHAT_URL = settings["public_display"]["chat_url"]
|
| 199 |
+
|
| 200 |
+
# ---------- 图片生成配置 ----------
|
| 201 |
+
IMAGE_GENERATION_ENABLED = settings["image_generation"]["enabled"]
|
| 202 |
+
IMAGE_GENERATION_MODELS = settings["image_generation"]["supported_models"]
|
| 203 |
|
| 204 |
# ---------- 图片存储配置 ----------
|
| 205 |
if os.path.exists("/data"):
|
|
|
|
| 207 |
else:
|
| 208 |
IMAGE_DIR = "./data/images" # 本地持久化存储
|
| 209 |
|
| 210 |
+
# ---------- 重试配置(从 settings.yaml 读取)----------
|
| 211 |
+
MAX_NEW_SESSION_TRIES = settings["retry"]["max_new_session_tries"]
|
| 212 |
+
MAX_REQUEST_RETRIES = settings["retry"]["max_request_retries"]
|
| 213 |
+
MAX_ACCOUNT_SWITCH_TRIES = settings["retry"]["max_account_switch_tries"]
|
| 214 |
+
ACCOUNT_FAILURE_THRESHOLD = settings["retry"]["account_failure_threshold"]
|
| 215 |
+
RATE_LIMIT_COOLDOWN_SECONDS = settings["retry"]["rate_limit_cooldown_seconds"]
|
| 216 |
+
SESSION_CACHE_TTL_SECONDS = settings["retry"]["session_cache_ttl_seconds"]
|
| 217 |
|
| 218 |
# ---------- 模型映射配置 ----------
|
| 219 |
MODEL_MAPPING = {
|
|
|
|
| 620 |
else:
|
| 621 |
# 未设置PATH_PREFIX(公开模式),根据登录状态重定向
|
| 622 |
if is_logged_in(request):
|
| 623 |
+
return HTMLResponse(content=templates.generate_admin_html(request, multi_account_mgr))
|
| 624 |
else:
|
| 625 |
return RedirectResponse(url="/login", status_code=302)
|
| 626 |
|
|
|
|
| 804 |
logger.error(f"[CONFIG] 启用账户失败: {str(e)}")
|
| 805 |
raise HTTPException(500, f"启用失败: {str(e)}")
|
| 806 |
|
| 807 |
+
# ---------- 系统设置 API ----------
|
| 808 |
+
@app.get("/admin/settings")
|
| 809 |
+
@require_login()
|
| 810 |
+
async def admin_get_settings(request: Request):
|
| 811 |
+
"""获取系统设置"""
|
| 812 |
+
return settings
|
| 813 |
+
|
| 814 |
+
@app.put("/admin/settings")
|
| 815 |
+
@require_login()
|
| 816 |
+
async def admin_update_settings(request: Request, new_settings: dict = Body(...)):
|
| 817 |
+
"""更新系统设置"""
|
| 818 |
+
global settings, API_KEY, PROXY, BASE_URL, LOGO_URL, CHAT_URL
|
| 819 |
+
global IMAGE_GENERATION_ENABLED, IMAGE_GENERATION_MODELS
|
| 820 |
+
global MAX_NEW_SESSION_TRIES, MAX_REQUEST_RETRIES, MAX_ACCOUNT_SWITCH_TRIES
|
| 821 |
+
global ACCOUNT_FAILURE_THRESHOLD, RATE_LIMIT_COOLDOWN_SECONDS, SESSION_CACHE_TTL_SECONDS
|
| 822 |
+
global SESSION_EXPIRE_HOURS, multi_account_mgr, http_client
|
| 823 |
+
|
| 824 |
+
try:
|
| 825 |
+
# 合并设置(保留未修改的项)
|
| 826 |
+
for key, value in new_settings.items():
|
| 827 |
+
if key in settings and isinstance(value, dict):
|
| 828 |
+
settings[key].update(value)
|
| 829 |
+
else:
|
| 830 |
+
settings[key] = value
|
| 831 |
+
|
| 832 |
+
# 添加更新时间
|
| 833 |
+
if "image_generation" in settings:
|
| 834 |
+
settings["image_generation"]["last_updated"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 835 |
+
|
| 836 |
+
# 保存到文件
|
| 837 |
+
save_settings(settings)
|
| 838 |
+
|
| 839 |
+
# 保存旧配置用于对比
|
| 840 |
+
old_proxy = PROXY
|
| 841 |
+
old_retry_config = {
|
| 842 |
+
"account_failure_threshold": ACCOUNT_FAILURE_THRESHOLD,
|
| 843 |
+
"rate_limit_cooldown_seconds": RATE_LIMIT_COOLDOWN_SECONDS,
|
| 844 |
+
"session_cache_ttl_seconds": SESSION_CACHE_TTL_SECONDS
|
| 845 |
+
}
|
| 846 |
+
|
| 847 |
+
# 更新全局变量(实时生效)
|
| 848 |
+
API_KEY = settings["basic"]["api_key"]
|
| 849 |
+
PROXY = settings["basic"]["proxy"]
|
| 850 |
+
BASE_URL = settings["basic"]["base_url"]
|
| 851 |
+
LOGO_URL = settings["public_display"]["logo_url"]
|
| 852 |
+
CHAT_URL = settings["public_display"]["chat_url"]
|
| 853 |
+
IMAGE_GENERATION_ENABLED = settings["image_generation"]["enabled"]
|
| 854 |
+
IMAGE_GENERATION_MODELS = settings["image_generation"]["supported_models"]
|
| 855 |
+
MAX_NEW_SESSION_TRIES = settings["retry"]["max_new_session_tries"]
|
| 856 |
+
MAX_REQUEST_RETRIES = settings["retry"]["max_request_retries"]
|
| 857 |
+
MAX_ACCOUNT_SWITCH_TRIES = settings["retry"]["max_account_switch_tries"]
|
| 858 |
+
ACCOUNT_FAILURE_THRESHOLD = settings["retry"]["account_failure_threshold"]
|
| 859 |
+
RATE_LIMIT_COOLDOWN_SECONDS = settings["retry"]["rate_limit_cooldown_seconds"]
|
| 860 |
+
SESSION_CACHE_TTL_SECONDS = settings["retry"]["session_cache_ttl_seconds"]
|
| 861 |
+
SESSION_EXPIRE_HOURS = settings["session"]["expire_hours"]
|
| 862 |
+
|
| 863 |
+
# 检查是否需要重建 HTTP 客户端(代理变化)
|
| 864 |
+
if old_proxy != PROXY:
|
| 865 |
+
logger.info(f"[CONFIG] 代理配置已变化,重建 HTTP 客户端")
|
| 866 |
+
await http_client.aclose() # 关闭旧客户端
|
| 867 |
+
http_client = httpx.AsyncClient(
|
| 868 |
+
proxy=PROXY or None,
|
| 869 |
+
verify=False,
|
| 870 |
+
http2=False,
|
| 871 |
+
timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0),
|
| 872 |
+
limits=httpx.Limits(
|
| 873 |
+
max_keepalive_connections=100,
|
| 874 |
+
max_connections=200
|
| 875 |
+
)
|
| 876 |
+
)
|
| 877 |
+
|
| 878 |
+
# 检查是否需要更新账户管理器配置(重试策略变化)
|
| 879 |
+
retry_changed = (
|
| 880 |
+
old_retry_config["account_failure_threshold"] != ACCOUNT_FAILURE_THRESHOLD or
|
| 881 |
+
old_retry_config["rate_limit_cooldown_seconds"] != RATE_LIMIT_COOLDOWN_SECONDS or
|
| 882 |
+
old_retry_config["session_cache_ttl_seconds"] != SESSION_CACHE_TTL_SECONDS
|
| 883 |
+
)
|
| 884 |
+
|
| 885 |
+
if retry_changed:
|
| 886 |
+
logger.info(f"[CONFIG] 重试策略已变化,更新账户管理器配置")
|
| 887 |
+
# 更新所有账户管理器的配置
|
| 888 |
+
multi_account_mgr.session_cache_ttl_seconds = SESSION_CACHE_TTL_SECONDS
|
| 889 |
+
for account_id, account_mgr in multi_account_mgr.accounts.items():
|
| 890 |
+
account_mgr.account_failure_threshold = ACCOUNT_FAILURE_THRESHOLD
|
| 891 |
+
account_mgr.rate_limit_cooldown_seconds = RATE_LIMIT_COOLDOWN_SECONDS
|
| 892 |
+
|
| 893 |
+
logger.info(f"[CONFIG] 系统设置已更新并实时生效")
|
| 894 |
+
return {"status": "success", "message": "设置已保存并实时生效!"}
|
| 895 |
+
except Exception as e:
|
| 896 |
+
logger.error(f"[CONFIG] 更新设置失败: {str(e)}")
|
| 897 |
+
raise HTTPException(500, f"更新失败: {str(e)}")
|
| 898 |
+
|
| 899 |
@app.get("/admin/log")
|
| 900 |
@require_login()
|
| 901 |
async def admin_get_logs(
|
|
|
|
| 1021 |
async def admin_logs_html_route_prefixed(request: Request):
|
| 1022 |
return await admin_logs_html_route(request=request)
|
| 1023 |
|
| 1024 |
+
@app.get(f"/{PATH_PREFIX}/settings")
|
| 1025 |
+
@require_login()
|
| 1026 |
+
async def admin_get_settings_prefixed(request: Request):
|
| 1027 |
+
return await admin_get_settings(request=request)
|
| 1028 |
+
|
| 1029 |
+
@app.put(f"/{PATH_PREFIX}/settings")
|
| 1030 |
+
@require_login()
|
| 1031 |
+
async def admin_update_settings_prefixed(request: Request, new_settings: dict = Body(...)):
|
| 1032 |
+
return await admin_update_settings(request=request, new_settings=new_settings)
|
| 1033 |
+
|
| 1034 |
# ---------- API端点(API Key认证) ----------
|
| 1035 |
|
| 1036 |
@app.get("/v1/models")
|
|
|
|
| 1113 |
request.state.model = req.model
|
| 1114 |
|
| 1115 |
# 3. 生成会话指纹,获取Session锁(防止同一对话的并发请求冲突)
|
| 1116 |
+
conv_key = get_conversation_key([m.model_dump() for m in req.messages], client_ip)
|
| 1117 |
session_lock = await multi_account_mgr.acquire_session_lock(conv_key)
|
| 1118 |
|
| 1119 |
# 4. 在锁的保护下检查缓存和处理Session(保证同一对话的请求串行化)
|
|
|
|
| 1456 |
jwt = await account_manager.get_jwt(request_id)
|
| 1457 |
headers = get_common_headers(jwt, USER_AGENT)
|
| 1458 |
|
| 1459 |
+
# 构建 toolsSpec(根据配置决定是否启用图片生成)
|
| 1460 |
+
tools_spec = {
|
| 1461 |
+
"webGroundingSpec": {},
|
| 1462 |
+
"toolRegistry": "default_tool_registry",
|
| 1463 |
+
}
|
| 1464 |
+
# 只在启用且模型支持时添加图片生成
|
| 1465 |
+
if IMAGE_GENERATION_ENABLED and model_name in IMAGE_GENERATION_MODELS:
|
| 1466 |
+
tools_spec["imageGenerationSpec"] = {}
|
| 1467 |
+
tools_spec["videoGenerationSpec"] = {}
|
| 1468 |
+
|
| 1469 |
body = {
|
| 1470 |
"configId": account_manager.config.config_id,
|
| 1471 |
"additionalParams": {"token": "-"},
|
|
|
|
| 1475 |
"filter": "",
|
| 1476 |
"fileIds": file_ids, # 注入文件 ID
|
| 1477 |
"answerGenerationMode": "NORMAL",
|
| 1478 |
+
"toolsSpec": tools_spec,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1479 |
"languageCode": "zh-CN",
|
| 1480 |
"userMetadata": {"timeZone": "Asia/Shanghai"},
|
| 1481 |
"assistSkippingMode": "REQUEST_ASSIST"
|
requirements.txt
CHANGED
|
@@ -5,4 +5,5 @@ pydantic==2.7.0
|
|
| 5 |
aiofiles==24.1.0
|
| 6 |
python-dotenv==1.0.1
|
| 7 |
itsdangerous==2.1.2
|
| 8 |
-
python-multipart==0.0.6
|
|
|
|
|
|
| 5 |
aiofiles==24.1.0
|
| 6 |
python-dotenv==1.0.1
|
| 7 |
itsdangerous==2.1.2
|
| 8 |
+
python-multipart==0.0.6
|
| 9 |
+
pyyaml>=6.0
|