Source code for document_stores.implementations.memory_vector_store

"""
内存向量存储实现

基于Haystack InMemoryDocumentStore的向量存储适配器,
提供与现有系统的向后兼容性。
"""

from dataclasses import replace
from typing import Any, Dict, List, Literal, Optional, Tuple

import numpy as np
from haystack import Document as HaystackDocument
from haystack.document_stores.in_memory import InMemoryDocumentStore

from .base_vector_store import BaseVectorStore


[docs] class InMemoryVectorStore(BaseVectorStore): """ 基于Haystack InMemoryDocumentStore的内存向量存储。 提供与现有系统的完全兼容性,适用于开发、测试和小规模场景。 """
[docs] def __init__(self, config: Dict[str, Any]) -> None: """ 初始化内存向量存储。 Args: config: 存储配置字典 """ super().__init__(config) # Haystack配置映射 similarity_map: Dict[str, Literal["dot_product", "cosine"]] = { "dot_product": "dot_product", "cosine": "cosine", "euclidean": "dot_product", # InMemoryDocumentStore不直接支持欧氏距离 } haystack_similarity = similarity_map.get(self.similarity_function, "dot_product") # 创建Haystack文档存储 self.store = InMemoryDocumentStore(embedding_similarity_function=haystack_similarity, return_embedding=True) self.logger.info("InMemoryVectorStore初始化完成")
[docs] def initialize(self) -> bool: """ 初始化内存存储(无需特殊初始化)。 Returns: 总是返回True """ self.is_initialized = True self.logger.info("内存向量存储初始化成功") return True
[docs] def add_documents(self, documents: List[HaystackDocument], embeddings: Optional[List[List[float]]] = None) -> bool: """ 添加文档到内存存储。 Args: documents: Haystack文档列表 embeddings: 对应的向量嵌入列表 Returns: 添加成功返回True,失败返回False """ if not self.is_initialized: self.logger.error("存储未初始化") return False if not documents: return True try: # 处理向量嵌入 docs_to_add = [] for i, doc in enumerate(documents): # 复制文档以避免修改原始对象 new_doc = HaystackDocument(content=doc.content, meta=doc.meta.copy() if doc.meta else {}, id=doc.id) embedding = embeddings[i] if embeddings and i < len(embeddings) else doc.embedding if not embedding: self.logger.warning(f"文档 {doc.id} 缺少向量嵌入,跳过") continue if not self.validate_embedding(embedding): self.logger.warning(f"文档 {doc.id} 的向量嵌入无效,跳过") continue new_doc = replace(new_doc, embedding=embedding) docs_to_add.append(new_doc) if docs_to_add: self.store.write_documents(docs_to_add) self.logger.info(f"成功添加 {len(docs_to_add)} 个文档到内存存储") return True return False except Exception as e: self.logger.error(f"添加文档失败: {str(e)}") return False
[docs] def search( self, query_embedding: List[float], top_k: int = 5, filters: Optional[Dict[str, Any]] = None ) -> List[Tuple[HaystackDocument, float]]: """ 在内存存储中进行向量搜索。 Args: query_embedding: 查询向量 top_k: 返回的最相似文档数量 filters: 元数据过滤条件 Returns: 包含(文档, 相似度得分)的元组列表 """ if not self.is_initialized: self.logger.error("存储未初始化") return [] if not self.validate_embedding(query_embedding): self.logger.error("查询向量无效") return [] try: # Haystack过滤器格式转换 haystack_filters = None if filters: conditions = [ {"field": f"meta.{key}", "operator": "in" if isinstance(value, list) else "==", "value": value} for key, value in filters.items() ] haystack_filters = {"operator": "AND", "conditions": conditions} # 执行搜索 results = self.store.embedding_retrieval( query_embedding=query_embedding, top_k=top_k, filters=haystack_filters ) # 格式化结果 search_results = [] for doc in results: # 计算相似度得分(Haystack可能没有直接提供得分) if hasattr(doc, "score") and doc.score is not None: score = float(doc.score) else: # 手动计算相似度 if doc.embedding: score = self._calculate_similarity(query_embedding, doc.embedding) else: score = 0.0 search_results.append((doc, score)) # 按得分排序 search_results.sort(key=lambda x: x[1], reverse=True) return search_results[:top_k] except Exception as e: self.logger.error(f"搜索失败: {str(e)}") return []
[docs] def get_document_by_id(self, doc_id: str) -> Optional[HaystackDocument]: """ 根据文档ID获取文档。 Args: doc_id: 文档ID Returns: 文档对象,不存在则返回None """ try: docs = self.store.filter_documents({"field": "id", "operator": "in", "value": [doc_id]}) return docs[0] if docs else None except Exception as e: self.logger.error(f"获取文档失败: {str(e)}") return None
[docs] def update_document(self, doc_id: str, document: HaystackDocument, embedding: Optional[List[float]] = None) -> bool: """ 更新文档。 Args: doc_id: 文档ID document: 新的文档对象 embedding: 新的向量嵌入 Returns: 更新成功返回True,失败返回False """ try: existing = self.get_document_by_id(doc_id) if existing is None: return False selected_embedding = embedding if embedding is not None else document.embedding if selected_embedding is None: selected_embedding = existing.embedding if selected_embedding is None or not self.validate_embedding(selected_embedding): return False replacement = replace(document, id=doc_id, embedding=selected_embedding) from haystack.document_stores.types import DuplicatePolicy self.store.write_documents([replacement], policy=DuplicatePolicy.OVERWRITE) return True except Exception as e: self.logger.error(f"更新文档失败: {str(e)}") return False
[docs] def delete_document(self, doc_id: str) -> bool: """ 删除文档。 Args: doc_id: 文档ID Returns: 删除成功返回True,失败返回False """ try: self.store.delete_documents([doc_id]) self.logger.info(f"文档 {doc_id} 已删除") return True except Exception as e: self.logger.error(f"删除文档失败: {str(e)}") return False
[docs] def delete_all_documents(self) -> bool: """ 删除所有文档。 Returns: 删除成功返回True,失败返回False """ try: self.store.delete_documents([doc.id for doc in self.store.filter_documents({})]) self.logger.info("所有文档已清空") return True except Exception as e: self.logger.error(f"清空文档失败: {str(e)}") return False
[docs] def get_document_count(self) -> int: """ 获取文档总数。 Returns: 文档总数 """ try: return self.store.count_documents() except Exception as e: self.logger.error(f"获取文档数量失败: {str(e)}") return 0
[docs] def save_index(self, path: str) -> bool: """ 保存索引(内存存储不支持持久化)。 Args: path: 保存路径 Returns: 总是返回False(不支持持久化) """ self.logger.warning("InMemoryVectorStore不支持持久化存储") return False
[docs] def load_index(self, path: str) -> bool: """ 加载索引(内存存储不支持持久化)。 Args: path: 索引文件路径 Returns: 总是返回False(不支持持久化) """ self.logger.warning("InMemoryVectorStore不支持从文件加载") return False
[docs] def get_haystack_store(self) -> InMemoryDocumentStore: """ 获取底层Haystack存储对象(用于兼容性)。 Returns: InMemoryDocumentStore对象 """ return self.store
def _calculate_similarity(self, vec1: List[float], vec2: List[float]) -> float: """ 计算两个向量的相似度。 Args: vec1: 第一个向量 vec2: 第二个向量 Returns: 相似度得分 """ try: v1 = np.array(vec1) v2 = np.array(vec2) if self.similarity_function == "cosine": # 余弦相似度 norm1 = np.linalg.norm(v1) norm2 = np.linalg.norm(v2) if norm1 == 0 or norm2 == 0: return 0.0 return float(np.dot(v1, v2) / (norm1 * norm2)) elif self.similarity_function == "euclidean": # 欧氏距离转相似度 distance = np.linalg.norm(v1 - v2) return float(1.0 / (1.0 + distance)) else: # dot_product return float(np.dot(v1, v2)) except Exception as e: self.logger.error(f"计算相似度失败: {str(e)}") return 0.0