Files
GMW/services/backend/src/modules/chatbot/chatbot.repository.ts
T
asepharyanaandClaude Opus 5 (Nous Research) 30828a5534 refactor(chatbot): drop static server-stats context, go fully tool-based
The chatbot already had an agentic tool loop (get_server_stats,
get_top_channels, get_recent_activity, get_top_flagged), but processMessage
still baked a serverInsights snapshot into the system prompt and told the
model to "answer from that data". That defeats the tools: the model answered
from a stale snapshot instead of living numbers, and the guild/channel scope
the frontend sends was never forwarded to the tools.

Changes (services/backend/src/modules/chatbot):
- Remove getServerInsights() + ServerInsights (dead after this change).
- buildSystemPrompt(): drop the hardcoded stats block; instruct the model it
  has NO memorized server numbers and MUST call a tool for any server-data
  question, answering only from tool results.
- processMessage(): stop fetching insights; pass the request guildId/channelId
  scope through to callLLM.
- callLLM(): accept scope; auto-fill empty guildId/channelId on tool calls from
  the request scope so the model never has to guess IDs and tools always query
  the right server.

Behavior: answers now come from live DB data via tools, scoped to the server
the user is chatting in. tsc + biome + 36 backend tests green.

Co-Authored-By: Claude Opus 5 (Nous Research)
2026-08-16 09:15:39 +07:00

82 lines
2.2 KiB
TypeScript

import { and, desc, eq, type SQL, sql } from "drizzle-orm";
import { getDatabase } from "../../shared/database/index.js";
import { pgChatbotMessagesTable, pgMessagesTable } from "../../shared/index.js";
import { createChildLogger } from "../../shared/logger/index.js";
const logger = createChildLogger("chatbot.repository");
export interface ChatbotContext {
messageCount?: number;
activeParticipants?: number;
lastActivity?: string;
topicsDiscussed?: string[];
guildId?: string;
channelId?: string;
}
export interface SaveConversationInput {
userId: string;
userMessage: string;
botResponse: string;
context?: ChatbotContext;
timestamp: Date;
}
export interface ChatbotHistoryRow {
id: string;
user_id: string;
user_message: string;
bot_response: string;
context: ChatbotContext | null;
created_at: string;
}
export class ChatbotRepository {
async saveConversation(input: SaveConversationInput): Promise<void> {
const db = getDatabase();
await db.insert(pgChatbotMessagesTable).values({
user_id: input.userId,
user_message: input.userMessage,
bot_response: input.botResponse,
context: (input.context ?? {}) as Record<string, unknown>,
created_at: input.timestamp,
});
logger.debug({ userId: input.userId }, "Conversation saved");
}
async getChatHistory(
userId: string,
limit: number,
): Promise<ChatbotHistoryRow[]> {
const db = getDatabase();
const rows = await db
.select()
.from(pgChatbotMessagesTable)
.where(eq(pgChatbotMessagesTable.user_id, userId))
.orderBy(desc(pgChatbotMessagesTable.created_at))
.limit(limit);
logger.debug({ userId, count: rows.length }, "Chat history fetched");
return rows.reverse() as unknown as ChatbotHistoryRow[];
}
async clearChatHistory(userId: string): Promise<void> {
const db = getDatabase();
const deleted = await db
.delete(pgChatbotMessagesTable)
.where(eq(pgChatbotMessagesTable.user_id, userId))
.returning({ id: pgChatbotMessagesTable.id });
logger.info(
{ userId, deletedRows: deleted.length },
"Chat history cleared",
);
}
}
export const chatbotRepository = new ChatbotRepository();