@elizaos/plugin-memory 1.1.0 → 1.1.2
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/README.md +230 -330
- package/dist/actions/remember.d.ts +11 -0
- package/dist/browser/index.browser.js +205 -348
- package/dist/browser/index.browser.js.map +13 -23
- package/dist/cjs/index.node.cjs +936 -2193
- package/dist/cjs/index.node.js.map +13 -23
- package/dist/evaluators/long-term-extraction.d.ts +8 -0
- package/dist/evaluators/summarization.d.ts +25 -6
- package/dist/index.d.ts +32 -152
- package/dist/node/index.node.js +944 -2210
- package/dist/node/index.node.js.map +13 -23
- package/dist/providers/context-summary.d.ts +12 -0
- package/dist/providers/long-term-memory.d.ts +11 -18
- package/dist/schemas/index.d.ts +6 -16
- package/dist/schemas/long-term-memories.d.ts +70 -308
- package/dist/schemas/memory-access-logs.d.ts +154 -0
- package/dist/schemas/session-summaries.d.ts +283 -0
- package/dist/services/memory-service.d.ts +51 -95
- package/dist/types/index.d.ts +53 -298
- package/package.json +2 -84
- package/dist/evaluators/consolidation.d.ts +0 -19
- package/dist/prompts/consolidation.d.ts +0 -35
- package/dist/prompts/summarization.d.ts +0 -25
- package/dist/providers/action-results.d.ts +0 -2
- package/dist/providers/recent-conversation-summary.d.ts +0 -2
- package/dist/repositories/conversation-summary.d.ts +0 -33
- package/dist/repositories/index.d.ts +0 -17
- package/dist/repositories/long-term-memory.d.ts +0 -53
- package/dist/schemas/conversation-summaries.d.ts +0 -494
- package/dist/utils/db-mapping.d.ts +0 -20
- package/dist/utils/decay-scoring.d.ts +0 -41
- package/dist/utils/embedding.d.ts +0 -21
- package/dist/utils/formatting.d.ts +0 -17
- package/dist/utils/index.d.ts +0 -17
- package/dist/utils/search-merging.d.ts +0 -18
- package/dist/utils/token-counter.d.ts +0 -53
package/dist/node/index.node.js
CHANGED
|
@@ -12,46 +12,16 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/services/memory-service.ts
|
|
13
13
|
import {
|
|
14
14
|
Service,
|
|
15
|
-
logger
|
|
16
|
-
ModelType as ModelType2,
|
|
17
|
-
parseKeyValueXml,
|
|
18
|
-
parseBooleanFromText
|
|
15
|
+
logger
|
|
19
16
|
} from "@elizaos/core";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
// src/types/index.ts
|
|
23
|
-
var MEMORY_DIMENSION_MAP = {
|
|
24
|
-
384: "dim384",
|
|
25
|
-
512: "dim512",
|
|
26
|
-
768: "dim768",
|
|
27
|
-
1024: "dim1024",
|
|
28
|
-
1536: "dim1536",
|
|
29
|
-
3072: "dim3072"
|
|
30
|
-
};
|
|
31
|
-
var MemoryType;
|
|
32
|
-
((MemoryType2) => {
|
|
33
|
-
MemoryType2["EPISODIC"] = "EPISODIC";
|
|
34
|
-
MemoryType2["SEMANTIC"] = "SEMANTIC";
|
|
35
|
-
MemoryType2["PROCEDURAL"] = "PROCEDURAL";
|
|
36
|
-
})(MemoryType ||= {});
|
|
37
|
-
var DecayFunction;
|
|
38
|
-
((DecayFunction2) => {
|
|
39
|
-
DecayFunction2["EXPONENTIAL"] = "EXPONENTIAL";
|
|
40
|
-
DecayFunction2["LINEAR"] = "LINEAR";
|
|
41
|
-
DecayFunction2["NONE"] = "NONE";
|
|
42
|
-
})(DecayFunction ||= {});
|
|
43
|
-
|
|
44
|
-
// src/repositories/long-term-memory.ts
|
|
45
|
-
import { logger } from "@elizaos/core";
|
|
46
|
-
import { eq, and, desc, sql as sql3, cosineDistance, gte, or, isNull } from "drizzle-orm";
|
|
17
|
+
import { eq, and, desc, sql as sql4, cosineDistance, gte } from "drizzle-orm";
|
|
47
18
|
|
|
48
19
|
// src/schemas/index.ts
|
|
49
20
|
var exports_schemas = {};
|
|
50
21
|
__export(exports_schemas, {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
conversationSummaries: () => conversationSummaries
|
|
22
|
+
sessionSummaries: () => sessionSummaries,
|
|
23
|
+
memoryAccessLogs: () => memoryAccessLogs,
|
|
24
|
+
longTermMemories: () => longTermMemories
|
|
55
25
|
});
|
|
56
26
|
|
|
57
27
|
// src/schemas/long-term-memories.ts
|
|
@@ -64,2385 +34,1149 @@ import {
|
|
|
64
34
|
real,
|
|
65
35
|
index,
|
|
66
36
|
varchar,
|
|
67
|
-
timestamp
|
|
68
|
-
boolean,
|
|
69
|
-
vector,
|
|
70
|
-
foreignKey
|
|
37
|
+
timestamp
|
|
71
38
|
} from "drizzle-orm/pg-core";
|
|
72
|
-
import { VECTOR_DIMS } from "@elizaos/core";
|
|
73
39
|
var longTermMemories = pgTable("long_term_memories", {
|
|
74
40
|
id: varchar("id", { length: 36 }).primaryKey(),
|
|
75
41
|
agentId: varchar("agent_id", { length: 36 }).notNull(),
|
|
76
42
|
entityId: varchar("entity_id", { length: 36 }).notNull(),
|
|
77
|
-
|
|
78
|
-
type: text("type").notNull(),
|
|
43
|
+
category: text("category").notNull(),
|
|
79
44
|
content: text("content").notNull(),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
45
|
+
metadata: jsonb("metadata"),
|
|
46
|
+
embedding: real("embedding").array(),
|
|
47
|
+
confidence: real("confidence").default(1),
|
|
48
|
+
source: text("source"),
|
|
84
49
|
createdAt: timestamp("created_at").default(sql`now()`).notNull(),
|
|
50
|
+
updatedAt: timestamp("updated_at").default(sql`now()`).notNull(),
|
|
85
51
|
lastAccessedAt: timestamp("last_accessed_at"),
|
|
86
|
-
accessCount: integer("access_count").default(0)
|
|
87
|
-
isActive: boolean("is_active").default(true).notNull(),
|
|
88
|
-
source: jsonb("source").notNull().default({}),
|
|
89
|
-
metadata: jsonb("metadata").notNull().default({}),
|
|
90
|
-
supersedesId: varchar("supersedes_id", { length: 36 })
|
|
52
|
+
accessCount: integer("access_count").default(0)
|
|
91
53
|
}, (table) => ({
|
|
92
|
-
agentEntityIdx: index("
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
confidenceIdx: index("ltm_confidence_idx").on(table.confidence),
|
|
97
|
-
createdAtIdx: index("ltm_created_at_idx").on(table.createdAt),
|
|
98
|
-
lastAccessedIdx: index("ltm_last_accessed_idx").on(table.lastAccessedAt),
|
|
99
|
-
agentEntityActiveConfidenceIdx: index("ltm_agent_entity_active_conf_idx").on(table.agentId, table.entityId, table.isActive, table.confidence)
|
|
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)
|
|
100
58
|
}));
|
|
101
|
-
|
|
102
|
-
id: varchar("id", { length: 36 }).primaryKey(),
|
|
103
|
-
memoryId: varchar("memory_id", { length: 36 }).notNull().references(() => longTermMemories.id, { onDelete: "cascade" }),
|
|
104
|
-
dim384: vector("dim_384", { dimensions: VECTOR_DIMS.SMALL }),
|
|
105
|
-
dim512: vector("dim_512", { dimensions: VECTOR_DIMS.MEDIUM }),
|
|
106
|
-
dim768: vector("dim_768", { dimensions: VECTOR_DIMS.LARGE }),
|
|
107
|
-
dim1024: vector("dim_1024", { dimensions: VECTOR_DIMS.XL }),
|
|
108
|
-
dim1536: vector("dim_1536", { dimensions: VECTOR_DIMS.XXL }),
|
|
109
|
-
dim3072: vector("dim_3072", { dimensions: VECTOR_DIMS.XXXL }),
|
|
110
|
-
createdAt: timestamp("created_at").default(sql`now()`).notNull()
|
|
111
|
-
}, (table) => [
|
|
112
|
-
index("idx_ltm_embedding_memory_id").on(table.memoryId),
|
|
113
|
-
foreignKey({
|
|
114
|
-
name: "fk_ltm_embedding_memory",
|
|
115
|
-
columns: [table.memoryId],
|
|
116
|
-
foreignColumns: [longTermMemories.id]
|
|
117
|
-
}).onDelete("cascade")
|
|
118
|
-
]);
|
|
119
|
-
// src/schemas/conversation-summaries.ts
|
|
59
|
+
// src/schemas/session-summaries.ts
|
|
120
60
|
import { sql as sql2 } from "drizzle-orm";
|
|
121
61
|
import {
|
|
122
62
|
pgTable as pgTable2,
|
|
123
63
|
text as text2,
|
|
124
64
|
integer as integer2,
|
|
125
65
|
jsonb as jsonb2,
|
|
66
|
+
real as real2,
|
|
126
67
|
index as index2,
|
|
127
68
|
varchar as varchar2,
|
|
128
|
-
timestamp as timestamp2
|
|
129
|
-
vector as vector2,
|
|
130
|
-
foreignKey as foreignKey2
|
|
69
|
+
timestamp as timestamp2
|
|
131
70
|
} from "drizzle-orm/pg-core";
|
|
132
|
-
|
|
133
|
-
var conversationSummaries = pgTable2("conversation_summaries", {
|
|
71
|
+
var sessionSummaries = pgTable2("session_summaries", {
|
|
134
72
|
id: varchar2("id", { length: 36 }).primaryKey(),
|
|
135
73
|
agentId: varchar2("agent_id", { length: 36 }).notNull(),
|
|
136
|
-
entityId: varchar2("entity_id", { length: 36 }).notNull(),
|
|
137
74
|
roomId: varchar2("room_id", { length: 36 }).notNull(),
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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),
|
|
142
79
|
startTime: timestamp2("start_time").notNull(),
|
|
143
80
|
endTime: timestamp2("end_time").notNull(),
|
|
144
|
-
|
|
145
|
-
|
|
81
|
+
topics: jsonb2("topics"),
|
|
82
|
+
metadata: jsonb2("metadata"),
|
|
83
|
+
embedding: real2("embedding").array(),
|
|
146
84
|
createdAt: timestamp2("created_at").default(sql2`now()`).notNull(),
|
|
147
|
-
|
|
148
|
-
accessCount: integer2("access_count").default(0).notNull(),
|
|
149
|
-
metadata: jsonb2("metadata").notNull().default({})
|
|
85
|
+
updatedAt: timestamp2("updated_at").default(sql2`now()`).notNull()
|
|
150
86
|
}, (table) => ({
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
timeRangeIdx: index2("cs_time_range_idx").on(table.startTime, table.endTime),
|
|
155
|
-
createdAtIdx: index2("cs_created_at_idx").on(table.createdAt),
|
|
156
|
-
lastAccessedIdx: index2("cs_last_accessed_idx").on(table.lastAccessedAt),
|
|
157
|
-
entityRoomLevelTimeIdx: index2("cs_entity_room_level_time_idx").on(table.entityId, table.roomId, table.level, table.createdAt)
|
|
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)
|
|
158
90
|
}));
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
content: row.content,
|
|
209
|
-
embedding: row.embedding,
|
|
210
|
-
tokenCount: row.tokenCount,
|
|
211
|
-
startTime: row.startTime,
|
|
212
|
-
endTime: row.endTime,
|
|
213
|
-
sourceCount: row.sourceCount,
|
|
214
|
-
sourceIds: row.sourceIds,
|
|
215
|
-
createdAt: row.createdAt,
|
|
216
|
-
lastAccessedAt: row.lastAccessedAt,
|
|
217
|
-
accessCount: row.accessCount,
|
|
218
|
-
metadata: row.metadata || {}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/repositories/long-term-memory.ts
|
|
223
|
-
class LongTermMemoryRepository {
|
|
224
|
-
runtime;
|
|
225
|
-
embeddingDimension;
|
|
226
|
-
constructor(runtime, embeddingDimension) {
|
|
227
|
-
this.runtime = runtime;
|
|
228
|
-
this.embeddingDimension = embeddingDimension;
|
|
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");
|
|
229
140
|
}
|
|
230
|
-
async
|
|
231
|
-
|
|
232
|
-
const
|
|
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;
|
|
233
190
|
if (!db) {
|
|
234
191
|
throw new Error("Database not available");
|
|
235
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
|
+
}
|
|
236
223
|
try {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const stillNotReady = await adapter.isReady();
|
|
242
|
-
if (stillNotReady === false) {
|
|
243
|
-
throw new Error("Database connection lost and could not reconnect");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
224
|
+
const checkpoint = await this.runtime.getCache(key);
|
|
225
|
+
const messageCount = checkpoint ?? 0;
|
|
226
|
+
this.lastExtractionCheckpoints.set(key, messageCount);
|
|
227
|
+
return messageCount;
|
|
246
228
|
} catch (error) {
|
|
247
|
-
logger.error
|
|
248
|
-
|
|
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");
|
|
249
241
|
}
|
|
250
|
-
return db;
|
|
251
242
|
}
|
|
252
|
-
async
|
|
253
|
-
const
|
|
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();
|
|
254
266
|
const id = crypto.randomUUID();
|
|
255
267
|
const now = new Date;
|
|
256
268
|
const newMemory = {
|
|
257
269
|
id,
|
|
258
270
|
createdAt: now,
|
|
259
|
-
|
|
271
|
+
updatedAt: now,
|
|
260
272
|
accessCount: 0,
|
|
261
|
-
isActive: true,
|
|
262
|
-
embedding: embedding || [],
|
|
263
273
|
...memory
|
|
264
274
|
};
|
|
265
|
-
|
|
266
|
-
await
|
|
275
|
+
try {
|
|
276
|
+
await db.insert(longTermMemories).values({
|
|
267
277
|
id: newMemory.id,
|
|
268
278
|
agentId: newMemory.agentId,
|
|
269
279
|
entityId: newMemory.entityId,
|
|
270
|
-
|
|
271
|
-
type: newMemory.type,
|
|
280
|
+
category: newMemory.category,
|
|
272
281
|
content: newMemory.content,
|
|
273
|
-
|
|
282
|
+
metadata: newMemory.metadata || {},
|
|
283
|
+
embedding: newMemory.embedding,
|
|
274
284
|
confidence: newMemory.confidence,
|
|
275
|
-
decayRate: newMemory.decayRate,
|
|
276
|
-
decayFunction: newMemory.decayFunction,
|
|
277
|
-
createdAt: now,
|
|
278
|
-
lastAccessedAt: null,
|
|
279
|
-
accessCount: 0,
|
|
280
|
-
isActive: true,
|
|
281
285
|
source: newMemory.source,
|
|
282
|
-
|
|
283
|
-
|
|
286
|
+
accessCount: newMemory.accessCount,
|
|
287
|
+
createdAt: now,
|
|
288
|
+
updatedAt: now,
|
|
289
|
+
lastAccessedAt: newMemory.lastAccessedAt
|
|
284
290
|
});
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
memoryId: id,
|
|
289
|
-
createdAt: now
|
|
290
|
-
};
|
|
291
|
-
embeddingValues[this.embeddingDimension] = embedding;
|
|
292
|
-
await tx.insert(longTermMemoryEmbeddings).values(embeddingValues);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
logger.info({
|
|
296
|
-
id: newMemory.id,
|
|
297
|
-
type: newMemory.type,
|
|
298
|
-
entityId: newMemory.entityId,
|
|
299
|
-
confidence: newMemory.confidence
|
|
300
|
-
}, "Stored new long-term memory");
|
|
301
|
-
return newMemory;
|
|
302
|
-
}
|
|
303
|
-
async findById(id) {
|
|
304
|
-
const db = await this.getDb();
|
|
305
|
-
const results = await db.select().from(longTermMemories).where(eq(longTermMemories.id, id)).limit(1);
|
|
306
|
-
if (results.length === 0) {
|
|
307
|
-
return null;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
logger.error({ error }, "Failed to store long-term memory");
|
|
293
|
+
throw error;
|
|
308
294
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
async update(id, updates, newEmbedding) {
|
|
312
|
-
const db = await this.getDb();
|
|
313
|
-
const updateData = {};
|
|
314
|
-
if (updates.content !== undefined)
|
|
315
|
-
updateData.content = updates.content;
|
|
316
|
-
if (updates.embeddingContext !== undefined)
|
|
317
|
-
updateData.embeddingContext = updates.embeddingContext;
|
|
318
|
-
if (updates.confidence !== undefined)
|
|
319
|
-
updateData.confidence = updates.confidence;
|
|
320
|
-
if (updates.decayRate !== undefined)
|
|
321
|
-
updateData.decayRate = updates.decayRate;
|
|
322
|
-
if (updates.decayFunction !== undefined)
|
|
323
|
-
updateData.decayFunction = updates.decayFunction;
|
|
324
|
-
if (updates.lastAccessedAt !== undefined)
|
|
325
|
-
updateData.lastAccessedAt = updates.lastAccessedAt;
|
|
326
|
-
if (updates.accessCount !== undefined)
|
|
327
|
-
updateData.accessCount = updates.accessCount;
|
|
328
|
-
if (updates.isActive !== undefined)
|
|
329
|
-
updateData.isActive = updates.isActive;
|
|
330
|
-
if (updates.source !== undefined)
|
|
331
|
-
updateData.source = updates.source;
|
|
332
|
-
if (updates.metadata !== undefined)
|
|
333
|
-
updateData.metadata = updates.metadata;
|
|
334
|
-
if (updates.supersedesId !== undefined)
|
|
335
|
-
updateData.supersedesId = updates.supersedesId;
|
|
336
|
-
await db.transaction(async (tx) => {
|
|
337
|
-
await tx.update(longTermMemories).set(updateData).where(eq(longTermMemories.id, id));
|
|
338
|
-
if (newEmbedding && this.embeddingDimension) {
|
|
339
|
-
const embeddingUpdate = {};
|
|
340
|
-
embeddingUpdate[this.embeddingDimension] = newEmbedding;
|
|
341
|
-
await tx.update(longTermMemoryEmbeddings).set(embeddingUpdate).where(eq(longTermMemoryEmbeddings.memoryId, id));
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
logger.info({ id }, "Updated long-term memory");
|
|
345
|
-
}
|
|
346
|
-
async delete(id) {
|
|
347
|
-
const db = await this.getDb();
|
|
348
|
-
await db.delete(longTermMemories).where(eq(longTermMemories.id, id));
|
|
349
|
-
logger.info({ id }, "Deleted long-term memory");
|
|
295
|
+
logger.info(`Stored long-term memory: ${newMemory.category} for entity ${newMemory.entityId}`);
|
|
296
|
+
return newMemory;
|
|
350
297
|
}
|
|
351
|
-
async
|
|
352
|
-
const db =
|
|
298
|
+
async getLongTermMemories(entityId, category, limit = 10) {
|
|
299
|
+
const db = this.getDb();
|
|
353
300
|
const conditions = [
|
|
354
301
|
eq(longTermMemories.agentId, this.runtime.agentId),
|
|
355
302
|
eq(longTermMemories.entityId, entityId)
|
|
356
303
|
];
|
|
357
|
-
if (
|
|
358
|
-
conditions.push(eq(longTermMemories.
|
|
359
|
-
}
|
|
360
|
-
if (type) {
|
|
361
|
-
conditions.push(eq(longTermMemories.type, type));
|
|
362
|
-
}
|
|
363
|
-
const results = await db.select().from(longTermMemories).where(and(...conditions)).orderBy(desc(longTermMemories.confidence), desc(longTermMemories.createdAt)).limit(limit);
|
|
364
|
-
return results.map((row) => mapDbRowToLongTermMemory(row));
|
|
365
|
-
}
|
|
366
|
-
async vectorSearch(params, queryEmbedding, similarityThreshold = 0.3) {
|
|
367
|
-
if (!this.embeddingDimension) {
|
|
368
|
-
logger.warn("Embedding dimension not set, skipping vector search");
|
|
369
|
-
return [];
|
|
304
|
+
if (category) {
|
|
305
|
+
conditions.push(eq(longTermMemories.category, category));
|
|
370
306
|
}
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
const similarity = sql3`1 - (${cosineDistance(longTermMemoryEmbeddings[this.embeddingDimension], queryEmbedding)})`;
|
|
374
|
-
const conditions = [
|
|
375
|
-
eq(longTermMemories.agentId, this.runtime.agentId),
|
|
376
|
-
eq(longTermMemories.entityId, params.entityId),
|
|
377
|
-
sql3`${longTermMemoryEmbeddings[this.embeddingDimension]} IS NOT NULL`,
|
|
378
|
-
gte(similarity, similarityThreshold)
|
|
379
|
-
];
|
|
380
|
-
if (params.minConfidence) {
|
|
381
|
-
conditions.push(gte(longTermMemories.confidence, params.minConfidence));
|
|
382
|
-
}
|
|
383
|
-
if (!params.includeInactive) {
|
|
384
|
-
conditions.push(eq(longTermMemories.isActive, true));
|
|
385
|
-
}
|
|
386
|
-
if (params.type) {
|
|
387
|
-
conditions.push(eq(longTermMemories.type, params.type));
|
|
388
|
-
}
|
|
389
|
-
if (params.roomId) {
|
|
390
|
-
conditions.push(or(eq(longTermMemories.roomId, params.roomId), isNull(longTermMemories.roomId)));
|
|
391
|
-
}
|
|
392
|
-
const results = await db.select({
|
|
393
|
-
memory: longTermMemories,
|
|
394
|
-
embedding: longTermMemoryEmbeddings[this.embeddingDimension],
|
|
395
|
-
similarity
|
|
396
|
-
}).from(longTermMemories).innerJoin(longTermMemoryEmbeddings, eq(longTermMemoryEmbeddings.memoryId, longTermMemories.id)).where(and(...conditions)).orderBy(desc(similarity)).limit(params.limit || 20);
|
|
397
|
-
return results.map((row) => ({
|
|
398
|
-
...mapDbRowToLongTermMemory(row.memory),
|
|
399
|
-
embedding: row.embedding,
|
|
400
|
-
relevanceScore: row.similarity,
|
|
401
|
-
activationScore: 0,
|
|
402
|
-
finalScore: 0
|
|
403
|
-
}));
|
|
404
|
-
} catch (error) {
|
|
405
|
-
logger.error("Failed to execute vector search:", JSON.stringify(error));
|
|
406
|
-
return [];
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
async fetchAllActive() {
|
|
410
|
-
const db = await this.getDb();
|
|
411
|
-
const memories = await db.select().from(longTermMemories).where(and(eq(longTermMemories.agentId, this.runtime.agentId), eq(longTermMemories.isActive, true)));
|
|
412
|
-
return memories.map((row) => ({
|
|
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) => ({
|
|
413
309
|
id: row.id,
|
|
310
|
+
agentId: row.agentId,
|
|
311
|
+
entityId: row.entityId,
|
|
312
|
+
category: row.category,
|
|
414
313
|
content: row.content,
|
|
415
|
-
|
|
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
|
|
416
322
|
}));
|
|
417
323
|
}
|
|
418
|
-
async
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
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;
|
|
433
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}`);
|
|
434
349
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
import { eq as eq2, and as and2, desc as desc2, sql as sql4, cosineDistance as cosineDistance2 } from "drizzle-orm";
|
|
440
|
-
class ConversationSummaryRepository {
|
|
441
|
-
runtime;
|
|
442
|
-
embeddingDimension;
|
|
443
|
-
constructor(runtime, embeddingDimension) {
|
|
444
|
-
this.runtime = runtime;
|
|
445
|
-
this.embeddingDimension = embeddingDimension;
|
|
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}`);
|
|
446
354
|
}
|
|
447
|
-
async
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
try {
|
|
454
|
-
const isReady = await adapter.isReady();
|
|
455
|
-
if (!isReady) {
|
|
456
|
-
logger2.warn("[ConversationSummaryRepository] Database not ready, attempting reconnect...");
|
|
457
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
458
|
-
const stillNotReady = await adapter.isReady();
|
|
459
|
-
if (stillNotReady === false) {
|
|
460
|
-
throw new Error("Database connection lost and could not reconnect");
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
} catch (error) {
|
|
464
|
-
logger2.error("[ConversationSummaryRepository] Database health check failed:", error);
|
|
465
|
-
throw new Error("Database connection health check failed");
|
|
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;
|
|
466
360
|
}
|
|
467
|
-
|
|
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
|
+
};
|
|
468
378
|
}
|
|
469
|
-
async
|
|
470
|
-
const db =
|
|
379
|
+
async storeSessionSummary(summary) {
|
|
380
|
+
const db = this.getDb();
|
|
471
381
|
const id = crypto.randomUUID();
|
|
472
382
|
const now = new Date;
|
|
473
383
|
const newSummary = {
|
|
474
384
|
id,
|
|
475
385
|
createdAt: now,
|
|
476
|
-
|
|
477
|
-
accessCount: 0,
|
|
478
|
-
embedding: embedding || [],
|
|
386
|
+
updatedAt: now,
|
|
479
387
|
...summary
|
|
480
388
|
};
|
|
481
|
-
await db.
|
|
482
|
-
await tx.insert(conversationSummaries).values({
|
|
483
|
-
id: newSummary.id,
|
|
484
|
-
agentId: newSummary.agentId,
|
|
485
|
-
entityId: newSummary.entityId,
|
|
486
|
-
roomId: newSummary.roomId,
|
|
487
|
-
level: newSummary.level,
|
|
488
|
-
parentSummaryId: newSummary.parentSummaryId || null,
|
|
489
|
-
content: newSummary.content,
|
|
490
|
-
tokenCount: newSummary.tokenCount,
|
|
491
|
-
startTime: newSummary.startTime,
|
|
492
|
-
endTime: newSummary.endTime,
|
|
493
|
-
sourceCount: newSummary.sourceCount,
|
|
494
|
-
sourceIds: newSummary.sourceIds,
|
|
495
|
-
createdAt: now,
|
|
496
|
-
lastAccessedAt: null,
|
|
497
|
-
accessCount: 0,
|
|
498
|
-
metadata: newSummary.metadata
|
|
499
|
-
});
|
|
500
|
-
if (embedding && this.embeddingDimension) {
|
|
501
|
-
const embeddingValues = {
|
|
502
|
-
id: crypto.randomUUID(),
|
|
503
|
-
summaryId: id,
|
|
504
|
-
createdAt: now
|
|
505
|
-
};
|
|
506
|
-
embeddingValues[this.embeddingDimension] = embedding;
|
|
507
|
-
await tx.insert(conversationSummaryEmbeddings).values(embeddingValues);
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
logger2.info({
|
|
389
|
+
await db.insert(sessionSummaries).values({
|
|
511
390
|
id: newSummary.id,
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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}`);
|
|
516
406
|
return newSummary;
|
|
517
407
|
}
|
|
518
|
-
async
|
|
519
|
-
const db =
|
|
520
|
-
const
|
|
521
|
-
|
|
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
|
+
}));
|
|
522
456
|
}
|
|
523
|
-
async
|
|
524
|
-
if (!this.
|
|
525
|
-
|
|
526
|
-
return
|
|
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);
|
|
527
461
|
}
|
|
528
|
-
const db =
|
|
462
|
+
const db = this.getDb();
|
|
529
463
|
try {
|
|
530
|
-
const
|
|
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
|
+
}
|
|
531
474
|
const results = await db.select({
|
|
532
|
-
|
|
533
|
-
embedding: conversationSummaryEmbeddings[this.embeddingDimension],
|
|
475
|
+
memory: longTermMemories,
|
|
534
476
|
similarity
|
|
535
|
-
}).from(
|
|
536
|
-
return results.map((
|
|
537
|
-
|
|
538
|
-
|
|
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
|
|
539
493
|
}));
|
|
540
494
|
} catch (error) {
|
|
541
|
-
|
|
542
|
-
return
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
async updateAccessMetadata(summaryIds) {
|
|
546
|
-
if (summaryIds.length === 0)
|
|
547
|
-
return;
|
|
548
|
-
const db = await this.getDb();
|
|
549
|
-
const now = new Date;
|
|
550
|
-
try {
|
|
551
|
-
for (const id of summaryIds) {
|
|
552
|
-
await db.update(conversationSummaries).set({
|
|
553
|
-
lastAccessedAt: now,
|
|
554
|
-
accessCount: sql4`${conversationSummaries.accessCount} + 1`
|
|
555
|
-
}).where(eq2(conversationSummaries.id, id));
|
|
556
|
-
}
|
|
557
|
-
logger2.debug({ count: summaryIds.length }, "Updated summary access metadata");
|
|
558
|
-
} catch (error) {
|
|
559
|
-
logger2.error({ error }, "Failed to update summary access metadata");
|
|
495
|
+
logger.warn({ error }, "Vector search failed, falling back to recent memories");
|
|
496
|
+
return this.getLongTermMemories(entityId, undefined, limit);
|
|
560
497
|
}
|
|
561
498
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
async function generateEmbedding(runtime, text3) {
|
|
567
|
-
try {
|
|
568
|
-
const embedding = await runtime.useModel(ModelType.TEXT_EMBEDDING, text3);
|
|
569
|
-
return embedding;
|
|
570
|
-
} catch (error) {
|
|
571
|
-
logger3.error({ error }, "Failed to generate embedding");
|
|
572
|
-
return new Array(1536).fill(0);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
function cleanEmbedding(embedding) {
|
|
576
|
-
return embedding.map((n) => Number.isFinite(n) ? Number(n.toFixed(6)) : 0);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// src/utils/decay-scoring.ts
|
|
580
|
-
function calculateDecayFactor(decayFunction, decayRate, timeDeltaDays) {
|
|
581
|
-
switch (decayFunction) {
|
|
582
|
-
case "EXPONENTIAL" /* EXPONENTIAL */:
|
|
583
|
-
return Math.exp(-decayRate * timeDeltaDays);
|
|
584
|
-
case "LINEAR" /* LINEAR */:
|
|
585
|
-
return Math.max(0, 1 - decayRate * timeDeltaDays);
|
|
586
|
-
case "NONE" /* NONE */:
|
|
587
|
-
return 1;
|
|
588
|
-
default:
|
|
589
|
-
return 1;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function calculateAccessBoost(accessCount) {
|
|
593
|
-
return 1 + Math.log(1 + accessCount) * 0.1;
|
|
594
|
-
}
|
|
595
|
-
function applyDecayScoring(memories) {
|
|
596
|
-
const now = Date.now();
|
|
597
|
-
return memories.map((memory) => {
|
|
598
|
-
const lastAccessed = memory.lastAccessedAt?.getTime() || memory.createdAt.getTime();
|
|
599
|
-
const timeDeltaDays = (now - lastAccessed) / (1000 * 60 * 60 * 24);
|
|
600
|
-
const decayFactor = calculateDecayFactor(memory.decayFunction, memory.decayRate, timeDeltaDays);
|
|
601
|
-
const accessBoost = calculateAccessBoost(memory.accessCount);
|
|
602
|
-
const activationScore = memory.confidence * decayFactor * accessBoost;
|
|
603
|
-
const finalScore = memory.relevanceScore * activationScore;
|
|
604
|
-
return {
|
|
605
|
-
...memory,
|
|
606
|
-
activationScore,
|
|
607
|
-
finalScore
|
|
608
|
-
};
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// src/utils/search-merging.ts
|
|
613
|
-
function mergeSearchResults(vectorResults, bm25Results) {
|
|
614
|
-
const merged = new Map;
|
|
615
|
-
for (const result of vectorResults) {
|
|
616
|
-
merged.set(result.id, result);
|
|
617
|
-
}
|
|
618
|
-
for (const result of bm25Results) {
|
|
619
|
-
if (merged.has(result.id)) {
|
|
620
|
-
const existing = merged.get(result.id);
|
|
621
|
-
existing.relevanceScore = (existing.relevanceScore + result.relevanceScore) / 2;
|
|
622
|
-
} else {
|
|
623
|
-
merged.set(result.id, result);
|
|
499
|
+
async getFormattedLongTermMemories(entityId) {
|
|
500
|
+
const memories = await this.getLongTermMemories(entityId, undefined, 20);
|
|
501
|
+
if (memories.length === 0) {
|
|
502
|
+
return "";
|
|
624
503
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (memories.length === 0) {
|
|
632
|
-
return "";
|
|
633
|
-
}
|
|
634
|
-
const grouped = new Map;
|
|
635
|
-
for (const memory of memories) {
|
|
636
|
-
if (!grouped.has(memory.type)) {
|
|
637
|
-
grouped.set(memory.type, []);
|
|
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);
|
|
638
510
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const facts = grouped.get("SEMANTIC" /* SEMANTIC */);
|
|
644
|
-
const items = facts.map((m) => `- ${m.content} (confidence: ${m.confidence.toFixed(2)})`).join(`
|
|
645
|
-
`);
|
|
646
|
-
sections.push(`**Semantic Knowledge (Facts)**:
|
|
647
|
-
${items}`);
|
|
648
|
-
}
|
|
649
|
-
if (grouped.has("EPISODIC" /* EPISODIC */)) {
|
|
650
|
-
const episodes = grouped.get("EPISODIC" /* EPISODIC */);
|
|
651
|
-
const items = episodes.map((m) => `- ${m.content} (${m.createdAt.toLocaleDateString()})`).join(`
|
|
652
|
-
`);
|
|
653
|
-
sections.push(`**Episodic Memory (Events)**:
|
|
654
|
-
${items}`);
|
|
655
|
-
}
|
|
656
|
-
if (grouped.has("PROCEDURAL" /* PROCEDURAL */)) {
|
|
657
|
-
const skills = grouped.get("PROCEDURAL" /* PROCEDURAL */);
|
|
658
|
-
const items = skills.map((m) => `- ${m.content}`).join(`
|
|
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(`
|
|
659
515
|
`);
|
|
660
|
-
|
|
516
|
+
sections.push(`**${categoryName}**:
|
|
661
517
|
${items}`);
|
|
662
|
-
|
|
663
|
-
|
|
518
|
+
}
|
|
519
|
+
return sections.join(`
|
|
664
520
|
|
|
665
521
|
`);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// src/utils/token-counter.ts
|
|
669
|
-
function estimateTokenCount(text3) {
|
|
670
|
-
if (!text3 || text3.length === 0) {
|
|
671
|
-
return 0;
|
|
672
522
|
}
|
|
673
|
-
const charCount = text3.length;
|
|
674
|
-
const estimatedTokens = Math.ceil(charCount / 4);
|
|
675
|
-
return estimatedTokens;
|
|
676
|
-
}
|
|
677
|
-
function estimateTokenCountForArray(texts) {
|
|
678
|
-
return texts.reduce((total, text3) => total + estimateTokenCount(text3), 0);
|
|
679
523
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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;
|
|
693
540
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
541
|
+
var initialSummarizationTemplate = `# Task: Summarize Conversation
|
|
542
|
+
|
|
543
|
+
You are analyzing a conversation to create a concise summary that captures the key points, topics, and important details.
|
|
544
|
+
|
|
545
|
+
# Recent Messages
|
|
546
|
+
{{recentMessages}}
|
|
547
|
+
|
|
548
|
+
# Instructions
|
|
549
|
+
Generate a summary that:
|
|
550
|
+
1. Captures the main topics discussed
|
|
551
|
+
2. Highlights key information shared
|
|
552
|
+
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 };
|
|
702
611
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
minConfidence: 0.7,
|
|
716
|
-
enableVectorSearch: true,
|
|
717
|
-
enableBM25: true,
|
|
718
|
-
retrievalLimit: 5,
|
|
719
|
-
tokenBudget: 1000,
|
|
720
|
-
defaultDecayRates: {
|
|
721
|
-
["EPISODIC" /* EPISODIC */]: 0.05,
|
|
722
|
-
["SEMANTIC" /* SEMANTIC */]: 0.01,
|
|
723
|
-
["PROCEDURAL" /* PROCEDURAL */]: 0.02
|
|
724
|
-
},
|
|
725
|
-
enableContradictionDetection: true,
|
|
726
|
-
summarization: {
|
|
727
|
-
enabled: true,
|
|
728
|
-
messagesPerSummary: 7,
|
|
729
|
-
summariesPerLevel: 5,
|
|
730
|
-
maxDepth: 3,
|
|
731
|
-
summaryTokenBudget: 500
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
constructor(runtime) {
|
|
735
|
-
super(runtime);
|
|
736
|
-
this.config = { ...MemoryService.defaultConfig };
|
|
737
|
-
}
|
|
738
|
-
static async start(runtime) {
|
|
739
|
-
const service = new MemoryService(runtime);
|
|
740
|
-
await service.initialize(runtime);
|
|
741
|
-
return service;
|
|
742
|
-
}
|
|
743
|
-
async stop() {
|
|
744
|
-
logger4.info("MemoryService stopped");
|
|
745
|
-
}
|
|
746
|
-
async initialize(runtime) {
|
|
747
|
-
this.runtime = runtime;
|
|
748
|
-
this.loadConfiguration();
|
|
749
|
-
await this.ensureEmbeddingDimension();
|
|
750
|
-
this.longTermMemoryRepo = new LongTermMemoryRepository(runtime, this.embeddingDimension);
|
|
751
|
-
this.conversationSummaryRepo = new ConversationSummaryRepository(runtime, this.embeddingDimension);
|
|
752
|
-
logger4.info({ config: this.config }, "MemoryService initialized");
|
|
753
|
-
if (this.config.enableBM25) {
|
|
754
|
-
await this.rebuildBM25Index();
|
|
755
|
-
}
|
|
756
|
-
this.isInitialized = true;
|
|
757
|
-
}
|
|
758
|
-
loadConfiguration() {
|
|
759
|
-
const threshold = this.runtime.getSetting("MEMORY_CONSOLIDATION_THRESHOLD");
|
|
760
|
-
if (threshold) {
|
|
761
|
-
this.config.consolidationThreshold = parseInt(threshold, 10);
|
|
762
|
-
}
|
|
763
|
-
const minConfidence = this.runtime.getSetting("MEMORY_MIN_CONFIDENCE");
|
|
764
|
-
if (minConfidence) {
|
|
765
|
-
this.config.minConfidence = parseFloat(minConfidence);
|
|
766
|
-
}
|
|
767
|
-
const enableVector = this.runtime.getSetting("MEMORY_ENABLE_VECTOR_SEARCH");
|
|
768
|
-
logger4.debug({
|
|
769
|
-
enableVector,
|
|
770
|
-
type: typeof enableVector,
|
|
771
|
-
defaultValue: this.config.enableVectorSearch
|
|
772
|
-
}, "Loading MEMORY_ENABLE_VECTOR_SEARCH setting");
|
|
773
|
-
if (enableVector !== undefined && enableVector !== null && enableVector !== "") {
|
|
774
|
-
this.config.enableVectorSearch = enableVector === "true" || enableVector === true;
|
|
775
|
-
logger4.info({ enabled: this.config.enableVectorSearch }, "Vector search explicitly configured via environment variable");
|
|
776
|
-
} else {
|
|
777
|
-
logger4.info({ enabled: this.config.enableVectorSearch }, "Vector search using default configuration");
|
|
778
|
-
}
|
|
779
|
-
const enableBM25 = this.runtime.getSetting("MEMORY_ENABLE_BM25");
|
|
780
|
-
logger4.debug({
|
|
781
|
-
enableBM25,
|
|
782
|
-
type: typeof enableBM25,
|
|
783
|
-
defaultValue: this.config.enableBM25
|
|
784
|
-
}, "Loading MEMORY_ENABLE_BM25 setting");
|
|
785
|
-
if (enableBM25 !== undefined && enableBM25 !== null && enableBM25 !== "") {
|
|
786
|
-
this.config.enableBM25 = enableBM25 === "true" || enableBM25 === true;
|
|
787
|
-
logger4.info({ enabled: this.config.enableBM25 }, "BM25 search explicitly configured via environment variable");
|
|
788
|
-
} else {
|
|
789
|
-
logger4.info({ enabled: this.config.enableBM25 }, "BM25 search using default configuration");
|
|
790
|
-
}
|
|
791
|
-
const retrievalLimit = this.runtime.getSetting("MEMORY_RETRIEVAL_LIMIT");
|
|
792
|
-
if (retrievalLimit) {
|
|
793
|
-
this.config.retrievalLimit = parseInt(retrievalLimit, 10);
|
|
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;
|
|
794
624
|
}
|
|
795
|
-
const
|
|
796
|
-
if (
|
|
797
|
-
|
|
625
|
+
const memoryService = runtime.getService("memory");
|
|
626
|
+
if (!memoryService) {
|
|
627
|
+
return false;
|
|
798
628
|
}
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
if (summaryEnabled !== undefined && summaryEnabled !== null && summaryEnabled !== "") {
|
|
806
|
-
this.config.summarization.enabled = summaryEnabled === "true" || summaryEnabled === true;
|
|
807
|
-
logger4.info({ enabled: this.config.summarization.enabled }, "Summarization explicitly configured via environment variable");
|
|
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;
|
|
808
635
|
} else {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
if (messagesPerSummary) {
|
|
813
|
-
this.config.summarization.messagesPerSummary = parseInt(messagesPerSummary, 10);
|
|
814
|
-
}
|
|
815
|
-
const summariesPerLevel = this.runtime.getSetting("MEMORY_SUMMARIES_PER_LEVEL");
|
|
816
|
-
if (summariesPerLevel) {
|
|
817
|
-
this.config.summarization.summariesPerLevel = parseInt(summariesPerLevel, 10);
|
|
636
|
+
const newDialogueCount = currentDialogueCount - existingSummary.lastMessageOffset;
|
|
637
|
+
const shouldUpdate = newDialogueCount >= config.shortTermSummarizationInterval;
|
|
638
|
+
return shouldUpdate;
|
|
818
639
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
this.config.summarization.summaryTokenBudget = parseInt(summaryTokenBudget, 10);
|
|
640
|
+
},
|
|
641
|
+
handler: async (runtime, message) => {
|
|
642
|
+
const memoryService = runtime.getService("memory");
|
|
643
|
+
if (!memoryService) {
|
|
644
|
+
logger2.error("MemoryService not found");
|
|
645
|
+
return;
|
|
826
646
|
}
|
|
827
|
-
|
|
828
|
-
|
|
647
|
+
const config = memoryService.getConfig();
|
|
648
|
+
const { roomId } = message;
|
|
829
649
|
try {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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");
|
|
833
664
|
return;
|
|
834
665
|
}
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
throw new Error("Invalid embedding received from model");
|
|
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.`);
|
|
840
670
|
}
|
|
841
|
-
const
|
|
842
|
-
const
|
|
843
|
-
if (
|
|
844
|
-
|
|
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;
|
|
845
676
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
return stored;
|
|
881
|
-
}
|
|
882
|
-
async getLongTermMemory(id) {
|
|
883
|
-
return this.longTermMemoryRepo.findById(id);
|
|
884
|
-
}
|
|
885
|
-
async updateLongTermMemory(id, updates) {
|
|
886
|
-
let newEmbedding;
|
|
887
|
-
if (updates.embeddingContext !== undefined && this.embeddingDimension) {
|
|
888
|
-
try {
|
|
889
|
-
const rawEmbedding = await generateEmbedding(this.runtime, updates.embeddingContext);
|
|
890
|
-
newEmbedding = cleanEmbedding(rawEmbedding);
|
|
891
|
-
} catch (error) {
|
|
892
|
-
logger4.warn("Failed to regenerate embedding:", JSON.stringify(error));
|
|
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
|
+
});
|
|
893
710
|
}
|
|
894
|
-
|
|
895
|
-
await this.longTermMemoryRepo.update(id, updates, newEmbedding);
|
|
896
|
-
}
|
|
897
|
-
async deleteLongTermMemory(id) {
|
|
898
|
-
await this.longTermMemoryRepo.delete(id);
|
|
899
|
-
}
|
|
900
|
-
async getLongTermMemories(entityId, type, limit = 20, includeInactive = false) {
|
|
901
|
-
return this.longTermMemoryRepo.findByEntity(entityId, type, limit, includeInactive);
|
|
902
|
-
}
|
|
903
|
-
async handleContradiction(entityId, newMemory) {
|
|
904
|
-
const searchResults = await this.searchLongTermMemories({
|
|
905
|
-
entityId,
|
|
906
|
-
query: newMemory.content,
|
|
907
|
-
type: newMemory.type,
|
|
908
|
-
limit: 5,
|
|
909
|
-
includeInactive: false
|
|
910
|
-
});
|
|
911
|
-
if (searchResults.length === 0) {
|
|
912
|
-
await this.storeLongTermMemory(newMemory);
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
const contradictingMemory = await this.detectContradiction(newMemory, searchResults);
|
|
916
|
-
if (contradictingMemory) {
|
|
917
|
-
logger4.info({
|
|
918
|
-
oldMemoryId: contradictingMemory.id,
|
|
919
|
-
newContent: newMemory.content
|
|
920
|
-
}, "Contradiction detected, superseding old memory");
|
|
921
|
-
await this.updateLongTermMemory(contradictingMemory.id, {
|
|
922
|
-
isActive: false
|
|
923
|
-
});
|
|
924
|
-
const stored = await this.storeLongTermMemory({
|
|
925
|
-
...newMemory,
|
|
926
|
-
supersedesId: contradictingMemory.id
|
|
927
|
-
});
|
|
928
|
-
logger4.info({ newMemoryId: stored.id }, "Stored superseding memory");
|
|
929
|
-
} else {
|
|
930
|
-
await this.storeLongTermMemory(newMemory);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
async detectContradiction(newMemory, existingMemories) {
|
|
934
|
-
const prompt = `# TASK: Contradiction Detection
|
|
935
|
-
|
|
936
|
-
You are analyzing whether a new long-term memory contradicts any existing long-term memories.
|
|
937
|
-
|
|
938
|
-
## New Long-Term Memory:
|
|
939
|
-
"${newMemory.content}"
|
|
940
|
-
|
|
941
|
-
## Existing Long-Term Memories:
|
|
942
|
-
${existingMemories.map((m, idx) => `${idx + 1}. "${m.content}" (confidence: ${m.confidence}, created: ${m.createdAt.toISOString()})`).join(`
|
|
943
|
-
`)}
|
|
944
|
-
|
|
945
|
-
## Instructions:
|
|
946
|
-
Determine if the new long-term memory directly contradicts any of the existing long-term memories. A contradiction means the statements cannot both be true.
|
|
947
|
-
|
|
948
|
-
Examples of contradictions:
|
|
949
|
-
- "User likes blue" vs "User hates blue"
|
|
950
|
-
- "User lives in Paris" vs "User lives in London"
|
|
951
|
-
|
|
952
|
-
Examples of non-contradictions (these are compatible):
|
|
953
|
-
- "User likes blue" vs "User likes blue only for clothes" (nuance, not contradiction)
|
|
954
|
-
- "User was in Paris" vs "User moved to London" (state change over time)
|
|
955
|
-
|
|
956
|
-
## Output Format:
|
|
957
|
-
Return an XML response:
|
|
958
|
-
|
|
959
|
-
<response>
|
|
960
|
-
<hasContradiction>true or false</hasContradiction>
|
|
961
|
-
<contradictingMemoryIndex>number or null</contradictingMemoryIndex>
|
|
962
|
-
<reasoning>Explanation</reasoning>
|
|
963
|
-
</response>
|
|
964
|
-
|
|
965
|
-
If no contradiction is found, set hasContradiction to false and contradictingMemoryIndex to null.`;
|
|
966
|
-
try {
|
|
967
|
-
const response = await this.runtime.useModel(ModelType2.TEXT_LARGE, {
|
|
711
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
968
712
|
prompt,
|
|
969
|
-
|
|
713
|
+
maxTokens: config.summaryMaxTokens || 2500
|
|
970
714
|
});
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
return contradictingMemory;
|
|
989
|
-
}
|
|
990
|
-
return null;
|
|
991
|
-
} catch (error) {
|
|
992
|
-
logger4.error({ error }, "Failed to detect contradiction");
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
async searchLongTermMemories(params) {
|
|
997
|
-
const limit = params.limit || this.config.retrievalLimit;
|
|
998
|
-
const tokenBudget = params.tokenBudget || this.config.tokenBudget;
|
|
999
|
-
const minConfidence = params.minConfidence !== undefined ? params.minConfidence : this.config.minConfidence;
|
|
1000
|
-
const similarityThreshold = params.similarityThreshold !== undefined ? params.similarityThreshold : 0.3;
|
|
1001
|
-
logger4.debug({
|
|
1002
|
-
limit,
|
|
1003
|
-
tokenBudget,
|
|
1004
|
-
minConfidence,
|
|
1005
|
-
similarityThreshold,
|
|
1006
|
-
vectorSearchEnabled: this.config.enableVectorSearch,
|
|
1007
|
-
bm25Enabled: this.config.enableBM25
|
|
1008
|
-
}, "Searching long-term memories");
|
|
1009
|
-
let vectorResults = [];
|
|
1010
|
-
if (this.config.enableVectorSearch) {
|
|
1011
|
-
logger4.debug("Vector search enabled, searching...");
|
|
1012
|
-
vectorResults = await this.vectorSearch(params, similarityThreshold);
|
|
1013
|
-
}
|
|
1014
|
-
logger4.debug({ vectorResults: vectorResults.length }, "Vector search results");
|
|
1015
|
-
let bm25Results = [];
|
|
1016
|
-
if (this.config.enableBM25) {
|
|
1017
|
-
bm25Results = await this.bm25Search(params);
|
|
1018
|
-
}
|
|
1019
|
-
const mergedResults = mergeSearchResults(vectorResults, bm25Results);
|
|
1020
|
-
const confidenceFiltered = mergedResults.filter((m) => m.confidence >= minConfidence);
|
|
1021
|
-
const decayedResults = applyDecayScoring(confidenceFiltered);
|
|
1022
|
-
const sorted = decayedResults.sort((a, b) => b.finalScore - a.finalScore);
|
|
1023
|
-
const topResults = sorted.slice(0, limit);
|
|
1024
|
-
const budgetedResults = trimToTokenBudget(topResults, tokenBudget, (memory) => memory.content, 15);
|
|
1025
|
-
logger4.debug({
|
|
1026
|
-
totalResults: sorted.length,
|
|
1027
|
-
afterCountLimit: topResults.length,
|
|
1028
|
-
afterTokenBudget: budgetedResults.length,
|
|
1029
|
-
tokenBudget,
|
|
1030
|
-
estimatedTokens: budgetedResults.reduce((sum, m) => sum + estimateTokenCount(m.content) + 15, 0)
|
|
1031
|
-
}, "Applied token budget to memory retrieval");
|
|
1032
|
-
await this.longTermMemoryRepo.updateAccessMetadata(budgetedResults.map((r) => r.id));
|
|
1033
|
-
return budgetedResults;
|
|
1034
|
-
}
|
|
1035
|
-
async vectorSearch(params, similarityThreshold = 0.3) {
|
|
1036
|
-
if (!this.embeddingDimension) {
|
|
1037
|
-
logger4.warn("Embedding dimension not set, skipping vector search");
|
|
1038
|
-
return [];
|
|
1039
|
-
}
|
|
1040
|
-
try {
|
|
1041
|
-
const rawEmbedding = await generateEmbedding(this.runtime, params.query);
|
|
1042
|
-
const queryEmbedding = cleanEmbedding(rawEmbedding);
|
|
1043
|
-
if (!queryEmbedding || !Array.isArray(queryEmbedding)) {
|
|
1044
|
-
logger4.warn("Failed to generate query embedding");
|
|
1045
|
-
return [];
|
|
1046
|
-
}
|
|
1047
|
-
return await this.longTermMemoryRepo.vectorSearch(params, queryEmbedding, similarityThreshold);
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
logger4.error("Failed to execute vector search:", JSON.stringify(error));
|
|
1050
|
-
return [];
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
async bm25Search(params) {
|
|
1054
|
-
if (!this.bm25Index) {
|
|
1055
|
-
return [];
|
|
1056
|
-
}
|
|
1057
|
-
try {
|
|
1058
|
-
const bm25Results = this.bm25Index.search(params.query, params.limit || 20);
|
|
1059
|
-
const memoryPromises = bm25Results.map(async (result) => {
|
|
1060
|
-
const doc = this.bm25Index.documents[result.index];
|
|
1061
|
-
if (!doc || !doc.id) {
|
|
1062
|
-
logger4.warn({ resultIndex: result.index }, "BM25 result has no document ID");
|
|
1063
|
-
return null;
|
|
1064
|
-
}
|
|
1065
|
-
const memory = await this.getLongTermMemory(doc.id);
|
|
1066
|
-
if (!memory)
|
|
1067
|
-
return null;
|
|
1068
|
-
if (memory.entityId !== params.entityId)
|
|
1069
|
-
return null;
|
|
1070
|
-
if (!params.includeInactive && !memory.isActive)
|
|
1071
|
-
return null;
|
|
1072
|
-
if (params.type && memory.type !== params.type)
|
|
1073
|
-
return null;
|
|
1074
|
-
if (params.minConfidence && memory.confidence < params.minConfidence)
|
|
1075
|
-
return null;
|
|
1076
|
-
return {
|
|
1077
|
-
...memory,
|
|
1078
|
-
relevanceScore: result.score,
|
|
1079
|
-
activationScore: 0,
|
|
1080
|
-
finalScore: 0
|
|
1081
|
-
};
|
|
1082
|
-
});
|
|
1083
|
-
const results = await Promise.all(memoryPromises);
|
|
1084
|
-
return results.filter((r) => r !== null);
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
logger4.error({ error }, "BM25 search failed");
|
|
1087
|
-
return [];
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
async rebuildBM25Index() {
|
|
1091
|
-
try {
|
|
1092
|
-
const memories = await this.longTermMemoryRepo.fetchAllActive();
|
|
1093
|
-
const documents = memories.map((row) => ({
|
|
1094
|
-
id: row.id,
|
|
1095
|
-
content: row.content,
|
|
1096
|
-
embeddingContext: row.embeddingContext
|
|
1097
|
-
}));
|
|
1098
|
-
this.bm25Index = new BM25(documents, {
|
|
1099
|
-
k1: 1.2,
|
|
1100
|
-
b: 0.75,
|
|
1101
|
-
stemming: true,
|
|
1102
|
-
minLength: 2
|
|
1103
|
-
});
|
|
1104
|
-
logger4.info({ documentCount: documents.length }, "Rebuilt BM25 index");
|
|
1105
|
-
} catch (error) {
|
|
1106
|
-
logger4.error({ error }, "Failed to rebuild BM25 index");
|
|
1107
|
-
this.bm25Index = null;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
async getFormattedLongTermMemoriesForContext(entityId, query, roomId) {
|
|
1111
|
-
const memories = await this.searchLongTermMemories({
|
|
1112
|
-
entityId,
|
|
1113
|
-
query,
|
|
1114
|
-
roomId,
|
|
1115
|
-
limit: this.config.retrievalLimit
|
|
1116
|
-
});
|
|
1117
|
-
return formatMemoriesForContext(memories);
|
|
1118
|
-
}
|
|
1119
|
-
async storeSummary(summary) {
|
|
1120
|
-
let embedding;
|
|
1121
|
-
if (this.embeddingDimension) {
|
|
1122
|
-
try {
|
|
1123
|
-
const rawEmbedding = await generateEmbedding(this.runtime, summary.content);
|
|
1124
|
-
embedding = cleanEmbedding(rawEmbedding);
|
|
1125
|
-
} catch (error) {
|
|
1126
|
-
logger4.warn("Failed to generate embedding for summary:", JSON.stringify(error));
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
return this.conversationSummaryRepo.insert(summary, embedding);
|
|
1130
|
-
}
|
|
1131
|
-
async getSummariesByLevel(roomId, level) {
|
|
1132
|
-
return this.conversationSummaryRepo.findByLevel(roomId, level);
|
|
1133
|
-
}
|
|
1134
|
-
async searchSummaries(params) {
|
|
1135
|
-
if (!this.embeddingDimension) {
|
|
1136
|
-
logger4.warn("Embedding dimension not set, skipping summary search");
|
|
1137
|
-
return [];
|
|
1138
|
-
}
|
|
1139
|
-
const limit = params.limit || 5;
|
|
1140
|
-
const tokenBudget = params.tokenBudget || this.config.summarization?.summaryTokenBudget || 500;
|
|
1141
|
-
try {
|
|
1142
|
-
const rawEmbedding = await generateEmbedding(this.runtime, params.query);
|
|
1143
|
-
const queryEmbedding = cleanEmbedding(rawEmbedding);
|
|
1144
|
-
if (!queryEmbedding || !Array.isArray(queryEmbedding)) {
|
|
1145
|
-
logger4.warn("Failed to generate query embedding");
|
|
1146
|
-
return [];
|
|
1147
|
-
}
|
|
1148
|
-
const summaries = await this.conversationSummaryRepo.vectorSearch(params.entityId, params.roomId, queryEmbedding, limit);
|
|
1149
|
-
const budgetedSummaries = trimToTokenBudget(summaries, tokenBudget, (s) => s.content, 10);
|
|
1150
|
-
await this.conversationSummaryRepo.updateAccessMetadata(budgetedSummaries.map((s) => s.id));
|
|
1151
|
-
return budgetedSummaries;
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
logger4.error("Failed to search summaries:", JSON.stringify(error));
|
|
1154
|
-
return [];
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
async getMostRecentLevel1Summary(roomId, entityId) {
|
|
1158
|
-
try {
|
|
1159
|
-
const summaries = await this.conversationSummaryRepo.findByLevel(roomId, 1);
|
|
1160
|
-
if (summaries.length === 0) {
|
|
1161
|
-
return null;
|
|
1162
|
-
}
|
|
1163
|
-
const entitySummaries = summaries.filter((s) => s.entityId === entityId).sort((a, b) => b.endTime.getTime() - a.endTime.getTime());
|
|
1164
|
-
return entitySummaries.length > 0 ? entitySummaries[0] : null;
|
|
1165
|
-
} catch (error) {
|
|
1166
|
-
logger4.error("Failed to get most recent Level 1 summary:", JSON.stringify(error));
|
|
1167
|
-
return null;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// src/evaluators/consolidation.ts
|
|
1173
|
-
import {
|
|
1174
|
-
logger as logger5,
|
|
1175
|
-
ModelType as ModelType3
|
|
1176
|
-
} from "@elizaos/core";
|
|
1177
|
-
|
|
1178
|
-
// src/prompts/consolidation.ts
|
|
1179
|
-
var CONSOLIDATION_SYSTEM_PROMPT = `You are the "Cortex" — an advanced Memory Extraction Engine.
|
|
1180
|
-
Your function is to parse conversation logs and extract persistent facts into a structured database format.
|
|
1181
|
-
|
|
1182
|
-
# CORE DIRECTIVE: "Subject-First" Extraction
|
|
1183
|
-
You must rephrase memories to focus on the *topic*, not the user. This optimizes vector retrieval.
|
|
1184
|
-
- BAD: "User likes to trade Bitcoin." (Too generic)
|
|
1185
|
-
- GOOD: "Bitcoin (BTC) is a preferred trading asset." (Topic-focused)
|
|
1186
|
-
|
|
1187
|
-
# COMPRESSION RULES (CRITICAL)
|
|
1188
|
-
1. **Aggressive Filtering**: Most user chatter is noise. If it won't be relevant in 30 days, DO NOT extract it.
|
|
1189
|
-
2. **Merge & Dedupe**: Do not create three separate memories for one topic. Combine them.
|
|
1190
|
-
- *Input:* "I like Red. I also like Blue. And Green."
|
|
1191
|
-
- *Output:* "Red, Blue, and Green are the preferred colors."
|
|
1192
|
-
3. **Conflict Resolution**: If a new fact contradicts an old one, mark 'isContradiction' as true.
|
|
1193
|
-
|
|
1194
|
-
# OUTPUT FORMAT
|
|
1195
|
-
Phase 1: [ANALYSIS]
|
|
1196
|
-
- List extracted points.
|
|
1197
|
-
- MARK items as [TRANSIENT] (Ignore) or [MERGE] (Combine).
|
|
1198
|
-
- Refine the final wording.
|
|
1199
|
-
|
|
1200
|
-
Phase 2: [MEMORIES]
|
|
1201
|
-
Format: \`MEM|TYPE|CATEGORY|CONFIDENCE|IS_CONTRADICTION|CONTENT\`
|
|
1202
|
-
|
|
1203
|
-
Types: EPISODIC, SEMANTIC, PROCEDURAL
|
|
1204
|
-
Categories: bio, health, finance, preferences, relationships, skills, work
|
|
1205
|
-
`;
|
|
1206
|
-
function buildExtractionPrompt(conversationLog) {
|
|
1207
|
-
const refDate = new Date().toISOString();
|
|
1208
|
-
return `# INPUT DATA
|
|
1209
|
-
**Reference Date:** ${refDate} (Use this to resolve relative dates like "yesterday" or "next Friday")
|
|
1210
|
-
|
|
1211
|
-
<conversation_log>
|
|
1212
|
-
${conversationLog}
|
|
1213
|
-
</conversation_log>
|
|
1214
|
-
|
|
1215
|
-
# FEW-SHOT EXAMPLES (DENSE INPUTS)
|
|
1216
|
-
|
|
1217
|
-
<example_1_finance_consolidation>
|
|
1218
|
-
Input: "Okay, market is looking bad. I'm closing my ETH long. Too risky. Also, can you check the weather in Tokyo? I might fly there. Actually, cancel all my limit orders on Solana too, I want to go all cash for the weekend."
|
|
1219
|
-
Output:
|
|
1220
|
-
[ANALYSIS]
|
|
1221
|
-
- "Market looking bad" -> Context, not memory.
|
|
1222
|
-
- "Closing ETH long" -> Actionable preference change.
|
|
1223
|
-
- "Check weather" -> [TRANSIENT] Ignore.
|
|
1224
|
-
- "Fly to Tokyo" -> [TRANSIENT] "Might" implies uncertainty. Ignore until confirmed.
|
|
1225
|
-
- "Cancel SOL orders" -> Actionable strategy.
|
|
1226
|
-
- "Go all cash" -> High-level strategy.
|
|
1227
|
-
- MERGE: Combine ETH close, SOL cancel, and Cash strategy into one record.
|
|
1228
|
-
[MEMORIES]
|
|
1229
|
-
MEM|PROCEDURAL|finance|0.95|true|Portfolio Strategy: All positions (ETH, SOL) liquidated; Cash-only stance adopted for weekend.
|
|
1230
|
-
</example_1_finance_consolidation>
|
|
1231
|
-
|
|
1232
|
-
<example_2_companion_emotional>
|
|
1233
|
-
Input: "I had a huge fight with my sister, Jenny. She's so controlling. I don't want to talk about her anymore. Let's play a game. Maybe chess? Actually no, I hate chess, it's boring. Let's do a quiz."
|
|
1234
|
-
Output:
|
|
1235
|
-
[ANALYSIS]
|
|
1236
|
-
- "Fight with sister Jenny" -> Relationship dynamic.
|
|
1237
|
-
- "She's controlling" -> Character attribute.
|
|
1238
|
-
- "Don't want to talk about her" -> Boundary/Preference.
|
|
1239
|
-
- "Play a game" -> [TRANSIENT] Immediate desire.
|
|
1240
|
-
- "Hate chess" -> Negative Preference.
|
|
1241
|
-
- "Do a quiz" -> [TRANSIENT] Immediate desire.
|
|
1242
|
-
- MERGE: Combine Jenny details. Separate Chess preference.
|
|
1243
|
-
[MEMORIES]
|
|
1244
|
-
MEM|EPISODIC|relationships|0.9|false|Jenny (sister) is characterized as controlling; currently a sensitive topic to be avoided.
|
|
1245
|
-
MEM|SEMANTIC|preferences|0.95|false|Chess is a disliked activity (described as boring).
|
|
1246
|
-
</example_2_companion_emotional>
|
|
1247
|
-
|
|
1248
|
-
<example_3_coding_stack>
|
|
1249
|
-
Input: "This node_modules folder is huge. I'm done with NPM. From now on we only use Bun for all projects. It's faster. Also, help me debug this loop. It's printing 'undefined'."
|
|
1250
|
-
Output:
|
|
1251
|
-
[ANALYSIS]
|
|
1252
|
-
- "node_modules huge" -> Rationale.
|
|
1253
|
-
- "Done with NPM" -> Deprecation.
|
|
1254
|
-
- "Use Bun" -> New Standard.
|
|
1255
|
-
- "Debug this loop" -> [TRANSIENT] Immediate task.
|
|
1256
|
-
- MERGE: Bun adoption and NPM rejection.
|
|
1257
|
-
[MEMORIES]
|
|
1258
|
-
MEM|PROCEDURAL|skills|0.95|true|Bun is the mandated package manager; NPM usage is deprecated/forbidden.
|
|
1259
|
-
</example_3_coding_stack>
|
|
1260
|
-
|
|
1261
|
-
<example_4_health_routine>
|
|
1262
|
-
Input: "I ate a burger today, felt kinda heavy. I think I'm going to start intermittent fasting. 16/8 window. Start eating at 12pm, stop at 8pm. Remind me to drink water."
|
|
1263
|
-
Output:
|
|
1264
|
-
[ANALYSIS]
|
|
1265
|
-
- "Ate a burger" -> [TRANSIENT] One-off meal.
|
|
1266
|
-
- "Felt heavy" -> [TRANSIENT] Temporary sensation.
|
|
1267
|
-
- "Start intermittent fasting" -> New Health Protocol.
|
|
1268
|
-
- "16/8 window, 12-8" -> Specific details of protocol.
|
|
1269
|
-
- "Remind me to drink water" -> [TRANSIENT] Command.
|
|
1270
|
-
- MERGE: All fasting details into one concise protocol.
|
|
1271
|
-
[MEMORIES]
|
|
1272
|
-
MEM|PROCEDURAL|health|0.9|false|Intermittent Fasting (16/8 protocol) adopted: Eating window restricted to 12pm-8pm.
|
|
1273
|
-
</example_4_health_routine>
|
|
1274
|
-
|
|
1275
|
-
<example_5_work_milestones>
|
|
1276
|
-
Input: "Meeting went well. The client, Apex Corp, agreed to the $50k budget. But they want the deadline moved to March 1st. Can you write a thank you note? Oh, and I need to update my resume."
|
|
1277
|
-
Output:
|
|
1278
|
-
[ANALYSIS]
|
|
1279
|
-
- "Meeting went well" -> [TRANSIENT] Sentiment.
|
|
1280
|
-
- "Apex Corp" -> Client Entity.
|
|
1281
|
-
- "$50k budget" -> Financial Fact.
|
|
1282
|
-
- "Deadline March 1st" -> Project Constraint.
|
|
1283
|
-
- "Write note" -> [TRANSIENT] Task.
|
|
1284
|
-
- "Update resume" -> [TRANSIENT] Generic task unless specific details given.
|
|
1285
|
-
- MERGE: Client details, budget, and deadline.
|
|
1286
|
-
[MEMORIES]
|
|
1287
|
-
MEM|SEMANTIC|work|1.0|false|Apex Corp project secured: $50k budget with March 1st deadline.
|
|
1288
|
-
</example_5_work_milestones>
|
|
1289
|
-
|
|
1290
|
-
# EXTRACTION CHECKLIST
|
|
1291
|
-
1. **Search for STATE CHANGES**: Did the user move, change jobs, break up, or alter a portfolio? These are high-value.
|
|
1292
|
-
2. **Search for HARD CONSTRAINTS**: Look for phrases like "Never do X", "Always use Y", "I hate Z".
|
|
1293
|
-
3. **COMPRESSION**:
|
|
1294
|
-
- You have received a long conversation.
|
|
1295
|
-
- **MERGE** related details into single, dense records.
|
|
1296
|
-
- **IGNORE** all small talk, greetings, and transient requests.
|
|
1297
|
-
4. **QUALITY CONTROL**: If a fact feels temporary or weak, **DO NOT** extract it. Silence is better than noise.
|
|
1298
|
-
|
|
1299
|
-
Begin the [ANALYSIS] phase now.`;
|
|
1300
|
-
}
|
|
1301
|
-
function buildContradictionPrompt(newMemoryContent, existingMemories) {
|
|
1302
|
-
return `Does this new memory contradict any existing memories?
|
|
1303
|
-
|
|
1304
|
-
New: "${newMemoryContent}"
|
|
1305
|
-
|
|
1306
|
-
Existing:
|
|
1307
|
-
${existingMemories.map((m, idx) => `${idx + 1}. "${m.content}" (confidence: ${m.confidence})`).join(`
|
|
1308
|
-
`)}
|
|
1309
|
-
|
|
1310
|
-
A contradiction means both statements cannot be true simultaneously.
|
|
1311
|
-
|
|
1312
|
-
TRUE contradictions:
|
|
1313
|
-
- "User likes blue" vs "User hates blue"
|
|
1314
|
-
- "User lives in Paris" vs "User lives in London"
|
|
1315
|
-
- "User is vegetarian" vs "User eats meat"
|
|
1316
|
-
|
|
1317
|
-
NOT contradictions:
|
|
1318
|
-
- "User likes blue" vs "User likes blue for clothes" (nuance)
|
|
1319
|
-
- "User was in Paris" vs "User moved to London" (time change)
|
|
1320
|
-
- "User likes Python" vs "User likes JavaScript" (not exclusive)
|
|
1321
|
-
|
|
1322
|
-
<response>
|
|
1323
|
-
<hasContradiction>true or false</hasContradiction>
|
|
1324
|
-
<contradictingIndex>number or null</contradictingIndex>
|
|
1325
|
-
<reasoning>Brief explanation</reasoning>
|
|
1326
|
-
</response>`;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
// src/evaluators/consolidation.ts
|
|
1330
|
-
var consolidationBuffers = new Map;
|
|
1331
|
-
var consolidationEvaluator = {
|
|
1332
|
-
name: "CONSOLIDATION",
|
|
1333
|
-
similes: ["MEMORY_CONSOLIDATION", "EXTRACT_FACTS", "MEMORY_BUFFER"],
|
|
1334
|
-
description: "Buffers conversation messages and performs periodic consolidation to extract persistent facts " + "using LLM analysis. Distinguishes transient intents from long-term knowledge.",
|
|
1335
|
-
validate: async (runtime, message) => {
|
|
1336
|
-
if (!consolidationBuffers.has(message.roomId)) {
|
|
1337
|
-
consolidationBuffers.set(message.roomId, []);
|
|
1338
|
-
}
|
|
1339
|
-
consolidationBuffers.get(message.roomId).push(message);
|
|
1340
|
-
const memoryService = runtime.getService("memory");
|
|
1341
|
-
if (!memoryService) {
|
|
1342
|
-
return false;
|
|
1343
|
-
}
|
|
1344
|
-
const config = memoryService.getConfig();
|
|
1345
|
-
const bufferSize = consolidationBuffers.get(message.roomId).length;
|
|
1346
|
-
const shouldConsolidate = bufferSize >= config.consolidationThreshold;
|
|
1347
|
-
if (shouldConsolidate) {
|
|
1348
|
-
logger5.info({
|
|
1349
|
-
roomId: message.roomId,
|
|
1350
|
-
bufferSize,
|
|
1351
|
-
threshold: config.consolidationThreshold
|
|
1352
|
-
}, "Consolidation threshold reached");
|
|
1353
|
-
}
|
|
1354
|
-
return shouldConsolidate;
|
|
1355
|
-
},
|
|
1356
|
-
handler: async (runtime, message) => {
|
|
1357
|
-
const roomId = message.roomId;
|
|
1358
|
-
try {
|
|
1359
|
-
const memoryService = runtime.getService("memory");
|
|
1360
|
-
if (!memoryService) {
|
|
1361
|
-
logger5.warn("Memory service not available for consolidation");
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
const buffer = consolidationBuffers.get(roomId) || [];
|
|
1365
|
-
if (buffer.length === 0) {
|
|
1366
|
-
return;
|
|
1367
|
-
}
|
|
1368
|
-
logger5.info({ roomId, messageCount: buffer.length }, "Starting memory consolidation");
|
|
1369
|
-
const conversationLog = buffer.map((m, idx) => {
|
|
1370
|
-
const content = typeof m.content === "string" ? m.content : m.content.text || JSON.stringify(m.content);
|
|
1371
|
-
return `[${idx + 1}] ${m.entityId}: ${content}`;
|
|
1372
|
-
}).join(`
|
|
1373
|
-
`);
|
|
1374
|
-
const extractionPrompt = buildExtractionPrompt(conversationLog);
|
|
1375
|
-
const originalSystemPrompt = runtime.character.system;
|
|
1376
|
-
try {
|
|
1377
|
-
runtime.character.system = CONSOLIDATION_SYSTEM_PROMPT;
|
|
1378
|
-
logger5.debug("Calling LLM for memory extraction");
|
|
1379
|
-
const response = await runtime.useModel(ModelType3.TEXT_LARGE, {
|
|
1380
|
-
prompt: extractionPrompt,
|
|
1381
|
-
temperature: 0.3
|
|
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
|
+
}
|
|
1382
732
|
});
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
} finally {
|
|
1401
|
-
runtime.character.system = originalSystemPrompt;
|
|
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})`);
|
|
1402
750
|
}
|
|
1403
751
|
} catch (error) {
|
|
1404
|
-
|
|
752
|
+
logger2.error({ error }, "Error during summarization:");
|
|
1405
753
|
}
|
|
1406
754
|
},
|
|
1407
755
|
examples: []
|
|
1408
756
|
};
|
|
1409
|
-
function parseConsolidationResult(response) {
|
|
1410
|
-
try {
|
|
1411
|
-
const responseText = typeof response === "string" ? response : JSON.stringify(response);
|
|
1412
|
-
const analysisMatch = responseText.match(/\[ANALYSIS\](.*?)(?:\[MEMORIES\]|$)/s);
|
|
1413
|
-
const reasoningTrace = analysisMatch ? analysisMatch[1].trim() : "";
|
|
1414
|
-
const memoriesMatch = responseText.match(/\[MEMORIES\](.*?)$/s);
|
|
1415
|
-
const memoriesText = memoriesMatch ? memoriesMatch[1].trim() : "";
|
|
1416
|
-
const extractedMemories = [];
|
|
1417
|
-
if (memoriesText) {
|
|
1418
|
-
const lines = memoriesText.split(`
|
|
1419
|
-
`);
|
|
1420
|
-
for (const line of lines) {
|
|
1421
|
-
const trimmedLine = line.trim();
|
|
1422
|
-
if (!trimmedLine || !trimmedLine.startsWith("MEM|")) {
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
|
-
const parts = trimmedLine.split("|");
|
|
1426
|
-
if (parts.length < 6) {
|
|
1427
|
-
logger5.warn({ line: trimmedLine }, "Skipping malformed memory line (insufficient fields)");
|
|
1428
|
-
continue;
|
|
1429
|
-
}
|
|
1430
|
-
const type = parts[1].trim();
|
|
1431
|
-
const category = parts[2].trim();
|
|
1432
|
-
const confidenceStr = parts[3].trim();
|
|
1433
|
-
const isContradictionStr = parts[4].trim();
|
|
1434
|
-
const content = parts.slice(5).join("|").trim();
|
|
1435
|
-
if (!["EPISODIC", "SEMANTIC", "PROCEDURAL"].includes(type)) {
|
|
1436
|
-
logger5.warn({ type, line: trimmedLine }, "Invalid memory type");
|
|
1437
|
-
continue;
|
|
1438
|
-
}
|
|
1439
|
-
if (!category) {
|
|
1440
|
-
logger5.warn({ line: trimmedLine }, "Missing category");
|
|
1441
|
-
continue;
|
|
1442
|
-
}
|
|
1443
|
-
const confidence = parseFloat(confidenceStr);
|
|
1444
|
-
if (isNaN(confidence) || confidence < 0 || confidence > 1) {
|
|
1445
|
-
logger5.warn({ confidenceStr, line: trimmedLine }, "Invalid confidence value");
|
|
1446
|
-
continue;
|
|
1447
|
-
}
|
|
1448
|
-
const isContradiction = isContradictionStr.toLowerCase() === "true";
|
|
1449
|
-
if (!content) {
|
|
1450
|
-
logger5.warn({ line: trimmedLine }, "Missing content");
|
|
1451
|
-
continue;
|
|
1452
|
-
}
|
|
1453
|
-
const metadata = {
|
|
1454
|
-
category
|
|
1455
|
-
};
|
|
1456
|
-
extractedMemories.push({
|
|
1457
|
-
type,
|
|
1458
|
-
content,
|
|
1459
|
-
confidence,
|
|
1460
|
-
isContradiction,
|
|
1461
|
-
metadata
|
|
1462
|
-
});
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
return {
|
|
1466
|
-
reasoningTrace,
|
|
1467
|
-
transientSummary: "",
|
|
1468
|
-
extractedMemories
|
|
1469
|
-
};
|
|
1470
|
-
} catch (error) {
|
|
1471
|
-
logger5.error({ error }, "Failed to parse consolidation response");
|
|
1472
|
-
return {
|
|
1473
|
-
reasoningTrace: "Parse error",
|
|
1474
|
-
transientSummary: "",
|
|
1475
|
-
extractedMemories: []
|
|
1476
|
-
};
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
async function storeExtractedMemory(runtime, memoryService, roomId, buffer, extracted) {
|
|
1480
|
-
const entityId = buffer[0]?.entityId || runtime.agentId;
|
|
1481
|
-
const embeddingContext = generateContextualString(extracted);
|
|
1482
|
-
const config = memoryService.getConfig();
|
|
1483
|
-
const decayRate = config.defaultDecayRates[extracted.type];
|
|
1484
|
-
const memoryData = {
|
|
1485
|
-
agentId: runtime.agentId,
|
|
1486
|
-
entityId,
|
|
1487
|
-
roomId,
|
|
1488
|
-
type: extracted.type,
|
|
1489
|
-
content: extracted.content,
|
|
1490
|
-
embeddingContext,
|
|
1491
|
-
confidence: extracted.confidence,
|
|
1492
|
-
decayRate,
|
|
1493
|
-
decayFunction: "EXPONENTIAL" /* EXPONENTIAL */,
|
|
1494
|
-
source: {
|
|
1495
|
-
sessionId: roomId,
|
|
1496
|
-
messageId: extracted.sourceMessageId,
|
|
1497
|
-
textSnippet: extracted.content.substring(0, 200)
|
|
1498
|
-
},
|
|
1499
|
-
metadata: extracted.metadata || {}
|
|
1500
|
-
};
|
|
1501
|
-
if (config.enableContradictionDetection && extracted.isContradiction) {
|
|
1502
|
-
await memoryService.handleContradiction(entityId, memoryData);
|
|
1503
|
-
} else {
|
|
1504
|
-
await memoryService.storeLongTermMemory(memoryData);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
function generateContextualString(extracted) {
|
|
1508
|
-
const typeLabel = {
|
|
1509
|
-
["EPISODIC" /* EPISODIC */]: "Event",
|
|
1510
|
-
["SEMANTIC" /* SEMANTIC */]: "Fact",
|
|
1511
|
-
["PROCEDURAL" /* PROCEDURAL */]: "Skill"
|
|
1512
|
-
}[extracted.type];
|
|
1513
|
-
const category = extracted.metadata?.category || "general";
|
|
1514
|
-
return `[${typeLabel} about ${category}]: ${extracted.content}`;
|
|
1515
|
-
}
|
|
1516
757
|
|
|
1517
|
-
// src/evaluators/
|
|
758
|
+
// src/evaluators/long-term-extraction.ts
|
|
1518
759
|
import {
|
|
1519
|
-
logger as
|
|
1520
|
-
ModelType as
|
|
760
|
+
logger as logger3,
|
|
761
|
+
ModelType as ModelType2,
|
|
762
|
+
composePromptFromState as composePromptFromState2
|
|
1521
763
|
} from "@elizaos/core";
|
|
1522
764
|
|
|
1523
|
-
// src/
|
|
1524
|
-
var
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
#
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
-
|
|
1548
|
-
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
-
|
|
1560
|
-
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
-
|
|
1581
|
-
-
|
|
1582
|
-
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
-
|
|
1616
|
-
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
- **
|
|
1634
|
-
- **
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
-
|
|
1641
|
-
-
|
|
1642
|
-
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
<
|
|
1652
|
-
|
|
1653
|
-
</
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
<
|
|
1658
|
-
|
|
1659
|
-
<
|
|
1660
|
-
<
|
|
1661
|
-
<
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
|
|
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
|
|
797
|
+
|
|
798
|
+
## 2. SEMANTIC Memory
|
|
799
|
+
General facts, concepts, knowledge, and established truths about the user.
|
|
800
|
+
**Examples:**
|
|
801
|
+
- "User is a senior backend engineer with 8 years experience"
|
|
802
|
+
- "User specializes in distributed systems and microservices architecture"
|
|
803
|
+
- "User's primary programming language is TypeScript"
|
|
804
|
+
- "User works at Acme Corp as technical lead"
|
|
805
|
+
|
|
806
|
+
**Requirements:**
|
|
807
|
+
- Must be factual, timeless information
|
|
808
|
+
- Must be explicitly stated or demonstrated conclusively
|
|
809
|
+
- No speculation or inference from single instances
|
|
810
|
+
- Core identity, expertise, or knowledge only
|
|
811
|
+
|
|
812
|
+
## 3. PROCEDURAL Memory
|
|
813
|
+
Skills, workflows, methodologies, and how-to knowledge.
|
|
814
|
+
**Examples:**
|
|
815
|
+
- "User follows strict TDD workflow: write tests first, then implementation"
|
|
816
|
+
- "User prefers git rebase over merge to maintain linear history"
|
|
817
|
+
- "User's debugging process: check logs → reproduce locally → binary search"
|
|
818
|
+
- "User always writes JSDoc comments before implementing functions"
|
|
819
|
+
|
|
820
|
+
**Requirements:**
|
|
821
|
+
- Must describe HOW user does something
|
|
822
|
+
- Must be a repeated, consistent pattern (seen 3+ times or explicitly stated as standard practice)
|
|
823
|
+
- Must be a workflow, methodology, or skill application
|
|
824
|
+
- Not one-off preferences
|
|
825
|
+
|
|
826
|
+
# ULTRA-STRICT EXTRACTION CRITERIA
|
|
827
|
+
|
|
828
|
+
## ✅ DO EXTRACT (Only These):
|
|
829
|
+
|
|
830
|
+
**EPISODIC:**
|
|
831
|
+
- Significant completed projects or milestones
|
|
832
|
+
- Important bugs, incidents, or problems encountered
|
|
833
|
+
- Major decisions made with lasting impact
|
|
834
|
+
- Formative experiences that shape future work
|
|
835
|
+
|
|
836
|
+
**SEMANTIC:**
|
|
837
|
+
- Professional identity (role, title, company)
|
|
838
|
+
- Core expertise and specializations (stated explicitly or demonstrated conclusively)
|
|
839
|
+
- Primary languages, frameworks, or tools (not exploratory use)
|
|
840
|
+
- Established facts about their work context
|
|
841
|
+
|
|
842
|
+
**PROCEDURAL:**
|
|
843
|
+
- Consistent workflows demonstrated 3+ times or explicitly stated
|
|
844
|
+
- Standard practices user always follows
|
|
845
|
+
- Methodology preferences with clear rationale
|
|
846
|
+
- Debugging, testing, or development processes
|
|
847
|
+
|
|
848
|
+
## ❌ NEVER EXTRACT:
|
|
849
|
+
|
|
850
|
+
- **One-time requests or tasks** (e.g., "can you generate an image", "help me debug this")
|
|
851
|
+
- **Casual conversations** without lasting significance
|
|
852
|
+
- **Exploratory questions** (e.g., "how does X work?")
|
|
853
|
+
- **Temporary context** (current bug, today's task)
|
|
854
|
+
- **Preferences from single occurrence** (e.g., user asked for code once)
|
|
855
|
+
- **Social pleasantries** (thank you, greetings)
|
|
856
|
+
- **Testing or experimentation** (trying out a feature)
|
|
857
|
+
- **Common patterns everyone has** (likes clear explanations)
|
|
858
|
+
- **Situational information** (working on feature X today)
|
|
859
|
+
- **Opinions without persistence** (single complaint, isolated praise)
|
|
860
|
+
- **General knowledge** (not specific to user)
|
|
861
|
+
|
|
862
|
+
# Quality Gates (ALL Must Pass)
|
|
863
|
+
|
|
864
|
+
1. **Significance Test**: Will this matter in 3+ months?
|
|
865
|
+
2. **Specificity Test**: Is this concrete and actionable?
|
|
866
|
+
3. **Evidence Test**: Is there strong evidence (3+ instances OR explicit self-identification)?
|
|
867
|
+
4. **Uniqueness Test**: Is this specific to THIS user (not generic)?
|
|
868
|
+
5. **Confidence Test**: Confidence must be >= 0.85 (be VERY conservative)
|
|
869
|
+
6. **Non-Redundancy Test**: Does this add NEW information not in existing memories?
|
|
870
|
+
|
|
871
|
+
# Confidence Scoring (Be Conservative)
|
|
872
|
+
|
|
873
|
+
- **0.95-1.0**: User explicitly stated as core identity/practice AND demonstrated multiple times
|
|
874
|
+
- **0.85-0.94**: User explicitly stated OR consistently demonstrated 5+ times
|
|
875
|
+
- **0.75-0.84**: Strong pattern (3-4 instances) with supporting context
|
|
876
|
+
- **Below 0.75**: DO NOT EXTRACT (insufficient evidence)
|
|
877
|
+
|
|
878
|
+
# Critical Instructions
|
|
879
|
+
|
|
880
|
+
1. **Default to NOT extracting** - When in doubt, skip it
|
|
881
|
+
2. **Require overwhelming evidence** - One or two mentions is NOT enough
|
|
882
|
+
3. **Focus on what's PERSISTENT** - Not what's temporary or situational
|
|
883
|
+
4. **Verify against existing memories** - Don't duplicate or contradict
|
|
884
|
+
5. **Maximum 2-3 extractions per run** - Quality over quantity
|
|
885
|
+
|
|
886
|
+
**If there are no qualifying facts (which is common), respond with <memories></memories>**
|
|
887
|
+
|
|
888
|
+
# Response Format
|
|
889
|
+
|
|
890
|
+
<memories>
|
|
891
|
+
<memory>
|
|
892
|
+
<category>semantic</category>
|
|
893
|
+
<content>User is a senior TypeScript developer with 8 years of backend experience</content>
|
|
894
|
+
<confidence>0.95</confidence>
|
|
895
|
+
</memory>
|
|
896
|
+
<memory>
|
|
897
|
+
<category>procedural</category>
|
|
898
|
+
<content>User follows TDD workflow: writes tests before implementation, runs tests after each change</content>
|
|
899
|
+
<confidence>0.88</confidence>
|
|
900
|
+
</memory>
|
|
901
|
+
<memory>
|
|
902
|
+
<category>episodic</category>
|
|
903
|
+
<content>User led database migration from MongoDB to PostgreSQL for payment system in Q2 2024</content>
|
|
904
|
+
<confidence>0.92</confidence>
|
|
905
|
+
</memory>
|
|
906
|
+
</memories>`;
|
|
907
|
+
function parseMemoryExtractionXML(xml) {
|
|
908
|
+
const memoryMatches = xml.matchAll(/<memory>[\s\S]*?<category>(.*?)<\/category>[\s\S]*?<content>(.*?)<\/content>[\s\S]*?<confidence>(.*?)<\/confidence>[\s\S]*?<\/memory>/g);
|
|
909
|
+
const extractions = [];
|
|
910
|
+
for (const match of memoryMatches) {
|
|
911
|
+
const category = match[1].trim();
|
|
912
|
+
const content = match[2].trim();
|
|
913
|
+
const confidence = parseFloat(match[3].trim());
|
|
914
|
+
if (!Object.values(LongTermMemoryCategory).includes(category)) {
|
|
915
|
+
logger3.warn(`Invalid memory category: ${category}`);
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (content && !isNaN(confidence)) {
|
|
919
|
+
extractions.push({ category, content, confidence });
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return extractions;
|
|
1676
923
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
similes: ["HIERARCHICAL_SUMMARIZATION", "EPISODIC_COMPRESSION", "CONVERSATION_SUMMARY"],
|
|
1683
|
-
description: "Hierarchical conversation summarization that compresses message history into multi-level narrative summaries " + "for token-efficient long-term episodic memory.",
|
|
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,
|
|
1684
929
|
validate: async (runtime, message) => {
|
|
930
|
+
if (message.entityId === runtime.agentId) {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
if (!message.content?.text) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
1685
936
|
const memoryService = runtime.getService("memory");
|
|
1686
937
|
if (!memoryService) {
|
|
1687
938
|
return false;
|
|
1688
939
|
}
|
|
1689
940
|
const config = memoryService.getConfig();
|
|
1690
|
-
if (!config.
|
|
1691
|
-
|
|
941
|
+
if (!config.longTermExtractionEnabled) {
|
|
942
|
+
logger3.debug("Long-term memory extraction is disabled");
|
|
1692
943
|
return false;
|
|
1693
944
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
const currentCount = summarizationCounters.get(message.roomId) + 1;
|
|
1698
|
-
summarizationCounters.set(message.roomId, currentCount);
|
|
1699
|
-
const threshold = config.summarization.messagesPerSummary;
|
|
1700
|
-
logger6.debug({
|
|
1701
|
-
currentCount,
|
|
1702
|
-
threshold,
|
|
1703
|
-
messageId: message.id,
|
|
1704
|
-
entityId: message.entityId,
|
|
1705
|
-
roomId: message.roomId
|
|
1706
|
-
}, "Message counted for summarization (user + agent messages)");
|
|
1707
|
-
const shouldSummarize = currentCount >= threshold;
|
|
1708
|
-
if (shouldSummarize) {
|
|
1709
|
-
logger6.info({
|
|
1710
|
-
roomId: message.roomId,
|
|
1711
|
-
messageCount: currentCount,
|
|
1712
|
-
threshold
|
|
1713
|
-
}, "Summarization threshold reached (Level 1) - triggering summarization");
|
|
1714
|
-
}
|
|
1715
|
-
return shouldSummarize;
|
|
945
|
+
const currentMessageCount = await runtime.countMemories(message.roomId, false, "messages");
|
|
946
|
+
const shouldRun = await memoryService.shouldRunExtraction(message.entityId, message.roomId, currentMessageCount);
|
|
947
|
+
return shouldRun;
|
|
1716
948
|
},
|
|
1717
949
|
handler: async (runtime, message) => {
|
|
1718
950
|
const memoryService = runtime.getService("memory");
|
|
1719
951
|
if (!memoryService) {
|
|
1720
|
-
|
|
952
|
+
logger3.error("MemoryService not found");
|
|
1721
953
|
return;
|
|
1722
954
|
}
|
|
1723
955
|
const config = memoryService.getConfig();
|
|
1724
|
-
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
const messageCount = summarizationCounters.get(message.roomId) || 0;
|
|
1728
|
-
if (messageCount === 0) {
|
|
1729
|
-
return;
|
|
1730
|
-
}
|
|
1731
|
-
logger6.info({
|
|
1732
|
-
roomId: message.roomId,
|
|
1733
|
-
messageCount
|
|
1734
|
-
}, "Starting Level 1 summarization - pulling messages from database");
|
|
956
|
+
const { entityId, roomId } = message;
|
|
1735
957
|
try {
|
|
1736
|
-
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
958
|
+
logger3.info(`Extracting long-term memories for entity ${entityId}`);
|
|
959
|
+
const recentMessages = await runtime.getMemories({
|
|
960
|
+
tableName: "messages",
|
|
961
|
+
roomId,
|
|
962
|
+
count: 20,
|
|
963
|
+
unique: false
|
|
964
|
+
});
|
|
965
|
+
const formattedMessages = recentMessages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0)).map((msg) => {
|
|
966
|
+
const sender = msg.entityId === runtime.agentId ? runtime.character.name : "User";
|
|
967
|
+
return `${sender}: ${msg.content.text || "[non-text message]"}`;
|
|
968
|
+
}).join(`
|
|
969
|
+
`);
|
|
970
|
+
const existingMemories = await memoryService.getLongTermMemories(entityId, undefined, 30);
|
|
971
|
+
const formattedExisting = existingMemories.length > 0 ? existingMemories.map((m) => `[${m.category}] ${m.content} (confidence: ${m.confidence})`).join(`
|
|
972
|
+
`) : "None yet";
|
|
973
|
+
const state = await runtime.composeState(message);
|
|
974
|
+
const prompt = composePromptFromState2({
|
|
975
|
+
state: {
|
|
976
|
+
...state,
|
|
977
|
+
recentMessages: formattedMessages,
|
|
978
|
+
existingMemories: formattedExisting
|
|
979
|
+
},
|
|
980
|
+
template: extractionTemplate
|
|
981
|
+
});
|
|
982
|
+
const response = await runtime.useModel(ModelType2.TEXT_LARGE, {
|
|
983
|
+
prompt
|
|
984
|
+
});
|
|
985
|
+
const extractions = parseMemoryExtractionXML(response);
|
|
986
|
+
logger3.info(`Extracted ${extractions.length} long-term memories`);
|
|
987
|
+
for (const extraction of extractions) {
|
|
988
|
+
if (extraction.confidence >= Math.max(config.longTermConfidenceThreshold, 0.85)) {
|
|
989
|
+
await memoryService.storeLongTermMemory({
|
|
990
|
+
agentId: runtime.agentId,
|
|
991
|
+
entityId,
|
|
992
|
+
category: extraction.category,
|
|
993
|
+
content: extraction.content,
|
|
994
|
+
confidence: extraction.confidence,
|
|
995
|
+
source: "conversation",
|
|
996
|
+
metadata: {
|
|
997
|
+
roomId,
|
|
998
|
+
extractedAt: new Date().toISOString()
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
logger3.info(`Stored long-term memory: [${extraction.category}] ${extraction.content.substring(0, 50)}...`);
|
|
1002
|
+
} else {
|
|
1003
|
+
logger3.debug(`Skipped low-confidence memory: ${extraction.content} (confidence: ${extraction.confidence}, threshold: ${Math.max(config.longTermConfidenceThreshold, 0.85)})`);
|
|
1004
|
+
}
|
|
1753
1005
|
}
|
|
1006
|
+
const currentMessageCount = await runtime.countMemories(roomId, false, "messages");
|
|
1007
|
+
await memoryService.setLastExtractionCheckpoint(entityId, roomId, currentMessageCount);
|
|
1008
|
+
logger3.debug(`Updated extraction checkpoint to ${currentMessageCount} for entity ${entityId} in room ${roomId}`);
|
|
1754
1009
|
} catch (error) {
|
|
1755
|
-
|
|
1756
|
-
error,
|
|
1757
|
-
roomId: message.roomId
|
|
1758
|
-
}, "Failed to create summary");
|
|
1010
|
+
logger3.error({ error }, "Error during long-term memory extraction:");
|
|
1759
1011
|
}
|
|
1760
1012
|
},
|
|
1761
1013
|
examples: []
|
|
1762
1014
|
};
|
|
1763
|
-
async function fetchMessagesSinceLastSummary(runtime, roomId, lastSummaryEndTime) {
|
|
1764
|
-
const allMessages = await runtime.getMemories({
|
|
1765
|
-
tableName: "messages",
|
|
1766
|
-
roomId,
|
|
1767
|
-
count: 1000,
|
|
1768
|
-
unique: false
|
|
1769
|
-
});
|
|
1770
|
-
const dialogueMessages = allMessages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result"));
|
|
1771
|
-
let messages = dialogueMessages;
|
|
1772
|
-
if (lastSummaryEndTime) {
|
|
1773
|
-
messages = dialogueMessages.filter((msg) => {
|
|
1774
|
-
if (!msg.createdAt)
|
|
1775
|
-
return false;
|
|
1776
|
-
const msgTime = new Date(msg.createdAt);
|
|
1777
|
-
return msgTime > lastSummaryEndTime;
|
|
1778
|
-
});
|
|
1779
|
-
}
|
|
1780
|
-
messages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
1781
|
-
logger6.debug({
|
|
1782
|
-
roomId,
|
|
1783
|
-
totalMessages: allMessages.length,
|
|
1784
|
-
dialogueMessages: dialogueMessages.length,
|
|
1785
|
-
filteredMessages: messages.length,
|
|
1786
|
-
lastSummaryEndTime: lastSummaryEndTime?.toISOString()
|
|
1787
|
-
}, "Fetched messages for summarization");
|
|
1788
|
-
return messages;
|
|
1789
|
-
}
|
|
1790
|
-
function parseSummarizationResult(response) {
|
|
1791
|
-
try {
|
|
1792
|
-
const responseText = typeof response === "string" ? response : JSON.stringify(response);
|
|
1793
|
-
const analysisMatch = responseText.match(/\[ANALYSIS\](.*?)(?:\[RESULT\]|$)/s);
|
|
1794
|
-
const reasoningTrace = analysisMatch ? analysisMatch[1].trim() : "";
|
|
1795
|
-
const resultMatch = responseText.match(/\[RESULT\](.*?)$/s);
|
|
1796
|
-
const resultText = resultMatch ? resultMatch[1].trim() : "";
|
|
1797
|
-
let summary = "";
|
|
1798
|
-
let keyTopics = "";
|
|
1799
|
-
if (resultText) {
|
|
1800
|
-
const lines = resultText.split(`
|
|
1801
|
-
`);
|
|
1802
|
-
for (const line of lines) {
|
|
1803
|
-
const trimmed = line.trim();
|
|
1804
|
-
if (trimmed.startsWith("SUMM|")) {
|
|
1805
|
-
const parts = trimmed.split("|");
|
|
1806
|
-
if (parts.length >= 3) {
|
|
1807
|
-
keyTopics = parts[1].trim();
|
|
1808
|
-
summary = parts.slice(2).join("|").trim();
|
|
1809
|
-
break;
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
return { summary, keyTopics, reasoningTrace };
|
|
1815
|
-
} catch (error) {
|
|
1816
|
-
logger6.error({ error }, "Failed to parse summarization response");
|
|
1817
|
-
return { summary: "", keyTopics: "", reasoningTrace: "" };
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
async function createLevel1Summary(runtime, memoryService, messages, roomId, previousSummary) {
|
|
1821
|
-
const formattedMessages = messages.map((m, i) => {
|
|
1822
|
-
const text3 = typeof m.content === "string" ? m.content : m.content.text || "";
|
|
1823
|
-
const author = m.entityId === runtime.agentId ? "Agent" : "User";
|
|
1824
|
-
const timestamp3 = m.createdAt ? new Date(m.createdAt).toISOString() : "Unknown time";
|
|
1825
|
-
return `[${timestamp3}] ${author}: ${text3}`;
|
|
1826
|
-
}).join(`
|
|
1827
|
-
`);
|
|
1828
|
-
let prompt;
|
|
1829
|
-
if (previousSummary) {
|
|
1830
|
-
prompt = buildLevel1SummaryPrompt(formattedMessages, previousSummary.content);
|
|
1831
|
-
} else {
|
|
1832
|
-
prompt = buildLevel1SummaryPrompt(formattedMessages);
|
|
1833
|
-
}
|
|
1834
|
-
const originalSystemPrompt = runtime.character.system;
|
|
1835
|
-
try {
|
|
1836
|
-
runtime.character.system = SUMMARIZATION_SYSTEM_PROMPT;
|
|
1837
|
-
logger6.debug("Calling LLM for Level 1 summarization with timestamped conversation log");
|
|
1838
|
-
const response = await runtime.useModel(ModelType4.TEXT_LARGE, {
|
|
1839
|
-
prompt,
|
|
1840
|
-
temperature: 0.3
|
|
1841
|
-
});
|
|
1842
|
-
runtime.character.system = originalSystemPrompt;
|
|
1843
|
-
const { summary, keyTopics, reasoningTrace } = parseSummarizationResult(response);
|
|
1844
|
-
if (!summary) {
|
|
1845
|
-
logger6.warn({ reasoningTrace }, "No summary extracted from LLM response");
|
|
1846
|
-
return null;
|
|
1847
|
-
}
|
|
1848
|
-
const tokenCount = estimateTokensInSummary(summary);
|
|
1849
|
-
const startTime = messages[0].createdAt ? new Date(messages[0].createdAt) : new Date;
|
|
1850
|
-
const endTime = messages[messages.length - 1].createdAt ? new Date(messages[messages.length - 1].createdAt) : new Date;
|
|
1851
|
-
const summaryData = {
|
|
1852
|
-
agentId: runtime.agentId,
|
|
1853
|
-
entityId: messages[0].entityId,
|
|
1854
|
-
roomId,
|
|
1855
|
-
level: 1,
|
|
1856
|
-
parentSummaryId: undefined,
|
|
1857
|
-
content: summary,
|
|
1858
|
-
tokenCount,
|
|
1859
|
-
startTime,
|
|
1860
|
-
endTime,
|
|
1861
|
-
sourceCount: messages.length,
|
|
1862
|
-
sourceIds: messages.map((m) => m.id),
|
|
1863
|
-
metadata: {
|
|
1864
|
-
keyTopics,
|
|
1865
|
-
hasPreviousSummary: !!previousSummary
|
|
1866
|
-
}
|
|
1867
|
-
};
|
|
1868
|
-
return await memoryService.storeSummary(summaryData);
|
|
1869
|
-
} catch (error) {
|
|
1870
|
-
logger6.error({ error }, "Failed to call LLM for Level 1 summarization");
|
|
1871
|
-
runtime.character.system = originalSystemPrompt;
|
|
1872
|
-
return null;
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
async function checkAndTriggerHigherLevelSummarization(runtime, memoryService, roomId, currentLevel) {
|
|
1876
|
-
const config = memoryService.getConfig();
|
|
1877
|
-
if (!config.summarization) {
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
if (currentLevel >= config.summarization.maxDepth) {
|
|
1881
|
-
logger6.debug({ currentLevel, maxDepth: config.summarization.maxDepth }, "Max depth reached");
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1884
|
-
const summaries = await memoryService.getSummariesByLevel(roomId, currentLevel);
|
|
1885
|
-
const threshold = config.summarization.summariesPerLevel;
|
|
1886
|
-
if (summaries.length < threshold) {
|
|
1887
|
-
return;
|
|
1888
|
-
}
|
|
1889
|
-
logger6.info({
|
|
1890
|
-
level: currentLevel,
|
|
1891
|
-
count: summaries.length,
|
|
1892
|
-
nextLevel: currentLevel + 1
|
|
1893
|
-
}, `Triggering Level ${currentLevel + 1} summarization`);
|
|
1894
|
-
const higherSummary = await createHigherLevelSummary(runtime, memoryService, summaries, roomId, currentLevel + 1);
|
|
1895
|
-
if (higherSummary) {
|
|
1896
|
-
await checkAndTriggerHigherLevelSummarization(runtime, memoryService, roomId, currentLevel + 1);
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
async function createHigherLevelSummary(runtime, memoryService, lowerSummaries, roomId, level) {
|
|
1900
|
-
const formattedSummaries = lowerSummaries.map((s, i) => `<summary${i + 1}>${s.content}</summary${i + 1}>`).join(`
|
|
1901
|
-
`);
|
|
1902
|
-
const prompt = buildHigherLevelSummaryPrompt(formattedSummaries);
|
|
1903
|
-
const originalSystemPrompt = runtime.character.system;
|
|
1904
|
-
try {
|
|
1905
|
-
runtime.character.system = HIGHER_LEVEL_SUMMARIZATION_SYSTEM_PROMPT;
|
|
1906
|
-
const response = await runtime.useModel(ModelType4.TEXT_LARGE, {
|
|
1907
|
-
prompt,
|
|
1908
|
-
temperature: 0.3
|
|
1909
|
-
});
|
|
1910
|
-
runtime.character.system = originalSystemPrompt;
|
|
1911
|
-
const { summary, keyTopics, reasoningTrace } = parseSummarizationResult(response);
|
|
1912
|
-
if (!summary) {
|
|
1913
|
-
logger6.warn({ reasoningTrace }, "No higher-level summary extracted from LLM response");
|
|
1914
|
-
return null;
|
|
1915
|
-
}
|
|
1916
|
-
const tokenCount = estimateTokensInSummary(summary);
|
|
1917
|
-
const startTime = new Date(Math.min(...lowerSummaries.map((s) => s.startTime.getTime())));
|
|
1918
|
-
const endTime = new Date(Math.max(...lowerSummaries.map((s) => s.endTime.getTime())));
|
|
1919
|
-
const summaryData = {
|
|
1920
|
-
agentId: runtime.agentId,
|
|
1921
|
-
entityId: lowerSummaries[0].entityId,
|
|
1922
|
-
roomId,
|
|
1923
|
-
level,
|
|
1924
|
-
parentSummaryId: undefined,
|
|
1925
|
-
content: summary,
|
|
1926
|
-
tokenCount,
|
|
1927
|
-
startTime,
|
|
1928
|
-
endTime,
|
|
1929
|
-
sourceCount: lowerSummaries.length,
|
|
1930
|
-
sourceIds: lowerSummaries.map((s) => s.id),
|
|
1931
|
-
metadata: {
|
|
1932
|
-
keyTopics,
|
|
1933
|
-
compressedSummaries: lowerSummaries.length
|
|
1934
|
-
}
|
|
1935
|
-
};
|
|
1936
|
-
return await memoryService.storeSummary(summaryData);
|
|
1937
|
-
} catch (error) {
|
|
1938
|
-
logger6.error({ error, level }, "Failed to create higher-level summary");
|
|
1939
|
-
runtime.character.system = originalSystemPrompt;
|
|
1940
|
-
return null;
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
1015
|
|
|
1944
1016
|
// src/providers/long-term-memory.ts
|
|
1945
1017
|
import {
|
|
1946
|
-
logger as
|
|
1018
|
+
logger as logger4,
|
|
1947
1019
|
addHeader
|
|
1948
1020
|
} from "@elizaos/core";
|
|
1949
1021
|
var longTermMemoryProvider = {
|
|
1950
1022
|
name: "LONG_TERM_MEMORY",
|
|
1951
|
-
description: "
|
|
1952
|
-
position:
|
|
1953
|
-
get: async (runtime, message,
|
|
1023
|
+
description: "Persistent facts and preferences about the user",
|
|
1024
|
+
position: 50,
|
|
1025
|
+
get: async (runtime, message, _state) => {
|
|
1954
1026
|
try {
|
|
1955
1027
|
const memoryService = runtime.getService("memory");
|
|
1956
1028
|
if (!memoryService) {
|
|
1957
|
-
logger7.warn("Memory service not available");
|
|
1958
1029
|
return {
|
|
1959
|
-
data: {},
|
|
1960
|
-
values: {},
|
|
1030
|
+
data: { memories: [] },
|
|
1031
|
+
values: { longTermMemories: "" },
|
|
1961
1032
|
text: ""
|
|
1962
1033
|
};
|
|
1963
1034
|
}
|
|
1964
|
-
const entityId = message
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
}, "Retrieving unified memories");
|
|
1972
|
-
const config = memoryService.getConfig();
|
|
1973
|
-
const [semanticMemories, proceduralMemories, episodicMemories] = await Promise.all([
|
|
1974
|
-
memoryService.searchLongTermMemories({
|
|
1975
|
-
entityId,
|
|
1976
|
-
query,
|
|
1977
|
-
roomId,
|
|
1978
|
-
type: "SEMANTIC" /* SEMANTIC */,
|
|
1979
|
-
limit: config.retrievalLimit,
|
|
1980
|
-
minConfidence: config.minConfidence,
|
|
1981
|
-
similarityThreshold: 0.15
|
|
1982
|
-
}),
|
|
1983
|
-
memoryService.searchLongTermMemories({
|
|
1984
|
-
entityId,
|
|
1985
|
-
query,
|
|
1986
|
-
roomId,
|
|
1987
|
-
type: "PROCEDURAL" /* PROCEDURAL */,
|
|
1988
|
-
limit: Math.floor(config.retrievalLimit / 2),
|
|
1989
|
-
minConfidence: config.minConfidence,
|
|
1990
|
-
similarityThreshold: 0.15
|
|
1991
|
-
}),
|
|
1992
|
-
memoryService.searchLongTermMemories({
|
|
1993
|
-
entityId,
|
|
1994
|
-
query,
|
|
1995
|
-
roomId,
|
|
1996
|
-
type: "EPISODIC" /* EPISODIC */,
|
|
1997
|
-
limit: config.retrievalLimit,
|
|
1998
|
-
minConfidence: config.minConfidence,
|
|
1999
|
-
similarityThreshold: 0.15
|
|
2000
|
-
})
|
|
2001
|
-
]);
|
|
2002
|
-
const longTermSections = [];
|
|
2003
|
-
if (semanticMemories.length > 0) {
|
|
2004
|
-
const items = semanticMemories.map((m) => `- ${m.content} (confidence: ${m.confidence.toFixed(2)}, strength: ${m.activationScore.toFixed(2)})`).join(`
|
|
2005
|
-
`);
|
|
2006
|
-
longTermSections.push(addHeader("## Semantic Knowledge (Facts)", items));
|
|
1035
|
+
const { entityId } = message;
|
|
1036
|
+
if (entityId === runtime.agentId) {
|
|
1037
|
+
return {
|
|
1038
|
+
data: { memories: [] },
|
|
1039
|
+
values: { longTermMemories: "" },
|
|
1040
|
+
text: ""
|
|
1041
|
+
};
|
|
2007
1042
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
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
|
+
};
|
|
2012
1050
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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);
|
|
2017
1057
|
}
|
|
2018
|
-
const
|
|
2019
|
-
|
|
2020
|
-
`);
|
|
2021
|
-
const data = {
|
|
2022
|
-
semanticMemories,
|
|
2023
|
-
proceduralMemories,
|
|
2024
|
-
episodicMemories,
|
|
2025
|
-
config: {
|
|
2026
|
-
retrievalLimit: config.retrievalLimit,
|
|
2027
|
-
tokenBudget: config.tokenBudget
|
|
2028
|
-
}
|
|
2029
|
-
};
|
|
2030
|
-
const values = {
|
|
2031
|
-
longTermMemories: longTermMemoriesText
|
|
2032
|
-
};
|
|
2033
|
-
const text3 = longTermMemoriesText;
|
|
2034
|
-
logger7.info({
|
|
2035
|
-
semanticCount: semanticMemories.length,
|
|
2036
|
-
proceduralCount: proceduralMemories.length,
|
|
2037
|
-
episodicCount: episodicMemories.length
|
|
2038
|
-
}, "Retrieved long-term memory facts");
|
|
1058
|
+
const categoryList = Array.from(categoryCounts.entries()).map(([cat, count]) => `${cat}: ${count}`).join(", ");
|
|
2039
1059
|
return {
|
|
2040
|
-
data
|
|
2041
|
-
|
|
2042
|
-
|
|
1060
|
+
data: {
|
|
1061
|
+
memories,
|
|
1062
|
+
categoryCounts: Object.fromEntries(categoryCounts)
|
|
1063
|
+
},
|
|
1064
|
+
values: {
|
|
1065
|
+
longTermMemories: text4,
|
|
1066
|
+
memoryCategories: categoryList
|
|
1067
|
+
},
|
|
1068
|
+
text: text4
|
|
2043
1069
|
};
|
|
2044
1070
|
} catch (error) {
|
|
2045
|
-
|
|
1071
|
+
logger4.error({ error }, "Error in longTermMemoryProvider:");
|
|
2046
1072
|
return {
|
|
2047
|
-
data: {},
|
|
2048
|
-
values: {
|
|
2049
|
-
longTermMemories: ""
|
|
2050
|
-
},
|
|
1073
|
+
data: { memories: [] },
|
|
1074
|
+
values: { longTermMemories: "" },
|
|
2051
1075
|
text: ""
|
|
2052
1076
|
};
|
|
2053
1077
|
}
|
|
2054
1078
|
}
|
|
2055
1079
|
};
|
|
2056
1080
|
|
|
2057
|
-
// src/providers/
|
|
1081
|
+
// src/providers/context-summary.ts
|
|
2058
1082
|
import {
|
|
2059
1083
|
addHeader as addHeader2,
|
|
2060
|
-
|
|
2061
|
-
formatMessages,
|
|
2062
|
-
formatPosts,
|
|
2063
|
-
getEntityDetails,
|
|
2064
|
-
logger as logger8
|
|
1084
|
+
logger as logger5
|
|
2065
1085
|
} from "@elizaos/core";
|
|
2066
|
-
var
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
getEntityDetails({ runtime, roomId }),
|
|
2072
|
-
runtime.getRoom(roomId),
|
|
2073
|
-
runtime.getMemories({
|
|
2074
|
-
tableName: "messages",
|
|
2075
|
-
roomId,
|
|
2076
|
-
count: 100,
|
|
2077
|
-
unique: false
|
|
2078
|
-
})
|
|
2079
|
-
]);
|
|
2080
|
-
return { entities, room, allMessages };
|
|
2081
|
-
}
|
|
2082
|
-
function filterAndSortDialogueMessages(allMessages) {
|
|
2083
|
-
const dialogueMessages = allMessages.filter((msg) => !(msg.content?.type === "action_result" && msg.metadata?.type === "action_result"));
|
|
2084
|
-
dialogueMessages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
2085
|
-
return dialogueMessages;
|
|
2086
|
-
}
|
|
2087
|
-
async function buildCompressedHistory(runtime, message, dialogueMessages, sessionStartTime) {
|
|
2088
|
-
let compressedHistoryText = "";
|
|
2089
|
-
let lastSummarizedIndex = -1;
|
|
2090
|
-
const memoryService = runtime.getService("memory");
|
|
2091
|
-
if (!memoryService?.searchSummaries) {
|
|
2092
|
-
return { compressedHistoryText, lastSummarizedIndex };
|
|
2093
|
-
}
|
|
2094
|
-
try {
|
|
2095
|
-
const summaries = await memoryService.searchSummaries({
|
|
2096
|
-
entityId: message.entityId,
|
|
2097
|
-
roomId: message.roomId,
|
|
2098
|
-
query: typeof message.content === "string" ? message.content : message.content.text || "",
|
|
2099
|
-
limit: 3,
|
|
2100
|
-
tokenBudget: 500
|
|
2101
|
-
});
|
|
2102
|
-
if (summaries.length === 0) {
|
|
2103
|
-
return { compressedHistoryText, lastSummarizedIndex };
|
|
2104
|
-
}
|
|
2105
|
-
lastSummarizedIndex = findLastSummarizedMessageIndex(summaries, dialogueMessages);
|
|
2106
|
-
const summaryItems = summaries.map((s) => {
|
|
2107
|
-
const levelLabel = s.level === 1 ? "Recent Session" : `Overview (L${s.level})`;
|
|
2108
|
-
return `**[${levelLabel}]** ${s.content}`;
|
|
2109
|
-
}).join(`
|
|
2110
|
-
|
|
2111
|
-
`);
|
|
2112
|
-
const headerText = `# Conversation History (Compressed)
|
|
2113
|
-
**Session Started:** ${formatDateTime(sessionStartTime)}`;
|
|
2114
|
-
compressedHistoryText = addHeader2(headerText, summaryItems);
|
|
2115
|
-
logger8.debug({
|
|
2116
|
-
summaryCount: summaries.length,
|
|
2117
|
-
totalTokens: summaries.reduce((sum, s) => sum + s.tokenCount, 0),
|
|
2118
|
-
lastSummarizedIndex
|
|
2119
|
-
}, "Using hierarchical summaries for compressed history");
|
|
2120
|
-
} catch (error) {
|
|
2121
|
-
logger8.warn({ error }, "Failed to retrieve summaries");
|
|
2122
|
-
}
|
|
2123
|
-
return { compressedHistoryText, lastSummarizedIndex };
|
|
2124
|
-
}
|
|
2125
|
-
function findLastSummarizedMessageIndex(summaries, dialogueMessages) {
|
|
2126
|
-
const level1Summaries = summaries.filter((s) => s.level === 1);
|
|
2127
|
-
if (level1Summaries.length === 0) {
|
|
2128
|
-
return -1;
|
|
2129
|
-
}
|
|
2130
|
-
const allSummarizedMessageIds = new Set;
|
|
2131
|
-
level1Summaries.forEach((summary) => {
|
|
2132
|
-
summary.sourceIds.forEach((id) => allSummarizedMessageIds.add(id));
|
|
2133
|
-
});
|
|
2134
|
-
for (let i = dialogueMessages.length - 1;i >= 0; i--) {
|
|
2135
|
-
if (allSummarizedMessageIds.has(dialogueMessages[i].id)) {
|
|
2136
|
-
logger8.debug({
|
|
2137
|
-
lastSummarizedIndex: i,
|
|
2138
|
-
totalMessages: dialogueMessages.length,
|
|
2139
|
-
summarizedCount: allSummarizedMessageIds.size
|
|
2140
|
-
}, "Determined last summarized message index");
|
|
2141
|
-
return i;
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
return -1;
|
|
2145
|
-
}
|
|
2146
|
-
function calculateBufferMessages(runtime, dialogueMessages, lastSummarizedIndex, hasSummaries, overlapUserMessageCount) {
|
|
2147
|
-
let bufferMessages;
|
|
2148
|
-
if (lastSummarizedIndex >= 0 && hasSummaries) {
|
|
2149
|
-
const summarizedMessages = dialogueMessages.slice(0, lastSummarizedIndex + 1);
|
|
2150
|
-
const summarizedUserMessages = summarizedMessages.filter((m) => m.entityId !== runtime.agentId);
|
|
2151
|
-
const overlapUserMessages = summarizedUserMessages.slice(-overlapUserMessageCount);
|
|
2152
|
-
const newUnsummarizedMessages = dialogueMessages.slice(lastSummarizedIndex + 1);
|
|
2153
|
-
let overlapStartIndex = lastSummarizedIndex + 1;
|
|
2154
|
-
if (overlapUserMessages.length > 0) {
|
|
2155
|
-
const firstOverlapUserMessageId = overlapUserMessages[0].id;
|
|
2156
|
-
const foundIndex = dialogueMessages.findIndex((m) => m.id === firstOverlapUserMessageId);
|
|
2157
|
-
if (foundIndex >= 0) {
|
|
2158
|
-
overlapStartIndex = foundIndex;
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
bufferMessages = dialogueMessages.slice(overlapStartIndex);
|
|
2162
|
-
logger8.debug({
|
|
2163
|
-
lastSummarizedIndex,
|
|
2164
|
-
summarizedMessageCount: summarizedMessages.length,
|
|
2165
|
-
overlapStartIndex,
|
|
2166
|
-
overlapSize: lastSummarizedIndex + 1 - overlapStartIndex,
|
|
2167
|
-
newUnsummarizedCount: newUnsummarizedMessages.length,
|
|
2168
|
-
bufferSize: bufferMessages.length,
|
|
2169
|
-
totalDialogueMessages: dialogueMessages.length,
|
|
2170
|
-
overlapUserMessageCount
|
|
2171
|
-
}, `Dynamic buffer: [overlap: last ${overlapUserMessageCount} user msgs from summary] + [all new unsummarized messages]`);
|
|
2172
|
-
} else {
|
|
2173
|
-
bufferMessages = dialogueMessages;
|
|
2174
|
-
logger8.debug({
|
|
2175
|
-
bufferSize: bufferMessages.length,
|
|
2176
|
-
totalMessages: dialogueMessages.length
|
|
2177
|
-
}, "Using full conversation: no summaries yet");
|
|
2178
|
-
}
|
|
2179
|
-
return { bufferMessages, lastSummarizedIndex };
|
|
2180
|
-
}
|
|
2181
|
-
function formatDateTime(timestamp3) {
|
|
2182
|
-
const date = new Date(timestamp3);
|
|
2183
|
-
return date.toLocaleString("en-US", {
|
|
2184
|
-
year: "numeric",
|
|
2185
|
-
month: "short",
|
|
2186
|
-
day: "numeric",
|
|
2187
|
-
hour: "2-digit",
|
|
2188
|
-
minute: "2-digit",
|
|
2189
|
-
hour12: false
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
function removeEntityIds(text3) {
|
|
2193
|
-
return text3.replace(/\s*\[[\w\-]+\]/g, "").replace(/\s*\([^)]*'s internal thought:[^)]*\)/gi, "").split(`
|
|
2194
|
-
`).map((line) => line.trim()).join(`
|
|
2195
|
-
`).replace(/\n{3,}/g, `
|
|
2196
|
-
|
|
2197
|
-
`);
|
|
2198
|
-
}
|
|
2199
|
-
async function formatBufferMessages(bufferMessages, entities, room, sessionStartTime) {
|
|
2200
|
-
const isPostFormat = room?.type ? room.type === ChannelType.FEED || room.type === ChannelType.THREAD : false;
|
|
2201
|
-
const [formattedRecentMessages, formattedRecentPosts] = await Promise.all([
|
|
2202
|
-
formatMessages({
|
|
2203
|
-
messages: bufferMessages,
|
|
2204
|
-
entities
|
|
2205
|
-
}),
|
|
2206
|
-
formatPosts({
|
|
2207
|
-
messages: bufferMessages,
|
|
2208
|
-
entities,
|
|
2209
|
-
conversationHeader: false
|
|
2210
|
-
})
|
|
2211
|
-
]);
|
|
2212
|
-
let recentBufferText = (isPostFormat ? formattedRecentPosts : formattedRecentMessages) || "";
|
|
2213
|
-
recentBufferText = removeEntityIds(recentBufferText);
|
|
2214
|
-
const firstBufferTime = bufferMessages.length > 0 ? bufferMessages[0].createdAt : null;
|
|
2215
|
-
const lastBufferTime = bufferMessages.length > 0 ? bufferMessages[bufferMessages.length - 1].createdAt : null;
|
|
2216
|
-
let headerText = `# Recent Messages (Last ${bufferMessages.length})`;
|
|
2217
|
-
if (firstBufferTime && lastBufferTime) {
|
|
2218
|
-
headerText += `
|
|
2219
|
-
**Time Range:** ${formatDateTime(firstBufferTime)} - ${formatDateTime(lastBufferTime)}`;
|
|
2220
|
-
}
|
|
2221
|
-
return recentBufferText ? addHeader2(headerText, recentBufferText) : "";
|
|
2222
|
-
}
|
|
2223
|
-
function buildReceivedMessageSection(message, entities) {
|
|
2224
|
-
const metaData = message.metadata;
|
|
2225
|
-
const senderName = entities.find((entity) => entity.id === message.entityId)?.names[0] || metaData?.entityName || "Unknown User";
|
|
2226
|
-
const receivedMessageContent = message.content.text;
|
|
2227
|
-
const hasReceivedMessage = !!receivedMessageContent?.trim();
|
|
2228
|
-
const receivedMessageHeader = hasReceivedMessage ? addHeader2("# Current Message", `**From ${senderName}:** ${receivedMessageContent}`) : "";
|
|
2229
|
-
const focusHeader = hasReceivedMessage ? addHeader2("# Response Focus", `Reply to **${senderName}**'s current message above. Stay relevant to their question. Don't repeat previous responses unless asked again.`) : "";
|
|
2230
|
-
return { receivedMessageHeader, focusHeader };
|
|
2231
|
-
}
|
|
2232
|
-
function assembleContextSections(compressedHistoryText, recentBufferHeader, receivedMessageHeader, focusHeader) {
|
|
2233
|
-
const textSections = [
|
|
2234
|
-
compressedHistoryText,
|
|
2235
|
-
recentBufferHeader,
|
|
2236
|
-
receivedMessageHeader,
|
|
2237
|
-
focusHeader
|
|
2238
|
-
].filter(Boolean);
|
|
2239
|
-
return textSections.join(`
|
|
2240
|
-
|
|
2241
|
-
`);
|
|
2242
|
-
}
|
|
2243
|
-
var recentContextProvider = {
|
|
2244
|
-
name: "RECENT_CONVERSATION_SUMMARY",
|
|
2245
|
-
description: "Intelligent context management combining recent messages with hierarchical summaries for optimal token efficiency",
|
|
2246
|
-
position: 100,
|
|
2247
|
-
get: async (runtime, message) => {
|
|
1086
|
+
var contextSummaryProvider = {
|
|
1087
|
+
name: "SUMMARIZED_CONTEXT",
|
|
1088
|
+
description: "Provides summarized context from previous conversations",
|
|
1089
|
+
position: 96,
|
|
1090
|
+
get: async (runtime, message, _state) => {
|
|
2248
1091
|
try {
|
|
1092
|
+
const memoryService = runtime.getService("memory");
|
|
2249
1093
|
const { roomId } = message;
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
dialogueMessages: bufferMessages,
|
|
2262
|
-
messageCount: dialogueMessages.length,
|
|
2263
|
-
lastSummarizedIndex,
|
|
2264
|
-
config
|
|
2265
|
-
};
|
|
2266
|
-
const isPostFormat = room?.type ? room.type === ChannelType.FEED || room.type === ChannelType.THREAD : false;
|
|
2267
|
-
const values = {
|
|
2268
|
-
compressedHistory: compressedHistoryText,
|
|
2269
|
-
recentMessages: recentBufferHeader,
|
|
2270
|
-
receivedMessage: receivedMessageHeader,
|
|
2271
|
-
focusInstruction: focusHeader,
|
|
2272
|
-
recentPosts: isPostFormat ? recentBufferHeader : ""
|
|
2273
|
-
};
|
|
2274
|
-
logger8.info({
|
|
2275
|
-
messageCount: dialogueMessages.length,
|
|
2276
|
-
bufferSize: bufferMessages.length,
|
|
2277
|
-
hasSummaries,
|
|
2278
|
-
lastSummarizedIndex,
|
|
2279
|
-
estimatedTokens: Math.ceil(text3.length / 4),
|
|
2280
|
-
overlapUserMessageCount: config.overlapUserMessageCount
|
|
2281
|
-
}, "Recent context assembled");
|
|
2282
|
-
return {
|
|
2283
|
-
data,
|
|
2284
|
-
values,
|
|
2285
|
-
text: text3
|
|
2286
|
-
};
|
|
2287
|
-
} catch (error) {
|
|
2288
|
-
logger8.error({ error }, "Error in recentContextProvider");
|
|
2289
|
-
return {
|
|
2290
|
-
data: {
|
|
2291
|
-
dialogueMessages: [],
|
|
2292
|
-
messageCount: 0,
|
|
2293
|
-
lastSummarizedIndex: -1,
|
|
2294
|
-
config: getProviderConfig(runtime)
|
|
2295
|
-
},
|
|
2296
|
-
values: {
|
|
2297
|
-
compressedHistory: "",
|
|
2298
|
-
recentMessages: "",
|
|
2299
|
-
receivedMessage: "",
|
|
2300
|
-
focusInstruction: "",
|
|
2301
|
-
recentPosts: ""
|
|
2302
|
-
},
|
|
2303
|
-
text: "Error retrieving context."
|
|
2304
|
-
};
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
};
|
|
2308
|
-
|
|
2309
|
-
// src/providers/action-results.ts
|
|
2310
|
-
import { addHeader as addHeader3, logger as logger9 } from "@elizaos/core";
|
|
2311
|
-
var getActionResultsConfig = (runtime) => ({
|
|
2312
|
-
limit: parseInt(runtime.getSetting("CONTEXT_ACTION_RESULTS_LIMIT") || "3", 10)
|
|
2313
|
-
});
|
|
2314
|
-
function formatActionResults(actionResultMessages, limit) {
|
|
2315
|
-
if (actionResultMessages.length === 0) {
|
|
2316
|
-
return "";
|
|
2317
|
-
}
|
|
2318
|
-
const groupedByRun = new Map;
|
|
2319
|
-
for (const mem of actionResultMessages) {
|
|
2320
|
-
const runId = String(mem.content?.runId || "unknown");
|
|
2321
|
-
if (!groupedByRun.has(runId)) {
|
|
2322
|
-
groupedByRun.set(runId, []);
|
|
2323
|
-
}
|
|
2324
|
-
groupedByRun.get(runId).push(mem);
|
|
2325
|
-
}
|
|
2326
|
-
const formattedActionResults = Array.from(groupedByRun.entries()).slice(-limit).map(([runId, memories]) => {
|
|
2327
|
-
const sortedMemories = memories.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
2328
|
-
const thought = sortedMemories[0]?.content?.planThought || "";
|
|
2329
|
-
const runText = sortedMemories.map((mem) => {
|
|
2330
|
-
const actionName = mem.content?.actionName || "Unknown";
|
|
2331
|
-
const status = mem.content?.actionStatus || "unknown";
|
|
2332
|
-
const planStep = mem.content?.planStep || "";
|
|
2333
|
-
const text3 = mem.content?.text || "";
|
|
2334
|
-
const error = mem.content?.error || "";
|
|
2335
|
-
let memText = ` - ${actionName} (${status})`;
|
|
2336
|
-
if (planStep)
|
|
2337
|
-
memText += ` [${planStep}]`;
|
|
2338
|
-
if (error) {
|
|
2339
|
-
memText += `: Error - ${error}`;
|
|
2340
|
-
} else if (text3 && text3 !== `Executed action: ${actionName}`) {
|
|
2341
|
-
memText += `: ${text3}`;
|
|
1094
|
+
if (!memoryService) {
|
|
1095
|
+
return {
|
|
1096
|
+
data: {
|
|
1097
|
+
summary: null
|
|
1098
|
+
},
|
|
1099
|
+
values: {
|
|
1100
|
+
sessionSummaries: "",
|
|
1101
|
+
sessionSummariesWithTopics: ""
|
|
1102
|
+
},
|
|
1103
|
+
text: ""
|
|
1104
|
+
};
|
|
2342
1105
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
const
|
|
2368
|
-
actionResultMessages.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
2369
|
-
const text3 = formatActionResults(actionResultMessages, config.limit);
|
|
2370
|
-
logger9.debug({
|
|
2371
|
-
actionResultCount: actionResultMessages.length,
|
|
2372
|
-
limit: config.limit
|
|
2373
|
-
}, "Action results provider assembled");
|
|
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(", ")}*`;
|
|
1128
|
+
}
|
|
1129
|
+
const sessionSummaries2 = addHeader2("# Conversation Summary", summaryOnly);
|
|
1130
|
+
const sessionSummariesWithTopics = addHeader2("# Conversation Summary", summaryWithTopics);
|
|
2374
1131
|
return {
|
|
2375
1132
|
data: {
|
|
2376
|
-
|
|
2377
|
-
config
|
|
1133
|
+
summary: currentSummary
|
|
2378
1134
|
},
|
|
2379
1135
|
values: {
|
|
2380
|
-
|
|
1136
|
+
sessionSummaries: sessionSummaries2,
|
|
1137
|
+
sessionSummariesWithTopics
|
|
2381
1138
|
},
|
|
2382
|
-
text:
|
|
1139
|
+
text: sessionSummariesWithTopics
|
|
2383
1140
|
};
|
|
2384
1141
|
} catch (error) {
|
|
2385
|
-
|
|
1142
|
+
logger5.error({ error }, "Error in contextSummaryProvider:");
|
|
2386
1143
|
return {
|
|
2387
1144
|
data: {
|
|
2388
|
-
|
|
2389
|
-
config: getActionResultsConfig(runtime)
|
|
1145
|
+
summary: null
|
|
2390
1146
|
},
|
|
2391
1147
|
values: {
|
|
2392
|
-
|
|
1148
|
+
sessionSummaries: "",
|
|
1149
|
+
sessionSummariesWithTopics: ""
|
|
2393
1150
|
},
|
|
2394
1151
|
text: ""
|
|
2395
1152
|
};
|
|
2396
1153
|
}
|
|
2397
1154
|
}
|
|
2398
1155
|
};
|
|
1156
|
+
|
|
2399
1157
|
// src/index.ts
|
|
2400
1158
|
var memoryPlugin = {
|
|
2401
1159
|
name: "memory",
|
|
2402
|
-
description: "
|
|
1160
|
+
description: "Advanced memory management with conversation summarization and long-term persistent memory",
|
|
2403
1161
|
services: [MemoryService],
|
|
2404
|
-
evaluators: [
|
|
2405
|
-
providers: [
|
|
1162
|
+
evaluators: [summarizationEvaluator, longTermExtractionEvaluator],
|
|
1163
|
+
providers: [
|
|
1164
|
+
longTermMemoryProvider,
|
|
1165
|
+
contextSummaryProvider
|
|
1166
|
+
],
|
|
2406
1167
|
schema: exports_schemas
|
|
2407
1168
|
};
|
|
2408
1169
|
var src_default = memoryPlugin;
|
|
2409
1170
|
export {
|
|
2410
|
-
|
|
2411
|
-
recentContextProvider,
|
|
2412
|
-
mergeSearchResults,
|
|
1171
|
+
sessionSummaries,
|
|
2413
1172
|
memoryPlugin,
|
|
2414
|
-
|
|
2415
|
-
mapDbRowToConversationSummary,
|
|
1173
|
+
memoryAccessLogs,
|
|
2416
1174
|
longTermMemoryProvider,
|
|
2417
|
-
longTermMemoryEmbeddings,
|
|
2418
1175
|
longTermMemories,
|
|
2419
|
-
generateEmbedding,
|
|
2420
|
-
formatTokenCount,
|
|
2421
|
-
formatMemoriesForContext,
|
|
2422
|
-
estimateTokensInSummary,
|
|
2423
|
-
estimateTokenCountForArray,
|
|
2424
|
-
estimateTokenCount,
|
|
2425
1176
|
src_default as default,
|
|
2426
|
-
|
|
2427
|
-
conversationSummaries,
|
|
2428
|
-
cleanEmbedding,
|
|
2429
|
-
calculateDecayFactor,
|
|
2430
|
-
calculateAccessBoost,
|
|
2431
|
-
buildLevel1SummaryPrompt,
|
|
2432
|
-
buildHigherLevelSummaryPrompt,
|
|
2433
|
-
buildExtractionPrompt,
|
|
2434
|
-
buildContradictionPrompt,
|
|
2435
|
-
applyDecayScoring,
|
|
2436
|
-
actionResultsProvider,
|
|
2437
|
-
SUMMARIZATION_SYSTEM_PROMPT,
|
|
2438
|
-
MemoryType,
|
|
1177
|
+
contextSummaryProvider,
|
|
2439
1178
|
MemoryService,
|
|
2440
|
-
|
|
2441
|
-
LongTermMemoryRepository,
|
|
2442
|
-
HIGHER_LEVEL_SUMMARIZATION_SYSTEM_PROMPT,
|
|
2443
|
-
DecayFunction,
|
|
2444
|
-
ConversationSummaryRepository,
|
|
2445
|
-
CONSOLIDATION_SYSTEM_PROMPT
|
|
1179
|
+
LongTermMemoryCategory
|
|
2446
1180
|
};
|
|
2447
1181
|
|
|
2448
|
-
//# debugId=
|
|
1182
|
+
//# debugId=6798CB2FB52CC5AF64756E2164756E21
|