aimemory-core 1.0.1
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/.eslintrc.json +22 -0
- package/.github/workflows/ci.yml +57 -0
- package/.prettierrc +8 -0
- package/README.md +197 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -0
- package/dist/contextBuilder.d.ts +16 -0
- package/dist/contextBuilder.d.ts.map +1 -0
- package/dist/contextBuilder.js +139 -0
- package/dist/contextBuilder.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/licensing.d.ts +45 -0
- package/dist/licensing.d.ts.map +1 -0
- package/dist/licensing.js +99 -0
- package/dist/licensing.js.map +1 -0
- package/dist/memoryManager.d.ts +35 -0
- package/dist/memoryManager.d.ts.map +1 -0
- package/dist/memoryManager.js +265 -0
- package/dist/memoryManager.js.map +1 -0
- package/dist/metadataStore.d.ts +24 -0
- package/dist/metadataStore.d.ts.map +1 -0
- package/dist/metadataStore.js +247 -0
- package/dist/metadataStore.js.map +1 -0
- package/dist/planManager.d.ts +80 -0
- package/dist/planManager.d.ts.map +1 -0
- package/dist/planManager.js +327 -0
- package/dist/planManager.js.map +1 -0
- package/dist/rateLimiter.d.ts +49 -0
- package/dist/rateLimiter.d.ts.map +1 -0
- package/dist/rateLimiter.js +142 -0
- package/dist/rateLimiter.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/postgres.d.ts +31 -0
- package/dist/storage/postgres.d.ts.map +1 -0
- package/dist/storage/postgres.js +171 -0
- package/dist/storage/postgres.js.map +1 -0
- package/dist/storage/redis.d.ts +34 -0
- package/dist/storage/redis.d.ts.map +1 -0
- package/dist/storage/redis.js +101 -0
- package/dist/storage/redis.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usageTracker.d.ts +63 -0
- package/dist/usageTracker.d.ts.map +1 -0
- package/dist/usageTracker.js +238 -0
- package/dist/usageTracker.js.map +1 -0
- package/dist/vectorStore.d.ts +18 -0
- package/dist/vectorStore.d.ts.map +1 -0
- package/dist/vectorStore.js +97 -0
- package/dist/vectorStore.js.map +1 -0
- package/examples/advanced.ts +164 -0
- package/examples/basic.ts +87 -0
- package/package.json +60 -0
- package/src/config.ts +65 -0
- package/src/contextBuilder.ts +184 -0
- package/src/index.ts +209 -0
- package/src/licensing.ts +138 -0
- package/src/memoryManager.ts +340 -0
- package/src/metadataStore.ts +298 -0
- package/src/planManager.ts +417 -0
- package/src/rateLimiter.ts +186 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/postgres.ts +209 -0
- package/src/storage/redis.ts +117 -0
- package/src/types.ts +114 -0
- package/src/usageTracker.ts +325 -0
- package/src/vectorStore.ts +116 -0
- package/tests/aibrain.test.ts +171 -0
- package/tests/contextBuilder.test.ts +138 -0
- package/tests/memoryManager.test.ts +205 -0
- package/tests/metadataStore.test.ts +131 -0
- package/tests/rateLimiter.test.ts +57 -0
- package/tests/usageTracker.test.ts +62 -0
- package/tests/vectorStore.test.ts +106 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createAIMemory, createMemoryManager, createContextBuilder, EmbeddingFunction, MemoryType } from '../src/index.js';
|
|
2
|
+
|
|
3
|
+
const mockEmbedding: EmbeddingFunction = async (text: string): Promise<number[]> => {
|
|
4
|
+
const hash = text.split('').reduce((acc, char) => {
|
|
5
|
+
return ((acc << 5) - acc) + char.charCodeAt(0);
|
|
6
|
+
}, 0);
|
|
7
|
+
|
|
8
|
+
const embedding = new Array(1536).fill(0);
|
|
9
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
10
|
+
embedding[i] = Math.sin(hash * i) * Math.cos(hash * i);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
14
|
+
return embedding.map(val => val / magnitude);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function advancedExample() {
|
|
18
|
+
console.log('=== AI Memory Advanced Example ===\n');
|
|
19
|
+
|
|
20
|
+
const memory = createAIMemory({
|
|
21
|
+
maxMemories: 5000,
|
|
22
|
+
embeddingDimension: 1536,
|
|
23
|
+
context: {
|
|
24
|
+
maxTokens: 4000,
|
|
25
|
+
relevanceThreshold: 0.5,
|
|
26
|
+
memoryTypes: ['fact', 'preference', 'instruction', 'conversation', 'context'],
|
|
27
|
+
maxMemories: 15,
|
|
28
|
+
},
|
|
29
|
+
autoCleanup: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
maxAge: 90 * 24 * 60 * 60 * 1000,
|
|
32
|
+
minImportance: 0.3,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
memory.setEmbeddingFunction(mockEmbedding);
|
|
37
|
+
|
|
38
|
+
console.log('--- Multi-user Scenario ---\n');
|
|
39
|
+
|
|
40
|
+
console.log('1. Adding memories for User A...');
|
|
41
|
+
await memory.rememberFact('User A lives in Buenos Aires, Argentina', {
|
|
42
|
+
userId: 'user-a',
|
|
43
|
+
tags: ['location', 'personal'],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await memory.rememberFact('User A is a data scientist', {
|
|
47
|
+
userId: 'user-a',
|
|
48
|
+
tags: ['profession', 'work'],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await memory.rememberPreference('User A prefers Python over JavaScript', {
|
|
52
|
+
userId: 'user-a',
|
|
53
|
+
tags: ['programming', 'preferences'],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log('2. Adding memories for User B...');
|
|
57
|
+
await memory.rememberFact('User B lives in Madrid, Spain', {
|
|
58
|
+
userId: 'user-b',
|
|
59
|
+
tags: ['location', 'personal'],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await memory.rememberFact('User B is a UX designer', {
|
|
63
|
+
userId: 'user-b',
|
|
64
|
+
tags: ['profession', 'work'],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await memory.rememberPreference('User B prefers Figma for design work', {
|
|
68
|
+
userId: 'user-b',
|
|
69
|
+
tags: ['tools', 'preferences'],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
console.log('\n--- Session Management ---\n');
|
|
73
|
+
|
|
74
|
+
console.log('3. Creating conversation session...');
|
|
75
|
+
const sessionId = 'session-conversation-001';
|
|
76
|
+
|
|
77
|
+
await memory.rememberConversation('Can you help me with Python数据分析?', 'user', {
|
|
78
|
+
userId: 'user-a',
|
|
79
|
+
sessionId,
|
|
80
|
+
tags: ['question', 'python'],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await memory.rememberConversation('Of course! I can help you with data analysis in Python. What specific task would you like to accomplish?', 'assistant', {
|
|
84
|
+
userId: 'user-a',
|
|
85
|
+
sessionId,
|
|
86
|
+
tags: ['response', 'help'],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
console.log('4. Retrieving session memories...');
|
|
90
|
+
const memoryManager = memory.getMemoryManager();
|
|
91
|
+
const sessionMemories = await memoryManager.getMemoriesBySession(sessionId);
|
|
92
|
+
console.log(`Found ${sessionMemories.length} memories in session ${sessionId}`);
|
|
93
|
+
|
|
94
|
+
console.log('\n--- Context Building ---\n');
|
|
95
|
+
|
|
96
|
+
console.log('5. Building context for User A...');
|
|
97
|
+
const contextA = await memory.getContext('programming preferences');
|
|
98
|
+
console.log(`Context includes ${contextA.stats.memoriesUsed} memories`);
|
|
99
|
+
|
|
100
|
+
for (const msg of contextA.messages) {
|
|
101
|
+
if (msg.role === 'system' && msg.content.includes('User Preferences')) {
|
|
102
|
+
console.log('User preferences found in context');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log('\n6. Searching with filters...');
|
|
107
|
+
const facts = await memoryManager.getMemoriesByType('fact' as MemoryType);
|
|
108
|
+
console.log(`Total facts: ${facts.length}`);
|
|
109
|
+
|
|
110
|
+
const userAPreferences = await memoryManager.getMemoriesByUser('user-a');
|
|
111
|
+
console.log(`User A memories: ${userAPreferences.length}`);
|
|
112
|
+
|
|
113
|
+
console.log('\n7. Updating a memory...');
|
|
114
|
+
const searchResults = await memory.recall('Python preference');
|
|
115
|
+
if (searchResults.length > 0) {
|
|
116
|
+
const memoryToUpdate = searchResults[0].memory;
|
|
117
|
+
await memory.updateMemory(memoryToUpdate.id, {
|
|
118
|
+
content: 'User A prefers Python over JavaScript and uses it for data science projects',
|
|
119
|
+
});
|
|
120
|
+
console.log('Memory updated successfully');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log('\n8. Getting comprehensive stats...');
|
|
124
|
+
const stats = await memory.getStats();
|
|
125
|
+
console.log(`
|
|
126
|
+
Total memories: ${stats.totalMemories}
|
|
127
|
+
By type:
|
|
128
|
+
- Facts: ${stats.memoriesByType.fact}
|
|
129
|
+
- Preferences: ${stats.memoriesByType.preference}
|
|
130
|
+
- Conversations: ${stats.memoriesByType.conversation}
|
|
131
|
+
By tag:
|
|
132
|
+
- user-a: ${stats.memoriesByTag['user-a'] || 0}
|
|
133
|
+
- user-b: ${stats.memoriesByTag['user-b'] || 0}
|
|
134
|
+
`);
|
|
135
|
+
|
|
136
|
+
console.log('\n9. Using MemoryManager directly...');
|
|
137
|
+
const manager = createMemoryManager();
|
|
138
|
+
manager.setEmbeddingFunction(mockEmbedding);
|
|
139
|
+
|
|
140
|
+
await manager.addMemory('Direct memory added', {
|
|
141
|
+
type: 'context',
|
|
142
|
+
importance: 0.8,
|
|
143
|
+
tags: ['direct', 'test'],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const directSearch = await manager.search({
|
|
147
|
+
text: 'Direct memory added',
|
|
148
|
+
limit: 5,
|
|
149
|
+
});
|
|
150
|
+
console.log(`Found ${directSearch.length} direct memories`);
|
|
151
|
+
|
|
152
|
+
console.log('\n10. Using ContextBuilder directly...');
|
|
153
|
+
const contextBuilder = createContextBuilder(manager, {
|
|
154
|
+
maxTokens: 1000,
|
|
155
|
+
includeMetadata: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const directContext = await contextBuilder.buildContext('test query');
|
|
159
|
+
console.log(`Built context with ${directContext.messages.length} messages`);
|
|
160
|
+
|
|
161
|
+
console.log('\n=== Advanced example completed! ===');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
advancedExample().catch(console.error);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createAIMemory, EmbeddingFunction } from '../src/index.js';
|
|
2
|
+
|
|
3
|
+
const mockEmbedding: EmbeddingFunction = async (text: string): Promise<number[]> => {
|
|
4
|
+
const hash = text.split('').reduce((acc, char) => {
|
|
5
|
+
return ((acc << 5) - acc) + char.charCodeAt(0);
|
|
6
|
+
}, 0);
|
|
7
|
+
|
|
8
|
+
const embedding = new Array(1536).fill(0);
|
|
9
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
10
|
+
embedding[i] = Math.sin(hash * i) * Math.cos(hash * i);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
14
|
+
return embedding.map(val => val / magnitude);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const memory = createAIMemory({
|
|
19
|
+
maxMemories: 1000,
|
|
20
|
+
context: {
|
|
21
|
+
maxTokens: 2000,
|
|
22
|
+
relevanceThreshold: 0.6,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
memory.setEmbeddingFunction(mockEmbedding);
|
|
27
|
+
|
|
28
|
+
console.log('=== AI Memory Basic Example ===\n');
|
|
29
|
+
|
|
30
|
+
console.log('1. Remembering facts...');
|
|
31
|
+
await memory.rememberFact('The user prefers dark mode interface', {
|
|
32
|
+
userId: 'user-123',
|
|
33
|
+
tags: ['preferences', 'ui'],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await memory.rememberFact('User works as a software developer', {
|
|
37
|
+
userId: 'user-123',
|
|
38
|
+
tags: ['work', 'developer'],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log('2. Remembering preferences...');
|
|
42
|
+
await memory.rememberPreference('I like to receive summaries in bullet points', {
|
|
43
|
+
userId: 'user-123',
|
|
44
|
+
tags: ['communication', 'format'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log('3. Remembering conversation...');
|
|
48
|
+
await memory.rememberConversation('Hello! How are you?', 'user', {
|
|
49
|
+
userId: 'user-123',
|
|
50
|
+
sessionId: 'session-1',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await memory.rememberConversation('Hello! I am doing great, thank you for asking!', 'assistant', {
|
|
54
|
+
userId: 'user-123',
|
|
55
|
+
sessionId: 'session-1',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log('4. Remembering instructions...');
|
|
59
|
+
await memory.rememberInstruction('Always verify information before responding', {
|
|
60
|
+
userId: 'user-123',
|
|
61
|
+
importance: 0.9,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log('\n5. Recalling relevant memories...');
|
|
65
|
+
const results = await memory.recall('user preferences interface');
|
|
66
|
+
console.log(`Found ${results.length} relevant memories:`);
|
|
67
|
+
for (const result of results) {
|
|
68
|
+
console.log(` - [${result.memory.metadata.type}] (score: ${result.score.toFixed(2)}) ${result.memory.content}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('\n6. Building context for LLM...');
|
|
72
|
+
const context = await memory.getContext('What does the user prefer?');
|
|
73
|
+
console.log('Context messages:');
|
|
74
|
+
for (const msg of context.messages) {
|
|
75
|
+
console.log(` [${msg.role}]: ${msg.content.substring(0, 100)}...`);
|
|
76
|
+
}
|
|
77
|
+
console.log(`\nStats: Memories used: ${context.stats.memoriesUsed}, Tokens estimated: ${context.stats.tokensEstimated}`);
|
|
78
|
+
|
|
79
|
+
console.log('\n7. Getting statistics...');
|
|
80
|
+
const stats = await memory.getStats();
|
|
81
|
+
console.log(`Total memories: ${stats.totalMemories}`);
|
|
82
|
+
console.log('Memories by type:', stats.memoriesByType);
|
|
83
|
+
|
|
84
|
+
console.log('\n=== Example completed successfully! ===');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aimemory-core",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Persistent memory system for LLM applications - semantic search, context management, and monetization ready",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest",
|
|
12
|
+
"lint": "eslint src --ext .ts",
|
|
13
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
14
|
+
"docs": "typedoc"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"memory",
|
|
19
|
+
"llm",
|
|
20
|
+
"context",
|
|
21
|
+
"persistent",
|
|
22
|
+
"semantic-search",
|
|
23
|
+
"vector-store",
|
|
24
|
+
"embedding",
|
|
25
|
+
"rate-limit",
|
|
26
|
+
"usage-tracking",
|
|
27
|
+
"monetization",
|
|
28
|
+
"saas"
|
|
29
|
+
],
|
|
30
|
+
"author": "Gabriel Pitrella <support@aimemory.dev>",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/gpitrella/ai-memory"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/gpitrella/ai-memory/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://aimemory.dev",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ioredis": "^5.10.1",
|
|
42
|
+
"pg": "^8.20.0",
|
|
43
|
+
"uuid": "^9.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.10.0",
|
|
47
|
+
"@types/pg": "^8.20.0",
|
|
48
|
+
"@types/uuid": "^9.0.0",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
50
|
+
"@typescript-eslint/parser": "^6.13.0",
|
|
51
|
+
"eslint": "^8.55.0",
|
|
52
|
+
"prettier": "^3.1.0",
|
|
53
|
+
"typedoc": "^0.25.0",
|
|
54
|
+
"typescript": "^5.3.0",
|
|
55
|
+
"vitest": "^1.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ContextConfig, MemoryType } from './types.js';
|
|
2
|
+
|
|
3
|
+
export interface AIMemoryConfig {
|
|
4
|
+
storagePath: string;
|
|
5
|
+
maxMemories: number;
|
|
6
|
+
defaultImportance: number;
|
|
7
|
+
embeddingDimension: number;
|
|
8
|
+
context: ContextConfig;
|
|
9
|
+
autoCleanup: {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
maxAge: number;
|
|
12
|
+
minImportance: number;
|
|
13
|
+
};
|
|
14
|
+
logging: {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_CONFIG: AIMemoryConfig = {
|
|
21
|
+
storagePath: './ai-memory-data',
|
|
22
|
+
maxMemories: 10000,
|
|
23
|
+
defaultImportance: 0.5,
|
|
24
|
+
embeddingDimension: 1536,
|
|
25
|
+
context: {
|
|
26
|
+
maxTokens: 4000,
|
|
27
|
+
includeSystemPrompt: true,
|
|
28
|
+
systemPrompt: 'You are a helpful AI assistant with memory of past conversations.',
|
|
29
|
+
relevanceThreshold: 0.7,
|
|
30
|
+
includeMetadata: false,
|
|
31
|
+
memoryTypes: ['conversation', 'fact', 'preference', 'context'] as MemoryType[],
|
|
32
|
+
maxMemories: 10,
|
|
33
|
+
},
|
|
34
|
+
autoCleanup: {
|
|
35
|
+
enabled: false,
|
|
36
|
+
maxAge: 90 * 24 * 60 * 60 * 1000,
|
|
37
|
+
minImportance: 0.3,
|
|
38
|
+
},
|
|
39
|
+
logging: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
level: 'info',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function validateConfig(config: Partial<AIMemoryConfig>): AIMemoryConfig {
|
|
46
|
+
const merged = { ...DEFAULT_CONFIG, ...config };
|
|
47
|
+
|
|
48
|
+
if (merged.maxMemories < 1) {
|
|
49
|
+
throw new Error('maxMemories must be at least 1');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (merged.embeddingDimension < 1) {
|
|
53
|
+
throw new Error('embeddingDimension must be at least 1');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (merged.context.maxTokens < 100) {
|
|
57
|
+
throw new Error('context.maxTokens must be at least 100');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (merged.context.relevanceThreshold < 0 || merged.context.relevanceThreshold > 1) {
|
|
61
|
+
throw new Error('context.relevanceThreshold must be between 0 and 1');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return merged;
|
|
65
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemoryQuery,
|
|
3
|
+
SearchResult,
|
|
4
|
+
ContextConfig,
|
|
5
|
+
ContextResult,
|
|
6
|
+
ContextMessage,
|
|
7
|
+
MemoryType
|
|
8
|
+
} from './types.js';
|
|
9
|
+
import { MemoryManager } from './memoryManager.js';
|
|
10
|
+
|
|
11
|
+
export class ContextBuilder {
|
|
12
|
+
private memoryManager: MemoryManager;
|
|
13
|
+
private config: ContextConfig;
|
|
14
|
+
|
|
15
|
+
constructor(memoryManager: MemoryManager, config?: Partial<ContextConfig>) {
|
|
16
|
+
this.memoryManager = memoryManager;
|
|
17
|
+
this.config = {
|
|
18
|
+
maxTokens: 4000,
|
|
19
|
+
includeSystemPrompt: true,
|
|
20
|
+
systemPrompt: 'You are a helpful AI assistant with memory of past conversations and user preferences.',
|
|
21
|
+
relevanceThreshold: 0.7,
|
|
22
|
+
includeMetadata: false,
|
|
23
|
+
memoryTypes: ['conversation', 'fact', 'preference', 'context'],
|
|
24
|
+
maxMemories: 10,
|
|
25
|
+
...config,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async buildContext(query: string, additionalConfig?: Partial<ContextConfig>): Promise<ContextResult> {
|
|
30
|
+
const config = { ...this.config, ...additionalConfig };
|
|
31
|
+
const messages: ContextMessage[] = [];
|
|
32
|
+
|
|
33
|
+
if (config.includeSystemPrompt) {
|
|
34
|
+
messages.push({
|
|
35
|
+
role: 'system',
|
|
36
|
+
content: config.systemPrompt!,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const relevantMemories = await this.findRelevantMemories(query, config);
|
|
41
|
+
|
|
42
|
+
const contextContent = this.buildContextContent(relevantMemories, config);
|
|
43
|
+
|
|
44
|
+
if (contextContent) {
|
|
45
|
+
messages.push({
|
|
46
|
+
role: 'system',
|
|
47
|
+
content: contextContent,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tokensEstimated = this.estimateTokens(messages);
|
|
52
|
+
const truncationApplied = tokensEstimated > config.maxTokens;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
messages,
|
|
56
|
+
memories: relevantMemories.map(r => r.memory),
|
|
57
|
+
stats: {
|
|
58
|
+
memoriesUsed: relevantMemories.length,
|
|
59
|
+
tokensEstimated,
|
|
60
|
+
truncationApplied,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async findRelevantMemories(
|
|
66
|
+
query: string,
|
|
67
|
+
config: ContextConfig
|
|
68
|
+
): Promise<SearchResult[]> {
|
|
69
|
+
const searchQuery: MemoryQuery = {
|
|
70
|
+
text: query,
|
|
71
|
+
filter: {
|
|
72
|
+
types: config.memoryTypes,
|
|
73
|
+
minImportance: config.relevanceThreshold,
|
|
74
|
+
},
|
|
75
|
+
limit: config.maxMemories,
|
|
76
|
+
threshold: config.relevanceThreshold,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const results = await this.memoryManager.search(searchQuery);
|
|
80
|
+
|
|
81
|
+
return results.filter(r => r.score >= config.relevanceThreshold);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private buildContextContent(memories: SearchResult[], config: ContextConfig): string {
|
|
85
|
+
if (memories.length === 0) return '';
|
|
86
|
+
|
|
87
|
+
const sections: string[] = [];
|
|
88
|
+
|
|
89
|
+
const groupedByType = this.groupByType(memories);
|
|
90
|
+
|
|
91
|
+
if (groupedByType.fact && groupedByType.fact.length > 0) {
|
|
92
|
+
const facts = groupedByType.fact
|
|
93
|
+
.map(r => `- ${r.memory.content}`)
|
|
94
|
+
.join('\n');
|
|
95
|
+
sections.push(`Key Facts:\n${facts}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (groupedByType.preference && groupedByType.preference.length > 0) {
|
|
99
|
+
const prefs = groupedByType.preference
|
|
100
|
+
.map(r => `- ${r.memory.content}`)
|
|
101
|
+
.join('\n');
|
|
102
|
+
sections.push(`User Preferences:\n${prefs}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (groupedByType.conversation && groupedByType.conversation.length > 0) {
|
|
106
|
+
const recentConversations = groupedByType.conversation
|
|
107
|
+
.slice(0, 5)
|
|
108
|
+
.map(r => r.memory.content)
|
|
109
|
+
.join('\n');
|
|
110
|
+
sections.push(`Recent Conversation Context:\n${recentConversations}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (groupedByType.context && groupedByType.context.length > 0) {
|
|
114
|
+
const context = groupedByType.context
|
|
115
|
+
.map(r => `- ${r.memory.content}`)
|
|
116
|
+
.join('\n');
|
|
117
|
+
sections.push(`Additional Context:\n${context}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (groupedByType.instruction && groupedByType.instruction.length > 0) {
|
|
121
|
+
const instructions = groupedByType.instruction
|
|
122
|
+
.map(r => `- ${r.memory.content}`)
|
|
123
|
+
.join('\n');
|
|
124
|
+
sections.push(`Important Instructions:\n${instructions}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let content = sections.join('\n\n');
|
|
128
|
+
|
|
129
|
+
if (config.includeMetadata) {
|
|
130
|
+
const metadataList = memories
|
|
131
|
+
.map(r => `[${r.memory.metadata.type}] ${r.memory.metadata.tags.join(', ')}`)
|
|
132
|
+
.join('\n');
|
|
133
|
+
content += `\n\nMemory Metadata:\n${metadataList}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return content;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private groupByType(memories: SearchResult[]): Record<MemoryType, SearchResult[]> {
|
|
140
|
+
const grouped: Record<MemoryType, SearchResult[]> = {
|
|
141
|
+
conversation: [],
|
|
142
|
+
fact: [],
|
|
143
|
+
preference: [],
|
|
144
|
+
instruction: [],
|
|
145
|
+
context: [],
|
|
146
|
+
custom: [],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
for (const result of memories) {
|
|
150
|
+
const type = result.memory.metadata.type;
|
|
151
|
+
if (grouped[type]) {
|
|
152
|
+
grouped[type].push(result);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return grouped;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private estimateTokens(messages: ContextMessage[]): number {
|
|
160
|
+
let total = 0;
|
|
161
|
+
|
|
162
|
+
for (const message of messages) {
|
|
163
|
+
const words = message.content.split(/\s+/).length;
|
|
164
|
+
total += Math.ceil(words * 1.3);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return total;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
updateConfig(config: Partial<ContextConfig>): void {
|
|
171
|
+
this.config = { ...this.config, ...config };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getConfig(): Readonly<ContextConfig> {
|
|
175
|
+
return { ...this.config };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createContextBuilder(
|
|
180
|
+
memoryManager: MemoryManager,
|
|
181
|
+
config?: Partial<ContextConfig>
|
|
182
|
+
): ContextBuilder {
|
|
183
|
+
return new ContextBuilder(memoryManager, config);
|
|
184
|
+
}
|