@elizaos/plugin-memory 1.1.2 → 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 -38
- package/dist/index.d.ts.map +1 -0
- package/dist/{node/index.node.js → index.js} +931 -980
- 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 -205
- package/dist/browser/index.browser.js.map +0 -19
- package/dist/browser/index.d.ts +0 -2
- package/dist/cjs/index.d.ts +0 -2
- package/dist/cjs/index.node.cjs +0 -1168
- package/dist/cjs/index.node.js.map +0 -19
- 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 -19
|
@@ -9,536 +9,15 @@ var __export = (target, all) => {
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/
|
|
12
|
+
// src/evaluators/long-term-extraction.ts
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
logger
|
|
14
|
+
composePromptFromState,
|
|
15
|
+
logger,
|
|
16
|
+
ModelType
|
|
16
17
|
} from "@elizaos/core";
|
|
17
|
-
import { eq, and, desc, sql as sql4, cosineDistance, gte } from "drizzle-orm";
|
|
18
18
|
|
|
19
|
-
// src/
|
|
20
|
-
var
|
|
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
|
|
526
|
-
import {
|
|
527
|
-
logger as logger2,
|
|
528
|
-
ModelType,
|
|
529
|
-
composePromptFromState
|
|
530
|
-
} from "@elizaos/core";
|
|
531
|
-
async function getDialogueMessageCount(runtime, roomId) {
|
|
532
|
-
const messages = await runtime.getMemories({
|
|
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
|
-
}
|
|
541
|
-
var initialSummarizationTemplate = `# Task: Summarize Conversation
|
|
19
|
+
// src/generated/prompts/typescript/prompts.ts
|
|
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.
|
|
544
23
|
|
|
@@ -568,232 +47,29 @@ Respond in this XML format:
|
|
|
568
47
|
<point>Second key point</point>
|
|
569
48
|
</keyPoints>
|
|
570
49
|
</summary>`;
|
|
571
|
-
var
|
|
50
|
+
var longTermExtractionTemplate = `# Task: Extract Long-Term Memory (Strict Criteria)
|
|
572
51
|
|
|
573
|
-
You are
|
|
52
|
+
You are analyzing a conversation to extract ONLY the most critical, persistent information about the user using cognitive science memory categories.
|
|
574
53
|
|
|
575
|
-
#
|
|
576
|
-
{{
|
|
54
|
+
# Recent Messages
|
|
55
|
+
{{recentMessages}}
|
|
577
56
|
|
|
578
|
-
#
|
|
579
|
-
{{
|
|
57
|
+
# Current Long-Term Memories
|
|
58
|
+
{{existingMemories}}
|
|
580
59
|
|
|
581
|
-
#
|
|
582
|
-
{{newMessages}}
|
|
60
|
+
# Memory Categories (Based on Cognitive Science)
|
|
583
61
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
5. **CRITICAL**: Keep the ENTIRE updated summary under 2500 tokens
|
|
62
|
+
## 1. EPISODIC Memory
|
|
63
|
+
Personal experiences and specific events with temporal/spatial context.
|
|
64
|
+
**Examples:**
|
|
65
|
+
- "User completed migration project from MongoDB to PostgreSQL in Q2 2024"
|
|
66
|
+
- "User encountered authentication bug in production on March 15th"
|
|
67
|
+
- "User had a negative experience with Docker networking in previous job"
|
|
591
68
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
};
|
|
757
|
-
|
|
758
|
-
// src/evaluators/long-term-extraction.ts
|
|
759
|
-
import {
|
|
760
|
-
logger as logger3,
|
|
761
|
-
ModelType as ModelType2,
|
|
762
|
-
composePromptFromState as composePromptFromState2
|
|
763
|
-
} from "@elizaos/core";
|
|
764
|
-
|
|
765
|
-
// src/types/index.ts
|
|
766
|
-
var LongTermMemoryCategory;
|
|
767
|
-
((LongTermMemoryCategory2) => {
|
|
768
|
-
LongTermMemoryCategory2["EPISODIC"] = "episodic";
|
|
769
|
-
LongTermMemoryCategory2["SEMANTIC"] = "semantic";
|
|
770
|
-
LongTermMemoryCategory2["PROCEDURAL"] = "procedural";
|
|
771
|
-
})(LongTermMemoryCategory ||= {});
|
|
772
|
-
|
|
773
|
-
// src/evaluators/long-term-extraction.ts
|
|
774
|
-
var extractionTemplate = `# Task: Extract Long-Term Memory (Strict Criteria)
|
|
775
|
-
|
|
776
|
-
You are analyzing a conversation to extract ONLY the most critical, persistent information about the user using cognitive science memory categories.
|
|
777
|
-
|
|
778
|
-
# Recent Messages
|
|
779
|
-
{{recentMessages}}
|
|
780
|
-
|
|
781
|
-
# Current Long-Term Memories
|
|
782
|
-
{{existingMemories}}
|
|
783
|
-
|
|
784
|
-
# Memory Categories (Based on Cognitive Science)
|
|
785
|
-
|
|
786
|
-
## 1. EPISODIC Memory
|
|
787
|
-
Personal experiences and specific events with temporal/spatial context.
|
|
788
|
-
**Examples:**
|
|
789
|
-
- "User completed migration project from MongoDB to PostgreSQL in Q2 2024"
|
|
790
|
-
- "User encountered authentication bug in production on March 15th"
|
|
791
|
-
- "User had a negative experience with Docker networking in previous job"
|
|
792
|
-
|
|
793
|
-
**Requirements:**
|
|
794
|
-
- Must include WHO did WHAT, WHEN/WHERE
|
|
795
|
-
- Must be a specific, concrete event (not a pattern)
|
|
796
|
-
- Must have significant impact or relevance to future work
|
|
69
|
+
**Requirements:**
|
|
70
|
+
- Must include WHO did WHAT, WHEN/WHERE
|
|
71
|
+
- Must be a specific, concrete event (not a pattern)
|
|
72
|
+
- Must have significant impact or relevance to future work
|
|
797
73
|
|
|
798
74
|
## 2. SEMANTIC Memory
|
|
799
75
|
General facts, concepts, knowledge, and established truths about the user.
|
|
@@ -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,271 +231,903 @@ 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
|
}
|
|
922
|
-
return extractions;
|
|
923
|
-
}
|
|
924
|
-
var longTermExtractionEvaluator = {
|
|
925
|
-
name: "LONG_TERM_MEMORY_EXTRACTION",
|
|
926
|
-
description: "Extracts long-term facts about users from conversations",
|
|
927
|
-
similes: ["MEMORY_EXTRACTION", "FACT_LEARNING", "USER_PROFILING"],
|
|
928
|
-
alwaysRun: true,
|
|
929
|
-
validate: async (runtime, message) => {
|
|
930
|
-
if (message.entityId === runtime.agentId) {
|
|
931
|
-
return false;
|
|
932
|
-
}
|
|
933
|
-
if (!message.content?.text) {
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
|
-
const memoryService = runtime.getService("memory");
|
|
937
|
-
if (!memoryService) {
|
|
938
|
-
return false;
|
|
939
|
-
}
|
|
940
|
-
const config = memoryService.getConfig();
|
|
941
|
-
if (!config.longTermExtractionEnabled) {
|
|
942
|
-
|
|
943
|
-
return false;
|
|
944
|
-
}
|
|
945
|
-
const currentMessageCount = await runtime.countMemories(message.roomId, false, "messages");
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
});
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
241
|
+
return extractions;
|
|
242
|
+
}
|
|
243
|
+
var longTermExtractionEvaluator = {
|
|
244
|
+
name: "LONG_TERM_MEMORY_EXTRACTION",
|
|
245
|
+
description: "Extracts long-term facts about users from conversations",
|
|
246
|
+
similes: ["MEMORY_EXTRACTION", "FACT_LEARNING", "USER_PROFILING"],
|
|
247
|
+
alwaysRun: true,
|
|
248
|
+
validate: async (runtime, message) => {
|
|
249
|
+
if (message.entityId === runtime.agentId) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
if (!message.content?.text) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const memoryService = runtime.getService("memory");
|
|
256
|
+
if (!memoryService) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
const config = memoryService.getConfig();
|
|
260
|
+
if (!config.longTermExtractionEnabled) {
|
|
261
|
+
logger.debug("Long-term memory extraction is disabled");
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const currentMessageCount = await runtime.countMemories(message.roomId, false, "messages");
|
|
265
|
+
return memoryService.shouldRunExtraction(message.entityId, message.roomId, currentMessageCount);
|
|
266
|
+
},
|
|
267
|
+
handler: async (runtime, message) => {
|
|
268
|
+
const memoryService = runtime.getService("memory");
|
|
269
|
+
if (!memoryService) {
|
|
270
|
+
logger.error("MemoryService not found");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const config = memoryService.getConfig();
|
|
274
|
+
const { entityId, roomId } = message;
|
|
275
|
+
try {
|
|
276
|
+
logger.info(`Extracting long-term memories for entity ${entityId}`);
|
|
277
|
+
const recentMessages = await runtime.getMemories({
|
|
278
|
+
tableName: "messages",
|
|
279
|
+
roomId,
|
|
280
|
+
count: 20,
|
|
281
|
+
unique: false
|
|
282
|
+
});
|
|
283
|
+
const formattedMessages = recentMessages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0)).map((msg) => {
|
|
284
|
+
const sender = msg.entityId === runtime.agentId ? runtime.character.name : "User";
|
|
285
|
+
return `${sender}: ${msg.content.text || "[non-text message]"}`;
|
|
286
|
+
}).join(`
|
|
287
|
+
`);
|
|
288
|
+
const existingMemories = await memoryService.getLongTermMemories(entityId, undefined, 30);
|
|
289
|
+
const formattedExisting = existingMemories.length > 0 ? existingMemories.map((m) => `[${m.category}] ${m.content} (confidence: ${m.confidence})`).join(`
|
|
290
|
+
`) : "None yet";
|
|
291
|
+
const state = await runtime.composeState(message);
|
|
292
|
+
const prompt = composePromptFromState({
|
|
293
|
+
state: {
|
|
294
|
+
...state,
|
|
295
|
+
recentMessages: formattedMessages,
|
|
296
|
+
existingMemories: formattedExisting
|
|
297
|
+
},
|
|
298
|
+
template: extractionTemplate
|
|
299
|
+
});
|
|
300
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
|
|
301
|
+
const extractions = parseMemoryExtractionXML(response);
|
|
302
|
+
logger.info(`Extracted ${extractions.length} long-term memories`);
|
|
303
|
+
for (const extraction of extractions) {
|
|
304
|
+
if (extraction.confidence >= Math.max(config.longTermConfidenceThreshold, 0.85)) {
|
|
305
|
+
await memoryService.storeLongTermMemory({
|
|
306
|
+
agentId: runtime.agentId,
|
|
307
|
+
entityId,
|
|
308
|
+
category: extraction.category,
|
|
309
|
+
content: extraction.content,
|
|
310
|
+
confidence: extraction.confidence,
|
|
311
|
+
source: "conversation",
|
|
312
|
+
metadata: {
|
|
313
|
+
roomId,
|
|
314
|
+
extractedAt: new Date().toISOString()
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
logger.info(`Stored long-term memory: [${extraction.category}] ${extraction.content.substring(0, 50)}...`);
|
|
318
|
+
} else {
|
|
319
|
+
logger.debug(`Skipped low-confidence memory: ${extraction.content} (confidence: ${extraction.confidence})`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const currentMessageCount = await runtime.countMemories(roomId, false, "messages");
|
|
323
|
+
await memoryService.setLastExtractionCheckpoint(entityId, roomId, currentMessageCount);
|
|
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
|
+
};
|
|
539
|
+
} catch (error) {
|
|
540
|
+
logger3.error({ error }, "Error in contextSummaryProvider:");
|
|
541
|
+
return {
|
|
542
|
+
data: {},
|
|
543
|
+
values: { sessionSummaries: "", sessionSummariesWithTopics: "" },
|
|
544
|
+
text: ""
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
// src/providers/long-term-memory.ts
|
|
550
|
+
import {
|
|
551
|
+
addHeader as addHeader2,
|
|
552
|
+
logger as logger4
|
|
553
|
+
} from "@elizaos/core";
|
|
554
|
+
var longTermMemoryProvider = {
|
|
555
|
+
name: "LONG_TERM_MEMORY",
|
|
556
|
+
description: "Persistent facts and preferences about the user",
|
|
557
|
+
position: 50,
|
|
558
|
+
get: async (runtime, message, _state) => {
|
|
559
|
+
try {
|
|
560
|
+
const memoryService = runtime.getService("memory");
|
|
561
|
+
if (!memoryService) {
|
|
562
|
+
return {
|
|
563
|
+
data: { memoryCount: 0 },
|
|
564
|
+
values: { longTermMemories: "" },
|
|
565
|
+
text: ""
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const { entityId } = message;
|
|
569
|
+
if (entityId === runtime.agentId) {
|
|
570
|
+
return {
|
|
571
|
+
data: { memoryCount: 0 },
|
|
572
|
+
values: { longTermMemories: "" },
|
|
573
|
+
text: ""
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const memories = await memoryService.getLongTermMemories(entityId, undefined, 25);
|
|
577
|
+
if (memories.length === 0) {
|
|
578
|
+
return {
|
|
579
|
+
data: { memoryCount: 0 },
|
|
580
|
+
values: { longTermMemories: "" },
|
|
581
|
+
text: ""
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
const formattedMemories = await memoryService.getFormattedLongTermMemories(entityId);
|
|
585
|
+
const text = addHeader2("# What I Know About You", formattedMemories);
|
|
586
|
+
const categoryCounts = new Map;
|
|
587
|
+
for (const memory of memories) {
|
|
588
|
+
const count = categoryCounts.get(memory.category) || 0;
|
|
589
|
+
categoryCounts.set(memory.category, count + 1);
|
|
590
|
+
}
|
|
591
|
+
const categoryList = Array.from(categoryCounts.entries()).map(([cat, count]) => `${cat}: ${count}`).join(", ");
|
|
592
|
+
return {
|
|
593
|
+
data: {
|
|
594
|
+
memoryCount: memories.length,
|
|
595
|
+
categories: categoryList
|
|
596
|
+
},
|
|
597
|
+
values: {
|
|
598
|
+
longTermMemories: text,
|
|
599
|
+
memoryCategories: categoryList
|
|
600
|
+
},
|
|
601
|
+
text
|
|
602
|
+
};
|
|
603
|
+
} catch (error) {
|
|
604
|
+
logger4.error({ error }, "Error in longTermMemoryProvider:");
|
|
605
|
+
return {
|
|
606
|
+
data: { memoryCount: 0 },
|
|
607
|
+
values: { longTermMemories: "" },
|
|
608
|
+
text: ""
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
}
|
|
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);
|
|
1011
1045
|
}
|
|
1012
|
-
|
|
1013
|
-
examples: []
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
// src/providers/long-term-memory.ts
|
|
1017
|
-
import {
|
|
1018
|
-
logger as logger4,
|
|
1019
|
-
addHeader
|
|
1020
|
-
} from "@elizaos/core";
|
|
1021
|
-
var longTermMemoryProvider = {
|
|
1022
|
-
name: "LONG_TERM_MEMORY",
|
|
1023
|
-
description: "Persistent facts and preferences about the user",
|
|
1024
|
-
position: 50,
|
|
1025
|
-
get: async (runtime, message, _state) => {
|
|
1046
|
+
const db = this.getDb();
|
|
1026
1047
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (entityId === runtime.agentId) {
|
|
1037
|
-
return {
|
|
1038
|
-
data: { memories: [] },
|
|
1039
|
-
values: { longTermMemories: "" },
|
|
1040
|
-
text: ""
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
const memories = await memoryService.getLongTermMemories(entityId, undefined, 25);
|
|
1044
|
-
if (memories.length === 0) {
|
|
1045
|
-
return {
|
|
1046
|
-
data: { memories: [] },
|
|
1047
|
-
values: { longTermMemories: "" },
|
|
1048
|
-
text: ""
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
const formattedMemories = await memoryService.getFormattedLongTermMemories(entityId);
|
|
1052
|
-
const text4 = addHeader("# What I Know About You", formattedMemories);
|
|
1053
|
-
const categoryCounts = new Map;
|
|
1054
|
-
for (const memory of memories) {
|
|
1055
|
-
const count = categoryCounts.get(memory.category) || 0;
|
|
1056
|
-
categoryCounts.set(memory.category, count + 1);
|
|
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));
|
|
1057
1057
|
}
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
+
}));
|
|
1070
1078
|
} catch (error) {
|
|
1071
|
-
|
|
1072
|
-
return
|
|
1073
|
-
data: { memories: [] },
|
|
1074
|
-
values: { longTermMemories: "" },
|
|
1075
|
-
text: ""
|
|
1076
|
-
};
|
|
1079
|
+
logger5.warn({ error }, "Vector search failed, falling back to recent memories");
|
|
1080
|
+
return this.getLongTermMemories(entityId, undefined, limit);
|
|
1077
1081
|
}
|
|
1078
1082
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
description: "Provides summarized context from previous conversations",
|
|
1089
|
-
position: 96,
|
|
1090
|
-
get: async (runtime, message, _state) => {
|
|
1091
|
-
try {
|
|
1092
|
-
const memoryService = runtime.getService("memory");
|
|
1093
|
-
const { roomId } = message;
|
|
1094
|
-
if (!memoryService) {
|
|
1095
|
-
return {
|
|
1096
|
-
data: {
|
|
1097
|
-
summary: null
|
|
1098
|
-
},
|
|
1099
|
-
values: {
|
|
1100
|
-
sessionSummaries: "",
|
|
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(", ")}*`;
|
|
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, []);
|
|
1128
1092
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
},
|
|
1139
|
-
text: sessionSummariesWithTopics
|
|
1140
|
-
};
|
|
1141
|
-
} catch (error) {
|
|
1142
|
-
logger5.error({ error }, "Error in contextSummaryProvider:");
|
|
1143
|
-
return {
|
|
1144
|
-
data: {
|
|
1145
|
-
summary: null
|
|
1146
|
-
},
|
|
1147
|
-
values: {
|
|
1148
|
-
sessionSummaries: "",
|
|
1149
|
-
sessionSummariesWithTopics: ""
|
|
1150
|
-
},
|
|
1151
|
-
text: ""
|
|
1152
|
-
};
|
|
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(`
|
|
1099
|
+
`);
|
|
1100
|
+
sections.push(`**${categoryName}**:
|
|
1101
|
+
${items}`);
|
|
1153
1102
|
}
|
|
1103
|
+
return sections.join(`
|
|
1104
|
+
|
|
1105
|
+
`);
|
|
1154
1106
|
}
|
|
1155
|
-
}
|
|
1107
|
+
}
|
|
1156
1108
|
|
|
1157
1109
|
// src/index.ts
|
|
1158
1110
|
var memoryPlugin = {
|
|
1159
1111
|
name: "memory",
|
|
1160
|
-
description: "
|
|
1112
|
+
description: "Memory management with conversation summarization and long-term persistent memory",
|
|
1161
1113
|
services: [MemoryService],
|
|
1162
1114
|
evaluators: [summarizationEvaluator, longTermExtractionEvaluator],
|
|
1163
|
-
providers: [
|
|
1164
|
-
longTermMemoryProvider,
|
|
1165
|
-
contextSummaryProvider
|
|
1166
|
-
],
|
|
1115
|
+
providers: [longTermMemoryProvider, contextSummaryProvider],
|
|
1167
1116
|
schema: exports_schemas
|
|
1168
1117
|
};
|
|
1169
1118
|
var src_default = memoryPlugin;
|
|
1170
1119
|
export {
|
|
1120
|
+
summarizationEvaluator,
|
|
1171
1121
|
sessionSummaries,
|
|
1172
1122
|
memoryPlugin,
|
|
1173
1123
|
memoryAccessLogs,
|
|
1174
1124
|
longTermMemoryProvider,
|
|
1175
1125
|
longTermMemories,
|
|
1126
|
+
longTermExtractionEvaluator,
|
|
1176
1127
|
src_default as default,
|
|
1177
1128
|
contextSummaryProvider,
|
|
1178
1129
|
MemoryService,
|
|
1179
1130
|
LongTermMemoryCategory
|
|
1180
1131
|
};
|
|
1181
1132
|
|
|
1182
|
-
//# debugId=
|
|
1133
|
+
//# debugId=FC048A8C8739D95E64756E2164756E21
|