fix_v3.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. PATH = '/data/ai/audio/server/dist/services/llm/index.js'
  2. with open(PATH, 'r', encoding='utf-8') as f:
  3. src = f.read()
  4. # 替换整个 invokeWithRetry 函数(旧的 + trySwitchModel 调用 + tried.has(resolveProviderKey(next)) 检查)
  5. # 找到从 "async function invokeWithRetry" 到下一个 "async function callLLM" 的整段
  6. OLD = """async function invokeWithRetry(fn, modelId, callType = 'llm_chat') {
  7. const tried = new Map();
  8. let cur = modelId, lastErr = null;
  9. for (let round = 0; round < exports.HA_RETRY_CONFIG.maxFallbackRounds; round++) {
  10. const provider = resolveProviderKey(cur);
  11. // tried check is now done in resolveNodeForModel below; keep this as a guard
  12. if (tried.has(provider))
  13. continue;
  14. let providerError = null;
  15. for (let attempt = 0; attempt < exports.HA_RETRY_CONFIG.maxRetriesPerProvider; attempt++) {
  16. // 用 AsyncLocalStorage 传递 tried set, 让 findVendorForModel 跳过已试 node
  17. const node = resolveNodeForModel(cur, tried);
  18. if (!node) break;
  19. const fp = node.provider.vendor + ':' + (node.provider._apiKey || '').slice(-12);
  20. tried.set(fp, { model: cur });
  21. try {
  22. const result = await _runWithTriedNodes(new Set(tried.keys()), () => fn(cur));
  23. return result;
  24. }
  25. catch (error) {
  26. const em = error?.message || String(error);
  27. lastErr = error;
  28. providerError = error;
  29. if (isBusinessError(em) && !isNetworkError(em)) {
  30. console.error(`[LLM-HA] ${provider}/${cur} 业务错误,立即抛出: ${em.substring(0, 100)}`);
  31. throw error;
  32. }
  33. if (isNetworkError(em)) {
  34. console.warn(`[LLM-HA] ${provider}/${cur} 网络错误,立即切换: ${em.substring(0, 100)}`);
  35. break;
  36. }
  37. if (attempt < exports.HA_RETRY_CONFIG.maxRetriesPerProvider - 1) {
  38. const delay = backoffWithJitter(attempt);
  39. console.log(`[LLM-HA] ${provider}/${cur} 第${attempt + 1}次失败: ${em.substring(0, 100)}, ${delay}ms后重试`);
  40. await new Promise(r => setTimeout(r, delay));
  41. }
  42. }
  43. }
  44. const em = providerError?.message || 'unknown';
  45. const et = isNetworkError(em) ? 'network' : (isBusinessError(em) ? 'business' : 'unknown');
  46. tried.set(provider, { model: cur, error: em, attempts: exports.HA_RETRY_CONFIG.maxRetriesPerProvider, errorType: et });
  47. console.log(`[LLM-HA] ${provider}/${cur} 失败 (${et}),尝试切换供应商`);
  48. const next = trySwitchModel(cur, providerError);
  49. if (!next)
  50. break;
  51. if (tried.has(resolveProviderKey(next))) {
  52. console.warn(`[LLM-HA] 下一供应商已尝试过,停止 fallback`);
  53. break;
  54. }
  55. cur = next;
  56. console.log(`[LLM-HA] 切换到 ${resolveProviderKey(cur)}/${cur}`);
  57. }
  58. throw new AllProvidersFailedError(Array.from(tried.entries()).map(([provider, info]) => ({ provider, ...info })));
  59. }"""
  60. NEW = """async function invokeWithRetry(fn, modelId, callType = 'llm_chat') {
  61. const tried = new Map();
  62. let cur = modelId, lastErr = null;
  63. for (let round = 0; round < exports.HA_RETRY_CONFIG.maxFallbackRounds; round++) {
  64. // 关键: 选下一个未 tried 的 node (而不是用 resolveProviderKey 取第一个匹配)
  65. const node = resolveNodeForModel(cur, tried);
  66. if (!node) {
  67. console.warn(`[LLM-HA] 已尝试所有 node,停止 fallback`);
  68. break;
  69. }
  70. const fp = node.provider.vendor + ':' + (node.provider._apiKey || '').slice(-12);
  71. tried.set(fp, { model: cur });
  72. let providerError = null;
  73. for (let attempt = 0; attempt < exports.HA_RETRY_CONFIG.maxRetriesPerProvider; attempt++) {
  74. try {
  75. const result = await _runWithTriedNodes(new Set(tried.keys()), () => fn(cur));
  76. return result;
  77. }
  78. catch (error) {
  79. const em = error?.message || String(error);
  80. lastErr = error;
  81. providerError = error;
  82. if (isBusinessError(em) && !isNetworkError(em)) {
  83. console.error(`[LLM-HA] ${fp}/${cur} 业务错误,立即抛出: ${em.substring(0, 100)}`);
  84. throw error;
  85. }
  86. if (isNetworkError(em)) {
  87. console.warn(`[LLM-HA] ${fp}/${cur} 网络错误,立即切换: ${em.substring(0, 100)}`);
  88. break;
  89. }
  90. if (attempt < exports.HA_RETRY_CONFIG.maxRetriesPerProvider - 1) {
  91. const delay = backoffWithJitter(attempt);
  92. console.log(`[LLM-HA] ${fp}/${cur} 第${attempt + 1}次失败: ${em.substring(0, 100)}, ${delay}ms后重试`);
  93. await new Promise(r => setTimeout(r, delay));
  94. }
  95. }
  96. }
  97. // 当前 node 失败, 标记其 info
  98. const em = providerError?.message || 'unknown';
  99. const et = isNetworkError(em) ? 'network' : (isBusinessError(em) ? 'business' : 'unknown');
  100. tried.set(fp, { model: cur, error: em, attempts: exports.HA_RETRY_CONFIG.maxRetriesPerProvider, errorType: et });
  101. console.log(`[LLM-HA] ${fp}/${cur} 失败 (${et}),尝试切换供应商`);
  102. // 关键: 自己挑下一个未 tried 的 node, 复用 switchToNextVendorModel 的逻辑但跳过 tried
  103. const next = pickNextModelAfterFailure(cur, tried, node);
  104. if (!next) {
  105. console.warn(`[LLM-HA] 找不到下一个未尝试的供应商`);
  106. break;
  107. }
  108. cur = next;
  109. console.log(`[LLM-HA] 切换到 ${next}`);
  110. }
  111. throw new AllProvidersFailedError(Array.from(tried.entries()).map(([provider, info]) => ({ provider, ...info })));
  112. }
  113. /** 从当前 node 找下一个未 tried 的 node, 返回 next modelId */
  114. function pickNextModelAfterFailure(currentModelId, tried, currentNode) {
  115. const registry = (0, provider_registry_1.getLlmRegistry)();
  116. const available = registry.listAvailable();
  117. if (available.length === 0) return null;
  118. // 关键: 完全按 node 顺序, 跳过 tried 节点
  119. const startIndex = currentNode ? (available.indexOf(currentNode) + 1) % available.length : 0;
  120. for (let i = 0; i < available.length; i++) {
  121. const idx = (startIndex + i) % available.length;
  122. const node = available[idx];
  123. if (node === currentNode) continue;
  124. const fp = node.provider.vendor + ':' + (node.provider._apiKey || '').slice(-12);
  125. if (tried.has(fp)) continue;
  126. // 1. 同名 model
  127. if (node.provider.hasModel(currentModelId)) {
  128. console.log(`[LLM] 同模型切换到供应商 ${node.provider.displayName},模型 ${currentModelId}`);
  129. return currentModelId;
  130. }
  131. // 2. 别名 model
  132. for (const modelId of node.provider.textModels) {
  133. const cfg = node.provider.getModelConfig(modelId);
  134. if (cfg?.canonicalModel === currentModelId) {
  135. console.log(`[LLM] 别名模型切换到供应商 ${node.provider.displayName},模型 ${modelId}`);
  136. return modelId;
  137. }
  138. }
  139. // 3. 第一个 text model
  140. if (node.provider.textModels.length > 0) {
  141. const nextId = node.provider.textModels[0];
  142. console.log(`[LLM] 切换到供应商 ${node.provider.displayName},模型 ${nextId}`);
  143. return nextId;
  144. }
  145. }
  146. return null;
  147. }"""
  148. if OLD not in src:
  149. print("ERROR: OLD invokeWithRetry not found exactly")
  150. # 看一下当前 invokeWithRetry 实际
  151. import re
  152. m = re.search(r'async function invokeWithRetry.*?throw new AllProvidersFailedError.*?;', src, re.DOTALL)
  153. if m:
  154. print("CURRENT (first 500 chars):")
  155. print(m.group(0)[:500])
  156. raise SystemExit(1)
  157. src = src.replace(OLD, NEW)
  158. with open(PATH, 'w', encoding='utf-8') as f:
  159. f.write(src)
  160. print("OK: invokeWithRetry v3 patched (skip tried nodes via pickNextModelAfterFailure)")