cuemap 0.2.0__tar.gz
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.
- cuemap-0.2.0/LICENSE +21 -0
- cuemap-0.2.0/PKG-INFO +274 -0
- cuemap-0.2.0/README.md +255 -0
- cuemap-0.2.0/cuemap/__init__.py +33 -0
- cuemap-0.2.0/cuemap/client.py +316 -0
- cuemap-0.2.0/cuemap/exceptions.py +16 -0
- cuemap-0.2.0/cuemap/models.py +24 -0
- cuemap-0.2.0/cuemap.egg-info/PKG-INFO +274 -0
- cuemap-0.2.0/cuemap.egg-info/SOURCES.txt +12 -0
- cuemap-0.2.0/cuemap.egg-info/dependency_links.txt +1 -0
- cuemap-0.2.0/cuemap.egg-info/requires.txt +6 -0
- cuemap-0.2.0/cuemap.egg-info/top_level.txt +1 -0
- cuemap-0.2.0/pyproject.toml +28 -0
- cuemap-0.2.0/setup.cfg +4 -0
cuemap-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CueMap
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cuemap-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cuemap
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Redis for AI Agents - High-performance temporal-associative memory
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://cuemap.dev
|
|
7
|
+
Project-URL: Documentation, https://docs.cuemap.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/cuemap/cuemap
|
|
9
|
+
Keywords: ai,memory,agents,redis,temporal
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: httpx>=0.25.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# CueMap: Redis for AI Agents
|
|
21
|
+
|
|
22
|
+
**High-performance, temporal-associative memory store.**
|
|
23
|
+
|
|
24
|
+
You give us the cues, we give you the right memory at the right time.
|
|
25
|
+
|
|
26
|
+
## Why CueMap?
|
|
27
|
+
|
|
28
|
+
Redis doesn't auto-serialize your data. It doesn't guess what you mean. It just stores keys and values **blazing fast**.
|
|
29
|
+
|
|
30
|
+
CueMap is the same philosophy for AI agent memory:
|
|
31
|
+
- ✅ **You control the cues** (tags)
|
|
32
|
+
- ✅ **We handle the speed** (sub-millisecond)
|
|
33
|
+
- ✅ **No magic** (predictable behavior)
|
|
34
|
+
- ✅ **No dependencies** (5KB SDK)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install cuemap
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's it. No ML models. No transformers. Just pure speed.
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from cuemap import CueMap
|
|
48
|
+
|
|
49
|
+
client = CueMap()
|
|
50
|
+
|
|
51
|
+
# Add a memory with cues
|
|
52
|
+
client.add(
|
|
53
|
+
"The server password is abc123",
|
|
54
|
+
cues=["server", "password", "credentials"]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Recall by cues
|
|
58
|
+
results = client.recall(["server", "password"])
|
|
59
|
+
print(results[0].content)
|
|
60
|
+
# Output: "The server password is abc123"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Core API
|
|
64
|
+
|
|
65
|
+
### Add Memory
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
memory_id = client.add(
|
|
69
|
+
content="Meeting with John at 3pm",
|
|
70
|
+
cues=["meeting", "john", "calendar", "today"]
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Recall Memories
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
results = client.recall(
|
|
78
|
+
cues=["meeting", "john"],
|
|
79
|
+
limit=10
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for result in results:
|
|
83
|
+
print(f"{result.content} (score: {result.score})")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Reinforce Memory
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# Make a memory more accessible
|
|
90
|
+
client.reinforce(memory_id, cues=["important", "urgent"])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## How It Works
|
|
94
|
+
|
|
95
|
+
### Temporal-Associative Retrieval
|
|
96
|
+
|
|
97
|
+
CueMap uses **Iterative Deepening Intersection**:
|
|
98
|
+
|
|
99
|
+
1. **Intersection**: Memories matching multiple cues rank higher
|
|
100
|
+
2. **Recency**: Recent memories are more accessible
|
|
101
|
+
3. **Reinforcement**: Frequently accessed memories stay "front of mind"
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Add memories
|
|
105
|
+
client.add("Pizza recipe", cues=["food", "italian"])
|
|
106
|
+
client.add("Pasta recipe", cues=["food", "italian"])
|
|
107
|
+
client.add("Sushi recipe", cues=["food", "japanese"])
|
|
108
|
+
|
|
109
|
+
# Query with multiple cues
|
|
110
|
+
results = client.recall(["food", "italian"])
|
|
111
|
+
# Returns: Pizza and Pasta (both match 2 cues)
|
|
112
|
+
# Sushi is filtered out (only matches 1 cue)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Performance
|
|
116
|
+
|
|
117
|
+
- **Write P99**: 0.33ms
|
|
118
|
+
- **Read P99**: 0.37ms
|
|
119
|
+
- **Throughput**: 2,900+ ops/sec
|
|
120
|
+
- **Accuracy**: 100% (validated on 120 test scenarios)
|
|
121
|
+
|
|
122
|
+
## Recipes
|
|
123
|
+
|
|
124
|
+
### Recipe 1: Use with OpenAI
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from cuemap import CueMap
|
|
128
|
+
import openai
|
|
129
|
+
|
|
130
|
+
client = CueMap()
|
|
131
|
+
|
|
132
|
+
def store_with_ai_tags(content: str):
|
|
133
|
+
# Let OpenAI extract the cues
|
|
134
|
+
response = openai.chat.completions.create(
|
|
135
|
+
model="gpt-4",
|
|
136
|
+
messages=[{
|
|
137
|
+
"role": "system",
|
|
138
|
+
"content": "Extract 3-5 search tags from the text. Return as JSON array."
|
|
139
|
+
}, {
|
|
140
|
+
"role": "user",
|
|
141
|
+
"content": content
|
|
142
|
+
}]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
cues = response.choices[0].message.content # ["tag1", "tag2", ...]
|
|
146
|
+
|
|
147
|
+
# Store in CueMap
|
|
148
|
+
return client.add(content, cues=cues)
|
|
149
|
+
|
|
150
|
+
# Usage
|
|
151
|
+
store_with_ai_tags("I need to buy groceries this weekend")
|
|
152
|
+
# OpenAI extracts: ["shopping", "groceries", "weekend", "todo"]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Recipe 2: Use with LangChain
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from cuemap import CueMap
|
|
159
|
+
from langchain.memory import BaseMemory
|
|
160
|
+
|
|
161
|
+
class CueMapMemory(BaseMemory):
|
|
162
|
+
def __init__(self, cue_extractor):
|
|
163
|
+
self.client = CueMap()
|
|
164
|
+
self.extract_cues = cue_extractor
|
|
165
|
+
|
|
166
|
+
def save_context(self, inputs, outputs):
|
|
167
|
+
context = f"User: {inputs['input']}\nAI: {outputs['output']}"
|
|
168
|
+
cues = self.extract_cues(context)
|
|
169
|
+
self.client.add(context, cues=cues)
|
|
170
|
+
|
|
171
|
+
def load_memory_variables(self, inputs):
|
|
172
|
+
cues = self.extract_cues(inputs['input'])
|
|
173
|
+
results = self.client.recall(cues, limit=5)
|
|
174
|
+
return {"history": "\n".join([r.content for r in results])}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Recipe 3: Manual Cues (Production)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# For production: explicit, predictable cues
|
|
181
|
+
client.add(
|
|
182
|
+
"Deploy command: kubectl apply -f deployment.yaml",
|
|
183
|
+
cues=["deployment", "kubernetes", "commands", "devops"]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
client.add(
|
|
187
|
+
"API endpoint: https://api.example.com/v1/users",
|
|
188
|
+
cues=["api", "endpoint", "users", "documentation"]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Query with specific cues
|
|
192
|
+
client.recall(["deployment", "kubernetes"])
|
|
193
|
+
client.recall(["api", "users"])
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
### Authentication
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
client = CueMap(
|
|
202
|
+
url="https://api.cuemap.dev",
|
|
203
|
+
api_key="your-api-key"
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Multi-tenancy
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
client = CueMap(
|
|
211
|
+
url="https://api.cuemap.dev",
|
|
212
|
+
api_key="your-api-key",
|
|
213
|
+
project_id="my-project"
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Async Support
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from cuemap import AsyncCueMap
|
|
221
|
+
|
|
222
|
+
async with AsyncCueMap() as client:
|
|
223
|
+
await client.add("Note", cues=["work"])
|
|
224
|
+
results = await client.recall(["work"])
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Philosophy
|
|
228
|
+
|
|
229
|
+
### What CueMap Does
|
|
230
|
+
|
|
231
|
+
✅ **Fast storage** - Sub-millisecond retrieval
|
|
232
|
+
✅ **Temporal ordering** - Recent memories prioritized
|
|
233
|
+
✅ **Intersection scoring** - Multi-cue matching
|
|
234
|
+
✅ **Reinforcement** - Move-to-front operation
|
|
235
|
+
|
|
236
|
+
### What CueMap Doesn't Do
|
|
237
|
+
|
|
238
|
+
❌ **Auto-tagging** - You provide the cues
|
|
239
|
+
❌ **Semantic search** - Use your own embeddings
|
|
240
|
+
❌ **LLM integration** - Bring your own model
|
|
241
|
+
❌ **Magic** - Explicit and predictable
|
|
242
|
+
|
|
243
|
+
## Why This Approach?
|
|
244
|
+
|
|
245
|
+
**Redis Philosophy**: Don't guess what the user wants. Provide primitives. Let them build.
|
|
246
|
+
|
|
247
|
+
**CueMap Philosophy**: Don't auto-extract cues. Don't auto-embed. Just store and retrieve **fast**.
|
|
248
|
+
|
|
249
|
+
**Benefits**:
|
|
250
|
+
- 🚀 **5KB SDK** (vs 500MB with ML models)
|
|
251
|
+
- ⚡ **Instant install** (1 second vs 5 minutes)
|
|
252
|
+
- 🎯 **Predictable** (no ML black boxes)
|
|
253
|
+
- 🔧 **Flexible** (works with any LLM/embedding model)
|
|
254
|
+
|
|
255
|
+
## Comparison
|
|
256
|
+
|
|
257
|
+
| Feature | CueMap | Vector DBs |
|
|
258
|
+
|---------|--------|------------|
|
|
259
|
+
| **Speed** | 0.37ms P99 | 200-500ms |
|
|
260
|
+
| **SDK Size** | 5KB | 500MB+ |
|
|
261
|
+
| **Dependencies** | 2 | 50+ |
|
|
262
|
+
| **Install Time** | 1 sec | 5 min |
|
|
263
|
+
| **Cue Control** | Explicit | Auto (black box) |
|
|
264
|
+
| **Temporal** | ✅ Built-in | ❌ None |
|
|
265
|
+
|
|
266
|
+
## Documentation
|
|
267
|
+
|
|
268
|
+
- [API Reference](https://docs.cuemap.dev/api)
|
|
269
|
+
- [Recipes](https://docs.cuemap.dev/recipes)
|
|
270
|
+
- [Deployment](https://docs.cuemap.dev/deployment)
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT
|
cuemap-0.2.0/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# CueMap: Redis for AI Agents
|
|
2
|
+
|
|
3
|
+
**High-performance, temporal-associative memory store.**
|
|
4
|
+
|
|
5
|
+
You give us the cues, we give you the right memory at the right time.
|
|
6
|
+
|
|
7
|
+
## Why CueMap?
|
|
8
|
+
|
|
9
|
+
Redis doesn't auto-serialize your data. It doesn't guess what you mean. It just stores keys and values **blazing fast**.
|
|
10
|
+
|
|
11
|
+
CueMap is the same philosophy for AI agent memory:
|
|
12
|
+
- ✅ **You control the cues** (tags)
|
|
13
|
+
- ✅ **We handle the speed** (sub-millisecond)
|
|
14
|
+
- ✅ **No magic** (predictable behavior)
|
|
15
|
+
- ✅ **No dependencies** (5KB SDK)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install cuemap
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That's it. No ML models. No transformers. Just pure speed.
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from cuemap import CueMap
|
|
29
|
+
|
|
30
|
+
client = CueMap()
|
|
31
|
+
|
|
32
|
+
# Add a memory with cues
|
|
33
|
+
client.add(
|
|
34
|
+
"The server password is abc123",
|
|
35
|
+
cues=["server", "password", "credentials"]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Recall by cues
|
|
39
|
+
results = client.recall(["server", "password"])
|
|
40
|
+
print(results[0].content)
|
|
41
|
+
# Output: "The server password is abc123"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Core API
|
|
45
|
+
|
|
46
|
+
### Add Memory
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
memory_id = client.add(
|
|
50
|
+
content="Meeting with John at 3pm",
|
|
51
|
+
cues=["meeting", "john", "calendar", "today"]
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Recall Memories
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
results = client.recall(
|
|
59
|
+
cues=["meeting", "john"],
|
|
60
|
+
limit=10
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
for result in results:
|
|
64
|
+
print(f"{result.content} (score: {result.score})")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Reinforce Memory
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Make a memory more accessible
|
|
71
|
+
client.reinforce(memory_id, cues=["important", "urgent"])
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## How It Works
|
|
75
|
+
|
|
76
|
+
### Temporal-Associative Retrieval
|
|
77
|
+
|
|
78
|
+
CueMap uses **Iterative Deepening Intersection**:
|
|
79
|
+
|
|
80
|
+
1. **Intersection**: Memories matching multiple cues rank higher
|
|
81
|
+
2. **Recency**: Recent memories are more accessible
|
|
82
|
+
3. **Reinforcement**: Frequently accessed memories stay "front of mind"
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# Add memories
|
|
86
|
+
client.add("Pizza recipe", cues=["food", "italian"])
|
|
87
|
+
client.add("Pasta recipe", cues=["food", "italian"])
|
|
88
|
+
client.add("Sushi recipe", cues=["food", "japanese"])
|
|
89
|
+
|
|
90
|
+
# Query with multiple cues
|
|
91
|
+
results = client.recall(["food", "italian"])
|
|
92
|
+
# Returns: Pizza and Pasta (both match 2 cues)
|
|
93
|
+
# Sushi is filtered out (only matches 1 cue)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Performance
|
|
97
|
+
|
|
98
|
+
- **Write P99**: 0.33ms
|
|
99
|
+
- **Read P99**: 0.37ms
|
|
100
|
+
- **Throughput**: 2,900+ ops/sec
|
|
101
|
+
- **Accuracy**: 100% (validated on 120 test scenarios)
|
|
102
|
+
|
|
103
|
+
## Recipes
|
|
104
|
+
|
|
105
|
+
### Recipe 1: Use with OpenAI
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from cuemap import CueMap
|
|
109
|
+
import openai
|
|
110
|
+
|
|
111
|
+
client = CueMap()
|
|
112
|
+
|
|
113
|
+
def store_with_ai_tags(content: str):
|
|
114
|
+
# Let OpenAI extract the cues
|
|
115
|
+
response = openai.chat.completions.create(
|
|
116
|
+
model="gpt-4",
|
|
117
|
+
messages=[{
|
|
118
|
+
"role": "system",
|
|
119
|
+
"content": "Extract 3-5 search tags from the text. Return as JSON array."
|
|
120
|
+
}, {
|
|
121
|
+
"role": "user",
|
|
122
|
+
"content": content
|
|
123
|
+
}]
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
cues = response.choices[0].message.content # ["tag1", "tag2", ...]
|
|
127
|
+
|
|
128
|
+
# Store in CueMap
|
|
129
|
+
return client.add(content, cues=cues)
|
|
130
|
+
|
|
131
|
+
# Usage
|
|
132
|
+
store_with_ai_tags("I need to buy groceries this weekend")
|
|
133
|
+
# OpenAI extracts: ["shopping", "groceries", "weekend", "todo"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Recipe 2: Use with LangChain
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from cuemap import CueMap
|
|
140
|
+
from langchain.memory import BaseMemory
|
|
141
|
+
|
|
142
|
+
class CueMapMemory(BaseMemory):
|
|
143
|
+
def __init__(self, cue_extractor):
|
|
144
|
+
self.client = CueMap()
|
|
145
|
+
self.extract_cues = cue_extractor
|
|
146
|
+
|
|
147
|
+
def save_context(self, inputs, outputs):
|
|
148
|
+
context = f"User: {inputs['input']}\nAI: {outputs['output']}"
|
|
149
|
+
cues = self.extract_cues(context)
|
|
150
|
+
self.client.add(context, cues=cues)
|
|
151
|
+
|
|
152
|
+
def load_memory_variables(self, inputs):
|
|
153
|
+
cues = self.extract_cues(inputs['input'])
|
|
154
|
+
results = self.client.recall(cues, limit=5)
|
|
155
|
+
return {"history": "\n".join([r.content for r in results])}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Recipe 3: Manual Cues (Production)
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
# For production: explicit, predictable cues
|
|
162
|
+
client.add(
|
|
163
|
+
"Deploy command: kubectl apply -f deployment.yaml",
|
|
164
|
+
cues=["deployment", "kubernetes", "commands", "devops"]
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
client.add(
|
|
168
|
+
"API endpoint: https://api.example.com/v1/users",
|
|
169
|
+
cues=["api", "endpoint", "users", "documentation"]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Query with specific cues
|
|
173
|
+
client.recall(["deployment", "kubernetes"])
|
|
174
|
+
client.recall(["api", "users"])
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
### Authentication
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
client = CueMap(
|
|
183
|
+
url="https://api.cuemap.dev",
|
|
184
|
+
api_key="your-api-key"
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Multi-tenancy
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
client = CueMap(
|
|
192
|
+
url="https://api.cuemap.dev",
|
|
193
|
+
api_key="your-api-key",
|
|
194
|
+
project_id="my-project"
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Async Support
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from cuemap import AsyncCueMap
|
|
202
|
+
|
|
203
|
+
async with AsyncCueMap() as client:
|
|
204
|
+
await client.add("Note", cues=["work"])
|
|
205
|
+
results = await client.recall(["work"])
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Philosophy
|
|
209
|
+
|
|
210
|
+
### What CueMap Does
|
|
211
|
+
|
|
212
|
+
✅ **Fast storage** - Sub-millisecond retrieval
|
|
213
|
+
✅ **Temporal ordering** - Recent memories prioritized
|
|
214
|
+
✅ **Intersection scoring** - Multi-cue matching
|
|
215
|
+
✅ **Reinforcement** - Move-to-front operation
|
|
216
|
+
|
|
217
|
+
### What CueMap Doesn't Do
|
|
218
|
+
|
|
219
|
+
❌ **Auto-tagging** - You provide the cues
|
|
220
|
+
❌ **Semantic search** - Use your own embeddings
|
|
221
|
+
❌ **LLM integration** - Bring your own model
|
|
222
|
+
❌ **Magic** - Explicit and predictable
|
|
223
|
+
|
|
224
|
+
## Why This Approach?
|
|
225
|
+
|
|
226
|
+
**Redis Philosophy**: Don't guess what the user wants. Provide primitives. Let them build.
|
|
227
|
+
|
|
228
|
+
**CueMap Philosophy**: Don't auto-extract cues. Don't auto-embed. Just store and retrieve **fast**.
|
|
229
|
+
|
|
230
|
+
**Benefits**:
|
|
231
|
+
- 🚀 **5KB SDK** (vs 500MB with ML models)
|
|
232
|
+
- ⚡ **Instant install** (1 second vs 5 minutes)
|
|
233
|
+
- 🎯 **Predictable** (no ML black boxes)
|
|
234
|
+
- 🔧 **Flexible** (works with any LLM/embedding model)
|
|
235
|
+
|
|
236
|
+
## Comparison
|
|
237
|
+
|
|
238
|
+
| Feature | CueMap | Vector DBs |
|
|
239
|
+
|---------|--------|------------|
|
|
240
|
+
| **Speed** | 0.37ms P99 | 200-500ms |
|
|
241
|
+
| **SDK Size** | 5KB | 500MB+ |
|
|
242
|
+
| **Dependencies** | 2 | 50+ |
|
|
243
|
+
| **Install Time** | 1 sec | 5 min |
|
|
244
|
+
| **Cue Control** | Explicit | Auto (black box) |
|
|
245
|
+
| **Temporal** | ✅ Built-in | ❌ None |
|
|
246
|
+
|
|
247
|
+
## Documentation
|
|
248
|
+
|
|
249
|
+
- [API Reference](https://docs.cuemap.dev/api)
|
|
250
|
+
- [Recipes](https://docs.cuemap.dev/recipes)
|
|
251
|
+
- [Deployment](https://docs.cuemap.dev/deployment)
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CueMap: Redis for AI Agents
|
|
3
|
+
|
|
4
|
+
A high-performance, temporal-associative memory store.
|
|
5
|
+
You give us the cues, we give you the right memory at the right time.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from cuemap import CueMap
|
|
9
|
+
>>>
|
|
10
|
+
>>> client = CueMap()
|
|
11
|
+
>>>
|
|
12
|
+
>>> # Add a memory with cues
|
|
13
|
+
>>> client.add("The server password is abc123", cues=["server", "password"])
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Recall by cues
|
|
16
|
+
>>> results = client.recall(["server", "password"])
|
|
17
|
+
>>> print(results[0].content)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .client import CueMap, AsyncCueMap
|
|
21
|
+
from .models import Memory, RecallResult
|
|
22
|
+
from .exceptions import CueMapError, ConnectionError, AuthenticationError
|
|
23
|
+
|
|
24
|
+
__version__ = "0.2.0"
|
|
25
|
+
__all__ = [
|
|
26
|
+
"CueMap",
|
|
27
|
+
"AsyncCueMap",
|
|
28
|
+
"Memory",
|
|
29
|
+
"RecallResult",
|
|
30
|
+
"CueMapError",
|
|
31
|
+
"ConnectionError",
|
|
32
|
+
"AuthenticationError",
|
|
33
|
+
]
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Pure CueMap client - no magic, just speed."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
|
|
6
|
+
from .models import Memory, RecallResult
|
|
7
|
+
from .exceptions import CueMapError, ConnectionError, AuthenticationError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CueMap:
|
|
11
|
+
"""
|
|
12
|
+
Pure CueMap client.
|
|
13
|
+
|
|
14
|
+
No auto-cue extraction. No semantic matching. Just fast memory storage.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> client = CueMap()
|
|
18
|
+
>>> client.add("Important note", cues=["work", "urgent"])
|
|
19
|
+
>>> results = client.recall(["work"])
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
url: str = "http://localhost:8080",
|
|
25
|
+
api_key: Optional[str] = None,
|
|
26
|
+
project_id: Optional[str] = None,
|
|
27
|
+
timeout: float = 30.0
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Initialize CueMap client.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
url: CueMap server URL
|
|
34
|
+
api_key: Optional API key for authentication
|
|
35
|
+
project_id: Optional project ID for multi-tenancy
|
|
36
|
+
timeout: Request timeout in seconds
|
|
37
|
+
"""
|
|
38
|
+
self.url = url
|
|
39
|
+
self.api_key = api_key
|
|
40
|
+
self.project_id = project_id
|
|
41
|
+
|
|
42
|
+
self.client = httpx.Client(
|
|
43
|
+
base_url=url,
|
|
44
|
+
timeout=timeout
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def _headers(self) -> Dict[str, str]:
|
|
48
|
+
"""Get request headers."""
|
|
49
|
+
headers = {}
|
|
50
|
+
if self.api_key:
|
|
51
|
+
headers["x-api-key"] = self.api_key
|
|
52
|
+
return headers
|
|
53
|
+
|
|
54
|
+
def _endpoint(self, path: str) -> str:
|
|
55
|
+
"""Get endpoint with optional project_id."""
|
|
56
|
+
if self.project_id:
|
|
57
|
+
return f"/v1/{self.project_id}{path}"
|
|
58
|
+
return path
|
|
59
|
+
|
|
60
|
+
def add(
|
|
61
|
+
self,
|
|
62
|
+
content: str,
|
|
63
|
+
cues: List[str],
|
|
64
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
65
|
+
) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Add a memory.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
content: Memory content
|
|
71
|
+
cues: List of cues (tags) for retrieval
|
|
72
|
+
metadata: Optional metadata
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Memory ID
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
>>> client.add(
|
|
79
|
+
... "Meeting with John at 3pm",
|
|
80
|
+
... cues=["meeting", "john", "calendar"]
|
|
81
|
+
... )
|
|
82
|
+
"""
|
|
83
|
+
response = self.client.post(
|
|
84
|
+
self._endpoint("/memories"),
|
|
85
|
+
json={
|
|
86
|
+
"content": content,
|
|
87
|
+
"cues": cues,
|
|
88
|
+
"metadata": metadata or {}
|
|
89
|
+
},
|
|
90
|
+
headers=self._headers()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if response.status_code == 401:
|
|
94
|
+
raise AuthenticationError("Invalid API key")
|
|
95
|
+
elif response.status_code != 200:
|
|
96
|
+
raise CueMapError(f"Failed to add memory: {response.status_code}")
|
|
97
|
+
|
|
98
|
+
return response.json()["id"]
|
|
99
|
+
|
|
100
|
+
def recall(
|
|
101
|
+
self,
|
|
102
|
+
cues: List[str],
|
|
103
|
+
limit: int = 10,
|
|
104
|
+
auto_reinforce: bool = False
|
|
105
|
+
) -> List[RecallResult]:
|
|
106
|
+
"""
|
|
107
|
+
Recall memories by cues.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
cues: List of cues to search for
|
|
111
|
+
limit: Maximum results to return
|
|
112
|
+
auto_reinforce: Automatically reinforce retrieved memories
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of recall results
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
>>> results = client.recall(["meeting", "john"])
|
|
119
|
+
>>> for r in results:
|
|
120
|
+
... print(r.content)
|
|
121
|
+
"""
|
|
122
|
+
response = self.client.post(
|
|
123
|
+
self._endpoint("/recall"),
|
|
124
|
+
json={
|
|
125
|
+
"cues": cues,
|
|
126
|
+
"limit": limit,
|
|
127
|
+
"auto_reinforce": auto_reinforce
|
|
128
|
+
},
|
|
129
|
+
headers=self._headers()
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if response.status_code != 200:
|
|
133
|
+
raise CueMapError(f"Failed to recall: {response.status_code}")
|
|
134
|
+
|
|
135
|
+
results = response.json()["results"]
|
|
136
|
+
return [RecallResult(**r) for r in results]
|
|
137
|
+
|
|
138
|
+
def reinforce(self, memory_id: str, cues: List[str]) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Reinforce a memory on specific cue pathways.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
memory_id: Memory ID
|
|
144
|
+
cues: Cues to reinforce on
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Success status
|
|
148
|
+
"""
|
|
149
|
+
response = self.client.patch(
|
|
150
|
+
self._endpoint(f"/memories/{memory_id}/reinforce"),
|
|
151
|
+
json={"cues": cues},
|
|
152
|
+
headers=self._headers()
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return response.status_code == 200
|
|
156
|
+
|
|
157
|
+
def get(self, memory_id: str) -> Memory:
|
|
158
|
+
"""Get a memory by ID."""
|
|
159
|
+
response = self.client.get(
|
|
160
|
+
self._endpoint(f"/memories/{memory_id}"),
|
|
161
|
+
headers=self._headers()
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if response.status_code == 404:
|
|
165
|
+
raise CueMapError(f"Memory not found: {memory_id}")
|
|
166
|
+
elif response.status_code != 200:
|
|
167
|
+
raise CueMapError(f"Failed to get memory: {response.status_code}")
|
|
168
|
+
|
|
169
|
+
return Memory(**response.json())
|
|
170
|
+
|
|
171
|
+
def stats(self) -> Dict[str, Any]:
|
|
172
|
+
"""Get server statistics."""
|
|
173
|
+
response = self.client.get(
|
|
174
|
+
self._endpoint("/stats"),
|
|
175
|
+
headers=self._headers()
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return response.json()
|
|
179
|
+
|
|
180
|
+
def close(self):
|
|
181
|
+
"""Close the client."""
|
|
182
|
+
self.client.close()
|
|
183
|
+
|
|
184
|
+
def __enter__(self):
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
188
|
+
self.close()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AsyncCueMap:
|
|
192
|
+
"""
|
|
193
|
+
Async CueMap client.
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
>>> async with AsyncCueMap() as client:
|
|
197
|
+
... await client.add("Note", cues=["work"])
|
|
198
|
+
... results = await client.recall(["work"])
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def __init__(
|
|
202
|
+
self,
|
|
203
|
+
url: str = "http://localhost:8080",
|
|
204
|
+
api_key: Optional[str] = None,
|
|
205
|
+
project_id: Optional[str] = None,
|
|
206
|
+
timeout: float = 30.0
|
|
207
|
+
):
|
|
208
|
+
self.url = url
|
|
209
|
+
self.api_key = api_key
|
|
210
|
+
self.project_id = project_id
|
|
211
|
+
|
|
212
|
+
self.client = httpx.AsyncClient(
|
|
213
|
+
base_url=url,
|
|
214
|
+
timeout=timeout
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _headers(self) -> Dict[str, str]:
|
|
218
|
+
headers = {}
|
|
219
|
+
if self.api_key:
|
|
220
|
+
headers["x-api-key"] = self.api_key
|
|
221
|
+
return headers
|
|
222
|
+
|
|
223
|
+
def _endpoint(self, path: str) -> str:
|
|
224
|
+
if self.project_id:
|
|
225
|
+
return f"/v1/{self.project_id}{path}"
|
|
226
|
+
return path
|
|
227
|
+
|
|
228
|
+
async def add(
|
|
229
|
+
self,
|
|
230
|
+
content: str,
|
|
231
|
+
cues: List[str],
|
|
232
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
233
|
+
) -> str:
|
|
234
|
+
"""Add a memory (async)."""
|
|
235
|
+
response = await self.client.post(
|
|
236
|
+
self._endpoint("/memories"),
|
|
237
|
+
json={
|
|
238
|
+
"content": content,
|
|
239
|
+
"cues": cues,
|
|
240
|
+
"metadata": metadata or {}
|
|
241
|
+
},
|
|
242
|
+
headers=self._headers()
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if response.status_code == 401:
|
|
246
|
+
raise AuthenticationError("Invalid API key")
|
|
247
|
+
elif response.status_code != 200:
|
|
248
|
+
raise CueMapError(f"Failed to add memory: {response.status_code}")
|
|
249
|
+
|
|
250
|
+
return response.json()["id"]
|
|
251
|
+
|
|
252
|
+
async def recall(
|
|
253
|
+
self,
|
|
254
|
+
cues: List[str],
|
|
255
|
+
limit: int = 10,
|
|
256
|
+
auto_reinforce: bool = False
|
|
257
|
+
) -> List[RecallResult]:
|
|
258
|
+
"""Recall memories (async)."""
|
|
259
|
+
response = await self.client.post(
|
|
260
|
+
self._endpoint("/recall"),
|
|
261
|
+
json={
|
|
262
|
+
"cues": cues,
|
|
263
|
+
"limit": limit,
|
|
264
|
+
"auto_reinforce": auto_reinforce
|
|
265
|
+
},
|
|
266
|
+
headers=self._headers()
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if response.status_code != 200:
|
|
270
|
+
raise CueMapError(f"Failed to recall: {response.status_code}")
|
|
271
|
+
|
|
272
|
+
results = response.json()["results"]
|
|
273
|
+
return [RecallResult(**r) for r in results]
|
|
274
|
+
|
|
275
|
+
async def reinforce(self, memory_id: str, cues: List[str]) -> bool:
|
|
276
|
+
"""Reinforce a memory (async)."""
|
|
277
|
+
response = await self.client.patch(
|
|
278
|
+
self._endpoint(f"/memories/{memory_id}/reinforce"),
|
|
279
|
+
json={"cues": cues},
|
|
280
|
+
headers=self._headers()
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return response.status_code == 200
|
|
284
|
+
|
|
285
|
+
async def get(self, memory_id: str) -> Memory:
|
|
286
|
+
"""Get a memory by ID (async)."""
|
|
287
|
+
response = await self.client.get(
|
|
288
|
+
self._endpoint(f"/memories/{memory_id}"),
|
|
289
|
+
headers=self._headers()
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if response.status_code == 404:
|
|
293
|
+
raise CueMapError(f"Memory not found: {memory_id}")
|
|
294
|
+
elif response.status_code != 200:
|
|
295
|
+
raise CueMapError(f"Failed to get memory: {response.status_code}")
|
|
296
|
+
|
|
297
|
+
return Memory(**response.json())
|
|
298
|
+
|
|
299
|
+
async def stats(self) -> Dict[str, Any]:
|
|
300
|
+
"""Get server statistics (async)."""
|
|
301
|
+
response = await self.client.get(
|
|
302
|
+
self._endpoint("/stats"),
|
|
303
|
+
headers=self._headers()
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return response.json()
|
|
307
|
+
|
|
308
|
+
async def close(self):
|
|
309
|
+
"""Close the client."""
|
|
310
|
+
await self.client.aclose()
|
|
311
|
+
|
|
312
|
+
async def __aenter__(self):
|
|
313
|
+
return self
|
|
314
|
+
|
|
315
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
316
|
+
await self.close()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Exceptions."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CueMapError(Exception):
|
|
5
|
+
"""Base exception."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConnectionError(CueMapError):
|
|
10
|
+
"""Connection failed."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthenticationError(CueMapError):
|
|
15
|
+
"""Authentication failed."""
|
|
16
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Data models."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Memory(BaseModel):
|
|
8
|
+
"""A memory object."""
|
|
9
|
+
|
|
10
|
+
id: str
|
|
11
|
+
content: str
|
|
12
|
+
cues: List[str] = Field(default_factory=list)
|
|
13
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RecallResult(BaseModel):
|
|
17
|
+
"""Result from a recall operation."""
|
|
18
|
+
|
|
19
|
+
memory_id: str
|
|
20
|
+
content: str
|
|
21
|
+
score: float
|
|
22
|
+
intersection_count: int
|
|
23
|
+
recency_score: float
|
|
24
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cuemap
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Redis for AI Agents - High-performance temporal-associative memory
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://cuemap.dev
|
|
7
|
+
Project-URL: Documentation, https://docs.cuemap.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/cuemap/cuemap
|
|
9
|
+
Keywords: ai,memory,agents,redis,temporal
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: httpx>=0.25.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# CueMap: Redis for AI Agents
|
|
21
|
+
|
|
22
|
+
**High-performance, temporal-associative memory store.**
|
|
23
|
+
|
|
24
|
+
You give us the cues, we give you the right memory at the right time.
|
|
25
|
+
|
|
26
|
+
## Why CueMap?
|
|
27
|
+
|
|
28
|
+
Redis doesn't auto-serialize your data. It doesn't guess what you mean. It just stores keys and values **blazing fast**.
|
|
29
|
+
|
|
30
|
+
CueMap is the same philosophy for AI agent memory:
|
|
31
|
+
- ✅ **You control the cues** (tags)
|
|
32
|
+
- ✅ **We handle the speed** (sub-millisecond)
|
|
33
|
+
- ✅ **No magic** (predictable behavior)
|
|
34
|
+
- ✅ **No dependencies** (5KB SDK)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install cuemap
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's it. No ML models. No transformers. Just pure speed.
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from cuemap import CueMap
|
|
48
|
+
|
|
49
|
+
client = CueMap()
|
|
50
|
+
|
|
51
|
+
# Add a memory with cues
|
|
52
|
+
client.add(
|
|
53
|
+
"The server password is abc123",
|
|
54
|
+
cues=["server", "password", "credentials"]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Recall by cues
|
|
58
|
+
results = client.recall(["server", "password"])
|
|
59
|
+
print(results[0].content)
|
|
60
|
+
# Output: "The server password is abc123"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Core API
|
|
64
|
+
|
|
65
|
+
### Add Memory
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
memory_id = client.add(
|
|
69
|
+
content="Meeting with John at 3pm",
|
|
70
|
+
cues=["meeting", "john", "calendar", "today"]
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Recall Memories
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
results = client.recall(
|
|
78
|
+
cues=["meeting", "john"],
|
|
79
|
+
limit=10
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for result in results:
|
|
83
|
+
print(f"{result.content} (score: {result.score})")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Reinforce Memory
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# Make a memory more accessible
|
|
90
|
+
client.reinforce(memory_id, cues=["important", "urgent"])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## How It Works
|
|
94
|
+
|
|
95
|
+
### Temporal-Associative Retrieval
|
|
96
|
+
|
|
97
|
+
CueMap uses **Iterative Deepening Intersection**:
|
|
98
|
+
|
|
99
|
+
1. **Intersection**: Memories matching multiple cues rank higher
|
|
100
|
+
2. **Recency**: Recent memories are more accessible
|
|
101
|
+
3. **Reinforcement**: Frequently accessed memories stay "front of mind"
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Add memories
|
|
105
|
+
client.add("Pizza recipe", cues=["food", "italian"])
|
|
106
|
+
client.add("Pasta recipe", cues=["food", "italian"])
|
|
107
|
+
client.add("Sushi recipe", cues=["food", "japanese"])
|
|
108
|
+
|
|
109
|
+
# Query with multiple cues
|
|
110
|
+
results = client.recall(["food", "italian"])
|
|
111
|
+
# Returns: Pizza and Pasta (both match 2 cues)
|
|
112
|
+
# Sushi is filtered out (only matches 1 cue)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Performance
|
|
116
|
+
|
|
117
|
+
- **Write P99**: 0.33ms
|
|
118
|
+
- **Read P99**: 0.37ms
|
|
119
|
+
- **Throughput**: 2,900+ ops/sec
|
|
120
|
+
- **Accuracy**: 100% (validated on 120 test scenarios)
|
|
121
|
+
|
|
122
|
+
## Recipes
|
|
123
|
+
|
|
124
|
+
### Recipe 1: Use with OpenAI
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from cuemap import CueMap
|
|
128
|
+
import openai
|
|
129
|
+
|
|
130
|
+
client = CueMap()
|
|
131
|
+
|
|
132
|
+
def store_with_ai_tags(content: str):
|
|
133
|
+
# Let OpenAI extract the cues
|
|
134
|
+
response = openai.chat.completions.create(
|
|
135
|
+
model="gpt-4",
|
|
136
|
+
messages=[{
|
|
137
|
+
"role": "system",
|
|
138
|
+
"content": "Extract 3-5 search tags from the text. Return as JSON array."
|
|
139
|
+
}, {
|
|
140
|
+
"role": "user",
|
|
141
|
+
"content": content
|
|
142
|
+
}]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
cues = response.choices[0].message.content # ["tag1", "tag2", ...]
|
|
146
|
+
|
|
147
|
+
# Store in CueMap
|
|
148
|
+
return client.add(content, cues=cues)
|
|
149
|
+
|
|
150
|
+
# Usage
|
|
151
|
+
store_with_ai_tags("I need to buy groceries this weekend")
|
|
152
|
+
# OpenAI extracts: ["shopping", "groceries", "weekend", "todo"]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Recipe 2: Use with LangChain
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from cuemap import CueMap
|
|
159
|
+
from langchain.memory import BaseMemory
|
|
160
|
+
|
|
161
|
+
class CueMapMemory(BaseMemory):
|
|
162
|
+
def __init__(self, cue_extractor):
|
|
163
|
+
self.client = CueMap()
|
|
164
|
+
self.extract_cues = cue_extractor
|
|
165
|
+
|
|
166
|
+
def save_context(self, inputs, outputs):
|
|
167
|
+
context = f"User: {inputs['input']}\nAI: {outputs['output']}"
|
|
168
|
+
cues = self.extract_cues(context)
|
|
169
|
+
self.client.add(context, cues=cues)
|
|
170
|
+
|
|
171
|
+
def load_memory_variables(self, inputs):
|
|
172
|
+
cues = self.extract_cues(inputs['input'])
|
|
173
|
+
results = self.client.recall(cues, limit=5)
|
|
174
|
+
return {"history": "\n".join([r.content for r in results])}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Recipe 3: Manual Cues (Production)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# For production: explicit, predictable cues
|
|
181
|
+
client.add(
|
|
182
|
+
"Deploy command: kubectl apply -f deployment.yaml",
|
|
183
|
+
cues=["deployment", "kubernetes", "commands", "devops"]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
client.add(
|
|
187
|
+
"API endpoint: https://api.example.com/v1/users",
|
|
188
|
+
cues=["api", "endpoint", "users", "documentation"]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Query with specific cues
|
|
192
|
+
client.recall(["deployment", "kubernetes"])
|
|
193
|
+
client.recall(["api", "users"])
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
### Authentication
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
client = CueMap(
|
|
202
|
+
url="https://api.cuemap.dev",
|
|
203
|
+
api_key="your-api-key"
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Multi-tenancy
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
client = CueMap(
|
|
211
|
+
url="https://api.cuemap.dev",
|
|
212
|
+
api_key="your-api-key",
|
|
213
|
+
project_id="my-project"
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Async Support
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from cuemap import AsyncCueMap
|
|
221
|
+
|
|
222
|
+
async with AsyncCueMap() as client:
|
|
223
|
+
await client.add("Note", cues=["work"])
|
|
224
|
+
results = await client.recall(["work"])
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Philosophy
|
|
228
|
+
|
|
229
|
+
### What CueMap Does
|
|
230
|
+
|
|
231
|
+
✅ **Fast storage** - Sub-millisecond retrieval
|
|
232
|
+
✅ **Temporal ordering** - Recent memories prioritized
|
|
233
|
+
✅ **Intersection scoring** - Multi-cue matching
|
|
234
|
+
✅ **Reinforcement** - Move-to-front operation
|
|
235
|
+
|
|
236
|
+
### What CueMap Doesn't Do
|
|
237
|
+
|
|
238
|
+
❌ **Auto-tagging** - You provide the cues
|
|
239
|
+
❌ **Semantic search** - Use your own embeddings
|
|
240
|
+
❌ **LLM integration** - Bring your own model
|
|
241
|
+
❌ **Magic** - Explicit and predictable
|
|
242
|
+
|
|
243
|
+
## Why This Approach?
|
|
244
|
+
|
|
245
|
+
**Redis Philosophy**: Don't guess what the user wants. Provide primitives. Let them build.
|
|
246
|
+
|
|
247
|
+
**CueMap Philosophy**: Don't auto-extract cues. Don't auto-embed. Just store and retrieve **fast**.
|
|
248
|
+
|
|
249
|
+
**Benefits**:
|
|
250
|
+
- 🚀 **5KB SDK** (vs 500MB with ML models)
|
|
251
|
+
- ⚡ **Instant install** (1 second vs 5 minutes)
|
|
252
|
+
- 🎯 **Predictable** (no ML black boxes)
|
|
253
|
+
- 🔧 **Flexible** (works with any LLM/embedding model)
|
|
254
|
+
|
|
255
|
+
## Comparison
|
|
256
|
+
|
|
257
|
+
| Feature | CueMap | Vector DBs |
|
|
258
|
+
|---------|--------|------------|
|
|
259
|
+
| **Speed** | 0.37ms P99 | 200-500ms |
|
|
260
|
+
| **SDK Size** | 5KB | 500MB+ |
|
|
261
|
+
| **Dependencies** | 2 | 50+ |
|
|
262
|
+
| **Install Time** | 1 sec | 5 min |
|
|
263
|
+
| **Cue Control** | Explicit | Auto (black box) |
|
|
264
|
+
| **Temporal** | ✅ Built-in | ❌ None |
|
|
265
|
+
|
|
266
|
+
## Documentation
|
|
267
|
+
|
|
268
|
+
- [API Reference](https://docs.cuemap.dev/api)
|
|
269
|
+
- [Recipes](https://docs.cuemap.dev/recipes)
|
|
270
|
+
- [Deployment](https://docs.cuemap.dev/deployment)
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
cuemap/__init__.py
|
|
5
|
+
cuemap/client.py
|
|
6
|
+
cuemap/exceptions.py
|
|
7
|
+
cuemap/models.py
|
|
8
|
+
cuemap.egg-info/PKG-INFO
|
|
9
|
+
cuemap.egg-info/SOURCES.txt
|
|
10
|
+
cuemap.egg-info/dependency_links.txt
|
|
11
|
+
cuemap.egg-info/requires.txt
|
|
12
|
+
cuemap.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cuemap
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cuemap"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Redis for AI Agents - High-performance temporal-associative memory"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
keywords = ["ai", "memory", "agents", "redis", "temporal"]
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
"httpx>=0.25.0",
|
|
16
|
+
"pydantic>=2.0.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=7.0.0",
|
|
22
|
+
"pytest-asyncio>=0.21.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://cuemap.dev"
|
|
27
|
+
Documentation = "https://docs.cuemap.dev"
|
|
28
|
+
Repository = "https://github.com/cuemap/cuemap"
|
cuemap-0.2.0/setup.cfg
ADDED