All files / modules/tts provider.registry.ts

0% Statements 0/119
0% Branches 0/1
0% Functions 0/1
0% Lines 0/119

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184                                                                                                                                                                                                                                                                                                                                                                               
/**
 * 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<ITtsProvider> | null = null;
 
const TTS_PROVIDER_MAP: Record<string, new (vendorKey: string, apiKey?: string, modelId?: string, ttsApiPath?: string, maxTextLength?: number) => ITtsProvider> = {
  bailian: AliyunTtsProvider,
};
 
/**
 * 初始化 TTS Provider 注册表(懒加载,服务启动时调用一次)
 */
export function initTtsRegistry(): ProviderRegistry<ITtsProvider> {
  if (_registry) return _registry;
 
  _registry = new ProviderRegistry<ITtsProvider>();
 
  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<ITtsProvider> {
  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<ITtsProvider>;
} {
  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<typeof setInterval> | 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 已停止');
  }
}