memories-lite 0.9.1 → 0.9.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 +127 -152
- package/dist/memory/index.js +9 -2
- package/dist/prompts/index.js +3 -3
- package/dist/vectorstores/lite.d.ts +10 -0
- package/dist/vectorstores/lite.js +46 -15
- package/{memories-lite.db → memories-lite-a42ac5108869b599bcbac21069f63fb47f07452fcc4b87e89b3c06a945612d0b.db} +0 -0
- package/memories-lite-a9137698d8d3fdbf27efcdc8cd372084b52d484e8db866c5455bbb3f85299b54.db +0 -0
- package/package.json +1 -1
- package/src/memory/index.ts +9 -3
- package/src/prompts/index.ts +3 -3
- package/src/vectorstores/lite.ts +52 -14
- package/tests/init.mem.ts +40 -0
- package/tests/memory.facts.test.ts +29 -75
- package/tests/memory.test.ts +16 -74
- package/tests/memory.update.test.ts +150 -0
- package/tests/memory.users.test.ts +235 -0
package/README.md
CHANGED
|
@@ -2,80 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
> **A lightweight memory layer for AI agents, leveraging LLMs for fact extraction and vector embeddings for retrieval.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 📋 Table of Contents
|
|
6
|
+
- [Quick Start](#-quick-start)
|
|
7
|
+
- [Installation](#-installation)
|
|
8
|
+
- [Basic Usage](#-basic-usage)
|
|
9
|
+
- [Key Features](#-key-features)
|
|
10
|
+
- [Memory Types](#-memory-types)
|
|
11
|
+
- [Use Cases](#-use-cases)
|
|
12
|
+
- [Advanced Configuration](#-advanced-configuration)
|
|
13
|
+
- [Documentation](#-documentation)
|
|
14
|
+
- [Acknowledgements](#-acknowledgements)
|
|
15
|
+
|
|
16
|
+
## 🚀 Quick Start
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Goal
|
|
12
|
-
|
|
13
|
-
Memories-lite provides contextual memory for AI agents. It uses Language Models (LLMs) like OpenAI's GPT models to extract key information (memories) from conversations and stores them using vector embeddings for efficient retrieval. Unlike purely stateless approaches, it utilizes configurable vector stores and an optional history manager (defaulting to in-memory SQLite) for persistence and tracking changes.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Use Cases
|
|
18
|
-
|
|
19
|
-
- **Personalized AI Assistants**: Enable more natural interactions through contextual memory.
|
|
20
|
-
- **Autonomous Agents**: Maintain conversational context without heavy infrastructure.
|
|
21
|
-
- **Local or Serverless Applications**: Add memory capabilities to embedded bots or assistants.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Core Features
|
|
25
|
-
- **Semantic Memory Typing**: Explicitly tagging and utilizing memory types (factual, episodic, semantic, procedural).
|
|
26
|
-
- **Memory Capture**: Processes user messages or structured input, uses an LLM to extract relevant facts/memories, generates embeddings, and stores them in a vector database (inspired by **A-MEM**).
|
|
27
|
-
- **Contextual Retrieval**: Searches the vector store based on a query embedding to find relevant memories using vector similarity (inspired by **A-MEM**).
|
|
28
|
-
- **Memory Management**: Provides methods to `get`, `update`, `delete`, `getAll`, and `deleteAll` memories associated with a specific user ID.
|
|
29
|
-
- **Configurable Backends**: Supports different providers for LLMs (e.g., OpenAI), Embedders (e.g., OpenAI, Google), and Vector Stores.
|
|
30
|
-
- **User-centric Persistence**: Memories are securely stored and isolated by user ID, ensuring data privacy and delegate storage control to the server.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## Roadmap & TODO
|
|
34
|
-
Features planned or under development:
|
|
35
|
-
|
|
36
|
-
- [ ] **Memory Decay & Scoring**: Implement hybrid scoring combining vector similarity with recency decay (e.g., exponential decay based on half-life per memory type, inspired by **MemoryLLM**) and explicit importance weights.
|
|
37
|
-
- [ ] **Reflexion Pattern Integration**: Add optional self-correction/reflection loops where the agent evaluates and potentially refines memories (inspired by **Reflexion**).
|
|
38
|
-
- [ ] **Memory Recency**: Implementing mechanisms to prioritize memories based on importance, relevance, or time decay.
|
|
39
|
-
- [X] **Semantic Memory Typing & Structuring**: Explicitly tagging and utilizing memory types (factual, episodic, semantic, procedural) within the storage and retrieval logic beyond basic metadata.
|
|
40
|
-
- [X] **Implicit Memory Updates**: Automatically updating or merging memories based on conversational context or corrections, rather than requiring explicit `update` calls with memory IDs.
|
|
41
|
-
- [X] **Virtual Sessions/Context Grouping**: Logic for grouping memories related to specific conversational contexts or sessions automatically.
|
|
42
|
-
|
|
43
|
-
## Memory Types (Conceptual)
|
|
44
|
-
|
|
45
|
-
While the internal storage is primarily based on vectorized text facts, `memories-lite` can be used to manage different conceptual types of memory through prompting and metadata:
|
|
46
|
-
|
|
47
|
-
### 1. Factual Memory
|
|
48
|
-
- **Description**: Explicit knowledge about the user, such as preferences, skills, or personal information.
|
|
49
|
-
- **Example**: "Likes Italian cuisine", "Speaks Spanish fluently".
|
|
50
|
-
|
|
51
|
-
### 2. Episodic Memory
|
|
52
|
-
- **Description**: Memories of past events or interactions, often tied to a specific time or place.
|
|
53
|
-
- **Example**: "Attended a concert in Paris in 2023", "Met Marie at a conference".
|
|
54
|
-
- **Context Dependency**: Highly dependent on history to establish a timeline and narrative coherence.
|
|
55
|
-
|
|
56
|
-
### 3. Semantic Memory
|
|
57
|
-
- **Description**: Understanding of general concepts, relationships, and meanings.
|
|
58
|
-
- **Example**: "Yoga is beneficial for mental health", "Cats are domestic animals".
|
|
59
|
-
- **Context Dependency**: Generally independent, but can be influenced by past interactions to refine understanding.
|
|
60
|
-
|
|
61
|
-
### 4. Procedural Memory
|
|
62
|
-
- **Description**: Knowledge of processes or sequences of actions, often acquired through practice.
|
|
63
|
-
- **Example**: "Knows how to make a latte", "Can configure a home Wi-Fi network".
|
|
64
|
-
- **Context Dependency**: May require history to adapt procedures to user preferences or habits.
|
|
18
|
+
```bash
|
|
19
|
+
# Install the package
|
|
20
|
+
npm install memories-lite
|
|
65
21
|
|
|
66
|
-
|
|
22
|
+
# Basic usage
|
|
23
|
+
import { MemoriesLite } from 'memories-lite';
|
|
67
24
|
|
|
68
|
-
|
|
25
|
+
const memory = new MemoriesLite({
|
|
26
|
+
llm: {
|
|
27
|
+
provider: 'openai',
|
|
28
|
+
config: { apiKey: 'YOUR_API_KEY' }
|
|
29
|
+
},
|
|
30
|
+
embedder: {
|
|
31
|
+
provider: 'openai',
|
|
32
|
+
config: { apiKey: 'YOUR_API_KEY', model: 'text-embedding-3-small' }
|
|
33
|
+
}
|
|
34
|
+
});
|
|
69
35
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- `"User started learning piano recently"` (or specific date if LLM extracts it)
|
|
36
|
+
// Add a memory for a user
|
|
37
|
+
await memory.capture("I prefer dark chocolate over milk chocolate", "user123");
|
|
73
38
|
|
|
74
|
-
|
|
39
|
+
// Retrieve relevant memories
|
|
40
|
+
const results = await memory.retrieve("What are my food preferences?", "user123");
|
|
41
|
+
```
|
|
75
42
|
|
|
43
|
+
## 🌟 Highlights
|
|
76
44
|
|
|
45
|
+
- **Higher Performance**: Optimized memory operations that run significantly faster than mem0
|
|
46
|
+
- **Business-Centric Design**: Simplified API and workflows specifically tailored for business use cases
|
|
47
|
+
- **Advanced Hybrid Scoring**: Improved relevance through a custom scoring algorithm that balances vector similarity, recency, and importance
|
|
48
|
+
- **Enhanced Security**: One database per user architecture that provides stronger isolation and data protection
|
|
49
|
+
- **Streamlined Implementation**: Focused on essential features with minimal dependencies
|
|
77
50
|
|
|
78
|
-
## Installation
|
|
51
|
+
## 📥 Installation
|
|
79
52
|
|
|
80
53
|
```bash
|
|
81
54
|
npm install memories-lite
|
|
@@ -83,139 +56,141 @@ npm install memories-lite
|
|
|
83
56
|
yarn add memories-lite
|
|
84
57
|
```
|
|
85
58
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
## 🚀 Usage
|
|
59
|
+
## 🔍 Basic Usage
|
|
89
60
|
|
|
90
61
|
```typescript
|
|
91
|
-
import { MemoriesLite
|
|
62
|
+
import { MemoriesLite } from 'memories-lite';
|
|
92
63
|
|
|
93
|
-
// Basic configuration
|
|
94
|
-
const
|
|
95
|
-
const memories = new MemoriesLite({
|
|
64
|
+
// Basic configuration
|
|
65
|
+
const memory = new MemoriesLite({
|
|
96
66
|
llm: {
|
|
97
67
|
provider: 'openai',
|
|
98
|
-
config: { apiKey }
|
|
68
|
+
config: { apiKey: 'YOUR_OPENAI_API_KEY' }
|
|
99
69
|
},
|
|
100
70
|
embedder: {
|
|
101
71
|
provider: 'openai',
|
|
102
|
-
config: { apiKey }
|
|
72
|
+
config: { apiKey: 'YOUR_OPENAI_API_KEY' }
|
|
103
73
|
}
|
|
104
74
|
// Vector store defaults to an in-memory store
|
|
105
|
-
// History defaults to in-memory SQLite
|
|
106
75
|
});
|
|
107
76
|
|
|
77
|
+
// Unique ID for each user
|
|
108
78
|
const userId = 'user-123';
|
|
109
79
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await memories.capture('I am passionate about the humanitarianism of Pol Pot.', userId); // Note: Example text used for demonstration.
|
|
113
|
-
|
|
114
|
-
// Retrieve relevant memories
|
|
115
|
-
const searchResults = await memories.retrieve('What are my interests?', userId);
|
|
80
|
+
// Add memories
|
|
81
|
+
await memory.capture('I love Italian food', userId);
|
|
116
82
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
83
|
+
// Retrieve relevant memories
|
|
84
|
+
const results = await memory.retrieve('What foods do I like?', userId);
|
|
85
|
+
console.log('Relevant memories:', results.results.map(m => m.memory));
|
|
120
86
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const memoryContext = relevantMemories.memories.map(m => m.memory).join('\n');
|
|
126
|
-
|
|
127
|
-
// You would typically pass this context to your main application's LLM call
|
|
128
|
-
console.log('\nContext for LLM:', memoryContext);
|
|
87
|
+
// Update a memory
|
|
88
|
+
if (results.results.length > 0) {
|
|
89
|
+
await memory.update(results.results[0].id, 'I love Italian and French cuisine', userId);
|
|
129
90
|
}
|
|
130
91
|
|
|
131
|
-
|
|
92
|
+
// Delete a memory
|
|
93
|
+
if (results.results.length > 0) {
|
|
94
|
+
await memory.delete(results.results[0].id, userId);
|
|
95
|
+
}
|
|
132
96
|
|
|
97
|
+
// Get all memories for a user
|
|
98
|
+
const allMemories = await memory.getAll(userId, {});
|
|
133
99
|
```
|
|
134
100
|
|
|
135
|
-
##
|
|
101
|
+
## 🔑 Key Features
|
|
136
102
|
|
|
137
|
-
|
|
103
|
+
- **Memory Capture**: Extract and store relevant information from conversations
|
|
104
|
+
- **Contextual Retrieval**: Find memories most relevant to the current query
|
|
105
|
+
- **User Isolation**: Each user's memories are stored separately for privacy and security
|
|
106
|
+
- **Memory Types**: Support for different types of memories (factual, episodic, etc.)
|
|
107
|
+
- **Custom Scoring**: Hybrid scoring system balancing similarity, recency, and importance
|
|
138
108
|
|
|
139
|
-
|
|
109
|
+
## 🧩 Memory Types
|
|
140
110
|
|
|
141
|
-
|
|
142
|
-
Forked from the [Mem0](https://github.com/mem0ai/mem0) project ❤️.
|
|
111
|
+
Memories-lite supports four main types of memory:
|
|
143
112
|
|
|
144
|
-
|
|
113
|
+
1. **Factual Memory** ✓
|
|
114
|
+
- User preferences, traits, and personal information
|
|
115
|
+
- Example: "User likes Italian cuisine"
|
|
145
116
|
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
- [**MemoryLLM: Towards Self-Updatable Large Language Models** (arXiv:2402.04624)](https://arxiv.org/abs/2402.04624)
|
|
117
|
+
2. **Episodic Memory** ⏱️
|
|
118
|
+
- Time-based events and interactions
|
|
119
|
+
- Example: "User has a meeting tomorrow at 2pm"
|
|
150
120
|
|
|
121
|
+
3. **Semantic Memory** 🧠
|
|
122
|
+
- General knowledge and concepts
|
|
123
|
+
- Example: "Yoga is beneficial for mental health"
|
|
151
124
|
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
125
|
+
4. **Procedural Memory** 🔄
|
|
126
|
+
- Step-by-step processes and workflows
|
|
127
|
+
- Example: "Steps to configure the company VPN"
|
|
155
128
|
|
|
129
|
+
## 💼 Use Cases
|
|
130
|
+
|
|
131
|
+
- **Customer Support Bots**: Remember customer preferences and past interactions
|
|
132
|
+
- **Personal Assistants**: Build context-aware AI assistants that learn about user preferences
|
|
133
|
+
- **Business Applications**: Integrate with enterprise systems to maintain contextual awareness
|
|
134
|
+
- **Educational Tools**: Create learning assistants that remember student progress
|
|
135
|
+
|
|
136
|
+
## ⚙️ Advanced Configuration
|
|
156
137
|
|
|
157
138
|
```typescript
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const apiKey = 'YOUR_OPENAI_API_KEY';
|
|
162
|
-
|
|
163
|
-
// Define custom scoring rules
|
|
164
|
-
const customScoring: MemoryScoringConfig = {
|
|
165
|
-
// Make Factual memory very durable (long half-life, high similarity weight)
|
|
166
|
-
factual: { alpha: 0.7, beta: 0.1, gamma: 0.1, halfLifeDays: 365 * 2 }, // 2 years HL
|
|
167
|
-
// Make Assistant Preferences permanent (infinite half-life, high base weight)
|
|
168
|
-
assistant_preference: { alpha: 0.5, beta: 0.0, gamma: 0.5, halfLifeDays: Infinity },
|
|
169
|
-
// Make Procedural memory decay extremely fast (useless after ~1 hour)
|
|
170
|
-
procedural: { alpha: 0.1, beta: 0.1, gamma: 0.0, halfLifeDays: 1 / 24 }, // 1 hour HL
|
|
171
|
-
// Keep defaults for others (or customize as needed)
|
|
172
|
-
episodic: { alpha: 0.40, beta: 0.50, gamma: 0.10, halfLifeDays: 7 },
|
|
173
|
-
semantic: { alpha: 0.50, beta: 0.25, gamma: 0.25, halfLifeDays: 120 },
|
|
174
|
-
default: { alpha: 0.5, beta: 0.3, gamma: 0.1, halfLifeDays: 30 },
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const memoriesWithCustomScoring = new MemoriesLite({
|
|
139
|
+
// Custom scoring for different memory types
|
|
140
|
+
const customMemory = new MemoriesLite({
|
|
178
141
|
llm: {
|
|
179
142
|
provider: 'openai',
|
|
180
|
-
config: { apiKey }
|
|
143
|
+
config: { apiKey: 'YOUR_API_KEY' }
|
|
181
144
|
},
|
|
182
145
|
embedder: {
|
|
183
|
-
provider: 'openai',
|
|
184
|
-
config: { apiKey }
|
|
146
|
+
provider: 'openai',
|
|
147
|
+
config: { apiKey: 'YOUR_API_KEY' }
|
|
185
148
|
},
|
|
186
149
|
vectorStore: {
|
|
187
|
-
provider: 'lite',
|
|
150
|
+
provider: 'lite',
|
|
188
151
|
config: {
|
|
189
|
-
|
|
190
|
-
scoring:
|
|
152
|
+
dimension: 1536,
|
|
153
|
+
scoring: {
|
|
154
|
+
// Prioritize factual memories with long retention
|
|
155
|
+
factual: { alpha: 0.7, beta: 0.2, gamma: 0.1, halfLifeDays: 365 },
|
|
156
|
+
// Make preferences permanently available
|
|
157
|
+
assistant_preference: { alpha: 0.6, beta: 0.0, gamma: 0.4, halfLifeDays: Infinity },
|
|
158
|
+
}
|
|
191
159
|
}
|
|
192
160
|
}
|
|
193
161
|
});
|
|
194
|
-
|
|
195
|
-
// Now use memoriesWithCustomScoring instance...
|
|
196
|
-
// const userId = 'user-456';
|
|
197
|
-
// await memoriesWithCustomScoring.capture('User learned how to bake bread.', userId, { type: 'procedural' });
|
|
198
|
-
// await memoriesWithCustomScoring.capture('User prefers results in French.', userId, { type: 'assistant_preference' });
|
|
199
162
|
```
|
|
200
163
|
|
|
201
|
-
##
|
|
164
|
+
## 📚 Documentation
|
|
202
165
|
|
|
203
|
-
|
|
166
|
+
For detailed technical information and implementation details, see:
|
|
204
167
|
|
|
205
|
-
|
|
168
|
+
- [TECHNICAL.md](./TECHNICAL.md) - Technical implementation details
|
|
169
|
+
- [MEMORIES.md](./MEMORIES.md) - Detailed memory models and concepts
|
|
206
170
|
|
|
207
|
-
## Acknowledgements
|
|
208
|
-
Forked from the [Mem0](https://github.com/mem0ai/mem0) project ❤️.
|
|
209
|
-
|
|
210
|
-
## Useful Links and research
|
|
171
|
+
## 🙏 Acknowledgements
|
|
211
172
|
|
|
212
|
-
|
|
213
|
-
- [**A-MEM: Agentic Memory for LLM Agents** (arXiv:2402.12110)](https://arxiv.org/abs/2402.12110) *(Note: Link points to 2402.12110, the user-provided 2502.12110 might be a typo)*
|
|
214
|
-
- [**Reflexion: Language Agents with Verbal Reinforcement Learning** (arXiv:2303.11366)](https://arxiv.org/abs/2303.11366)
|
|
215
|
-
- [**MemoryLLM: Towards Self-Updatable Large Language Models** (arXiv:2402.04624)](https://arxiv.org/abs/2402.04624)
|
|
173
|
+
Forked from the [Mem0](https://github.com/mem0ai/mem0) project ❤️.
|
|
216
174
|
|
|
175
|
+
Inspired by research concepts from:
|
|
176
|
+
- **A-MEM**: Agentic Memory for LLM Agents
|
|
177
|
+
- **MemoryLLM**: Self-Updatable Large Language Models
|
|
178
|
+
- **Reflexion**: Language Agents with Verbal Reinforcement Learning
|
|
179
|
+
|
|
180
|
+
## 📝 Development Roadmap
|
|
181
|
+
|
|
182
|
+
- [x] **Semantic Memory Typing & Structuring**: Explicitly tagging and utilizing memory types (factual, episodic, semantic, procedural)
|
|
183
|
+
- [x] **Implicit Memory Updates**: Auto-merging memories based on context without explicit ID references
|
|
184
|
+
- [x] **Virtual Sessions/Context Grouping**: Group memories related to specific conversation contexts
|
|
185
|
+
- [x] **User Isolation**: Separate storage per user for enhanced security and data privacy
|
|
186
|
+
- [x] **Memory Type Detection**: LLM-based automatic classification of memory types
|
|
187
|
+
- [x] **Core Memory Operations**: Basic CRUD operations with user-specific isolation
|
|
188
|
+
- [x] **Memory Decay & Scoring**: Hybrid scoring with recency decay and importance weights
|
|
189
|
+
- [ ] **Reflexion Pattern Integration**: Self-correction loops for memory refinement
|
|
190
|
+
- [x] **Memory Recency**: Prioritizing memories based on importance and time decay
|
|
191
|
+
- [x] **Edge Case Tests**: Complete unit tests for episodic and factual memory edge cases
|
|
192
|
+
- [ ] **Middleware Support**: Hooks and middleware for custom processing pipelines
|
|
217
193
|
|
|
218
|
-
|
|
219
|
-
- **Half-Life**: Defining a half-life (HL) for different memory types to calculate \( \lambda = \ln(2) / \text{HL} \).
|
|
220
|
-
- **Hybrid Scoring**: Integrating the decay factor into the memory retrieval score alongside vector similarity and potentially other metadata.
|
|
194
|
+
## 📄 License
|
|
221
195
|
|
|
196
|
+
MIT
|
package/dist/memory/index.js
CHANGED
|
@@ -124,6 +124,9 @@ class MemoriesLite {
|
|
|
124
124
|
// Get embeddings for new facts
|
|
125
125
|
const newMessageEmbeddings = {};
|
|
126
126
|
const retrievedOldMemory = [];
|
|
127
|
+
//
|
|
128
|
+
// add the userId to the filters
|
|
129
|
+
filters.userId = userId;
|
|
127
130
|
// Create embeddings and search for similar memories
|
|
128
131
|
for (const elem of facts) {
|
|
129
132
|
const fact = elem.fact;
|
|
@@ -155,7 +158,7 @@ class MemoriesLite {
|
|
|
155
158
|
console.log(`-- ⛔ LLM Error: ${action.event}, ${action.type}, "${action.text}"`);
|
|
156
159
|
continue;
|
|
157
160
|
}
|
|
158
|
-
console.log(`-- DBG memory
|
|
161
|
+
console.log(`-- DBG memory "${userId}": ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
|
|
159
162
|
try {
|
|
160
163
|
switch (action.event) {
|
|
161
164
|
case "ADD": {
|
|
@@ -176,7 +179,7 @@ class MemoriesLite {
|
|
|
176
179
|
}
|
|
177
180
|
case "UPDATE": {
|
|
178
181
|
const realMemoryId = tempUuidMapping[action.id];
|
|
179
|
-
const type = uniqueOldMemories[action.id].type;
|
|
182
|
+
const type = metadata.type = uniqueOldMemories[action.id].type || action.type;
|
|
180
183
|
await this.updateMemory(realMemoryId, action.text, newMessageEmbeddings, metadata, userId);
|
|
181
184
|
results.push({
|
|
182
185
|
id: realMemoryId,
|
|
@@ -307,6 +310,7 @@ class MemoriesLite {
|
|
|
307
310
|
throw new Error("One of the filters: userId, agentId or runId is required!");
|
|
308
311
|
}
|
|
309
312
|
const vectorStore = await this.getVectorStore(userId);
|
|
313
|
+
filters.userId = userId;
|
|
310
314
|
// Search vector store
|
|
311
315
|
const queryEmbedding = await this.embedder.embed(query);
|
|
312
316
|
const memories = await vectorStore.search(queryEmbedding, limit, filters);
|
|
@@ -420,6 +424,7 @@ class MemoriesLite {
|
|
|
420
424
|
filters.runId = runId;
|
|
421
425
|
if (type)
|
|
422
426
|
filters.type = type;
|
|
427
|
+
filters.userId = userId;
|
|
423
428
|
const [memories] = await vectorStore.list(filters, limit);
|
|
424
429
|
const excludedKeys = new Set([
|
|
425
430
|
"userId",
|
|
@@ -453,6 +458,7 @@ class MemoriesLite {
|
|
|
453
458
|
...metadata,
|
|
454
459
|
data,
|
|
455
460
|
hash: (0, crypto_1.createHash)("md5").update(data).digest("hex"),
|
|
461
|
+
userId,
|
|
456
462
|
createdAt: new Date().toISOString(),
|
|
457
463
|
};
|
|
458
464
|
await vectorStore.insert([embedding], [memoryId], [memoryMetadata]);
|
|
@@ -471,6 +477,7 @@ class MemoriesLite {
|
|
|
471
477
|
...metadata,
|
|
472
478
|
data,
|
|
473
479
|
hash: (0, crypto_1.createHash)("md5").update(data).digest("hex"),
|
|
480
|
+
type: existingMemory.payload.type,
|
|
474
481
|
createdAt: existingMemory.payload.createdAt,
|
|
475
482
|
updatedAt: new Date().toISOString(),
|
|
476
483
|
...(existingMemory.payload.agentId && {
|
package/dist/prompts/index.js
CHANGED
|
@@ -128,15 +128,15 @@ You must strictly extract {Subject, Predicate, Object} triplets by following the
|
|
|
128
128
|
- Extract triplets that describe facts *about the user* based on their statements, covering areas like preferences, beliefs, actions, experiences, learning, identity, work, or relationships (e.g., "I love working").
|
|
129
129
|
- Apply explicit, precise, and unambiguous predicates (e.g., "owns", "is located at", "is a", "has function", "causes", etc.).
|
|
130
130
|
- Determine the triplet type (e.g., "factual", "episodic", "procedural", "semantic") based on the content and meaning.
|
|
131
|
-
- "episodic"
|
|
131
|
+
- "episodic" If a fact depends on a temporal, situational, or immediate personal context, then that fact AND ALL OF ITS sub-facts MUST be classified as episodic.
|
|
132
132
|
- "procedural" for business processes (e.g., "Looking for customer John Doe address", "How to create a new contract").
|
|
133
133
|
- "factual" for stable user data (except procedural that prevails).
|
|
134
134
|
|
|
135
135
|
- Eliminate introductions, sub-facts, detailed repetitive elements, stylistic fillers, or vague statements. General facts always takes precedence over multiple sub-facts (signal vs noise).
|
|
136
136
|
- The query intention can include specific preferences about how the Assistant should respond (e.g., "answer concisely", "explain in detail").
|
|
137
|
-
- Compress each fact
|
|
137
|
+
- Compress each OUTPUT (fact and reason) with less than 10 words.
|
|
138
138
|
- DO NOT infer personal facts from third-party informations.
|
|
139
|
-
- Treat "
|
|
139
|
+
- Treat "**ASSISTANT**:" as responses to enrich context of your reasoning process about the USER query.
|
|
140
140
|
2. Use pronoun "I" instead of "The user" in the subject of the triplet.
|
|
141
141
|
3. Do not output any facts already present in section # PRE-EXISTING FACTS.
|
|
142
142
|
- If you find facts already present in section # PRE-EXISTING FACTS, use field "existing" to store them.
|
|
@@ -12,6 +12,7 @@ import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class LiteVectorStore implements VectorStore {
|
|
14
14
|
private db;
|
|
15
|
+
private dbPath;
|
|
15
16
|
private isSecure;
|
|
16
17
|
private dimension;
|
|
17
18
|
private currentUserId;
|
|
@@ -36,5 +37,14 @@ export declare class LiteVectorStore implements VectorStore {
|
|
|
36
37
|
list(filters?: SearchFilters, limit?: number): Promise<[VectorStoreResult[], number]>;
|
|
37
38
|
private calculateRecencyScore;
|
|
38
39
|
private calculateHybridScore;
|
|
40
|
+
/**
|
|
41
|
+
* Internal method to clean up vectors based on recency score threshold.
|
|
42
|
+
*
|
|
43
|
+
* @param threshold - The minimum recency score required for a memory to be retained.
|
|
44
|
+
* - Recency score is calculated using exponential decay: 1.0 means brand new, 0.5 means at half-life, 0.0 means fully decayed.
|
|
45
|
+
* - Memories with a recency score below this threshold will be deleted (unless their half-life is infinite or zero).
|
|
46
|
+
* - For example, a threshold of 0.25 will remove all memories whose recency score has decayed 2 times the half-life.
|
|
47
|
+
* - Use a lower threshold to keep more old memories, or a higher threshold to keep only fresher ones.
|
|
48
|
+
*/
|
|
39
49
|
private _cleanupByRecency;
|
|
40
50
|
}
|
|
@@ -8,6 +8,7 @@ exports.LiteVectorStore = void 0;
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const sqlite3_1 = __importDefault(require("sqlite3"));
|
|
10
10
|
const crypto_1 = require("crypto");
|
|
11
|
+
const fs_1 = require("fs");
|
|
11
12
|
/**
|
|
12
13
|
* LiteVectorStore provides a simple vector storage implementation.
|
|
13
14
|
*
|
|
@@ -27,27 +28,32 @@ class LiteVectorStore {
|
|
|
27
28
|
this.currentUserId = currentUserId;
|
|
28
29
|
this.isSecure = config.secure || false;
|
|
29
30
|
this.scoringConfig = config.scoring;
|
|
30
|
-
this.cleanupThreshold = config.recencyCleanupThreshold; //
|
|
31
|
+
this.cleanupThreshold = config.recencyCleanupThreshold || 0.25; // (default 0.25 means 2 times the half-life )
|
|
31
32
|
config.rootPath = config.rootPath || process.cwd();
|
|
32
33
|
const filename = this.isSecure ? `memories-lite-${currentUserId}.db` : `memories-lite-global.db`;
|
|
33
|
-
|
|
34
|
+
this.dbPath = (config.rootPath == ':memory:') ? ':memory:' : path_1.default.join(config.rootPath, filename);
|
|
34
35
|
// Add error handling callback for the database connection
|
|
35
|
-
this.db = new sqlite3_1.default.Database(dbPath);
|
|
36
|
+
this.db = new sqlite3_1.default.Database(this.dbPath);
|
|
36
37
|
}
|
|
37
38
|
async init() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
try {
|
|
40
|
+
await this.run(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS vectors (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
41
43
|
vector BLOB NOT NULL,
|
|
42
44
|
payload TEXT NOT NULL
|
|
43
45
|
)
|
|
44
46
|
`);
|
|
45
|
-
|
|
47
|
+
await this.run(`
|
|
46
48
|
CREATE TABLE IF NOT EXISTS memory_migrations (
|
|
47
49
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
48
50
|
user_id TEXT NOT NULL UNIQUE
|
|
49
51
|
)
|
|
50
52
|
`);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.log("-- DBG init error:", err);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
async run(sql, params = []) {
|
|
53
59
|
return new Promise((resolve, reject) => {
|
|
@@ -129,9 +135,11 @@ class LiteVectorStore {
|
|
|
129
135
|
if (cachedStore) {
|
|
130
136
|
Object.setPrototypeOf(cachedStore, LiteVectorStore.prototype);
|
|
131
137
|
cachedStore.currentUserId = hashedUserId;
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
cachedStore.
|
|
138
|
+
//
|
|
139
|
+
// if the database file does not exist, we need to reinitialize the store
|
|
140
|
+
if (cachedStore.dbPath !== ':memory:' && !(0, fs_1.existsSync)(cachedStore.dbPath)) {
|
|
141
|
+
return new LiteVectorStore(config, hashedUserId);
|
|
142
|
+
}
|
|
135
143
|
return cachedStore;
|
|
136
144
|
}
|
|
137
145
|
// Pass the full config (including scoring) to the constructor
|
|
@@ -145,17 +153,28 @@ class LiteVectorStore {
|
|
|
145
153
|
if (vectors[i].length !== this.dimension) {
|
|
146
154
|
throw new Error(`Vector dimension mismatch. Expected ${this.dimension}, got ${vectors[i].length}`);
|
|
147
155
|
}
|
|
156
|
+
const payload = { ...payloads[i] };
|
|
157
|
+
//
|
|
158
|
+
// case of global store (insecure)
|
|
159
|
+
if (!payload.userId) {
|
|
160
|
+
throw new Error("userId is required in payload");
|
|
161
|
+
}
|
|
148
162
|
//
|
|
149
163
|
// remove the userId from the payload as sensitive data
|
|
150
|
-
this.isSecure && delete
|
|
164
|
+
this.isSecure && delete payload.userId;
|
|
151
165
|
const vectorBuffer = Buffer.from(new Float32Array(vectors[i]).buffer);
|
|
152
|
-
await this.run(`INSERT OR REPLACE INTO vectors (id, vector, payload) VALUES (?, ?, ?)`, [ids[i], vectorBuffer, JSON.stringify(
|
|
166
|
+
await this.run(`INSERT OR REPLACE INTO vectors (id, vector, payload) VALUES (?, ?, ?)`, [ids[i], vectorBuffer, JSON.stringify(payload)]);
|
|
153
167
|
}
|
|
154
168
|
}
|
|
155
169
|
async search(query, limit = 10, filters) {
|
|
156
170
|
if (query.length !== this.dimension) {
|
|
157
171
|
throw new Error(`Query dimension mismatch. Expected ${this.dimension}, got ${query.length}`);
|
|
158
172
|
}
|
|
173
|
+
if (!filters || !filters.userId) {
|
|
174
|
+
throw new Error("userId is mandatory in search");
|
|
175
|
+
}
|
|
176
|
+
filters = { ...filters };
|
|
177
|
+
this.isSecure && delete filters.userId;
|
|
159
178
|
const results = [];
|
|
160
179
|
const rows = await this.all(`SELECT * FROM vectors`);
|
|
161
180
|
for (const row of rows) {
|
|
@@ -225,17 +244,21 @@ class LiteVectorStore {
|
|
|
225
244
|
async list(filters, limit = 100) {
|
|
226
245
|
const rows = await this.all(`SELECT * FROM vectors`);
|
|
227
246
|
const results = [];
|
|
247
|
+
//
|
|
248
|
+
// remove the userId from the payload as sensitive data
|
|
249
|
+
filters = { ...filters };
|
|
250
|
+
this.isSecure && delete filters?.userId;
|
|
228
251
|
for (const row of rows) {
|
|
229
252
|
const memoryVector = {
|
|
230
253
|
id: row.id,
|
|
231
254
|
vector: Array.from(new Float32Array(row.vector.buffer)),
|
|
232
|
-
payload:
|
|
255
|
+
payload: JSON.parse(row.payload),
|
|
233
256
|
};
|
|
234
257
|
if (this.filterVector(memoryVector, filters)) {
|
|
235
258
|
// load payload at the end
|
|
236
259
|
results.push({
|
|
237
260
|
id: memoryVector.id,
|
|
238
|
-
payload:
|
|
261
|
+
payload: memoryVector.payload,
|
|
239
262
|
});
|
|
240
263
|
}
|
|
241
264
|
}
|
|
@@ -278,7 +301,15 @@ class LiteVectorStore {
|
|
|
278
301
|
// Ensure score is within a reasonable range (e.g., 0 to alpha+beta+gamma)
|
|
279
302
|
return Math.max(0, hybridScore);
|
|
280
303
|
}
|
|
281
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Internal method to clean up vectors based on recency score threshold.
|
|
306
|
+
*
|
|
307
|
+
* @param threshold - The minimum recency score required for a memory to be retained.
|
|
308
|
+
* - Recency score is calculated using exponential decay: 1.0 means brand new, 0.5 means at half-life, 0.0 means fully decayed.
|
|
309
|
+
* - Memories with a recency score below this threshold will be deleted (unless their half-life is infinite or zero).
|
|
310
|
+
* - For example, a threshold of 0.25 will remove all memories whose recency score has decayed 2 times the half-life.
|
|
311
|
+
* - Use a lower threshold to keep more old memories, or a higher threshold to keep only fresher ones.
|
|
312
|
+
*/
|
|
282
313
|
async _cleanupByRecency(threshold) {
|
|
283
314
|
const rows = await this.all(`SELECT id, payload FROM vectors`);
|
|
284
315
|
let deletedCount = 0;
|
|
Binary file
|
|
Binary file
|