From 00851e76dab1511cbe3744d85bd2df909ad19554 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Tue, 2 Jun 2026 22:16:06 +0700 Subject: [PATCH] =?UTF-8?q?There=20are=20**no=20staged=20changes**=20to=20?= =?UTF-8?q?work=20with=20=E2=80=94=20the=20working=20tree=20is=20clean=20a?= =?UTF-8?q?nd=20`git=20diff=20--cached`=20returned=20nothing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai-moderation/llmModerationClient.ts | 74 +++++++++++++------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts b/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts index 9a184bc..8682c80 100644 --- a/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts +++ b/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts @@ -504,6 +504,14 @@ function hasMediaContent( // Single-image vision analysis (reused by both text-only and media paths) // --------------------------------------------------------------------------- +/** + * In-flight deduplication map — prevents concurrent vision API calls for + * the same cache key. Multiple concurrent requests for an identical image + * share the same promise, eliminating the race condition between cache + * check and cache write. + */ +const inFlightVisionCalls = new Map>(); + const analyzeSingleMediaImage = async ( messageId: string, image: MessageImagePart, @@ -520,35 +528,59 @@ const analyzeSingleMediaImage = async ( return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${cached}`; } + // Deduplicate in-flight vision calls: if another caller is already + // processing this exact image, wait for it instead of starting a duplicate. + const existing = inFlightVisionCalls.get(cacheKey); + if (existing) { + log.debug({ cacheKey }, "Media analysis in-flight dedupe — waiting for existing call"); + const result = await existing; + if (!result) return null; + return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${result}`; + } + const promptText = image.stickerName ? buildStickerVisionPrompt(image.stickerName, messageId) : image.customEmojiName ? buildCustomEmojiVisionPrompt(image.customEmojiName, messageId) : buildGeneralImageVisionPrompt(image.sourceLabel, messageId); + const visionPromise = (async (): Promise => { + try { + const content = await llmVision(promptText, image.image_url); + if (!content) return null; + + await upsertCachedMediaAnalysis( + cacheKey, + content, + "vision_llm", + Date.now() + 24 * 60 * 60 * 1000, + ); + + return content; + } catch (error) { + log.warn( + { + messageId, + error: error instanceof Error ? error.message : String(error), + }, + "Vision analysis failed after retries — image not analyzed", + ); + // Return a clear signal that vision analysis FAILED — so batch LLM knows + // the image could not be described and must NOT assume it's clean. + return null; + } + })(); + + inFlightVisionCalls.set(cacheKey, visionPromise); + try { - const content = await llmVision(promptText, image.image_url); - if (!content) return null; - - await upsertCachedMediaAnalysis( - cacheKey, - content, - "vision_llm", - Date.now() + 24 * 60 * 60 * 1000, - ); - + const content = await visionPromise; + if (!content) { + return `[Media analysis for message ${messageId}] ${image.sourceLabel}: GAGAL DIANALISIS — gambar tidak dapat diunduh atau vision API gagal setelah 3x percobaan. JANGAN mengasumsikan gambar aman hanya karena gagal dianalisis. Gunakan metadata URL/nama file saja sebagai petunjuk.`; + } return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${content}`; - } catch (error) { - log.warn( - { - messageId, - error: error instanceof Error ? error.message : String(error), - }, - "Vision analysis failed after retries — image not analyzed", - ); - // Return a clear signal that vision analysis FAILED — so batch LLM knows - // the image could not be described and must NOT assume it's clean. - return `[Media analysis for message ${messageId}] ${image.sourceLabel}: GAGAL DIANALISIS — gambar tidak dapat diunduh atau vision API gagal setelah 3x percobaan. JANGAN mengasumsikan gambar aman hanya karena gagal dianalisis. Gunakan metadata URL/nama file saja sebagai petunjuk.`; + } finally { + inFlightVisionCalls.delete(cacheKey); } };