joel memories
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* Joel feature exports
|
||||
*/
|
||||
|
||||
export { joelResponder } from "./responder";
|
||||
export { joelResponder, type TemplateVariables } from "./responder";
|
||||
export { getRandomMention } from "./mentions";
|
||||
export { TypingIndicator } from "./typing";
|
||||
export { personalities, getPersonality, buildStyledPrompt, STYLE_MODIFIERS } from "./personalities";
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { MessageStyle } from "../../services/ai";
|
||||
|
||||
export interface Personality {
|
||||
name: string;
|
||||
buildSystemPrompt: (author: string, memoryContext?: string) => string;
|
||||
buildSystemPrompt: (author: string) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,8 +40,8 @@ Be reluctantly helpful, like you're doing them a huge favor.`,
|
||||
*/
|
||||
export const defaultPersonality: Personality = {
|
||||
name: "default",
|
||||
buildSystemPrompt: (author: string, memoryContext?: string) => {
|
||||
const basePrompt = [
|
||||
buildSystemPrompt: (author: string) => {
|
||||
return [
|
||||
"You are Joel. Speak directly as Joel in first person.",
|
||||
"NEVER narrate or describe your actions. NEVER say 'Joel responds with' or similar.",
|
||||
"Just speak naturally like a real person in a chat.",
|
||||
@@ -55,10 +55,6 @@ export const defaultPersonality: Personality = {
|
||||
"Roleplay along if the user describes actions.",
|
||||
`The user's name is ${author}. Insult ${author} by name.`,
|
||||
].join("\n");
|
||||
|
||||
return memoryContext
|
||||
? `${basePrompt}\n\n${memoryContext}`
|
||||
: basePrompt;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -67,10 +63,9 @@ export const defaultPersonality: Personality = {
|
||||
*/
|
||||
export function buildStyledPrompt(
|
||||
author: string,
|
||||
style: MessageStyle,
|
||||
memoryContext?: string
|
||||
style: MessageStyle
|
||||
): string {
|
||||
const basePrompt = defaultPersonality.buildSystemPrompt(author, memoryContext);
|
||||
const basePrompt = defaultPersonality.buildSystemPrompt(author);
|
||||
const styleModifier = STYLE_MODIFIERS[style];
|
||||
|
||||
return `${basePrompt}\n\n=== CURRENT STYLE: ${style.toUpperCase()} ===\n${styleModifier}`;
|
||||
|
||||
@@ -6,9 +6,11 @@ import type { Message } from "discord.js";
|
||||
import type { BotClient } from "../../core/client";
|
||||
import { config } from "../../core/config";
|
||||
import { createLogger } from "../../core/logger";
|
||||
import { getAiService, type MessageStyle } from "../../services/ai";
|
||||
import { memoryRepository } from "../../database";
|
||||
import { buildStyledPrompt } from "./personalities";
|
||||
import { getAiService, type MessageStyle, type ToolContext } from "../../services/ai";
|
||||
import { db } from "../../database";
|
||||
import { personalities, botOptions } from "../../database/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { buildStyledPrompt, STYLE_MODIFIERS } from "./personalities";
|
||||
import { getRandomMention } from "./mentions";
|
||||
import { TypingIndicator } from "./typing";
|
||||
|
||||
@@ -17,6 +19,43 @@ const logger = createLogger("Features:Joel");
|
||||
// Regex to match various spellings of "Joel"
|
||||
const JOEL_VARIATIONS = /\b(joel|jogel|johogel|jorl|jole|joeel|jöel|joal|jol|johel)\b/i;
|
||||
|
||||
/**
|
||||
* Template variables that can be used in custom system prompts
|
||||
*/
|
||||
export interface TemplateVariables {
|
||||
author: string; // Display name of the user
|
||||
userId: string; // Discord user ID
|
||||
username: string; // Discord username (without discriminator)
|
||||
channelName: string; // Channel name
|
||||
channelId: string; // Channel ID
|
||||
guildName: string; // Server name
|
||||
guildId: string; // Server ID
|
||||
messageContent: string; // The user's message
|
||||
memories: string; // Formatted memories about the user (if any)
|
||||
style: MessageStyle; // Detected message style
|
||||
styleModifier: string; // Style-specific instructions
|
||||
timestamp: string; // Current timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitute template variables in a system prompt
|
||||
*/
|
||||
function substituteTemplateVariables(template: string, vars: TemplateVariables): string {
|
||||
return template
|
||||
.replace(/\{author\}/gi, vars.author)
|
||||
.replace(/\{userId\}/gi, vars.userId)
|
||||
.replace(/\{username\}/gi, vars.username)
|
||||
.replace(/\{channelName\}/gi, vars.channelName)
|
||||
.replace(/\{channelId\}/gi, vars.channelId)
|
||||
.replace(/\{guildName\}/gi, vars.guildName)
|
||||
.replace(/\{guildId\}/gi, vars.guildId)
|
||||
.replace(/\{messageContent\}/gi, vars.messageContent)
|
||||
.replace(/\{memories\}/gi, vars.memories)
|
||||
.replace(/\{style\}/gi, vars.style)
|
||||
.replace(/\{styleModifier\}/gi, vars.styleModifier)
|
||||
.replace(/\{timestamp\}/gi, vars.timestamp);
|
||||
}
|
||||
|
||||
export const joelResponder = {
|
||||
/**
|
||||
* Handle an incoming message and potentially respond as Joel
|
||||
@@ -75,22 +114,59 @@ export const joelResponder = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a response using AI
|
||||
* Generate a response using AI with tool calling support
|
||||
*/
|
||||
async generateResponse(message: Message<true>): Promise<string | null> {
|
||||
const ai = getAiService();
|
||||
const author = message.author.displayName;
|
||||
const userId = message.author.id;
|
||||
const guildId = message.guildId;
|
||||
|
||||
// Create tool context for this conversation
|
||||
const toolContext: ToolContext = {
|
||||
userId,
|
||||
guildId,
|
||||
channelId: message.channelId,
|
||||
authorName: author,
|
||||
};
|
||||
|
||||
// Classify the message to determine response style
|
||||
const style = await this.classifyMessage(message.cleanContent);
|
||||
logger.debug("Message style classified", { style, content: message.cleanContent.slice(0, 50) });
|
||||
|
||||
// Build memory context
|
||||
const memoryContext = await this.buildMemoryContext(userId, author);
|
||||
|
||||
// Build system prompt with style
|
||||
const systemPrompt = buildStyledPrompt(author, style, memoryContext);
|
||||
// Extract memories from the incoming message (async, non-blocking)
|
||||
// This runs in the background while we generate the response
|
||||
ai.extractMemories(message.cleanContent, author, toolContext).catch((err) => {
|
||||
logger.error("Background memory extraction failed", err);
|
||||
});
|
||||
|
||||
// Check for custom personality
|
||||
const systemPrompt = await this.buildSystemPrompt(guildId, {
|
||||
author,
|
||||
userId,
|
||||
username: message.author.username,
|
||||
channelName: message.channel.name,
|
||||
channelId: message.channelId,
|
||||
guildName: message.guild.name,
|
||||
guildId,
|
||||
messageContent: message.cleanContent,
|
||||
memories: "", // Not pre-loading - AI can look them up via tools
|
||||
style,
|
||||
styleModifier: STYLE_MODIFIERS[style],
|
||||
timestamp: new Date().toISOString(),
|
||||
}, style);
|
||||
|
||||
// Add tool instructions to the system prompt
|
||||
const systemPromptWithTools = `${systemPrompt}
|
||||
|
||||
=== MEMORY TOOLS ===
|
||||
You have access to tools for managing memories about users:
|
||||
- Use lookup_user_memories to recall what you know about someone
|
||||
- Use save_memory to remember interesting facts for later
|
||||
- Use search_memories to find information across all users
|
||||
|
||||
Feel free to look up memories when you want to make personalized insults.
|
||||
The current user's ID is: ${userId}`;
|
||||
|
||||
// Get reply context if this is a reply
|
||||
let prompt = message.cleanContent;
|
||||
@@ -103,10 +179,57 @@ export const joelResponder = {
|
||||
}
|
||||
}
|
||||
|
||||
const response = await ai.generateResponse(prompt, systemPrompt);
|
||||
// Use tool-enabled response generation
|
||||
const response = await ai.generateResponseWithTools(
|
||||
prompt,
|
||||
systemPromptWithTools,
|
||||
toolContext
|
||||
);
|
||||
|
||||
return response.text || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Build system prompt - uses custom personality if set, otherwise default
|
||||
*/
|
||||
async buildSystemPrompt(
|
||||
guildId: string,
|
||||
vars: TemplateVariables,
|
||||
style: MessageStyle
|
||||
): Promise<string> {
|
||||
// Check for guild-specific options
|
||||
const options = await db
|
||||
.select()
|
||||
.from(botOptions)
|
||||
.where(eq(botOptions.guild_id, guildId))
|
||||
.limit(1);
|
||||
|
||||
if (options.length > 0 && options[0].active_personality_id) {
|
||||
// Fetch the custom personality
|
||||
const customPersonality = await db
|
||||
.select()
|
||||
.from(personalities)
|
||||
.where(eq(personalities.id, options[0].active_personality_id))
|
||||
.limit(1);
|
||||
|
||||
if (customPersonality.length > 0) {
|
||||
logger.debug(`Using custom personality: ${customPersonality[0].name}`);
|
||||
// Substitute template variables in the custom prompt
|
||||
let prompt = substituteTemplateVariables(customPersonality[0].system_prompt, vars);
|
||||
|
||||
// Add style modifier if not already included
|
||||
if (!prompt.includes(vars.styleModifier)) {
|
||||
prompt += `\n\n=== CURRENT STYLE: ${style.toUpperCase()} ===\n${vars.styleModifier}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default prompt (no memory context - AI uses tools now)
|
||||
return buildStyledPrompt(vars.author, style);
|
||||
},
|
||||
|
||||
/**
|
||||
* Classify a message to determine response style
|
||||
*/
|
||||
@@ -115,28 +238,6 @@ export const joelResponder = {
|
||||
return ai.classifyMessage(content);
|
||||
},
|
||||
|
||||
/**
|
||||
* Build memory context for personalized attacks
|
||||
*/
|
||||
async buildMemoryContext(userId: string, author: string): Promise<string | undefined> {
|
||||
// Only use memories sometimes
|
||||
if (Math.random() >= config.bot.memoryChance) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const memories = await memoryRepository.findByUserId(userId, 5);
|
||||
|
||||
if (memories.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
logger.debug(`Using memories against ${author}`);
|
||||
|
||||
return `You remember these things about ${author} - use them to be extra brutal:\n${
|
||||
memories.map((m) => `- ${m.content}`).join("\n")
|
||||
}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send response, splitting if necessary
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user