updated to cue memory now.
This commit is contained in:
10
src/bot.js
10
src/bot.js
@@ -22,9 +22,15 @@ let coderPingTimer;
|
||||
const continuationState = new Map();
|
||||
let isSleeping = false;
|
||||
|
||||
const recallPatterns = config.memoryRecallTriggerPatterns || [];
|
||||
const contextCache = new Map();
|
||||
const CONTEXT_CACHE_TTL_MS = 2 * 60 * 1000;
|
||||
|
||||
function matchesMemoryRecallCue(text) {
|
||||
if (!text) return false;
|
||||
return recallPatterns.some((pattern) => pattern.test(text));
|
||||
}
|
||||
|
||||
const cloneShortTerm = (entries = []) => entries.map((entry) => ({ ...entry }));
|
||||
const cloneMemories = (entries = []) => entries.map((entry) => ({ ...entry }));
|
||||
|
||||
@@ -203,6 +209,7 @@ function startContinuationForUser(userId, channel) {
|
||||
const { messages, debug } = await buildPrompt(userId, incomingText, {
|
||||
context: cachedContext,
|
||||
userName: cachedContext?.userName || null,
|
||||
includeMemories: false,
|
||||
});
|
||||
cacheContext(userId, debug.context);
|
||||
const reply = await chatCompletion(messages, { temperature: 0.7, maxTokens: 200 });
|
||||
@@ -687,6 +694,7 @@ client.on('messageCreate', async (message) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const recallTrigger = matchesMemoryRecallCue(cleaned);
|
||||
const intelMeta = (await maybeFetchLiveIntel(userId, cleaned)) || {
|
||||
liveIntel: null,
|
||||
blockedSearchTerm: null,
|
||||
@@ -696,6 +704,8 @@ client.on('messageCreate', async (message) => {
|
||||
liveIntel: intelMeta.liveIntel,
|
||||
blockedSearchTerm: intelMeta.blockedSearchTerm,
|
||||
searchOutage: intelMeta.searchOutage,
|
||||
includeMemories: recallTrigger,
|
||||
similarityThreshold: config.memoryRecallSimilarityThreshold,
|
||||
userName: message.member?.displayName || message.author.username,
|
||||
});
|
||||
cacheContext(userId, debug.context);
|
||||
|
||||
@@ -7,6 +7,16 @@ dotenv.config();
|
||||
const defaultMemoryDbFile = fileURLToPath(new URL('../data/memory.sqlite', import.meta.url));
|
||||
const legacyMemoryFile = fileURLToPath(new URL('../data/memory.json', import.meta.url));
|
||||
|
||||
const memoryRecallTriggerPatterns = [
|
||||
/remember( when| that)?/i,
|
||||
/do you know( about)?/i,
|
||||
/do you remember/i,
|
||||
/\bwe talked\b/i,
|
||||
/\brecall\b/i,
|
||||
/\bremind\b/i,
|
||||
/\brefresh my memory\b/i,
|
||||
];
|
||||
|
||||
const requiredEnv = ['DISCORD_TOKEN'];
|
||||
requiredEnv.forEach((key) => {
|
||||
if (!process.env[key]) {
|
||||
@@ -33,10 +43,15 @@ export const config = {
|
||||
memoryDbFile: process.env.MEMORY_DB_FILE ? path.resolve(process.env.MEMORY_DB_FILE) : defaultMemoryDbFile,
|
||||
legacyMemoryFile,
|
||||
summaryTriggerChars: 2200,
|
||||
summaryTriggerTurns: process.env.SUMMARY_TRIGGER_TURNS ? parseInt(process.env.SUMMARY_TRIGGER_TURNS, 10) : 12,
|
||||
memoryPruneThreshold: 0.2,
|
||||
memoryCooldownMs: process.env.MEMORY_COOLDOWN_MS ? parseInt(process.env.MEMORY_COOLDOWN_MS, 10) : 3 * 60 * 1000,
|
||||
maxMemories: 8000,
|
||||
enableShortTermSummary: process.env.ENABLE_SHORT_TERM_SUMMARY !== 'false',
|
||||
memoryRecallSimilarityThreshold: process.env.MEMORY_RECALL_SIMILARITY_THRESHOLD
|
||||
? parseFloat(process.env.MEMORY_RECALL_SIMILARITY_THRESHOLD)
|
||||
: 0.62,
|
||||
memoryRecallTriggerPatterns,
|
||||
relevantMemoryCount: 3,
|
||||
longTermFetchLimit: 120,
|
||||
// Optional local dashboard that runs alongside the bot. Enable with
|
||||
|
||||
@@ -242,7 +242,10 @@ const fullShortTerm = (db, userId) =>
|
||||
const maybeSummarize = async (db, userId) => {
|
||||
const shortTermEntries = fullShortTerm(db, userId);
|
||||
const charCount = shortTermEntries.reduce((sum, msg) => sum + (msg.content?.length || 0), 0);
|
||||
if (charCount < config.summaryTriggerChars || shortTermEntries.length < config.shortTermLimit) {
|
||||
if (
|
||||
charCount < config.summaryTriggerChars &&
|
||||
shortTermEntries.length < (config.summaryTriggerTurns || config.shortTermLimit)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const userRow = get(db, 'SELECT summary FROM users WHERE id = ?', [userId]) || { summary: '' };
|
||||
@@ -327,7 +330,7 @@ const retrieveRelevantMemories = async (db, userId, query, options = {}) => {
|
||||
return [];
|
||||
}
|
||||
const limit = config.longTermFetchLimit || 200;
|
||||
const { includeAllUsers = false } = options;
|
||||
const { includeAllUsers = false, minScore = Number.NEGATIVE_INFINITY } = options;
|
||||
const params = [];
|
||||
const whereClause = includeAllUsers ? '' : ' WHERE user_id = ?';
|
||||
if (!includeAllUsers) {
|
||||
@@ -344,7 +347,7 @@ const retrieveRelevantMemories = async (db, userId, query, options = {}) => {
|
||||
}
|
||||
const now = Date.now();
|
||||
const cooldown = config.memoryCooldownMs || 0;
|
||||
const usage = memoryUsageMap.get(userId);
|
||||
const usage = getMemoryUsageMapForUser(userId);
|
||||
const eligibleRows =
|
||||
cooldown && usage
|
||||
? rows.filter((entry) => now - (usage.get(entry.id) || 0) > cooldown)
|
||||
@@ -360,13 +363,13 @@ const retrieveRelevantMemories = async (db, userId, query, options = {}) => {
|
||||
score: cosineSimilarity(queryEmbedding, embedding) + entry.importance * 0.1,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, config.relevantMemoryCount);
|
||||
if (scored.length) {
|
||||
const usageMap = getMemoryUsageMapForUser(userId);
|
||||
scored.forEach((entry) => usageMap.set(entry.id, now));
|
||||
.sort((a, b) => b.score - a.score);
|
||||
const filtered = scored.filter((entry) => entry.score >= minScore);
|
||||
const capped = filtered.slice(0, config.relevantMemoryCount);
|
||||
if (capped.length) {
|
||||
capped.forEach((entry) => usage.set(entry.id, now));
|
||||
}
|
||||
return scored;
|
||||
return capped;
|
||||
};
|
||||
|
||||
export async function appendShortTerm(userId, role, content) {
|
||||
@@ -390,8 +393,18 @@ export async function prepareContext(userId, incomingMessage, options = {}) {
|
||||
ensureUser(db, userId);
|
||||
const userRow = get(db, 'SELECT summary FROM users WHERE id = ?', [userId]) || { summary: '' };
|
||||
const shortTerm = getShortTermHistory(db, userId, config.shortTermLimit);
|
||||
const { includeAllUsers = false } = options;
|
||||
const memories = await retrieveRelevantMemories(db, userId, incomingMessage, { includeAllUsers });
|
||||
const {
|
||||
includeAllUsers = false,
|
||||
includeLongTerm = true,
|
||||
memorySimilarityThreshold = Number.NEGATIVE_INFINITY,
|
||||
} = options;
|
||||
const memories =
|
||||
includeLongTerm && incomingMessage?.trim()
|
||||
? await retrieveRelevantMemories(db, userId, incomingMessage, {
|
||||
includeAllUsers,
|
||||
minScore: memorySimilarityThreshold,
|
||||
})
|
||||
: [];
|
||||
return {
|
||||
shortTerm,
|
||||
summary: userRow.summary || '',
|
||||
|
||||
@@ -95,9 +95,21 @@ export async function buildPrompt(userId, incomingText, options = {}) {
|
||||
context: providedContext = null,
|
||||
useGlobalMemories = false,
|
||||
userName = null,
|
||||
includeMemories = false,
|
||||
similarityThreshold = null,
|
||||
} = options;
|
||||
const context =
|
||||
providedContext || (await prepareContext(userId, incomingText, { includeAllUsers: useGlobalMemories }));
|
||||
providedContext ||
|
||||
(await prepareContext(userId, incomingText, {
|
||||
includeAllUsers: useGlobalMemories,
|
||||
includeLongTerm: includeMemories || useGlobalMemories,
|
||||
memorySimilarityThreshold:
|
||||
includeMemories && similarityThreshold !== null
|
||||
? similarityThreshold
|
||||
: includeMemories
|
||||
? config.memoryRecallSimilarityThreshold
|
||||
: Number.NEGATIVE_INFINITY,
|
||||
}));
|
||||
if (userName) {
|
||||
context.userName = userName;
|
||||
} else if (context.userName === undefined) {
|
||||
|
||||
Reference in New Issue
Block a user