镜像自地址
https://github.com/binary-husky/gpt_academic.git
已同步 2025-12-06 06:26:47 +00:00
Version 3.74: Merge latest updates on dev branch (frontier) (#1621)
* Update version to 3.74 * Add support for Yi Model API (#1635) * 更新以支持零一万物模型 * 删除newbing * 修改config --------- Co-authored-by: binary-husky <qingxu.fu@outlook.com> * Refactor function signatures in bridge files * fix qwen api change * rename and ref functions * rename and move some cookie functions * 增加haiku模型,新增endpoint配置说明 (#1626) * haiku added * 新增haiku,新增endpoint配置说明 * Haiku added * 将说明同步至最新Endpoint --------- Co-authored-by: binary-husky <qingxu.fu@outlook.com> * private_upload目录下进行文件鉴权 (#1596) * private_upload目录下进行文件鉴权 * minor fastapi adjustment * Add logging functionality to enable saving conversation records * waiting to fix username retrieve * support 2rd web path * allow accessing default user dir --------- Co-authored-by: binary-husky <qingxu.fu@outlook.com> * remove yaml deps * fix favicon * fix abs path auth problem * forget to write a return * add `dashscope` to deps * fix GHSA-v9q9-xj86-953p * 用户名重叠越权访问patch (#1681) * add cohere model api access * cohere + can_multi_thread * fix block user access(fail) * fix fastapi bug * change cohere api endpoint * explain version --------- Co-authored-by: Menghuan1918 <menghuan2003@outlook.com> Co-authored-by: Skyzayre <120616113+Skyzayre@users.noreply.github.com> Co-authored-by: XIao <46100050+Kilig947@users.noreply.github.com>
这个提交包含在:
@@ -1 +1,61 @@
|
||||
from typing import Callable
|
||||
def load_web_cookie_cache__fn_builder(customize_btns, cookies, predefined_btns)->Callable:
|
||||
def load_web_cookie_cache(persistent_cookie_, cookies_):
|
||||
import gradio as gr
|
||||
from themes.theme import load_dynamic_theme, to_cookie_str, from_cookie_str, assign_user_uuid
|
||||
|
||||
ret = {}
|
||||
for k in customize_btns:
|
||||
ret.update({customize_btns[k]: gr.update(visible=False, value="")})
|
||||
|
||||
try: persistent_cookie_ = from_cookie_str(persistent_cookie_) # persistent cookie to dict
|
||||
except: return ret
|
||||
|
||||
customize_fn_overwrite_ = persistent_cookie_.get("custom_bnt", {})
|
||||
cookies_['customize_fn_overwrite'] = customize_fn_overwrite_
|
||||
ret.update({cookies: cookies_})
|
||||
|
||||
for k,v in persistent_cookie_["custom_bnt"].items():
|
||||
if v['Title'] == "": continue
|
||||
if k in customize_btns: ret.update({customize_btns[k]: gr.update(visible=True, value=v['Title'])})
|
||||
else: ret.update({predefined_btns[k]: gr.update(visible=True, value=v['Title'])})
|
||||
return ret
|
||||
return load_web_cookie_cache
|
||||
|
||||
|
||||
def assign_btn__fn_builder(customize_btns, predefined_btns, cookies, web_cookie_cache)->Callable:
|
||||
def assign_btn(persistent_cookie_, cookies_, basic_btn_dropdown_, basic_fn_title, basic_fn_prefix, basic_fn_suffix, clean_up=False):
|
||||
import gradio as gr
|
||||
from themes.theme import load_dynamic_theme, to_cookie_str, from_cookie_str, assign_user_uuid
|
||||
ret = {}
|
||||
# 读取之前的自定义按钮
|
||||
customize_fn_overwrite_ = cookies_['customize_fn_overwrite']
|
||||
# 更新新的自定义按钮
|
||||
customize_fn_overwrite_.update({
|
||||
basic_btn_dropdown_:
|
||||
{
|
||||
"Title":basic_fn_title,
|
||||
"Prefix":basic_fn_prefix,
|
||||
"Suffix":basic_fn_suffix,
|
||||
}
|
||||
}
|
||||
)
|
||||
if clean_up:
|
||||
customize_fn_overwrite_ = {}
|
||||
cookies_.update(customize_fn_overwrite_) # 更新cookie
|
||||
visible = (not clean_up) and (basic_fn_title != "")
|
||||
if basic_btn_dropdown_ in customize_btns:
|
||||
# 是自定义按钮,不是预定义按钮
|
||||
ret.update({customize_btns[basic_btn_dropdown_]: gr.update(visible=visible, value=basic_fn_title)})
|
||||
else:
|
||||
# 是预定义按钮
|
||||
ret.update({predefined_btns[basic_btn_dropdown_]: gr.update(visible=visible, value=basic_fn_title)})
|
||||
ret.update({cookies: cookies_})
|
||||
try: persistent_cookie_ = from_cookie_str(persistent_cookie_) # persistent cookie to dict
|
||||
except: persistent_cookie_ = {}
|
||||
persistent_cookie_["custom_bnt"] = customize_fn_overwrite_ # dict update new value
|
||||
persistent_cookie_ = to_cookie_str(persistent_cookie_) # persistent cookie to dict
|
||||
ret.update({web_cookie_cache: persistent_cookie_}) # write persistent cookie
|
||||
return ret
|
||||
return assign_btn
|
||||
|
||||
|
||||
211
shared_utils/fastapi_server.py
普通文件
211
shared_utils/fastapi_server.py
普通文件
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Tests:
|
||||
|
||||
- custom_path false / no user auth:
|
||||
-- upload file(yes)
|
||||
-- download file(yes)
|
||||
-- websocket(yes)
|
||||
-- block __pycache__ access(yes)
|
||||
-- rel (yes)
|
||||
-- abs (yes)
|
||||
-- block user access(fail) http://localhost:45013/file=gpt_log/admin/chat_secrets.log
|
||||
-- fix(commit f6bf05048c08f5cd84593f7fdc01e64dec1f584a)-> block successful
|
||||
|
||||
- custom_path yes("/cc/gptac") / no user auth:
|
||||
-- upload file(yes)
|
||||
-- download file(yes)
|
||||
-- websocket(yes)
|
||||
-- block __pycache__ access(yes)
|
||||
-- block user access(yes)
|
||||
|
||||
- custom_path yes("/cc/gptac/") / no user auth:
|
||||
-- upload file(yes)
|
||||
-- download file(yes)
|
||||
-- websocket(yes)
|
||||
-- block user access(yes)
|
||||
|
||||
- custom_path yes("/cc/gptac/") / + user auth:
|
||||
-- upload file(yes)
|
||||
-- download file(yes)
|
||||
-- websocket(yes)
|
||||
-- block user access(yes)
|
||||
-- block user-wise access (yes)
|
||||
|
||||
- custom_path no + user auth:
|
||||
-- upload file(yes)
|
||||
-- download file(yes)
|
||||
-- websocket(yes)
|
||||
-- block user access(yes)
|
||||
-- block user-wise access (yes)
|
||||
|
||||
queue cocurrent effectiveness
|
||||
-- upload file(yes)
|
||||
-- download file(yes)
|
||||
-- websocket(yes)
|
||||
"""
|
||||
|
||||
import os, requests, threading, time
|
||||
import uvicorn
|
||||
|
||||
def _authorize_user(path_or_url, request, gradio_app):
|
||||
from toolbox import get_conf, default_user_name
|
||||
PATH_PRIVATE_UPLOAD, PATH_LOGGING = get_conf('PATH_PRIVATE_UPLOAD', 'PATH_LOGGING')
|
||||
sensitive_path = None
|
||||
path_or_url = os.path.relpath(path_or_url)
|
||||
if path_or_url.startswith(PATH_LOGGING):
|
||||
sensitive_path = PATH_LOGGING
|
||||
if path_or_url.startswith(PATH_PRIVATE_UPLOAD):
|
||||
sensitive_path = PATH_PRIVATE_UPLOAD
|
||||
if sensitive_path:
|
||||
token = request.cookies.get("access-token") or request.cookies.get("access-token-unsecure")
|
||||
user = gradio_app.tokens.get(token) # get user
|
||||
allowed_users = [user, 'autogen', default_user_name] # three user path that can be accessed
|
||||
for user_allowed in allowed_users:
|
||||
# exact match
|
||||
if f"{os.sep}".join(path_or_url.split(os.sep)[:2]) == os.path.join(sensitive_path, user_allowed):
|
||||
return True
|
||||
return False # "越权访问!"
|
||||
return True
|
||||
|
||||
|
||||
class Server(uvicorn.Server):
|
||||
# A server that runs in a separate thread
|
||||
def install_signal_handlers(self):
|
||||
pass
|
||||
|
||||
def run_in_thread(self):
|
||||
self.thread = threading.Thread(target=self.run, daemon=True)
|
||||
self.thread.start()
|
||||
while not self.started:
|
||||
time.sleep(1e-3)
|
||||
|
||||
def close(self):
|
||||
self.should_exit = True
|
||||
self.thread.join()
|
||||
|
||||
|
||||
def start_app(app_block, CONCURRENT_COUNT, AUTHENTICATION, PORT, SSL_KEYFILE, SSL_CERTFILE):
|
||||
import uvicorn
|
||||
import fastapi
|
||||
import gradio as gr
|
||||
from fastapi import FastAPI
|
||||
from gradio.routes import App
|
||||
from toolbox import get_conf
|
||||
CUSTOM_PATH, PATH_LOGGING = get_conf('CUSTOM_PATH', 'PATH_LOGGING')
|
||||
|
||||
# --- --- configurate gradio app block --- ---
|
||||
app_block:gr.Blocks
|
||||
app_block.ssl_verify = False
|
||||
app_block.auth_message = '请登录'
|
||||
app_block.favicon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "docs/logo.png")
|
||||
app_block.auth = AUTHENTICATION if len(AUTHENTICATION) != 0 else None
|
||||
app_block.blocked_paths = ["config.py", "__pycache__", "config_private.py", "docker-compose.yml", "Dockerfile", f"{PATH_LOGGING}/admin"]
|
||||
app_block.dev_mode = False
|
||||
app_block.config = app_block.get_config_file()
|
||||
app_block.enable_queue = True
|
||||
app_block.queue(concurrency_count=CONCURRENT_COUNT)
|
||||
app_block.validate_queue_settings()
|
||||
app_block.show_api = False
|
||||
app_block.config = app_block.get_config_file()
|
||||
max_threads = 40
|
||||
app_block.max_threads = max(
|
||||
app_block._queue.max_thread_count if app_block.enable_queue else 0, max_threads
|
||||
)
|
||||
app_block.is_colab = False
|
||||
app_block.is_kaggle = False
|
||||
app_block.is_sagemaker = False
|
||||
|
||||
gradio_app = App.create_app(app_block)
|
||||
|
||||
# --- --- replace gradio endpoint to forbid access to sensitive files --- ---
|
||||
if len(AUTHENTICATION) > 0:
|
||||
dependencies = []
|
||||
endpoint = None
|
||||
for route in list(gradio_app.router.routes):
|
||||
if route.path == "/file/{path:path}":
|
||||
gradio_app.router.routes.remove(route)
|
||||
if route.path == "/file={path_or_url:path}":
|
||||
dependencies = route.dependencies
|
||||
endpoint = route.endpoint
|
||||
gradio_app.router.routes.remove(route)
|
||||
@gradio_app.get("/file/{path:path}", dependencies=dependencies)
|
||||
@gradio_app.head("/file={path_or_url:path}", dependencies=dependencies)
|
||||
@gradio_app.get("/file={path_or_url:path}", dependencies=dependencies)
|
||||
async def file(path_or_url: str, request: fastapi.Request):
|
||||
if len(AUTHENTICATION) > 0:
|
||||
if not _authorize_user(path_or_url, request, gradio_app):
|
||||
return "越权访问!"
|
||||
return await endpoint(path_or_url, request)
|
||||
|
||||
# --- --- app_lifespan --- ---
|
||||
from contextlib import asynccontextmanager
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(app):
|
||||
async def startup_gradio_app():
|
||||
if gradio_app.get_blocks().enable_queue:
|
||||
gradio_app.get_blocks().startup_events()
|
||||
async def shutdown_gradio_app():
|
||||
pass
|
||||
await startup_gradio_app() # startup logic here
|
||||
yield # The application will serve requests after this point
|
||||
await shutdown_gradio_app() # cleanup/shutdown logic here
|
||||
|
||||
# --- --- FastAPI --- ---
|
||||
fastapi_app = FastAPI(lifespan=app_lifespan)
|
||||
fastapi_app.mount(CUSTOM_PATH, gradio_app)
|
||||
|
||||
# --- --- favicon --- ---
|
||||
if CUSTOM_PATH != '/':
|
||||
from fastapi.responses import FileResponse
|
||||
@fastapi_app.get("/favicon.ico")
|
||||
async def favicon():
|
||||
return FileResponse(app_block.favicon_path)
|
||||
|
||||
# --- --- uvicorn.Config --- ---
|
||||
ssl_keyfile = None if SSL_KEYFILE == "" else SSL_KEYFILE
|
||||
ssl_certfile = None if SSL_CERTFILE == "" else SSL_CERTFILE
|
||||
server_name = "0.0.0.0"
|
||||
config = uvicorn.Config(
|
||||
fastapi_app,
|
||||
host=server_name,
|
||||
port=PORT,
|
||||
reload=False,
|
||||
log_level="warning",
|
||||
ssl_keyfile=ssl_keyfile,
|
||||
ssl_certfile=ssl_certfile,
|
||||
)
|
||||
server = Server(config)
|
||||
url_host_name = "localhost" if server_name == "0.0.0.0" else server_name
|
||||
if ssl_keyfile is not None:
|
||||
if ssl_certfile is None:
|
||||
raise ValueError(
|
||||
"ssl_certfile must be provided if ssl_keyfile is provided."
|
||||
)
|
||||
path_to_local_server = f"https://{url_host_name}:{PORT}/"
|
||||
else:
|
||||
path_to_local_server = f"http://{url_host_name}:{PORT}/"
|
||||
if CUSTOM_PATH != '/':
|
||||
path_to_local_server += CUSTOM_PATH.lstrip('/').rstrip('/') + '/'
|
||||
# --- --- begin --- ---
|
||||
server.run_in_thread()
|
||||
|
||||
# --- --- after server launch --- ---
|
||||
app_block.server = server
|
||||
app_block.server_name = server_name
|
||||
app_block.local_url = path_to_local_server
|
||||
app_block.protocol = (
|
||||
"https"
|
||||
if app_block.local_url.startswith("https") or app_block.is_colab
|
||||
else "http"
|
||||
)
|
||||
|
||||
if app_block.enable_queue:
|
||||
app_block._queue.set_url(path_to_local_server)
|
||||
|
||||
forbid_proxies = {
|
||||
"http": "",
|
||||
"https": "",
|
||||
}
|
||||
requests.get(f"{app_block.local_url}startup-events", verify=app_block.ssl_verify, proxies=forbid_proxies)
|
||||
app_block.is_running = True
|
||||
app_block.block_thread()
|
||||
@@ -28,6 +28,11 @@ def is_api2d_key(key):
|
||||
return bool(API_MATCH_API2D)
|
||||
|
||||
|
||||
def is_cohere_api_key(key):
|
||||
API_MATCH_AZURE = re.match(r"[a-zA-Z0-9]{40}$", key)
|
||||
return bool(API_MATCH_AZURE)
|
||||
|
||||
|
||||
def is_any_api_key(key):
|
||||
if ',' in key:
|
||||
keys = key.split(',')
|
||||
@@ -35,7 +40,7 @@ def is_any_api_key(key):
|
||||
if is_any_api_key(k): return True
|
||||
return False
|
||||
else:
|
||||
return is_openai_api_key(key) or is_api2d_key(key) or is_azure_api_key(key)
|
||||
return is_openai_api_key(key) or is_api2d_key(key) or is_azure_api_key(key) or is_cohere_api_key(key)
|
||||
|
||||
|
||||
def what_keys(keys):
|
||||
@@ -74,8 +79,12 @@ def select_api_key(keys, llm_model):
|
||||
for k in key_list:
|
||||
if is_azure_api_key(k): avail_key_list.append(k)
|
||||
|
||||
if llm_model.startswith('cohere-'):
|
||||
for k in key_list:
|
||||
if is_cohere_api_key(k): avail_key_list.append(k)
|
||||
|
||||
if len(avail_key_list) == 0:
|
||||
raise RuntimeError(f"您提供的api-key不满足要求,不包含任何可用于{llm_model}的api-key。您可能选择了错误的模型或请求源(右下角更换模型菜单中可切换openai,azure,claude,api2d等请求源)。")
|
||||
raise RuntimeError(f"您提供的api-key不满足要求,不包含任何可用于{llm_model}的api-key。您可能选择了错误的模型或请求源(左上角更换模型菜单中可切换openai,azure,claude,cohere等请求源)。")
|
||||
|
||||
api_key = random.choice(avail_key_list) # 随机负载均衡
|
||||
return api_key
|
||||
|
||||
在新工单中引用
屏蔽一个用户