lodedb 0.2.0__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.2.0 → lodedb-0.2.1}/PKG-INFO +121 -7
- {lodedb-0.2.0 → lodedb-0.2.1}/README.md +116 -6
- {lodedb-0.2.0 → lodedb-0.2.1}/pyproject.toml +5 -1
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/__init__.py +1 -1
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/core.py +115 -10
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/index.py +36 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/graph/knowledge_graph.py +60 -14
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/backends.py +16 -0
- lodedb-0.2.1/src/lodedb/local/cli.py +518 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/db.py +41 -4
- {lodedb-0.2.0 → 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.2.0/src/lodedb/local/cli.py +0 -267
- lodedb-0.2.0/src/lodedb/local/integrations/__init__.py +0 -10
- lodedb-0.2.0/src/lodedb/local/mcp_server.py +0 -158
- {lodedb-0.2.0 → lodedb-0.2.1}/LICENSE +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/NOTICE +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/__main__.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/config.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/__init__.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_atomic_io.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_commit_manifest.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_filelock.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_filter_plan.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_lexical.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_predicate.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/document_text_store.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/embedding_backends.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/gpu_turbovec.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/lexical_index_store.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/mps_turbovec.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/route_profiles.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/route_registry.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/runtime_policy.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/state_journal_store.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/turbovec_delta_store.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/turbovec_index.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/turbovec_resident.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/graph/__init__.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/graph/_store.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/__init__.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/benchmark.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/integrations/langchain.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/presets.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/server.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/py.typed +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/Cargo.lock +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/Cargo.toml +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/Cargo.toml +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/README.md +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/build.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/examples/dump_state.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/examples/kernel_xtest.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/codebook.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/encode.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/error.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/id_map.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/io.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/lib.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/pack.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/rotation.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/search.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/codebook.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/concurrent_search.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/distortion.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/encode.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/encoded_rows.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/filtering.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/id_map.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/input_validation.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/io_versioning.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/kernel_correctness.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/lazy_init.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/reconstruction.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/rotation.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/state_sequences.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/swap_remove.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/tqplus_calibration.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/Cargo.toml +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/README.md +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/build.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/__init__.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/_dedup.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/_persist.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/agno.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/haystack.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/langchain.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/llama_index.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/src/lib.rs +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_agno.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_dedup.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_filtering.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_haystack.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_id_map.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_index.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_langchain.py +0 -0
- {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_llama_index.py +0 -0
- {lodedb-0.2.0 → 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.2.
|
|
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`).
|
|
@@ -292,6 +330,82 @@ lodedb mcp # stdio MCP server for agent memory
|
|
|
292
330
|
lodedb benchmark # local, metrics-only benchmark
|
|
293
331
|
```
|
|
294
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
|
+
|
|
295
409
|
## Concurrency & durability
|
|
296
410
|
|
|
297
411
|
- **Single writer, many readers, per path.** One handle holds the path open for *writing* at
|
|
@@ -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`).
|
|
@@ -258,6 +292,82 @@ lodedb mcp # stdio MCP server for agent memory
|
|
|
258
292
|
lodedb benchmark # local, metrics-only benchmark
|
|
259
293
|
```
|
|
260
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
|
+
|
|
261
371
|
## Concurrency & durability
|
|
262
372
|
|
|
263
373
|
- **Single writer, many readers, per path.** One handle holds the path open for *writing* at
|
|
@@ -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.2.
|
|
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"]
|
|
@@ -188,15 +188,18 @@ class EngineVectorDocument:
|
|
|
188
188
|
"""Stores one pre-embedded document supplied to the local engine (vector-in).
|
|
189
189
|
|
|
190
190
|
Unlike :class:`EngineDocument`, the caller supplies the embedding vector
|
|
191
|
-
directly
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
directly: the engine skips chunking and embedding and stores the vector as a
|
|
192
|
+
single chunk. ``text`` is optional retained payload text only; when present
|
|
193
|
+
and raw-text storage is enabled it is stored in the dedicated text sidecar,
|
|
194
|
+
never in the redacted snapshot or metadata. The vector must match the
|
|
195
|
+
index's ``native_dim`` and is expected to be L2-normalized (the SDK
|
|
196
|
+
normalizes by default) so cosine scoring stays comparable with the text path.
|
|
195
197
|
"""
|
|
196
198
|
|
|
197
199
|
document_id: str
|
|
198
200
|
vector: tuple[float, ...]
|
|
199
201
|
metadata: dict[str, str] = field(default_factory=dict)
|
|
202
|
+
text: str | None = None
|
|
200
203
|
|
|
201
204
|
|
|
202
205
|
@dataclass(frozen=True)
|
|
@@ -1107,10 +1110,12 @@ class LodeEngine:
|
|
|
1107
1110
|
"""Upserts pre-embedded documents (vector-in), skipping chunking/embedding.
|
|
1108
1111
|
|
|
1109
1112
|
The caller supplies embedding vectors directly; each is stored as a
|
|
1110
|
-
single chunk
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1113
|
+
single chunk. If a vector document carries optional ``text``, that text
|
|
1114
|
+
is captured only in the raw-text sidecar when enabled; it is never
|
|
1115
|
+
embedded, chunked, or written to redacted metadata. Vectors must match
|
|
1116
|
+
the index ``native_dim``. Reuses the same atomic-commit + O(changed)
|
|
1117
|
+
direct TurboVec sync path as :meth:`upsert_documents`, so vector-in
|
|
1118
|
+
inherits crash-atomic commits and incremental persistence unchanged.
|
|
1114
1119
|
"""
|
|
1115
1120
|
|
|
1116
1121
|
state = self._index_for_context(context, index_id=index_id, operation="upsert_vectors")
|
|
@@ -1124,8 +1129,7 @@ class LodeEngine:
|
|
|
1124
1129
|
)
|
|
1125
1130
|
if isinstance(result, EngineResponse):
|
|
1126
1131
|
return result
|
|
1127
|
-
|
|
1128
|
-
# raw text, so get()/get_text() return None for them by design.
|
|
1132
|
+
self._capture_vector_document_text(state, vectors)
|
|
1129
1133
|
self._finalize_document_ingest(
|
|
1130
1134
|
context=context,
|
|
1131
1135
|
state=state,
|
|
@@ -1140,6 +1144,92 @@ class LodeEngine:
|
|
|
1140
1144
|
},
|
|
1141
1145
|
)
|
|
1142
1146
|
|
|
1147
|
+
@_synchronized
|
|
1148
|
+
def update_document_payload(
|
|
1149
|
+
self,
|
|
1150
|
+
*,
|
|
1151
|
+
context: EngineRequestContext,
|
|
1152
|
+
document_id: str,
|
|
1153
|
+
metadata: Mapping[str, Any] | None = None,
|
|
1154
|
+
text: str | None = None,
|
|
1155
|
+
clear_text: bool = False,
|
|
1156
|
+
index_id: str | None = None,
|
|
1157
|
+
) -> EngineResponse:
|
|
1158
|
+
"""Updates an existing document's metadata and/or retained raw text.
|
|
1159
|
+
|
|
1160
|
+
This is intentionally narrow: it does not alter vectors or chunk
|
|
1161
|
+
membership, and it never embeds. It exists for adapters that maintain
|
|
1162
|
+
host-framework payloads beside precomputed vectors, such as mem0's
|
|
1163
|
+
payload-only entity-link updates.
|
|
1164
|
+
"""
|
|
1165
|
+
|
|
1166
|
+
state = self._index_for_context(
|
|
1167
|
+
context, index_id=index_id, operation="update_document_payload"
|
|
1168
|
+
)
|
|
1169
|
+
if isinstance(state, EngineResponse):
|
|
1170
|
+
return state
|
|
1171
|
+
document_id = str(document_id).strip()
|
|
1172
|
+
if not document_id:
|
|
1173
|
+
return self._error(
|
|
1174
|
+
400, "document_id is required", "update_document_payload", context.client_id
|
|
1175
|
+
)
|
|
1176
|
+
if document_id not in state.document_hashes:
|
|
1177
|
+
return self._error(
|
|
1178
|
+
404, "document not found", "update_document_payload", context.client_id
|
|
1179
|
+
)
|
|
1180
|
+
|
|
1181
|
+
changed = False
|
|
1182
|
+
if metadata is not None:
|
|
1183
|
+
try:
|
|
1184
|
+
state.document_metadata[document_id] = _validate_metadata(metadata)
|
|
1185
|
+
except ValueError as exc:
|
|
1186
|
+
return self._error(400, str(exc), "update_document_payload", context.client_id)
|
|
1187
|
+
self._metadata_posting_indexes.pop(state.index_key, None)
|
|
1188
|
+
changed = True
|
|
1189
|
+
|
|
1190
|
+
if text is not None or clear_text:
|
|
1191
|
+
if not self.raw_text_storage_enabled:
|
|
1192
|
+
return self._error(
|
|
1193
|
+
400,
|
|
1194
|
+
"raw document text storage is not enabled for this index",
|
|
1195
|
+
"update_document_payload",
|
|
1196
|
+
context.client_id,
|
|
1197
|
+
)
|
|
1198
|
+
if clear_text:
|
|
1199
|
+
state.document_text.pop(document_id, None)
|
|
1200
|
+
else:
|
|
1201
|
+
if not isinstance(text, str):
|
|
1202
|
+
return self._error(
|
|
1203
|
+
400,
|
|
1204
|
+
"text must be a string",
|
|
1205
|
+
"update_document_payload",
|
|
1206
|
+
context.client_id,
|
|
1207
|
+
)
|
|
1208
|
+
state.document_text[document_id] = text
|
|
1209
|
+
changed = True
|
|
1210
|
+
|
|
1211
|
+
if changed:
|
|
1212
|
+
state.updated_at = _utc_now_iso(context.now)
|
|
1213
|
+
self._record_pending_state_journal_documents(
|
|
1214
|
+
state, upserted_document_ids=(document_id,)
|
|
1215
|
+
)
|
|
1216
|
+
# Run the incremental TurboVec sync even though no vectors changed: it is
|
|
1217
|
+
# also what marks this commit delta-eligible (it sets the pending TVIM
|
|
1218
|
+
# delta that _delta_append_ok requires), so _persist_state appends an
|
|
1219
|
+
# O(changed) journal delta instead of rewriting the full O(corpus) base.
|
|
1220
|
+
self._sync_direct_turbovec_index(state)
|
|
1221
|
+
self._persist_state(state)
|
|
1222
|
+
|
|
1223
|
+
return EngineResponse(
|
|
1224
|
+
200,
|
|
1225
|
+
{
|
|
1226
|
+
"status": "updated",
|
|
1227
|
+
"document_id": document_id,
|
|
1228
|
+
"metadata_updated": metadata is not None,
|
|
1229
|
+
"text_updated": text is not None or clear_text,
|
|
1230
|
+
},
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1143
1233
|
def _build_initial_documents(
|
|
1144
1234
|
self,
|
|
1145
1235
|
*,
|
|
@@ -4491,6 +4581,21 @@ class LodeEngine:
|
|
|
4491
4581
|
for document in documents:
|
|
4492
4582
|
state.document_text[document.document_id] = document.text
|
|
4493
4583
|
|
|
4584
|
+
def _capture_vector_document_text(
|
|
4585
|
+
self,
|
|
4586
|
+
state: ClientIndexState,
|
|
4587
|
+
documents: tuple[EngineVectorDocument, ...],
|
|
4588
|
+
) -> None:
|
|
4589
|
+
"""Records optional vector-in payload text without embedding it."""
|
|
4590
|
+
|
|
4591
|
+
if not self.raw_text_storage_enabled:
|
|
4592
|
+
return
|
|
4593
|
+
for document in documents:
|
|
4594
|
+
if document.text is None:
|
|
4595
|
+
state.document_text.pop(document.document_id, None)
|
|
4596
|
+
else:
|
|
4597
|
+
state.document_text[document.document_id] = document.text
|
|
4598
|
+
|
|
4494
4599
|
def _capture_lexical_tokens(
|
|
4495
4600
|
self,
|
|
4496
4601
|
state: ClientIndexState,
|
|
@@ -227,6 +227,27 @@ class LodeIndex:
|
|
|
227
227
|
)
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
+
def update_document_payload(
|
|
231
|
+
self,
|
|
232
|
+
document_id: str,
|
|
233
|
+
*,
|
|
234
|
+
metadata: Mapping[str, Any] | None = None,
|
|
235
|
+
text: str | None = None,
|
|
236
|
+
clear_text: bool = False,
|
|
237
|
+
) -> dict[str, Any]:
|
|
238
|
+
"""Updates an existing document's metadata and/or retained raw text."""
|
|
239
|
+
|
|
240
|
+
return self._unwrap(
|
|
241
|
+
self.engine.update_document_payload(
|
|
242
|
+
context=self._context(),
|
|
243
|
+
index_id=self.index_id,
|
|
244
|
+
document_id=_required_text(document_id, "document_id"),
|
|
245
|
+
metadata=metadata,
|
|
246
|
+
text=text,
|
|
247
|
+
clear_text=clear_text,
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
230
251
|
def query_vector(
|
|
231
252
|
self,
|
|
232
253
|
vector: Iterable[float],
|
|
@@ -540,6 +561,7 @@ def _vector_document_from_item(
|
|
|
540
561
|
document_id=_required_text(item.get("document_id"), "document_id"),
|
|
541
562
|
vector=tuple(float(value) for value in vector),
|
|
542
563
|
metadata=safe_metadata,
|
|
564
|
+
text=_optional_text(item.get("text"), "text"),
|
|
543
565
|
)
|
|
544
566
|
|
|
545
567
|
|
|
@@ -666,6 +688,20 @@ def _required_text(value: Any, name: str) -> str:
|
|
|
666
688
|
return stripped
|
|
667
689
|
|
|
668
690
|
|
|
691
|
+
def _optional_text(value: Any, name: str) -> str | None:
|
|
692
|
+
"""Returns an optional string for vector-in retained payload text."""
|
|
693
|
+
|
|
694
|
+
if value is None:
|
|
695
|
+
return None
|
|
696
|
+
if not isinstance(value, str):
|
|
697
|
+
raise EngineError(
|
|
698
|
+
f"{name} must be a string",
|
|
699
|
+
status_code=400,
|
|
700
|
+
response={"status": "error", "error": f"{name} must be a string"},
|
|
701
|
+
)
|
|
702
|
+
return value
|
|
703
|
+
|
|
704
|
+
|
|
669
705
|
def _required_bool(value: Any, name: str) -> bool:
|
|
670
706
|
"""Returns a JSON-style boolean value while rejecting strings and integers."""
|
|
671
707
|
|