chat-agent-toolkit 1.2.33 → 1.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/research-agent.cjs.js.map +1 -1
- package/dist/research-agent.es.js.map +1 -1
- package/package.json +1 -1
- package/src/config/config-manager.ts +295 -295
- package/src/config/config-types.ts +65 -65
- package/src/config/environment-variables.ts +16 -16
- package/src/config/index.ts +26 -26
- package/src/config/language-models-database.ts +1434 -1434
- package/src/config/mcp-server-registry.ts +24 -24
- package/src/config/model-registry.ts +271 -271
- package/src/config/model-tester.ts +211 -211
- package/src/config/model-utils.ts +193 -193
- package/src/config/provider-ui-config.ts +196 -196
- package/src/connectors/open-connector.json +130 -130
- package/src/index.ts +26 -26
- package/src/memory/ARCHITECTURE.md +302 -302
- package/src/memory/README.md +224 -224
- package/src/memory/agent-memory-manager.ts +416 -416
- package/src/memory/example.ts +343 -343
- package/src/memory/index.ts +47 -47
- package/src/memory/mastra-integration.ts +604 -604
- package/src/memory/storage/drizzle-storage.ts +236 -236
- package/src/memory/storage/in-memory-storage.ts +551 -551
- package/src/memory/storage/storage-interface.ts +68 -68
- package/src/memory/types.ts +125 -125
- package/src/models/types.ts +4 -4
- package/src/tools/index.ts +13 -13
- package/src/tools/open-connector-mastra.ts +270 -270
- package/src/tools/open-connector-mcp.ts +170 -170
- package/src/tools/qwksearch-api-tools.ts +326 -326
- package/src/tools/search/doc-utils.ts +139 -139
- package/src/tools/search/document.ts +47 -47
- package/src/tools/search/index.ts +14 -14
- package/src/tools/search/link-summarizer.ts +112 -112
- package/src/tools/search/meta-search-types.ts +62 -62
- package/src/tools/search/metaSearchAgent.ts +465 -465
- package/src/tools/search/search-handlers.ts +97 -97
- package/src/tools/search/suggestionGeneratorAgent.ts +56 -56
- package/src/types.d.ts +137 -137
- package/src/utils/index.ts +2 -2
- package/src/utils/markdown-to-html.ts +53 -53
- package/src/utils/outputParser.ts +73 -73
|
@@ -1,551 +1,551 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* Simple Memory Class
|
|
4
|
-
*
|
|
5
|
-
* Core memory management functionality with:
|
|
6
|
-
* - Message deduplication
|
|
7
|
-
* - Automatic summarization
|
|
8
|
-
* - Vector-based relevance search
|
|
9
|
-
* - Caching with TTL
|
|
10
|
-
* - Batch processing
|
|
11
|
-
* - Conflict resolution
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { writeLanguageResponse } from "write-language";
|
|
15
|
-
import type { IMemoryStorage } from "./storage-interface";
|
|
16
|
-
import type {
|
|
17
|
-
MemoryRecord,
|
|
18
|
-
Message,
|
|
19
|
-
MemorySearchOptions,
|
|
20
|
-
MemoryMetrics,
|
|
21
|
-
MemoryOptions,
|
|
22
|
-
MemoryContextOptions,
|
|
23
|
-
ExtractedFact,
|
|
24
|
-
MemoryType,
|
|
25
|
-
} from "../types";
|
|
26
|
-
import { MEMORY_CONFIG, MEMORY_TYPES } from "../types";
|
|
27
|
-
|
|
28
|
-
export class SimpleMemory {
|
|
29
|
-
private userId: string;
|
|
30
|
-
private storage: IMemoryStorage;
|
|
31
|
-
private maxMemories: number;
|
|
32
|
-
private summaryThreshold: number;
|
|
33
|
-
private cacheExpiry: number;
|
|
34
|
-
private batchSize: number;
|
|
35
|
-
private relevanceThreshold: number;
|
|
36
|
-
private enableVectorSearch: boolean;
|
|
37
|
-
private enableAutoSummarization: boolean;
|
|
38
|
-
private recentMessages: Message[];
|
|
39
|
-
private memoryCache: Map<string, { data: any; timestamp: number }>;
|
|
40
|
-
private isProcessing: boolean;
|
|
41
|
-
private processingQueue: any[];
|
|
42
|
-
private summarizeTimeout?: NodeJS.Timeout;
|
|
43
|
-
private metrics: {
|
|
44
|
-
cacheHits: number;
|
|
45
|
-
cacheMisses: number;
|
|
46
|
-
vectorSearches: number;
|
|
47
|
-
summarizations: number;
|
|
48
|
-
errors: number;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Initialize memory system for a user
|
|
53
|
-
*/
|
|
54
|
-
constructor(
|
|
55
|
-
userId: string,
|
|
56
|
-
storage: IMemoryStorage,
|
|
57
|
-
options: MemoryOptions = {},
|
|
58
|
-
) {
|
|
59
|
-
if (!userId || !storage) {
|
|
60
|
-
throw new Error("userId and storage are required parameters");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.userId = userId;
|
|
64
|
-
this.storage = storage;
|
|
65
|
-
this.maxMemories =
|
|
66
|
-
options.maxMemories || MEMORY_CONFIG.DEFAULT_MAX_MEMORIES;
|
|
67
|
-
this.summaryThreshold =
|
|
68
|
-
options.summaryThreshold || MEMORY_CONFIG.DEFAULT_SUMMARY_THRESHOLD;
|
|
69
|
-
this.cacheExpiry =
|
|
70
|
-
options.cacheExpiry || MEMORY_CONFIG.DEFAULT_CACHE_EXPIRY;
|
|
71
|
-
this.batchSize = options.batchSize || MEMORY_CONFIG.DEFAULT_BATCH_SIZE;
|
|
72
|
-
this.relevanceThreshold =
|
|
73
|
-
options.relevanceThreshold || MEMORY_CONFIG.DEFAULT_RELEVANCE_THRESHOLD;
|
|
74
|
-
|
|
75
|
-
// Feature flags
|
|
76
|
-
this.enableVectorSearch =
|
|
77
|
-
options.enableVectorSearch !== false &&
|
|
78
|
-
MEMORY_CONFIG.VECTOR_SEARCH_ENABLED;
|
|
79
|
-
this.enableAutoSummarization =
|
|
80
|
-
options.enableAutoSummarization !== false &&
|
|
81
|
-
MEMORY_CONFIG.AUTO_SUMMARIZATION_ENABLED;
|
|
82
|
-
|
|
83
|
-
// State management
|
|
84
|
-
this.recentMessages = [];
|
|
85
|
-
this.memoryCache = new Map();
|
|
86
|
-
this.isProcessing = false;
|
|
87
|
-
this.processingQueue = [];
|
|
88
|
-
|
|
89
|
-
// Performance metrics
|
|
90
|
-
this.metrics = {
|
|
91
|
-
cacheHits: 0,
|
|
92
|
-
cacheMisses: 0,
|
|
93
|
-
vectorSearches: 0,
|
|
94
|
-
summarizations: 0,
|
|
95
|
-
errors: 0,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Add a message to current session with intelligent deduplication
|
|
101
|
-
*/
|
|
102
|
-
addMessage(
|
|
103
|
-
role: "user" | "assistant",
|
|
104
|
-
content: string,
|
|
105
|
-
metadata: Record<string, any> = {},
|
|
106
|
-
): boolean {
|
|
107
|
-
if (!role || !content || typeof content !== "string") {
|
|
108
|
-
console.warn("Invalid message parameters:", { role, content });
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Intelligent deduplication - check last few messages
|
|
113
|
-
const lastMessages = this.recentMessages.slice(-3);
|
|
114
|
-
const isDuplicate = lastMessages.some(
|
|
115
|
-
(msg) =>
|
|
116
|
-
msg.role === role &&
|
|
117
|
-
msg.content === content &&
|
|
118
|
-
Date.now() - msg.timestamp < 60000, // Within 1 minute
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
if (isDuplicate) {
|
|
122
|
-
console.log("Duplicate message detected, skipping");
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Add message with metadata
|
|
127
|
-
const message: Message = {
|
|
128
|
-
role,
|
|
129
|
-
content: content.trim(),
|
|
130
|
-
timestamp: metadata.timestamp || Date.now(),
|
|
131
|
-
metadata: { ...metadata },
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
this.recentMessages.push(message);
|
|
135
|
-
|
|
136
|
-
// Auto-summarization with debouncing
|
|
137
|
-
if (
|
|
138
|
-
this.enableAutoSummarization &&
|
|
139
|
-
this.recentMessages.length >= this.summaryThreshold &&
|
|
140
|
-
!this.isProcessing
|
|
141
|
-
) {
|
|
142
|
-
this.debouncedSummarize();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Debounced summarization to prevent excessive processing
|
|
150
|
-
*/
|
|
151
|
-
private debouncedSummarize(): void {
|
|
152
|
-
if (this.summarizeTimeout) {
|
|
153
|
-
clearTimeout(this.summarizeTimeout);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
this.summarizeTimeout = setTimeout(() => {
|
|
157
|
-
this.summarizeAndStore().catch((error) => {
|
|
158
|
-
console.error("Auto-summarization failed:", error);
|
|
159
|
-
this.metrics.errors++;
|
|
160
|
-
});
|
|
161
|
-
}, 1000); // 1 second debounce
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Store important facts with validation and conflict resolution
|
|
166
|
-
*/
|
|
167
|
-
async storeFact(
|
|
168
|
-
content: string,
|
|
169
|
-
importance: number = 1,
|
|
170
|
-
category: MemoryType = MEMORY_TYPES.FACT,
|
|
171
|
-
metadata: Record<string, any> = {},
|
|
172
|
-
): Promise<string> {
|
|
173
|
-
// Input validation
|
|
174
|
-
if (
|
|
175
|
-
!content ||
|
|
176
|
-
typeof content !== "string" ||
|
|
177
|
-
content.trim().length === 0
|
|
178
|
-
) {
|
|
179
|
-
throw new Error("Content cannot be empty");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
importance < MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.min ||
|
|
184
|
-
importance > MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.max
|
|
185
|
-
) {
|
|
186
|
-
throw new Error(
|
|
187
|
-
`Importance must be between ${MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.min} and ${MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.max}`,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const normalizedContent = content.trim();
|
|
192
|
-
const normalizedCategory = Object.values(MEMORY_TYPES).includes(category)
|
|
193
|
-
? category
|
|
194
|
-
: MEMORY_TYPES.FACT;
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
// Check for existing similar facts using fuzzy matching
|
|
198
|
-
const existingFacts = await this.findSimilarFacts(normalizedContent);
|
|
199
|
-
|
|
200
|
-
if (existingFacts.length > 0) {
|
|
201
|
-
// Update existing fact if importance is higher
|
|
202
|
-
const bestMatch = existingFacts[0];
|
|
203
|
-
if (importance > bestMatch.importance) {
|
|
204
|
-
await this.storage.updateMemory(bestMatch.id, {
|
|
205
|
-
importance,
|
|
206
|
-
updated_at: new Date(),
|
|
207
|
-
access_count: { increment: 1 },
|
|
208
|
-
metadata: { ...bestMatch.metadata, ...metadata },
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return bestMatch.id;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Insert new fact
|
|
215
|
-
const id = await this.storage.insertMemory(
|
|
216
|
-
this.userId,
|
|
217
|
-
normalizedCategory,
|
|
218
|
-
normalizedContent,
|
|
219
|
-
Math.max(0, Math.min(10, importance)),
|
|
220
|
-
metadata,
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
// Clear cache after new insertion
|
|
224
|
-
this.clearCache();
|
|
225
|
-
|
|
226
|
-
return id;
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.error("Error storing fact:", error);
|
|
229
|
-
this.metrics.errors++;
|
|
230
|
-
throw error;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Find similar facts using content similarity
|
|
236
|
-
*/
|
|
237
|
-
private async findSimilarFacts(content: string): Promise<MemoryRecord[]> {
|
|
238
|
-
try {
|
|
239
|
-
return await this.storage.findSimilarMemories(this.userId, content, 5);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error("Error finding similar facts:", error);
|
|
242
|
-
return [];
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Enhanced memory recall with caching and vector search
|
|
248
|
-
*/
|
|
249
|
-
async recallRelevantMemories(
|
|
250
|
-
query: string = "",
|
|
251
|
-
limit: number = 10,
|
|
252
|
-
options: MemorySearchOptions = {},
|
|
253
|
-
): Promise<MemoryRecord[]> {
|
|
254
|
-
const cacheKey = `${query}-${limit}-${JSON.stringify(options)}`;
|
|
255
|
-
|
|
256
|
-
// Check cache first
|
|
257
|
-
if (this.memoryCache.has(cacheKey)) {
|
|
258
|
-
const cached = this.memoryCache.get(cacheKey)!;
|
|
259
|
-
if (Date.now() - cached.timestamp < this.cacheExpiry) {
|
|
260
|
-
this.metrics.cacheHits++;
|
|
261
|
-
return cached.data;
|
|
262
|
-
}
|
|
263
|
-
this.memoryCache.delete(cacheKey);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
this.metrics.cacheMisses++;
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
let memories = await this.storage.findMemories(
|
|
270
|
-
this.userId,
|
|
271
|
-
query,
|
|
272
|
-
limit,
|
|
273
|
-
options,
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
if (memories.length === 0) {
|
|
277
|
-
return [];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Apply vector search if enabled and query provided
|
|
281
|
-
if (this.enableVectorSearch && query.trim() && memories.length > 0) {
|
|
282
|
-
memories = await this.applyVectorSearch(query, memories, options);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Apply relevance filtering
|
|
286
|
-
if (options.minImportance) {
|
|
287
|
-
memories = memories.filter(
|
|
288
|
-
(m) => m.importance >= options.minImportance!,
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Format results
|
|
293
|
-
const result = memories.map((m) => ({
|
|
294
|
-
...m,
|
|
295
|
-
relevance_score: m.relevance_score || 0,
|
|
296
|
-
metadata: options.includeMetadata ? m.metadata : undefined,
|
|
297
|
-
}));
|
|
298
|
-
|
|
299
|
-
// Cache the result
|
|
300
|
-
this.memoryCache.set(cacheKey, {
|
|
301
|
-
data: result,
|
|
302
|
-
timestamp: Date.now(),
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
return result;
|
|
306
|
-
} catch (error) {
|
|
307
|
-
console.error("Error retrieving memories:", error);
|
|
308
|
-
this.metrics.errors++;
|
|
309
|
-
return [];
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Apply vector search to memories
|
|
315
|
-
*/
|
|
316
|
-
private async applyVectorSearch(
|
|
317
|
-
query: string,
|
|
318
|
-
memories: MemoryRecord[],
|
|
319
|
-
options: MemorySearchOptions,
|
|
320
|
-
): Promise<MemoryRecord[]> {
|
|
321
|
-
// Placeholder for vector search implementation
|
|
322
|
-
// In production, integrate with your vector similarity API
|
|
323
|
-
// try {
|
|
324
|
-
// this.metrics.vectorSearches++;
|
|
325
|
-
// const sentencesByRelevance = await weighRelevanceConceptVectorAPI(
|
|
326
|
-
// query,
|
|
327
|
-
// memories.map(m => m.content)
|
|
328
|
-
// );
|
|
329
|
-
// await this.updateRelevanceScores(memories, sentencesByRelevance);
|
|
330
|
-
// return memories
|
|
331
|
-
// .map(memory => {
|
|
332
|
-
// const relevanceData = sentencesByRelevance.find(s => s.sentence === memory.content);
|
|
333
|
-
// return {
|
|
334
|
-
// ...memory,
|
|
335
|
-
// relevance_score: relevanceData?.relevance || 0
|
|
336
|
-
// };
|
|
337
|
-
// })
|
|
338
|
-
// .sort((a, b) => b.relevance_score - a.relevance_score);
|
|
339
|
-
// } catch (vectorError) {
|
|
340
|
-
// console.warn('Vector search failed, falling back to basic search:', vectorError);
|
|
341
|
-
// return memories;
|
|
342
|
-
// }
|
|
343
|
-
return memories;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Update relevance scores for memories
|
|
348
|
-
*/
|
|
349
|
-
private async updateRelevanceScores(
|
|
350
|
-
memories: MemoryRecord[],
|
|
351
|
-
sentencesByRelevance: Array<{ sentence: string; relevance: number }>,
|
|
352
|
-
): Promise<void> {
|
|
353
|
-
const updates = sentencesByRelevance
|
|
354
|
-
.filter((m) => m.relevance > this.relevanceThreshold)
|
|
355
|
-
.map((m) => {
|
|
356
|
-
const memory = memories.find((mem) => mem.content === m.sentence);
|
|
357
|
-
if (memory) {
|
|
358
|
-
return {
|
|
359
|
-
id: memory.id,
|
|
360
|
-
updates: {
|
|
361
|
-
importance: Math.min(10, memory.importance + m.relevance * 0.5),
|
|
362
|
-
access_count: { increment: 1 },
|
|
363
|
-
updated_at: new Date(),
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
return null;
|
|
368
|
-
})
|
|
369
|
-
.filter(Boolean) as Array<{ id: string; updates: any }>;
|
|
370
|
-
|
|
371
|
-
if (updates.length > 0) {
|
|
372
|
-
await this.storage.batchUpdateMemories(updates);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Improved summarization with error handling and batch processing
|
|
378
|
-
*/
|
|
379
|
-
async summarizeAndStore(): Promise<boolean> {
|
|
380
|
-
if (this.recentMessages.length === 0 || this.isProcessing) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
this.isProcessing = true;
|
|
385
|
-
this.metrics.summarizations++;
|
|
386
|
-
|
|
387
|
-
try {
|
|
388
|
-
// Create conversation summary
|
|
389
|
-
const conversationText = this.recentMessages
|
|
390
|
-
.map((msg) => `${msg.role}: ${msg.content}`)
|
|
391
|
-
.join("\n");
|
|
392
|
-
|
|
393
|
-
// Extract facts using LLM
|
|
394
|
-
const factsResponse =
|
|
395
|
-
await this.extractFactsFromConversation(conversationText);
|
|
396
|
-
|
|
397
|
-
if (!Array.isArray(factsResponse) || factsResponse.length === 0) {
|
|
398
|
-
console.warn("No facts extracted from conversation");
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Process facts in batches
|
|
403
|
-
await this.processFactsInBatches(factsResponse);
|
|
404
|
-
|
|
405
|
-
// Clear processed messages
|
|
406
|
-
this.recentMessages = [];
|
|
407
|
-
|
|
408
|
-
return true;
|
|
409
|
-
} catch (error) {
|
|
410
|
-
console.error("Error in summarizeAndStore:", error);
|
|
411
|
-
this.metrics.errors++;
|
|
412
|
-
return false;
|
|
413
|
-
} finally {
|
|
414
|
-
this.isProcessing = false;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Extract facts from conversation using LLM
|
|
420
|
-
*/
|
|
421
|
-
private async extractFactsFromConversation(
|
|
422
|
-
conversationText: string,
|
|
423
|
-
): Promise<ExtractedFact[]> {
|
|
424
|
-
try {
|
|
425
|
-
const { extract: factsResponse } = await writeLanguageResponse({
|
|
426
|
-
agent: "remember-facts",
|
|
427
|
-
chat_history: conversationText,
|
|
428
|
-
provider: "groq",
|
|
429
|
-
model: "mixtral-8x7b-32768",
|
|
430
|
-
timeout: MEMORY_CONFIG.DEFAULT_TIMEOUT,
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
return Array.isArray(factsResponse) ? factsResponse : [];
|
|
434
|
-
} catch (error) {
|
|
435
|
-
console.error("Error extracting facts:", error);
|
|
436
|
-
return [];
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Process facts in batches to avoid overwhelming the database
|
|
442
|
-
*/
|
|
443
|
-
private async processFactsInBatches(
|
|
444
|
-
factsResponse: ExtractedFact[],
|
|
445
|
-
): Promise<void> {
|
|
446
|
-
for (let i = 0; i < factsResponse.length; i += this.batchSize) {
|
|
447
|
-
const batch = factsResponse.slice(i, i + this.batchSize);
|
|
448
|
-
|
|
449
|
-
const storePromises = batch.map(async (fact) => {
|
|
450
|
-
try {
|
|
451
|
-
if (fact && typeof fact === "object" && fact.content) {
|
|
452
|
-
return await this.storeFact(
|
|
453
|
-
fact.content,
|
|
454
|
-
fact.importance || 1,
|
|
455
|
-
fact.category || MEMORY_TYPES.CONVERSATION,
|
|
456
|
-
fact.metadata || {},
|
|
457
|
-
);
|
|
458
|
-
} else if (typeof fact === "string" && fact.trim()) {
|
|
459
|
-
return await this.storeFact(
|
|
460
|
-
fact.trim(),
|
|
461
|
-
1,
|
|
462
|
-
MEMORY_TYPES.CONVERSATION,
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
} catch (error) {
|
|
466
|
-
console.error("Error storing individual fact:", error);
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
await Promise.allSettled(storePromises);
|
|
472
|
-
|
|
473
|
-
// Small delay between batches
|
|
474
|
-
if (i + this.batchSize < factsResponse.length) {
|
|
475
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Clear cache utility
|
|
482
|
-
*/
|
|
483
|
-
clearCache(): void {
|
|
484
|
-
this.memoryCache.clear();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Get memory context with better formatting and relevance
|
|
489
|
-
*/
|
|
490
|
-
async getMemoryContext(
|
|
491
|
-
query: string = "",
|
|
492
|
-
includeRecent: boolean = true,
|
|
493
|
-
options: MemoryContextOptions = {},
|
|
494
|
-
): Promise<string> {
|
|
495
|
-
const memories = await this.recallRelevantMemories(
|
|
496
|
-
query,
|
|
497
|
-
options.maxMemories || 8,
|
|
498
|
-
{ minImportance: options.minImportance || 0.5 },
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
if (memories.length === 0 && this.recentMessages.length === 0) {
|
|
502
|
-
return "";
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
let context = "";
|
|
506
|
-
|
|
507
|
-
// Add relevant memories
|
|
508
|
-
if (memories.length > 0) {
|
|
509
|
-
context += "What I remember about you:\n";
|
|
510
|
-
memories
|
|
511
|
-
.filter((memory) => memory.importance > (options.minImportance || 0.5))
|
|
512
|
-
.forEach((memory) => {
|
|
513
|
-
const importanceIndicator =
|
|
514
|
-
memory.importance >= 8
|
|
515
|
-
? "\u2b50"
|
|
516
|
-
: memory.importance >= 5
|
|
517
|
-
? "\u2022"
|
|
518
|
-
: "-";
|
|
519
|
-
context += `${importanceIndicator} ${memory.content}\n`;
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Add recent conversation if requested
|
|
524
|
-
if (includeRecent && this.recentMessages.length > 0) {
|
|
525
|
-
context += context
|
|
526
|
-
? "\nRecent conversation:\n"
|
|
527
|
-
: "Recent conversation:\n";
|
|
528
|
-
this.recentMessages.slice(-3).forEach((msg) => {
|
|
529
|
-
const truncatedContent =
|
|
530
|
-
msg.content.length > 100
|
|
531
|
-
? msg.content.substring(0, 100) + "..."
|
|
532
|
-
: msg.content;
|
|
533
|
-
context += `${msg.role}: ${truncatedContent}\n`;
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return context;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Get performance metrics
|
|
542
|
-
*/
|
|
543
|
-
getMetrics(): MemoryMetrics {
|
|
544
|
-
return {
|
|
545
|
-
...this.metrics,
|
|
546
|
-
cacheSize: this.memoryCache.size,
|
|
547
|
-
recentMessagesCount: this.recentMessages.length,
|
|
548
|
-
isProcessing: this.isProcessing,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
}
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Simple Memory Class
|
|
4
|
+
*
|
|
5
|
+
* Core memory management functionality with:
|
|
6
|
+
* - Message deduplication
|
|
7
|
+
* - Automatic summarization
|
|
8
|
+
* - Vector-based relevance search
|
|
9
|
+
* - Caching with TTL
|
|
10
|
+
* - Batch processing
|
|
11
|
+
* - Conflict resolution
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { writeLanguageResponse } from "write-language";
|
|
15
|
+
import type { IMemoryStorage } from "./storage-interface";
|
|
16
|
+
import type {
|
|
17
|
+
MemoryRecord,
|
|
18
|
+
Message,
|
|
19
|
+
MemorySearchOptions,
|
|
20
|
+
MemoryMetrics,
|
|
21
|
+
MemoryOptions,
|
|
22
|
+
MemoryContextOptions,
|
|
23
|
+
ExtractedFact,
|
|
24
|
+
MemoryType,
|
|
25
|
+
} from "../types";
|
|
26
|
+
import { MEMORY_CONFIG, MEMORY_TYPES } from "../types";
|
|
27
|
+
|
|
28
|
+
export class SimpleMemory {
|
|
29
|
+
private userId: string;
|
|
30
|
+
private storage: IMemoryStorage;
|
|
31
|
+
private maxMemories: number;
|
|
32
|
+
private summaryThreshold: number;
|
|
33
|
+
private cacheExpiry: number;
|
|
34
|
+
private batchSize: number;
|
|
35
|
+
private relevanceThreshold: number;
|
|
36
|
+
private enableVectorSearch: boolean;
|
|
37
|
+
private enableAutoSummarization: boolean;
|
|
38
|
+
private recentMessages: Message[];
|
|
39
|
+
private memoryCache: Map<string, { data: any; timestamp: number }>;
|
|
40
|
+
private isProcessing: boolean;
|
|
41
|
+
private processingQueue: any[];
|
|
42
|
+
private summarizeTimeout?: NodeJS.Timeout;
|
|
43
|
+
private metrics: {
|
|
44
|
+
cacheHits: number;
|
|
45
|
+
cacheMisses: number;
|
|
46
|
+
vectorSearches: number;
|
|
47
|
+
summarizations: number;
|
|
48
|
+
errors: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initialize memory system for a user
|
|
53
|
+
*/
|
|
54
|
+
constructor(
|
|
55
|
+
userId: string,
|
|
56
|
+
storage: IMemoryStorage,
|
|
57
|
+
options: MemoryOptions = {},
|
|
58
|
+
) {
|
|
59
|
+
if (!userId || !storage) {
|
|
60
|
+
throw new Error("userId and storage are required parameters");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.userId = userId;
|
|
64
|
+
this.storage = storage;
|
|
65
|
+
this.maxMemories =
|
|
66
|
+
options.maxMemories || MEMORY_CONFIG.DEFAULT_MAX_MEMORIES;
|
|
67
|
+
this.summaryThreshold =
|
|
68
|
+
options.summaryThreshold || MEMORY_CONFIG.DEFAULT_SUMMARY_THRESHOLD;
|
|
69
|
+
this.cacheExpiry =
|
|
70
|
+
options.cacheExpiry || MEMORY_CONFIG.DEFAULT_CACHE_EXPIRY;
|
|
71
|
+
this.batchSize = options.batchSize || MEMORY_CONFIG.DEFAULT_BATCH_SIZE;
|
|
72
|
+
this.relevanceThreshold =
|
|
73
|
+
options.relevanceThreshold || MEMORY_CONFIG.DEFAULT_RELEVANCE_THRESHOLD;
|
|
74
|
+
|
|
75
|
+
// Feature flags
|
|
76
|
+
this.enableVectorSearch =
|
|
77
|
+
options.enableVectorSearch !== false &&
|
|
78
|
+
MEMORY_CONFIG.VECTOR_SEARCH_ENABLED;
|
|
79
|
+
this.enableAutoSummarization =
|
|
80
|
+
options.enableAutoSummarization !== false &&
|
|
81
|
+
MEMORY_CONFIG.AUTO_SUMMARIZATION_ENABLED;
|
|
82
|
+
|
|
83
|
+
// State management
|
|
84
|
+
this.recentMessages = [];
|
|
85
|
+
this.memoryCache = new Map();
|
|
86
|
+
this.isProcessing = false;
|
|
87
|
+
this.processingQueue = [];
|
|
88
|
+
|
|
89
|
+
// Performance metrics
|
|
90
|
+
this.metrics = {
|
|
91
|
+
cacheHits: 0,
|
|
92
|
+
cacheMisses: 0,
|
|
93
|
+
vectorSearches: 0,
|
|
94
|
+
summarizations: 0,
|
|
95
|
+
errors: 0,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Add a message to current session with intelligent deduplication
|
|
101
|
+
*/
|
|
102
|
+
addMessage(
|
|
103
|
+
role: "user" | "assistant",
|
|
104
|
+
content: string,
|
|
105
|
+
metadata: Record<string, any> = {},
|
|
106
|
+
): boolean {
|
|
107
|
+
if (!role || !content || typeof content !== "string") {
|
|
108
|
+
console.warn("Invalid message parameters:", { role, content });
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Intelligent deduplication - check last few messages
|
|
113
|
+
const lastMessages = this.recentMessages.slice(-3);
|
|
114
|
+
const isDuplicate = lastMessages.some(
|
|
115
|
+
(msg) =>
|
|
116
|
+
msg.role === role &&
|
|
117
|
+
msg.content === content &&
|
|
118
|
+
Date.now() - msg.timestamp < 60000, // Within 1 minute
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (isDuplicate) {
|
|
122
|
+
console.log("Duplicate message detected, skipping");
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add message with metadata
|
|
127
|
+
const message: Message = {
|
|
128
|
+
role,
|
|
129
|
+
content: content.trim(),
|
|
130
|
+
timestamp: metadata.timestamp || Date.now(),
|
|
131
|
+
metadata: { ...metadata },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.recentMessages.push(message);
|
|
135
|
+
|
|
136
|
+
// Auto-summarization with debouncing
|
|
137
|
+
if (
|
|
138
|
+
this.enableAutoSummarization &&
|
|
139
|
+
this.recentMessages.length >= this.summaryThreshold &&
|
|
140
|
+
!this.isProcessing
|
|
141
|
+
) {
|
|
142
|
+
this.debouncedSummarize();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Debounced summarization to prevent excessive processing
|
|
150
|
+
*/
|
|
151
|
+
private debouncedSummarize(): void {
|
|
152
|
+
if (this.summarizeTimeout) {
|
|
153
|
+
clearTimeout(this.summarizeTimeout);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.summarizeTimeout = setTimeout(() => {
|
|
157
|
+
this.summarizeAndStore().catch((error) => {
|
|
158
|
+
console.error("Auto-summarization failed:", error);
|
|
159
|
+
this.metrics.errors++;
|
|
160
|
+
});
|
|
161
|
+
}, 1000); // 1 second debounce
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Store important facts with validation and conflict resolution
|
|
166
|
+
*/
|
|
167
|
+
async storeFact(
|
|
168
|
+
content: string,
|
|
169
|
+
importance: number = 1,
|
|
170
|
+
category: MemoryType = MEMORY_TYPES.FACT,
|
|
171
|
+
metadata: Record<string, any> = {},
|
|
172
|
+
): Promise<string> {
|
|
173
|
+
// Input validation
|
|
174
|
+
if (
|
|
175
|
+
!content ||
|
|
176
|
+
typeof content !== "string" ||
|
|
177
|
+
content.trim().length === 0
|
|
178
|
+
) {
|
|
179
|
+
throw new Error("Content cannot be empty");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
importance < MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.min ||
|
|
184
|
+
importance > MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.max
|
|
185
|
+
) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Importance must be between ${MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.min} and ${MEMORY_CONFIG.DEFAULT_IMPORTANCE_RANGE.max}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const normalizedContent = content.trim();
|
|
192
|
+
const normalizedCategory = Object.values(MEMORY_TYPES).includes(category)
|
|
193
|
+
? category
|
|
194
|
+
: MEMORY_TYPES.FACT;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Check for existing similar facts using fuzzy matching
|
|
198
|
+
const existingFacts = await this.findSimilarFacts(normalizedContent);
|
|
199
|
+
|
|
200
|
+
if (existingFacts.length > 0) {
|
|
201
|
+
// Update existing fact if importance is higher
|
|
202
|
+
const bestMatch = existingFacts[0];
|
|
203
|
+
if (importance > bestMatch.importance) {
|
|
204
|
+
await this.storage.updateMemory(bestMatch.id, {
|
|
205
|
+
importance,
|
|
206
|
+
updated_at: new Date(),
|
|
207
|
+
access_count: { increment: 1 },
|
|
208
|
+
metadata: { ...bestMatch.metadata, ...metadata },
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return bestMatch.id;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Insert new fact
|
|
215
|
+
const id = await this.storage.insertMemory(
|
|
216
|
+
this.userId,
|
|
217
|
+
normalizedCategory,
|
|
218
|
+
normalizedContent,
|
|
219
|
+
Math.max(0, Math.min(10, importance)),
|
|
220
|
+
metadata,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Clear cache after new insertion
|
|
224
|
+
this.clearCache();
|
|
225
|
+
|
|
226
|
+
return id;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error("Error storing fact:", error);
|
|
229
|
+
this.metrics.errors++;
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Find similar facts using content similarity
|
|
236
|
+
*/
|
|
237
|
+
private async findSimilarFacts(content: string): Promise<MemoryRecord[]> {
|
|
238
|
+
try {
|
|
239
|
+
return await this.storage.findSimilarMemories(this.userId, content, 5);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error("Error finding similar facts:", error);
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Enhanced memory recall with caching and vector search
|
|
248
|
+
*/
|
|
249
|
+
async recallRelevantMemories(
|
|
250
|
+
query: string = "",
|
|
251
|
+
limit: number = 10,
|
|
252
|
+
options: MemorySearchOptions = {},
|
|
253
|
+
): Promise<MemoryRecord[]> {
|
|
254
|
+
const cacheKey = `${query}-${limit}-${JSON.stringify(options)}`;
|
|
255
|
+
|
|
256
|
+
// Check cache first
|
|
257
|
+
if (this.memoryCache.has(cacheKey)) {
|
|
258
|
+
const cached = this.memoryCache.get(cacheKey)!;
|
|
259
|
+
if (Date.now() - cached.timestamp < this.cacheExpiry) {
|
|
260
|
+
this.metrics.cacheHits++;
|
|
261
|
+
return cached.data;
|
|
262
|
+
}
|
|
263
|
+
this.memoryCache.delete(cacheKey);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.metrics.cacheMisses++;
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
let memories = await this.storage.findMemories(
|
|
270
|
+
this.userId,
|
|
271
|
+
query,
|
|
272
|
+
limit,
|
|
273
|
+
options,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (memories.length === 0) {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Apply vector search if enabled and query provided
|
|
281
|
+
if (this.enableVectorSearch && query.trim() && memories.length > 0) {
|
|
282
|
+
memories = await this.applyVectorSearch(query, memories, options);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Apply relevance filtering
|
|
286
|
+
if (options.minImportance) {
|
|
287
|
+
memories = memories.filter(
|
|
288
|
+
(m) => m.importance >= options.minImportance!,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Format results
|
|
293
|
+
const result = memories.map((m) => ({
|
|
294
|
+
...m,
|
|
295
|
+
relevance_score: m.relevance_score || 0,
|
|
296
|
+
metadata: options.includeMetadata ? m.metadata : undefined,
|
|
297
|
+
}));
|
|
298
|
+
|
|
299
|
+
// Cache the result
|
|
300
|
+
this.memoryCache.set(cacheKey, {
|
|
301
|
+
data: result,
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return result;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error("Error retrieving memories:", error);
|
|
308
|
+
this.metrics.errors++;
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Apply vector search to memories
|
|
315
|
+
*/
|
|
316
|
+
private async applyVectorSearch(
|
|
317
|
+
query: string,
|
|
318
|
+
memories: MemoryRecord[],
|
|
319
|
+
options: MemorySearchOptions,
|
|
320
|
+
): Promise<MemoryRecord[]> {
|
|
321
|
+
// Placeholder for vector search implementation
|
|
322
|
+
// In production, integrate with your vector similarity API
|
|
323
|
+
// try {
|
|
324
|
+
// this.metrics.vectorSearches++;
|
|
325
|
+
// const sentencesByRelevance = await weighRelevanceConceptVectorAPI(
|
|
326
|
+
// query,
|
|
327
|
+
// memories.map(m => m.content)
|
|
328
|
+
// );
|
|
329
|
+
// await this.updateRelevanceScores(memories, sentencesByRelevance);
|
|
330
|
+
// return memories
|
|
331
|
+
// .map(memory => {
|
|
332
|
+
// const relevanceData = sentencesByRelevance.find(s => s.sentence === memory.content);
|
|
333
|
+
// return {
|
|
334
|
+
// ...memory,
|
|
335
|
+
// relevance_score: relevanceData?.relevance || 0
|
|
336
|
+
// };
|
|
337
|
+
// })
|
|
338
|
+
// .sort((a, b) => b.relevance_score - a.relevance_score);
|
|
339
|
+
// } catch (vectorError) {
|
|
340
|
+
// console.warn('Vector search failed, falling back to basic search:', vectorError);
|
|
341
|
+
// return memories;
|
|
342
|
+
// }
|
|
343
|
+
return memories;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Update relevance scores for memories
|
|
348
|
+
*/
|
|
349
|
+
private async updateRelevanceScores(
|
|
350
|
+
memories: MemoryRecord[],
|
|
351
|
+
sentencesByRelevance: Array<{ sentence: string; relevance: number }>,
|
|
352
|
+
): Promise<void> {
|
|
353
|
+
const updates = sentencesByRelevance
|
|
354
|
+
.filter((m) => m.relevance > this.relevanceThreshold)
|
|
355
|
+
.map((m) => {
|
|
356
|
+
const memory = memories.find((mem) => mem.content === m.sentence);
|
|
357
|
+
if (memory) {
|
|
358
|
+
return {
|
|
359
|
+
id: memory.id,
|
|
360
|
+
updates: {
|
|
361
|
+
importance: Math.min(10, memory.importance + m.relevance * 0.5),
|
|
362
|
+
access_count: { increment: 1 },
|
|
363
|
+
updated_at: new Date(),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
})
|
|
369
|
+
.filter(Boolean) as Array<{ id: string; updates: any }>;
|
|
370
|
+
|
|
371
|
+
if (updates.length > 0) {
|
|
372
|
+
await this.storage.batchUpdateMemories(updates);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Improved summarization with error handling and batch processing
|
|
378
|
+
*/
|
|
379
|
+
async summarizeAndStore(): Promise<boolean> {
|
|
380
|
+
if (this.recentMessages.length === 0 || this.isProcessing) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this.isProcessing = true;
|
|
385
|
+
this.metrics.summarizations++;
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
// Create conversation summary
|
|
389
|
+
const conversationText = this.recentMessages
|
|
390
|
+
.map((msg) => `${msg.role}: ${msg.content}`)
|
|
391
|
+
.join("\n");
|
|
392
|
+
|
|
393
|
+
// Extract facts using LLM
|
|
394
|
+
const factsResponse =
|
|
395
|
+
await this.extractFactsFromConversation(conversationText);
|
|
396
|
+
|
|
397
|
+
if (!Array.isArray(factsResponse) || factsResponse.length === 0) {
|
|
398
|
+
console.warn("No facts extracted from conversation");
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Process facts in batches
|
|
403
|
+
await this.processFactsInBatches(factsResponse);
|
|
404
|
+
|
|
405
|
+
// Clear processed messages
|
|
406
|
+
this.recentMessages = [];
|
|
407
|
+
|
|
408
|
+
return true;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error("Error in summarizeAndStore:", error);
|
|
411
|
+
this.metrics.errors++;
|
|
412
|
+
return false;
|
|
413
|
+
} finally {
|
|
414
|
+
this.isProcessing = false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Extract facts from conversation using LLM
|
|
420
|
+
*/
|
|
421
|
+
private async extractFactsFromConversation(
|
|
422
|
+
conversationText: string,
|
|
423
|
+
): Promise<ExtractedFact[]> {
|
|
424
|
+
try {
|
|
425
|
+
const { extract: factsResponse } = await writeLanguageResponse({
|
|
426
|
+
agent: "remember-facts",
|
|
427
|
+
chat_history: conversationText,
|
|
428
|
+
provider: "groq",
|
|
429
|
+
model: "mixtral-8x7b-32768",
|
|
430
|
+
timeout: MEMORY_CONFIG.DEFAULT_TIMEOUT,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
return Array.isArray(factsResponse) ? factsResponse : [];
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error("Error extracting facts:", error);
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Process facts in batches to avoid overwhelming the database
|
|
442
|
+
*/
|
|
443
|
+
private async processFactsInBatches(
|
|
444
|
+
factsResponse: ExtractedFact[],
|
|
445
|
+
): Promise<void> {
|
|
446
|
+
for (let i = 0; i < factsResponse.length; i += this.batchSize) {
|
|
447
|
+
const batch = factsResponse.slice(i, i + this.batchSize);
|
|
448
|
+
|
|
449
|
+
const storePromises = batch.map(async (fact) => {
|
|
450
|
+
try {
|
|
451
|
+
if (fact && typeof fact === "object" && fact.content) {
|
|
452
|
+
return await this.storeFact(
|
|
453
|
+
fact.content,
|
|
454
|
+
fact.importance || 1,
|
|
455
|
+
fact.category || MEMORY_TYPES.CONVERSATION,
|
|
456
|
+
fact.metadata || {},
|
|
457
|
+
);
|
|
458
|
+
} else if (typeof fact === "string" && fact.trim()) {
|
|
459
|
+
return await this.storeFact(
|
|
460
|
+
fact.trim(),
|
|
461
|
+
1,
|
|
462
|
+
MEMORY_TYPES.CONVERSATION,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error("Error storing individual fact:", error);
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
await Promise.allSettled(storePromises);
|
|
472
|
+
|
|
473
|
+
// Small delay between batches
|
|
474
|
+
if (i + this.batchSize < factsResponse.length) {
|
|
475
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Clear cache utility
|
|
482
|
+
*/
|
|
483
|
+
clearCache(): void {
|
|
484
|
+
this.memoryCache.clear();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Get memory context with better formatting and relevance
|
|
489
|
+
*/
|
|
490
|
+
async getMemoryContext(
|
|
491
|
+
query: string = "",
|
|
492
|
+
includeRecent: boolean = true,
|
|
493
|
+
options: MemoryContextOptions = {},
|
|
494
|
+
): Promise<string> {
|
|
495
|
+
const memories = await this.recallRelevantMemories(
|
|
496
|
+
query,
|
|
497
|
+
options.maxMemories || 8,
|
|
498
|
+
{ minImportance: options.minImportance || 0.5 },
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
if (memories.length === 0 && this.recentMessages.length === 0) {
|
|
502
|
+
return "";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let context = "";
|
|
506
|
+
|
|
507
|
+
// Add relevant memories
|
|
508
|
+
if (memories.length > 0) {
|
|
509
|
+
context += "What I remember about you:\n";
|
|
510
|
+
memories
|
|
511
|
+
.filter((memory) => memory.importance > (options.minImportance || 0.5))
|
|
512
|
+
.forEach((memory) => {
|
|
513
|
+
const importanceIndicator =
|
|
514
|
+
memory.importance >= 8
|
|
515
|
+
? "\u2b50"
|
|
516
|
+
: memory.importance >= 5
|
|
517
|
+
? "\u2022"
|
|
518
|
+
: "-";
|
|
519
|
+
context += `${importanceIndicator} ${memory.content}\n`;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Add recent conversation if requested
|
|
524
|
+
if (includeRecent && this.recentMessages.length > 0) {
|
|
525
|
+
context += context
|
|
526
|
+
? "\nRecent conversation:\n"
|
|
527
|
+
: "Recent conversation:\n";
|
|
528
|
+
this.recentMessages.slice(-3).forEach((msg) => {
|
|
529
|
+
const truncatedContent =
|
|
530
|
+
msg.content.length > 100
|
|
531
|
+
? msg.content.substring(0, 100) + "..."
|
|
532
|
+
: msg.content;
|
|
533
|
+
context += `${msg.role}: ${truncatedContent}\n`;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return context;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Get performance metrics
|
|
542
|
+
*/
|
|
543
|
+
getMetrics(): MemoryMetrics {
|
|
544
|
+
return {
|
|
545
|
+
...this.metrics,
|
|
546
|
+
cacheSize: this.memoryCache.size,
|
|
547
|
+
recentMessagesCount: this.recentMessages.length,
|
|
548
|
+
isProcessing: this.isProcessing,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|