mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-07-13 08:31:59 +08:00
问题1 — DeepSeek 模型下拉为空:
provider 的 /models 调用失败时,get_model_list 吞掉异常返回 [],
get_all_models_by_id 再对 [] 取 .data 触发 AttributeError 又被吞,
最终接口返回 {"code":0,"msg":"success","data":[]},把失败伪装成空成功,
用户看不到任何原因。
- 捕获并回传真实错误,不再二次吞
- 新增 model_fallback.normalize_models 兼容 SyncPage/list/dict,绝不再 .data 崩
- 内置供应商提供已知模型兜底清单(DeepSeek→deepseek-chat/deepseek-reasoner,
Qwen→qwen-plus 等),动态拿不到时回退,保证下拉非空;动态可用时仍以动态为准
问题2 — whisper 模型下载不走代理(Docker 里代理没生效):
snapshot_download 既不读 UI 配的代理,HF_ENDPOINT 又固定 hf-mirror.com。
- ProxyConfigManager.apply_to_env() 把生效代理 export 到 HTTP(S)_PROXY/ALL_PROXY,
huggingface_hub 即可复用;在每次下载前与启动时应用(覆盖转写按需下载)
- 网络类下载报错翻译成可操作提示(配代理 / 改 HF_ENDPOINT / 检查容器外网)
- .env.example 补充 HF_ENDPOINT 与代理覆盖说明
新增 tests/test_model_fallback.py、tests/test_proxy_apply_env.py(14 用例)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
import json
|
||
import os
|
||
from pathlib import Path
|
||
from typing import Any, Dict, Optional
|
||
|
||
|
||
class ProxyConfigManager:
|
||
"""全局代理配置,存 JSON 文件,支持前端动态修改。
|
||
|
||
作用范围:LLM API + 转写 API(Groq 等)+ yt-dlp 视频下载。
|
||
优先级:配置文件里 enabled=true 的 url > 环境变量 HTTP_PROXY/HTTPS_PROXY/ALL_PROXY。
|
||
这样桌面端/web 用户在设置页填,docker/服务器部署用环境变量兜底。
|
||
"""
|
||
|
||
def __init__(self, filepath: str = "config/proxy.json"):
|
||
self.path = Path(filepath)
|
||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
def _read(self) -> Dict[str, Any]:
|
||
if not self.path.exists():
|
||
return {}
|
||
try:
|
||
with self.path.open("r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
except Exception:
|
||
return {}
|
||
|
||
def _write(self, data: Dict[str, Any]):
|
||
with self.path.open("w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
def get_config(self) -> Dict[str, Any]:
|
||
data = self._read()
|
||
return {
|
||
"enabled": bool(data.get("enabled", False)),
|
||
"url": data.get("url", "") or "",
|
||
}
|
||
|
||
def update_config(self, enabled: bool, url: Optional[str] = None) -> Dict[str, Any]:
|
||
data = self._read()
|
||
data["enabled"] = bool(enabled)
|
||
if url is not None:
|
||
data["url"] = url.strip()
|
||
self._write(data)
|
||
return self.get_config()
|
||
|
||
def get_proxy_url(self) -> Optional[str]:
|
||
"""返回当前生效的代理 URL;没有则 None。
|
||
|
||
- 配置文件 enabled=true 且 url 非空 → 用配置的 url
|
||
- 否则回退到环境变量(标准的 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY,大小写都认)
|
||
"""
|
||
cfg = self.get_config()
|
||
if cfg["enabled"] and cfg["url"]:
|
||
return cfg["url"]
|
||
for key in ("HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"):
|
||
val = os.environ.get(key)
|
||
if val:
|
||
return val
|
||
return None
|
||
|
||
def apply_to_env(self) -> Optional[str]:
|
||
"""把当前生效的代理 URL 写进进程环境变量,返回生效的 url(无则 None)。
|
||
|
||
为什么需要(issue #417):huggingface_hub / requests 这类库**只认**环境变量
|
||
HTTP_PROXY / HTTPS_PROXY / ALL_PROXY,不读我们 UI 配置文件。whisper 模型用
|
||
snapshot_download 从 HuggingFace 拉取,如果用户只在设置页填了代理,下载根本
|
||
不走代理 —— 就是用户说的「Docker 容器里代理没生效」。在下载前/启动时调用本
|
||
方法,把 UI 配的代理 export 到环境变量,HF 下载就能复用同一个代理。
|
||
|
||
大小写别名都写,覆盖不同库的读取习惯。
|
||
"""
|
||
url = self.get_proxy_url()
|
||
if not url:
|
||
return None
|
||
for key in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY",
|
||
"http_proxy", "https_proxy", "all_proxy"):
|
||
os.environ[key] = url
|
||
return url
|