"""ASR 转写客户端:灵台 → 元炁 ``/v1/audio/transcriptions``(Whisper 兼容 multipart)。 多模态关闭纪律:YUANQI_BASE_URL 未配置(或调用失败)时 503,不降级、不伪造转写; 文字相伴链路(镜树/缘契/百工)不依赖本模块——关闭全部多模态后完整可用。 """ from __future__ import annotations import httpx from app.core.config import settings from app.core.errors import AppError _TIMEOUT_SECONDS = 120.0 # 本地推理首包较慢,给足窗口 async def transcribe(audio_bytes: bytes, filename: str) -> dict: base_url = settings.yuanqi_base_url if not base_url: raise AppError( "service_unavailable", "语音转写未启用(YUANQI_BASE_URL 未配置,多模态已关闭)。", status_code=503, ) async with httpx.AsyncClient(trust_env=False, timeout=_TIMEOUT_SECONDS) as client: response = await client.post( f"{base_url.rstrip('/')}/v1/audio/transcriptions", files={"file": (filename, audio_bytes)}, data={"model": settings.yuanqi_asr_model}, ) if response.status_code >= 400: raise AppError( "service_unavailable", f"元炁转写失败(HTTP {response.status_code}):{response.text[:200]}", status_code=502, ) data = response.json() text = data.get("text") or "" return { "text": text, "engine": "yuanqi.sensevoice", "original": text, # ASR 原转写(与正影文本并存的语义由镜树承载) }