/** * TTS Provider 注册表 * * 集中管理所有语音合成供应商,提供熔断保护和轮转调度。 * 与 LLM 采用相同逻辑:平等轮转,无优先级。 */ import { ProviderRegistry } from '../../common/provider-registry'; import { ITtsProvider } from './provider.interface'; import { AliyunTtsProvider } from './aliyun.provider'; import { MockTtsProvider } from './mock.provider'; import { ttsLogger } from './tts-logger'; import { config } from '../../config'; let _registry: ProviderRegistry | null = null; const TTS_PROVIDER_MAP: Record ITtsProvider> = { bailian: AliyunTtsProvider, }; /** * 初始化 TTS Provider 注册表(懒加载,服务启动时调用一次) */ export function initTtsRegistry(): ProviderRegistry { if (_registry) return _registry; _registry = new ProviderRegistry(); const vendors = (config.models as any).vendors; // 遍历所有 vendor,为有 TTS 模型的供应商注册 TTS Provider if (vendors) { for (const [vendorKey, vendorData] of Object.entries(vendors) as [string, any][]) { const hasTts = vendorData.models?.some((m: any) => m.input?.includes('tts') && m.enabled); if (!hasTts) continue; // 1. 匹配映射表中的 key const ProviderClass = TTS_PROVIDER_MAP[vendorKey]; if (!ProviderClass) { ttsLogger.registry('skip', vendorKey, '无对应 Provider 实现'); continue; } try { // 从 models.json 提取 TTS 模型配置 const ttsModels = (vendorData.models || []) .filter((m: any) => m.input?.includes('tts') && m.enabled); const ttsModel = ttsModels[0]; // 取第一个启用的 TTS 模型 const modelId = ttsModel?.id; // 模型有独立 apiPath 时用它,否则用 vendor 级 ttsApiPath const ttsApiPath = ttsModel?.apiPath ? `https://dashscope.aliyuncs.com${ttsModel.apiPath}` : vendorData.ttsApiPath; const maxTextLen = ttsModel?.maxTextLength; const provider = new ProviderClass(vendorKey, vendorData.apiKey, modelId, ttsApiPath, maxTextLen); _registry.register(provider, { enabled: true, breakerConfig: { name: `${vendorKey}-tts`, failureThreshold: 3, cooldownMs: 60000 }, }); ttsLogger.registry('register', provider.name, `model=${modelId}, vendor=${vendorKey}`); } catch (e) { ttsLogger.error(`注册 ${vendorKey} TTS 失败`, e as Error); } } } // Mock Provider 兜底(当前已禁用,需要时改为 enabled: true) if (!_registry.get('mock-tts')) { const mockProvider = new MockTtsProvider(); _registry.register(mockProvider, { enabled: false, breakerConfig: { name: 'mock-tts', failureThreshold: 999, cooldownMs: 1000 }, }); ttsLogger.registry('register', mockProvider.name, '已禁用'); } ttsLogger.info(`TTS Registry 初始化完成,共 ${_registry.size} 个 Provider`); return _registry; } /** * 获取 TTS Provider 注册表 */ export function getTtsRegistry(): ProviderRegistry { if (!_registry) { return initTtsRegistry(); } return _registry; } /** * 按名称获取特定 TTS Provider(带熔断保护) * @param preferredName 优先使用的 Provider 名称 * @returns 返回可用的 Provider 节点,如果优先的已熔断则返回下一个 */ export function getAvailableTtsProvider(preferredName?: string): { provider: ITtsProvider; node: import('../../common/provider-registry').ProviderNode; } { const registry = getTtsRegistry(); if (preferredName) { const preferredNode = registry.get(preferredName); if (preferredNode && preferredNode.enabled && !preferredNode.breaker.isOpen() && !preferredNode.exhausted) { return { provider: preferredNode.provider, node: preferredNode }; } // 优先的不可用,尝试下一个 const next = registry.getNextAvailable(preferredName); if (next) { ttsLogger.fallback(preferredName, next.provider.name, '优先 Provider 不可用'); return { provider: next.provider, node: next }; } } // 无偏好,用最高优先级 const primary = registry.getPrimary(); if (!primary) { throw new Error('没有可用的 TTS Provider'); } return { provider: primary.provider, node: primary }; } // ============ 定时健康检查 ============ let _healthCheckTimer: ReturnType | null = null; const HEALTH_CHECK_INTERVAL_MS = 30_000; // 30s /** 启动 TTS 定时健康检查 */ export function startTtsHealthCheck(): void { if (_healthCheckTimer) return; _healthCheckTimer = setInterval(async () => { const registry = getTtsRegistry(); // 检查熔断恢复 const broken = registry.listBroken(); for (const node of broken) { try { const state = node.breaker.getState(); if (state === 'HALF_OPEN') { ttsLogger.healthCheck(node.provider.name, 'still_down', '熔断冷却期已过,尝试探测...'); const healthy = await node.provider.healthCheck?.(); if (healthy) { node.breaker.reset(); ttsLogger.circuitBreaker(node.provider.name, 'closed', '探测成功'); } else { ttsLogger.healthCheck(node.provider.name, 'still_down', '探测失败,继续熔断'); } } } catch { // 探测异常,保持熔断 } } // 检查额度耗尽恢复(已标记 exhausted 的供应商恢复探测) const exhausted = registry.listExhausted(); for (const node of exhausted) { try { // 额度耗尽不带 TTL,由健康检查探测恢复 const healthy = await node.provider.healthCheck?.(); if (healthy) { registry.clearExhausted(node.provider.name); ttsLogger.quota(node.provider.name, 'recovered'); } } catch { // 探测失败,保持耗尽 } } }, HEALTH_CHECK_INTERVAL_MS); ttsLogger.info(`TTS HealthCheck 已启动,间隔 ${HEALTH_CHECK_INTERVAL_MS / 1000}s`); } /** 停止 TTS 定时健康检查 */ export function stopTtsHealthCheck(): void { if (_healthCheckTimer) { clearInterval(_healthCheckTimer); _healthCheckTimer = null; ttsLogger.info('TTS HealthCheck 已停止'); } }