omendb 0.0.23__cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
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.
- omendb/__init__.py +24 -0
- omendb/__init__.pyi +589 -0
- omendb/langchain.py +386 -0
- omendb/llamaindex.py +336 -0
- omendb/omendb.cpython-313t-aarch64-linux-gnu.so +0 -0
- omendb-0.0.23.dist-info/METADATA +280 -0
- omendb-0.0.23.dist-info/RECORD +8 -0
- omendb-0.0.23.dist-info/WHEEL +5 -0
omendb/llamaindex.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""LlamaIndex VectorStore integration for OmenDB.
|
|
2
|
+
|
|
3
|
+
This module provides a LlamaIndex-compatible VectorStore implementation
|
|
4
|
+
that wraps OmenDB for seamless integration with LlamaIndex RAG pipelines.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from llama_index.core import VectorStoreIndex, StorageContext
|
|
8
|
+
>>> from llama_index.embeddings.openai import OpenAIEmbedding
|
|
9
|
+
>>> from omendb.llamaindex import OmenDBVectorStore
|
|
10
|
+
>>>
|
|
11
|
+
>>> # Create vector store
|
|
12
|
+
>>> vector_store = OmenDBVectorStore(path="./my_vectors")
|
|
13
|
+
>>> storage_context = StorageContext.from_defaults(vector_store=vector_store)
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Build index from documents
|
|
16
|
+
>>> index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
|
|
17
|
+
>>>
|
|
18
|
+
>>> # Query
|
|
19
|
+
>>> query_engine = index.as_query_engine()
|
|
20
|
+
>>> response = query_engine.query("What is the main topic?")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from llama_index.core.schema import BaseNode, TextNode
|
|
28
|
+
from llama_index.core.vector_stores.types import (
|
|
29
|
+
BasePydanticVectorStore,
|
|
30
|
+
VectorStoreQuery,
|
|
31
|
+
VectorStoreQueryResult,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OmenDBVectorStore(BasePydanticVectorStore):
|
|
36
|
+
"""LlamaIndex VectorStore implementation using OmenDB.
|
|
37
|
+
|
|
38
|
+
OmenDB is a fast embedded vector database with HNSW + ACORN-1
|
|
39
|
+
that provides ~19,000 QPS @ 10K vectors with 100% recall.
|
|
40
|
+
|
|
41
|
+
Features:
|
|
42
|
+
- HNSW index with adaptive parameters
|
|
43
|
+
- Extended RaBitQ quantization (8x compression)
|
|
44
|
+
- ACORN-1 filtered search (37.79x speedup)
|
|
45
|
+
- MongoDB-style metadata filtering
|
|
46
|
+
- Automatic persistence with persistent storage
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
path: Path to database directory. Uses persistent persistent storage.
|
|
50
|
+
dimensions: Vector dimensionality. If None, auto-detected on first insert.
|
|
51
|
+
**kwargs: Additional arguments passed to omendb.open().
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> from llama_index.core import VectorStoreIndex, StorageContext
|
|
55
|
+
>>> from omendb.llamaindex import OmenDBVectorStore
|
|
56
|
+
>>>
|
|
57
|
+
>>> vector_store = OmenDBVectorStore(path="./my_vectors")
|
|
58
|
+
>>> storage_context = StorageContext.from_defaults(vector_store=vector_store)
|
|
59
|
+
>>> index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
stores_text: bool = True
|
|
63
|
+
flat_metadata: bool = True
|
|
64
|
+
|
|
65
|
+
# Pydantic fields
|
|
66
|
+
path: str = "./omendb-vectors"
|
|
67
|
+
dimensions: int | None = None
|
|
68
|
+
|
|
69
|
+
# Private attributes (not serialized)
|
|
70
|
+
_db: Any = None
|
|
71
|
+
_initialized: bool = False
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
path: str = "./omendb-vectors",
|
|
76
|
+
dimensions: int | None = None,
|
|
77
|
+
**kwargs: Any,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Initialize OmenDBVectorStore.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
path: Path to database directory.
|
|
83
|
+
dimensions: Vector dimensionality (auto-detected on first insert if None).
|
|
84
|
+
**kwargs: Additional arguments for omendb.open().
|
|
85
|
+
"""
|
|
86
|
+
super().__init__(path=path, dimensions=dimensions)
|
|
87
|
+
self._kwargs = kwargs
|
|
88
|
+
self._db = None
|
|
89
|
+
self._initialized = False
|
|
90
|
+
|
|
91
|
+
def _ensure_db(self, dimensions: int | None = None) -> None:
|
|
92
|
+
"""Ensure database is initialized."""
|
|
93
|
+
if self._db is not None:
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
import omendb
|
|
97
|
+
|
|
98
|
+
# Use provided dimensions or fall back to stored
|
|
99
|
+
dims = dimensions or self.dimensions or 1536 # Default to OpenAI dimensions
|
|
100
|
+
self._db = omendb.open(self.path, dimensions=dims, **getattr(self, "_kwargs", {}))
|
|
101
|
+
self._initialized = True
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def class_name(cls) -> str:
|
|
105
|
+
"""Return class name for serialization."""
|
|
106
|
+
return "OmenDBVectorStore"
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def client(self) -> Any:
|
|
110
|
+
"""Return the underlying OmenDB database client."""
|
|
111
|
+
self._ensure_db()
|
|
112
|
+
return self._db
|
|
113
|
+
|
|
114
|
+
def add(
|
|
115
|
+
self,
|
|
116
|
+
nodes: list[BaseNode],
|
|
117
|
+
**kwargs: Any,
|
|
118
|
+
) -> list[str]:
|
|
119
|
+
"""Add nodes to the vector store.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
nodes: List of nodes to add.
|
|
123
|
+
**kwargs: Additional arguments (unused).
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of node IDs that were added.
|
|
127
|
+
"""
|
|
128
|
+
if not nodes:
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
# Get dimensions from first node's embedding
|
|
132
|
+
first_embedding = nodes[0].get_embedding()
|
|
133
|
+
if first_embedding:
|
|
134
|
+
self._ensure_db(dimensions=len(first_embedding))
|
|
135
|
+
else:
|
|
136
|
+
self._ensure_db()
|
|
137
|
+
|
|
138
|
+
ids = []
|
|
139
|
+
items = []
|
|
140
|
+
|
|
141
|
+
for node in nodes:
|
|
142
|
+
node_id = node.node_id
|
|
143
|
+
embedding = node.get_embedding()
|
|
144
|
+
|
|
145
|
+
if embedding is None:
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
# Build metadata from node
|
|
149
|
+
metadata = node.metadata.copy() if node.metadata else {}
|
|
150
|
+
|
|
151
|
+
# Store text content in metadata for retrieval
|
|
152
|
+
text = node.get_content()
|
|
153
|
+
if text:
|
|
154
|
+
metadata["_text"] = text
|
|
155
|
+
|
|
156
|
+
# Store node type info
|
|
157
|
+
metadata["_node_type"] = node.class_name()
|
|
158
|
+
|
|
159
|
+
items.append(
|
|
160
|
+
{
|
|
161
|
+
"id": node_id,
|
|
162
|
+
"vector": embedding,
|
|
163
|
+
"metadata": metadata,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
ids.append(node_id)
|
|
167
|
+
|
|
168
|
+
if items:
|
|
169
|
+
self._db.set(items)
|
|
170
|
+
|
|
171
|
+
return ids
|
|
172
|
+
|
|
173
|
+
def delete(self, ref_doc_id: str, **kwargs: Any) -> None:
|
|
174
|
+
"""Delete nodes by reference document ID.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
ref_doc_id: The document ID to delete.
|
|
178
|
+
**kwargs: Additional arguments (unused).
|
|
179
|
+
"""
|
|
180
|
+
self._ensure_db()
|
|
181
|
+
# OmenDB delete expects a list of IDs
|
|
182
|
+
self._db.delete([ref_doc_id])
|
|
183
|
+
|
|
184
|
+
def delete_nodes(
|
|
185
|
+
self,
|
|
186
|
+
node_ids: list[str] | None = None,
|
|
187
|
+
**kwargs: Any,
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Delete specific nodes by their IDs.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
node_ids: List of node IDs to delete.
|
|
193
|
+
**kwargs: Additional arguments (unused).
|
|
194
|
+
"""
|
|
195
|
+
if not node_ids:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
self._ensure_db()
|
|
199
|
+
self._db.delete(node_ids)
|
|
200
|
+
|
|
201
|
+
def query(
|
|
202
|
+
self,
|
|
203
|
+
query: VectorStoreQuery,
|
|
204
|
+
**kwargs: Any,
|
|
205
|
+
) -> VectorStoreQueryResult:
|
|
206
|
+
"""Query the vector store.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
query: VectorStoreQuery containing query embedding and parameters.
|
|
210
|
+
**kwargs: Additional arguments (unused).
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
VectorStoreQueryResult with matching nodes, similarities, and IDs.
|
|
214
|
+
"""
|
|
215
|
+
self._ensure_db()
|
|
216
|
+
|
|
217
|
+
if query.query_embedding is None:
|
|
218
|
+
return VectorStoreQueryResult(nodes=[], similarities=[], ids=[])
|
|
219
|
+
|
|
220
|
+
# Build filter dict from LlamaIndex filters
|
|
221
|
+
filter_dict = None
|
|
222
|
+
if query.filters is not None:
|
|
223
|
+
filter_dict = self._convert_filters(query.filters)
|
|
224
|
+
|
|
225
|
+
# Query OmenDB
|
|
226
|
+
k = query.similarity_top_k or 10
|
|
227
|
+
results = self._db.search(
|
|
228
|
+
query=query.query_embedding,
|
|
229
|
+
k=k,
|
|
230
|
+
filter=filter_dict,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Convert results to LlamaIndex format
|
|
234
|
+
nodes = []
|
|
235
|
+
similarities = []
|
|
236
|
+
ids = []
|
|
237
|
+
|
|
238
|
+
for result in results:
|
|
239
|
+
node_id = result.get("id", "")
|
|
240
|
+
distance = result.get("distance", 0.0)
|
|
241
|
+
metadata = result.get("metadata", {})
|
|
242
|
+
|
|
243
|
+
# Extract text from metadata
|
|
244
|
+
text = metadata.pop("_text", "")
|
|
245
|
+
metadata.pop("_node_type", None)
|
|
246
|
+
|
|
247
|
+
# Create TextNode
|
|
248
|
+
node = TextNode(
|
|
249
|
+
id_=node_id,
|
|
250
|
+
text=text,
|
|
251
|
+
metadata=metadata,
|
|
252
|
+
embedding=result.get("vector"),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
nodes.append(node)
|
|
256
|
+
# Convert distance to similarity (assuming L2 distance)
|
|
257
|
+
# For L2: similarity = 1 / (1 + distance)
|
|
258
|
+
similarity = 1.0 / (1.0 + distance) if distance >= 0 else 0.0
|
|
259
|
+
similarities.append(similarity)
|
|
260
|
+
ids.append(node_id)
|
|
261
|
+
|
|
262
|
+
return VectorStoreQueryResult(
|
|
263
|
+
nodes=nodes,
|
|
264
|
+
similarities=similarities,
|
|
265
|
+
ids=ids,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def _convert_filters(self, filters: Any) -> dict[str, Any] | None:
|
|
269
|
+
"""Convert LlamaIndex MetadataFilters to OmenDB filter format.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
filters: LlamaIndex MetadataFilters object.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
OmenDB-compatible filter dictionary.
|
|
276
|
+
"""
|
|
277
|
+
if filters is None:
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
from llama_index.core.vector_stores.types import (
|
|
281
|
+
FilterCondition,
|
|
282
|
+
FilterOperator,
|
|
283
|
+
MetadataFilter,
|
|
284
|
+
MetadataFilters,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if not isinstance(filters, MetadataFilters):
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
filter_list = []
|
|
291
|
+
|
|
292
|
+
for f in filters.filters:
|
|
293
|
+
if not isinstance(f, MetadataFilter):
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
key = f.key
|
|
297
|
+
value = f.value
|
|
298
|
+
op = f.operator
|
|
299
|
+
|
|
300
|
+
# Map LlamaIndex operators to OmenDB operators
|
|
301
|
+
if op == FilterOperator.EQ:
|
|
302
|
+
filter_list.append({key: value})
|
|
303
|
+
elif op == FilterOperator.NE:
|
|
304
|
+
filter_list.append({key: {"$ne": value}})
|
|
305
|
+
elif op == FilterOperator.GT:
|
|
306
|
+
filter_list.append({key: {"$gt": value}})
|
|
307
|
+
elif op == FilterOperator.GTE:
|
|
308
|
+
filter_list.append({key: {"$gte": value}})
|
|
309
|
+
elif op == FilterOperator.LT:
|
|
310
|
+
filter_list.append({key: {"$lt": value}})
|
|
311
|
+
elif op == FilterOperator.LTE:
|
|
312
|
+
filter_list.append({key: {"$lte": value}})
|
|
313
|
+
elif op == FilterOperator.IN:
|
|
314
|
+
filter_list.append({key: {"$in": value}})
|
|
315
|
+
elif op == FilterOperator.CONTAINS:
|
|
316
|
+
filter_list.append({key: {"$contains": value}})
|
|
317
|
+
else:
|
|
318
|
+
# Default to equality
|
|
319
|
+
filter_list.append({key: value})
|
|
320
|
+
|
|
321
|
+
if not filter_list:
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
# Combine filters based on condition
|
|
325
|
+
if len(filter_list) == 1:
|
|
326
|
+
return filter_list[0]
|
|
327
|
+
|
|
328
|
+
condition = getattr(filters, "condition", FilterCondition.AND)
|
|
329
|
+
if condition == FilterCondition.OR:
|
|
330
|
+
return {"$or": filter_list}
|
|
331
|
+
else:
|
|
332
|
+
return {"$and": filter_list}
|
|
333
|
+
|
|
334
|
+
def flush(self) -> None:
|
|
335
|
+
"""Flush data to disk for persistence."""
|
|
336
|
+
self._db.flush()
|
|
Binary file
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: omendb
|
|
3
|
+
Version: 0.0.23
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: Other/Proprietary License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Rust
|
|
14
|
+
Requires-Dist: numpy>=1.24.4
|
|
15
|
+
Requires-Dist: langchain-core>=0.2.0 ; extra == 'langchain'
|
|
16
|
+
Requires-Dist: llama-index-core>=0.10.0 ; extra == 'llamaindex'
|
|
17
|
+
Provides-Extra: langchain
|
|
18
|
+
Provides-Extra: llamaindex
|
|
19
|
+
Summary: Fast embedded vector database with HNSW + ACORN-1 filtered search
|
|
20
|
+
Author: OmenDB Team
|
|
21
|
+
License: Elastic-2.0
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
24
|
+
Project-URL: Homepage, https://github.com/omendb/omendb
|
|
25
|
+
Project-URL: Repository, https://github.com/omendb/omendb
|
|
26
|
+
|
|
27
|
+
# OmenDB
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/omendb/)
|
|
30
|
+
[](https://github.com/omendb/omendb/blob/main/LICENSE)
|
|
31
|
+
|
|
32
|
+
Embedded vector database for Python and Node.js. No server, no setup, just install.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install omendb
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import omendb
|
|
42
|
+
|
|
43
|
+
# Create database (persistent) - creates ./mydb.omen file
|
|
44
|
+
db = omendb.open("./mydb", dimensions=128)
|
|
45
|
+
|
|
46
|
+
# Add vectors with metadata
|
|
47
|
+
db.set([
|
|
48
|
+
{"id": "doc1", "vector": [0.1] * 128, "metadata": {"category": "science"}},
|
|
49
|
+
{"id": "doc2", "vector": [0.2] * 128, "metadata": {"category": "history"}},
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
# Search
|
|
53
|
+
results = db.search([0.1] * 128, k=5)
|
|
54
|
+
|
|
55
|
+
# Filtered search
|
|
56
|
+
results = db.search([0.1] * 128, k=5, filter={"category": "science"})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- **Embedded** - Runs in-process, no server needed
|
|
62
|
+
- **Persistent** - Data survives restarts automatically
|
|
63
|
+
- **Filtered search** - Query by metadata with JSON-style filters
|
|
64
|
+
- **Hybrid search** - Combine vector similarity with BM25 text search
|
|
65
|
+
- **Quantization** - 4-8x smaller indexes with minimal recall loss
|
|
66
|
+
|
|
67
|
+
## Platforms
|
|
68
|
+
|
|
69
|
+
| Platform | Status |
|
|
70
|
+
| ---------------------------- | ------------ |
|
|
71
|
+
| Linux (x86_64, ARM64) | Supported |
|
|
72
|
+
| macOS (Intel, Apple Silicon) | Supported |
|
|
73
|
+
| Windows (x86_64) | Experimental |
|
|
74
|
+
|
|
75
|
+
## API
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# Database
|
|
79
|
+
db = omendb.open(path, dimensions) # Open or create
|
|
80
|
+
db = omendb.open(":memory:", dimensions) # In-memory (ephemeral)
|
|
81
|
+
|
|
82
|
+
# CRUD
|
|
83
|
+
db.set(items) # Insert/update vectors
|
|
84
|
+
db.get(id) # Get by ID
|
|
85
|
+
db.get_batch(ids) # Batch get by IDs
|
|
86
|
+
db.delete(ids) # Delete by IDs
|
|
87
|
+
db.delete_by_filter(filter) # Delete by metadata filter
|
|
88
|
+
db.update(id, metadata) # Update metadata only
|
|
89
|
+
|
|
90
|
+
# Iteration
|
|
91
|
+
len(db) # Number of vectors
|
|
92
|
+
db.count() # Same as len(db)
|
|
93
|
+
db.count(filter={...}) # Count matching filter
|
|
94
|
+
db.ids() # Iterate all IDs (lazy)
|
|
95
|
+
db.items() # Get all items as list
|
|
96
|
+
db.exists(id) # Check if ID exists
|
|
97
|
+
"id" in db # Same as exists()
|
|
98
|
+
for item in db: ... # Iterate all items (lazy)
|
|
99
|
+
|
|
100
|
+
# Search
|
|
101
|
+
db.search(query, k) # Vector search
|
|
102
|
+
db.search(query, k, filter={...}) # Filtered search
|
|
103
|
+
db.search(query, k, max_distance=0.5) # Only results with distance <= 0.5
|
|
104
|
+
db.search_batch(queries, k) # Batch search (parallel)
|
|
105
|
+
|
|
106
|
+
# Hybrid search (requires text field in vectors)
|
|
107
|
+
db.search_hybrid(query_vector, query_text, k)
|
|
108
|
+
db.search_hybrid(query_vector, query_text, k, alpha=0.7) # 70% vector, 30% text
|
|
109
|
+
db.search_hybrid(query_vector, query_text, k, subscores=True) # Return separate scores
|
|
110
|
+
db.search_text(query_text, k) # Text-only BM25
|
|
111
|
+
|
|
112
|
+
# Persistence
|
|
113
|
+
db.flush() # Flush to disk
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Distance Filtering
|
|
117
|
+
|
|
118
|
+
Use `max_distance` to filter out low-relevance results (prevents "context rot" in RAG):
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
# Only return results with distance <= 0.5
|
|
122
|
+
results = db.search(query, k=10, max_distance=0.5)
|
|
123
|
+
|
|
124
|
+
# Combine with metadata filter
|
|
125
|
+
results = db.search(query, k=10, filter={"type": "doc"}, max_distance=0.5)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This ensures your RAG pipeline only receives highly relevant context, avoiding distractors that can hurt LLM performance.
|
|
129
|
+
|
|
130
|
+
## Filters
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# Equality
|
|
134
|
+
{"field": "value"} # Shorthand
|
|
135
|
+
{"field": {"$eq": "value"}} # Explicit
|
|
136
|
+
|
|
137
|
+
# Comparison
|
|
138
|
+
{"field": {"$ne": "value"}} # Not equal
|
|
139
|
+
{"field": {"$gt": 10}} # Greater than
|
|
140
|
+
{"field": {"$gte": 10}} # Greater or equal
|
|
141
|
+
{"field": {"$lt": 10}} # Less than
|
|
142
|
+
{"field": {"$lte": 10}} # Less or equal
|
|
143
|
+
|
|
144
|
+
# Membership
|
|
145
|
+
{"field": {"$in": ["a", "b"]}} # In list
|
|
146
|
+
{"field": {"$contains": "sub"}} # String contains
|
|
147
|
+
|
|
148
|
+
# Logical
|
|
149
|
+
{"$and": [{...}, {...}]} # AND
|
|
150
|
+
{"$or": [{...}, {...}]} # OR
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Configuration
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
db = omendb.open(
|
|
157
|
+
"./mydb", # Creates ./mydb.omen + ./mydb.wal
|
|
158
|
+
dimensions=384,
|
|
159
|
+
m=16, # HNSW connections per node (default: 16)
|
|
160
|
+
ef_construction=200, # Index build quality (default: 100)
|
|
161
|
+
ef_search=100, # Search quality (default: 100)
|
|
162
|
+
quantization=True, # SQ8 quantization (default: None)
|
|
163
|
+
metric="cosine", # Distance metric (default: "l2")
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Quantization options:
|
|
167
|
+
# - True or "sq8": SQ8 ~4x smaller, ~99% recall (recommended)
|
|
168
|
+
# - "rabitq": RaBitQ ~8x smaller, ~98% recall
|
|
169
|
+
# - None/False: Full precision (default)
|
|
170
|
+
|
|
171
|
+
# Distance metric options:
|
|
172
|
+
# - "l2" or "euclidean": Euclidean distance (default)
|
|
173
|
+
# - "cosine": Cosine distance (1 - cosine similarity)
|
|
174
|
+
# - "dot" or "ip": Inner product (for MIPS)
|
|
175
|
+
|
|
176
|
+
# Context manager (auto-flush on exit)
|
|
177
|
+
with omendb.open("./db", dimensions=768) as db:
|
|
178
|
+
db.set([...])
|
|
179
|
+
|
|
180
|
+
# Hybrid search with alpha (0=text, 1=vector, default=0.5)
|
|
181
|
+
db.search_hybrid(query_vec, "query text", k=10, alpha=0.7)
|
|
182
|
+
|
|
183
|
+
# Get separate keyword and semantic scores for debugging/tuning
|
|
184
|
+
results = db.search_hybrid(query_vec, "query text", k=10, subscores=True)
|
|
185
|
+
# Returns: {"id": "...", "score": 0.85, "keyword_score": 0.92, "semantic_score": 0.78}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Performance
|
|
189
|
+
|
|
190
|
+
**10K vectors, Apple M3 Max** (m=16, ef=100, k=10):
|
|
191
|
+
|
|
192
|
+
| Dimension | Single QPS | Batch QPS | Speedup |
|
|
193
|
+
| --------- | ---------- | --------- | ------- |
|
|
194
|
+
| 128D | 12,000+ | 87,000+ | 7.2x |
|
|
195
|
+
| 768D | 3,800+ | 20,500+ | 5.4x |
|
|
196
|
+
| 1536D | 1,600+ | 6,200+ | 3.8x |
|
|
197
|
+
|
|
198
|
+
**SIFT-1M** (1M vectors, 128D, m=16, ef=100, k=10):
|
|
199
|
+
|
|
200
|
+
| Machine | QPS | Recall |
|
|
201
|
+
| ------------ | ----- | ------ |
|
|
202
|
+
| i9-13900KF | 4,591 | 98.6% |
|
|
203
|
+
| Apple M3 Max | 3,216 | 98.4% |
|
|
204
|
+
|
|
205
|
+
**Quantization** reduces memory with minimal recall loss:
|
|
206
|
+
|
|
207
|
+
| Mode | Compression | Use Case |
|
|
208
|
+
| ------ | ----------- | ------------------------------ |
|
|
209
|
+
| f32 | 1x | Default, highest recall |
|
|
210
|
+
| sq8 | 4x | Recommended for most users |
|
|
211
|
+
| rabitq | 8x | Large datasets, cost-sensitive |
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
db = omendb.open("./db", dimensions=768, quantization=True) # Enable SQ8
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
<details>
|
|
218
|
+
<summary>Benchmark methodology</summary>
|
|
219
|
+
|
|
220
|
+
- **Parameters**: m=16, ef_construction=100, ef_search=100
|
|
221
|
+
- **Batch**: Uses Rayon for parallel search across all cores
|
|
222
|
+
- **Recall**: Validated against brute-force ground truth on SIFT/GloVe
|
|
223
|
+
- **Reproduce**:
|
|
224
|
+
- Quick (10K): `uv run python benchmarks/run.py`
|
|
225
|
+
- SIFT-1M: `uv run python benchmarks/ann_dataset_test.py --dataset sift-128-euclidean`
|
|
226
|
+
|
|
227
|
+
</details>
|
|
228
|
+
|
|
229
|
+
## Examples
|
|
230
|
+
|
|
231
|
+
See [`python/examples/`](python/examples/) for complete working examples:
|
|
232
|
+
|
|
233
|
+
- `quickstart.py` - Minimal working example
|
|
234
|
+
- `basic.py` - CRUD operations and persistence
|
|
235
|
+
- `filters.py` - All filter operators
|
|
236
|
+
- `rag.py` - RAG workflow with mock embeddings
|
|
237
|
+
|
|
238
|
+
## Integrations
|
|
239
|
+
|
|
240
|
+
### LangChain
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pip install omendb[langchain]
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from langchain_openai import OpenAIEmbeddings
|
|
248
|
+
from omendb.langchain import OmenDBVectorStore
|
|
249
|
+
|
|
250
|
+
store = OmenDBVectorStore.from_texts(
|
|
251
|
+
texts=["Paris is the capital of France"],
|
|
252
|
+
embedding=OpenAIEmbeddings(),
|
|
253
|
+
path="./langchain_vectors",
|
|
254
|
+
)
|
|
255
|
+
docs = store.similarity_search("capital of France", k=1)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### LlamaIndex
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
pip install omendb[llamaindex]
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from llama_index.core import VectorStoreIndex, Document, StorageContext
|
|
266
|
+
from omendb.llamaindex import OmenDBVectorStore
|
|
267
|
+
|
|
268
|
+
vector_store = OmenDBVectorStore(path="./llama_vectors")
|
|
269
|
+
storage_context = StorageContext.from_defaults(vector_store=vector_store)
|
|
270
|
+
index = VectorStoreIndex.from_documents(
|
|
271
|
+
[Document(text="OmenDB is fast")],
|
|
272
|
+
storage_context=storage_context,
|
|
273
|
+
)
|
|
274
|
+
response = index.as_query_engine().query("What is OmenDB?")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
[Elastic License 2.0](LICENSE) - Free to use, modify, and embed. The only restriction: you can't offer OmenDB as a managed service to third parties.
|
|
280
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
omendb/__init__.py,sha256=u92FKzI-Sr3Usd9LLi-XCul0QC4QgKhzICaDBIBOtAE,834
|
|
2
|
+
omendb/__init__.pyi,sha256=oWtFhkFRi-5FvR0a4gayZ9W2Fu8iP60vlwlqR9Ru3U8,17118
|
|
3
|
+
omendb/langchain.py,sha256=w0W5W17ZlSzxoor1LYIzrZYrRvoKK0WW1oQBNcZ_7mk,12184
|
|
4
|
+
omendb/llamaindex.py,sha256=3aKSUufpn33uSK6h61BdKEZvmXNPbz-FexRmlALOtLs,10328
|
|
5
|
+
omendb/omendb.cpython-313t-aarch64-linux-gnu.so,sha256=wS-gTAdJc0-7yyVeuqVQkExy7oM-s6sIMZ2ODpVaSZ0,81094304
|
|
6
|
+
omendb-0.0.23.dist-info/METADATA,sha256=-xyaRPNp1moyGIbD1JJOtH7wODIY7fEVrqhZtUedRls,9186
|
|
7
|
+
omendb-0.0.23.dist-info/WHEEL,sha256=q835leI5tUqUNzGLT6_2V4Cxb6Anw8rRDNKWlUcsVqo,151
|
|
8
|
+
omendb-0.0.23.dist-info/RECORD,,
|