fix: remove user history injection to prevent false positive moderation
- Removed getUserRecentInfractions usage in textBatchProcessor.ts and visionAnalyzer.ts - Removed buildUserHistoryXml import and calls - Messages are now evaluated standalone, not influenced by past violations in other channels - Updated moderation prompts with clearer instructions about user_history usage - Fixes issue where benign messages like 'tubuh manusia vs gravitasi' were incorrectly flagged due to carryover from previous drone weapons discussion The user history context was causing the LLM to interpret unrelated current messages as threats because it conflated them with past violations. Now each message is judged on its own merit with only channel-specific context.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -35,10 +35,12 @@ export const SYSTEM_RULES = `Kamu adalah asisten moderasi konten untuk server Di
|
|||||||
Kata alat kelamin/anatomi seksual (kontol, memek, titten, tit, dick) atau istilah seksual eksplisit WAJIB di-flag sebagai vulgar_language/sexual_content — TANPA pengecualian bercanda, slang, atau "santai".
|
Kata alat kelamin/anatomi seksual (kontol, memek, titten, tit, dick) atau istilah seksual eksplisit WAJIB di-flag sebagai vulgar_language/sexual_content — TANPA pengecualian bercanda, slang, atau "santai".
|
||||||
|
|
||||||
## Nilai Server — Diskriminasi
|
## Nilai Server — Diskriminasi
|
||||||
- Seksisme ("dasar perempuan", "logika cewek") → hate_speech (umum) / harassment (terarah).
|
-Ketika sesuatu yang melanggar terjadi di channel, flag jika relevan. Setiap pesan dinilai BERDASARKAN ISINYA SENDIRI, bukan sekadar histori pengguna.
|
||||||
- Ageisme ("dasar bocil", "tau aja lo tua") → hate_speech / harassment.
|
-Seksisme ("dasar perempuan", "logika cewek") → hate_speech (umum) / harassment (terarah).
|
||||||
- Diskriminasi fisik ("gendut", "iteman", "cungkring") → harassment jika terarah.
|
-Ageisme ("dasar bocil", "tau aja lo tua") → hate_speech / harassment.
|
||||||
- Serangan personal, penghinaan, merendahkan = tidak ditoleransi. Perbedaan pendapat wajar.
|
-Diskriminasi fisik ("gendut", "iteman", "cungkring") → harassment jika terarah.
|
||||||
|
-Serangan personal, penghinaan, merendahkan = tidak ditoleransi. Perbedaan pendapat wajar.
|
||||||
|
+**PESAN DINILAI SECARA STANDALONE:** Setiap pesan baru dinilai BERDASARKAN ISINYA SENDIRI. <user_history> (jika ada) HANYA untuk mendeteksi POLA PENGULANGAN dengan JAMAK (spam link yang SAMA, provokasi berulang yang MENGANDALKAN KONTEN YANG SAMA). JANGAN gunakan history untuk "menginterpretasi ulang" pesan bersih yang TERPISAH DARI riwayat pelanggaran sebelumnya. Jika pesan tidak mengandung unsur yang BERPANDUAN PADA riwayat → tetap CLEAN.
|
||||||
|
|
||||||
## LARANGAN BERAT (ZERO TOLERANCE)
|
## LARANGAN BERAT (ZERO TOLERANCE)
|
||||||
- **LGBT:** Segala promosi, diskusi, pengakuan orientasi, coming out, atau curhat personal tentang LGBT WAJIB di-flag "sexual_deviation". Tidak ada pengecualian.
|
- **LGBT:** Segala promosi, diskusi, pengakuan orientasi, coming out, atau curhat personal tentang LGBT WAJIB di-flag "sexual_deviation". Tidak ada pengecualian.
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { callModerationLLM } from "./llmCaller.js";
|
|||||||
import { analyzeSingleMediaImage } from "./mediaAnalysisClient.js";
|
import { analyzeSingleMediaImage } from "./mediaAnalysisClient.js";
|
||||||
import {
|
import {
|
||||||
buildReferenceXml,
|
buildReferenceXml,
|
||||||
buildUserHistoryXml,
|
|
||||||
buildUserProfileRef,
|
buildUserProfileRef,
|
||||||
buildUserProfilesBlock,
|
buildUserProfilesBlock,
|
||||||
escapeXml,
|
escapeXml,
|
||||||
@@ -41,10 +40,7 @@ import { buildTermGlossaryBlock } from "./termGlossary.js";
|
|||||||
import { getRecentCorrectedModerations } from "./textCacheStore.js";
|
import { getRecentCorrectedModerations } from "./textCacheStore.js";
|
||||||
import { extractUrlsFromText, fetchUrlSafely } from "./urlFetcher.js";
|
import { extractUrlsFromText, fetchUrlSafely } from "./urlFetcher.js";
|
||||||
import { getUserProfile } from "./userProfileStore.js";
|
import { getUserProfile } from "./userProfileStore.js";
|
||||||
import {
|
import { initializeUserReputation } from "./userReputationStore.js";
|
||||||
getUserRecentInfractions,
|
|
||||||
initializeUserReputation,
|
|
||||||
} from "./userReputationStore.js";
|
|
||||||
import type { MessageImagePart } from "./visionAnalyzer.js";
|
import type { MessageImagePart } from "./visionAnalyzer.js";
|
||||||
|
|
||||||
const log = createChildLogger("textBatchProcessor");
|
const log = createChildLogger("textBatchProcessor");
|
||||||
@@ -218,27 +214,7 @@ export async function runTextOnlyBatch(
|
|||||||
if (!userContexts.has(msg.user_id)) {
|
if (!userContexts.has(msg.user_id)) {
|
||||||
const rep = await initializeUserReputation(msg.user_id, msg.guild_id);
|
const rep = await initializeUserReputation(msg.user_id, msg.guild_id);
|
||||||
const repAttrs = formatReputationAttrs(rep);
|
const repAttrs = formatReputationAttrs(rep);
|
||||||
let repXml = `<user_reputation ${repAttrs}/>`;
|
const repXml = `<user_reputation ${repAttrs}/>`;
|
||||||
// Repeat offenders get their last flagged messages as <user_history>
|
|
||||||
// so the LLM can recognize PATTERNS (same scam link, repeated
|
|
||||||
// provocation) — history is reference, never proof. Best-effort.
|
|
||||||
if (rep.total_infractions > 0) {
|
|
||||||
try {
|
|
||||||
const history = await getUserRecentInfractions(msg.user_id, 2);
|
|
||||||
const historyXml = buildUserHistoryXml(
|
|
||||||
history.map((h) => ({
|
|
||||||
content: h.content ?? "",
|
|
||||||
severity: h.severity,
|
|
||||||
created_at: h.created_at,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
if (historyXml) {
|
|
||||||
repXml = `<user_reputation ${repAttrs}>\n${historyXml}\n</user_reputation>`;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// history is a bonus — fall back to attrs-only reputation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userContexts.set(msg.user_id, repXml);
|
userContexts.set(msg.user_id, repXml);
|
||||||
}
|
}
|
||||||
if (!userProfiles.has(msg.user_id)) {
|
if (!userProfiles.has(msg.user_id)) {
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ import {
|
|||||||
} from "./mediaDownloader.js";
|
} from "./mediaDownloader.js";
|
||||||
import {
|
import {
|
||||||
buildReferenceXml,
|
buildReferenceXml,
|
||||||
buildUserHistoryXml,
|
|
||||||
buildUserProfileRef,
|
buildUserProfileRef,
|
||||||
escapeXml,
|
escapeXml,
|
||||||
formatReputationAttrs,
|
formatReputationAttrs,
|
||||||
@@ -90,10 +89,7 @@ import {
|
|||||||
import { buildTermGlossaryBlock } from "./termGlossary.js";
|
import { buildTermGlossaryBlock } from "./termGlossary.js";
|
||||||
import { extractUrlsFromText } from "./urlFetcher.js";
|
import { extractUrlsFromText } from "./urlFetcher.js";
|
||||||
import { getUserProfile } from "./userProfileStore.js";
|
import { getUserProfile } from "./userProfileStore.js";
|
||||||
import {
|
import { initializeUserReputation } from "./userReputationStore.js";
|
||||||
getUserRecentInfractions,
|
|
||||||
initializeUserReputation,
|
|
||||||
} from "./userReputationStore.js";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Types
|
// Types
|
||||||
@@ -459,27 +455,9 @@ export async function prepareMediaMessage(
|
|||||||
? buildUserProfileRef(target.user_id)
|
? buildUserProfileRef(target.user_id)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
// Rich reputation — same shape as the text path: attrs + optional
|
// Rich reputation — attrs only, no user history injection (per channel context preference)
|
||||||
// <user_history> with the last flagged messages for repeat offenders.
|
|
||||||
const repAttrs = formatReputationAttrs(rep);
|
const repAttrs = formatReputationAttrs(rep);
|
||||||
let repXml = `<user_reputation ${repAttrs}/>`;
|
const repXml = `<user_reputation ${repAttrs}/>`;
|
||||||
if (rep.total_infractions > 0) {
|
|
||||||
try {
|
|
||||||
const history = await getUserRecentInfractions(target.user_id, 2);
|
|
||||||
const historyXml = buildUserHistoryXml(
|
|
||||||
history.map((h) => ({
|
|
||||||
content: h.content ?? "",
|
|
||||||
severity: h.severity,
|
|
||||||
created_at: h.created_at,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
if (historyXml) {
|
|
||||||
repXml = `<user_reputation ${repAttrs}>\n${historyXml}\n</user_reputation>`;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// history is a bonus — fall back to attrs-only reputation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isBot = resolveIsBot(target);
|
const isBot = resolveIsBot(target);
|
||||||
const isEdited = resolveIsEdited(target);
|
const isEdited = resolveIsEdited(target);
|
||||||
|
|||||||
Reference in New Issue
Block a user