@elizaos/plugin-memory 1.1.1 → 2.0.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/evaluators/index.d.ts +3 -0
- package/dist/evaluators/index.d.ts.map +1 -0
- package/dist/evaluators/long-term-extraction.d.ts +1 -6
- package/dist/evaluators/long-term-extraction.d.ts.map +1 -0
- package/dist/evaluators/summarization.d.ts +1 -28
- package/dist/evaluators/summarization.d.ts.map +1 -0
- package/dist/generated/prompts/typescript/prompts.d.ts +16 -0
- package/dist/generated/prompts/typescript/prompts.d.ts.map +1 -0
- package/dist/index.d.ts +7 -39
- package/dist/index.d.ts.map +1 -0
- package/dist/{node/index.node.js → index.js} +800 -981
- package/dist/index.js.map +20 -0
- package/dist/providers/context-summary.d.ts +2 -11
- package/dist/providers/context-summary.d.ts.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/long-term-memory.d.ts +2 -16
- package/dist/providers/long-term-memory.d.ts.map +1 -0
- package/dist/schemas/index.d.ts +4 -9
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/long-term-memories.d.ts +1 -4
- package/dist/schemas/long-term-memories.d.ts.map +1 -0
- package/dist/schemas/memory-access-logs.d.ts +20 -58
- package/dist/schemas/memory-access-logs.d.ts.map +1 -0
- package/dist/schemas/session-summaries.d.ts +1 -4
- package/dist/schemas/session-summaries.d.ts.map +1 -0
- package/dist/services/memory-service.d.ts +7 -75
- package/dist/services/memory-service.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -26
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +45 -48
- package/README.md +0 -335
- package/dist/actions/remember.d.ts +0 -11
- package/dist/browser/index.browser.js +0 -209
- package/dist/browser/index.browser.js.map +0 -20
- package/dist/browser/index.d.ts +0 -2
- package/dist/cjs/index.d.ts +0 -2
- package/dist/cjs/index.node.cjs +0 -1293
- package/dist/cjs/index.node.js.map +0 -20
- package/dist/index.browser.d.ts +0 -2
- package/dist/index.node.d.ts +0 -2
- package/dist/node/index.d.ts +0 -2
- package/dist/node/index.node.js.map +0 -20
- package/dist/providers/recent-messages.d.ts +0 -15
|
@@ -9,535 +9,14 @@ var __export = (target, all) => {
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/
|
|
13
|
-
import {
|
|
14
|
-
Service,
|
|
15
|
-
logger
|
|
16
|
-
} from "@elizaos/core";
|
|
17
|
-
import { eq, and, desc, sql as sql4, cosineDistance, gte } from "drizzle-orm";
|
|
18
|
-
|
|
19
|
-
// src/schemas/index.ts
|
|
20
|
-
var exports_schemas = {};
|
|
21
|
-
__export(exports_schemas, {
|
|
22
|
-
sessionSummaries: () => sessionSummaries,
|
|
23
|
-
memoryAccessLogs: () => memoryAccessLogs,
|
|
24
|
-
longTermMemories: () => longTermMemories
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// src/schemas/long-term-memories.ts
|
|
28
|
-
import { sql } from "drizzle-orm";
|
|
29
|
-
import {
|
|
30
|
-
pgTable,
|
|
31
|
-
text,
|
|
32
|
-
integer,
|
|
33
|
-
jsonb,
|
|
34
|
-
real,
|
|
35
|
-
index,
|
|
36
|
-
varchar,
|
|
37
|
-
timestamp
|
|
38
|
-
} from "drizzle-orm/pg-core";
|
|
39
|
-
var longTermMemories = pgTable("long_term_memories", {
|
|
40
|
-
id: varchar("id", { length: 36 }).primaryKey(),
|
|
41
|
-
agentId: varchar("agent_id", { length: 36 }).notNull(),
|
|
42
|
-
entityId: varchar("entity_id", { length: 36 }).notNull(),
|
|
43
|
-
category: text("category").notNull(),
|
|
44
|
-
content: text("content").notNull(),
|
|
45
|
-
metadata: jsonb("metadata"),
|
|
46
|
-
embedding: real("embedding").array(),
|
|
47
|
-
confidence: real("confidence").default(1),
|
|
48
|
-
source: text("source"),
|
|
49
|
-
createdAt: timestamp("created_at").default(sql`now()`).notNull(),
|
|
50
|
-
updatedAt: timestamp("updated_at").default(sql`now()`).notNull(),
|
|
51
|
-
lastAccessedAt: timestamp("last_accessed_at"),
|
|
52
|
-
accessCount: integer("access_count").default(0)
|
|
53
|
-
}, (table) => ({
|
|
54
|
-
agentEntityIdx: index("long_term_memories_agent_entity_idx").on(table.agentId, table.entityId),
|
|
55
|
-
categoryIdx: index("long_term_memories_category_idx").on(table.category),
|
|
56
|
-
confidenceIdx: index("long_term_memories_confidence_idx").on(table.confidence),
|
|
57
|
-
createdAtIdx: index("long_term_memories_created_at_idx").on(table.createdAt)
|
|
58
|
-
}));
|
|
59
|
-
// src/schemas/session-summaries.ts
|
|
60
|
-
import { sql as sql2 } from "drizzle-orm";
|
|
61
|
-
import {
|
|
62
|
-
pgTable as pgTable2,
|
|
63
|
-
text as text2,
|
|
64
|
-
integer as integer2,
|
|
65
|
-
jsonb as jsonb2,
|
|
66
|
-
real as real2,
|
|
67
|
-
index as index2,
|
|
68
|
-
varchar as varchar2,
|
|
69
|
-
timestamp as timestamp2
|
|
70
|
-
} from "drizzle-orm/pg-core";
|
|
71
|
-
var sessionSummaries = pgTable2("session_summaries", {
|
|
72
|
-
id: varchar2("id", { length: 36 }).primaryKey(),
|
|
73
|
-
agentId: varchar2("agent_id", { length: 36 }).notNull(),
|
|
74
|
-
roomId: varchar2("room_id", { length: 36 }).notNull(),
|
|
75
|
-
entityId: varchar2("entity_id", { length: 36 }),
|
|
76
|
-
summary: text2("summary").notNull(),
|
|
77
|
-
messageCount: integer2("message_count").notNull(),
|
|
78
|
-
lastMessageOffset: integer2("last_message_offset").notNull().default(0),
|
|
79
|
-
startTime: timestamp2("start_time").notNull(),
|
|
80
|
-
endTime: timestamp2("end_time").notNull(),
|
|
81
|
-
topics: jsonb2("topics"),
|
|
82
|
-
metadata: jsonb2("metadata"),
|
|
83
|
-
embedding: real2("embedding").array(),
|
|
84
|
-
createdAt: timestamp2("created_at").default(sql2`now()`).notNull(),
|
|
85
|
-
updatedAt: timestamp2("updated_at").default(sql2`now()`).notNull()
|
|
86
|
-
}, (table) => ({
|
|
87
|
-
agentRoomIdx: index2("session_summaries_agent_room_idx").on(table.agentId, table.roomId),
|
|
88
|
-
entityIdx: index2("session_summaries_entity_idx").on(table.entityId),
|
|
89
|
-
startTimeIdx: index2("session_summaries_start_time_idx").on(table.startTime)
|
|
90
|
-
}));
|
|
91
|
-
// src/schemas/memory-access-logs.ts
|
|
92
|
-
import { sql as sql3 } from "drizzle-orm";
|
|
93
|
-
import { pgTable as pgTable3, text as text3, integer as integer3, real as real3, index as index3, varchar as varchar3, timestamp as timestamp3 } from "drizzle-orm/pg-core";
|
|
94
|
-
var memoryAccessLogs = pgTable3("memory_access_logs", {
|
|
95
|
-
id: varchar3("id", { length: 36 }).primaryKey(),
|
|
96
|
-
agentId: varchar3("agent_id", { length: 36 }).notNull(),
|
|
97
|
-
memoryId: varchar3("memory_id", { length: 36 }).notNull(),
|
|
98
|
-
memoryType: text3("memory_type").notNull(),
|
|
99
|
-
accessedAt: timestamp3("accessed_at").default(sql3`now()`).notNull(),
|
|
100
|
-
roomId: varchar3("room_id", { length: 36 }),
|
|
101
|
-
relevanceScore: real3("relevance_score"),
|
|
102
|
-
wasUseful: integer3("was_useful")
|
|
103
|
-
}, (table) => ({
|
|
104
|
-
memoryIdx: index3("memory_access_logs_memory_idx").on(table.memoryId),
|
|
105
|
-
agentIdx: index3("memory_access_logs_agent_idx").on(table.agentId),
|
|
106
|
-
accessedAtIdx: index3("memory_access_logs_accessed_at_idx").on(table.accessedAt)
|
|
107
|
-
}));
|
|
108
|
-
// src/services/memory-service.ts
|
|
109
|
-
class MemoryService extends Service {
|
|
110
|
-
static serviceType = "memory";
|
|
111
|
-
sessionMessageCounts;
|
|
112
|
-
memoryConfig;
|
|
113
|
-
lastExtractionCheckpoints;
|
|
114
|
-
capabilityDescription = "Advanced memory management with short-term summarization and long-term persistent facts";
|
|
115
|
-
constructor(runtime) {
|
|
116
|
-
super(runtime);
|
|
117
|
-
this.sessionMessageCounts = new Map;
|
|
118
|
-
this.lastExtractionCheckpoints = new Map;
|
|
119
|
-
this.memoryConfig = {
|
|
120
|
-
shortTermSummarizationThreshold: 16,
|
|
121
|
-
shortTermRetainRecent: 6,
|
|
122
|
-
shortTermSummarizationInterval: 10,
|
|
123
|
-
longTermExtractionEnabled: true,
|
|
124
|
-
longTermVectorSearchEnabled: false,
|
|
125
|
-
longTermConfidenceThreshold: 0.85,
|
|
126
|
-
longTermExtractionThreshold: 30,
|
|
127
|
-
longTermExtractionInterval: 10,
|
|
128
|
-
summaryModelType: "TEXT_LARGE",
|
|
129
|
-
summaryMaxTokens: 2500,
|
|
130
|
-
summaryMaxNewMessages: 20
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
static async start(runtime) {
|
|
134
|
-
const service = new MemoryService(runtime);
|
|
135
|
-
await service.initialize(runtime);
|
|
136
|
-
return service;
|
|
137
|
-
}
|
|
138
|
-
async stop() {
|
|
139
|
-
logger.info("MemoryService stopped");
|
|
140
|
-
}
|
|
141
|
-
async initialize(runtime) {
|
|
142
|
-
this.runtime = runtime;
|
|
143
|
-
const threshold = runtime.getSetting("MEMORY_SUMMARIZATION_THRESHOLD");
|
|
144
|
-
if (threshold) {
|
|
145
|
-
this.memoryConfig.shortTermSummarizationThreshold = parseInt(threshold, 10);
|
|
146
|
-
}
|
|
147
|
-
const retainRecent = runtime.getSetting("MEMORY_RETAIN_RECENT");
|
|
148
|
-
if (retainRecent) {
|
|
149
|
-
this.memoryConfig.shortTermRetainRecent = parseInt(retainRecent, 10);
|
|
150
|
-
}
|
|
151
|
-
const summarizationInterval = runtime.getSetting("MEMORY_SUMMARIZATION_INTERVAL");
|
|
152
|
-
if (summarizationInterval) {
|
|
153
|
-
this.memoryConfig.shortTermSummarizationInterval = parseInt(summarizationInterval, 10);
|
|
154
|
-
}
|
|
155
|
-
const maxNewMessages = runtime.getSetting("MEMORY_MAX_NEW_MESSAGES");
|
|
156
|
-
if (maxNewMessages) {
|
|
157
|
-
this.memoryConfig.summaryMaxNewMessages = parseInt(maxNewMessages, 10);
|
|
158
|
-
}
|
|
159
|
-
const longTermEnabled = runtime.getSetting("MEMORY_LONG_TERM_ENABLED");
|
|
160
|
-
if (longTermEnabled === "false") {
|
|
161
|
-
this.memoryConfig.longTermExtractionEnabled = false;
|
|
162
|
-
} else if (longTermEnabled === "true") {
|
|
163
|
-
this.memoryConfig.longTermExtractionEnabled = true;
|
|
164
|
-
}
|
|
165
|
-
const confidenceThreshold = runtime.getSetting("MEMORY_CONFIDENCE_THRESHOLD");
|
|
166
|
-
if (confidenceThreshold) {
|
|
167
|
-
this.memoryConfig.longTermConfidenceThreshold = parseFloat(confidenceThreshold);
|
|
168
|
-
}
|
|
169
|
-
const extractionThreshold = runtime.getSetting("MEMORY_EXTRACTION_THRESHOLD");
|
|
170
|
-
if (extractionThreshold) {
|
|
171
|
-
this.memoryConfig.longTermExtractionThreshold = parseInt(extractionThreshold, 10);
|
|
172
|
-
}
|
|
173
|
-
const extractionInterval = runtime.getSetting("MEMORY_EXTRACTION_INTERVAL");
|
|
174
|
-
if (extractionInterval) {
|
|
175
|
-
this.memoryConfig.longTermExtractionInterval = parseInt(extractionInterval, 10);
|
|
176
|
-
}
|
|
177
|
-
logger.debug({
|
|
178
|
-
summarizationThreshold: this.memoryConfig.shortTermSummarizationThreshold,
|
|
179
|
-
summarizationInterval: this.memoryConfig.shortTermSummarizationInterval,
|
|
180
|
-
maxNewMessages: this.memoryConfig.summaryMaxNewMessages,
|
|
181
|
-
retainRecent: this.memoryConfig.shortTermRetainRecent,
|
|
182
|
-
longTermEnabled: this.memoryConfig.longTermExtractionEnabled,
|
|
183
|
-
extractionThreshold: this.memoryConfig.longTermExtractionThreshold,
|
|
184
|
-
extractionInterval: this.memoryConfig.longTermExtractionInterval,
|
|
185
|
-
confidenceThreshold: this.memoryConfig.longTermConfidenceThreshold
|
|
186
|
-
}, "MemoryService initialized");
|
|
187
|
-
}
|
|
188
|
-
getDb() {
|
|
189
|
-
const db = this.runtime.db;
|
|
190
|
-
if (!db) {
|
|
191
|
-
throw new Error("Database not available");
|
|
192
|
-
}
|
|
193
|
-
return db;
|
|
194
|
-
}
|
|
195
|
-
getConfig() {
|
|
196
|
-
return { ...this.memoryConfig };
|
|
197
|
-
}
|
|
198
|
-
updateConfig(updates) {
|
|
199
|
-
this.memoryConfig = { ...this.memoryConfig, ...updates };
|
|
200
|
-
}
|
|
201
|
-
incrementMessageCount(roomId) {
|
|
202
|
-
const current = this.sessionMessageCounts.get(roomId) || 0;
|
|
203
|
-
const newCount = current + 1;
|
|
204
|
-
this.sessionMessageCounts.set(roomId, newCount);
|
|
205
|
-
return newCount;
|
|
206
|
-
}
|
|
207
|
-
resetMessageCount(roomId) {
|
|
208
|
-
this.sessionMessageCounts.set(roomId, 0);
|
|
209
|
-
}
|
|
210
|
-
async shouldSummarize(roomId) {
|
|
211
|
-
const count = await this.runtime.countMemories(roomId, false, "messages");
|
|
212
|
-
return count >= this.memoryConfig.shortTermSummarizationThreshold;
|
|
213
|
-
}
|
|
214
|
-
getExtractionKey(entityId, roomId) {
|
|
215
|
-
return `memory:extraction:${entityId}:${roomId}`;
|
|
216
|
-
}
|
|
217
|
-
async getLastExtractionCheckpoint(entityId, roomId) {
|
|
218
|
-
const key = this.getExtractionKey(entityId, roomId);
|
|
219
|
-
const cached = this.lastExtractionCheckpoints.get(key);
|
|
220
|
-
if (cached !== undefined) {
|
|
221
|
-
return cached;
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
const checkpoint = await this.runtime.getCache(key);
|
|
225
|
-
const messageCount = checkpoint ?? 0;
|
|
226
|
-
this.lastExtractionCheckpoints.set(key, messageCount);
|
|
227
|
-
return messageCount;
|
|
228
|
-
} catch (error) {
|
|
229
|
-
logger.warn({ error }, "Failed to get extraction checkpoint from cache");
|
|
230
|
-
return 0;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
async setLastExtractionCheckpoint(entityId, roomId, messageCount) {
|
|
234
|
-
const key = this.getExtractionKey(entityId, roomId);
|
|
235
|
-
this.lastExtractionCheckpoints.set(key, messageCount);
|
|
236
|
-
try {
|
|
237
|
-
await this.runtime.setCache(key, messageCount);
|
|
238
|
-
logger.debug(`Set extraction checkpoint for ${entityId} in room ${roomId} at message count ${messageCount}`);
|
|
239
|
-
} catch (error) {
|
|
240
|
-
logger.error({ error }, "Failed to persist extraction checkpoint to cache");
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async shouldRunExtraction(entityId, roomId, currentMessageCount) {
|
|
244
|
-
const threshold = this.memoryConfig.longTermExtractionThreshold;
|
|
245
|
-
const interval = this.memoryConfig.longTermExtractionInterval;
|
|
246
|
-
if (currentMessageCount < threshold) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
const lastCheckpoint = await this.getLastExtractionCheckpoint(entityId, roomId);
|
|
250
|
-
const currentCheckpoint = Math.floor(currentMessageCount / interval) * interval;
|
|
251
|
-
const shouldRun = currentMessageCount >= threshold && currentCheckpoint > lastCheckpoint;
|
|
252
|
-
logger.debug({
|
|
253
|
-
entityId,
|
|
254
|
-
roomId,
|
|
255
|
-
currentMessageCount,
|
|
256
|
-
threshold,
|
|
257
|
-
interval,
|
|
258
|
-
lastCheckpoint,
|
|
259
|
-
currentCheckpoint,
|
|
260
|
-
shouldRun
|
|
261
|
-
}, "Extraction check");
|
|
262
|
-
return shouldRun;
|
|
263
|
-
}
|
|
264
|
-
async storeLongTermMemory(memory) {
|
|
265
|
-
const db = this.getDb();
|
|
266
|
-
const id = crypto.randomUUID();
|
|
267
|
-
const now = new Date;
|
|
268
|
-
const newMemory = {
|
|
269
|
-
id,
|
|
270
|
-
createdAt: now,
|
|
271
|
-
updatedAt: now,
|
|
272
|
-
accessCount: 0,
|
|
273
|
-
...memory
|
|
274
|
-
};
|
|
275
|
-
try {
|
|
276
|
-
await db.insert(longTermMemories).values({
|
|
277
|
-
id: newMemory.id,
|
|
278
|
-
agentId: newMemory.agentId,
|
|
279
|
-
entityId: newMemory.entityId,
|
|
280
|
-
category: newMemory.category,
|
|
281
|
-
content: newMemory.content,
|
|
282
|
-
metadata: newMemory.metadata || {},
|
|
283
|
-
embedding: newMemory.embedding,
|
|
284
|
-
confidence: newMemory.confidence,
|
|
285
|
-
source: newMemory.source,
|
|
286
|
-
accessCount: newMemory.accessCount,
|
|
287
|
-
createdAt: now,
|
|
288
|
-
updatedAt: now,
|
|
289
|
-
lastAccessedAt: newMemory.lastAccessedAt
|
|
290
|
-
});
|
|
291
|
-
} catch (error) {
|
|
292
|
-
logger.error({ error }, "Failed to store long-term memory");
|
|
293
|
-
throw error;
|
|
294
|
-
}
|
|
295
|
-
logger.info(`Stored long-term memory: ${newMemory.category} for entity ${newMemory.entityId}`);
|
|
296
|
-
return newMemory;
|
|
297
|
-
}
|
|
298
|
-
async getLongTermMemories(entityId, category, limit = 10) {
|
|
299
|
-
const db = this.getDb();
|
|
300
|
-
const conditions = [
|
|
301
|
-
eq(longTermMemories.agentId, this.runtime.agentId),
|
|
302
|
-
eq(longTermMemories.entityId, entityId)
|
|
303
|
-
];
|
|
304
|
-
if (category) {
|
|
305
|
-
conditions.push(eq(longTermMemories.category, category));
|
|
306
|
-
}
|
|
307
|
-
const results = await db.select().from(longTermMemories).where(and(...conditions)).orderBy(desc(longTermMemories.confidence), desc(longTermMemories.updatedAt)).limit(limit);
|
|
308
|
-
return results.map((row) => ({
|
|
309
|
-
id: row.id,
|
|
310
|
-
agentId: row.agentId,
|
|
311
|
-
entityId: row.entityId,
|
|
312
|
-
category: row.category,
|
|
313
|
-
content: row.content,
|
|
314
|
-
metadata: row.metadata,
|
|
315
|
-
embedding: row.embedding,
|
|
316
|
-
confidence: row.confidence,
|
|
317
|
-
source: row.source,
|
|
318
|
-
createdAt: row.createdAt,
|
|
319
|
-
updatedAt: row.updatedAt,
|
|
320
|
-
lastAccessedAt: row.lastAccessedAt,
|
|
321
|
-
accessCount: row.accessCount
|
|
322
|
-
}));
|
|
323
|
-
}
|
|
324
|
-
async updateLongTermMemory(id, entityId, updates) {
|
|
325
|
-
const db = this.getDb();
|
|
326
|
-
const updateData = {
|
|
327
|
-
updatedAt: new Date
|
|
328
|
-
};
|
|
329
|
-
if (updates.content !== undefined) {
|
|
330
|
-
updateData.content = updates.content;
|
|
331
|
-
}
|
|
332
|
-
if (updates.metadata !== undefined) {
|
|
333
|
-
updateData.metadata = updates.metadata;
|
|
334
|
-
}
|
|
335
|
-
if (updates.confidence !== undefined) {
|
|
336
|
-
updateData.confidence = updates.confidence;
|
|
337
|
-
}
|
|
338
|
-
if (updates.embedding !== undefined) {
|
|
339
|
-
updateData.embedding = updates.embedding;
|
|
340
|
-
}
|
|
341
|
-
if (updates.lastAccessedAt !== undefined) {
|
|
342
|
-
updateData.lastAccessedAt = updates.lastAccessedAt;
|
|
343
|
-
}
|
|
344
|
-
if (updates.accessCount !== undefined) {
|
|
345
|
-
updateData.accessCount = updates.accessCount;
|
|
346
|
-
}
|
|
347
|
-
await db.update(longTermMemories).set(updateData).where(and(eq(longTermMemories.id, id), eq(longTermMemories.agentId, this.runtime.agentId), eq(longTermMemories.entityId, entityId)));
|
|
348
|
-
logger.info(`Updated long-term memory: ${id} for entity ${entityId}`);
|
|
349
|
-
}
|
|
350
|
-
async deleteLongTermMemory(id, entityId) {
|
|
351
|
-
const db = this.getDb();
|
|
352
|
-
await db.delete(longTermMemories).where(and(eq(longTermMemories.id, id), eq(longTermMemories.agentId, this.runtime.agentId), eq(longTermMemories.entityId, entityId)));
|
|
353
|
-
logger.info(`Deleted long-term memory: ${id} for entity ${entityId}`);
|
|
354
|
-
}
|
|
355
|
-
async getCurrentSessionSummary(roomId) {
|
|
356
|
-
const db = this.getDb();
|
|
357
|
-
const results = await db.select().from(sessionSummaries).where(and(eq(sessionSummaries.agentId, this.runtime.agentId), eq(sessionSummaries.roomId, roomId))).orderBy(desc(sessionSummaries.updatedAt)).limit(1);
|
|
358
|
-
if (results.length === 0) {
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
const row = results[0];
|
|
362
|
-
return {
|
|
363
|
-
id: row.id,
|
|
364
|
-
agentId: row.agentId,
|
|
365
|
-
roomId: row.roomId,
|
|
366
|
-
entityId: row.entityId,
|
|
367
|
-
summary: row.summary,
|
|
368
|
-
messageCount: row.messageCount,
|
|
369
|
-
lastMessageOffset: row.lastMessageOffset,
|
|
370
|
-
startTime: row.startTime,
|
|
371
|
-
endTime: row.endTime,
|
|
372
|
-
topics: row.topics || [],
|
|
373
|
-
metadata: row.metadata,
|
|
374
|
-
embedding: row.embedding,
|
|
375
|
-
createdAt: row.createdAt,
|
|
376
|
-
updatedAt: row.updatedAt
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
async storeSessionSummary(summary) {
|
|
380
|
-
const db = this.getDb();
|
|
381
|
-
const id = crypto.randomUUID();
|
|
382
|
-
const now = new Date;
|
|
383
|
-
const newSummary = {
|
|
384
|
-
id,
|
|
385
|
-
createdAt: now,
|
|
386
|
-
updatedAt: now,
|
|
387
|
-
...summary
|
|
388
|
-
};
|
|
389
|
-
await db.insert(sessionSummaries).values({
|
|
390
|
-
id: newSummary.id,
|
|
391
|
-
agentId: newSummary.agentId,
|
|
392
|
-
roomId: newSummary.roomId,
|
|
393
|
-
entityId: newSummary.entityId || null,
|
|
394
|
-
summary: newSummary.summary,
|
|
395
|
-
messageCount: newSummary.messageCount,
|
|
396
|
-
lastMessageOffset: newSummary.lastMessageOffset,
|
|
397
|
-
startTime: newSummary.startTime,
|
|
398
|
-
endTime: newSummary.endTime,
|
|
399
|
-
topics: newSummary.topics || [],
|
|
400
|
-
metadata: newSummary.metadata || {},
|
|
401
|
-
embedding: newSummary.embedding,
|
|
402
|
-
createdAt: now,
|
|
403
|
-
updatedAt: now
|
|
404
|
-
});
|
|
405
|
-
logger.info(`Stored session summary for room ${newSummary.roomId}`);
|
|
406
|
-
return newSummary;
|
|
407
|
-
}
|
|
408
|
-
async updateSessionSummary(id, roomId, updates) {
|
|
409
|
-
const db = this.getDb();
|
|
410
|
-
const updateData = {
|
|
411
|
-
updatedAt: new Date
|
|
412
|
-
};
|
|
413
|
-
if (updates.summary !== undefined) {
|
|
414
|
-
updateData.summary = updates.summary;
|
|
415
|
-
}
|
|
416
|
-
if (updates.messageCount !== undefined) {
|
|
417
|
-
updateData.messageCount = updates.messageCount;
|
|
418
|
-
}
|
|
419
|
-
if (updates.lastMessageOffset !== undefined) {
|
|
420
|
-
updateData.lastMessageOffset = updates.lastMessageOffset;
|
|
421
|
-
}
|
|
422
|
-
if (updates.endTime !== undefined) {
|
|
423
|
-
updateData.endTime = updates.endTime;
|
|
424
|
-
}
|
|
425
|
-
if (updates.topics !== undefined) {
|
|
426
|
-
updateData.topics = updates.topics;
|
|
427
|
-
}
|
|
428
|
-
if (updates.metadata !== undefined) {
|
|
429
|
-
updateData.metadata = updates.metadata;
|
|
430
|
-
}
|
|
431
|
-
if (updates.embedding !== undefined) {
|
|
432
|
-
updateData.embedding = updates.embedding;
|
|
433
|
-
}
|
|
434
|
-
await db.update(sessionSummaries).set(updateData).where(and(eq(sessionSummaries.id, id), eq(sessionSummaries.agentId, this.runtime.agentId), eq(sessionSummaries.roomId, roomId)));
|
|
435
|
-
logger.info(`Updated session summary: ${id} for room ${roomId}`);
|
|
436
|
-
}
|
|
437
|
-
async getSessionSummaries(roomId, limit = 5) {
|
|
438
|
-
const db = this.getDb();
|
|
439
|
-
const results = await db.select().from(sessionSummaries).where(and(eq(sessionSummaries.agentId, this.runtime.agentId), eq(sessionSummaries.roomId, roomId))).orderBy(desc(sessionSummaries.updatedAt)).limit(limit);
|
|
440
|
-
return results.map((row) => ({
|
|
441
|
-
id: row.id,
|
|
442
|
-
agentId: row.agentId,
|
|
443
|
-
roomId: row.roomId,
|
|
444
|
-
entityId: row.entityId,
|
|
445
|
-
summary: row.summary,
|
|
446
|
-
messageCount: row.messageCount,
|
|
447
|
-
lastMessageOffset: row.lastMessageOffset,
|
|
448
|
-
startTime: row.startTime,
|
|
449
|
-
endTime: row.endTime,
|
|
450
|
-
topics: row.topics || [],
|
|
451
|
-
metadata: row.metadata,
|
|
452
|
-
embedding: row.embedding,
|
|
453
|
-
createdAt: row.createdAt,
|
|
454
|
-
updatedAt: row.updatedAt
|
|
455
|
-
}));
|
|
456
|
-
}
|
|
457
|
-
async searchLongTermMemories(entityId, queryEmbedding, limit = 5, matchThreshold = 0.7) {
|
|
458
|
-
if (!this.memoryConfig.longTermVectorSearchEnabled) {
|
|
459
|
-
logger.warn("Vector search is not enabled, falling back to recent memories");
|
|
460
|
-
return this.getLongTermMemories(entityId, undefined, limit);
|
|
461
|
-
}
|
|
462
|
-
const db = this.getDb();
|
|
463
|
-
try {
|
|
464
|
-
const cleanVector = queryEmbedding.map((n) => Number.isFinite(n) ? Number(n.toFixed(6)) : 0);
|
|
465
|
-
const similarity = sql4`1 - (${cosineDistance(longTermMemories.embedding, cleanVector)})`;
|
|
466
|
-
const conditions = [
|
|
467
|
-
eq(longTermMemories.agentId, this.runtime.agentId),
|
|
468
|
-
eq(longTermMemories.entityId, entityId),
|
|
469
|
-
sql4`${longTermMemories.embedding} IS NOT NULL`
|
|
470
|
-
];
|
|
471
|
-
if (matchThreshold > 0) {
|
|
472
|
-
conditions.push(gte(similarity, matchThreshold));
|
|
473
|
-
}
|
|
474
|
-
const results = await db.select({
|
|
475
|
-
memory: longTermMemories,
|
|
476
|
-
similarity
|
|
477
|
-
}).from(longTermMemories).where(and(...conditions)).orderBy(desc(similarity)).limit(limit);
|
|
478
|
-
return results.map((row) => ({
|
|
479
|
-
id: row.memory.id,
|
|
480
|
-
agentId: row.memory.agentId,
|
|
481
|
-
entityId: row.memory.entityId,
|
|
482
|
-
category: row.memory.category,
|
|
483
|
-
content: row.memory.content,
|
|
484
|
-
metadata: row.memory.metadata,
|
|
485
|
-
embedding: row.memory.embedding,
|
|
486
|
-
confidence: row.memory.confidence,
|
|
487
|
-
source: row.memory.source,
|
|
488
|
-
createdAt: row.memory.createdAt,
|
|
489
|
-
updatedAt: row.memory.updatedAt,
|
|
490
|
-
lastAccessedAt: row.memory.lastAccessedAt,
|
|
491
|
-
accessCount: row.memory.accessCount,
|
|
492
|
-
similarity: row.similarity
|
|
493
|
-
}));
|
|
494
|
-
} catch (error) {
|
|
495
|
-
logger.warn({ error }, "Vector search failed, falling back to recent memories");
|
|
496
|
-
return this.getLongTermMemories(entityId, undefined, limit);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
async getFormattedLongTermMemories(entityId) {
|
|
500
|
-
const memories = await this.getLongTermMemories(entityId, undefined, 20);
|
|
501
|
-
if (memories.length === 0) {
|
|
502
|
-
return "";
|
|
503
|
-
}
|
|
504
|
-
const grouped = new Map;
|
|
505
|
-
for (const memory of memories) {
|
|
506
|
-
if (!grouped.has(memory.category)) {
|
|
507
|
-
grouped.set(memory.category, []);
|
|
508
|
-
}
|
|
509
|
-
grouped.get(memory.category)?.push(memory);
|
|
510
|
-
}
|
|
511
|
-
const sections = [];
|
|
512
|
-
for (const [category, categoryMemories] of grouped.entries()) {
|
|
513
|
-
const categoryName = category.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
514
|
-
const items = categoryMemories.map((m) => `- ${m.content}`).join(`
|
|
515
|
-
`);
|
|
516
|
-
sections.push(`**${categoryName}**:
|
|
517
|
-
${items}`);
|
|
518
|
-
}
|
|
519
|
-
return sections.join(`
|
|
520
|
-
|
|
521
|
-
`);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// src/evaluators/summarization.ts
|
|
12
|
+
// src/evaluators/long-term-extraction.ts
|
|
526
13
|
import {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
14
|
+
composePromptFromState,
|
|
15
|
+
logger,
|
|
16
|
+
ModelType
|
|
530
17
|
} from "@elizaos/core";
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
tableName: "messages",
|
|
534
|
-
roomId,
|
|
535
|
-
count: 100,
|
|
536
|
-
unique: false
|
|
537
|
-
});
|
|
538
|
-
const dialogueMessages = messages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result") && (msg.metadata?.type === "agent_response_message" || msg.metadata?.type === "user_message"));
|
|
539
|
-
return dialogueMessages.length;
|
|
540
|
-
}
|
|
18
|
+
|
|
19
|
+
// src/generated/prompts/typescript/prompts.ts
|
|
541
20
|
var initialSummarizationTemplate = `# Task: Summarize Conversation
|
|
542
21
|
|
|
543
22
|
You are analyzing a conversation to create a concise summary that captures the key points, topics, and important details.
|
|
@@ -550,228 +29,25 @@ Generate a summary that:
|
|
|
550
29
|
1. Captures the main topics discussed
|
|
551
30
|
2. Highlights key information shared
|
|
552
31
|
3. Notes any decisions made or questions asked
|
|
553
|
-
4. Maintains context for future reference
|
|
554
|
-
5. Is concise but comprehensive
|
|
555
|
-
|
|
556
|
-
**IMPORTANT**: Keep the summary under 2500 tokens. Be comprehensive but concise.
|
|
557
|
-
|
|
558
|
-
Also extract:
|
|
559
|
-
- **Topics**: List of main topics discussed (comma-separated)
|
|
560
|
-
- **Key Points**: Important facts or decisions (bullet points)
|
|
561
|
-
|
|
562
|
-
Respond in this XML format:
|
|
563
|
-
<summary>
|
|
564
|
-
<text>Your comprehensive summary here</text>
|
|
565
|
-
<topics>topic1, topic2, topic3</topics>
|
|
566
|
-
<keyPoints>
|
|
567
|
-
<point>First key point</point>
|
|
568
|
-
<point>Second key point</point>
|
|
569
|
-
</keyPoints>
|
|
570
|
-
</summary>`;
|
|
571
|
-
var updateSummarizationTemplate = `# Task: Update and Condense Conversation Summary
|
|
572
|
-
|
|
573
|
-
You are updating an existing conversation summary with new messages, while keeping the total summary concise.
|
|
574
|
-
|
|
575
|
-
# Existing Summary
|
|
576
|
-
{{existingSummary}}
|
|
577
|
-
|
|
578
|
-
# Existing Topics
|
|
579
|
-
{{existingTopics}}
|
|
580
|
-
|
|
581
|
-
# New Messages Since Last Summary
|
|
582
|
-
{{newMessages}}
|
|
583
|
-
|
|
584
|
-
# Instructions
|
|
585
|
-
Update the summary by:
|
|
586
|
-
1. Merging the existing summary with insights from the new messages
|
|
587
|
-
2. Removing redundant or less important details to stay under the token limit
|
|
588
|
-
3. Keeping the most important context and decisions
|
|
589
|
-
4. Adding new topics if they emerge
|
|
590
|
-
5. **CRITICAL**: Keep the ENTIRE updated summary under 2500 tokens
|
|
591
|
-
|
|
592
|
-
The goal is a rolling summary that captures the essence of the conversation without growing indefinitely.
|
|
593
|
-
|
|
594
|
-
Respond in this XML format:
|
|
595
|
-
<summary>
|
|
596
|
-
<text>Your updated and condensed summary here</text>
|
|
597
|
-
<topics>topic1, topic2, topic3</topics>
|
|
598
|
-
<keyPoints>
|
|
599
|
-
<point>First key point</point>
|
|
600
|
-
<point>Second key point</point>
|
|
601
|
-
</keyPoints>
|
|
602
|
-
</summary>`;
|
|
603
|
-
function parseSummaryXML(xml) {
|
|
604
|
-
const summaryMatch = xml.match(/<text>([\s\S]*?)<\/text>/);
|
|
605
|
-
const topicsMatch = xml.match(/<topics>([\s\S]*?)<\/topics>/);
|
|
606
|
-
const keyPointsMatches = xml.matchAll(/<point>([\s\S]*?)<\/point>/g);
|
|
607
|
-
const summary = summaryMatch ? summaryMatch[1].trim() : "Summary not available";
|
|
608
|
-
const topics = topicsMatch ? topicsMatch[1].split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
609
|
-
const keyPoints = Array.from(keyPointsMatches).map((match) => match[1].trim());
|
|
610
|
-
return { summary, topics, keyPoints };
|
|
611
|
-
}
|
|
612
|
-
var summarizationEvaluator = {
|
|
613
|
-
name: "MEMORY_SUMMARIZATION",
|
|
614
|
-
description: "Automatically summarizes conversations to optimize context usage",
|
|
615
|
-
similes: [
|
|
616
|
-
"CONVERSATION_SUMMARY",
|
|
617
|
-
"CONTEXT_COMPRESSION",
|
|
618
|
-
"MEMORY_OPTIMIZATION"
|
|
619
|
-
],
|
|
620
|
-
alwaysRun: true,
|
|
621
|
-
validate: async (runtime, message) => {
|
|
622
|
-
if (!message.content?.text) {
|
|
623
|
-
return false;
|
|
624
|
-
}
|
|
625
|
-
const memoryService = runtime.getService("memory");
|
|
626
|
-
if (!memoryService) {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
const config = memoryService.getConfig();
|
|
630
|
-
const currentDialogueCount = await getDialogueMessageCount(runtime, message.roomId);
|
|
631
|
-
const existingSummary = await memoryService.getCurrentSessionSummary(message.roomId);
|
|
632
|
-
if (!existingSummary) {
|
|
633
|
-
const shouldSummarize = currentDialogueCount >= config.shortTermSummarizationThreshold;
|
|
634
|
-
return shouldSummarize;
|
|
635
|
-
} else {
|
|
636
|
-
const newDialogueCount = currentDialogueCount - existingSummary.lastMessageOffset;
|
|
637
|
-
const shouldUpdate = newDialogueCount >= config.shortTermSummarizationInterval;
|
|
638
|
-
return shouldUpdate;
|
|
639
|
-
}
|
|
640
|
-
},
|
|
641
|
-
handler: async (runtime, message) => {
|
|
642
|
-
const memoryService = runtime.getService("memory");
|
|
643
|
-
if (!memoryService) {
|
|
644
|
-
logger2.error("MemoryService not found");
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
const config = memoryService.getConfig();
|
|
648
|
-
const { roomId } = message;
|
|
649
|
-
try {
|
|
650
|
-
logger2.info(`Starting summarization for room ${roomId}`);
|
|
651
|
-
const existingSummary = await memoryService.getCurrentSessionSummary(roomId);
|
|
652
|
-
const lastOffset = existingSummary?.lastMessageOffset || 0;
|
|
653
|
-
const allMessages = await runtime.getMemories({
|
|
654
|
-
tableName: "messages",
|
|
655
|
-
roomId,
|
|
656
|
-
count: 1000,
|
|
657
|
-
unique: false
|
|
658
|
-
});
|
|
659
|
-
const allDialogueMessages = allMessages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result") && (msg.metadata?.type === "agent_response_message" || msg.metadata?.type === "user_message"));
|
|
660
|
-
const totalDialogueCount = allDialogueMessages.length;
|
|
661
|
-
const newDialogueCount = totalDialogueCount - lastOffset;
|
|
662
|
-
if (newDialogueCount === 0) {
|
|
663
|
-
logger2.debug("No new dialogue messages to summarize");
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const maxNewMessages = config.summaryMaxNewMessages || 50;
|
|
667
|
-
const messagesToProcess = Math.min(newDialogueCount, maxNewMessages);
|
|
668
|
-
if (newDialogueCount > maxNewMessages) {
|
|
669
|
-
logger2.warn(`Capping new dialogue messages at ${maxNewMessages} (${newDialogueCount} available). Oldest messages will be skipped.`);
|
|
670
|
-
}
|
|
671
|
-
const sortedDialogueMessages = allDialogueMessages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
672
|
-
const newDialogueMessages = sortedDialogueMessages.slice(lastOffset, lastOffset + messagesToProcess);
|
|
673
|
-
if (newDialogueMessages.length === 0) {
|
|
674
|
-
logger2.debug("No new dialogue messages retrieved after filtering");
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
const formattedMessages = newDialogueMessages.map((msg) => {
|
|
678
|
-
const sender = msg.entityId === runtime.agentId ? runtime.character.name : "User";
|
|
679
|
-
return `${sender}: ${msg.content.text || "[non-text message]"}`;
|
|
680
|
-
}).join(`
|
|
681
|
-
`);
|
|
682
|
-
const state = await runtime.composeState(message);
|
|
683
|
-
let prompt;
|
|
684
|
-
let template;
|
|
685
|
-
if (existingSummary) {
|
|
686
|
-
template = updateSummarizationTemplate;
|
|
687
|
-
prompt = composePromptFromState({
|
|
688
|
-
state: {
|
|
689
|
-
...state,
|
|
690
|
-
existingSummary: existingSummary.summary,
|
|
691
|
-
existingTopics: existingSummary.topics?.join(", ") || "None",
|
|
692
|
-
newMessages: formattedMessages
|
|
693
|
-
},
|
|
694
|
-
template
|
|
695
|
-
});
|
|
696
|
-
} else {
|
|
697
|
-
const initialMessages = sortedDialogueMessages.map((msg) => {
|
|
698
|
-
const sender = msg.entityId === runtime.agentId ? runtime.character.name : "User";
|
|
699
|
-
return `${sender}: ${msg.content.text || "[non-text message]"}`;
|
|
700
|
-
}).join(`
|
|
701
|
-
`);
|
|
702
|
-
template = initialSummarizationTemplate;
|
|
703
|
-
prompt = composePromptFromState({
|
|
704
|
-
state: {
|
|
705
|
-
...state,
|
|
706
|
-
recentMessages: initialMessages
|
|
707
|
-
},
|
|
708
|
-
template
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
712
|
-
prompt,
|
|
713
|
-
maxTokens: config.summaryMaxTokens || 2500
|
|
714
|
-
});
|
|
715
|
-
const summaryResult = parseSummaryXML(response);
|
|
716
|
-
logger2.info(`${existingSummary ? "Updated" : "Generated"} summary: ${summaryResult.summary.substring(0, 100)}...`);
|
|
717
|
-
const newOffset = lastOffset + newDialogueMessages.length;
|
|
718
|
-
const firstMessage = newDialogueMessages[0];
|
|
719
|
-
const lastMessage = newDialogueMessages[newDialogueMessages.length - 1];
|
|
720
|
-
const startTime = existingSummary ? existingSummary.startTime : firstMessage?.createdAt && firstMessage.createdAt > 0 ? new Date(firstMessage.createdAt) : new Date;
|
|
721
|
-
const endTime = lastMessage?.createdAt && lastMessage.createdAt > 0 ? new Date(lastMessage.createdAt) : new Date;
|
|
722
|
-
if (existingSummary) {
|
|
723
|
-
await memoryService.updateSessionSummary(existingSummary.id, roomId, {
|
|
724
|
-
summary: summaryResult.summary,
|
|
725
|
-
messageCount: existingSummary.messageCount + newDialogueMessages.length,
|
|
726
|
-
lastMessageOffset: newOffset,
|
|
727
|
-
endTime,
|
|
728
|
-
topics: summaryResult.topics,
|
|
729
|
-
metadata: {
|
|
730
|
-
keyPoints: summaryResult.keyPoints
|
|
731
|
-
}
|
|
732
|
-
});
|
|
733
|
-
logger2.info(`Updated summary for room ${roomId}: ${newDialogueMessages.length} new dialogue messages processed (offset: ${lastOffset} → ${newOffset})`);
|
|
734
|
-
} else {
|
|
735
|
-
await memoryService.storeSessionSummary({
|
|
736
|
-
agentId: runtime.agentId,
|
|
737
|
-
roomId,
|
|
738
|
-
entityId: message.entityId !== runtime.agentId ? message.entityId : undefined,
|
|
739
|
-
summary: summaryResult.summary,
|
|
740
|
-
messageCount: totalDialogueCount,
|
|
741
|
-
lastMessageOffset: totalDialogueCount,
|
|
742
|
-
startTime,
|
|
743
|
-
endTime,
|
|
744
|
-
topics: summaryResult.topics,
|
|
745
|
-
metadata: {
|
|
746
|
-
keyPoints: summaryResult.keyPoints
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
logger2.info(`Created new summary for room ${roomId}: ${totalDialogueCount} dialogue messages summarized (offset: 0 → ${totalDialogueCount})`);
|
|
750
|
-
}
|
|
751
|
-
} catch (error) {
|
|
752
|
-
logger2.error({ error }, "Error during summarization:");
|
|
753
|
-
}
|
|
754
|
-
},
|
|
755
|
-
examples: []
|
|
756
|
-
};
|
|
32
|
+
4. Maintains context for future reference
|
|
33
|
+
5. Is concise but comprehensive
|
|
757
34
|
|
|
758
|
-
|
|
759
|
-
import {
|
|
760
|
-
logger as logger3,
|
|
761
|
-
ModelType as ModelType2,
|
|
762
|
-
composePromptFromState as composePromptFromState2
|
|
763
|
-
} from "@elizaos/core";
|
|
35
|
+
**IMPORTANT**: Keep the summary under 2500 tokens. Be comprehensive but concise.
|
|
764
36
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
(
|
|
768
|
-
LongTermMemoryCategory2["EPISODIC"] = "episodic";
|
|
769
|
-
LongTermMemoryCategory2["SEMANTIC"] = "semantic";
|
|
770
|
-
LongTermMemoryCategory2["PROCEDURAL"] = "procedural";
|
|
771
|
-
})(LongTermMemoryCategory ||= {});
|
|
37
|
+
Also extract:
|
|
38
|
+
- **Topics**: List of main topics discussed (comma-separated)
|
|
39
|
+
- **Key Points**: Important facts or decisions (bullet points)
|
|
772
40
|
|
|
773
|
-
|
|
774
|
-
|
|
41
|
+
Respond in this XML format:
|
|
42
|
+
<summary>
|
|
43
|
+
<text>Your comprehensive summary here</text>
|
|
44
|
+
<topics>topic1, topic2, topic3</topics>
|
|
45
|
+
<keyPoints>
|
|
46
|
+
<point>First key point</point>
|
|
47
|
+
<point>Second key point</point>
|
|
48
|
+
</keyPoints>
|
|
49
|
+
</summary>`;
|
|
50
|
+
var longTermExtractionTemplate = `# Task: Extract Long-Term Memory (Strict Criteria)
|
|
775
51
|
|
|
776
52
|
You are analyzing a conversation to extract ONLY the most critical, persistent information about the user using cognitive science memory categories.
|
|
777
53
|
|
|
@@ -904,6 +180,49 @@ Skills, workflows, methodologies, and how-to knowledge.
|
|
|
904
180
|
<confidence>0.92</confidence>
|
|
905
181
|
</memory>
|
|
906
182
|
</memories>`;
|
|
183
|
+
var updateSummarizationTemplate = `# Task: Update and Condense Conversation Summary
|
|
184
|
+
|
|
185
|
+
You are updating an existing conversation summary with new messages, while keeping the total summary concise.
|
|
186
|
+
|
|
187
|
+
# Existing Summary
|
|
188
|
+
{{existingSummary}}
|
|
189
|
+
|
|
190
|
+
# Existing Topics
|
|
191
|
+
{{existingTopics}}
|
|
192
|
+
|
|
193
|
+
# New Messages Since Last Summary
|
|
194
|
+
{{newMessages}}
|
|
195
|
+
|
|
196
|
+
# Instructions
|
|
197
|
+
Update the summary by:
|
|
198
|
+
1. Merging the existing summary with insights from the new messages
|
|
199
|
+
2. Removing redundant or less important details to stay under the token limit
|
|
200
|
+
3. Keeping the most important context and decisions
|
|
201
|
+
4. Adding new topics if they emerge
|
|
202
|
+
5. **CRITICAL**: Keep the ENTIRE updated summary under 2500 tokens
|
|
203
|
+
|
|
204
|
+
The goal is a rolling summary that captures the essence of the conversation without growing indefinitely.
|
|
205
|
+
|
|
206
|
+
Respond in this XML format:
|
|
207
|
+
<summary>
|
|
208
|
+
<text>Your updated and condensed summary here</text>
|
|
209
|
+
<topics>topic1, topic2, topic3</topics>
|
|
210
|
+
<keyPoints>
|
|
211
|
+
<point>First key point</point>
|
|
212
|
+
<point>Second key point</point>
|
|
213
|
+
</keyPoints>
|
|
214
|
+
</summary>`;
|
|
215
|
+
|
|
216
|
+
// src/types/index.ts
|
|
217
|
+
var LongTermMemoryCategory;
|
|
218
|
+
((LongTermMemoryCategory2) => {
|
|
219
|
+
LongTermMemoryCategory2["EPISODIC"] = "episodic";
|
|
220
|
+
LongTermMemoryCategory2["SEMANTIC"] = "semantic";
|
|
221
|
+
LongTermMemoryCategory2["PROCEDURAL"] = "procedural";
|
|
222
|
+
})(LongTermMemoryCategory ||= {});
|
|
223
|
+
|
|
224
|
+
// src/evaluators/long-term-extraction.ts
|
|
225
|
+
var extractionTemplate = longTermExtractionTemplate;
|
|
907
226
|
function parseMemoryExtractionXML(xml) {
|
|
908
227
|
const memoryMatches = xml.matchAll(/<memory>[\s\S]*?<category>(.*?)<\/category>[\s\S]*?<content>(.*?)<\/content>[\s\S]*?<confidence>(.*?)<\/confidence>[\s\S]*?<\/memory>/g);
|
|
909
228
|
const extractions = [];
|
|
@@ -912,10 +231,10 @@ function parseMemoryExtractionXML(xml) {
|
|
|
912
231
|
const content = match[2].trim();
|
|
913
232
|
const confidence = parseFloat(match[3].trim());
|
|
914
233
|
if (!Object.values(LongTermMemoryCategory).includes(category)) {
|
|
915
|
-
|
|
234
|
+
logger.warn(`Invalid memory category: ${category}`);
|
|
916
235
|
continue;
|
|
917
236
|
}
|
|
918
|
-
if (content && !isNaN(confidence)) {
|
|
237
|
+
if (content && !Number.isNaN(confidence)) {
|
|
919
238
|
extractions.push({ category, content, confidence });
|
|
920
239
|
}
|
|
921
240
|
}
|
|
@@ -939,23 +258,22 @@ var longTermExtractionEvaluator = {
|
|
|
939
258
|
}
|
|
940
259
|
const config = memoryService.getConfig();
|
|
941
260
|
if (!config.longTermExtractionEnabled) {
|
|
942
|
-
|
|
261
|
+
logger.debug("Long-term memory extraction is disabled");
|
|
943
262
|
return false;
|
|
944
263
|
}
|
|
945
264
|
const currentMessageCount = await runtime.countMemories(message.roomId, false, "messages");
|
|
946
|
-
|
|
947
|
-
return shouldRun;
|
|
265
|
+
return memoryService.shouldRunExtraction(message.entityId, message.roomId, currentMessageCount);
|
|
948
266
|
},
|
|
949
267
|
handler: async (runtime, message) => {
|
|
950
268
|
const memoryService = runtime.getService("memory");
|
|
951
269
|
if (!memoryService) {
|
|
952
|
-
|
|
270
|
+
logger.error("MemoryService not found");
|
|
953
271
|
return;
|
|
954
272
|
}
|
|
955
273
|
const config = memoryService.getConfig();
|
|
956
274
|
const { entityId, roomId } = message;
|
|
957
275
|
try {
|
|
958
|
-
|
|
276
|
+
logger.info(`Extracting long-term memories for entity ${entityId}`);
|
|
959
277
|
const recentMessages = await runtime.getMemories({
|
|
960
278
|
tableName: "messages",
|
|
961
279
|
roomId,
|
|
@@ -971,7 +289,7 @@ var longTermExtractionEvaluator = {
|
|
|
971
289
|
const formattedExisting = existingMemories.length > 0 ? existingMemories.map((m) => `[${m.category}] ${m.content} (confidence: ${m.confidence})`).join(`
|
|
972
290
|
`) : "None yet";
|
|
973
291
|
const state = await runtime.composeState(message);
|
|
974
|
-
const prompt =
|
|
292
|
+
const prompt = composePromptFromState({
|
|
975
293
|
state: {
|
|
976
294
|
...state,
|
|
977
295
|
recentMessages: formattedMessages,
|
|
@@ -979,11 +297,9 @@ var longTermExtractionEvaluator = {
|
|
|
979
297
|
},
|
|
980
298
|
template: extractionTemplate
|
|
981
299
|
});
|
|
982
|
-
const response = await runtime.useModel(
|
|
983
|
-
prompt
|
|
984
|
-
});
|
|
300
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
|
|
985
301
|
const extractions = parseMemoryExtractionXML(response);
|
|
986
|
-
|
|
302
|
+
logger.info(`Extracted ${extractions.length} long-term memories`);
|
|
987
303
|
for (const extraction of extractions) {
|
|
988
304
|
if (extraction.confidence >= Math.max(config.longTermConfidenceThreshold, 0.85)) {
|
|
989
305
|
await memoryService.storeLongTermMemory({
|
|
@@ -998,25 +314,242 @@ var longTermExtractionEvaluator = {
|
|
|
998
314
|
extractedAt: new Date().toISOString()
|
|
999
315
|
}
|
|
1000
316
|
});
|
|
1001
|
-
|
|
317
|
+
logger.info(`Stored long-term memory: [${extraction.category}] ${extraction.content.substring(0, 50)}...`);
|
|
1002
318
|
} else {
|
|
1003
|
-
|
|
319
|
+
logger.debug(`Skipped low-confidence memory: ${extraction.content} (confidence: ${extraction.confidence})`);
|
|
1004
320
|
}
|
|
1005
321
|
}
|
|
1006
322
|
const currentMessageCount = await runtime.countMemories(roomId, false, "messages");
|
|
1007
323
|
await memoryService.setLastExtractionCheckpoint(entityId, roomId, currentMessageCount);
|
|
1008
|
-
|
|
324
|
+
logger.debug(`Updated checkpoint to ${currentMessageCount} for entity ${entityId}`);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.error({ error }, "Error during long-term memory extraction:");
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
},
|
|
330
|
+
examples: []
|
|
331
|
+
};
|
|
332
|
+
// src/evaluators/summarization.ts
|
|
333
|
+
import {
|
|
334
|
+
composePromptFromState as composePromptFromState2,
|
|
335
|
+
logger as logger2,
|
|
336
|
+
ModelType as ModelType2
|
|
337
|
+
} from "@elizaos/core";
|
|
338
|
+
async function getDialogueMessageCount(runtime, roomId) {
|
|
339
|
+
const messages = await runtime.getMemories({
|
|
340
|
+
tableName: "messages",
|
|
341
|
+
roomId,
|
|
342
|
+
count: 100,
|
|
343
|
+
unique: false
|
|
344
|
+
});
|
|
345
|
+
const dialogueMessages = messages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result") && (msg.metadata?.type === "agent_response_message" || msg.metadata?.type === "user_message"));
|
|
346
|
+
return dialogueMessages.length;
|
|
347
|
+
}
|
|
348
|
+
function parseSummaryXML(xml) {
|
|
349
|
+
const summaryMatch = xml.match(/<text>([\s\S]*?)<\/text>/);
|
|
350
|
+
const topicsMatch = xml.match(/<topics>([\s\S]*?)<\/topics>/);
|
|
351
|
+
const keyPointsMatches = xml.matchAll(/<point>([\s\S]*?)<\/point>/g);
|
|
352
|
+
const summary = summaryMatch ? summaryMatch[1].trim() : "Summary not available";
|
|
353
|
+
const topics = topicsMatch ? topicsMatch[1].split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
354
|
+
const keyPoints = Array.from(keyPointsMatches).map((match) => match[1].trim());
|
|
355
|
+
return { summary, topics, keyPoints };
|
|
356
|
+
}
|
|
357
|
+
var summarizationEvaluator = {
|
|
358
|
+
name: "MEMORY_SUMMARIZATION",
|
|
359
|
+
description: "Automatically summarizes conversations to optimize context usage",
|
|
360
|
+
similes: ["CONVERSATION_SUMMARY", "CONTEXT_COMPRESSION", "MEMORY_OPTIMIZATION"],
|
|
361
|
+
alwaysRun: true,
|
|
362
|
+
validate: async (runtime, message) => {
|
|
363
|
+
if (!message.content?.text) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
const memoryService = runtime.getService("memory");
|
|
367
|
+
if (!memoryService) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const config = memoryService.getConfig();
|
|
371
|
+
const currentDialogueCount = await getDialogueMessageCount(runtime, message.roomId);
|
|
372
|
+
const existingSummary = await memoryService.getCurrentSessionSummary(message.roomId);
|
|
373
|
+
if (!existingSummary) {
|
|
374
|
+
return currentDialogueCount >= config.shortTermSummarizationThreshold;
|
|
375
|
+
} else {
|
|
376
|
+
const newDialogueCount = currentDialogueCount - existingSummary.lastMessageOffset;
|
|
377
|
+
return newDialogueCount >= config.shortTermSummarizationInterval;
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
handler: async (runtime, message) => {
|
|
381
|
+
const memoryService = runtime.getService("memory");
|
|
382
|
+
if (!memoryService) {
|
|
383
|
+
logger2.error("MemoryService not found");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const config = memoryService.getConfig();
|
|
387
|
+
const { roomId } = message;
|
|
388
|
+
try {
|
|
389
|
+
logger2.info(`Starting summarization for room ${roomId}`);
|
|
390
|
+
const existingSummary = await memoryService.getCurrentSessionSummary(roomId);
|
|
391
|
+
const lastOffset = existingSummary?.lastMessageOffset || 0;
|
|
392
|
+
const allMessages = await runtime.getMemories({
|
|
393
|
+
tableName: "messages",
|
|
394
|
+
roomId,
|
|
395
|
+
count: 1000,
|
|
396
|
+
unique: false
|
|
397
|
+
});
|
|
398
|
+
const allDialogueMessages = allMessages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result") && (msg.metadata?.type === "agent_response_message" || msg.metadata?.type === "user_message"));
|
|
399
|
+
const totalDialogueCount = allDialogueMessages.length;
|
|
400
|
+
const newDialogueCount = totalDialogueCount - lastOffset;
|
|
401
|
+
if (newDialogueCount === 0) {
|
|
402
|
+
logger2.debug("No new dialogue messages to summarize");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const maxNewMessages = config.summaryMaxNewMessages || 50;
|
|
406
|
+
const messagesToProcess = Math.min(newDialogueCount, maxNewMessages);
|
|
407
|
+
if (newDialogueCount > maxNewMessages) {
|
|
408
|
+
logger2.warn(`Capping new dialogue messages at ${maxNewMessages} (${newDialogueCount} available)`);
|
|
409
|
+
}
|
|
410
|
+
const sortedDialogueMessages = allDialogueMessages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
411
|
+
const newDialogueMessages = sortedDialogueMessages.slice(lastOffset, lastOffset + messagesToProcess);
|
|
412
|
+
if (newDialogueMessages.length === 0) {
|
|
413
|
+
logger2.debug("No new dialogue messages retrieved after filtering");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const formattedMessages = newDialogueMessages.map((msg) => {
|
|
417
|
+
const sender = msg.entityId === runtime.agentId ? runtime.character.name : "User";
|
|
418
|
+
return `${sender}: ${msg.content.text || "[non-text message]"}`;
|
|
419
|
+
}).join(`
|
|
420
|
+
`);
|
|
421
|
+
const state = await runtime.composeState(message);
|
|
422
|
+
let prompt;
|
|
423
|
+
let template;
|
|
424
|
+
if (existingSummary) {
|
|
425
|
+
template = updateSummarizationTemplate;
|
|
426
|
+
prompt = composePromptFromState2({
|
|
427
|
+
state: {
|
|
428
|
+
...state,
|
|
429
|
+
existingSummary: existingSummary.summary,
|
|
430
|
+
existingTopics: existingSummary.topics?.join(", ") || "None",
|
|
431
|
+
newMessages: formattedMessages
|
|
432
|
+
},
|
|
433
|
+
template
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
const initialMessages = sortedDialogueMessages.map((msg) => {
|
|
437
|
+
const sender = msg.entityId === runtime.agentId ? runtime.character.name : "User";
|
|
438
|
+
return `${sender}: ${msg.content.text || "[non-text message]"}`;
|
|
439
|
+
}).join(`
|
|
440
|
+
`);
|
|
441
|
+
template = initialSummarizationTemplate;
|
|
442
|
+
prompt = composePromptFromState2({
|
|
443
|
+
state: { ...state, recentMessages: initialMessages },
|
|
444
|
+
template
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const response = await runtime.useModel(ModelType2.TEXT_LARGE, {
|
|
448
|
+
prompt,
|
|
449
|
+
maxTokens: config.summaryMaxTokens || 2500
|
|
450
|
+
});
|
|
451
|
+
const summaryResult = parseSummaryXML(response);
|
|
452
|
+
logger2.info(`${existingSummary ? "Updated" : "Generated"} summary: ${summaryResult.summary.substring(0, 100)}...`);
|
|
453
|
+
const newOffset = lastOffset + newDialogueMessages.length;
|
|
454
|
+
const firstMessage = newDialogueMessages[0];
|
|
455
|
+
const lastMessage = newDialogueMessages[newDialogueMessages.length - 1];
|
|
456
|
+
const startTime = existingSummary ? existingSummary.startTime : firstMessage?.createdAt && firstMessage.createdAt > 0 ? new Date(firstMessage.createdAt) : new Date;
|
|
457
|
+
const endTime = lastMessage?.createdAt && lastMessage.createdAt > 0 ? new Date(lastMessage.createdAt) : new Date;
|
|
458
|
+
if (existingSummary) {
|
|
459
|
+
await memoryService.updateSessionSummary(existingSummary.id, roomId, {
|
|
460
|
+
summary: summaryResult.summary,
|
|
461
|
+
messageCount: existingSummary.messageCount + newDialogueMessages.length,
|
|
462
|
+
lastMessageOffset: newOffset,
|
|
463
|
+
endTime,
|
|
464
|
+
topics: summaryResult.topics,
|
|
465
|
+
metadata: { keyPoints: summaryResult.keyPoints }
|
|
466
|
+
});
|
|
467
|
+
logger2.info(`Updated summary for room ${roomId}: ${newDialogueMessages.length} messages processed`);
|
|
468
|
+
} else {
|
|
469
|
+
await memoryService.storeSessionSummary({
|
|
470
|
+
agentId: runtime.agentId,
|
|
471
|
+
roomId,
|
|
472
|
+
entityId: message.entityId !== runtime.agentId ? message.entityId : undefined,
|
|
473
|
+
summary: summaryResult.summary,
|
|
474
|
+
messageCount: totalDialogueCount,
|
|
475
|
+
lastMessageOffset: totalDialogueCount,
|
|
476
|
+
startTime,
|
|
477
|
+
endTime,
|
|
478
|
+
topics: summaryResult.topics,
|
|
479
|
+
metadata: { keyPoints: summaryResult.keyPoints }
|
|
480
|
+
});
|
|
481
|
+
logger2.info(`Created summary for room ${roomId}: ${totalDialogueCount} messages summarized`);
|
|
482
|
+
}
|
|
483
|
+
} catch (error) {
|
|
484
|
+
logger2.error({ error }, "Error during summarization:");
|
|
485
|
+
}
|
|
486
|
+
return;
|
|
487
|
+
},
|
|
488
|
+
examples: []
|
|
489
|
+
};
|
|
490
|
+
// src/providers/context-summary.ts
|
|
491
|
+
import {
|
|
492
|
+
addHeader,
|
|
493
|
+
logger as logger3
|
|
494
|
+
} from "@elizaos/core";
|
|
495
|
+
var contextSummaryProvider = {
|
|
496
|
+
name: "SUMMARIZED_CONTEXT",
|
|
497
|
+
description: "Provides summarized context from previous conversations",
|
|
498
|
+
position: 96,
|
|
499
|
+
get: async (runtime, message, _state) => {
|
|
500
|
+
try {
|
|
501
|
+
const memoryService = runtime.getService("memory");
|
|
502
|
+
const { roomId } = message;
|
|
503
|
+
if (!memoryService) {
|
|
504
|
+
return {
|
|
505
|
+
data: {},
|
|
506
|
+
values: { sessionSummaries: "", sessionSummariesWithTopics: "" },
|
|
507
|
+
text: ""
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
const currentSummary = await memoryService.getCurrentSessionSummary(roomId);
|
|
511
|
+
if (!currentSummary) {
|
|
512
|
+
return {
|
|
513
|
+
data: {},
|
|
514
|
+
values: { sessionSummaries: "", sessionSummariesWithTopics: "" },
|
|
515
|
+
text: ""
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const messageRange = `${currentSummary.messageCount} messages`;
|
|
519
|
+
const timeRange = new Date(currentSummary.startTime).toLocaleDateString();
|
|
520
|
+
let summaryOnly = `**Previous Conversation** (${messageRange}, ${timeRange})
|
|
521
|
+
`;
|
|
522
|
+
summaryOnly += currentSummary.summary;
|
|
523
|
+
let summaryWithTopics = summaryOnly;
|
|
524
|
+
if (currentSummary.topics && currentSummary.topics.length > 0) {
|
|
525
|
+
summaryWithTopics += `
|
|
526
|
+
*Topics: ${currentSummary.topics.join(", ")}*`;
|
|
527
|
+
}
|
|
528
|
+
const sessionSummaries = addHeader("# Conversation Summary", summaryOnly);
|
|
529
|
+
const sessionSummariesWithTopics = addHeader("# Conversation Summary", summaryWithTopics);
|
|
530
|
+
return {
|
|
531
|
+
data: {
|
|
532
|
+
summaryText: currentSummary.summary,
|
|
533
|
+
messageCount: currentSummary.messageCount,
|
|
534
|
+
topics: currentSummary.topics?.join(", ") || ""
|
|
535
|
+
},
|
|
536
|
+
values: { sessionSummaries, sessionSummariesWithTopics },
|
|
537
|
+
text: sessionSummariesWithTopics
|
|
538
|
+
};
|
|
1009
539
|
} catch (error) {
|
|
1010
|
-
logger3.error({ error }, "Error
|
|
540
|
+
logger3.error({ error }, "Error in contextSummaryProvider:");
|
|
541
|
+
return {
|
|
542
|
+
data: {},
|
|
543
|
+
values: { sessionSummaries: "", sessionSummariesWithTopics: "" },
|
|
544
|
+
text: ""
|
|
545
|
+
};
|
|
1011
546
|
}
|
|
1012
|
-
}
|
|
1013
|
-
examples: []
|
|
547
|
+
}
|
|
1014
548
|
};
|
|
1015
|
-
|
|
1016
549
|
// src/providers/long-term-memory.ts
|
|
1017
550
|
import {
|
|
1018
|
-
|
|
1019
|
-
|
|
551
|
+
addHeader as addHeader2,
|
|
552
|
+
logger as logger4
|
|
1020
553
|
} from "@elizaos/core";
|
|
1021
554
|
var longTermMemoryProvider = {
|
|
1022
555
|
name: "LONG_TERM_MEMORY",
|
|
@@ -1027,7 +560,7 @@ var longTermMemoryProvider = {
|
|
|
1027
560
|
const memoryService = runtime.getService("memory");
|
|
1028
561
|
if (!memoryService) {
|
|
1029
562
|
return {
|
|
1030
|
-
data: {
|
|
563
|
+
data: { memoryCount: 0 },
|
|
1031
564
|
values: { longTermMemories: "" },
|
|
1032
565
|
text: ""
|
|
1033
566
|
};
|
|
@@ -1035,7 +568,7 @@ var longTermMemoryProvider = {
|
|
|
1035
568
|
const { entityId } = message;
|
|
1036
569
|
if (entityId === runtime.agentId) {
|
|
1037
570
|
return {
|
|
1038
|
-
data: {
|
|
571
|
+
data: { memoryCount: 0 },
|
|
1039
572
|
values: { longTermMemories: "" },
|
|
1040
573
|
text: ""
|
|
1041
574
|
};
|
|
@@ -1043,13 +576,13 @@ var longTermMemoryProvider = {
|
|
|
1043
576
|
const memories = await memoryService.getLongTermMemories(entityId, undefined, 25);
|
|
1044
577
|
if (memories.length === 0) {
|
|
1045
578
|
return {
|
|
1046
|
-
data: {
|
|
579
|
+
data: { memoryCount: 0 },
|
|
1047
580
|
values: { longTermMemories: "" },
|
|
1048
581
|
text: ""
|
|
1049
582
|
};
|
|
1050
583
|
}
|
|
1051
584
|
const formattedMemories = await memoryService.getFormattedLongTermMemories(entityId);
|
|
1052
|
-
const
|
|
585
|
+
const text = addHeader2("# What I Know About You", formattedMemories);
|
|
1053
586
|
const categoryCounts = new Map;
|
|
1054
587
|
for (const memory of memories) {
|
|
1055
588
|
const count = categoryCounts.get(memory.category) || 0;
|
|
@@ -1058,257 +591,543 @@ var longTermMemoryProvider = {
|
|
|
1058
591
|
const categoryList = Array.from(categoryCounts.entries()).map(([cat, count]) => `${cat}: ${count}`).join(", ");
|
|
1059
592
|
return {
|
|
1060
593
|
data: {
|
|
1061
|
-
memories,
|
|
1062
|
-
|
|
594
|
+
memoryCount: memories.length,
|
|
595
|
+
categories: categoryList
|
|
1063
596
|
},
|
|
1064
597
|
values: {
|
|
1065
|
-
longTermMemories:
|
|
598
|
+
longTermMemories: text,
|
|
1066
599
|
memoryCategories: categoryList
|
|
1067
600
|
},
|
|
1068
|
-
text
|
|
601
|
+
text
|
|
1069
602
|
};
|
|
1070
603
|
} catch (error) {
|
|
1071
604
|
logger4.error({ error }, "Error in longTermMemoryProvider:");
|
|
1072
605
|
return {
|
|
1073
|
-
data: {
|
|
606
|
+
data: { memoryCount: 0 },
|
|
1074
607
|
values: { longTermMemories: "" },
|
|
1075
608
|
text: ""
|
|
1076
609
|
};
|
|
1077
610
|
}
|
|
1078
611
|
}
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
612
|
+
};
|
|
613
|
+
// src/schemas/index.ts
|
|
614
|
+
var exports_schemas = {};
|
|
615
|
+
__export(exports_schemas, {
|
|
616
|
+
sessionSummaries: () => sessionSummaries,
|
|
617
|
+
memoryAccessLogs: () => memoryAccessLogs,
|
|
618
|
+
longTermMemories: () => longTermMemories
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// src/schemas/long-term-memories.ts
|
|
622
|
+
import { sql } from "drizzle-orm";
|
|
623
|
+
import {
|
|
624
|
+
index,
|
|
625
|
+
integer,
|
|
626
|
+
jsonb,
|
|
627
|
+
pgTable,
|
|
628
|
+
real,
|
|
629
|
+
text,
|
|
630
|
+
timestamp,
|
|
631
|
+
varchar
|
|
632
|
+
} from "drizzle-orm/pg-core";
|
|
633
|
+
var longTermMemories = pgTable("long_term_memories", {
|
|
634
|
+
id: varchar("id", { length: 36 }).primaryKey(),
|
|
635
|
+
agentId: varchar("agent_id", { length: 36 }).notNull(),
|
|
636
|
+
entityId: varchar("entity_id", { length: 36 }).notNull(),
|
|
637
|
+
category: text("category").notNull(),
|
|
638
|
+
content: text("content").notNull(),
|
|
639
|
+
metadata: jsonb("metadata"),
|
|
640
|
+
embedding: real("embedding").array(),
|
|
641
|
+
confidence: real("confidence").default(1),
|
|
642
|
+
source: text("source"),
|
|
643
|
+
createdAt: timestamp("created_at").default(sql`now()`).notNull(),
|
|
644
|
+
updatedAt: timestamp("updated_at").default(sql`now()`).notNull(),
|
|
645
|
+
lastAccessedAt: timestamp("last_accessed_at"),
|
|
646
|
+
accessCount: integer("access_count").default(0)
|
|
647
|
+
}, (table) => ({
|
|
648
|
+
agentEntityIdx: index("long_term_memories_agent_entity_idx").on(table.agentId, table.entityId),
|
|
649
|
+
categoryIdx: index("long_term_memories_category_idx").on(table.category),
|
|
650
|
+
confidenceIdx: index("long_term_memories_confidence_idx").on(table.confidence),
|
|
651
|
+
createdAtIdx: index("long_term_memories_created_at_idx").on(table.createdAt)
|
|
652
|
+
}));
|
|
653
|
+
// src/schemas/memory-access-logs.ts
|
|
654
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
655
|
+
import { index as index2, pgTable as pgTable2, text as text2, timestamp as timestamp2, varchar as varchar2 } from "drizzle-orm/pg-core";
|
|
656
|
+
var memoryAccessLogs = pgTable2("memory_access_logs", {
|
|
657
|
+
id: varchar2("id", { length: 36 }).primaryKey(),
|
|
658
|
+
memoryId: varchar2("memory_id", { length: 36 }).notNull(),
|
|
659
|
+
memoryType: text2("memory_type").notNull(),
|
|
660
|
+
agentId: varchar2("agent_id", { length: 36 }).notNull(),
|
|
661
|
+
accessType: text2("access_type").notNull(),
|
|
662
|
+
accessedAt: timestamp2("accessed_at").default(sql2`now()`).notNull()
|
|
663
|
+
}, (table) => ({
|
|
664
|
+
memoryIdIdx: index2("memory_access_logs_memory_id_idx").on(table.memoryId),
|
|
665
|
+
agentIdIdx: index2("memory_access_logs_agent_id_idx").on(table.agentId),
|
|
666
|
+
accessedAtIdx: index2("memory_access_logs_accessed_at_idx").on(table.accessedAt)
|
|
667
|
+
}));
|
|
668
|
+
// src/schemas/session-summaries.ts
|
|
669
|
+
import { sql as sql3 } from "drizzle-orm";
|
|
670
|
+
import {
|
|
671
|
+
index as index3,
|
|
672
|
+
integer as integer2,
|
|
673
|
+
jsonb as jsonb2,
|
|
674
|
+
pgTable as pgTable3,
|
|
675
|
+
real as real2,
|
|
676
|
+
text as text3,
|
|
677
|
+
timestamp as timestamp3,
|
|
678
|
+
varchar as varchar3
|
|
679
|
+
} from "drizzle-orm/pg-core";
|
|
680
|
+
var sessionSummaries = pgTable3("session_summaries", {
|
|
681
|
+
id: varchar3("id", { length: 36 }).primaryKey(),
|
|
682
|
+
agentId: varchar3("agent_id", { length: 36 }).notNull(),
|
|
683
|
+
roomId: varchar3("room_id", { length: 36 }).notNull(),
|
|
684
|
+
entityId: varchar3("entity_id", { length: 36 }),
|
|
685
|
+
summary: text3("summary").notNull(),
|
|
686
|
+
messageCount: integer2("message_count").notNull(),
|
|
687
|
+
lastMessageOffset: integer2("last_message_offset").notNull().default(0),
|
|
688
|
+
startTime: timestamp3("start_time").notNull(),
|
|
689
|
+
endTime: timestamp3("end_time").notNull(),
|
|
690
|
+
topics: jsonb2("topics"),
|
|
691
|
+
metadata: jsonb2("metadata"),
|
|
692
|
+
embedding: real2("embedding").array(),
|
|
693
|
+
createdAt: timestamp3("created_at").default(sql3`now()`).notNull(),
|
|
694
|
+
updatedAt: timestamp3("updated_at").default(sql3`now()`).notNull()
|
|
695
|
+
}, (table) => ({
|
|
696
|
+
agentRoomIdx: index3("session_summaries_agent_room_idx").on(table.agentId, table.roomId),
|
|
697
|
+
entityIdx: index3("session_summaries_entity_idx").on(table.entityId),
|
|
698
|
+
startTimeIdx: index3("session_summaries_start_time_idx").on(table.startTime)
|
|
699
|
+
}));
|
|
700
|
+
// src/services/memory-service.ts
|
|
701
|
+
import {
|
|
702
|
+
logger as logger5,
|
|
703
|
+
Service
|
|
704
|
+
} from "@elizaos/core";
|
|
705
|
+
import { and, cosineDistance, desc, eq, gte, sql as sql4 } from "drizzle-orm";
|
|
706
|
+
class MemoryService extends Service {
|
|
707
|
+
static serviceType = "memory";
|
|
708
|
+
sessionMessageCounts;
|
|
709
|
+
memoryConfig;
|
|
710
|
+
lastExtractionCheckpoints;
|
|
711
|
+
capabilityDescription = "Memory management with short-term summarization and long-term persistent facts";
|
|
712
|
+
constructor(runtime) {
|
|
713
|
+
super(runtime);
|
|
714
|
+
this.sessionMessageCounts = new Map;
|
|
715
|
+
this.lastExtractionCheckpoints = new Map;
|
|
716
|
+
this.memoryConfig = {
|
|
717
|
+
shortTermSummarizationThreshold: 16,
|
|
718
|
+
shortTermRetainRecent: 6,
|
|
719
|
+
shortTermSummarizationInterval: 10,
|
|
720
|
+
longTermExtractionEnabled: true,
|
|
721
|
+
longTermVectorSearchEnabled: false,
|
|
722
|
+
longTermConfidenceThreshold: 0.85,
|
|
723
|
+
longTermExtractionThreshold: 30,
|
|
724
|
+
longTermExtractionInterval: 10,
|
|
725
|
+
summaryModelType: "TEXT_LARGE",
|
|
726
|
+
summaryMaxTokens: 2500,
|
|
727
|
+
summaryMaxNewMessages: 20
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
static async start(runtime) {
|
|
731
|
+
const service = new MemoryService(runtime);
|
|
732
|
+
await service.initialize(runtime);
|
|
733
|
+
return service;
|
|
734
|
+
}
|
|
735
|
+
async stop() {
|
|
736
|
+
logger5.info("MemoryService stopped");
|
|
737
|
+
}
|
|
738
|
+
async initialize(runtime) {
|
|
739
|
+
this.runtime = runtime;
|
|
740
|
+
const threshold = runtime.getSetting("MEMORY_SUMMARIZATION_THRESHOLD");
|
|
741
|
+
if (threshold) {
|
|
742
|
+
this.memoryConfig.shortTermSummarizationThreshold = parseInt(String(threshold), 10);
|
|
743
|
+
}
|
|
744
|
+
const retainRecent = runtime.getSetting("MEMORY_RETAIN_RECENT");
|
|
745
|
+
if (retainRecent) {
|
|
746
|
+
this.memoryConfig.shortTermRetainRecent = parseInt(String(retainRecent), 10);
|
|
747
|
+
}
|
|
748
|
+
const summarizationInterval = runtime.getSetting("MEMORY_SUMMARIZATION_INTERVAL");
|
|
749
|
+
if (summarizationInterval) {
|
|
750
|
+
this.memoryConfig.shortTermSummarizationInterval = parseInt(String(summarizationInterval), 10);
|
|
751
|
+
}
|
|
752
|
+
const maxNewMessages = runtime.getSetting("MEMORY_MAX_NEW_MESSAGES");
|
|
753
|
+
if (maxNewMessages) {
|
|
754
|
+
this.memoryConfig.summaryMaxNewMessages = parseInt(String(maxNewMessages), 10);
|
|
755
|
+
}
|
|
756
|
+
const longTermEnabled = runtime.getSetting("MEMORY_LONG_TERM_ENABLED");
|
|
757
|
+
if (longTermEnabled === "false" || longTermEnabled === false) {
|
|
758
|
+
this.memoryConfig.longTermExtractionEnabled = false;
|
|
759
|
+
} else if (longTermEnabled === "true" || longTermEnabled === true) {
|
|
760
|
+
this.memoryConfig.longTermExtractionEnabled = true;
|
|
761
|
+
}
|
|
762
|
+
const confidenceThreshold = runtime.getSetting("MEMORY_CONFIDENCE_THRESHOLD");
|
|
763
|
+
if (confidenceThreshold) {
|
|
764
|
+
this.memoryConfig.longTermConfidenceThreshold = parseFloat(String(confidenceThreshold));
|
|
765
|
+
}
|
|
766
|
+
const extractionThreshold = runtime.getSetting("MEMORY_EXTRACTION_THRESHOLD");
|
|
767
|
+
if (extractionThreshold) {
|
|
768
|
+
this.memoryConfig.longTermExtractionThreshold = parseInt(String(extractionThreshold), 10);
|
|
769
|
+
}
|
|
770
|
+
const extractionInterval = runtime.getSetting("MEMORY_EXTRACTION_INTERVAL");
|
|
771
|
+
if (extractionInterval) {
|
|
772
|
+
this.memoryConfig.longTermExtractionInterval = parseInt(String(extractionInterval), 10);
|
|
773
|
+
}
|
|
774
|
+
logger5.debug({
|
|
775
|
+
summarizationThreshold: this.memoryConfig.shortTermSummarizationThreshold,
|
|
776
|
+
summarizationInterval: this.memoryConfig.shortTermSummarizationInterval,
|
|
777
|
+
maxNewMessages: this.memoryConfig.summaryMaxNewMessages,
|
|
778
|
+
retainRecent: this.memoryConfig.shortTermRetainRecent,
|
|
779
|
+
longTermEnabled: this.memoryConfig.longTermExtractionEnabled,
|
|
780
|
+
extractionThreshold: this.memoryConfig.longTermExtractionThreshold,
|
|
781
|
+
extractionInterval: this.memoryConfig.longTermExtractionInterval,
|
|
782
|
+
confidenceThreshold: this.memoryConfig.longTermConfidenceThreshold
|
|
783
|
+
}, "MemoryService initialized");
|
|
784
|
+
}
|
|
785
|
+
getDb() {
|
|
786
|
+
const db = this.runtime.db;
|
|
787
|
+
if (!db) {
|
|
788
|
+
throw new Error("Database not available");
|
|
789
|
+
}
|
|
790
|
+
return db;
|
|
791
|
+
}
|
|
792
|
+
getConfig() {
|
|
793
|
+
return { ...this.memoryConfig };
|
|
794
|
+
}
|
|
795
|
+
updateConfig(updates) {
|
|
796
|
+
this.memoryConfig = { ...this.memoryConfig, ...updates };
|
|
797
|
+
}
|
|
798
|
+
incrementMessageCount(roomId) {
|
|
799
|
+
const current = this.sessionMessageCounts.get(roomId) || 0;
|
|
800
|
+
const newCount = current + 1;
|
|
801
|
+
this.sessionMessageCounts.set(roomId, newCount);
|
|
802
|
+
return newCount;
|
|
803
|
+
}
|
|
804
|
+
resetMessageCount(roomId) {
|
|
805
|
+
this.sessionMessageCounts.set(roomId, 0);
|
|
806
|
+
}
|
|
807
|
+
async shouldSummarize(roomId) {
|
|
808
|
+
const count = await this.runtime.countMemories(roomId, false, "messages");
|
|
809
|
+
return count >= this.memoryConfig.shortTermSummarizationThreshold;
|
|
810
|
+
}
|
|
811
|
+
getExtractionKey(entityId, roomId) {
|
|
812
|
+
return `memory:extraction:${entityId}:${roomId}`;
|
|
813
|
+
}
|
|
814
|
+
async getLastExtractionCheckpoint(entityId, roomId) {
|
|
815
|
+
const key = this.getExtractionKey(entityId, roomId);
|
|
816
|
+
const cached = this.lastExtractionCheckpoints.get(key);
|
|
817
|
+
if (cached !== undefined) {
|
|
818
|
+
return cached;
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
const checkpoint = await this.runtime.getCache(key);
|
|
822
|
+
const messageCount = checkpoint ?? 0;
|
|
823
|
+
this.lastExtractionCheckpoints.set(key, messageCount);
|
|
824
|
+
return messageCount;
|
|
825
|
+
} catch (error) {
|
|
826
|
+
logger5.warn({ error }, "Failed to get extraction checkpoint from cache");
|
|
827
|
+
return 0;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async setLastExtractionCheckpoint(entityId, roomId, messageCount) {
|
|
831
|
+
const key = this.getExtractionKey(entityId, roomId);
|
|
832
|
+
this.lastExtractionCheckpoints.set(key, messageCount);
|
|
833
|
+
try {
|
|
834
|
+
await this.runtime.setCache(key, messageCount);
|
|
835
|
+
logger5.debug(`Set extraction checkpoint for ${entityId} in room ${roomId} at count ${messageCount}`);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
logger5.error({ error }, "Failed to persist extraction checkpoint to cache");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async shouldRunExtraction(entityId, roomId, currentMessageCount) {
|
|
841
|
+
const threshold = this.memoryConfig.longTermExtractionThreshold;
|
|
842
|
+
const interval = this.memoryConfig.longTermExtractionInterval;
|
|
843
|
+
if (currentMessageCount < threshold) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
const lastCheckpoint = await this.getLastExtractionCheckpoint(entityId, roomId);
|
|
847
|
+
const currentCheckpoint = Math.floor(currentMessageCount / interval) * interval;
|
|
848
|
+
const shouldRun = currentMessageCount >= threshold && currentCheckpoint > lastCheckpoint;
|
|
849
|
+
logger5.debug({
|
|
850
|
+
entityId,
|
|
851
|
+
roomId,
|
|
852
|
+
currentMessageCount,
|
|
853
|
+
threshold,
|
|
854
|
+
interval,
|
|
855
|
+
lastCheckpoint,
|
|
856
|
+
currentCheckpoint,
|
|
857
|
+
shouldRun
|
|
858
|
+
}, "Extraction check");
|
|
859
|
+
return shouldRun;
|
|
860
|
+
}
|
|
861
|
+
async storeLongTermMemory(memory) {
|
|
862
|
+
const db = this.getDb();
|
|
863
|
+
const id = crypto.randomUUID();
|
|
864
|
+
const now = new Date;
|
|
865
|
+
const newMemory = {
|
|
866
|
+
id,
|
|
867
|
+
createdAt: now,
|
|
868
|
+
updatedAt: now,
|
|
869
|
+
accessCount: 0,
|
|
870
|
+
...memory
|
|
871
|
+
};
|
|
872
|
+
try {
|
|
873
|
+
await db.insert(longTermMemories).values({
|
|
874
|
+
id: newMemory.id,
|
|
875
|
+
agentId: newMemory.agentId,
|
|
876
|
+
entityId: newMemory.entityId,
|
|
877
|
+
category: newMemory.category,
|
|
878
|
+
content: newMemory.content,
|
|
879
|
+
metadata: newMemory.metadata || {},
|
|
880
|
+
embedding: newMemory.embedding,
|
|
881
|
+
confidence: newMemory.confidence,
|
|
882
|
+
source: newMemory.source,
|
|
883
|
+
accessCount: newMemory.accessCount,
|
|
884
|
+
createdAt: now,
|
|
885
|
+
updatedAt: now,
|
|
886
|
+
lastAccessedAt: newMemory.lastAccessedAt
|
|
887
|
+
});
|
|
888
|
+
} catch (error) {
|
|
889
|
+
logger5.error({ error }, "Failed to store long-term memory");
|
|
890
|
+
throw error;
|
|
891
|
+
}
|
|
892
|
+
logger5.info(`Stored long-term memory: ${newMemory.category} for entity ${newMemory.entityId}`);
|
|
893
|
+
return newMemory;
|
|
894
|
+
}
|
|
895
|
+
async getLongTermMemories(entityId, category, limit = 10) {
|
|
896
|
+
const db = this.getDb();
|
|
897
|
+
const conditions = [
|
|
898
|
+
eq(longTermMemories.agentId, this.runtime.agentId),
|
|
899
|
+
eq(longTermMemories.entityId, entityId)
|
|
900
|
+
];
|
|
901
|
+
if (category) {
|
|
902
|
+
conditions.push(eq(longTermMemories.category, category));
|
|
903
|
+
}
|
|
904
|
+
const results = await db.select().from(longTermMemories).where(and(...conditions)).orderBy(desc(longTermMemories.confidence), desc(longTermMemories.updatedAt)).limit(limit);
|
|
905
|
+
return results.map((row) => ({
|
|
906
|
+
id: row.id,
|
|
907
|
+
agentId: row.agentId,
|
|
908
|
+
entityId: row.entityId,
|
|
909
|
+
category: row.category,
|
|
910
|
+
content: row.content,
|
|
911
|
+
metadata: row.metadata,
|
|
912
|
+
embedding: row.embedding,
|
|
913
|
+
confidence: row.confidence,
|
|
914
|
+
source: row.source,
|
|
915
|
+
createdAt: row.createdAt,
|
|
916
|
+
updatedAt: row.updatedAt,
|
|
917
|
+
lastAccessedAt: row.lastAccessedAt,
|
|
918
|
+
accessCount: row.accessCount
|
|
919
|
+
}));
|
|
920
|
+
}
|
|
921
|
+
async updateLongTermMemory(id, entityId, updates) {
|
|
922
|
+
const db = this.getDb();
|
|
923
|
+
const updateData = {
|
|
924
|
+
updatedAt: new Date
|
|
925
|
+
};
|
|
926
|
+
if (updates.content !== undefined)
|
|
927
|
+
updateData.content = updates.content;
|
|
928
|
+
if (updates.metadata !== undefined)
|
|
929
|
+
updateData.metadata = updates.metadata;
|
|
930
|
+
if (updates.confidence !== undefined)
|
|
931
|
+
updateData.confidence = updates.confidence;
|
|
932
|
+
if (updates.embedding !== undefined)
|
|
933
|
+
updateData.embedding = updates.embedding;
|
|
934
|
+
if (updates.lastAccessedAt !== undefined)
|
|
935
|
+
updateData.lastAccessedAt = updates.lastAccessedAt;
|
|
936
|
+
if (updates.accessCount !== undefined)
|
|
937
|
+
updateData.accessCount = updates.accessCount;
|
|
938
|
+
await db.update(longTermMemories).set(updateData).where(and(eq(longTermMemories.id, id), eq(longTermMemories.agentId, this.runtime.agentId), eq(longTermMemories.entityId, entityId)));
|
|
939
|
+
logger5.info(`Updated long-term memory: ${id} for entity ${entityId}`);
|
|
940
|
+
}
|
|
941
|
+
async deleteLongTermMemory(id, entityId) {
|
|
942
|
+
const db = this.getDb();
|
|
943
|
+
await db.delete(longTermMemories).where(and(eq(longTermMemories.id, id), eq(longTermMemories.agentId, this.runtime.agentId), eq(longTermMemories.entityId, entityId)));
|
|
944
|
+
logger5.info(`Deleted long-term memory: ${id} for entity ${entityId}`);
|
|
945
|
+
}
|
|
946
|
+
async getCurrentSessionSummary(roomId) {
|
|
947
|
+
const db = this.getDb();
|
|
948
|
+
const results = await db.select().from(sessionSummaries).where(and(eq(sessionSummaries.agentId, this.runtime.agentId), eq(sessionSummaries.roomId, roomId))).orderBy(desc(sessionSummaries.updatedAt)).limit(1);
|
|
949
|
+
if (results.length === 0) {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
const row = results[0];
|
|
953
|
+
return {
|
|
954
|
+
id: row.id,
|
|
955
|
+
agentId: row.agentId,
|
|
956
|
+
roomId: row.roomId,
|
|
957
|
+
entityId: row.entityId,
|
|
958
|
+
summary: row.summary,
|
|
959
|
+
messageCount: row.messageCount,
|
|
960
|
+
lastMessageOffset: row.lastMessageOffset,
|
|
961
|
+
startTime: row.startTime,
|
|
962
|
+
endTime: row.endTime,
|
|
963
|
+
topics: row.topics || [],
|
|
964
|
+
metadata: row.metadata,
|
|
965
|
+
embedding: row.embedding,
|
|
966
|
+
createdAt: row.createdAt,
|
|
967
|
+
updatedAt: row.updatedAt
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
async storeSessionSummary(summary) {
|
|
971
|
+
const db = this.getDb();
|
|
972
|
+
const id = crypto.randomUUID();
|
|
973
|
+
const now = new Date;
|
|
974
|
+
const newSummary = {
|
|
975
|
+
id,
|
|
976
|
+
createdAt: now,
|
|
977
|
+
updatedAt: now,
|
|
978
|
+
...summary
|
|
979
|
+
};
|
|
980
|
+
await db.insert(sessionSummaries).values({
|
|
981
|
+
id: newSummary.id,
|
|
982
|
+
agentId: newSummary.agentId,
|
|
983
|
+
roomId: newSummary.roomId,
|
|
984
|
+
entityId: newSummary.entityId || null,
|
|
985
|
+
summary: newSummary.summary,
|
|
986
|
+
messageCount: newSummary.messageCount,
|
|
987
|
+
lastMessageOffset: newSummary.lastMessageOffset,
|
|
988
|
+
startTime: newSummary.startTime,
|
|
989
|
+
endTime: newSummary.endTime,
|
|
990
|
+
topics: newSummary.topics || [],
|
|
991
|
+
metadata: newSummary.metadata || {},
|
|
992
|
+
embedding: newSummary.embedding,
|
|
993
|
+
createdAt: now,
|
|
994
|
+
updatedAt: now
|
|
995
|
+
});
|
|
996
|
+
logger5.info(`Stored session summary for room ${newSummary.roomId}`);
|
|
997
|
+
return newSummary;
|
|
998
|
+
}
|
|
999
|
+
async updateSessionSummary(id, roomId, updates) {
|
|
1000
|
+
const db = this.getDb();
|
|
1001
|
+
const updateData = {
|
|
1002
|
+
updatedAt: new Date
|
|
1003
|
+
};
|
|
1004
|
+
if (updates.summary !== undefined)
|
|
1005
|
+
updateData.summary = updates.summary;
|
|
1006
|
+
if (updates.messageCount !== undefined)
|
|
1007
|
+
updateData.messageCount = updates.messageCount;
|
|
1008
|
+
if (updates.lastMessageOffset !== undefined)
|
|
1009
|
+
updateData.lastMessageOffset = updates.lastMessageOffset;
|
|
1010
|
+
if (updates.endTime !== undefined)
|
|
1011
|
+
updateData.endTime = updates.endTime;
|
|
1012
|
+
if (updates.topics !== undefined)
|
|
1013
|
+
updateData.topics = updates.topics;
|
|
1014
|
+
if (updates.metadata !== undefined)
|
|
1015
|
+
updateData.metadata = updates.metadata;
|
|
1016
|
+
if (updates.embedding !== undefined)
|
|
1017
|
+
updateData.embedding = updates.embedding;
|
|
1018
|
+
await db.update(sessionSummaries).set(updateData).where(and(eq(sessionSummaries.id, id), eq(sessionSummaries.agentId, this.runtime.agentId), eq(sessionSummaries.roomId, roomId)));
|
|
1019
|
+
logger5.info(`Updated session summary: ${id} for room ${roomId}`);
|
|
1020
|
+
}
|
|
1021
|
+
async getSessionSummaries(roomId, limit = 5) {
|
|
1022
|
+
const db = this.getDb();
|
|
1023
|
+
const results = await db.select().from(sessionSummaries).where(and(eq(sessionSummaries.agentId, this.runtime.agentId), eq(sessionSummaries.roomId, roomId))).orderBy(desc(sessionSummaries.updatedAt)).limit(limit);
|
|
1024
|
+
return results.map((row) => ({
|
|
1025
|
+
id: row.id,
|
|
1026
|
+
agentId: row.agentId,
|
|
1027
|
+
roomId: row.roomId,
|
|
1028
|
+
entityId: row.entityId,
|
|
1029
|
+
summary: row.summary,
|
|
1030
|
+
messageCount: row.messageCount,
|
|
1031
|
+
lastMessageOffset: row.lastMessageOffset,
|
|
1032
|
+
startTime: row.startTime,
|
|
1033
|
+
endTime: row.endTime,
|
|
1034
|
+
topics: row.topics || [],
|
|
1035
|
+
metadata: row.metadata,
|
|
1036
|
+
embedding: row.embedding,
|
|
1037
|
+
createdAt: row.createdAt,
|
|
1038
|
+
updatedAt: row.updatedAt
|
|
1039
|
+
}));
|
|
1040
|
+
}
|
|
1041
|
+
async searchLongTermMemories(entityId, queryEmbedding, limit = 5, matchThreshold = 0.7) {
|
|
1042
|
+
if (!this.memoryConfig.longTermVectorSearchEnabled) {
|
|
1043
|
+
logger5.warn("Vector search is not enabled, falling back to recent memories");
|
|
1044
|
+
return this.getLongTermMemories(entityId, undefined, limit);
|
|
1045
|
+
}
|
|
1046
|
+
const db = this.getDb();
|
|
1091
1047
|
try {
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
sessionSummariesWithTopics: ""
|
|
1102
|
-
},
|
|
1103
|
-
text: ""
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
const currentSummary = await memoryService.getCurrentSessionSummary(roomId);
|
|
1107
|
-
if (!currentSummary) {
|
|
1108
|
-
return {
|
|
1109
|
-
data: {
|
|
1110
|
-
summary: null
|
|
1111
|
-
},
|
|
1112
|
-
values: {
|
|
1113
|
-
sessionSummaries: "",
|
|
1114
|
-
sessionSummariesWithTopics: ""
|
|
1115
|
-
},
|
|
1116
|
-
text: ""
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
const messageRange = `${currentSummary.messageCount} messages`;
|
|
1120
|
-
const timeRange = new Date(currentSummary.startTime).toLocaleDateString();
|
|
1121
|
-
let summaryOnly = `**Previous Conversation** (${messageRange}, ${timeRange})
|
|
1122
|
-
`;
|
|
1123
|
-
summaryOnly += currentSummary.summary;
|
|
1124
|
-
let summaryWithTopics = summaryOnly;
|
|
1125
|
-
if (currentSummary.topics && currentSummary.topics.length > 0) {
|
|
1126
|
-
summaryWithTopics += `
|
|
1127
|
-
*Topics: ${currentSummary.topics.join(", ")}*`;
|
|
1048
|
+
const cleanVector = queryEmbedding.map((n) => Number.isFinite(n) ? Number(n.toFixed(6)) : 0);
|
|
1049
|
+
const similarity = sql4`1 - (${cosineDistance(longTermMemories.embedding, cleanVector)})`;
|
|
1050
|
+
const conditions = [
|
|
1051
|
+
eq(longTermMemories.agentId, this.runtime.agentId),
|
|
1052
|
+
eq(longTermMemories.entityId, entityId),
|
|
1053
|
+
sql4`${longTermMemories.embedding} IS NOT NULL`
|
|
1054
|
+
];
|
|
1055
|
+
if (matchThreshold > 0) {
|
|
1056
|
+
conditions.push(gte(similarity, matchThreshold));
|
|
1128
1057
|
}
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1058
|
+
const results = await db.select({
|
|
1059
|
+
memory: longTermMemories,
|
|
1060
|
+
similarity
|
|
1061
|
+
}).from(longTermMemories).where(and(...conditions)).orderBy(desc(similarity)).limit(limit);
|
|
1062
|
+
return results.map((row) => ({
|
|
1063
|
+
id: row.memory.id,
|
|
1064
|
+
agentId: row.memory.agentId,
|
|
1065
|
+
entityId: row.memory.entityId,
|
|
1066
|
+
category: row.memory.category,
|
|
1067
|
+
content: row.memory.content,
|
|
1068
|
+
metadata: row.memory.metadata ?? {},
|
|
1069
|
+
embedding: row.memory.embedding ?? [],
|
|
1070
|
+
confidence: row.memory.confidence ?? 1,
|
|
1071
|
+
source: row.memory.source ?? "",
|
|
1072
|
+
createdAt: row.memory.createdAt,
|
|
1073
|
+
updatedAt: row.memory.updatedAt,
|
|
1074
|
+
lastAccessedAt: row.memory.lastAccessedAt ?? row.memory.updatedAt,
|
|
1075
|
+
accessCount: row.memory.accessCount ?? 0,
|
|
1076
|
+
similarity: row.similarity
|
|
1077
|
+
}));
|
|
1141
1078
|
} catch (error) {
|
|
1142
|
-
logger5.
|
|
1143
|
-
return
|
|
1144
|
-
data: {
|
|
1145
|
-
summary: null
|
|
1146
|
-
},
|
|
1147
|
-
values: {
|
|
1148
|
-
sessionSummaries: "",
|
|
1149
|
-
sessionSummariesWithTopics: ""
|
|
1150
|
-
},
|
|
1151
|
-
text: ""
|
|
1152
|
-
};
|
|
1079
|
+
logger5.warn({ error }, "Vector search failed, falling back to recent memories");
|
|
1080
|
+
return this.getLongTermMemories(entityId, undefined, limit);
|
|
1153
1081
|
}
|
|
1154
1082
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
logger as logger6
|
|
1165
|
-
} from "@elizaos/core";
|
|
1166
|
-
var recentMessagesProvider = {
|
|
1167
|
-
name: "RECENT_MESSAGES",
|
|
1168
|
-
description: "Provides recent conversation messages with detailed context",
|
|
1169
|
-
position: 94,
|
|
1170
|
-
get: async (runtime, message, _state) => {
|
|
1171
|
-
try {
|
|
1172
|
-
const memoryService = runtime.getService("memory");
|
|
1173
|
-
const { roomId } = message;
|
|
1174
|
-
const config = memoryService?.getConfig() || {
|
|
1175
|
-
shortTermSummarizationThreshold: 16,
|
|
1176
|
-
shortTermRetainRecent: 6
|
|
1177
|
-
};
|
|
1178
|
-
const conversationLength = runtime.getConversationLength();
|
|
1179
|
-
let messagesToFetch = config.shortTermRetainRecent;
|
|
1180
|
-
let startOffset = 0;
|
|
1181
|
-
let hasSummary = false;
|
|
1182
|
-
if (memoryService) {
|
|
1183
|
-
const currentSummary = await memoryService.getCurrentSessionSummary(roomId);
|
|
1184
|
-
if (currentSummary) {
|
|
1185
|
-
hasSummary = true;
|
|
1186
|
-
startOffset = currentSummary.lastMessageOffset || 0;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
if (!hasSummary) {
|
|
1190
|
-
const allMessages = await runtime.getMemories({
|
|
1191
|
-
tableName: "messages",
|
|
1192
|
-
roomId,
|
|
1193
|
-
count: conversationLength,
|
|
1194
|
-
unique: false
|
|
1195
|
-
});
|
|
1196
|
-
const dialogueMessageCount = allMessages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result") && (msg.metadata?.type === "agent_response_message" || msg.metadata?.type === "user_message")).length;
|
|
1197
|
-
if (dialogueMessageCount < config.shortTermSummarizationThreshold) {
|
|
1198
|
-
messagesToFetch = conversationLength;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
const [entitiesData, room, recentMessagesData] = await Promise.all([
|
|
1202
|
-
getEntityDetails({ runtime, roomId }),
|
|
1203
|
-
runtime.getRoom(roomId),
|
|
1204
|
-
runtime.getMemories({
|
|
1205
|
-
tableName: "messages",
|
|
1206
|
-
roomId,
|
|
1207
|
-
count: messagesToFetch,
|
|
1208
|
-
unique: false,
|
|
1209
|
-
start: startOffset
|
|
1210
|
-
})
|
|
1211
|
-
]);
|
|
1212
|
-
const isPostFormat = room?.type ? room.type === ChannelType.FEED || room.type === ChannelType.THREAD : false;
|
|
1213
|
-
const dialogueMessages = recentMessagesData.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result") && (msg.metadata?.type === "agent_response_message" || msg.metadata?.type === "user_message"));
|
|
1214
|
-
let recentMessagesText = "";
|
|
1215
|
-
if (dialogueMessages.length > 0) {
|
|
1216
|
-
if (isPostFormat) {
|
|
1217
|
-
recentMessagesText = formatPosts({
|
|
1218
|
-
messages: dialogueMessages,
|
|
1219
|
-
entities: entitiesData,
|
|
1220
|
-
conversationHeader: false
|
|
1221
|
-
});
|
|
1222
|
-
} else {
|
|
1223
|
-
recentMessagesText = formatMessages({
|
|
1224
|
-
messages: dialogueMessages,
|
|
1225
|
-
entities: entitiesData
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
if (recentMessagesText) {
|
|
1229
|
-
recentMessagesText = addHeader3("# Recent Messages", recentMessagesText);
|
|
1230
|
-
}
|
|
1083
|
+
async getFormattedLongTermMemories(entityId) {
|
|
1084
|
+
const memories = await this.getLongTermMemories(entityId, undefined, 20);
|
|
1085
|
+
if (memories.length === 0) {
|
|
1086
|
+
return "";
|
|
1087
|
+
}
|
|
1088
|
+
const grouped = new Map;
|
|
1089
|
+
for (const memory of memories) {
|
|
1090
|
+
if (!grouped.has(memory.category)) {
|
|
1091
|
+
grouped.set(memory.category, []);
|
|
1231
1092
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const thought = includeThoughts && msg.content.thought ? `
|
|
1239
|
-
[Internal thought: ${msg.content.thought}]` : "";
|
|
1240
|
-
return `[${timestamp4}] ${entityName}: ${text5}${thought}`;
|
|
1241
|
-
}).join(`
|
|
1093
|
+
grouped.get(memory.category)?.push(memory);
|
|
1094
|
+
}
|
|
1095
|
+
const sections = [];
|
|
1096
|
+
for (const [category, categoryMemories] of grouped.entries()) {
|
|
1097
|
+
const categoryName = category.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1098
|
+
const items = categoryMemories.map((m) => `- ${m.content}`).join(`
|
|
1242
1099
|
`);
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const senderName = entitiesData.find((entity) => entity.id === message.entityId)?.names[0] || metaData?.entityName || "Unknown User";
|
|
1248
|
-
const receivedMessageContent = message.content.text;
|
|
1249
|
-
const hasReceivedMessage = !!receivedMessageContent?.trim();
|
|
1250
|
-
const receivedMessageHeader = hasReceivedMessage ? addHeader3("# Received Message", `${senderName}: ${receivedMessageContent}`) : "";
|
|
1251
|
-
const focusHeader = hasReceivedMessage ? addHeader3("# Focus your response", `You are replying to the above message from **${senderName}**. Keep your answer relevant to that message.`) : "";
|
|
1252
|
-
const text4 = [recentMessagesText, receivedMessageHeader, focusHeader].filter(Boolean).join(`
|
|
1100
|
+
sections.push(`**${categoryName}**:
|
|
1101
|
+
${items}`);
|
|
1102
|
+
}
|
|
1103
|
+
return sections.join(`
|
|
1253
1104
|
|
|
1254
1105
|
`);
|
|
1255
|
-
return {
|
|
1256
|
-
data: {
|
|
1257
|
-
messages: dialogueMessages
|
|
1258
|
-
},
|
|
1259
|
-
values: {
|
|
1260
|
-
recentMessages: recentMessagesText,
|
|
1261
|
-
conversationLog,
|
|
1262
|
-
conversationLogWithAgentThoughts,
|
|
1263
|
-
...receivedMessageHeader && { receivedMessageHeader },
|
|
1264
|
-
...focusHeader && { focusHeader }
|
|
1265
|
-
},
|
|
1266
|
-
text: text4
|
|
1267
|
-
};
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
logger6.error({ error }, "Error in recentMessagesProvider:");
|
|
1270
|
-
return {
|
|
1271
|
-
data: {
|
|
1272
|
-
messages: []
|
|
1273
|
-
},
|
|
1274
|
-
values: {
|
|
1275
|
-
recentMessages: "",
|
|
1276
|
-
conversationLog: "",
|
|
1277
|
-
conversationLogWithAgentThoughts: "",
|
|
1278
|
-
receivedMessageHeader: "",
|
|
1279
|
-
focusHeader: ""
|
|
1280
|
-
},
|
|
1281
|
-
text: ""
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
1106
|
}
|
|
1285
|
-
}
|
|
1107
|
+
}
|
|
1286
1108
|
|
|
1287
1109
|
// src/index.ts
|
|
1288
1110
|
var memoryPlugin = {
|
|
1289
1111
|
name: "memory",
|
|
1290
|
-
description: "
|
|
1112
|
+
description: "Memory management with conversation summarization and long-term persistent memory",
|
|
1291
1113
|
services: [MemoryService],
|
|
1292
1114
|
evaluators: [summarizationEvaluator, longTermExtractionEvaluator],
|
|
1293
|
-
providers: [
|
|
1294
|
-
longTermMemoryProvider,
|
|
1295
|
-
contextSummaryProvider,
|
|
1296
|
-
recentMessagesProvider
|
|
1297
|
-
],
|
|
1115
|
+
providers: [longTermMemoryProvider, contextSummaryProvider],
|
|
1298
1116
|
schema: exports_schemas
|
|
1299
1117
|
};
|
|
1300
1118
|
var src_default = memoryPlugin;
|
|
1301
1119
|
export {
|
|
1120
|
+
summarizationEvaluator,
|
|
1302
1121
|
sessionSummaries,
|
|
1303
|
-
recentMessagesProvider,
|
|
1304
1122
|
memoryPlugin,
|
|
1305
1123
|
memoryAccessLogs,
|
|
1306
1124
|
longTermMemoryProvider,
|
|
1307
1125
|
longTermMemories,
|
|
1126
|
+
longTermExtractionEvaluator,
|
|
1308
1127
|
src_default as default,
|
|
1309
1128
|
contextSummaryProvider,
|
|
1310
1129
|
MemoryService,
|
|
1311
1130
|
LongTermMemoryCategory
|
|
1312
1131
|
};
|
|
1313
1132
|
|
|
1314
|
-
//# debugId=
|
|
1133
|
+
//# debugId=FC048A8C8739D95E64756E2164756E21
|