lodedb 0.1.2__tar.gz → 0.2.1__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.
- {lodedb-0.1.2 → lodedb-0.2.1}/PKG-INFO +190 -7
- {lodedb-0.1.2 → lodedb-0.2.1}/README.md +185 -6
- {lodedb-0.1.2 → lodedb-0.2.1}/pyproject.toml +5 -1
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/__init__.py +1 -1
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/_commit_manifest.py +17 -2
- lodedb-0.2.1/src/lodedb/engine/_filter_plan.py +205 -0
- lodedb-0.2.1/src/lodedb/engine/_lexical.py +566 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/core.py +1299 -72
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/index.py +207 -3
- lodedb-0.2.1/src/lodedb/engine/lexical_index_store.py +394 -0
- lodedb-0.2.1/src/lodedb/graph/__init__.py +13 -0
- lodedb-0.2.1/src/lodedb/graph/_store.py +355 -0
- lodedb-0.2.1/src/lodedb/graph/knowledge_graph.py +826 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/backends.py +16 -0
- lodedb-0.2.1/src/lodedb/local/cli.py +518 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/db.py +492 -23
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/doctor.py +48 -0
- lodedb-0.2.1/src/lodedb/local/integrations/__init__.py +17 -0
- lodedb-0.2.1/src/lodedb/local/integrations/llama_index.py +468 -0
- lodedb-0.2.1/src/lodedb/local/integrations/llama_index_graph.py +404 -0
- lodedb-0.2.1/src/lodedb/local/integrations/mem0.py +531 -0
- lodedb-0.2.1/src/lodedb/local/mcp_install.py +587 -0
- lodedb-0.2.1/src/lodedb/local/mcp_server.py +220 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/presets.py +31 -0
- lodedb-0.1.2/src/lodedb/local/cli.py +0 -267
- lodedb-0.1.2/src/lodedb/local/integrations/__init__.py +0 -10
- lodedb-0.1.2/src/lodedb/local/mcp_server.py +0 -158
- {lodedb-0.1.2 → lodedb-0.2.1}/LICENSE +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/NOTICE +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/__main__.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/config.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/__init__.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/_atomic_io.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/_filelock.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/_predicate.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/document_text_store.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/embedding_backends.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/gpu_turbovec.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/mps_turbovec.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/route_profiles.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/route_registry.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/runtime_policy.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/state_journal_store.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/turbovec_delta_store.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/turbovec_index.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/engine/turbovec_resident.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/__init__.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/benchmark.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/integrations/langchain.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/local/server.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/src/lodedb/py.typed +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/Cargo.lock +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/Cargo.toml +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/Cargo.toml +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/README.md +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/build.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/examples/dump_state.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/examples/kernel_xtest.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/codebook.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/encode.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/error.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/id_map.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/io.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/lib.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/pack.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/rotation.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/search.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/codebook.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/concurrent_search.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/distortion.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/encode.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/encoded_rows.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/filtering.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/id_map.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/input_validation.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/io_versioning.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/kernel_correctness.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/lazy_init.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/reconstruction.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/rotation.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/state_sequences.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/swap_remove.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/tqplus_calibration.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/Cargo.toml +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/README.md +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/build.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/__init__.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/_dedup.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/_persist.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/agno.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/haystack.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/langchain.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/llama_index.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/src/lib.rs +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_agno.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_dedup.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_filtering.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_haystack.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_id_map.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_index.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_langchain.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_llama_index.py +0 -0
- {lodedb-0.1.2 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_security.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lodedb
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -14,11 +14,15 @@ Requires-Dist: pytest>=8.3.0 ; extra == 'dev'
|
|
|
14
14
|
Requires-Dist: ruff==0.15.17 ; extra == 'dev'
|
|
15
15
|
Requires-Dist: cupy-cuda12x>=13.0.0 ; platform_system == 'Linux' and extra == 'gpu'
|
|
16
16
|
Requires-Dist: langchain-core>=0.3.0 ; extra == 'langchain'
|
|
17
|
+
Requires-Dist: llama-index-core>=0.11.0 ; extra == 'llama-index'
|
|
17
18
|
Requires-Dist: mcp>=1.0.0 ; extra == 'mcp'
|
|
19
|
+
Requires-Dist: mem0ai>=2.0.0 ; extra == 'mem0'
|
|
18
20
|
Provides-Extra: dev
|
|
19
21
|
Provides-Extra: gpu
|
|
20
22
|
Provides-Extra: langchain
|
|
23
|
+
Provides-Extra: llama-index
|
|
21
24
|
Provides-Extra: mcp
|
|
25
|
+
Provides-Extra: mem0
|
|
22
26
|
License-File: LICENSE
|
|
23
27
|
License-File: NOTICE
|
|
24
28
|
Summary: Local-first, privacy-by-default embedded vector database. Your data never leaves your machine.
|
|
@@ -62,8 +66,9 @@ Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
|
|
|
62
66
|
- **Private by default**: text, ids, and vectors stay local; telemetry is metrics-only
|
|
63
67
|
(counts, bytes, latency), never raw payloads.
|
|
64
68
|
- **Local embeddings**: `sentence-transformers` on CUDA, MPS, or CPU.
|
|
65
|
-
- **Batteries included**: a `lodedb` CLI, a loopback dev server, an
|
|
66
|
-
LangChain
|
|
69
|
+
- **Batteries included**: a `lodedb` CLI, a loopback dev server, an
|
|
70
|
+
[MCP server](#use-as-an-mcp-server), and LangChain, LlamaIndex, and mem0 adapters
|
|
71
|
+
(`VectorStore`s, plus a LlamaIndex `PropertyGraphStore`).
|
|
67
72
|
|
|
68
73
|
> 🏢 **Enterprise** The LodeDB core is Apache-2.0 and free to use. Enterprise licensing is
|
|
69
74
|
> available for commercial support, managed and at-scale serving, and on-prem / BYOC
|
|
@@ -80,10 +85,43 @@ Python 3.11+, and bundle the TurboVec (Rust) core, so there's nothing to compile
|
|
|
80
85
|
the install with `lodedb doctor`. Optional extras:
|
|
81
86
|
|
|
82
87
|
```bash
|
|
83
|
-
pip install "lodedb[gpu]"
|
|
84
|
-
pip install "lodedb[mcp,langchain]"
|
|
88
|
+
pip install "lodedb[gpu]" # GPU-resident scan (Linux/CUDA)
|
|
89
|
+
pip install "lodedb[mcp,langchain,llama-index,mem0]" # MCP server + LangChain/LlamaIndex/mem0 adapters
|
|
85
90
|
```
|
|
86
91
|
|
|
92
|
+
Using LodeDB as memory for a coding assistant? After installing the `mcp` extra, register its
|
|
93
|
+
server in one step (details under [Use as an MCP server](#use-as-an-mcp-server)):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary><b>Windows: NVIDIA GPU embeddings</b></summary>
|
|
101
|
+
|
|
102
|
+
On Windows, PyPI serves the CPU-only PyTorch build by default, so `pip install lodedb` (and
|
|
103
|
+
`uv`) leave embeddings on the CPU even on a CUDA machine, and no package metadata can override
|
|
104
|
+
which torch wheel pip resolves. `lodedb doctor` detects this and prints the fix; `lodedb doctor
|
|
105
|
+
--fix` reinstalls the CUDA build for you:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
lodedb doctor # flags a CPU-only PyTorch on Windows and prints the command
|
|
109
|
+
lodedb doctor --fix # reinstalls the CUDA build so embeddings use your NVIDIA GPU
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or reinstall manually, picking the index for your CUDA version (`cu121`, `cu124`, ...) from the
|
|
113
|
+
[PyTorch install guide](https://pytorch.org/get-started/locally/):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install torch --force-reinstall --no-deps --index-url https://download.pytorch.org/whl/cu121
|
|
117
|
+
uv pip install torch --reinstall --index-url https://download.pytorch.org/whl/cu121 # with uv
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This is Windows-only: the default Linux PyPI wheel already bundles CUDA, and macOS uses CPU
|
|
121
|
+
or MPS.
|
|
122
|
+
|
|
123
|
+
</details>
|
|
124
|
+
|
|
87
125
|
<details>
|
|
88
126
|
<summary><b>Build from source</b> (contributors, or a platform without a wheel)</summary>
|
|
89
127
|
|
|
@@ -93,8 +131,8 @@ Linux). [uv](https://docs.astral.sh/uv/) builds and bundles the core for you:
|
|
|
93
131
|
```bash
|
|
94
132
|
git clone https://github.com/Egoist-Machines/LodeDB && cd LodeDB
|
|
95
133
|
uv sync # builds + bundles the TurboVec core via maturin
|
|
96
|
-
uv sync --extra mcp --extra langchain
|
|
97
|
-
uv sync --extra gpu
|
|
134
|
+
uv sync --extra mcp --extra langchain --extra llama-index --extra mem0 # + MCP server, adapters
|
|
135
|
+
uv sync --extra gpu # + GPU-resident scan (Linux/CUDA)
|
|
98
136
|
```
|
|
99
137
|
|
|
100
138
|
Run with `uv run` (e.g. `uv run lodedb doctor`).
|
|
@@ -121,6 +159,9 @@ for hits in db.search_many(["fox", "dog"], k=5): # batched; the GPU can serve
|
|
|
121
159
|
db.search("fox", k=5, filter={"topic": "animals"}) # bare scalar = exact
|
|
122
160
|
db.search("fox", k=5, filter={"$or": [{"topic": "animals"}, {"year": {"$gte": 2020}}]})
|
|
123
161
|
|
|
162
|
+
# hybrid search: vector recall plus exact lexical matches the embedding misses
|
|
163
|
+
db.search("E1234", k=5, mode="hybrid") # surfaces error codes, serials, dates in the body
|
|
164
|
+
|
|
124
165
|
db.get(fox) # -> "the quick brown fox jumps" (text retained by default)
|
|
125
166
|
db.persist() # durable .tvim/.tvd/.jsd snapshot; replays on reopen
|
|
126
167
|
```
|
|
@@ -139,6 +180,68 @@ reader.search("fox", k=5) # reads a committed snapshot
|
|
|
139
180
|
reader.add("nope") # raises ReadOnlyError
|
|
140
181
|
```
|
|
141
182
|
|
|
183
|
+
## Hybrid search
|
|
184
|
+
|
|
185
|
+
Vector search alone misses exact tokens the embedding does not capture: error codes
|
|
186
|
+
(`E1234`), serial numbers (`ABC-123`), dates (`2024-01-15`). Pass `mode="hybrid"` to run a
|
|
187
|
+
lexical BM25 ranker alongside the vector scan and fuse the two ranked lists with Reciprocal
|
|
188
|
+
Rank Fusion. The lexical ranker matches those tokens exactly, so a document whose body carries
|
|
189
|
+
the code is recovered even when the embedding ranks it nowhere near the top.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
db.add("the turbine tripped and reported fault code E1234 overnight", metadata={"unit": "t3"})
|
|
193
|
+
|
|
194
|
+
db.search("E1234", k=5) # mode="vector" (default): may miss the exact code
|
|
195
|
+
db.search("E1234", k=5, mode="hybrid") # BM25 + RRF: surfaces it in the top-k
|
|
196
|
+
db.search("E1234", k=5, mode="lexical") # BM25 ranking alone, no vector scan
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Prerequisites
|
|
200
|
+
|
|
201
|
+
`mode="hybrid"` and `mode="lexical"` build a BM25 index over your text, so they need a text
|
|
202
|
+
source enabled when you open the database. `mode="vector"` (the default) needs nothing.
|
|
203
|
+
|
|
204
|
+
| Mode | Enable | Source of the BM25 index |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| `"vector"` (default) | nothing | not used |
|
|
207
|
+
| `"hybrid"`, `"lexical"` | `store_text=True` (on by default) | rebuilt in memory from the retained raw text |
|
|
208
|
+
| `"hybrid"`, `"lexical"` | `index_text=True` | a durable on-disk postings store, no raw text required |
|
|
209
|
+
|
|
210
|
+
Either source is enough and you can enable both. `store_text=True` is the default, so hybrid
|
|
211
|
+
works out of the box. With neither source enabled, a hybrid or lexical query raises a clear,
|
|
212
|
+
actionable error rather than silently degrading.
|
|
213
|
+
|
|
214
|
+
### How it works
|
|
215
|
+
|
|
216
|
+
A `filter` constrains both rankers, so `mode="hybrid"` with a filter returns the true top-k of
|
|
217
|
+
the matching subset. The vector half of a hybrid query runs on the same scan as `mode="vector"`,
|
|
218
|
+
including the GPU-resident batch scan that serves `search_many`; only the BM25 ranking and the
|
|
219
|
+
fusion run on the CPU, and the vector kernel and on-disk format are untouched. The serving BM25
|
|
220
|
+
index lives in memory and is maintained incrementally: a small mutation folds just the changed
|
|
221
|
+
chunks into the existing index, so a single `add` never forces a full re-tokenization.
|
|
222
|
+
|
|
223
|
+
### Durable lexical index (`index_text=True`)
|
|
224
|
+
|
|
225
|
+
By default the BM25 index is rebuilt from the retained raw text, so it needs `store_text=True`
|
|
226
|
+
and is re-tokenized on the first hybrid query after opening. Pass `index_text=True` to persist
|
|
227
|
+
it instead: each document's per-chunk terms are captured at `add` time into a dedicated `.tvlex`
|
|
228
|
+
sidecar (a base plus a `.lxd` delta journal, committed O(changed) per write), so hybrid and
|
|
229
|
+
lexical search survive a reopen rebuilt straight from the persisted terms, with no
|
|
230
|
+
re-tokenization and without retaining the raw text at all. The `.tvlex` sidecar holds
|
|
231
|
+
payload-derived terms only and, like the raw-text sidecar, never reaches the redacted artifacts
|
|
232
|
+
or telemetry. The tokenizer lowercases and splits on punctuation but keeps code-like tokens
|
|
233
|
+
whole, so `ABC-123` and `2024-01-15` stay findable as single tokens. Reopen with the same
|
|
234
|
+
`index_text` value you wrote with.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
db = LodeDB(path="./data", index_text=True, store_text=False) # durable lexical index, no raw text
|
|
238
|
+
db.add("the turbine tripped and reported fault code E1234 overnight")
|
|
239
|
+
db.close()
|
|
240
|
+
|
|
241
|
+
reopened = LodeDB(path="./data", index_text=True, store_text=False)
|
|
242
|
+
reopened.search("E1234", k=5, mode="hybrid") # works after reopen, rebuilt from persisted terms
|
|
243
|
+
```
|
|
244
|
+
|
|
142
245
|
## GPU-resident index
|
|
143
246
|
|
|
144
247
|
With the `[gpu]` extra on a CUDA host, LodeDB reconstructs the compact index into an fp16
|
|
@@ -227,6 +330,82 @@ lodedb mcp # stdio MCP server for agent memory
|
|
|
227
330
|
lodedb benchmark # local, metrics-only benchmark
|
|
228
331
|
```
|
|
229
332
|
|
|
333
|
+
## Use as an MCP server
|
|
334
|
+
|
|
335
|
+
LodeDB ships a [Model Context Protocol](https://modelcontextprotocol.io) server, so an agent
|
|
336
|
+
can use a local on-disk database as long-term memory or a RAG store. It runs over stdio, adds
|
|
337
|
+
no storage logic of its own, and your data stays on the machine. Install the extra and point
|
|
338
|
+
your host at `lodedb mcp`:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
pip install "lodedb[mcp]"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
|
|
345
|
+
`lodedb_get` when text is available. `lodedb_search` returns each hit's stored text alongside
|
|
346
|
+
the score, id, and metadata, so a model can rank and answer in a single call rather than
|
|
347
|
+
chaining a follow-up lookup. It runs [hybrid search](#hybrid-search) (BM25 lexical + vector,
|
|
348
|
+
fused with RRF) by default when text is retained, so exact tokens like error codes and serials
|
|
349
|
+
surface next to semantic matches; with no text retained it falls back to a vector scan. Start
|
|
350
|
+
the server with `--exclude-text` to return metrics only (this also withdraws `lodedb_get`), or
|
|
351
|
+
`--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
|
|
352
|
+
query text never leaves the process.
|
|
353
|
+
|
|
354
|
+
### One command
|
|
355
|
+
|
|
356
|
+
`lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
|
|
357
|
+
find the file or hand-write the JSON/TOML:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
|
|
361
|
+
lodedb mcp install --client cursor --path ./data --model bge
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
It resolves the launch command for your environment, so `command` and `args` are correct even when
|
|
365
|
+
`lodedb` is not on `PATH` (it falls back to the `uv run --project ...` form, then an absolute path to
|
|
366
|
+
the entry point), and it resolves `--path` to an absolute path so the server opens the right
|
|
367
|
+
directory wherever the client starts it. The edit is idempotent (an existing `lodedb` entry is
|
|
368
|
+
updated, never duplicated) and never touches other servers in the file. It passes through the same
|
|
369
|
+
options as `lodedb mcp` (`--path`, `--model`, `--device`, `--exclude-text`, `--no-store-text`);
|
|
370
|
+
`--dry-run` prints the entry and target file without writing, and `lodedb mcp uninstall --client
|
|
371
|
+
<client>` removes it again. Override the config location with `--config <path>` (Claude Desktop and
|
|
372
|
+
LM Studio paths differ per OS), and use `--project <dir>` to write Cursor's project-level
|
|
373
|
+
`.cursor/mcp.json`. For Claude Code it runs `claude mcp add`; for the others it edits the config file
|
|
374
|
+
directly.
|
|
375
|
+
|
|
376
|
+
<details>
|
|
377
|
+
<summary><b>Register by hand</b> (Claude Code, Claude Desktop, Cursor, LM Studio, Codex)</summary>
|
|
378
|
+
|
|
379
|
+
The `lodedb` command must be on the host's `PATH`; if you installed into a virtual environment
|
|
380
|
+
(including a `uv` project) where it isn't, use the `uv run` form at the bottom.
|
|
381
|
+
|
|
382
|
+
**Claude Code, Claude Desktop, Cursor, LM Studio**: add the stdio entry to the host's MCP
|
|
383
|
+
config (`claude_desktop_config.json`, `.cursor/mcp.json`, or LM Studio's `mcp.json`), or run
|
|
384
|
+
`claude mcp add lodedb -- lodedb mcp --path ./data`:
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{ "mcpServers": { "lodedb": { "command": "lodedb", "args": ["mcp", "--path", "./data"] } } }
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Codex**: add to `~/.codex/config.toml`:
|
|
391
|
+
|
|
392
|
+
```toml
|
|
393
|
+
[mcp_servers.lodedb]
|
|
394
|
+
command = "lodedb"
|
|
395
|
+
args = ["mcp", "--path", "./data"]
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**From a virtual environment (uv)**, when `lodedb` is not on `PATH`:
|
|
399
|
+
|
|
400
|
+
```json
|
|
401
|
+
{ "mcpServers": { "lodedb": { "command": "uv",
|
|
402
|
+
"args": ["run", "--project", "/path/to/LodeDB", "lodedb", "mcp", "--path", "/path/to/data"] } } }
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste starting point.
|
|
406
|
+
|
|
407
|
+
</details>
|
|
408
|
+
|
|
230
409
|
## Concurrency & durability
|
|
231
410
|
|
|
232
411
|
- **Single writer, many readers, per path.** One handle holds the path open for *writing* at
|
|
@@ -255,6 +434,10 @@ lodedb benchmark # local, metrics-only benchmark
|
|
|
255
434
|
faster on the measured M1, so the MPS scan stays off by default until newer Apple GPUs are
|
|
256
435
|
re-measured.
|
|
257
436
|
- **Single queries run on the CPU**; the GPU serves batched `search_many`.
|
|
437
|
+
- **Hybrid search needs a lexical source and serves from memory.** `mode="hybrid"`/`"lexical"`
|
|
438
|
+
need either `store_text=True` (the index built from raw text) or `index_text=True` (a durable
|
|
439
|
+
`.tvlex` postings store that survives reopens without raw text). The serving index is held in
|
|
440
|
+
memory and maintained incrementally across mutations.
|
|
258
441
|
- **Single writer per path.** One writer at a time (many concurrent readers), with no live
|
|
259
442
|
cross-process refresh, on local filesystems only. See
|
|
260
443
|
[Concurrency & durability](#concurrency--durability).
|
|
@@ -28,8 +28,9 @@ Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
|
|
|
28
28
|
- **Private by default**: text, ids, and vectors stay local; telemetry is metrics-only
|
|
29
29
|
(counts, bytes, latency), never raw payloads.
|
|
30
30
|
- **Local embeddings**: `sentence-transformers` on CUDA, MPS, or CPU.
|
|
31
|
-
- **Batteries included**: a `lodedb` CLI, a loopback dev server, an
|
|
32
|
-
LangChain
|
|
31
|
+
- **Batteries included**: a `lodedb` CLI, a loopback dev server, an
|
|
32
|
+
[MCP server](#use-as-an-mcp-server), and LangChain, LlamaIndex, and mem0 adapters
|
|
33
|
+
(`VectorStore`s, plus a LlamaIndex `PropertyGraphStore`).
|
|
33
34
|
|
|
34
35
|
> 🏢 **Enterprise** The LodeDB core is Apache-2.0 and free to use. Enterprise licensing is
|
|
35
36
|
> available for commercial support, managed and at-scale serving, and on-prem / BYOC
|
|
@@ -46,10 +47,43 @@ Python 3.11+, and bundle the TurboVec (Rust) core, so there's nothing to compile
|
|
|
46
47
|
the install with `lodedb doctor`. Optional extras:
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
|
-
pip install "lodedb[gpu]"
|
|
50
|
-
pip install "lodedb[mcp,langchain]"
|
|
50
|
+
pip install "lodedb[gpu]" # GPU-resident scan (Linux/CUDA)
|
|
51
|
+
pip install "lodedb[mcp,langchain,llama-index,mem0]" # MCP server + LangChain/LlamaIndex/mem0 adapters
|
|
51
52
|
```
|
|
52
53
|
|
|
54
|
+
Using LodeDB as memory for a coding assistant? After installing the `mcp` extra, register its
|
|
55
|
+
server in one step (details under [Use as an MCP server](#use-as-an-mcp-server)):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
<details>
|
|
62
|
+
<summary><b>Windows: NVIDIA GPU embeddings</b></summary>
|
|
63
|
+
|
|
64
|
+
On Windows, PyPI serves the CPU-only PyTorch build by default, so `pip install lodedb` (and
|
|
65
|
+
`uv`) leave embeddings on the CPU even on a CUDA machine, and no package metadata can override
|
|
66
|
+
which torch wheel pip resolves. `lodedb doctor` detects this and prints the fix; `lodedb doctor
|
|
67
|
+
--fix` reinstalls the CUDA build for you:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
lodedb doctor # flags a CPU-only PyTorch on Windows and prints the command
|
|
71
|
+
lodedb doctor --fix # reinstalls the CUDA build so embeddings use your NVIDIA GPU
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or reinstall manually, picking the index for your CUDA version (`cu121`, `cu124`, ...) from the
|
|
75
|
+
[PyTorch install guide](https://pytorch.org/get-started/locally/):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install torch --force-reinstall --no-deps --index-url https://download.pytorch.org/whl/cu121
|
|
79
|
+
uv pip install torch --reinstall --index-url https://download.pytorch.org/whl/cu121 # with uv
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This is Windows-only: the default Linux PyPI wheel already bundles CUDA, and macOS uses CPU
|
|
83
|
+
or MPS.
|
|
84
|
+
|
|
85
|
+
</details>
|
|
86
|
+
|
|
53
87
|
<details>
|
|
54
88
|
<summary><b>Build from source</b> (contributors, or a platform without a wheel)</summary>
|
|
55
89
|
|
|
@@ -59,8 +93,8 @@ Linux). [uv](https://docs.astral.sh/uv/) builds and bundles the core for you:
|
|
|
59
93
|
```bash
|
|
60
94
|
git clone https://github.com/Egoist-Machines/LodeDB && cd LodeDB
|
|
61
95
|
uv sync # builds + bundles the TurboVec core via maturin
|
|
62
|
-
uv sync --extra mcp --extra langchain
|
|
63
|
-
uv sync --extra gpu
|
|
96
|
+
uv sync --extra mcp --extra langchain --extra llama-index --extra mem0 # + MCP server, adapters
|
|
97
|
+
uv sync --extra gpu # + GPU-resident scan (Linux/CUDA)
|
|
64
98
|
```
|
|
65
99
|
|
|
66
100
|
Run with `uv run` (e.g. `uv run lodedb doctor`).
|
|
@@ -87,6 +121,9 @@ for hits in db.search_many(["fox", "dog"], k=5): # batched; the GPU can serve
|
|
|
87
121
|
db.search("fox", k=5, filter={"topic": "animals"}) # bare scalar = exact
|
|
88
122
|
db.search("fox", k=5, filter={"$or": [{"topic": "animals"}, {"year": {"$gte": 2020}}]})
|
|
89
123
|
|
|
124
|
+
# hybrid search: vector recall plus exact lexical matches the embedding misses
|
|
125
|
+
db.search("E1234", k=5, mode="hybrid") # surfaces error codes, serials, dates in the body
|
|
126
|
+
|
|
90
127
|
db.get(fox) # -> "the quick brown fox jumps" (text retained by default)
|
|
91
128
|
db.persist() # durable .tvim/.tvd/.jsd snapshot; replays on reopen
|
|
92
129
|
```
|
|
@@ -105,6 +142,68 @@ reader.search("fox", k=5) # reads a committed snapshot
|
|
|
105
142
|
reader.add("nope") # raises ReadOnlyError
|
|
106
143
|
```
|
|
107
144
|
|
|
145
|
+
## Hybrid search
|
|
146
|
+
|
|
147
|
+
Vector search alone misses exact tokens the embedding does not capture: error codes
|
|
148
|
+
(`E1234`), serial numbers (`ABC-123`), dates (`2024-01-15`). Pass `mode="hybrid"` to run a
|
|
149
|
+
lexical BM25 ranker alongside the vector scan and fuse the two ranked lists with Reciprocal
|
|
150
|
+
Rank Fusion. The lexical ranker matches those tokens exactly, so a document whose body carries
|
|
151
|
+
the code is recovered even when the embedding ranks it nowhere near the top.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
db.add("the turbine tripped and reported fault code E1234 overnight", metadata={"unit": "t3"})
|
|
155
|
+
|
|
156
|
+
db.search("E1234", k=5) # mode="vector" (default): may miss the exact code
|
|
157
|
+
db.search("E1234", k=5, mode="hybrid") # BM25 + RRF: surfaces it in the top-k
|
|
158
|
+
db.search("E1234", k=5, mode="lexical") # BM25 ranking alone, no vector scan
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Prerequisites
|
|
162
|
+
|
|
163
|
+
`mode="hybrid"` and `mode="lexical"` build a BM25 index over your text, so they need a text
|
|
164
|
+
source enabled when you open the database. `mode="vector"` (the default) needs nothing.
|
|
165
|
+
|
|
166
|
+
| Mode | Enable | Source of the BM25 index |
|
|
167
|
+
| --- | --- | --- |
|
|
168
|
+
| `"vector"` (default) | nothing | not used |
|
|
169
|
+
| `"hybrid"`, `"lexical"` | `store_text=True` (on by default) | rebuilt in memory from the retained raw text |
|
|
170
|
+
| `"hybrid"`, `"lexical"` | `index_text=True` | a durable on-disk postings store, no raw text required |
|
|
171
|
+
|
|
172
|
+
Either source is enough and you can enable both. `store_text=True` is the default, so hybrid
|
|
173
|
+
works out of the box. With neither source enabled, a hybrid or lexical query raises a clear,
|
|
174
|
+
actionable error rather than silently degrading.
|
|
175
|
+
|
|
176
|
+
### How it works
|
|
177
|
+
|
|
178
|
+
A `filter` constrains both rankers, so `mode="hybrid"` with a filter returns the true top-k of
|
|
179
|
+
the matching subset. The vector half of a hybrid query runs on the same scan as `mode="vector"`,
|
|
180
|
+
including the GPU-resident batch scan that serves `search_many`; only the BM25 ranking and the
|
|
181
|
+
fusion run on the CPU, and the vector kernel and on-disk format are untouched. The serving BM25
|
|
182
|
+
index lives in memory and is maintained incrementally: a small mutation folds just the changed
|
|
183
|
+
chunks into the existing index, so a single `add` never forces a full re-tokenization.
|
|
184
|
+
|
|
185
|
+
### Durable lexical index (`index_text=True`)
|
|
186
|
+
|
|
187
|
+
By default the BM25 index is rebuilt from the retained raw text, so it needs `store_text=True`
|
|
188
|
+
and is re-tokenized on the first hybrid query after opening. Pass `index_text=True` to persist
|
|
189
|
+
it instead: each document's per-chunk terms are captured at `add` time into a dedicated `.tvlex`
|
|
190
|
+
sidecar (a base plus a `.lxd` delta journal, committed O(changed) per write), so hybrid and
|
|
191
|
+
lexical search survive a reopen rebuilt straight from the persisted terms, with no
|
|
192
|
+
re-tokenization and without retaining the raw text at all. The `.tvlex` sidecar holds
|
|
193
|
+
payload-derived terms only and, like the raw-text sidecar, never reaches the redacted artifacts
|
|
194
|
+
or telemetry. The tokenizer lowercases and splits on punctuation but keeps code-like tokens
|
|
195
|
+
whole, so `ABC-123` and `2024-01-15` stay findable as single tokens. Reopen with the same
|
|
196
|
+
`index_text` value you wrote with.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
db = LodeDB(path="./data", index_text=True, store_text=False) # durable lexical index, no raw text
|
|
200
|
+
db.add("the turbine tripped and reported fault code E1234 overnight")
|
|
201
|
+
db.close()
|
|
202
|
+
|
|
203
|
+
reopened = LodeDB(path="./data", index_text=True, store_text=False)
|
|
204
|
+
reopened.search("E1234", k=5, mode="hybrid") # works after reopen, rebuilt from persisted terms
|
|
205
|
+
```
|
|
206
|
+
|
|
108
207
|
## GPU-resident index
|
|
109
208
|
|
|
110
209
|
With the `[gpu]` extra on a CUDA host, LodeDB reconstructs the compact index into an fp16
|
|
@@ -193,6 +292,82 @@ lodedb mcp # stdio MCP server for agent memory
|
|
|
193
292
|
lodedb benchmark # local, metrics-only benchmark
|
|
194
293
|
```
|
|
195
294
|
|
|
295
|
+
## Use as an MCP server
|
|
296
|
+
|
|
297
|
+
LodeDB ships a [Model Context Protocol](https://modelcontextprotocol.io) server, so an agent
|
|
298
|
+
can use a local on-disk database as long-term memory or a RAG store. It runs over stdio, adds
|
|
299
|
+
no storage logic of its own, and your data stays on the machine. Install the extra and point
|
|
300
|
+
your host at `lodedb mcp`:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
pip install "lodedb[mcp]"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
|
|
307
|
+
`lodedb_get` when text is available. `lodedb_search` returns each hit's stored text alongside
|
|
308
|
+
the score, id, and metadata, so a model can rank and answer in a single call rather than
|
|
309
|
+
chaining a follow-up lookup. It runs [hybrid search](#hybrid-search) (BM25 lexical + vector,
|
|
310
|
+
fused with RRF) by default when text is retained, so exact tokens like error codes and serials
|
|
311
|
+
surface next to semantic matches; with no text retained it falls back to a vector scan. Start
|
|
312
|
+
the server with `--exclude-text` to return metrics only (this also withdraws `lodedb_get`), or
|
|
313
|
+
`--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
|
|
314
|
+
query text never leaves the process.
|
|
315
|
+
|
|
316
|
+
### One command
|
|
317
|
+
|
|
318
|
+
`lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
|
|
319
|
+
find the file or hand-write the JSON/TOML:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
|
|
323
|
+
lodedb mcp install --client cursor --path ./data --model bge
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
It resolves the launch command for your environment, so `command` and `args` are correct even when
|
|
327
|
+
`lodedb` is not on `PATH` (it falls back to the `uv run --project ...` form, then an absolute path to
|
|
328
|
+
the entry point), and it resolves `--path` to an absolute path so the server opens the right
|
|
329
|
+
directory wherever the client starts it. The edit is idempotent (an existing `lodedb` entry is
|
|
330
|
+
updated, never duplicated) and never touches other servers in the file. It passes through the same
|
|
331
|
+
options as `lodedb mcp` (`--path`, `--model`, `--device`, `--exclude-text`, `--no-store-text`);
|
|
332
|
+
`--dry-run` prints the entry and target file without writing, and `lodedb mcp uninstall --client
|
|
333
|
+
<client>` removes it again. Override the config location with `--config <path>` (Claude Desktop and
|
|
334
|
+
LM Studio paths differ per OS), and use `--project <dir>` to write Cursor's project-level
|
|
335
|
+
`.cursor/mcp.json`. For Claude Code it runs `claude mcp add`; for the others it edits the config file
|
|
336
|
+
directly.
|
|
337
|
+
|
|
338
|
+
<details>
|
|
339
|
+
<summary><b>Register by hand</b> (Claude Code, Claude Desktop, Cursor, LM Studio, Codex)</summary>
|
|
340
|
+
|
|
341
|
+
The `lodedb` command must be on the host's `PATH`; if you installed into a virtual environment
|
|
342
|
+
(including a `uv` project) where it isn't, use the `uv run` form at the bottom.
|
|
343
|
+
|
|
344
|
+
**Claude Code, Claude Desktop, Cursor, LM Studio**: add the stdio entry to the host's MCP
|
|
345
|
+
config (`claude_desktop_config.json`, `.cursor/mcp.json`, or LM Studio's `mcp.json`), or run
|
|
346
|
+
`claude mcp add lodedb -- lodedb mcp --path ./data`:
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{ "mcpServers": { "lodedb": { "command": "lodedb", "args": ["mcp", "--path", "./data"] } } }
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Codex**: add to `~/.codex/config.toml`:
|
|
353
|
+
|
|
354
|
+
```toml
|
|
355
|
+
[mcp_servers.lodedb]
|
|
356
|
+
command = "lodedb"
|
|
357
|
+
args = ["mcp", "--path", "./data"]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**From a virtual environment (uv)**, when `lodedb` is not on `PATH`:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{ "mcpServers": { "lodedb": { "command": "uv",
|
|
364
|
+
"args": ["run", "--project", "/path/to/LodeDB", "lodedb", "mcp", "--path", "/path/to/data"] } } }
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste starting point.
|
|
368
|
+
|
|
369
|
+
</details>
|
|
370
|
+
|
|
196
371
|
## Concurrency & durability
|
|
197
372
|
|
|
198
373
|
- **Single writer, many readers, per path.** One handle holds the path open for *writing* at
|
|
@@ -221,6 +396,10 @@ lodedb benchmark # local, metrics-only benchmark
|
|
|
221
396
|
faster on the measured M1, so the MPS scan stays off by default until newer Apple GPUs are
|
|
222
397
|
re-measured.
|
|
223
398
|
- **Single queries run on the CPU**; the GPU serves batched `search_many`.
|
|
399
|
+
- **Hybrid search needs a lexical source and serves from memory.** `mode="hybrid"`/`"lexical"`
|
|
400
|
+
need either `store_text=True` (the index built from raw text) or `index_text=True` (a durable
|
|
401
|
+
`.tvlex` postings store that survives reopens without raw text). The serving index is held in
|
|
402
|
+
memory and maintained incrementally across mutations.
|
|
224
403
|
- **Single writer per path.** One writer at a time (many concurrent readers), with no live
|
|
225
404
|
cross-process refresh, on local filesystems only. See
|
|
226
405
|
[Concurrency & durability](#concurrency--durability).
|
|
@@ -5,7 +5,7 @@ build-backend = "maturin"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lodedb"
|
|
7
7
|
# Static (maturin reads it here); keep in sync with src/lodedb/__init__.py __version__.
|
|
8
|
-
version = "0.1
|
|
8
|
+
version = "0.2.1"
|
|
9
9
|
description = "Local-first, privacy-by-default embedded vector database. Your data never leaves your machine."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -52,6 +52,10 @@ gpu = ["cupy-cuda12x>=13.0.0; platform_system == 'Linux'"]
|
|
|
52
52
|
mcp = ["mcp>=1.0.0"]
|
|
53
53
|
# Optional LangChain VectorStore adapter.
|
|
54
54
|
langchain = ["langchain-core>=0.3.0"]
|
|
55
|
+
# Optional LlamaIndex VectorStore adapter.
|
|
56
|
+
llama-index = ["llama-index-core>=0.11.0"]
|
|
57
|
+
# Optional mem0 vector-store adapter.
|
|
58
|
+
mem0 = ["mem0ai>=2.0.0"]
|
|
55
59
|
# ruff is pinned (not floored) so `ruff format`/`ruff check` are reproducible across
|
|
56
60
|
# contributors and CI — the formatter's output can change between ruff releases.
|
|
57
61
|
dev = ["pytest>=8.3.0", "ruff==0.15.17"]
|
|
@@ -17,6 +17,8 @@ at which a full base was last written):
|
|
|
17
17
|
<key>.gen/g<epoch>.tvim.tvim-delta/ its ``.tvd`` vector journal
|
|
18
18
|
<key>.gen/g<epoch>.tvtext opt-in raw-text base (full id->text map)
|
|
19
19
|
<key>.gen/g<epoch>.tvtext.tvtext-delta/ its ``.txd`` raw-text journal
|
|
20
|
+
<key>.gen/g<epoch>.tvlex opt-in lexical-index base (id->token lists)
|
|
21
|
+
<key>.gen/g<epoch>.tvlex.tvlex-delta/ its ``.lxd`` lexical-index journal
|
|
20
22
|
|
|
21
23
|
and a single top-level pointer:
|
|
22
24
|
|
|
@@ -89,6 +91,12 @@ def base_tvtext_path(persistence_dir: str | Path, index_key: str, epoch: int) ->
|
|
|
89
91
|
return generation_dir(persistence_dir, index_key) / f"g{int(epoch)}.tvtext"
|
|
90
92
|
|
|
91
93
|
|
|
94
|
+
def base_tvlex_path(persistence_dir: str | Path, index_key: str, epoch: int) -> Path:
|
|
95
|
+
"""Returns the lexical-index base path for one index key and base epoch."""
|
|
96
|
+
|
|
97
|
+
return generation_dir(persistence_dir, index_key) / f"g{int(epoch)}.tvlex"
|
|
98
|
+
|
|
99
|
+
|
|
92
100
|
def is_commit_manifest_name(name: str) -> bool:
|
|
93
101
|
"""Returns whether a top-level file name is a root commit manifest."""
|
|
94
102
|
|
|
@@ -173,13 +181,19 @@ def build_commit_body(
|
|
|
173
181
|
tvim_manifest: dict[str, Any] | None,
|
|
174
182
|
tvim_present: bool,
|
|
175
183
|
tvtext_manifest: dict[str, Any] | None,
|
|
184
|
+
tvlex_manifest: dict[str, Any] | None = None,
|
|
176
185
|
) -> dict[str, Any]:
|
|
177
186
|
"""Assembles a root-manifest body capturing one consistent committed generation.
|
|
178
187
|
|
|
179
188
|
``tvtext_manifest`` is the raw-text journal manifest (base + delta segments)
|
|
180
189
|
for this committed generation, or ``None`` when raw-text storage is disabled
|
|
181
|
-
or the index holds no text.
|
|
182
|
-
|
|
190
|
+
or the index holds no text. ``tvlex_manifest`` is the analogous lexical-index
|
|
191
|
+
(BM25 postings) journal manifest, or ``None`` when lexical-index persistence
|
|
192
|
+
is disabled or the index holds no tokens. Both are pinned by the root exactly
|
|
193
|
+
like the JSON and TurboVec manifests, so the text sidecar and the lexical
|
|
194
|
+
sidecar commit and roll back atomically with the generation. ``tvlex`` is an
|
|
195
|
+
optional key read via ``.get`` (like ``tvtext``), so an older reader that
|
|
196
|
+
does not know it ignores it.
|
|
183
197
|
"""
|
|
184
198
|
|
|
185
199
|
return {
|
|
@@ -192,6 +206,7 @@ def build_commit_body(
|
|
|
192
206
|
"tvim": tvim_manifest,
|
|
193
207
|
"tvim_present": bool(tvim_present),
|
|
194
208
|
"tvtext": tvtext_manifest,
|
|
209
|
+
"tvlex": tvlex_manifest,
|
|
195
210
|
}
|
|
196
211
|
|
|
197
212
|
|