dspy-lancedb-memory 0.1.4__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.
- dspy_lancedb_memory-0.1.4/LICENSE +21 -0
- dspy_lancedb_memory-0.1.4/PKG-INFO +561 -0
- dspy_lancedb_memory-0.1.4/README.md +532 -0
- dspy_lancedb_memory-0.1.4/pyproject.toml +66 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/__init__.py +35 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/config.py +136 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/extraction.py +234 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/memory.py +214 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/models.py +139 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/reranking.py +200 -0
- dspy_lancedb_memory-0.1.4/src/dspy_lancedb_memory/store.py +1022 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edward Boswell
|
|
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.
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dspy-lancedb-memory
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Keywords: dspy,ai,memory,lancedb
|
|
6
|
+
Author: Edward Boswell
|
|
7
|
+
Author-email: Edward Boswell <thememium@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Dist: dspy>=3.2.0
|
|
17
|
+
Requires-Dist: httpx>=0.28.1
|
|
18
|
+
Requires-Dist: lancedb<=0.25.3
|
|
19
|
+
Requires-Dist: litellm>=1.82.6
|
|
20
|
+
Requires-Dist: pyarrow>=24.0.0
|
|
21
|
+
Requires-Dist: pydantic>=2.13.3
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Project-URL: Homepage, https://github.com/thememium/dspy-lancedb-memory
|
|
24
|
+
Project-URL: Documentation, https://github.com/thememium/dspy-lancedb-memory
|
|
25
|
+
Project-URL: Repository, https://github.com/thememium/dspy-lancedb-memory.git
|
|
26
|
+
Project-URL: Issues, https://github.com/thememium/dspy-lancedb-memory/issues
|
|
27
|
+
Project-URL: Changelog, https://github.com/thememium/dspy-lancedb-memory/blob/master/CHANGELOG.md
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<a name="readme-top"></a>
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
<h3 align="center">DSPy LanceDB Memory</h3>
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
Persistent vector memory store for DSPy-powered AI agents — extract, store, and recall structured memories from conversation.
|
|
37
|
+
<br />
|
|
38
|
+
<a href="#table-of-contents"><strong>Explore the Documentation »</strong></a>
|
|
39
|
+
<br />
|
|
40
|
+
<a href="https://github.com/thememium/dspy-lancedb-memory/issues">Report Bug</a>
|
|
41
|
+
·
|
|
42
|
+
<a href="https://github.com/thememium/dspy-lancedb-memory/issues">Request Feature</a>
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- TABLE OF CONTENTS -->
|
|
47
|
+
|
|
48
|
+
<a name="table-of-contents"></a>
|
|
49
|
+
|
|
50
|
+
<details>
|
|
51
|
+
<summary>Table of Contents</summary>
|
|
52
|
+
<ol>
|
|
53
|
+
<li><a href="#about">About</a></li>
|
|
54
|
+
<li><a href="#quick-start">Quick Start</a></li>
|
|
55
|
+
<li><a href="#usage">Usage</a></li>
|
|
56
|
+
<li><a href="#memory-taxonomy">Memory Taxonomy</a></li>
|
|
57
|
+
<li><a href="#development">Development</a></li>
|
|
58
|
+
<li><a href="#contributing">Contributing</a></li>
|
|
59
|
+
<li><a href="#license">License</a></li>
|
|
60
|
+
</ol>
|
|
61
|
+
</details>
|
|
62
|
+
|
|
63
|
+
<!-- ABOUT -->
|
|
64
|
+
|
|
65
|
+
## About
|
|
66
|
+
|
|
67
|
+
DSPy Memory is a persistent vector memory store for DSPy-powered AI agents. It uses DSPy signatures to extract structured, categorized memories from conversation turns and stores them in LanceDB for efficient semantic retrieval.
|
|
68
|
+
|
|
69
|
+
- **Method-based SDK** — Single ``memory.configure()`` entry point for extraction LM, embedding model, and reranker
|
|
70
|
+
- **DSPy-native extraction** — Uses `ChainOfThought` with a typed `ExtractMemory` signature to pull salient information from conversations
|
|
71
|
+
- **Structured memory taxonomy** — Six memory categories (preference, semantic, episodic, procedural, summary, artifact) for fine-grained organization
|
|
72
|
+
- **Persistent vector storage** — LanceDB-backed with automatic text embeddings via the DSPy `Embedder`
|
|
73
|
+
- **Semantic search** — Query memories by user ID, session ID, conversation ID, memory type, or natural language
|
|
74
|
+
- **Optional reranking** — ``LiteLLMReranker`` wraps ``litellm.rerank()`` for cross-encoder reranking via Cohere, Jina, and any LiteLLM-compatible provider
|
|
75
|
+
- **Full CRUD** — Create, search, update, and delete individual memories or batch-extract from conversations
|
|
76
|
+
|
|
77
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
78
|
+
|
|
79
|
+
<!-- QUICK START -->
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
### Prerequisites
|
|
84
|
+
|
|
85
|
+
Set the API key environment variable for your chosen provider. LiteLLM routes to the correct key automatically based on the model prefix:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Examples — set whichever matches your provider
|
|
89
|
+
export OPENAI_API_KEY="sk-..."
|
|
90
|
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
91
|
+
export CO_API_KEY="..." # Cohere reranker
|
|
92
|
+
export JINA_API_KEY="..." # Jina reranker
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If you use a proxy or gateway (e.g. OpenRouter, LiteLLM proxy), set the base URL and key:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
export OPENAI_API_BASE="https://openrouter.ai/api/v1"
|
|
99
|
+
export OPENAI_API_KEY="sk-or-v1-..."
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Install
|
|
103
|
+
|
|
104
|
+
Install dspy-lancedb-memory with uv (recommended)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv add dspy-lancedb-memory
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Install with pip (alternative)
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install dspy-lancedb-memory
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
117
|
+
|
|
118
|
+
<!-- USAGE -->
|
|
119
|
+
|
|
120
|
+
## Usage
|
|
121
|
+
|
|
122
|
+
### Basic Usage
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from dspy_lancedb_memory import memory
|
|
126
|
+
import dspy
|
|
127
|
+
|
|
128
|
+
# Configure: extraction LM, embedding LM, and optional reranker LM
|
|
129
|
+
memory.configure(
|
|
130
|
+
extraction_lm=dspy.LM("openai/gpt-4o-mini"),
|
|
131
|
+
embedding_lm=dspy.LM("openai/text-embedding-3-small"),
|
|
132
|
+
reranker_lm=dspy.LM("cohere/rerank-4-fast"), # optional — omit to disable
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Create a store — picks up all defaults from configure()
|
|
136
|
+
store = memory.Store()
|
|
137
|
+
|
|
138
|
+
# Store a single memory
|
|
139
|
+
store.create_memory(
|
|
140
|
+
user_id="user_123",
|
|
141
|
+
content="Edward prefers DSPy signatures over ad-hoc prompts.",
|
|
142
|
+
memory_type="preference",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Store with session and conversation scoping
|
|
146
|
+
store.create_memory(
|
|
147
|
+
user_id="user_123",
|
|
148
|
+
session_id="session_abc",
|
|
149
|
+
conversation_id="conv_456",
|
|
150
|
+
content="Remember this from our conversation about RAG pipelines.",
|
|
151
|
+
memory_type="episodic",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Search memories (with reranking)
|
|
155
|
+
results = store.search_memories(
|
|
156
|
+
user_id="user_123",
|
|
157
|
+
session_id="session_abc", # optional — narrow to a session
|
|
158
|
+
query="What does Edward prefer?",
|
|
159
|
+
use_reranker=True,
|
|
160
|
+
)
|
|
161
|
+
print(results)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Extract Memories from Conversation
|
|
165
|
+
|
|
166
|
+
The real power is automatic extraction. Pass a conversation turn and the LLM extracts all salient memories, each categorized by type.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from dspy_lancedb_memory import memory
|
|
170
|
+
import dspy
|
|
171
|
+
|
|
172
|
+
memory.configure(extraction_lm=dspy.LM("openai/gpt-4o-mini"))
|
|
173
|
+
store = memory.Store()
|
|
174
|
+
|
|
175
|
+
messages = [
|
|
176
|
+
{
|
|
177
|
+
"role": "user",
|
|
178
|
+
"content": "I really like using DSPy signatures instead of writing prompts by hand. "
|
|
179
|
+
"I'm building a CLI tool to organize my digital bookmarks by topic. "
|
|
180
|
+
"The PR is at github.com/example/bookmark-organizer/pull/42.",
|
|
181
|
+
},
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
created = store.create_memories(
|
|
185
|
+
user_id="user_123",
|
|
186
|
+
contents=messages,
|
|
187
|
+
extract=True, # default; uses DSPy ChainOfThought to extract memories
|
|
188
|
+
)
|
|
189
|
+
# Returns multiple MemoryItems categorized automatically:
|
|
190
|
+
# preference: "User prefers DSPy signatures over writing prompts by hand"
|
|
191
|
+
# semantic: "User is building a CLI bookmark organizer"
|
|
192
|
+
# procedural: "User is organizing bookmarks by topic"
|
|
193
|
+
# artifact: "github.com/example/bookmark-organizer/pull/42"
|
|
194
|
+
|
|
195
|
+
for m in created:
|
|
196
|
+
print(f"[{m['memory_type']}] {m['content']}")
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Search with Memory Type and Reranking
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from dspy_lancedb_memory import memory
|
|
203
|
+
import dspy
|
|
204
|
+
|
|
205
|
+
memory.configure(
|
|
206
|
+
extraction_lm=dspy.LM("openai/gpt-4o-mini"),
|
|
207
|
+
reranker_lm=dspy.LM("cohere/rerank-4-fast"),
|
|
208
|
+
)
|
|
209
|
+
store = memory.Store()
|
|
210
|
+
|
|
211
|
+
# Filter by memory type and optionally use reranking for better results
|
|
212
|
+
results = store.search_memories(
|
|
213
|
+
user_id="user_123",
|
|
214
|
+
query="What are Edward's tool preferences?",
|
|
215
|
+
memory_type="preference", # optional filter
|
|
216
|
+
limit=5,
|
|
217
|
+
use_reranker=True, # uses configured reranker endpoint
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Filtering by Session and Conversation
|
|
222
|
+
|
|
223
|
+
Every memory can be scoped to a ``session_id`` and ``conversation_id``. Both are optional and can be used independently or together.
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# Scope to a specific session
|
|
227
|
+
session_memories = store.search_memories(
|
|
228
|
+
user_id="user_123",
|
|
229
|
+
session_id="session_abc",
|
|
230
|
+
query="What did we discuss last time?",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Scope to a specific conversation
|
|
234
|
+
conversation_memories = store.search_memories(
|
|
235
|
+
user_id="user_123",
|
|
236
|
+
conversation_id="conv_456",
|
|
237
|
+
query="RAG pipeline details",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Combine session + conversation for maximum precision
|
|
241
|
+
precise = store.search_memories(
|
|
242
|
+
user_id="user_123",
|
|
243
|
+
session_id="session_abc",
|
|
244
|
+
conversation_id="conv_456",
|
|
245
|
+
query="specific topic",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Omit both to search across all sessions and conversations
|
|
249
|
+
all_results = store.search_memories(
|
|
250
|
+
user_id="user_123",
|
|
251
|
+
query="anything",
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Raw Store (No Extraction)
|
|
256
|
+
|
|
257
|
+
Store content verbatim without LLM extraction.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
store.create_memories(
|
|
261
|
+
user_id="user_123",
|
|
262
|
+
contents=[{"role": "user", "content": "A raw fact worth storing."}],
|
|
263
|
+
extract=False,
|
|
264
|
+
memory_type="semantic",
|
|
265
|
+
)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Update and Delete
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
# Update memory content (re-embeds automatically)
|
|
272
|
+
store.update_memory(
|
|
273
|
+
memory_id="some-uuid",
|
|
274
|
+
content="Updated memory text",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Delete a memory
|
|
278
|
+
store.delete_memory(memory_id="some-uuid")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Upsert — Insert, Update, or Skip
|
|
282
|
+
|
|
283
|
+
``upsert_memory`` uses semantic similarity to decide what to do:
|
|
284
|
+
|
|
285
|
+
1. **Exact match** — same ``content`` string exists → skip (no-op)
|
|
286
|
+
2. **Semantic match** — similar content found (cosine similarity ≥ threshold) → **update** it
|
|
287
|
+
3. **No match** — nothing close enough → **insert** a new memory
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
# First insert
|
|
291
|
+
store.upsert_memory(
|
|
292
|
+
user_id="user_123",
|
|
293
|
+
content="Edward is building a RAG pipeline for climate modeling.",
|
|
294
|
+
memory_type="semantic",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Same content string → skip (returns existing row unchanged)
|
|
298
|
+
store.upsert_memory(
|
|
299
|
+
user_id="user_123",
|
|
300
|
+
content="Edward is building a RAG pipeline for climate modeling.",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Semantically similar content → update that memory in place
|
|
304
|
+
store.upsert_memory(
|
|
305
|
+
user_id="user_123",
|
|
306
|
+
content="Edward is designing a RAG pipeline for climate data analysis.",
|
|
307
|
+
similarity_threshold=0.8, # lower = more aggressive updates
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Completely different content → insert a new row
|
|
311
|
+
store.upsert_memory(
|
|
312
|
+
user_id="user_123",
|
|
313
|
+
content="Edward prefers DSPy signatures over raw prompts.",
|
|
314
|
+
memory_type="preference",
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The ``similarity_threshold`` (default ``0.85``) controls how close two
|
|
319
|
+
memories must be to consider them the same. Higher values make upsert
|
|
320
|
+
more conservative (mostly inserts); lower values make it more aggressive
|
|
321
|
+
(mostly updates).
|
|
322
|
+
|
|
323
|
+
### Batch Upsert with Extraction
|
|
324
|
+
|
|
325
|
+
``upsert_memories`` mirrors ``create_memories`` exactly — same parameters,
|
|
326
|
+
same DSPy extraction — but each extracted memory goes through the upsert
|
|
327
|
+
decision instead of a blind insert.
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from dspy_lancedb_memory import memory
|
|
331
|
+
import dspy
|
|
332
|
+
|
|
333
|
+
memory.configure(extraction_lm=dspy.LM("openai/gpt-4o-mini"))
|
|
334
|
+
store = memory.Store()
|
|
335
|
+
|
|
336
|
+
messages = [
|
|
337
|
+
{
|
|
338
|
+
"role": "user",
|
|
339
|
+
"content": "I really like using DSPy signatures instead of writing prompts by hand. "
|
|
340
|
+
"I'm working on a RAG pipeline for my thesis on climate modeling. "
|
|
341
|
+
"The PR is at github.com/example/climate-rag/pull/42.",
|
|
342
|
+
},
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
upserted = store.upsert_memories(
|
|
346
|
+
user_id="user_123",
|
|
347
|
+
contents=messages,
|
|
348
|
+
extract=True,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
for m in upserted:
|
|
352
|
+
# Each extracted memory was independently upserted:
|
|
353
|
+
# - exact match → skip
|
|
354
|
+
# - semantic match → update
|
|
355
|
+
# - no match → insert
|
|
356
|
+
print(f"[{m['memory_type']}] {m['content']}")
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Using the Reranker
|
|
360
|
+
|
|
361
|
+
The easiest way — configure via ``memory.configure(reranker_lm=...)`` with a ``dspy.LM`` and `memory.Store()` picks it up automatically:
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from dspy_lancedb_memory import memory
|
|
365
|
+
import dspy
|
|
366
|
+
|
|
367
|
+
memory.configure(
|
|
368
|
+
extraction_lm=dspy.LM("openai/gpt-4o-mini"),
|
|
369
|
+
reranker_lm=dspy.LM("cohere/rerank-4-fast"),
|
|
370
|
+
)
|
|
371
|
+
store = memory.Store() # LiteLLMReranker auto-created from reranker_lm
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
You can also pass a plain model string instead of a ``dspy.LM``:
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
memory.configure(reranker_lm="cohere/rerank-english-v3.0")
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
For full control (custom column, top_n, etc.), build a ``LiteLLMReranker`` and pass it to ``Store()``:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
from dspy_lancedb_memory import LiteLLMReranker
|
|
384
|
+
|
|
385
|
+
reranker = LiteLLMReranker(
|
|
386
|
+
model="cohere/rerank-english-v3.0",
|
|
387
|
+
column="content", # LanceDB column to rerank against
|
|
388
|
+
top_n=20, # optional: limit reranked candidates
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
store = memory.Store(reranker=reranker)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
The model string uses the same ``provider/model`` format as ``dspy.LM`` — e.g.
|
|
395
|
+
``"cohere/rerank-english-v3.0"``, ``"jina/jina-reranker-v2-base-multilingual"``.
|
|
396
|
+
LiteLLM handles the routing.
|
|
397
|
+
|
|
398
|
+
### Custom API Base and Key
|
|
399
|
+
|
|
400
|
+
When running behind a proxy, gateway, or self-hosted endpoint, pass ``api_base`` and ``api_key`` directly to ``LiteLLMReranker``:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
from dspy_lancedb_memory import LiteLLMReranker
|
|
404
|
+
|
|
405
|
+
reranker = LiteLLMReranker(
|
|
406
|
+
model="my-provider/rerank-model",
|
|
407
|
+
api_base="https://my-gateway.example.com/v1",
|
|
408
|
+
api_key="sk-my-secret",
|
|
409
|
+
column="content",
|
|
410
|
+
top_n=20,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
store = memory.Store(reranker=reranker)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
For embeddings behind a custom endpoint, pass the same ``api_base``/``api_key`` via a ``dspy.LM``:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
import dspy
|
|
420
|
+
|
|
421
|
+
memory.configure(
|
|
422
|
+
embedding_lm=dspy.LM(
|
|
423
|
+
"openai/text-embedding-3-small",
|
|
424
|
+
api_base="https://my-gateway.example.com/v1",
|
|
425
|
+
api_key="sk-my-secret",
|
|
426
|
+
),
|
|
427
|
+
)
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
LiteLLM automatically routes to the correct provider based on the model prefix. If your
|
|
431
|
+
provider is not in LiteLLM's built-in list, ``LiteLLMReranker`` falls back to calling a
|
|
432
|
+
Cohere-compatible ``/rerank`` endpoint on your ``api_base``.
|
|
433
|
+
|
|
434
|
+
### Custom Configuration
|
|
435
|
+
|
|
436
|
+
Everything — including LanceDB defaults — in one call:
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
from dspy_lancedb_memory import memory
|
|
440
|
+
import dspy
|
|
441
|
+
|
|
442
|
+
memory.configure(
|
|
443
|
+
extraction_lm=dspy.LM("anthropic/claude-sonnet-4-20250514"), # extraction LM
|
|
444
|
+
embedding_lm=dspy.LM("openai/text-embedding-3-small"), # embedding LM
|
|
445
|
+
embedding_dim=1536, # must match
|
|
446
|
+
reranker_lm=dspy.LM("cohere/rerank-4-fast"), # reranker model
|
|
447
|
+
uri=".my_memories", # LanceDB path
|
|
448
|
+
table_name="user_memories", # LanceDB table
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
store = memory.Store() # everything inherited from configure()
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Override individual fields on ``Store()`` when you need something different:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
store = memory.Store(uri="./scratch", reranker=None)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
461
|
+
|
|
462
|
+
## Memory Taxonomy
|
|
463
|
+
|
|
464
|
+
Every extracted memory is categorized into one of six types:
|
|
465
|
+
|
|
466
|
+
| Type | Description | Example |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
| `preference` | User tastes, likes/dislikes, preferred formats, tone, tools | `"User prefers DSPy signatures over ad-hoc prompts"` |
|
|
469
|
+
| `semantic` | Facts, biographical data, stable knowledge about the user | `"User is a PhD student researching climate modeling"` |
|
|
470
|
+
| `episodic` | Events, tasks, decisions, or outcomes from a specific interaction | `"User decided to use LanceDB over Chroma for persistence"` |
|
|
471
|
+
| `procedural` | Learned rules, workflows, steps, patterns, or how-to knowledge | `"User's RAG pipeline uses hybrid search with reranking"` |
|
|
472
|
+
| `summary` | Compressed conversation or task summaries capturing the gist | `"User discussed their thesis work on climate RAG pipelines"` |
|
|
473
|
+
| `artifact` | Links, paths, IDs, or references to files, PRs, docs, outputs | `"github.com/example/climate-rag/pull/42"` |
|
|
474
|
+
|
|
475
|
+
When storing directly (without extraction), the default type is `semantic`.
|
|
476
|
+
|
|
477
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
478
|
+
|
|
479
|
+
## Available API
|
|
480
|
+
|
|
481
|
+
| API | Description |
|
|
482
|
+
|---|---|
|
|
483
|
+
| [`memory`](#basic-usage) | SDK module — ``memory.configure()`` and ``memory.Store()`` |
|
|
484
|
+
| [`MemoryExtractor`](#extract-memories-from-conversation) | DSPy `ChainOfThought` module for memory extraction |
|
|
485
|
+
| [`LiteLLMReranker`](#using-the-reranker) | Cross-encoder reranker via ``litellm.rerank()`` — supports Cohere, Jina, and any LiteLLM-compatible provider |
|
|
486
|
+
| [`MemoryType`](#memory-taxonomy) | Enum of the six memory categories |
|
|
487
|
+
| [`MemoryItem`](#extract-memories-from-conversation) | Pydantic model for extracted memories |
|
|
488
|
+
| [`upsert_memory`](#upsert--insert-update-or-skip) | Semantic upsert — insert, update, or skip based on content similarity |
|
|
489
|
+
| `session_id` / `conversation_id` | Optional scoping fields on ``create_memory``, ``create_memories``, ``search_memories``, and ``upsert_memory`` |
|
|
490
|
+
|
|
491
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
492
|
+
|
|
493
|
+
<!-- DEVELOPMENT -->
|
|
494
|
+
|
|
495
|
+
## Development
|
|
496
|
+
|
|
497
|
+
### Code Quality
|
|
498
|
+
|
|
499
|
+
This project uses several tools to maintain code quality:
|
|
500
|
+
|
|
501
|
+
- **Ruff:** Linting and formatting
|
|
502
|
+
- **isort:** Import sorting
|
|
503
|
+
- **pytest:** Testing framework
|
|
504
|
+
- **deptry:** Dependency checking
|
|
505
|
+
- **ty:** Type checking (based on pyright)
|
|
506
|
+
|
|
507
|
+
**Available commands:**
|
|
508
|
+
|
|
509
|
+
```sh
|
|
510
|
+
# Run all quality checks
|
|
511
|
+
uv run poe clean-full
|
|
512
|
+
|
|
513
|
+
# Individual checks
|
|
514
|
+
uv run poe lint # Ruff linting
|
|
515
|
+
uv run poe format # Ruff formatting
|
|
516
|
+
uv run poe sort # Import sorting
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Testing
|
|
520
|
+
|
|
521
|
+
Run tests using pytest:
|
|
522
|
+
|
|
523
|
+
```sh
|
|
524
|
+
# Run all tests
|
|
525
|
+
uv run pytest
|
|
526
|
+
|
|
527
|
+
# Run specific test
|
|
528
|
+
uv run pytest path/to/test.py::test_name
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
532
|
+
|
|
533
|
+
<!-- CONTRIBUTING -->
|
|
534
|
+
|
|
535
|
+
## Contributing
|
|
536
|
+
|
|
537
|
+
Quick workflow:
|
|
538
|
+
|
|
539
|
+
1. Fork and branch: `git checkout -b feature/name`
|
|
540
|
+
2. Make changes
|
|
541
|
+
3. Run checks: `uv run poe clean-full`
|
|
542
|
+
4. Commit and push
|
|
543
|
+
5. Open a Pull Request
|
|
544
|
+
|
|
545
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
546
|
+
|
|
547
|
+
<!-- LICENSE -->
|
|
548
|
+
|
|
549
|
+
## License
|
|
550
|
+
|
|
551
|
+
MIT (as declared in `pyproject.toml`).
|
|
552
|
+
|
|
553
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
<div align="center">
|
|
558
|
+
<p>
|
|
559
|
+
<sub>Built by <a href="https://github.com/thememium">thememium</a></sub>
|
|
560
|
+
</p>
|
|
561
|
+
</div>
|