Files
BiliNote/backend/app/services/proxy_config_manager.py
huangjianwu bebf2e8c61 fix(backend): 修复 DeepSeek 模型列表为空 & whisper 下载不走代理 (#417)
问题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>
2026-06-29 13:54:44 +08:00

80 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import os
from pathlib import Path
from typing import Any, Dict, Optional
class ProxyConfigManager:
"""全局代理配置,存 JSON 文件,支持前端动态修改。
作用范围LLM API + 转写 APIGroq 等)+ 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 #417huggingface_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