The chatbot agent now has 14 tools (was 4) so it can answer about ANY server situation from live data instead of a static snapshot: - get_server_stats (now also returns clean count) - get_top_channels, get_recent_activity, get_top_flagged - search_messages (LIKE keyword search) - get_user_messages, get_user_profile, get_user_reputation - get_channel_culture - get_message_detail (full AI analysis of one message) - get_message_reviews (human moderation queue by status) - get_voice_recordings (with transcriptions) - get_moderation_timeline (daily flagged/warn/clean trend) - get_corrections (AI false-positive correction history) Security/quality: - Every executor now uses parameterized drizzle queries (eq/like/and). The old code interpolated model-supplied IDs into sql.raw() — a SQL injection vector. Removed. - Split static tool *definitions* into chatbot.toolDefs.ts (no DB import) so the LLM-facing schema can be unit-tested without loading the database/config layer. chatbot.tools.ts keeps only the executor. Verified: tsc + biome clean, 40 backend tests pass (4 new covering the tool-contract: names unique, required args declared, full situation coverage). Co-Authored-By: Claude Opus 5 (Nous Research)
271 lines
9.1 KiB
TypeScript
271 lines
9.1 KiB
TypeScript
/**
|
|
* Static tool *definitions* for the chatbot LLM (OpenAI function-calling
|
|
* format). Kept separate from the executor (chatbot.tools.ts) so the schema
|
|
* the model depends on can be imported without pulling in the database /
|
|
* config layer.
|
|
*
|
|
* The chatbot is a server-watcher agent: it can answer about ANY server
|
|
* situation — activity, moderation queue, specific users, channels, voice
|
|
* recordings, AI correction history, and trends over time — by calling these
|
|
* tools, which the executor implements against real tables.
|
|
*/
|
|
|
|
export interface ToolDef {
|
|
type: "function";
|
|
function: {
|
|
name: string;
|
|
description: string;
|
|
parameters: {
|
|
type: "object";
|
|
properties: Record<string, unknown>;
|
|
required?: string[];
|
|
};
|
|
};
|
|
}
|
|
|
|
export const tools: ToolDef[] = [
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_server_stats",
|
|
description:
|
|
"Ambil statistik ringkas server/guild: total pesan, user aktif, jumlah pesan flagged, warn, dan clean. Panggil untuk jawab pertanyaan umum soal kondisi server. guildId/channelId otomatis ter-isi dari scope; kosongkan untuk semua data.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
channelId: { type: "string", description: "ID channel (opsional)." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_top_channels",
|
|
description:
|
|
"Ambil daftar channel paling aktif (jumlah pesan terbanyak). Panggil untuk 'channel mana paling ramai' atau aktivitas per-channel.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
limit: {
|
|
type: "number",
|
|
description: "Jumlah channel teratas (default 5, max 10).",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_recent_activity",
|
|
description:
|
|
"Ambil pesan terbaru di server: siapa, di channel mana, jam berapa, isinya. Panggil untuk 'lagi ngapain' / aktivitas terbaru.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
channelId: { type: "string", description: "ID channel (opsional)." },
|
|
limit: {
|
|
type: "number",
|
|
description: "Jumlah pesan terakhir (default 5, max 20).",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_top_flagged",
|
|
description:
|
|
"Ambil pesan dengan ai_status flagged (beserta alasan, severity, analysis). Panggil untuk bahas pesan bermasalah / kerjaan moderator.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
channelId: { type: "string", description: "ID channel (opsional)." },
|
|
limit: { type: "number", description: "Jumlah pesan (default 5)." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "search_messages",
|
|
description:
|
|
"Cari pesan berdasarkan kata kunci di isi pesan (case-insensitive, LIKE). Untuk 'ada yang bahas X gak?' / temukan topik tertentu. Hindari kata terlalu umum.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
query: {
|
|
type: "string",
|
|
description: "Kata kunci pencarian (wajib).",
|
|
},
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
channelId: { type: "string", description: "ID channel (opsional)." },
|
|
limit: { type: "number", description: "Jumlah hasil (default 5)." },
|
|
},
|
|
required: ["query"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_user_messages",
|
|
description:
|
|
"Ambil pesan terbaru dari satu user tertentu (user_id), opsional di-scope ke guild/channel. Untuk 'chat si A gimana akhir-akhir ini?' — butuh user_id.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
userId: { type: "string", description: "ID user (wajib)." },
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
channelId: { type: "string", description: "ID channel (opsional)." },
|
|
limit: { type: "number", description: "Jumlah pesan (default 10)." },
|
|
},
|
|
required: ["userId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_user_profile",
|
|
description:
|
|
"Ambil ringkasan profil AI dari seorang user (pola perilaku, gaya bicara) dari tabel user_profiles. Untuk 'siapa si A?' / konteks perilaku. Butuh user_id.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
userId: { type: "string", description: "ID user (wajib)." },
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
},
|
|
required: ["userId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_user_reputation",
|
|
description:
|
|
"Ambil skor trust, jumlah infraction, dan streak pesan bersih seorang user dari user_reputations. Untuk 'berapa trust score si A?' / riwayat pelanggaran. Butuh user_id.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
userId: { type: "string", description: "ID user (wajib)." },
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
},
|
|
required: ["userId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_channel_culture",
|
|
description:
|
|
"Ambil ringkasan norma/slang channel dari tabel channel_cultures (AI-generated). Untuk 'norma channel ini gimana?' / konteks sebelum nge-flag. Butuh channel_id.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
channelId: { type: "string", description: "ID channel (wajib)." },
|
|
},
|
|
required: ["channelId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_message_detail",
|
|
description:
|
|
"Ambil 1 pesan lengkap beserta hasil analisis AI-nya (status, flags, score, severity, kategori, analysis, recommended action). Untuk jelasin keputusan moderasi pada pesan tertentu. Butuh message_id.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
messageId: { type: "string", description: "ID pesan (wajib)." },
|
|
},
|
|
required: ["messageId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_message_reviews",
|
|
description:
|
|
"Ambil antrean review moderasi manual (message_reviews) berdasarkan status: pending/approved/rejected/escalated. Untuk 'ada review moderasi pending?' / cek kerjaan human moderator. guildId otomatis ter-isi.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
status: {
|
|
type: "string",
|
|
description:
|
|
"Status review: pending / approved / rejected / escalated (opsional, default semua).",
|
|
},
|
|
limit: { type: "number", description: "Jumlah (default 10)." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_voice_recordings",
|
|
description:
|
|
"Ambil rekaman suara terbaru (voice_recordings): user, channel, transkripsi, status upload. Untuk 'ada rekaman suara terbaru?' / cek transkripsi. Bisa di-scope ke user_id atau channel_id.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
userId: { type: "string", description: "Filter user (opsional)." },
|
|
channelId: {
|
|
type: "string",
|
|
description: "Filter channel (opsional).",
|
|
},
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
limit: { type: "number", description: "Jumlah (default 10)." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_moderation_timeline",
|
|
description:
|
|
"Ambil tren harian: per hari, jumlah total pesan vs flagged vs warn vs clean. Untuk 'minggu ini pelanggaran naik?' / lihat tren moderasi. guildId otomatis ter-isi.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
channelId: { type: "string", description: "ID channel (opsional)." },
|
|
days: {
|
|
type: "number",
|
|
description: "Jumlah hari ke belakang (default 14, max 60).",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_corrections",
|
|
description:
|
|
"Ambil riwayat koreksi false-positive AI (corrected_moderations): pesan yang awalnya di-flag tapi dikoreksi manusia, beserta alasannya. Untuk 'AI pernah salah nge-flag apa aja?' / audit akurasi moderasi.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
guildId: { type: "string", description: "ID server (opsional)." },
|
|
limit: { type: "number", description: "Jumlah (default 10)." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
];
|