feat(ai-moderation): parse moderation category from LLM fallback response instead of using static defaults
This commit is contained in:
@@ -1740,23 +1740,40 @@ Jawab HANYA dengan satu kata: clean, warn, atau flagged`;
|
|||||||
status = "clean";
|
status = "clean";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 2: Real analysis text (only if not clean) ──
|
// ── Step 2: Real analysis text + category (only if not clean) ──
|
||||||
// We ask the LLM for a real reason — no static/dummy text.
|
// We ask the LLM for a real reason and a category word — no complex JSON.
|
||||||
let analysis: string;
|
let analysis: string;
|
||||||
|
let category = "";
|
||||||
|
|
||||||
if (status === "clean") {
|
if (status === "clean") {
|
||||||
analysis = `${message.username ?? "user"}: ${content.length > 200 ? content.slice(0, 200) + "..." : content}. Percakapan normal, tidak ada pelanggaran.`;
|
analysis = `${message.username ?? "user"}: ${content.length > 200 ? content.slice(0, 200) + "..." : content}. Percakapan normal, tidak ada pelanggaran.`;
|
||||||
} else {
|
} else {
|
||||||
|
category = status === "flagged" ? "harassment" : "spam"; // default fallback
|
||||||
|
const categoryOptions =
|
||||||
|
status === "flagged" ? "harassment, gambling, atau sara" : "spam";
|
||||||
const reasonPrompt = `Pesan berikut telah diklasifikasikan sebagai "${status}".
|
const reasonPrompt = `Pesan berikut telah diklasifikasikan sebagai "${status}".
|
||||||
|
|
||||||
Pesan: "${truncatedContent}"
|
Pesan: "${truncatedContent}"
|
||||||
|
|
||||||
Jelaskan dalam 1-2 kalimat Bahasa Indonesia: APA yang melanggar dan KENAPA. Jangan gunakan kata "mungkin" atau "sepertinya". Jangan tulis ulang pesan. Langsung ke alasan.
|
Jelaskan dalam 1-2 kalimat Bahasa Indonesia: APA yang melanggar dan KENAPA. Jangan gunakan kata "mungkin" atau "sepertinya". Jangan tulis ulang pesan. Langsung ke alasan.
|
||||||
|
|
||||||
Contoh jawaban untuk "flagged": "Mengandung kata kasar terarah ke individu tertentu sebagai hinaan."
|
Setelah alasan, sebutkan Kategori: ${categoryOptions}
|
||||||
Contoh jawaban untuk "flagged": "Promosi situs judi online dengan link dan ajakan."
|
|
||||||
Contoh jawaban untuk "warn": "Promosi channel Discord tanpa konteks, berpotensi spam."
|
Contoh untuk "flagged":
|
||||||
Contoh jawaban untuk "warn": "Bahasa kasar ringan yang tidak terarah, tidak melanggar berat tapi perlu diingatkan."`;
|
Mengandung kata kasar terarah ke individu tertentu sebagai hinaan.
|
||||||
|
Kategori: harassment
|
||||||
|
|
||||||
|
Contoh untuk "flagged":
|
||||||
|
Promosi situs judi online dengan link dan ajakan.
|
||||||
|
Kategori: gambling
|
||||||
|
|
||||||
|
Contoh untuk "warn":
|
||||||
|
Promosi channel Discord tanpa konteks, berpotensi spam.
|
||||||
|
Kategori: spam
|
||||||
|
|
||||||
|
Contoh untuk "warn":
|
||||||
|
Bahasa kasar ringan yang tidak terarah.
|
||||||
|
Kategori: spam`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const completion = await llmChat({
|
const completion = await llmChat({
|
||||||
@@ -1772,9 +1789,25 @@ Contoh jawaban untuk "warn": "Bahasa kasar ringan yang tidak terarah, tidak mela
|
|||||||
analysis = `Pesan diklasifikasikan sebagai ${status} oleh sistem moderasi otomatis.`;
|
analysis = `Pesan diklasifikasikan sebagai ${status} oleh sistem moderasi otomatis.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Parse category from "Kategori: xxx" line ──
|
||||||
|
const categoryMatch = analysis.match(
|
||||||
|
/[Kk]ategori:\s*(\w+)/i,
|
||||||
|
);
|
||||||
|
if (categoryMatch) {
|
||||||
|
const parsedCat = categoryMatch[1].toLowerCase();
|
||||||
|
// Only accept known categories
|
||||||
|
if (
|
||||||
|
["harassment", "spam", "gambling", "sara"].includes(parsedCat)
|
||||||
|
) {
|
||||||
|
category = parsedCat;
|
||||||
|
}
|
||||||
|
// Strip the "Kategori:" line from the analysis text so it's cleaner
|
||||||
|
analysis = analysis.replace(/[Kk]ategori:\s*\w+\s*/i, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
{ messageId: message.id, status, analysis: analysis.slice(0, 100) },
|
{ messageId: message.id, status, category, analysis: analysis.slice(0, 100) },
|
||||||
"Simple fallback step 2 — reason",
|
"Simple fallback step 2 — reason + category",
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
analysis = `Pesan diklasifikasikan sebagai ${status} oleh sistem moderasi otomatis berdasarkan analisis konten.`;
|
analysis = `Pesan diklasifikasikan sebagai ${status} oleh sistem moderasi otomatis berdasarkan analisis konten.`;
|
||||||
@@ -1788,7 +1821,11 @@ Contoh jawaban untuk "warn": "Bahasa kasar ringan yang tidak terarah, tidak mela
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the result fields
|
// Build the result fields using parsed category
|
||||||
|
const flags: string[] =
|
||||||
|
status === "clean" ? [] : [category];
|
||||||
|
const categories: string[] =
|
||||||
|
status === "clean" ? [] : [category];
|
||||||
const score = status === "flagged" ? 0.7 : status === "warn" ? 0.4 : 0;
|
const score = status === "flagged" ? 0.7 : status === "warn" ? 0.4 : 0;
|
||||||
const severity: "none" | "low" | "medium" | "high" | "critical" =
|
const severity: "none" | "low" | "medium" | "high" | "critical" =
|
||||||
status === "flagged" ? "medium" : status === "warn" ? "low" : "none";
|
status === "flagged" ? "medium" : status === "warn" ? "low" : "none";
|
||||||
@@ -1797,12 +1834,10 @@ Contoh jawaban untuk "warn": "Bahasa kasar ringan yang tidak terarah, tidak mela
|
|||||||
return {
|
return {
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
status,
|
status,
|
||||||
flags:
|
flags,
|
||||||
status === "flagged" ? ["harassment"] : status === "warn" ? ["spam"] : [],
|
|
||||||
score,
|
score,
|
||||||
analysis,
|
analysis,
|
||||||
categories:
|
categories,
|
||||||
status === "flagged" ? ["harassment"] : status === "warn" ? ["spam"] : [],
|
|
||||||
severity,
|
severity,
|
||||||
confidence,
|
confidence,
|
||||||
recommendedAction:
|
recommendedAction:
|
||||||
|
|||||||
Reference in New Issue
Block a user