llama-index-vector-stores-couchbase 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llama_index_vector_stores_couchbase-0.1.0/PKG-INFO +19 -0
- llama_index_vector_stores_couchbase-0.1.0/README.md +1 -0
- llama_index_vector_stores_couchbase-0.1.0/llama_index/vector_stores/couchbase/BUILD +1 -0
- llama_index_vector_stores_couchbase-0.1.0/llama_index/vector_stores/couchbase/__init__.py +4 -0
- llama_index_vector_stores_couchbase-0.1.0/llama_index/vector_stores/couchbase/base.py +529 -0
- llama_index_vector_stores_couchbase-0.1.0/pyproject.toml +57 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: llama-index-vector-stores-couchbase
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: llama-index vector_stores couchbase integration
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Couchbase
|
|
7
|
+
Author-email: devadvocates@couchbase.com
|
|
8
|
+
Requires-Python: >=3.8.1,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Requires-Dist: couchbase (>=4.2.0,<5.0.0)
|
|
15
|
+
Requires-Dist: llama-index-core (>=0.10.1)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# LlamaIndex Vector_Stores Integration: Couchbase
|
|
19
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# LlamaIndex Vector_Stores Integration: Couchbase
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
python_sources()
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Couchbase Vector store interface.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from llama_index.core.schema import BaseNode, MetadataMode, TextNode
|
|
10
|
+
from llama_index.core.vector_stores.types import (
|
|
11
|
+
BasePydanticVectorStore,
|
|
12
|
+
VectorStoreQuery,
|
|
13
|
+
VectorStoreQueryResult,
|
|
14
|
+
MetadataFilters,
|
|
15
|
+
)
|
|
16
|
+
from llama_index.core.vector_stores.utils import (
|
|
17
|
+
node_to_metadata_dict,
|
|
18
|
+
metadata_dict_to_node,
|
|
19
|
+
)
|
|
20
|
+
from llama_index.core.bridge.pydantic import PrivateAttr
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _transform_couchbase_filter_condition(condition: str) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Convert standard metadata filter condition to Couchbase specific condition.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
condition: standard metadata filter condition
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Couchbase specific condition
|
|
34
|
+
"""
|
|
35
|
+
if condition == "and":
|
|
36
|
+
return "conjuncts"
|
|
37
|
+
elif condition == "or":
|
|
38
|
+
return "disjuncts"
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Filter condition {condition} not supported")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _transform_couchbase_filter_operator(
|
|
44
|
+
operator: str, field: str, value: Any
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Convert standard metadata filter operator to Couchbase specific filter operation.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
operator: standard metadata filter operator
|
|
51
|
+
field: metadata field
|
|
52
|
+
value: value to apply for the filter
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Dictionary with Couchbase specific search operation.
|
|
56
|
+
"""
|
|
57
|
+
if operator == "!=":
|
|
58
|
+
return {"must_not": {"disjuncts": [{"field": field, "match": value}]}}
|
|
59
|
+
elif operator == "==":
|
|
60
|
+
return {"field": field, "match": value}
|
|
61
|
+
elif operator == ">":
|
|
62
|
+
return {"min": value, "inclusive_min": False, "field": field}
|
|
63
|
+
elif operator == "<":
|
|
64
|
+
return {"max": value, "inclusive_max": False, "field": field}
|
|
65
|
+
elif operator == ">=":
|
|
66
|
+
return {"min": value, "inclusive_min": True, "field": field}
|
|
67
|
+
elif operator == "<=":
|
|
68
|
+
return {"max": value, "inclusive_max": True, "field": field}
|
|
69
|
+
elif operator == "text_match":
|
|
70
|
+
return {"match_phrase": value, "field": field}
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Filter operator {operator} not supported")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _to_couchbase_filter(standard_filters: MetadataFilters) -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Convert standard filters to Couchbase filter.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
standard_filters (str): Standard Llama-index filters.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Dictionary with Couchbase search query.
|
|
84
|
+
"""
|
|
85
|
+
filters = {}
|
|
86
|
+
filters_list = []
|
|
87
|
+
condition = standard_filters.condition
|
|
88
|
+
condition = _transform_couchbase_filter_condition(condition)
|
|
89
|
+
|
|
90
|
+
if standard_filters.filters:
|
|
91
|
+
for filter in standard_filters.filters:
|
|
92
|
+
if filter.operator:
|
|
93
|
+
transformed_filter = _transform_couchbase_filter_operator(
|
|
94
|
+
filter.operator, f"metadata.{filter.key}", filter.value
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
filters_list.append(transformed_filter)
|
|
98
|
+
else:
|
|
99
|
+
filters_list.append(
|
|
100
|
+
{
|
|
101
|
+
"match": {
|
|
102
|
+
"field": f"metadata.{filter.key}",
|
|
103
|
+
"value": filter.value,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
if len(filters_list) == 1:
|
|
108
|
+
# If there is only one filter, return it directly
|
|
109
|
+
return filters_list[0]
|
|
110
|
+
elif len(filters_list) > 1:
|
|
111
|
+
filters[condition] = filters_list
|
|
112
|
+
return {"query": filters}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class CouchbaseVectorStore(BasePydanticVectorStore):
|
|
116
|
+
"""
|
|
117
|
+
Couchbase Vector Store.
|
|
118
|
+
|
|
119
|
+
To use, you should have the ``couchbase`` python package installed.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
stores_text: bool = True
|
|
124
|
+
flat_metadata: bool = True
|
|
125
|
+
# Default batch size
|
|
126
|
+
DEFAULT_BATCH_SIZE: int = 100
|
|
127
|
+
|
|
128
|
+
_cluster: Any = PrivateAttr()
|
|
129
|
+
_bucket: Any = PrivateAttr()
|
|
130
|
+
_scope: Any = PrivateAttr()
|
|
131
|
+
_collection: Any = PrivateAttr()
|
|
132
|
+
_bucket_name: str = PrivateAttr()
|
|
133
|
+
_scope_name: str = PrivateAttr()
|
|
134
|
+
_collection_name: str = PrivateAttr()
|
|
135
|
+
_index_name: str = PrivateAttr()
|
|
136
|
+
_id_key: str = PrivateAttr()
|
|
137
|
+
_text_key: str = PrivateAttr()
|
|
138
|
+
_embedding_key: str = PrivateAttr()
|
|
139
|
+
_metadata_key: str = PrivateAttr()
|
|
140
|
+
_scoped_index: bool = PrivateAttr()
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
cluster: Any,
|
|
145
|
+
bucket_name: str,
|
|
146
|
+
scope_name: str,
|
|
147
|
+
collection_name: str,
|
|
148
|
+
index_name: str,
|
|
149
|
+
text_key: Optional[str] = "text",
|
|
150
|
+
embedding_key: Optional[str] = "embedding",
|
|
151
|
+
metadata_key: Optional[str] = "metadata",
|
|
152
|
+
scoped_index: bool = True,
|
|
153
|
+
) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Initializes a connection to a Couchbase Vector Store.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
cluster (Cluster): Couchbase cluster object with active connection.
|
|
159
|
+
bucket_name (str): Name of bucket to store documents in.
|
|
160
|
+
scope_name (str): Name of scope in the bucket to store documents in.
|
|
161
|
+
collection_name (str): Name of collection in the scope to store documents in.
|
|
162
|
+
index_name (str): Name of the Search index.
|
|
163
|
+
text_key (Optional[str], optional): The field for the document text.
|
|
164
|
+
Defaults to "text".
|
|
165
|
+
embedding_key (Optional[str], optional): The field for the document embedding.
|
|
166
|
+
Defaults to "embedding".
|
|
167
|
+
metadata_key (Optional[str], optional): The field for the document metadata.
|
|
168
|
+
Defaults to "metadata".
|
|
169
|
+
scoped_index (Optional[bool]): specify whether the index is a scoped index.
|
|
170
|
+
Set to True by default.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
None
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
from couchbase.cluster import Cluster
|
|
177
|
+
except ImportError as e:
|
|
178
|
+
raise ImportError(
|
|
179
|
+
"Could not import couchbase python package. "
|
|
180
|
+
"Please install couchbase SDK with `pip install couchbase`."
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not isinstance(cluster, Cluster):
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"cluster should be an instance of couchbase.Cluster, "
|
|
186
|
+
f"got {type(cluster)}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
self._cluster = cluster
|
|
190
|
+
|
|
191
|
+
if not bucket_name:
|
|
192
|
+
raise ValueError("bucket_name must be provided.")
|
|
193
|
+
|
|
194
|
+
if not scope_name:
|
|
195
|
+
raise ValueError("scope_name must be provided.")
|
|
196
|
+
|
|
197
|
+
if not collection_name:
|
|
198
|
+
raise ValueError("collection_name must be provided.")
|
|
199
|
+
|
|
200
|
+
if not index_name:
|
|
201
|
+
raise ValueError("index_name must be provided.")
|
|
202
|
+
|
|
203
|
+
self._bucket_name = bucket_name
|
|
204
|
+
self._scope_name = scope_name
|
|
205
|
+
self._collection_name = collection_name
|
|
206
|
+
self._text_key = text_key
|
|
207
|
+
self._embedding_key = embedding_key
|
|
208
|
+
self._index_name = index_name
|
|
209
|
+
self._metadata_key = metadata_key
|
|
210
|
+
self._scoped_index = scoped_index
|
|
211
|
+
|
|
212
|
+
# Check if the bucket exists
|
|
213
|
+
if not self._check_bucket_exists():
|
|
214
|
+
raise ValueError(
|
|
215
|
+
f"Bucket {self._bucket_name} does not exist. "
|
|
216
|
+
" Please create the bucket before searching."
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
self._bucket = self._cluster.bucket(self._bucket_name)
|
|
221
|
+
self._scope = self._bucket.scope(self._scope_name)
|
|
222
|
+
self._collection = self._scope.collection(self._collection_name)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
raise ValueError(
|
|
225
|
+
"Error connecting to couchbase. "
|
|
226
|
+
"Please check the connection and credentials."
|
|
227
|
+
) from e
|
|
228
|
+
|
|
229
|
+
# Check if the scope and collection exists. Throws ValueError if they don't
|
|
230
|
+
try:
|
|
231
|
+
self._check_scope_and_collection_exists()
|
|
232
|
+
except Exception as e:
|
|
233
|
+
raise
|
|
234
|
+
|
|
235
|
+
# Check if the index exists. Throws ValueError if it doesn't
|
|
236
|
+
try:
|
|
237
|
+
self._check_index_exists()
|
|
238
|
+
except Exception as e:
|
|
239
|
+
raise
|
|
240
|
+
|
|
241
|
+
self._bucket = self._cluster.bucket(self._bucket_name)
|
|
242
|
+
self._scope = self._bucket.scope(self._scope_name)
|
|
243
|
+
self._collection = self._scope.collection(self._collection_name)
|
|
244
|
+
|
|
245
|
+
super().__init__()
|
|
246
|
+
|
|
247
|
+
def add(self, nodes: List[BaseNode], **kwargs: Any) -> List[str]:
|
|
248
|
+
"""
|
|
249
|
+
Add nodes to the collection and return their document IDs.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
nodes (List[BaseNode]): List of nodes to add.
|
|
253
|
+
**kwargs (Any): Additional keyword arguments.
|
|
254
|
+
batch_size (int): Size of the batch for batch insert.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
List[str]: List of document IDs for the added nodes.
|
|
258
|
+
"""
|
|
259
|
+
from couchbase.exceptions import DocumentExistsException
|
|
260
|
+
|
|
261
|
+
batch_size = kwargs.get("batch_size", self.DEFAULT_BATCH_SIZE)
|
|
262
|
+
documents_to_insert = []
|
|
263
|
+
doc_ids = []
|
|
264
|
+
|
|
265
|
+
for node in nodes:
|
|
266
|
+
metadata = node_to_metadata_dict(
|
|
267
|
+
node,
|
|
268
|
+
remove_text=True,
|
|
269
|
+
text_field=self._text_key,
|
|
270
|
+
flat_metadata=self.flat_metadata,
|
|
271
|
+
)
|
|
272
|
+
doc_id: str = node.node_id
|
|
273
|
+
|
|
274
|
+
doc = {
|
|
275
|
+
self._text_key: node.get_content(metadata_mode=MetadataMode.NONE),
|
|
276
|
+
self._embedding_key: node.embedding,
|
|
277
|
+
self._metadata_key: metadata,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
documents_to_insert.append({doc_id: doc})
|
|
281
|
+
|
|
282
|
+
for i in range(0, len(documents_to_insert), batch_size):
|
|
283
|
+
batch = documents_to_insert[i : i + batch_size]
|
|
284
|
+
try:
|
|
285
|
+
# convert the list of dicts to a single dict for batch insert
|
|
286
|
+
insert_batch = {}
|
|
287
|
+
for doc in batch:
|
|
288
|
+
insert_batch.update(doc)
|
|
289
|
+
|
|
290
|
+
logger.debug("Inserting batch of documents to Couchbase", insert_batch)
|
|
291
|
+
|
|
292
|
+
# upsert the batch of documents into the collection
|
|
293
|
+
result = self._collection.upsert_multi(insert_batch)
|
|
294
|
+
|
|
295
|
+
logger.debug(f"Insert result: {result.all_ok}")
|
|
296
|
+
if result.all_ok:
|
|
297
|
+
doc_ids.extend(insert_batch.keys())
|
|
298
|
+
|
|
299
|
+
except DocumentExistsException as e:
|
|
300
|
+
logger.debug(f"Document already exists: {e}")
|
|
301
|
+
|
|
302
|
+
logger.debug("Inserted batch of documents to Couchbase")
|
|
303
|
+
return doc_ids
|
|
304
|
+
|
|
305
|
+
def delete(self, ref_doc_id: str, **kwargs: Any) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Delete a document by its reference document ID.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
ref_doc_id: The reference document ID to be deleted.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
None
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
document_field = self._metadata_key + ".ref_doc_id"
|
|
317
|
+
self._scope.query(
|
|
318
|
+
f"DELETE FROM `{self._collection_name}` WHERE {document_field} = '{ref_doc_id}'"
|
|
319
|
+
).execute()
|
|
320
|
+
logger.debug(f"Deleted document {ref_doc_id}")
|
|
321
|
+
except Exception:
|
|
322
|
+
logger.error(f"Error deleting document {ref_doc_id}")
|
|
323
|
+
raise
|
|
324
|
+
|
|
325
|
+
def query(self, query: VectorStoreQuery, **kwargs: Any) -> VectorStoreQueryResult:
|
|
326
|
+
"""
|
|
327
|
+
Executes a query in the vector store and returns the result.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
query (VectorStoreQuery): The query object containing the search parameters.
|
|
331
|
+
**kwargs (Any): Additional keyword arguments.
|
|
332
|
+
cb_search_options (Dict): Search options to pass to Couchbase Search
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
VectorStoreQueryResult: The result of the query containing the top-k nodes, similarities, and ids.
|
|
336
|
+
"""
|
|
337
|
+
import couchbase.search as search
|
|
338
|
+
from couchbase.options import SearchOptions
|
|
339
|
+
from couchbase.vector_search import VectorQuery, VectorSearch
|
|
340
|
+
|
|
341
|
+
fields = query.output_fields
|
|
342
|
+
|
|
343
|
+
if not fields:
|
|
344
|
+
fields = ["*"]
|
|
345
|
+
|
|
346
|
+
# Document text field needs to be returned from the search
|
|
347
|
+
if self._text_key not in fields and fields != ["*"]:
|
|
348
|
+
fields.append(self._text_key)
|
|
349
|
+
|
|
350
|
+
logger.debug("Output Fields: ", fields)
|
|
351
|
+
|
|
352
|
+
k = query.similarity_top_k
|
|
353
|
+
|
|
354
|
+
# Get the search options
|
|
355
|
+
search_options = kwargs.get("cb_search_options", {})
|
|
356
|
+
|
|
357
|
+
if search_options and query.filters:
|
|
358
|
+
raise ValueError("Cannot use both filters and cb_search_options")
|
|
359
|
+
elif query.filters:
|
|
360
|
+
couchbase_options = _to_couchbase_filter(query.filters)
|
|
361
|
+
logger.debug(f"Filters transformed to Couchbase: {couchbase_options}")
|
|
362
|
+
search_options = couchbase_options
|
|
363
|
+
|
|
364
|
+
logger.debug(f"Filters: {search_options}")
|
|
365
|
+
|
|
366
|
+
# Create Search Request
|
|
367
|
+
search_req = search.SearchRequest.create(
|
|
368
|
+
VectorSearch.from_vector_query(
|
|
369
|
+
VectorQuery(
|
|
370
|
+
self._embedding_key,
|
|
371
|
+
query.query_embedding,
|
|
372
|
+
k,
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
logger.debug("Querying Couchbase")
|
|
379
|
+
if self._scoped_index:
|
|
380
|
+
search_iter = self._scope.search(
|
|
381
|
+
self._index_name,
|
|
382
|
+
search_req,
|
|
383
|
+
SearchOptions(limit=k, fields=fields, raw=search_options),
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
else:
|
|
387
|
+
search_iter = self._cluster.search(
|
|
388
|
+
self._index_name,
|
|
389
|
+
search_req,
|
|
390
|
+
SearchOptions(limit=k, fields=fields, raw=search_options),
|
|
391
|
+
)
|
|
392
|
+
except Exception as e:
|
|
393
|
+
logger.debug(f"Search failed with error {e}")
|
|
394
|
+
raise ValueError(f"Search failed with error: {e}")
|
|
395
|
+
|
|
396
|
+
top_k_nodes = []
|
|
397
|
+
top_k_scores = []
|
|
398
|
+
top_k_ids = []
|
|
399
|
+
|
|
400
|
+
# Parse the results
|
|
401
|
+
for result in search_iter.rows():
|
|
402
|
+
text = result.fields.pop(self._text_key, "")
|
|
403
|
+
|
|
404
|
+
score = result.score
|
|
405
|
+
|
|
406
|
+
# Format the metadata into a dictionary
|
|
407
|
+
metadata_dict = self._format_metadata(result.fields)
|
|
408
|
+
|
|
409
|
+
id = result.id
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
node = metadata_dict_to_node(metadata_dict, text)
|
|
413
|
+
except Exception:
|
|
414
|
+
# Deprecated legacy logic for backwards compatibility
|
|
415
|
+
node = TextNode(
|
|
416
|
+
text=text,
|
|
417
|
+
id_=id,
|
|
418
|
+
score=score,
|
|
419
|
+
metadata=metadata_dict,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
top_k_nodes.append(node)
|
|
423
|
+
top_k_scores.append(score)
|
|
424
|
+
top_k_ids.append(id)
|
|
425
|
+
|
|
426
|
+
return VectorStoreQueryResult(
|
|
427
|
+
nodes=top_k_nodes, similarities=top_k_scores, ids=top_k_ids
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def client(self) -> Any:
|
|
432
|
+
"""
|
|
433
|
+
Property function to access the client attribute.
|
|
434
|
+
"""
|
|
435
|
+
return self._cluster
|
|
436
|
+
|
|
437
|
+
def _check_bucket_exists(self) -> bool:
|
|
438
|
+
"""Check if the bucket exists in the linked Couchbase cluster.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
True if the bucket exists
|
|
442
|
+
"""
|
|
443
|
+
bucket_manager = self._cluster.buckets()
|
|
444
|
+
try:
|
|
445
|
+
bucket_manager.get_bucket(self._bucket_name)
|
|
446
|
+
return True
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.debug("Error checking if bucket exists:", e)
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
def _check_scope_and_collection_exists(self) -> bool:
|
|
452
|
+
"""Check if the scope and collection exists in the linked Couchbase bucket
|
|
453
|
+
Returns:
|
|
454
|
+
True if the scope and collection exist in the bucket
|
|
455
|
+
Raises a ValueError if either is not found.
|
|
456
|
+
"""
|
|
457
|
+
scope_collection_map: Dict[str, Any] = {}
|
|
458
|
+
|
|
459
|
+
# Get a list of all scopes in the bucket
|
|
460
|
+
for scope in self._bucket.collections().get_all_scopes():
|
|
461
|
+
scope_collection_map[scope.name] = []
|
|
462
|
+
|
|
463
|
+
# Get a list of all the collections in the scope
|
|
464
|
+
for collection in scope.collections:
|
|
465
|
+
scope_collection_map[scope.name].append(collection.name)
|
|
466
|
+
|
|
467
|
+
# Check if the scope exists
|
|
468
|
+
if self._scope_name not in scope_collection_map:
|
|
469
|
+
raise ValueError(
|
|
470
|
+
f"Scope {self._scope_name} not found in Couchbase "
|
|
471
|
+
f"bucket {self._bucket_name}"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# Check if the collection exists in the scope
|
|
475
|
+
if self._collection_name not in scope_collection_map[self._scope_name]:
|
|
476
|
+
raise ValueError(
|
|
477
|
+
f"Collection {self._collection_name} not found in scope "
|
|
478
|
+
f"{self._scope_name} in Couchbase bucket {self._bucket_name}"
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
return True
|
|
482
|
+
|
|
483
|
+
def _check_index_exists(self) -> bool:
|
|
484
|
+
"""Check if the Search index exists in the linked Couchbase cluster
|
|
485
|
+
Returns:
|
|
486
|
+
bool: True if the index exists, False otherwise.
|
|
487
|
+
Raises a ValueError if the index does not exist.
|
|
488
|
+
"""
|
|
489
|
+
if self._scoped_index:
|
|
490
|
+
all_indexes = [
|
|
491
|
+
index.name for index in self._scope.search_indexes().get_all_indexes()
|
|
492
|
+
]
|
|
493
|
+
if self._index_name not in all_indexes:
|
|
494
|
+
raise ValueError(
|
|
495
|
+
f"Index {self._index_name} does not exist. "
|
|
496
|
+
" Please create the index before searching."
|
|
497
|
+
)
|
|
498
|
+
else:
|
|
499
|
+
all_indexes = [
|
|
500
|
+
index.name for index in self._cluster.search_indexes().get_all_indexes()
|
|
501
|
+
]
|
|
502
|
+
if self._index_name not in all_indexes:
|
|
503
|
+
raise ValueError(
|
|
504
|
+
f"Index {self._index_name} does not exist. "
|
|
505
|
+
" Please create the index before searching."
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
return True
|
|
509
|
+
|
|
510
|
+
def _format_metadata(self, row_fields: Dict[str, Any]) -> Dict[str, Any]:
|
|
511
|
+
"""Helper method to format the metadata from the Couchbase Search API.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
row_fields (Dict[str, Any]): The fields to format.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Dict[str, Any]: The formatted metadata.
|
|
518
|
+
"""
|
|
519
|
+
metadata = {}
|
|
520
|
+
for key, value in row_fields.items():
|
|
521
|
+
# Couchbase Search returns the metadata key with a prefix
|
|
522
|
+
# `metadata.` We remove it to get the original metadata key
|
|
523
|
+
if key.startswith(self._metadata_key):
|
|
524
|
+
new_key = key.split(self._metadata_key + ".")[-1]
|
|
525
|
+
metadata[new_key] = value
|
|
526
|
+
else:
|
|
527
|
+
metadata[key] = value
|
|
528
|
+
|
|
529
|
+
return metadata
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "poetry.core.masonry.api"
|
|
3
|
+
requires = ["poetry-core"]
|
|
4
|
+
|
|
5
|
+
[tool.codespell]
|
|
6
|
+
check-filenames = true
|
|
7
|
+
check-hidden = true
|
|
8
|
+
# Feel free to un-skip examples, and experimental, you will just need to
|
|
9
|
+
# work through many typos (--write-changes and --interactive will help)
|
|
10
|
+
skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb"
|
|
11
|
+
|
|
12
|
+
[tool.llamahub]
|
|
13
|
+
contains_example = false
|
|
14
|
+
import_path = "llama_index.vector_stores.couchbase"
|
|
15
|
+
|
|
16
|
+
[tool.llamahub.class_authors]
|
|
17
|
+
CouchbaseVectorStore = "nithishr"
|
|
18
|
+
|
|
19
|
+
[tool.mypy]
|
|
20
|
+
disallow_untyped_defs = true
|
|
21
|
+
# Remove venv skip when integrated with pre-commit
|
|
22
|
+
exclude = ["_static", "build", "examples", "notebooks", "venv"]
|
|
23
|
+
ignore_missing_imports = true
|
|
24
|
+
python_version = "3.8"
|
|
25
|
+
|
|
26
|
+
[tool.poetry]
|
|
27
|
+
authors = ["Couchbase <devadvocates@couchbase.com>"]
|
|
28
|
+
description = "llama-index vector_stores couchbase integration"
|
|
29
|
+
license = "MIT"
|
|
30
|
+
name = "llama-index-vector-stores-couchbase"
|
|
31
|
+
packages = [{include = "llama_index/"}]
|
|
32
|
+
readme = "README.md"
|
|
33
|
+
version = "0.1.0"
|
|
34
|
+
|
|
35
|
+
[tool.poetry.dependencies]
|
|
36
|
+
python = ">=3.8.1,<4.0"
|
|
37
|
+
llama-index-core = ">=0.10.1"
|
|
38
|
+
couchbase = "^4.2.0"
|
|
39
|
+
|
|
40
|
+
[tool.poetry.group.dev.dependencies]
|
|
41
|
+
black = {extras = ["jupyter"], version = "<=23.9.1,>=23.7.0"}
|
|
42
|
+
codespell = {extras = ["toml"], version = ">=v2.2.6"}
|
|
43
|
+
ipython = "8.10.0"
|
|
44
|
+
jupyter = "^1.0.0"
|
|
45
|
+
mypy = "0.991"
|
|
46
|
+
pre-commit = "3.2.0"
|
|
47
|
+
pylint = "2.15.10"
|
|
48
|
+
pytest = "7.2.1"
|
|
49
|
+
pytest-mock = "3.11.1"
|
|
50
|
+
ruff = "0.0.292"
|
|
51
|
+
tree-sitter-languages = "^1.8.0"
|
|
52
|
+
types-Deprecated = ">=0.1.0"
|
|
53
|
+
types-PyYAML = "^6.0.12.12"
|
|
54
|
+
types-protobuf = "^4.24.0.4"
|
|
55
|
+
types-redis = "4.5.5.0"
|
|
56
|
+
types-requests = "2.28.11.8" # TODO: unpin when mypy>0.991
|
|
57
|
+
types-setuptools = "67.1.0.0"
|