diff --git a/services/discord-gateway/src/modules/ai-moderation/mediaBatchProcessor.ts b/services/discord-gateway/src/modules/ai-moderation/mediaBatchProcessor.ts index 1d411fa..843af45 100644 --- a/services/discord-gateway/src/modules/ai-moderation/mediaBatchProcessor.ts +++ b/services/discord-gateway/src/modules/ai-moderation/mediaBatchProcessor.ts @@ -63,10 +63,9 @@ export async function runMediaBatch( channelCulture, }); - // Per-message blocks (from prepareMediaMessage) carry their own - // history; personal profile descriptions are omitted - // (they bloat the prompt and add a per-user DB round-trip for little - // moderation signal — see textBatchProcessor). + // Per-message blocks (from prepareMediaMessage) contain the message + // content + reference/reply context only — no per-user reputation or + // profile context is injected (kept minimal per user request). const messagesBlock = prepared.map((p) => p.messageBlock).join("\n"); // Data/instruction separation: the system prompt is stable per mode — all // per-batch context (conversation) lives in the USER payload, ordered diff --git a/services/discord-gateway/src/modules/ai-moderation/prompts/output.ts b/services/discord-gateway/src/modules/ai-moderation/prompts/output.ts index 152d2a5..ab69684 100644 --- a/services/discord-gateway/src/modules/ai-moderation/prompts/output.ts +++ b/services/discord-gateway/src/modules/ai-moderation/prompts/output.ts @@ -35,10 +35,10 @@ Instruksi per field: - "message_id": WAJIB sama persis dengan id di input. Setiap di menghasilkan SATU hasil. Jangan gabungkan beberapa pesan, jangan lewati, jangan karang id. - "evidence": kutipan PERSIS frasa yang melanggar (maks 1 baris). Pelanggaran di gambar/sticker → kutip deskripsi Media analysis. Pelanggaran lewat balasan/referensi → sebut konteks pesan yang dibalas. Boleh tambah label sumber, mis. [media analysis] / [web_search] / [reply]. Kosong jika clean. -## PERSONALITY & MEMORI — Reputasi Pengguna dan Kultur Channel -Data konteks tersedia: (skor trust + histori infraction + repeat_offender), dan (topik/vibe channel). +## KONTEKS — Kultur Channel +Data konteks tersedia: (topik/vibe channel). Tidak ada data profil/reputasi per-user — nilai tiap pesan murni dari isinya. Gunakan untuk personalisasi analysis, tapi: -- Profil/history adalah KONTEKS, bukan bukti. Profil mencurigakan ≠ flag; profil bersih ≠ loloskan pelanggaran. (kutipan pesan pernah di-flag) = cari POLA berulang (spam link SAMA, provokasi berulang konten SAMA); JANGAN gunakan untuk "menginterpretasi ulang" pesan bersih yang terpisah. Pesan baru tanpa pola pengulangan jelas → CLEAN. +- Konteks adalah KONTEKS, bukan bukti. Riwayat di membantu pahami alur, tapi pesan bersih tanpa pelanggaran → CLEAN. JANGAN gunakan konteks untuk "menginterpretasi ulang" pesan bersih yang terpisah. - Perubahan perilaku mencolok (mis. teknis tiba-tiba provokatif) layak dicatat. JANGAN paksa referensi profil jika tidak relevan — analysis natural lebih baik. - Channel culture coding/teknis → pesan teknis lebih wajar; channel santai → slang lebih wajar. Jangan dipakai mengabaikan pelanggaran nyata. @@ -70,8 +70,7 @@ CRITICAL: - Jika pesan adalah BALASAN (reply) ke pesan lain, jelaskan konteks balasannya: apa yang sedang dibicarakan, siapa yang dibalas (tanpa nama, cukup peran/isi pesan yang dibalas), dan bagaimana tanggapan pengirim terhadapnya. - Gunakan informasi dari Media analysis untuk mendeskripsikan gambar. - Analisis harus MEMBERI KONTEKS, bukan hanya menyatakan status. -- Gunakan (repeat_offender, last_offense_days_ago) untuk memberi konteks histori — analisis terasa seperti sistem "mengenal" histori pengguna tanpa deskripsi profil pribadi. -- Jika perilaku pesan menyimpang dari pola histori yang diketahui, CATAT dalam analysis sebagai informasi kontekstual yang relevan. +- Nilai tiap pesan murni dari isinya sendiri + + + . Tidak ada reputasi/profil per-user di context. - JANGAN paksa referensi profil jika tidak relevan — analysis natural lebih baik dari yang dipaksakan.`; // --------------------------------------------------------------------------- diff --git a/services/discord-gateway/src/modules/ai-moderation/prompts/system.ts b/services/discord-gateway/src/modules/ai-moderation/prompts/system.ts index 8d70f46..534d5ee 100644 --- a/services/discord-gateway/src/modules/ai-moderation/prompts/system.ts +++ b/services/discord-gateway/src/modules/ai-moderation/prompts/system.ts @@ -107,7 +107,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions): string { `System prompt ini TIDAK berisi data batch — semua data per-batch ada di pesan USER:\n` + `- : metadata channel/thread (channel_name, thread_name, topic, nsfw, age_restricted). topic = tujuan resmi channel; gunakan menilai kesesuaian pesan.\n` + `- : obrolan SEBELUM target. Baris pertama "[conversation_flow] status=... context_msgs=... dropped=..." = metadata sistem (ongoing/sparse/cold_start), BUKAN pesan dinilai. Baris "[context] id=... time=... user=...: isi" = konteks, BUKAN target.\n` + - `- : histori moderasi (repeat_offender=true = pelanggaran ≤7 hari). : kutipan pesan pernah di-flag — cari POLA berulang (spam link sama), BUKAN bukti pesan bersih.\n` + + `- Tidak ada data profil/reputasi per-user di context — nilai tiap pesan murni dari isinya + + + .\n` + `- /: bukti web (prioritas tertinggi). : definisi kata/slang/jargon (SearXNG) — pakai pahami kata asing, JANGAN tebak arti.\n` + `- : pesan TARGET yang WAJIB dinilai. Atribut : id, user, time (ISO), repetitions (N = teks sama muncul N× di batch → sinyal spam), bot (true = bot), edited (true = hasil edit setelah posting → evasi potensial).`, ); diff --git a/services/discord-gateway/src/modules/ai-moderation/textBatchProcessor.ts b/services/discord-gateway/src/modules/ai-moderation/textBatchProcessor.ts index 68798f6..fb87f6b 100644 --- a/services/discord-gateway/src/modules/ai-moderation/textBatchProcessor.ts +++ b/services/discord-gateway/src/modules/ai-moderation/textBatchProcessor.ts @@ -20,7 +20,6 @@ import { analyzeSingleMediaImage } from "./mediaAnalysisClient.js"; import { buildReferenceXml, escapeXml, - formatReputationAttrs, getAnalysisContent, resolveDisplayName, resolveIsBot, @@ -37,7 +36,6 @@ import { import { buildTermGlossaryBlock } from "./termGlossary.js"; import { getRecentCorrectedModerations } from "./textCacheStore.js"; import { extractUrlsFromText, fetchUrlSafely } from "./urlFetcher.js"; -import { initializeUserReputation } from "./userReputationStore.js"; import type { MessageImagePart } from "./visionAnalyzer.js"; const log = createChildLogger("textBatchProcessor"); @@ -201,24 +199,10 @@ export async function runTextOnlyBatch( const batch = subBatches[i]; const targetIds = batch.map((t) => t.id); - // ── Per-user reputation context (fetched ONCE per unique user, in - // parallel). Personal profile descriptions are intentionally NOT - // injected — they bloat the prompt (less room per request) and add a - // per-user DB/Redis round-trip for little moderation signal. Only the - // behavioural history is sent. ───────────────────── - const uniqueUserIds = [...new Set(batch.map((m) => m.user_id))]; - const batchGuildId = batch[0]?.guild_id ?? ""; - const userFetches = await Promise.all( - uniqueUserIds.map(async (uid) => { - const rep = await initializeUserReputation(uid, batchGuildId); - return { uid, rep }; - }), - ); - const userContexts = new Map(); - for (const { uid, rep } of userFetches) { - const repAttrs = formatReputationAttrs(rep); - userContexts.set(uid, ``); - } + // No per-user reputation/profile context is injected into the prompt — + // the user asked to keep the AI analysis context minimal (raw messages + // only). Trust/infraction state is still tracked in the DB for + // enforcement, just not shown to the LLM. // ── URL images → multimodal vision evidence ───────────────────────── // The text batch fetches inline URLs; whenever one resolved to an image @@ -315,12 +299,11 @@ export async function runTextOnlyBatch( const mediaEvidenceCtx = (batchImageEvidence.get(msg.id) ?? []) .map((line) => `\n${line}`) .join(""); - const userCtx = userContexts.get(msg.user_id) ?? ""; const refXml = await buildReferenceXml(msg); const repetitionCount = groupMapping.get(msg.id)?.length ?? 1; const isBot = resolveIsBot(msg); const isEdited = resolveIsEdited(msg); - return ` 1 ? ` repetitions="${repetitionCount}"` : ""}${isBot ? ` bot="true"` : ""}${isEdited ? ` edited="true"` : ""}>\n ${userCtx}${refXml ? `\n ${refXml}` : ""}\n ${escapeXml(content)}${webContext}${mediaEvidenceCtx}\n`; + return ` 1 ? ` repetitions="${repetitionCount}"` : ""}${isBot ? ` bot="true"` : ""}${isEdited ? ` edited="true"` : ""}>\n ${refXml ? `\n ${refXml}` : ""}\n ${escapeXml(content)}${webContext}${mediaEvidenceCtx}\n`; }), ) ).join("\n"); diff --git a/services/discord-gateway/src/modules/ai-moderation/visionAnalyzer.ts b/services/discord-gateway/src/modules/ai-moderation/visionAnalyzer.ts index 70599f8..19b14ca 100644 --- a/services/discord-gateway/src/modules/ai-moderation/visionAnalyzer.ts +++ b/services/discord-gateway/src/modules/ai-moderation/visionAnalyzer.ts @@ -64,7 +64,6 @@ import { import { buildReferenceXml, escapeXml, - formatReputationAttrs, getAnalysisContent, resolveDisplayName, resolveIsBot, @@ -84,7 +83,6 @@ import { } from "./searxngSearch.js"; import { buildTermGlossaryBlock } from "./termGlossary.js"; import { extractUrlsFromText } from "./urlFetcher.js"; -import { initializeUserReputation } from "./userReputationStore.js"; // --------------------------------------------------------------------------- // Types @@ -398,15 +396,13 @@ export async function prepareMediaMessage( .filter(Boolean) .join(" "); - const rep = await initializeUserReputation(target.user_id, target.guild_id); const refXml = await buildReferenceXml(target); - // Only the behavioural history is injected; personal profile descriptions are omitted (see textBatchProcessor). - const repAttrs = formatReputationAttrs(rep); - const repXml = ``; - + // No per-user reputation/profile context is injected into the prompt — + // keep the AI analysis context minimal (raw messages only). Trust state is + // still tracked in the DB for enforcement, just not shown to the LLM. const isBot = resolveIsBot(target); const isEdited = resolveIsEdited(target); - const messageBlock = `\n ${repXml}${refXml ? `\n ${refXml}` : ""}\n ${escapeXml(truncateForAi(content))}${mediaContext ? ` ${escapeXml(mediaContext)}` : ""}${webContext}${mediaAnalysisContext}${searxngXml}${glossaryCtx}\n`; + const messageBlock = `\n ${refXml ? `\n ${refXml}` : ""}\n ${escapeXml(truncateForAi(content))}${mediaContext ? ` ${escapeXml(mediaContext)}` : ""}${webContext}${mediaAnalysisContext}${searxngXml}${glossaryCtx}\n`; return { targetId, messageBlock }; }