# -*- coding: utf-8 -*- """ 统一配置管理 ASR语音识别配置选项 """ import os from typing import Optional from pathlib import Path class Settings: """统一应用配置类""" # 应用信息 APP_NAME: str = "Qwen3-ASR Server" APP_VERSION: str = "1.0.1" APP_DESCRIPTION: str = "Qwen3-ASR speech recognition API service" # 服务器配置 HOST: str = "0.0.0.0" PORT: int = 8000 DEBUG: bool = False # 鉴权配置 API_KEY: Optional[str] = None # 从环境变量API_KEY读取,如果为None则鉴权可选 # 设备配置 ACCELERATOR: str = "auto" # auto, cpu, nvidia, metax, iluvatar, mthreads DEVICE: str = "auto" # auto, cpu, cuda:0 ASR_DEPLOY_TOPOLOGY: str = "isolated" # isolated, sharded, auto # 路径配置 BASE_DIR: Path = Path(__file__).parent.parent.parent DATA_DIR: str = str(BASE_DIR / "data") TEMP_DIR: str = str(BASE_DIR / "data" / "temp") # 项目总模型目录。实际模型目录直接扁平化到: # /models/{Qwen,iic,damo,...} MODELS_DIR: str = str(BASE_DIR / "models") # ModelScope 会在 MODELSCOPE_CACHE 下创建 models/{publisher}/{model_name}。 # 因此 cache 根目录应指向 models 的上一级,实际运行模型根目录仍由 MODELSCOPE_PATH 指定。 MODELSCOPE_CACHE: str = str(BASE_DIR) MODELSCOPE_PATH: str = str(BASE_DIR / "models") # 日志配置 LOG_LEVEL: str = "INFO" LOG_FILE: Optional[str] = str(BASE_DIR / "data" / "logs" / "qwen3-asr.log") LOG_MAX_BYTES: int = 20 * 1024 * 1024 # 20MB LOG_BACKUP_COUNT: int = 50 # 保留50个备份文件 # ASR模型配置 WS_MAX_BUFFER_SIZE: int = 10 * 16000 # WebSocket音频缓冲区最大大小(10秒@16kHz) FUNASR_AUTOMODEL_KWARGS = { "trust_remote_code": False, "disable_update": True, "disable_pbar": True, "disable_log": True, # 禁用FunASR的tables输出 "local_files_only": True, # 强制使用本地模型,禁止联网下载 } ASR_MODELS_CONFIG: str = str(BASE_DIR / "app/services/asr/models.json") ASR_ENABLE_REALTIME_PUNC: bool = True # 是否启用实时标点模型(用于中间结果展示) VAD_MODEL: str = "damo/speech_fsmn_vad_zh-cn-16k-common-pytorch" PUNC_MODEL: str = "iic/punc_ct-transformer_zh-cn-common-vocab272727-pytorch" PUNC_REALTIME_MODEL: str = ( "iic/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727" ) # 流式ASR远场过滤配置 ASR_ENABLE_NEARFIELD_FILTER: bool = True # 是否启用远场声音过滤 ASR_NEARFIELD_RMS_THRESHOLD: float = 0.01 # RMS能量阈值(宽松模式,适合大多数场景) # 音频处理配置 MAX_AUDIO_SIZE: int = 2048 * 1024 * 1024 # 2GB # 批处理推理配置(GPU 真并行) ASR_BATCH_SIZE: int = 4 # ASR 批处理大小(同时推理的片段数),建议 2-8 ASR_ENABLE_WORD_TIMESTAMPS: bool = False # 全局字词级时间戳开关;关闭时不预热 forced aligner,接口参数也默认隐藏 # 音频分段配置 MAX_SEGMENT_SEC: float = 60.0 # Max offline ASR segment duration in seconds. # Runtime 并发配置(按 backend 独立控制) QWEN_VLLM_SHARED_CONCURRENCY: int = 8 QWEN_VLLM_ENFORCE_EAGER: bool = True QWEN_RUST_CPU_WORKERS: int = 4 QWEN_RUST_ASR_CONCURRENCY: int = 0 QWEN_RUST_ALIGN_CONCURRENCY: int = 0 FUNASR_WORKERS: int = 1 # 声纹数据库配置(与 Model-Test-New 使用同一套 PostgreSQL/pgvector 表结构) SPEAKER_DB_ENABLED: bool = True DB_USER: str = "postgres" DB_PASSWORD: str = "postgres" DB_NAME: str = "asr_db" DB_HOST: str = "127.0.0.1" DB_PORT: int = 5432 DB_POOL_MAX_SIZE: int = 5 SV_MODEL: str = "iic/speech_campplus_sv_zh-cn_16k-common" SV_MODEL_REVISION: str = "v2.0.2" REALTIME_SV_MODEL: str = "iic/speech_eres2netv2_sv_zh-cn_16k-common" REALTIME_SV_MODEL_REVISION: str = "" SV_THRESHOLD: float = 0.6 REALTIME_MAX_SEGMENT_SEC: float = 12.0 REALTIME_MAX_SEGMENT_TAIL_SEC: float = 1.6 REALTIME_FORCE_STABLE_SEGMENT_SEC: float = 8.0 REALTIME_FORCE_STABLE_MIN_CHARS: int = 24 REALTIME_MIN_PARTIAL_SEC: float = 0.45 REALTIME_PARTIAL_EMIT_INTERVAL_SEC: float = 0.25 REALTIME_PARTIAL_WINDOW_SEC: float = 8.0 # Match FunASR's roughly 600 ms online decode cadence; Qwen re-decodes the # accumulated stream, so this stays configurable for server-side tuning. REALTIME_STREAM_CHUNK_SEC: float = 0.6 REALTIME_STREAM_MAX_PENDING_CHUNKS: int = 3 REALTIME_STREAM_WINDOW_SEC: float = 8.0 REALTIME_STREAM_STABLE_TAIL_CHARS: int = 8 REALTIME_STREAM_STABLE_MIN_GROW_CHARS: int = 2 REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS: int = 2 REALTIME_PARTIAL_HOLDBACK_CHARS: int = 6 REALTIME_LONGFORM_MIN_SEC: float = 8.0 REALTIME_LONGFORM_CHUNK_SEC: float = 6.0 REALTIME_LONGFORM_OVERLAP_SEC: float = 1.2 REALTIME_VAD_CHECK_INTERVAL_SEC: float = 0.8 REALTIME_VAD_FINALIZE_SILENCE_SEC: float = 0.6 REALTIME_ENABLE_VAD: bool = True REALTIME_VAD_CHUNK_MS: int = 200 REALTIME_VAD_PRE_ROLL_MS: int = 600 REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC: float = 8.0 REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC: float = 12.0 REALTIME_ENABLE_DIARIZATION: bool = True REALTIME_ENABLE_SEGMENT_REFINE: bool = False REALTIME_DIARIZATION_MIN_SEC: float = 3.0 REALTIME_DIARIZATION_LOOKBACK_SEC: float = 3.0 REALTIME_DIARIZATION_WINDOW_SEC: float = 15.0 REALTIME_SPEAKER_MIN_SEC: float = 1.2 REALTIME_SPEAKER_CLUSTER_THRESHOLD: float = 0.75 REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD: float = 0.58 REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD: float = 0.50 REALTIME_SPEAKER_CONFIRM_THRESHOLD: float = 0.62 REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE: float = 0.72 REALTIME_SPEAKER_MAX_SLOTS: int = 8 REALTIME_SESSION_RESUME_TTL_SEC: int = 120 # WebSocket 断线后保留会话上下文的秒数;TTL 内同 session_id 可恢复 API_PREFIX: str = "/api/v1" TASK_STATE_DIR: str = str(BASE_DIR / "data" / "tasks") TASK_RETENTION_HOURS: int = 24 def __init__(self): """从环境变量读取配置""" self._load_from_env() self._ensure_directories() def _load_from_env(self): """从环境变量加载配置""" # 服务器配置 self.HOST = os.getenv("HOST", self.HOST) self.PORT = int(os.getenv("PORT", str(self.PORT))) self.DEBUG = os.getenv("DEBUG", "false").lower() == "true" self.DATA_DIR = os.getenv("DATA_DIR", self.DATA_DIR) self.TEMP_DIR = os.getenv("TEMP_DIR", self.TEMP_DIR) # 日志配置 self.LOG_LEVEL = os.getenv("LOG_LEVEL", self.LOG_LEVEL) self.LOG_FILE = os.getenv("LOG_FILE", self.LOG_FILE) self.LOG_MAX_BYTES = int(os.getenv("LOG_MAX_BYTES", str(self.LOG_MAX_BYTES))) self.LOG_BACKUP_COUNT = int( os.getenv("LOG_BACKUP_COUNT", str(self.LOG_BACKUP_COUNT)) ) # 鉴权配置:空值/空白统一视为未配置 self.API_KEY = (os.getenv("API_KEY") or "").strip() or None # 设备配置 self.ACCELERATOR = os.getenv("ACCELERATOR", self.ACCELERATOR) self.DEVICE = os.getenv("DEVICE", self.DEVICE) self.ASR_DEPLOY_TOPOLOGY = os.getenv( "ASR_DEPLOY_TOPOLOGY", self.ASR_DEPLOY_TOPOLOGY, ).strip().lower() # 模型缓存路径 self.MODELS_DIR = os.getenv("MODELS_DIR", self.MODELS_DIR) self.MODELSCOPE_CACHE = os.getenv("MODELSCOPE_CACHE", self.MODELSCOPE_CACHE) self.MODELSCOPE_PATH = os.getenv("MODELSCOPE_PATH", self.MODELSCOPE_PATH) # 给第三方库补齐默认缓存环境变量,允许用户自行覆盖 os.environ.setdefault("MODELS_DIR", self.MODELS_DIR) os.environ.setdefault("MODELSCOPE_CACHE", self.MODELSCOPE_CACHE) os.environ.setdefault("MODELSCOPE_PATH", self.MODELSCOPE_PATH) # ASR模型配置 self.ASR_ENABLE_REALTIME_PUNC = ( os.getenv("ASR_ENABLE_REALTIME_PUNC", "true").lower() == "true" ) # WebSocket缓冲区配置 self.WS_MAX_BUFFER_SIZE = int( os.getenv("WS_MAX_BUFFER_SIZE", str(self.WS_MAX_BUFFER_SIZE)) ) # 远场过滤配置 self.ASR_ENABLE_NEARFIELD_FILTER = ( os.getenv("ASR_ENABLE_NEARFIELD_FILTER", "true").lower() == "true" ) self.ASR_NEARFIELD_RMS_THRESHOLD = float( os.getenv( "ASR_NEARFIELD_RMS_THRESHOLD", str(self.ASR_NEARFIELD_RMS_THRESHOLD) ) ) # 音频处理配置 # 支持简化格式:纯数字表示MB,或带单位(如 2048MB, 2GB) max_audio_size_str = os.getenv("MAX_AUDIO_SIZE") if max_audio_size_str: self.MAX_AUDIO_SIZE = self._parse_size(max_audio_size_str) self.ASR_BATCH_SIZE = int( os.getenv("ASR_BATCH_SIZE", str(self.ASR_BATCH_SIZE)) ) self.ASR_ENABLE_WORD_TIMESTAMPS = ( os.getenv( "ASR_ENABLE_WORD_TIMESTAMPS", str(self.ASR_ENABLE_WORD_TIMESTAMPS), ).lower() == "true" ) self.MAX_SEGMENT_SEC = float( os.getenv("MAX_SEGMENT_SEC", str(self.MAX_SEGMENT_SEC)) ) self.QWEN_VLLM_SHARED_CONCURRENCY = int( os.getenv( "QWEN_VLLM_SHARED_CONCURRENCY", str(self.QWEN_VLLM_SHARED_CONCURRENCY), ) ) self.QWEN_VLLM_ENFORCE_EAGER = ( os.getenv( "QWEN_VLLM_ENFORCE_EAGER", str(self.QWEN_VLLM_ENFORCE_EAGER), ).lower() == "true" ) self.QWEN_RUST_CPU_WORKERS = int( os.getenv("QWEN_RUST_CPU_WORKERS", str(self.QWEN_RUST_CPU_WORKERS)) ) self.QWEN_RUST_ASR_CONCURRENCY = int( os.getenv("QWEN_RUST_ASR_CONCURRENCY", str(self.QWEN_RUST_ASR_CONCURRENCY)) ) self.QWEN_RUST_ALIGN_CONCURRENCY = int( os.getenv("QWEN_RUST_ALIGN_CONCURRENCY", str(self.QWEN_RUST_ALIGN_CONCURRENCY)) ) self.FUNASR_WORKERS = int( os.getenv("FUNASR_WORKERS", str(self.FUNASR_WORKERS)) ) self.SPEAKER_DB_ENABLED = ( os.getenv("SPEAKER_DB_ENABLED", str(self.SPEAKER_DB_ENABLED)).lower() == "true" ) self.DB_USER = os.getenv("DB_USER", self.DB_USER) self.DB_PASSWORD = os.getenv("DB_PASSWORD", self.DB_PASSWORD) self.DB_NAME = os.getenv("DB_NAME", self.DB_NAME) self.DB_HOST = os.getenv("DB_HOST", self.DB_HOST) self.DB_PORT = int(os.getenv("DB_PORT", str(self.DB_PORT))) self.DB_POOL_MAX_SIZE = int( os.getenv("DB_POOL_MAX_SIZE", str(self.DB_POOL_MAX_SIZE)) ) self.SV_MODEL = os.getenv("SV_MODEL", self.SV_MODEL) self.SV_MODEL_REVISION = os.getenv("SV_MODEL_REVISION", self.SV_MODEL_REVISION) self.REALTIME_SV_MODEL = os.getenv("REALTIME_SV_MODEL", self.REALTIME_SV_MODEL) self.REALTIME_SV_MODEL_REVISION = os.getenv( "REALTIME_SV_MODEL_REVISION", self.REALTIME_SV_MODEL_REVISION, ) self.SV_THRESHOLD = float(os.getenv("SV_THRESHOLD", str(self.SV_THRESHOLD))) self.REALTIME_MAX_SEGMENT_SEC = float( os.getenv( "REALTIME_MAX_SEGMENT_SEC", str(self.REALTIME_MAX_SEGMENT_SEC), ) ) self.REALTIME_MAX_SEGMENT_TAIL_SEC = float( os.getenv( "REALTIME_MAX_SEGMENT_TAIL_SEC", str(self.REALTIME_MAX_SEGMENT_TAIL_SEC), ) ) self.REALTIME_FORCE_STABLE_SEGMENT_SEC = float( os.getenv( "REALTIME_FORCE_STABLE_SEGMENT_SEC", str(self.REALTIME_FORCE_STABLE_SEGMENT_SEC), ) ) self.REALTIME_FORCE_STABLE_MIN_CHARS = int( os.getenv( "REALTIME_FORCE_STABLE_MIN_CHARS", str(self.REALTIME_FORCE_STABLE_MIN_CHARS), ) ) self.REALTIME_MIN_PARTIAL_SEC = float( os.getenv( "REALTIME_MIN_PARTIAL_SEC", str(self.REALTIME_MIN_PARTIAL_SEC), ) ) self.REALTIME_PARTIAL_EMIT_INTERVAL_SEC = float( os.getenv( "REALTIME_PARTIAL_EMIT_INTERVAL_SEC", str(self.REALTIME_PARTIAL_EMIT_INTERVAL_SEC), ) ) self.REALTIME_PARTIAL_WINDOW_SEC = float( os.getenv( "REALTIME_PARTIAL_WINDOW_SEC", str(self.REALTIME_PARTIAL_WINDOW_SEC), ) ) self.REALTIME_STREAM_CHUNK_SEC = float( os.getenv( "REALTIME_STREAM_CHUNK_SEC", str(self.REALTIME_STREAM_CHUNK_SEC), ) ) self.REALTIME_STREAM_MAX_PENDING_CHUNKS = int( os.getenv( "REALTIME_STREAM_MAX_PENDING_CHUNKS", str(self.REALTIME_STREAM_MAX_PENDING_CHUNKS), ) ) self.REALTIME_STREAM_WINDOW_SEC = float( os.getenv( "REALTIME_STREAM_WINDOW_SEC", str(self.REALTIME_STREAM_WINDOW_SEC), ) ) self.REALTIME_STREAM_STABLE_TAIL_CHARS = int( os.getenv( "REALTIME_STREAM_STABLE_TAIL_CHARS", str(self.REALTIME_STREAM_STABLE_TAIL_CHARS), ) ) self.REALTIME_STREAM_STABLE_MIN_GROW_CHARS = int( os.getenv( "REALTIME_STREAM_STABLE_MIN_GROW_CHARS", str(self.REALTIME_STREAM_STABLE_MIN_GROW_CHARS), ) ) self.REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS = int( os.getenv( "REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS", str(self.REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS), ) ) self.REALTIME_PARTIAL_HOLDBACK_CHARS = int( os.getenv( "REALTIME_PARTIAL_HOLDBACK_CHARS", str(self.REALTIME_PARTIAL_HOLDBACK_CHARS), ) ) self.REALTIME_LONGFORM_MIN_SEC = float( os.getenv( "REALTIME_LONGFORM_MIN_SEC", str(self.REALTIME_LONGFORM_MIN_SEC), ) ) self.REALTIME_LONGFORM_CHUNK_SEC = float( os.getenv( "REALTIME_LONGFORM_CHUNK_SEC", str(self.REALTIME_LONGFORM_CHUNK_SEC), ) ) self.REALTIME_LONGFORM_OVERLAP_SEC = float( os.getenv( "REALTIME_LONGFORM_OVERLAP_SEC", str(self.REALTIME_LONGFORM_OVERLAP_SEC), ) ) self.REALTIME_VAD_CHECK_INTERVAL_SEC = float( os.getenv( "REALTIME_VAD_CHECK_INTERVAL_SEC", str(self.REALTIME_VAD_CHECK_INTERVAL_SEC), ) ) self.REALTIME_VAD_FINALIZE_SILENCE_SEC = float( os.getenv( "REALTIME_VAD_FINALIZE_SILENCE_SEC", str(self.REALTIME_VAD_FINALIZE_SILENCE_SEC), ) ) self.REALTIME_ENABLE_VAD = ( os.getenv("REALTIME_ENABLE_VAD", str(self.REALTIME_ENABLE_VAD)).lower() == "true" ) self.REALTIME_VAD_CHUNK_MS = int( os.getenv("REALTIME_VAD_CHUNK_MS", str(self.REALTIME_VAD_CHUNK_MS)) ) self.REALTIME_VAD_PRE_ROLL_MS = int( os.getenv("REALTIME_VAD_PRE_ROLL_MS", str(self.REALTIME_VAD_PRE_ROLL_MS)) ) self.REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC = float( os.getenv( "REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC", str(self.REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC), ) ) self.REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC = float( os.getenv( "REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC", str(self.REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC), ) ) self.REALTIME_ENABLE_DIARIZATION = ( os.getenv( "REALTIME_ENABLE_DIARIZATION", str(self.REALTIME_ENABLE_DIARIZATION), ).lower() == "true" ) self.REALTIME_ENABLE_SEGMENT_REFINE = ( os.getenv( "REALTIME_ENABLE_SEGMENT_REFINE", str(self.REALTIME_ENABLE_SEGMENT_REFINE), ).lower() == "true" ) self.REALTIME_DIARIZATION_MIN_SEC = float( os.getenv( "REALTIME_DIARIZATION_MIN_SEC", str(self.REALTIME_DIARIZATION_MIN_SEC), ) ) self.REALTIME_DIARIZATION_LOOKBACK_SEC = float( os.getenv( "REALTIME_DIARIZATION_LOOKBACK_SEC", str(self.REALTIME_DIARIZATION_LOOKBACK_SEC), ) ) self.REALTIME_DIARIZATION_WINDOW_SEC = float( os.getenv( "REALTIME_DIARIZATION_WINDOW_SEC", str(self.REALTIME_DIARIZATION_WINDOW_SEC), ) ) self.REALTIME_SPEAKER_MIN_SEC = float( os.getenv( "REALTIME_SPEAKER_MIN_SEC", str(self.REALTIME_SPEAKER_MIN_SEC), ) ) self.REALTIME_SPEAKER_CLUSTER_THRESHOLD = float( os.getenv( "REALTIME_SPEAKER_CLUSTER_THRESHOLD", str(self.REALTIME_SPEAKER_CLUSTER_THRESHOLD), ) ) self.REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD = float( os.getenv( "REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD", str(self.REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD), ) ) self.REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD = float( os.getenv( "REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD", str(self.REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD), ) ) self.REALTIME_SPEAKER_CONFIRM_THRESHOLD = float( os.getenv( "REALTIME_SPEAKER_CONFIRM_THRESHOLD", str(self.REALTIME_SPEAKER_CONFIRM_THRESHOLD), ) ) self.REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE = float( os.getenv( "REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE", str(self.REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE), ) ) self.REALTIME_SPEAKER_MAX_SLOTS = int( os.getenv( "REALTIME_SPEAKER_MAX_SLOTS", str(self.REALTIME_SPEAKER_MAX_SLOTS), ) ) self.REALTIME_SESSION_RESUME_TTL_SEC = int( os.getenv( "REALTIME_SESSION_RESUME_TTL_SEC", str(self.REALTIME_SESSION_RESUME_TTL_SEC), ) ) self.API_PREFIX = os.getenv("API_PREFIX", self.API_PREFIX) self.TASK_STATE_DIR = os.getenv("TASK_STATE_DIR", self.TASK_STATE_DIR) self.TASK_RETENTION_HOURS = int( os.getenv("TASK_RETENTION_HOURS", str(self.TASK_RETENTION_HOURS)) ) def _parse_size(self, size_str: str) -> int: """解析带单位的大小字符串 支持格式: - 纯数字:视为 MB(如 2048 = 2048MB = 2147483648 bytes) - 带单位:如 2GB, 2048MB, 1.5GB """ size_str = size_str.strip().upper() # 如果纯数字,视为 MB if size_str.isdigit(): return int(size_str) * 1024 * 1024 # 带单位的处理 if size_str.endswith('GB'): return int(float(size_str[:-2]) * 1024 * 1024 * 1024) elif size_str.endswith('MB'): return int(float(size_str[:-2]) * 1024 * 1024) elif size_str.endswith('KB'): return int(float(size_str[:-2]) * 1024) else: # 默认视为字节 return int(size_str) def _ensure_directories(self): """确保必需的目录存在""" os.makedirs(self.TEMP_DIR, exist_ok=True) if self.LOG_FILE: os.makedirs(os.path.dirname(self.LOG_FILE), exist_ok=True) os.makedirs(self.MODELS_DIR, exist_ok=True) os.makedirs(self.MODELSCOPE_CACHE, exist_ok=True) os.makedirs(self.MODELSCOPE_PATH, exist_ok=True) os.makedirs(self.DATA_DIR, exist_ok=True) os.makedirs(self.TASK_STATE_DIR, exist_ok=True) @property def models_config_path(self) -> str: """获取模型配置文件的完整路径""" return str(self.BASE_DIR / self.ASR_MODELS_CONFIG) @property def docs_url(self) -> Optional[str]: """获取文档URL""" return "/docs" @property def redoc_url(self) -> Optional[str]: """获取ReDoc URL""" return "/redoc" # 全局配置实例 settings = Settings()