From 049582504e25e92c906500ed79361e71a04e04dd Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Fri, 5 Jun 2026 18:47:30 +0700 Subject: [PATCH] feat(ai-moderation): implement error handling and robust streaming for llmClient Wrap the LLM completion logic in a try-catch block to provide detailed error logging, including status codes and raw response data, when API requests fail. - Add comprehensive error logging for failed LLM API calls. - Ensure streaming responses are correctly aggregated and returned even when wrapped in error handling logic. --- .../src/modules/ai-moderation/llmClient.ts | 85 +++++++++++-------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/services/discord-gateway/src/modules/ai-moderation/llmClient.ts b/services/discord-gateway/src/modules/ai-moderation/llmClient.ts index 5c96ed6..f238691 100644 --- a/services/discord-gateway/src/modules/ai-moderation/llmClient.ts +++ b/services/discord-gateway/src/modules/ai-moderation/llmClient.ts @@ -100,46 +100,59 @@ export async function llmChat( return retryWithBackoff( async () => { return withLlmConcurrency(async () => { - const response = await client.chat.completions.create(params); - if (stream) { - let content = ""; - let finishReason = "stop"; - for await (const chunk of response as any) { - const choice = chunk?.choices?.[0]; - - // Dynamic parsing to support multiple providers (OpenAI, Ollama, Groq, Anthropic via proxy, etc.) - const textChunk = - choice?.delta?.content || - choice?.message?.content || - choice?.text || - chunk?.message?.content || - chunk?.response || - chunk?.content || - ""; + try { + const response = await client.chat.completions.create(params); + if (stream) { + let content = ""; + let finishReason = "stop"; + for await (const chunk of response as any) { + const choice = chunk?.choices?.[0]; - content += textChunk; - - const fr = choice?.finish_reason || chunk?.finish_reason; - if (fr) { - finishReason = fr; + // Dynamic parsing to support multiple providers (OpenAI, Ollama, Groq, Anthropic via proxy, etc.) + const textChunk = + choice?.delta?.content || + choice?.message?.content || + choice?.text || + chunk?.message?.content || + chunk?.response || + chunk?.content || + ""; + + content += textChunk; + + const fr = choice?.finish_reason || chunk?.finish_reason; + if (fr) { + finishReason = fr; + } } + return { + id: 'stream-aggregated', + choices: [ + { + message: { role: 'assistant', content, refusal: null }, + finish_reason: finishReason, + index: 0, + logprobs: null, + }, + ], + created: Math.floor(Date.now() / 1000), + model: model, + object: 'chat.completion', + } as OpenAI.Chat.Completions.ChatCompletion; } - return { - id: 'stream-aggregated', - choices: [ - { - message: { role: 'assistant', content, refusal: null }, - finish_reason: finishReason, - index: 0, - logprobs: null, - }, - ], - created: Math.floor(Date.now() / 1000), - model: model, - object: 'chat.completion', - } as OpenAI.Chat.Completions.ChatCompletion; + return response as OpenAI.Chat.Completions.ChatCompletion; + } catch (error: any) { + log.error( + { + error: error.message, + status: error.status, + rawResponse: error.error || error.body || error.response?.data || "N/A", + model + }, + "LLM API request failed" + ); + throw error; } - return response as OpenAI.Chat.Completions.ChatCompletion; }); }, {