qdrant_client.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """
  2. Qdrant 向量数据库客户端
  3. 用于存储和检索文档切片的向量嵌入
  4. """
  5. from qdrant_client import QdrantClient
  6. from qdrant_client.models import Distance, VectorParams, PointStruct, Filter, FieldCondition, MatchValue
  7. from typing import List, Dict, Optional
  8. import os
  9. import uuid
  10. class QdrantManager:
  11. def __init__(self):
  12. # 从环境变量读取 Qdrant 配置
  13. self.host = os.getenv("QDRANT_HOST", "localhost")
  14. self.port = int(os.getenv("QDRANT_PORT", "6333"))
  15. self.collection_name = os.getenv("QDRANT_COLLECTION", "kb_vectors")
  16. self.vector_size = 1536 # doubao-embedding-vision-251215 的向量维度
  17. # 初始化客户端
  18. self.client = QdrantClient(host=self.host, port=self.port)
  19. # 确保集合存在
  20. self._ensure_collection()
  21. def _ensure_collection(self):
  22. """确保 Qdrant 集合存在,不存在则创建"""
  23. collections = self.client.get_collections().collections
  24. collection_names = [col.name for col in collections]
  25. if self.collection_name not in collection_names:
  26. self.client.create_collection(
  27. collection_name=self.collection_name,
  28. vectors_config=VectorParams(
  29. size=self.vector_size,
  30. distance=Distance.COSINE
  31. )
  32. )
  33. print(f"创建 Qdrant 集合: {self.collection_name}")
  34. def add_vectors(self, tenant_id: str, doc_id: str, chunks: List[Dict]) -> List[str]:
  35. """
  36. 添加文档切片的向量到 Qdrant
  37. :param tenant_id: 租户ID
  38. :param doc_id: 文档ID
  39. :param chunks: 切片列表 [{"content": "...", "vector": [...]}, ...]
  40. :return: vector_id 列表
  41. """
  42. points = []
  43. vector_ids = []
  44. for idx, chunk in enumerate(chunks):
  45. vector_id = str(uuid.uuid4())
  46. vector_ids.append(vector_id)
  47. point = PointStruct(
  48. id=vector_id,
  49. vector=chunk["vector"],
  50. payload={
  51. "tenant_id": tenant_id,
  52. "doc_id": doc_id,
  53. "chunk_index": idx,
  54. "content": chunk["content"][:500], # 存储前500字符用于预览
  55. }
  56. )
  57. points.append(point)
  58. # 批量插入
  59. self.client.upsert(
  60. collection_name=self.collection_name,
  61. points=points
  62. )
  63. return vector_ids
  64. def search_vectors(self, tenant_id: str, query_vector: List[float], top_k: int = 5) -> List[Dict]:
  65. """
  66. 向量相似度搜索
  67. :param tenant_id: 租户ID(只搜索该租户的数据)
  68. :param query_vector: 查询向量
  69. :param top_k: 返回Top K个结果
  70. :return: 搜索结果列表
  71. """
  72. search_result = self.client.search(
  73. collection_name=self.collection_name,
  74. query_vector=query_vector,
  75. query_filter=Filter(
  76. must=[
  77. FieldCondition(
  78. key="tenant_id",
  79. match=MatchValue(value=tenant_id)
  80. )
  81. ]
  82. ),
  83. limit=top_k
  84. )
  85. results = []
  86. for hit in search_result:
  87. results.append({
  88. "vector_id": hit.id,
  89. "score": hit.score,
  90. "content": hit.payload.get("content", ""),
  91. "doc_id": hit.payload.get("doc_id", ""),
  92. "chunk_index": hit.payload.get("chunk_index", 0),
  93. })
  94. return results
  95. def delete_vectors_by_doc(self, doc_id: str):
  96. """
  97. 删除文档的所有向量
  98. :param doc_id: 文档ID
  99. """
  100. self.client.delete(
  101. collection_name=self.collection_name,
  102. points_selector=Filter(
  103. must=[
  104. FieldCondition(
  105. key="doc_id",
  106. match=MatchValue(value=doc_id)
  107. )
  108. ]
  109. )
  110. )
  111. # 全局单例
  112. _qdrant_manager = None
  113. def get_qdrant_manager() -> QdrantManager:
  114. """获取 Qdrant 管理器单例"""
  115. global _qdrant_manager
  116. if _qdrant_manager is None:
  117. _qdrant_manager = QdrantManager()
  118. return _qdrant_manager