Calmlo commited on
Commit
e706cfb
·
verified ·
1 Parent(s): 5de0798

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +223 -130
server.js CHANGED
@@ -22,16 +22,8 @@ if (falKeys.length === 0) {
22
 
23
  console.log(`Loaded ${falKeys.length} Fal AI Keys.`);
24
 
25
- let currentFalKeyIndex = 0;
26
-
27
- // 获取下一个 Fal Key 并循环
28
- function getNextFalKey() {
29
- const key = falKeys[currentFalKeyIndex];
30
- const usedIndex = currentFalKeyIndex; // 记录本次使用的索引,用于日志
31
- currentFalKeyIndex = (currentFalKeyIndex + 1) % falKeys.length; // 移动到下一个,如果到末尾则回到开头
32
- console.log(`Using Fal Key at index: ${usedIndex}`);
33
- return key;
34
- }
35
  // --- End Key Management ---
36
 
37
 
@@ -40,11 +32,6 @@ if (!API_KEY) {
40
  process.exit(1);
41
  }
42
 
43
- // 注意:不再在这里全局配置 fal.config
44
- // fal.config({
45
- // credentials: FAL_KEY, // 移除这行
46
- // });
47
-
48
  const app = express();
49
  app.use(express.json({ limit: '50mb' }));
50
  app.use(express.urlencoded({ extended: true, limit: '50mb' }));
@@ -223,23 +210,29 @@ function convertMessagesToFalPrompt(messages) {
223
 
224
  let final_system_prompt = "";
225
 
 
226
  const hasFixedSystem = fixed_system_prompt_content.length > 0;
227
  const hasSystemHistory = system_prompt_history_content.length > 0;
228
 
229
  if (hasFixedSystem && hasSystemHistory) {
 
230
  final_system_prompt = fixed_system_prompt_content + SEPARATOR + system_prompt_history_content;
231
  console.log("Combining fixed system prompt and history with separator.");
232
  } else if (hasFixedSystem) {
 
233
  final_system_prompt = fixed_system_prompt_content;
234
  console.log("Using only fixed system prompt.");
235
  } else if (hasSystemHistory) {
 
236
  final_system_prompt = system_prompt_history_content;
237
  console.log("Using only history in system prompt slot.");
238
  }
 
239
 
 
240
  const result = {
241
- system_prompt: final_system_prompt,
242
- prompt: final_prompt
243
  };
244
 
245
  console.log(`Final system_prompt length (Sys+Separator+Hist): ${result.system_prompt.length}`);
@@ -250,149 +243,249 @@ function convertMessagesToFalPrompt(messages) {
250
  // === convertMessagesToFalPrompt 函数结束 ===
251
 
252
 
253
- // POST /v1/chat/completions endpoint (主要修改处)
254
  app.post('/v1/chat/completions', async (req, res) => {
255
  const { model, messages, stream = false, reasoning = false, ...restOpenAIParams } = req.body;
 
256
 
257
- console.log(`Received chat completion request for model: ${model}, stream: ${stream}`);
258
 
259
  if (!FAL_SUPPORTED_MODELS.includes(model)) {
260
- console.warn(`Warning: Requested model '${model}' is not in the explicitly supported list.`);
261
  }
262
  if (!model || !messages || !Array.isArray(messages) || messages.length === 0) {
263
- console.error("Invalid request parameters:", { model, messages: Array.isArray(messages) ? messages.length : typeof messages });
264
  return res.status(400).json({ error: 'Missing or invalid parameters: model and messages array are required.' });
265
  }
266
 
267
- try {
268
- // *** 在处理请求前,获取下一个 Fal Key 并配置 fal 客户端 ***
269
- const selectedFalKey = getNextFalKey();
270
- fal.config({
271
- credentials: selectedFalKey,
272
- });
273
- // *********************************************************
274
-
275
- const { prompt, system_prompt } = convertMessagesToFalPrompt(messages);
276
-
277
- const falInput = {
278
- model: model,
279
- prompt: prompt,
280
- ...(system_prompt && { system_prompt: system_prompt }),
281
- reasoning: !!reasoning,
282
- };
283
- console.log("Fal Input:", JSON.stringify(falInput, null, 2));
284
- console.log("Forwarding request to fal-ai with system-priority + separator + recency input:");
285
- console.log("System Prompt Length:", system_prompt?.length || 0);
286
- console.log("Prompt Length:", prompt?.length || 0);
287
- console.log("--- System Prompt Start ---");
288
- console.log(system_prompt);
289
- console.log("--- System Prompt End ---");
290
- console.log("--- Prompt Start ---");
291
- console.log(prompt);
292
- console.log("--- Prompt End ---");
293
-
294
-
295
- // --- 流式/非流���处理逻辑 (保持不变) ---
296
- if (stream) {
297
- res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
298
- res.setHeader('Cache-Control', 'no-cache');
299
- res.setHeader('Connection', 'keep-alive');
300
- res.setHeader('Access-Control-Allow-Origin', '*');
301
- res.flushHeaders();
302
-
303
- let previousOutput = '';
304
-
305
- const falStream = await fal.stream("fal-ai/any-llm", { input: falInput });
306
 
307
- try {
308
- for await (const event of falStream) {
309
- const currentOutput = (event && typeof event.output === 'string') ? event.output : '';
310
- const isPartial = (event && typeof event.partial === 'boolean') ? event.partial : true;
311
- const errorInfo = (event && event.error) ? event.error : null;
312
-
313
- if (errorInfo) {
314
- console.error("Error received in fal stream event:", errorInfo);
315
- const errorChunk = { id: `chatcmpl-${Date.now()}-error`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model, choices: [{ index: 0, delta: {}, finish_reason: "error", message: { role: 'assistant', content: `Fal Stream Error: ${JSON.stringify(errorInfo)}` } }] };
316
- res.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
317
- break; // Stop processing on error
318
- }
 
 
319
 
320
- let deltaContent = '';
321
- if (currentOutput.startsWith(previousOutput)) {
322
- deltaContent = currentOutput.substring(previousOutput.length);
323
- } else if (currentOutput.length > 0) {
324
- console.warn("Fal stream output mismatch detected. Sending full current output as delta.", { previousLength: previousOutput.length, currentLength: currentOutput.length });
325
- deltaContent = currentOutput;
326
- previousOutput = ''; // Reset previous if mismatch
327
- }
328
- previousOutput = currentOutput;
329
 
330
- // Send chunk if there's content or if it's the final chunk (isPartial is false)
331
- if (deltaContent || !isPartial) {
332
- const openAIChunk = { id: `chatcmpl-${Date.now()}`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model, choices: [{ index: 0, delta: { content: deltaContent }, finish_reason: isPartial === false ? "stop" : null }] };
333
- res.write(`data: ${JSON.stringify(openAIChunk)}\n\n`);
334
- }
335
- }
336
- // After the loop, ensure the [DONE] signal is sent if the stream finished normally
337
- if (!res.writableEnded) {
338
- res.write(`data: [DONE]\n\n`);
339
- res.end();
340
- console.log("Stream finished and [DONE] sent.");
341
- }
342
 
343
- } catch (streamError) {
344
- console.error('Error during fal stream processing loop:', streamError);
 
 
 
 
345
  try {
346
- if (!res.writableEnded) { // Check if we can still write to the response
347
- const errorDetails = (streamError instanceof Error) ? streamError.message : JSON.stringify(streamError);
348
- res.write(`data: ${JSON.stringify({ error: { message: "Stream processing error", type: "proxy_error", details: errorDetails } })}\n\n`);
349
- res.write(`data: [DONE]\n\n`); // Send DONE even after error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  res.end();
351
- } else {
352
- console.error("Stream already ended, cannot send error message.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  }
354
- } catch (finalError) {
355
- console.error('Error sending stream error message to client:', finalError);
356
- if (!res.writableEnded) { res.end(); }
357
  }
358
- }
359
- } else {
360
- // --- 非流式处理 (保持不变) ---
361
- console.log("Executing non-stream request...");
362
- const result = await fal.subscribe("fal-ai/any-llm", { input: falInput, logs: true });
363
- console.log("Received non-stream result from fal-ai:", JSON.stringify(result, null, 2));
364
-
365
- if (result && result.error) {
366
- console.error("Fal-ai returned an error in non-stream mode:", result.error);
367
- return res.status(500).json({ object: "error", message: `Fal-ai error: ${JSON.stringify(result.error)}`, type: "fal_ai_error", param: null, code: null });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  }
369
 
370
- const openAIResponse = {
371
- id: `chatcmpl-${result.requestId || Date.now()}`, object: "chat.completion", created: Math.floor(Date.now() / 1000), model: model,
372
- choices: [{ index: 0, message: { role: "assistant", content: result.output || "" }, finish_reason: "stop" }],
373
- usage: { prompt_tokens: null, completion_tokens: null, total_tokens: null }, system_fingerprint: null,
374
- ...(result.reasoning && { fal_reasoning: result.reasoning }),
375
- };
376
- res.json(openAIResponse);
377
- console.log("Returned non-stream response.");
 
 
 
 
 
 
 
 
 
 
378
  }
 
379
 
380
- } catch (error) {
381
- console.error('Unhandled error in /v1/chat/completions:', error);
 
382
  if (!res.headersSent) {
383
- const errorMessage = (error instanceof Error) ? error.message : JSON.stringify(error);
384
- res.status(500).json({ error: 'Internal Server Error in Proxy', details: errorMessage });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  } else if (!res.writableEnded) {
386
- console.error("Headers already sent, ending response.");
387
- res.end();
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  }
389
  }
 
390
  });
391
 
392
  // 启动服务器 (更新启动信息)
393
  app.listen(PORT, () => {
394
  console.log(`===================================================`);
395
- console.log(` Fal OpenAI Proxy Server (Key Rotation + System Top + Separator + Recency)`);
396
  console.log(` Listening on port: ${PORT}`);
397
  console.log(` Loaded ${falKeys.length} Fal AI Keys for rotation.`);
398
  console.log(` Using Limits: System Prompt=${SYSTEM_PROMPT_LIMIT}, Prompt=${PROMPT_LIMIT}`);
@@ -404,5 +497,5 @@ app.listen(PORT, () => {
404
 
405
  // 根路径响应 (更新信息)
406
  app.get('/', (req, res) => {
407
- res.send('Fal OpenAI Proxy (Key Rotation + System Top + Separator + Recency Strategy) is running.');
408
  });
 
22
 
23
  console.log(`Loaded ${falKeys.length} Fal AI Keys.`);
24
 
25
+ let currentFalKeyIndex = 0; // Index of the *next* key to try for a *new* request
26
+
 
 
 
 
 
 
 
 
27
  // --- End Key Management ---
28
 
29
 
 
32
  process.exit(1);
33
  }
34
 
 
 
 
 
 
35
  const app = express();
36
  app.use(express.json({ limit: '50mb' }));
37
  app.use(express.urlencoded({ extended: true, limit: '50mb' }));
 
210
 
211
  let final_system_prompt = "";
212
 
213
+ // 检查各部分是否有内容 (使用 trim 后的固定部分)
214
  const hasFixedSystem = fixed_system_prompt_content.length > 0;
215
  const hasSystemHistory = system_prompt_history_content.length > 0;
216
 
217
  if (hasFixedSystem && hasSystemHistory) {
218
+ // 两部分都有,用分隔符连接
219
  final_system_prompt = fixed_system_prompt_content + SEPARATOR + system_prompt_history_content;
220
  console.log("Combining fixed system prompt and history with separator.");
221
  } else if (hasFixedSystem) {
222
+ // 只有固定部分
223
  final_system_prompt = fixed_system_prompt_content;
224
  console.log("Using only fixed system prompt.");
225
  } else if (hasSystemHistory) {
226
+ // 只有历史部分 (固定部分为空)
227
  final_system_prompt = system_prompt_history_content;
228
  console.log("Using only history in system prompt slot.");
229
  }
230
+ // 如果两部分都为空,final_system_prompt 保持空字符串 ""
231
 
232
+ // 6. 返回结果
233
  const result = {
234
+ system_prompt: final_system_prompt, // 最终结果不需要再 trim
235
+ prompt: final_prompt // final_prompt 在组合前已 trim
236
  };
237
 
238
  console.log(`Final system_prompt length (Sys+Separator+Hist): ${result.system_prompt.length}`);
 
243
  // === convertMessagesToFalPrompt 函数结束 ===
244
 
245
 
246
+ // POST /v1/chat/completions endpoint (带 Key 重试逻辑)
247
  app.post('/v1/chat/completions', async (req, res) => {
248
  const { model, messages, stream = false, reasoning = false, ...restOpenAIParams } = req.body;
249
+ const requestId = `req-${Date.now()}`; // Unique ID for this incoming request
250
 
251
+ console.log(`[${requestId}] Received chat completion request for model: ${model}, stream: ${stream}`);
252
 
253
  if (!FAL_SUPPORTED_MODELS.includes(model)) {
254
+ console.warn(`[${requestId}] Warning: Requested model '${model}' is not in the explicitly supported list.`);
255
  }
256
  if (!model || !messages || !Array.isArray(messages) || messages.length === 0) {
257
+ console.error(`[${requestId}] Invalid request parameters:`, { model, messages: Array.isArray(messages) ? messages.length : typeof messages });
258
  return res.status(400).json({ error: 'Missing or invalid parameters: model and messages array are required.' });
259
  }
260
 
261
+ let lastError = null; // Store the last error encountered during key rotation
262
+ let success = false; // Flag to indicate if any key succeeded
263
+
264
+ // *** 重试循环:尝试最多 falKeys.length 次 ***
265
+ for (let attempt = 0; attempt < falKeys.length; attempt++) {
266
+ const keyIndexToTry = (currentFalKeyIndex + attempt) % falKeys.length;
267
+ const selectedFalKey = falKeys[keyIndexToTry];
268
+ console.log(`[${requestId}] Attempt ${attempt + 1}/${falKeys.length}: Trying Fal Key at index ${keyIndexToTry}`);
269
+
270
+ try {
271
+ // 配置 fal 客户端 (每次尝试都重新配置)
272
+ fal.config({
273
+ credentials: selectedFalKey,
274
+ });
275
+
276
+ // 准备 Fal Input (只需要准备一次)
277
+ // 注意:如果 convertMessagesToFalPrompt 很耗时,可以移到循环外
278
+ const { prompt, system_prompt } = convertMessagesToFalPrompt(messages);
279
+ const falInput = {
280
+ model: model,
281
+ prompt: prompt,
282
+ ...(system_prompt && { system_prompt: system_prompt }),
283
+ reasoning: !!reasoning,
284
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
+ // 打印一次 Fal Input 和 Prompt 信息
287
+ if (attempt === 0) {
288
+ console.log(`[${requestId}] Fal Input:`, JSON.stringify(falInput, null, 2));
289
+ console.log(`[${requestId}] Forwarding request to fal-ai with system-priority + separator + recency input:`);
290
+ console.log(`[${requestId}] System Prompt Length:`, system_prompt?.length || 0);
291
+ console.log(`[${requestId}] Prompt Length:`, prompt?.length || 0);
292
+ // 为了简洁,默认注释掉详细内容的打印
293
+ // console.log(`[${requestId}] --- System Prompt Start ---`);
294
+ // console.log(system_prompt);
295
+ // console.log(`[${requestId}] --- System Prompt End ---`);
296
+ // console.log(`[${requestId}] --- Prompt Start ---`);
297
+ // console.log(prompt);
298
+ // console.log(`[${requestId}] --- Prompt End ---`);
299
+ }
300
 
301
+ // --- 执行 Fal AI 调用 ---
302
+ if (stream) {
303
+ // --- 流式处理 ---
304
+ res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
305
+ res.setHeader('Cache-Control', 'no-cache');
306
+ res.setHeader('Connection', 'keep-alive');
307
+ res.setHeader('Access-Control-Allow-Origin', '*');
308
+ // 注意:Headers 只能发送一次
 
309
 
310
+ let previousOutput = '';
311
+ const falStream = await fal.stream("fal-ai/any-llm", { input: falInput });
 
 
 
 
 
 
 
 
 
 
312
 
313
+ // 标记成功,设置下一次请求的起始 Key 索引
314
+ success = true;
315
+ currentFalKeyIndex = (keyIndexToTry + 1) % falKeys.length;
316
+ console.log(`[${requestId}] Key at index ${keyIndexToTry} successful. Next request starts at index ${currentFalKeyIndex}.`);
317
+
318
+ // 处理流
319
  try {
320
+ if (!res.headersSent) { // 确保 header 只发送一次
321
+ res.flushHeaders();
322
+ console.log(`[${requestId}] Stream headers flushed.`);
323
+ }
324
+ for await (const event of falStream) {
325
+ // ... (流处理逻辑基本不变,添加 requestId 用于日志) ...
326
+ const currentOutput = (event && typeof event.output === 'string') ? event.output : '';
327
+ const isPartial = (event && typeof event.partial === 'boolean') ? event.partial : true;
328
+ const errorInfo = (event && event.error) ? event.error : null;
329
+
330
+ if (errorInfo) {
331
+ console.error(`[${requestId}] Error received in fal stream event:`, errorInfo);
332
+ const errorChunk = { id: `chatcmpl-${Date.now()}-error`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model, choices: [{ index: 0, delta: {}, finish_reason: "error", message: { role: 'assistant', content: `Fal Stream Error: ${JSON.stringify(errorInfo)}` } }] };
333
+ if (!res.writableEnded) {
334
+ res.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
335
+ }
336
+ break; // Stop processing on error in stream event
337
+ }
338
+
339
+ let deltaContent = '';
340
+ if (currentOutput.startsWith(previousOutput)) {
341
+ deltaContent = currentOutput.substring(previousOutput.length);
342
+ } else if (currentOutput.length > 0) {
343
+ console.warn(`[${requestId}] Fal stream output mismatch detected. Sending full current output as delta.`, { previousLength: previousOutput.length, currentLength: currentOutput.length });
344
+ deltaContent = currentOutput;
345
+ previousOutput = ''; // Reset previous if mismatch
346
+ }
347
+ previousOutput = currentOutput;
348
+
349
+ // Send chunk if there's content or if it's the final chunk (isPartial is false)
350
+ if (deltaContent || !isPartial) {
351
+ const openAIChunk = { id: `chatcmpl-${Date.now()}`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model, choices: [{ index: 0, delta: { content: deltaContent }, finish_reason: isPartial === false ? "stop" : null }] };
352
+ if (!res.writableEnded) {
353
+ res.write(`data: ${JSON.stringify(openAIChunk)}\n\n`);
354
+ }
355
+ }
356
+ }
357
+ // After the loop, ensure the [DONE] signal is sent if the stream finished normally
358
+ if (!res.writableEnded) {
359
+ res.write(`data: [DONE]\n\n`);
360
  res.end();
361
+ console.log(`[${requestId}] Stream finished and [DONE] sent.`);
362
+ }
363
+ } catch (streamError) {
364
+ console.error(`[${requestId}] Error during fal stream processing loop:`, streamError);
365
+ lastError = streamError; // Store error from stream processing
366
+ try {
367
+ // Don't mark success=false here, the key worked but the stream itself failed.
368
+ // The outer loop should break because the response has likely been ended.
369
+ if (!res.writableEnded) { // Check if we can still write to the response
370
+ const errorDetails = (streamError instanceof Error) ? streamError.message : JSON.stringify(streamError);
371
+ res.write(`data: ${JSON.stringify({ error: { message: "Stream processing error", type: "proxy_error", details: errorDetails } })}\n\n`);
372
+ res.write(`data: [DONE]\n\n`); // Send DONE even after error
373
+ res.end();
374
+ } else {
375
+ console.error(`[${requestId}] Stream already ended, cannot send error message.`);
376
+ }
377
+ } catch (finalError) {
378
+ console.error(`[${requestId}] Error sending stream error message to client:`, finalError);
379
+ if (!res.writableEnded) { res.end(); }
380
  }
381
+ // Break the outer key retry loop as the stream failed mid-way
382
+ break;
 
383
  }
384
+ // 如果流成功处理完,直接跳出重试循环
385
+ break; // Exit the key retry loop
386
+
387
+ } else {
388
+ // --- 非流式处理 ---
389
+ console.log(`[${requestId}] Executing non-stream request with key index ${keyIndexToTry}...`);
390
+ const result = await fal.subscribe("fal-ai/any-llm", { input: falInput, logs: true });
391
+
392
+ // 检查 Fal AI 返回的业务错误 (例如输入无效),这种错误不应该因为换 Key 而解决
393
+ if (result && result.error) {
394
+ console.error(`[${requestId}] Fal-ai returned a business error with key index ${keyIndexToTry}:`, result.error);
395
+ // 将此视为最终错误,不重试其他 key
396
+ lastError = new Error(`Fal-ai error: ${JSON.stringify(result.error)}`);
397
+ lastError.status = 500; // Or map from Fal error if possible, default 500
398
+ lastError.type = "fal_ai_error";
399
+ break; // Exit retry loop, no point trying other keys for bad input
400
+ }
401
+
402
+ console.log(`[${requestId}] Received non-stream result from fal-ai with key index ${keyIndexToTry}`);
403
+ // console.log("Full non-stream result:", JSON.stringify(result, null, 2)); // Uncomment for detailed logs
404
+
405
+ // 标记成功,设置下一次请求的起始 Key 索引
406
+ success = true;
407
+ currentFalKeyIndex = (keyIndexToTry + 1) % falKeys.length;
408
+ console.log(`[${requestId}] Key at index ${keyIndexToTry} successful. Next request starts at index ${currentFalKeyIndex}.`);
409
+
410
+
411
+ const openAIResponse = {
412
+ id: `chatcmpl-${result.requestId || Date.now()}`, object: "chat.completion", created: Math.floor(Date.now() / 1000), model: model,
413
+ choices: [{ index: 0, message: { role: "assistant", content: result.output || "" }, finish_reason: "stop" }],
414
+ usage: { prompt_tokens: null, completion_tokens: null, total_tokens: null }, // Fal doesn't provide token usage
415
+ system_fingerprint: null, // Fal doesn't provide system fingerprint
416
+ ...(result.reasoning && { fal_reasoning: result.reasoning }), // Include reasoning if present
417
+ };
418
+ res.json(openAIResponse);
419
+ console.log(`[${requestId}] Returned non-stream response.`);
420
+ break; // 成功,跳出重试循环
421
  }
422
 
423
+ } catch (error) {
424
+ lastError = error; // Store the error from this attempt
425
+ const status = error?.status; // Fal client errors should have status
426
+ const errorMessage = error?.body?.detail || error?.message || 'Unknown error'; // Get detailed message if possible
427
+
428
+ console.warn(`[${requestId}] Attempt ${attempt + 1} with key index ${keyIndexToTry} failed. Status: ${status || 'N/A'}, Message: ${errorMessage}`);
429
+
430
+ // 检查是否是与 Key 相关的错误 (401 Unauthorized, 403 Forbidden, 429 Rate Limit)
431
+ if (status === 401 || status === 403 || status === 429) {
432
+ console.log(`[${requestId}] Key-related error (${status}). Trying next key...`);
433
+ // 继续循环尝试下一个 Key
434
+ } else {
435
+ // 如果是其他类型的错误 (如网络问题、Fal内部服务器错误5xx、请求参数错误400等)
436
+ // 通常重试其他 Key 没有意义,直接中断重试
437
+ console.error(`[${requestId}] Unrecoverable error encountered. Status: ${status || 'N/A'}. Stopping key rotation for this request.`);
438
+ console.error("Error details:", error); // Log the full error object for debugging
439
+ break; // 跳出重试循环
440
+ }
441
  }
442
+ } // --- 结束重试循环 ---
443
 
444
+ // 如果循环结束了还没有成功 (所有 Key 都失败了或遇到不可恢复错误)
445
+ if (!success) {
446
+ console.error(`[${requestId}] All Fal Key attempts failed or an unrecoverable error occurred.`);
447
  if (!res.headersSent) {
448
+ const statusCode = lastError?.status || 503; // Use status from last error if available, default to 503 Service Unavailable
449
+ const errorMessage = (lastError instanceof Error) ? lastError.message : JSON.stringify(lastError);
450
+ // Try to extract a more specific message if available
451
+ const detailMessage = lastError?.body?.detail || errorMessage;
452
+ const errorType = lastError?.type || (statusCode === 401 || statusCode === 403 || statusCode === 429 ? "key_error" : "proxy_error");
453
+
454
+ console.error(`[${requestId}] Sending final error response. Status: ${statusCode}, Type: ${errorType}, Message: ${detailMessage}`);
455
+
456
+ // 返回一个标准的 OpenAI 错误格式
457
+ res.status(statusCode).json({
458
+ object: "error",
459
+ message: `All Fal Key attempts failed or an unrecoverable error occurred. Last error: ${detailMessage}`,
460
+ type: errorType,
461
+ param: null,
462
+ code: statusCode === 429 ? "rate_limit_exceeded" : (statusCode === 401 || statusCode === 403 ? "invalid_api_key" : null)
463
+ });
464
  } else if (!res.writableEnded) {
465
+ console.error(`[${requestId}] Headers already sent, but request failed after stream started or during processing. Ending response with error chunk.`);
466
+ // 尝试在流式响应中发送错误(如果可能)
467
+ try {
468
+ const errorDetails = (lastError instanceof Error) ? lastError.message : JSON.stringify(lastError);
469
+ const detailMessage = lastError?.body?.detail || errorDetails;
470
+ const errorChunk = { id: `chatcmpl-${Date.now()}-final-error`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model, choices: [{ index: 0, delta: {}, finish_reason: "error", message: { role: 'assistant', content: `Proxy Error: All key attempts failed or stream processing error. Last error: ${detailMessage}` } }] };
471
+ res.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
472
+ res.write(`data: [DONE]\n\n`);
473
+ res.end();
474
+ } catch (e) {
475
+ console.error(`[${requestId}] Failed to write final error to stream:`, e);
476
+ if (!res.writableEnded) res.end(); // Force end if possible
477
+ }
478
+ } else {
479
+ console.error(`[${requestId}] Request failed, but response stream was already fully ended. Cannot send error.`);
480
  }
481
  }
482
+
483
  });
484
 
485
  // 启动服务器 (更新启动信息)
486
  app.listen(PORT, () => {
487
  console.log(`===================================================`);
488
+ console.log(` Fal OpenAI Proxy Server (Key Rotation with Retry + System Top + Separator + Recency)`); // 更新描述
489
  console.log(` Listening on port: ${PORT}`);
490
  console.log(` Loaded ${falKeys.length} Fal AI Keys for rotation.`);
491
  console.log(` Using Limits: System Prompt=${SYSTEM_PROMPT_LIMIT}, Prompt=${PROMPT_LIMIT}`);
 
497
 
498
  // 根路径响应 (更新信息)
499
  app.get('/', (req, res) => {
500
+ res.send('Fal OpenAI Proxy (Key Rotation with Retry + System Top + Separator + Recency Strategy) is running.'); // 更新描述
501
  });