feat(chatbot): expand tool set to cover all server-watcher situations
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)
This commit is contained in:
co-authored by
Claude Opus 5 (Nous Research)
parent
30828a5534
commit
b67856462f
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* 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)." },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user