555e282cc4
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
649 lines
22 KiB
Python
649 lines
22 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
import pickle
|
|
import uuid
|
|
import warnings
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
import numpy as np
|
|
from pydantic import BaseModel
|
|
|
|
try:
|
|
# Suppress SWIG deprecation warnings from FAISS
|
|
warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*SwigPy.*")
|
|
warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*swigvarlink.*")
|
|
|
|
logging.getLogger("faiss").setLevel(logging.WARNING)
|
|
logging.getLogger("faiss.loader").setLevel(logging.WARNING)
|
|
|
|
import faiss
|
|
except ImportError:
|
|
raise ImportError(
|
|
"Could not import faiss python package. "
|
|
"Please install it with `pip install faiss-gpu` (for CUDA supported GPU) "
|
|
"or `pip install faiss-cpu` (depending on Python version)."
|
|
)
|
|
|
|
from mem0.vector_stores.base import VectorStoreBase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SafeUnpickler(pickle.Unpickler):
|
|
"""
|
|
Restricted unpickler that only allows safe built-in types.
|
|
|
|
This prevents arbitrary code execution via pickle deserialization by only
|
|
allowing a whitelist of safe types (dict, list, str, int, float, bool, tuple, None).
|
|
"""
|
|
|
|
# Only allow builtins module
|
|
SAFE_MODULES = frozenset({"builtins", "__builtin__"})
|
|
# Only allow safe basic types
|
|
SAFE_NAMES = frozenset({"dict", "list", "str", "int", "float", "bool", "tuple", "set", "frozenset", "NoneType"})
|
|
|
|
def find_class(self, module: str, name: str) -> Any:
|
|
"""Override find_class to only allow safe types."""
|
|
if module in self.SAFE_MODULES and name in self.SAFE_NAMES:
|
|
import builtins
|
|
|
|
if hasattr(builtins, name):
|
|
return getattr(builtins, name)
|
|
# NoneType special case
|
|
if name == "NoneType":
|
|
return type(None)
|
|
raise pickle.UnpicklingError(
|
|
f"Unsafe pickle: attempted to load '{module}.{name}'. "
|
|
f"Only basic Python types are allowed for security reasons."
|
|
)
|
|
|
|
|
|
def _safe_pickle_load(file_path: str) -> Any:
|
|
"""
|
|
Safely load a pickle file using restricted unpickler.
|
|
|
|
Args:
|
|
file_path: Path to the pickle file.
|
|
|
|
Returns:
|
|
The deserialized object (only basic Python types allowed).
|
|
|
|
Raises:
|
|
pickle.UnpicklingError: If the pickle contains unsafe types.
|
|
"""
|
|
with open(file_path, "rb") as f:
|
|
return SafeUnpickler(f).load()
|
|
|
|
|
|
def _validate_docstore_structure(data: Any) -> tuple:
|
|
"""
|
|
Validate that loaded data has the expected structure.
|
|
|
|
Args:
|
|
data: The loaded data to validate.
|
|
|
|
Returns:
|
|
Tuple of (docstore, index_to_id) if valid.
|
|
|
|
Raises:
|
|
ValueError: If the data structure is invalid.
|
|
"""
|
|
if not isinstance(data, tuple) or len(data) != 2:
|
|
raise ValueError("Invalid docstore format: expected tuple of (docstore, index_to_id)")
|
|
|
|
docstore, index_to_id = data
|
|
|
|
if not isinstance(docstore, dict):
|
|
raise ValueError("Invalid docstore format: docstore must be a dict")
|
|
|
|
if not isinstance(index_to_id, dict):
|
|
raise ValueError("Invalid docstore format: index_to_id must be a dict")
|
|
|
|
# Validate docstore entries
|
|
for key, value in docstore.items():
|
|
if not isinstance(key, str):
|
|
raise ValueError(f"Invalid docstore key type: {type(key)}, expected str")
|
|
if not isinstance(value, dict):
|
|
raise ValueError(f"Invalid docstore value type: {type(value)}, expected dict")
|
|
|
|
# Validate index_to_id entries
|
|
for key, value in index_to_id.items():
|
|
if not isinstance(key, int):
|
|
raise ValueError(f"Invalid index_to_id key type: {type(key)}, expected int")
|
|
if not isinstance(value, str):
|
|
raise ValueError(f"Invalid index_to_id value type: {type(value)}, expected str")
|
|
|
|
return docstore, index_to_id
|
|
|
|
|
|
class OutputData(BaseModel):
|
|
id: Optional[str] # memory id
|
|
score: Optional[float] # distance
|
|
payload: Optional[Dict] # metadata
|
|
|
|
|
|
class FAISS(VectorStoreBase):
|
|
def __init__(
|
|
self,
|
|
collection_name: str,
|
|
path: Optional[str] = None,
|
|
distance_strategy: str = "euclidean",
|
|
normalize_L2: bool = False,
|
|
embedding_model_dims: int = 1536,
|
|
):
|
|
"""
|
|
Initialize the FAISS vector store.
|
|
|
|
Args:
|
|
collection_name (str): Name of the collection.
|
|
path (str, optional): Path for local FAISS database. Defaults to None.
|
|
distance_strategy (str, optional): Distance strategy to use. Options: 'euclidean', 'inner_product', 'cosine'.
|
|
Defaults to "euclidean".
|
|
normalize_L2 (bool, optional): Whether to normalize L2 vectors. Only applicable for euclidean distance.
|
|
Defaults to False.
|
|
"""
|
|
self.collection_name = collection_name
|
|
self.path = path or f"/tmp/faiss/{collection_name}"
|
|
self.distance_strategy = distance_strategy
|
|
self.normalize_L2 = normalize_L2
|
|
self.embedding_model_dims = embedding_model_dims
|
|
|
|
# Initialize storage structures
|
|
self.index = None
|
|
self.docstore = {}
|
|
self.index_to_id = {}
|
|
|
|
# Create directory if it doesn't exist
|
|
if self.path:
|
|
os.makedirs(os.path.dirname(self.path), exist_ok=True)
|
|
|
|
# Try to load existing index if available
|
|
index_path = f"{self.path}/{collection_name}.faiss"
|
|
json_docstore_path = f"{self.path}/{collection_name}.json"
|
|
pkl_docstore_path = f"{self.path}/{collection_name}.pkl"
|
|
|
|
# Check for index file and either JSON (preferred) or legacy pickle docstore
|
|
if os.path.exists(index_path) and (os.path.exists(json_docstore_path) or os.path.exists(pkl_docstore_path)):
|
|
# _load will prefer JSON over pickle and auto-migrate
|
|
self._load(index_path, pkl_docstore_path)
|
|
else:
|
|
self.create_col(collection_name)
|
|
|
|
def _load(self, index_path: str, docstore_path: str):
|
|
"""
|
|
Load FAISS index and docstore from disk.
|
|
|
|
Supports both JSON (preferred) and legacy pickle formats. Pickle files are loaded
|
|
using a restricted unpickler that only allows basic Python types to prevent
|
|
arbitrary code execution (CVE mitigation).
|
|
|
|
Args:
|
|
index_path (str): Path to FAISS index file.
|
|
docstore_path (str): Path to docstore file (.json or legacy .pkl).
|
|
"""
|
|
try:
|
|
self.index = faiss.read_index(index_path)
|
|
|
|
# Determine docstore format - prefer JSON over pickle
|
|
json_docstore_path = docstore_path.replace(".pkl", ".json")
|
|
|
|
if os.path.exists(json_docstore_path):
|
|
# Load from JSON (safe, preferred format)
|
|
with open(json_docstore_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
self.docstore = data.get("docstore", {})
|
|
# JSON keys are always strings, convert back to int
|
|
self.index_to_id = {int(k): v for k, v in data.get("index_to_id", {}).items()}
|
|
logger.info(f"Loaded FAISS index from {index_path} with {self.index.ntotal} vectors (JSON format)")
|
|
|
|
elif os.path.exists(docstore_path):
|
|
# Load from legacy pickle using safe unpickler
|
|
# This prevents arbitrary code execution from malicious pickle files
|
|
logger.warning(
|
|
f"Loading legacy pickle docstore from {docstore_path}. "
|
|
f"Consider migrating to JSON format for better security."
|
|
)
|
|
data = _safe_pickle_load(docstore_path)
|
|
self.docstore, self.index_to_id = _validate_docstore_structure(data)
|
|
logger.info(f"Loaded FAISS index from {index_path} with {self.index.ntotal} vectors (pickle format)")
|
|
|
|
# Auto-migrate to JSON format
|
|
self._save()
|
|
logger.info(f"Migrated docstore to JSON format: {json_docstore_path}")
|
|
|
|
else:
|
|
raise FileNotFoundError(f"No docstore found at {docstore_path} or {json_docstore_path}")
|
|
|
|
except pickle.UnpicklingError as e:
|
|
logger.error(f"Security error loading FAISS docstore: {e}")
|
|
raise ValueError(f"Failed to load FAISS docstore: potentially malicious pickle file. {e}") from e
|
|
except Exception as e:
|
|
logger.warning(f"Failed to load FAISS index: {e}")
|
|
self.docstore = {}
|
|
self.index_to_id = {}
|
|
|
|
def _save(self):
|
|
"""Save FAISS index and docstore to disk using JSON format (secure)."""
|
|
if not self.path or not self.index:
|
|
return
|
|
|
|
try:
|
|
os.makedirs(self.path, exist_ok=True)
|
|
index_path = f"{self.path}/{self.collection_name}.faiss"
|
|
json_docstore_path = f"{self.path}/{self.collection_name}.json"
|
|
|
|
faiss.write_index(self.index, index_path)
|
|
|
|
# Save docstore as JSON (safe format, no code execution risk)
|
|
# JSON keys must be strings, so convert int keys to str
|
|
data = {
|
|
"docstore": self.docstore,
|
|
"index_to_id": {str(k): v for k, v in self.index_to_id.items()},
|
|
}
|
|
with open(json_docstore_path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Failed to save FAISS index: {e}")
|
|
|
|
def _should_normalize(self) -> bool:
|
|
"""Whether vectors must be L2-normalized before indexing/searching.
|
|
|
|
Cosine similarity is implemented on top of an inner-product index
|
|
(``IndexFlatIP``), which only equals cosine when the inputs are unit
|
|
vectors — so cosine *always* requires normalization. For euclidean the
|
|
``normalize_L2`` flag remains an opt-in.
|
|
"""
|
|
strategy = self.distance_strategy.lower()
|
|
if strategy == "cosine":
|
|
return True
|
|
return self.normalize_L2 and strategy == "euclidean"
|
|
|
|
def _parse_output(self, scores, ids, top_k=None) -> List[OutputData]:
|
|
"""
|
|
Parse the output data.
|
|
|
|
Args:
|
|
scores: Similarity scores from FAISS.
|
|
ids: Indices from FAISS.
|
|
top_k: Maximum number of results to return.
|
|
|
|
Returns:
|
|
List[OutputData]: Parsed output data.
|
|
"""
|
|
if top_k is None:
|
|
top_k = len(ids)
|
|
|
|
results = []
|
|
for i in range(min(len(ids), top_k)):
|
|
if ids[i] == -1: # FAISS returns -1 for empty results
|
|
continue
|
|
|
|
index_id = int(ids[i])
|
|
vector_id = self.index_to_id.get(index_id)
|
|
if vector_id is None:
|
|
continue
|
|
|
|
payload = self.docstore.get(vector_id)
|
|
if payload is None:
|
|
continue
|
|
|
|
payload_copy = payload.copy()
|
|
|
|
raw_score = float(scores[i])
|
|
if self.distance_strategy.lower() == "euclidean":
|
|
score = 1.0 / (1.0 + raw_score)
|
|
else:
|
|
score = raw_score
|
|
entry = OutputData(
|
|
id=vector_id,
|
|
score=score,
|
|
payload=payload_copy,
|
|
)
|
|
results.append(entry)
|
|
|
|
return results
|
|
|
|
def create_col(self, name: str, distance: str = None):
|
|
"""
|
|
Create a new collection.
|
|
|
|
Args:
|
|
name (str): Name of the collection.
|
|
distance (str, optional): Distance metric to use. Overrides the distance_strategy
|
|
passed during initialization. Defaults to None.
|
|
|
|
Returns:
|
|
self: The FAISS instance.
|
|
"""
|
|
distance_strategy = distance or self.distance_strategy
|
|
|
|
# Create index based on distance strategy
|
|
if distance_strategy.lower() == "inner_product" or distance_strategy.lower() == "cosine":
|
|
self.index = faiss.IndexFlatIP(self.embedding_model_dims)
|
|
else:
|
|
self.index = faiss.IndexFlatL2(self.embedding_model_dims)
|
|
|
|
self.collection_name = name
|
|
|
|
self._save()
|
|
|
|
return self
|
|
|
|
def insert(
|
|
self,
|
|
vectors: List[list],
|
|
payloads: Optional[List[Dict]] = None,
|
|
ids: Optional[List[str]] = None,
|
|
):
|
|
"""
|
|
Insert vectors into a collection.
|
|
|
|
Args:
|
|
vectors (List[list]): List of vectors to insert.
|
|
payloads (Optional[List[Dict]], optional): List of payloads corresponding to vectors. Defaults to None.
|
|
ids (Optional[List[str]], optional): List of IDs corresponding to vectors. Defaults to None.
|
|
"""
|
|
if self.index is None:
|
|
raise ValueError("Collection not initialized. Call create_col first.")
|
|
|
|
if ids is None:
|
|
ids = [str(uuid.uuid4()) for _ in range(len(vectors))]
|
|
|
|
if payloads is None:
|
|
payloads = [{} for _ in range(len(vectors))]
|
|
|
|
if len(vectors) != len(ids) or len(vectors) != len(payloads):
|
|
raise ValueError("Vectors, payloads, and IDs must have the same length")
|
|
|
|
vectors_np = np.array(vectors, dtype=np.float32)
|
|
|
|
if self._should_normalize():
|
|
faiss.normalize_L2(vectors_np)
|
|
|
|
self.index.add(vectors_np)
|
|
|
|
starting_idx = len(self.index_to_id)
|
|
for i, (vector_id, payload) in enumerate(zip(ids, payloads)):
|
|
self.docstore[vector_id] = payload.copy()
|
|
self.index_to_id[starting_idx + i] = vector_id
|
|
|
|
self._save()
|
|
|
|
logger.info(f"Inserted {len(vectors)} vectors into collection {self.collection_name}")
|
|
|
|
def search(
|
|
self, query: str, vectors: List[list], top_k: int = 5, filters: Optional[Dict] = None
|
|
) -> List[OutputData]:
|
|
"""
|
|
Search for similar vectors.
|
|
|
|
Args:
|
|
query (str): Query (not used, kept for API compatibility).
|
|
vectors (List[list]): List of vectors to search.
|
|
top_k (int, optional): Number of results to return. Defaults to 5.
|
|
filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None.
|
|
|
|
Returns:
|
|
List[OutputData]: Search results.
|
|
"""
|
|
if self.index is None:
|
|
raise ValueError("Collection not initialized. Call create_col first.")
|
|
|
|
query_vectors = np.array(vectors, dtype=np.float32)
|
|
|
|
if len(query_vectors.shape) == 1:
|
|
query_vectors = query_vectors.reshape(1, -1)
|
|
|
|
if self._should_normalize():
|
|
faiss.normalize_L2(query_vectors)
|
|
|
|
fetch_k = top_k * 2 if filters else top_k
|
|
scores, indices = self.index.search(query_vectors, fetch_k)
|
|
|
|
results = self._parse_output(scores[0], indices[0], fetch_k)
|
|
|
|
if filters:
|
|
filtered_results = []
|
|
for result in results:
|
|
if self._apply_filters(result.payload, filters):
|
|
filtered_results.append(result)
|
|
if len(filtered_results) >= top_k:
|
|
break
|
|
results = filtered_results[:top_k]
|
|
|
|
return results
|
|
|
|
def _apply_filters(self, payload: Dict, filters: Dict) -> bool:
|
|
"""
|
|
Apply filters to a payload.
|
|
|
|
Args:
|
|
payload (Dict): Payload to filter.
|
|
filters (Dict): Filters to apply.
|
|
|
|
Returns:
|
|
bool: True if payload passes filters, False otherwise.
|
|
"""
|
|
if not filters or not payload:
|
|
return True
|
|
|
|
for key, value in filters.items():
|
|
if key not in payload:
|
|
return False
|
|
|
|
if isinstance(value, list):
|
|
if payload[key] not in value:
|
|
return False
|
|
elif payload[key] != value:
|
|
return False
|
|
|
|
return True
|
|
|
|
def delete(self, vector_id: str):
|
|
"""
|
|
Delete a vector by ID.
|
|
|
|
Args:
|
|
vector_id (str): ID of the vector to delete.
|
|
"""
|
|
if self.index is None:
|
|
raise ValueError("Collection not initialized. Call create_col first.")
|
|
|
|
index_to_delete = None
|
|
for idx, vid in self.index_to_id.items():
|
|
if vid == vector_id:
|
|
index_to_delete = idx
|
|
break
|
|
|
|
if index_to_delete is not None:
|
|
# Reconstruct remaining vectors and rebuild the FAISS index
|
|
remaining_vectors = []
|
|
new_index_to_id = {}
|
|
new_idx = 0
|
|
for old_idx in sorted(self.index_to_id.keys()):
|
|
if old_idx == index_to_delete:
|
|
continue
|
|
remaining_vectors.append(self.index.reconstruct(int(old_idx)))
|
|
new_index_to_id[new_idx] = self.index_to_id[old_idx]
|
|
new_idx += 1
|
|
|
|
self.index.reset()
|
|
if remaining_vectors:
|
|
self.index.add(np.array(remaining_vectors, dtype=np.float32))
|
|
|
|
self.docstore.pop(vector_id, None)
|
|
self.index_to_id = new_index_to_id
|
|
|
|
self._save()
|
|
|
|
logger.info(f"Deleted vector {vector_id} from collection {self.collection_name}")
|
|
else:
|
|
logger.warning(f"Vector {vector_id} not found in collection {self.collection_name}")
|
|
|
|
def update(
|
|
self,
|
|
vector_id: str,
|
|
vector: Optional[List[float]] = None,
|
|
payload: Optional[Dict] = None,
|
|
):
|
|
"""
|
|
Update a vector and its payload.
|
|
|
|
Args:
|
|
vector_id (str): ID of the vector to update.
|
|
vector (Optional[List[float]], optional): Updated vector. Defaults to None.
|
|
payload (Optional[Dict], optional): Updated payload. Defaults to None.
|
|
"""
|
|
if self.index is None:
|
|
raise ValueError("Collection not initialized. Call create_col first.")
|
|
|
|
if vector_id not in self.docstore:
|
|
raise ValueError(f"Vector {vector_id} not found")
|
|
|
|
current_payload = self.docstore[vector_id].copy()
|
|
|
|
if payload is not None:
|
|
self.docstore[vector_id] = payload.copy()
|
|
current_payload = self.docstore[vector_id].copy()
|
|
|
|
if vector is not None:
|
|
self.delete(vector_id)
|
|
self.insert([vector], [current_payload], [vector_id])
|
|
else:
|
|
self._save()
|
|
|
|
logger.info(f"Updated vector {vector_id} in collection {self.collection_name}")
|
|
|
|
def get(self, vector_id: str) -> OutputData:
|
|
"""
|
|
Retrieve a vector by ID.
|
|
|
|
Args:
|
|
vector_id (str): ID of the vector to retrieve.
|
|
|
|
Returns:
|
|
OutputData: Retrieved vector.
|
|
"""
|
|
if self.index is None:
|
|
raise ValueError("Collection not initialized. Call create_col first.")
|
|
|
|
if vector_id not in self.docstore:
|
|
return None
|
|
|
|
payload = self.docstore[vector_id].copy()
|
|
|
|
return OutputData(
|
|
id=vector_id,
|
|
score=None,
|
|
payload=payload,
|
|
)
|
|
|
|
def list_cols(self) -> List[str]:
|
|
"""
|
|
List all collections.
|
|
|
|
Returns:
|
|
List[str]: List of collection names.
|
|
"""
|
|
if not self.path:
|
|
return [self.collection_name] if self.index else []
|
|
|
|
try:
|
|
collections = []
|
|
path = Path(self.path).parent
|
|
for file in path.glob("*.faiss"):
|
|
collections.append(file.stem)
|
|
return collections
|
|
except Exception as e:
|
|
logger.warning(f"Failed to list collections: {e}")
|
|
return [self.collection_name] if self.index else []
|
|
|
|
def delete_col(self):
|
|
"""
|
|
Delete a collection.
|
|
"""
|
|
if self.path:
|
|
try:
|
|
index_path = f"{self.path}/{self.collection_name}.faiss"
|
|
json_docstore_path = f"{self.path}/{self.collection_name}.json"
|
|
pkl_docstore_path = f"{self.path}/{self.collection_name}.pkl"
|
|
|
|
if os.path.exists(index_path):
|
|
os.remove(index_path)
|
|
if os.path.exists(json_docstore_path):
|
|
os.remove(json_docstore_path)
|
|
# Also clean up legacy pickle files if they exist
|
|
if os.path.exists(pkl_docstore_path):
|
|
os.remove(pkl_docstore_path)
|
|
|
|
logger.info(f"Deleted collection {self.collection_name}")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to delete collection: {e}")
|
|
|
|
self.index = None
|
|
self.docstore = {}
|
|
self.index_to_id = {}
|
|
|
|
def col_info(self) -> Dict:
|
|
"""
|
|
Get information about a collection.
|
|
|
|
Returns:
|
|
Dict: Collection information.
|
|
"""
|
|
if self.index is None:
|
|
return {"name": self.collection_name, "count": 0}
|
|
|
|
return {
|
|
"name": self.collection_name,
|
|
"count": self.index.ntotal,
|
|
"dimension": self.index.d,
|
|
"distance": self.distance_strategy,
|
|
}
|
|
|
|
def list(self, filters: Optional[Dict] = None, top_k: int = 100) -> List[OutputData]:
|
|
"""
|
|
List all vectors in a collection.
|
|
|
|
Args:
|
|
filters (Optional[Dict], optional): Filters to apply to the list. Defaults to None.
|
|
top_k (int, optional): Number of vectors to return. Defaults to 100.
|
|
|
|
Returns:
|
|
List[OutputData]: List of vectors.
|
|
"""
|
|
if self.index is None:
|
|
return [[]]
|
|
|
|
results = []
|
|
count = 0
|
|
|
|
for vector_id, payload in self.docstore.items():
|
|
if filters and not self._apply_filters(payload, filters):
|
|
continue
|
|
|
|
payload_copy = payload.copy()
|
|
|
|
results.append(
|
|
OutputData(
|
|
id=vector_id,
|
|
score=None,
|
|
payload=payload_copy,
|
|
)
|
|
)
|
|
|
|
count += 1
|
|
if count >= top_k:
|
|
break
|
|
|
|
return [results]
|
|
|
|
def reset(self):
|
|
"""Reset the index by deleting and recreating it."""
|
|
logger.warning(f"Resetting index {self.collection_name}...")
|
|
self.delete_col()
|
|
self.create_col(self.collection_name)
|