mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-07-07 23:51:21 +08:00
fix(backend): 部署韧性——模型自愈/就绪门禁/全局代理/启动诊断
- whisper: model.bin 截断/损坏时删目录重下重试一次,修「Unable to
open file model.bin」死循环;mlx 同样按 config.json 判完整性
- /generate_note 加就绪门禁:本地转写引擎模型没下好直接拦截,返回
reason=transcriber_model_not_ready,不让任务静默卡在首次下载
- 全局代理:新增 ProxyConfigManager(JSON 配置 + HTTP_PROXY env 兜底)
+ build_openai_client,统一注入代理到 LLM/Groq 客户端;yt-dlp 与
youtube-transcript-api 也走代理
- build_openai_client 校验 api_key 非空,空 key 给「xxx 的 API Key
未配置」而不是天书般的 Illegal header value b'Bearer '
- universal_gpt: 模型拒绝自定义 temperature(o1/o3/gpt-5 系列)时
就地去掉参数重试,不消耗重试预算
- connect_test 改用真实 chat completion 而非 /v1/models 探测
- main.py: lifespan 拆 [startup 1/5..5/5] 分段日志 + 异常清晰定位
- /sys_health 重构为结构化返回 {backend,ffmpeg,db,whisper_model}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ from app.decorators.timeit import timeit
|
||||
from app.models.transcriber_model import TranscriptResult, TranscriptSegment
|
||||
from app.services.provider import ProviderService
|
||||
from app.transcriber.base import Transcriber
|
||||
from openai import OpenAI
|
||||
from app.utils.openai_client import build_openai_client
|
||||
import ffmpeg
|
||||
import tempfile
|
||||
from dotenv import load_dotenv
|
||||
@@ -30,12 +30,14 @@ class GroqTranscriber(Transcriber, ABC):
|
||||
print(f"压缩完成,临时路径:{file_path}")
|
||||
provider = ProviderService.get_provider_by_id('groq')
|
||||
|
||||
|
||||
if not provider:
|
||||
raise Exception("Groq 供应商未配置,请配置以后使用。")
|
||||
client = OpenAI(
|
||||
# build_openai_client 会校验 api_key 非空(空 key 会抛天书般的
|
||||
# `Illegal header value b'Bearer '`),并自动注入全局代理
|
||||
client = build_openai_client(
|
||||
api_key=provider.get('api_key'),
|
||||
base_url=provider.get('base_url')
|
||||
base_url=provider.get('base_url'),
|
||||
key_label="Groq 转写引擎的 API Key",
|
||||
)
|
||||
filename = file_path
|
||||
|
||||
|
||||
@@ -58,9 +58,16 @@ class MLXWhisperTranscriber(Transcriber):
|
||||
# 设置模型路径
|
||||
model_dir = get_model_dir("mlx-whisper")
|
||||
self.model_path = os.path.join(model_dir, self.model_name)
|
||||
# 检查并下载模型
|
||||
if not Path(self.model_path).exists():
|
||||
logger.info(f"模型 {self.model_name} 不存在,开始下载...")
|
||||
# 用 config.json 而非目录存在作为「下载完成」的判据,
|
||||
# 同 fast-whisper 的 model.bin:避免半成品目录把后续下载吞掉
|
||||
config_file = Path(self.model_path) / "config.json"
|
||||
if not config_file.exists():
|
||||
if Path(self.model_path).exists():
|
||||
logger.warning(
|
||||
f"MLX 模型目录 {self.model_path} 存在但 config.json 缺失(上次下载未完成),重新下载"
|
||||
)
|
||||
else:
|
||||
logger.info(f"模型 {self.model_name} 不存在,开始下载...")
|
||||
snapshot_download(
|
||||
self.model_name,
|
||||
local_dir=self.model_path,
|
||||
|
||||
@@ -10,6 +10,7 @@ from app.utils.path_helper import get_model_dir
|
||||
from events import transcription_finished
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
from tqdm import tqdm
|
||||
from modelscope import snapshot_download
|
||||
|
||||
@@ -50,22 +51,41 @@ class WhisperTranscriber(Transcriber):
|
||||
|
||||
model_dir = get_model_dir("whisper")
|
||||
model_path = os.path.join(model_dir, f"whisper-{model_size}")
|
||||
if not Path(model_path).exists():
|
||||
logger.info(f"模型 whisper-{model_size} 不存在,开始下载...")
|
||||
repo_id = MODEL_MAP[model_size]
|
||||
model_path = snapshot_download(
|
||||
repo_id,
|
||||
repo_id = MODEL_MAP[model_size]
|
||||
|
||||
local_dir=model_path,
|
||||
)
|
||||
# 第一步:目录 / model.bin 不在 → 下载。
|
||||
# 关键判据用 model.bin 而不是目录存在:首次下载若被打断(网络中断 / 磁盘满 /
|
||||
# 容器被 kill)会留下半成品目录,只看目录存在会跳过下载。
|
||||
model_bin = Path(model_path) / "model.bin"
|
||||
if not model_bin.exists():
|
||||
if Path(model_path).exists():
|
||||
logger.warning(f"模型目录 {model_path} 存在但 model.bin 缺失(上次下载未完成),重新下载")
|
||||
else:
|
||||
logger.info(f"模型 whisper-{model_size} 不存在,开始下载...")
|
||||
model_path = snapshot_download(repo_id, local_dir=model_path)
|
||||
logger.info("模型下载完成")
|
||||
|
||||
self.model = WhisperModel(
|
||||
model_size_or_path=model_path,
|
||||
device=self.device,
|
||||
compute_type=self.compute_type,
|
||||
download_root=model_dir
|
||||
)
|
||||
# 第二步:加载。model.bin 可能存在但【内容截断】(下载到一半被 kill),
|
||||
# 此时 WhisperModel() 会抛 "File model.bin is incomplete: failed to read a buffer..."。
|
||||
# 捕获后删掉损坏目录、重新下载、再试一次——自愈,避免 500 死循环。
|
||||
try:
|
||||
self.model = WhisperModel(
|
||||
model_size_or_path=model_path,
|
||||
device=self.device,
|
||||
compute_type=self.compute_type,
|
||||
download_root=model_dir,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"加载 whisper-{model_size} 失败(疑似模型文件损坏 / 截断):{e};删除后重新下载")
|
||||
shutil.rmtree(model_path, ignore_errors=True)
|
||||
model_path = snapshot_download(repo_id, local_dir=model_path)
|
||||
logger.info("模型重新下载完成,重试加载")
|
||||
self.model = WhisperModel(
|
||||
model_size_or_path=model_path,
|
||||
device=self.device,
|
||||
compute_type=self.compute_type,
|
||||
download_root=model_dir,
|
||||
)
|
||||
@staticmethod
|
||||
def is_torch_installed() -> bool:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user