isage-middleware 0.1.0__py3-none-any.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.

Potentially problematic release.


This version of isage-middleware might be problematic. Click here for more details.

Files changed (191) hide show
  1. isage_middleware-0.1.0.dist-info/METADATA +424 -0
  2. isage_middleware-0.1.0.dist-info/RECORD +191 -0
  3. isage_middleware-0.1.0.dist-info/WHEEL +5 -0
  4. isage_middleware-0.1.0.dist-info/top_level.txt +1 -0
  5. sage/__init__.py +2 -0
  6. sage/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  7. sage/__pycache__/__init__.cpython-311.pyc +0 -0
  8. sage/middleware/__init__.py +83 -0
  9. sage/middleware/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  10. sage/middleware/__pycache__/__init__.cpython-311.pyc +0 -0
  11. sage/middleware/api/__init__.py +22 -0
  12. sage/middleware/api/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  13. sage/middleware/api/__pycache__/__init__.cpython-311.pyc +0 -0
  14. sage/middleware/api/__pycache__/graph_api.cpython-311.opt-2.pyc +0 -0
  15. sage/middleware/api/__pycache__/graph_api.cpython-311.pyc +0 -0
  16. sage/middleware/api/__pycache__/kv_api.cpython-311.opt-2.pyc +0 -0
  17. sage/middleware/api/__pycache__/kv_api.cpython-311.pyc +0 -0
  18. sage/middleware/api/__pycache__/memory_api.cpython-311.opt-2.pyc +0 -0
  19. sage/middleware/api/__pycache__/memory_api.cpython-311.pyc +0 -0
  20. sage/middleware/api/__pycache__/vdb_api.cpython-311.opt-2.pyc +0 -0
  21. sage/middleware/api/__pycache__/vdb_api.cpython-311.pyc +0 -0
  22. sage/middleware/api/graph_api.py +74 -0
  23. sage/middleware/api/kv_api.py +45 -0
  24. sage/middleware/api/memory_api.py +64 -0
  25. sage/middleware/api/vdb_api.py +60 -0
  26. sage/middleware/enterprise/__init__.py +75 -0
  27. sage/middleware/enterprise/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  28. sage/middleware/enterprise/__pycache__/__init__.cpython-311.pyc +0 -0
  29. sage/middleware/enterprise/sage_db/__init__.py +132 -0
  30. sage/middleware/enterprise/sage_db/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  31. sage/middleware/enterprise/sage_db/__pycache__/__init__.cpython-311.pyc +0 -0
  32. sage/middleware/enterprise/sage_db/__pycache__/sage_db.cpython-311.opt-2.pyc +0 -0
  33. sage/middleware/enterprise/sage_db/__pycache__/sage_db.cpython-311.pyc +0 -0
  34. sage/middleware/enterprise/sage_db/python/__init__.py +7 -0
  35. sage/middleware/enterprise/sage_db/python/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  36. sage/middleware/enterprise/sage_db/python/__pycache__/__init__.cpython-311.pyc +0 -0
  37. sage/middleware/enterprise/sage_db/python/__pycache__/sage_db.cpython-311.opt-2.pyc +0 -0
  38. sage/middleware/enterprise/sage_db/python/__pycache__/sage_db.cpython-311.pyc +0 -0
  39. sage/middleware/enterprise/sage_db/python/sage_db.py +44 -0
  40. sage/middleware/enterprise/sage_db/sage_db.py +395 -0
  41. sage/middleware/enterprise/sage_db/tests/__pycache__/test_python.cpython-311.opt-2.pyc +0 -0
  42. sage/middleware/enterprise/sage_db/tests/__pycache__/test_python.cpython-311.pyc +0 -0
  43. sage/middleware/enterprise/sage_db/tests/test_python.py +144 -0
  44. sage/middleware/examples/__pycache__/api_usage_tutorial.cpython-311.opt-2.pyc +0 -0
  45. sage/middleware/examples/__pycache__/api_usage_tutorial.cpython-311.pyc +0 -0
  46. sage/middleware/examples/__pycache__/dag_microservices_demo.cpython-311.opt-2.pyc +0 -0
  47. sage/middleware/examples/__pycache__/dag_microservices_demo.cpython-311.pyc +0 -0
  48. sage/middleware/examples/__pycache__/microservices_demo.cpython-311.opt-2.pyc +0 -0
  49. sage/middleware/examples/__pycache__/microservices_demo.cpython-311.pyc +0 -0
  50. sage/middleware/examples/__pycache__/microservices_integration_demo.cpython-311.opt-2.pyc +0 -0
  51. sage/middleware/examples/__pycache__/microservices_integration_demo.cpython-311.pyc +0 -0
  52. sage/middleware/examples/__pycache__/microservices_registration_demo.cpython-311.opt-2.pyc +0 -0
  53. sage/middleware/examples/__pycache__/microservices_registration_demo.cpython-311.pyc +0 -0
  54. sage/middleware/examples/api_usage_tutorial.py +339 -0
  55. sage/middleware/examples/dag_microservices_demo.py +220 -0
  56. sage/middleware/examples/microservices_demo.py +0 -0
  57. sage/middleware/examples/microservices_integration_demo.py +373 -0
  58. sage/middleware/examples/microservices_registration_demo.py +144 -0
  59. sage/middleware/py.typed +2 -0
  60. sage/middleware/services/graph/__init__.py +8 -0
  61. sage/middleware/services/graph/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  62. sage/middleware/services/graph/__pycache__/__init__.cpython-311.pyc +0 -0
  63. sage/middleware/services/graph/__pycache__/graph_index.cpython-311.opt-2.pyc +0 -0
  64. sage/middleware/services/graph/__pycache__/graph_index.cpython-311.pyc +0 -0
  65. sage/middleware/services/graph/__pycache__/graph_service.cpython-311.opt-2.pyc +0 -0
  66. sage/middleware/services/graph/__pycache__/graph_service.cpython-311.pyc +0 -0
  67. sage/middleware/services/graph/examples/__pycache__/graph_demo.cpython-311.opt-2.pyc +0 -0
  68. sage/middleware/services/graph/examples/__pycache__/graph_demo.cpython-311.pyc +0 -0
  69. sage/middleware/services/graph/examples/graph_demo.py +177 -0
  70. sage/middleware/services/graph/graph_index.py +194 -0
  71. sage/middleware/services/graph/graph_service.py +541 -0
  72. sage/middleware/services/graph/search_engine/__init__.py +0 -0
  73. sage/middleware/services/graph/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  74. sage/middleware/services/graph/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  75. sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.opt-2.pyc +0 -0
  76. sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.pyc +0 -0
  77. sage/middleware/services/graph/search_engine/base_graph_index.py +0 -0
  78. sage/middleware/services/kv/__init__.py +8 -0
  79. sage/middleware/services/kv/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  80. sage/middleware/services/kv/__pycache__/__init__.cpython-311.pyc +0 -0
  81. sage/middleware/services/kv/__pycache__/kv_service.cpython-311.opt-2.pyc +0 -0
  82. sage/middleware/services/kv/__pycache__/kv_service.cpython-311.pyc +0 -0
  83. sage/middleware/services/kv/examples/__pycache__/kv_demo.cpython-311.opt-2.pyc +0 -0
  84. sage/middleware/services/kv/examples/__pycache__/kv_demo.cpython-311.pyc +0 -0
  85. sage/middleware/services/kv/examples/kv_demo.py +213 -0
  86. sage/middleware/services/kv/kv_service.py +306 -0
  87. sage/middleware/services/kv/search_engine/__init__.py +0 -0
  88. sage/middleware/services/kv/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  89. sage/middleware/services/kv/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  90. sage/middleware/services/kv/search_engine/__pycache__/base_kv_index.cpython-311.opt-2.pyc +0 -0
  91. sage/middleware/services/kv/search_engine/__pycache__/base_kv_index.cpython-311.pyc +0 -0
  92. sage/middleware/services/kv/search_engine/__pycache__/bm25s_index.cpython-311.opt-2.pyc +0 -0
  93. sage/middleware/services/kv/search_engine/__pycache__/bm25s_index.cpython-311.pyc +0 -0
  94. sage/middleware/services/kv/search_engine/base_kv_index.py +75 -0
  95. sage/middleware/services/kv/search_engine/bm25s_index.py +238 -0
  96. sage/middleware/services/memory/__init__.py +12 -0
  97. sage/middleware/services/memory/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  98. sage/middleware/services/memory/__pycache__/__init__.cpython-311.pyc +0 -0
  99. sage/middleware/services/memory/__pycache__/memory_service.cpython-311.opt-2.pyc +0 -0
  100. sage/middleware/services/memory/__pycache__/memory_service.cpython-311.pyc +0 -0
  101. sage/middleware/services/memory/examples/__pycache__/dag_microservices_demo.cpython-311.opt-2.pyc +0 -0
  102. sage/middleware/services/memory/examples/__pycache__/dag_microservices_demo.cpython-311.pyc +0 -0
  103. sage/middleware/services/memory/examples/__pycache__/memory_demo.cpython-311.opt-2.pyc +0 -0
  104. sage/middleware/services/memory/examples/__pycache__/memory_demo.cpython-311.pyc +0 -0
  105. sage/middleware/services/memory/examples/dag_microservices_demo.py +220 -0
  106. sage/middleware/services/memory/examples/memory_demo.py +490 -0
  107. sage/middleware/services/memory/memory_collection/__pycache__/base_collection.cpython-311.opt-2.pyc +0 -0
  108. sage/middleware/services/memory/memory_collection/__pycache__/base_collection.cpython-311.pyc +0 -0
  109. sage/middleware/services/memory/memory_collection/__pycache__/graph_collection.cpython-311.opt-2.pyc +0 -0
  110. sage/middleware/services/memory/memory_collection/__pycache__/graph_collection.cpython-311.pyc +0 -0
  111. sage/middleware/services/memory/memory_collection/__pycache__/kv_collection.cpython-311.opt-2.pyc +0 -0
  112. sage/middleware/services/memory/memory_collection/__pycache__/kv_collection.cpython-311.pyc +0 -0
  113. sage/middleware/services/memory/memory_collection/__pycache__/vdb_collection.cpython-311.opt-2.pyc +0 -0
  114. sage/middleware/services/memory/memory_collection/__pycache__/vdb_collection.cpython-311.pyc +0 -0
  115. sage/middleware/services/memory/memory_collection/base_collection.py +0 -0
  116. sage/middleware/services/memory/memory_collection/graph_collection.py +0 -0
  117. sage/middleware/services/memory/memory_collection/kv_collection.py +0 -0
  118. sage/middleware/services/memory/memory_collection/vdb_collection.py +0 -0
  119. sage/middleware/services/memory/memory_service.py +474 -0
  120. sage/middleware/services/memory/utils/__init__.py +0 -0
  121. sage/middleware/services/memory/utils/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  122. sage/middleware/services/memory/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  123. sage/middleware/services/memory/utils/__pycache__/path_utils.cpython-311.opt-2.pyc +0 -0
  124. sage/middleware/services/memory/utils/__pycache__/path_utils.cpython-311.pyc +0 -0
  125. sage/middleware/services/memory/utils/path_utils.py +0 -0
  126. sage/middleware/services/vdb/__init__.py +8 -0
  127. sage/middleware/services/vdb/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  128. sage/middleware/services/vdb/__pycache__/__init__.cpython-311.pyc +0 -0
  129. sage/middleware/services/vdb/__pycache__/vdb_service.cpython-311.opt-2.pyc +0 -0
  130. sage/middleware/services/vdb/__pycache__/vdb_service.cpython-311.pyc +0 -0
  131. sage/middleware/services/vdb/examples/__pycache__/vdb_demo.cpython-311.opt-2.pyc +0 -0
  132. sage/middleware/services/vdb/examples/__pycache__/vdb_demo.cpython-311.pyc +0 -0
  133. sage/middleware/services/vdb/examples/vdb_demo.py +447 -0
  134. sage/middleware/services/vdb/search_engine/__init__.py +0 -0
  135. sage/middleware/services/vdb/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  136. sage/middleware/services/vdb/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  137. sage/middleware/services/vdb/search_engine/__pycache__/base_vdb_index.cpython-311.opt-2.pyc +0 -0
  138. sage/middleware/services/vdb/search_engine/__pycache__/base_vdb_index.cpython-311.pyc +0 -0
  139. sage/middleware/services/vdb/search_engine/__pycache__/faiss_index.cpython-311.opt-2.pyc +0 -0
  140. sage/middleware/services/vdb/search_engine/__pycache__/faiss_index.cpython-311.pyc +0 -0
  141. sage/middleware/services/vdb/search_engine/base_vdb_index.py +58 -0
  142. sage/middleware/services/vdb/search_engine/faiss_index.py +461 -0
  143. sage/middleware/services/vdb/vdb_service.py +433 -0
  144. sage/middleware/utils/__init__.py +5 -0
  145. sage/middleware/utils/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  146. sage/middleware/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  147. sage/middleware/utils/embedding/__init__.py +35 -0
  148. sage/middleware/utils/embedding/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  149. sage/middleware/utils/embedding/__pycache__/__init__.cpython-311.pyc +0 -0
  150. sage/middleware/utils/embedding/__pycache__/_cohere.cpython-311.opt-2.pyc +0 -0
  151. sage/middleware/utils/embedding/__pycache__/_cohere.cpython-311.pyc +0 -0
  152. sage/middleware/utils/embedding/__pycache__/bedrock.cpython-311.opt-2.pyc +0 -0
  153. sage/middleware/utils/embedding/__pycache__/bedrock.cpython-311.pyc +0 -0
  154. sage/middleware/utils/embedding/__pycache__/embedding_api.cpython-311.opt-2.pyc +0 -0
  155. sage/middleware/utils/embedding/__pycache__/embedding_api.cpython-311.pyc +0 -0
  156. sage/middleware/utils/embedding/__pycache__/embedding_model.cpython-311.opt-2.pyc +0 -0
  157. sage/middleware/utils/embedding/__pycache__/embedding_model.cpython-311.pyc +0 -0
  158. sage/middleware/utils/embedding/__pycache__/hf.cpython-311.opt-2.pyc +0 -0
  159. sage/middleware/utils/embedding/__pycache__/hf.cpython-311.pyc +0 -0
  160. sage/middleware/utils/embedding/__pycache__/instructor.cpython-311.opt-2.pyc +0 -0
  161. sage/middleware/utils/embedding/__pycache__/instructor.cpython-311.pyc +0 -0
  162. sage/middleware/utils/embedding/__pycache__/jina.cpython-311.opt-2.pyc +0 -0
  163. sage/middleware/utils/embedding/__pycache__/jina.cpython-311.pyc +0 -0
  164. sage/middleware/utils/embedding/__pycache__/lollms.cpython-311.opt-2.pyc +0 -0
  165. sage/middleware/utils/embedding/__pycache__/lollms.cpython-311.pyc +0 -0
  166. sage/middleware/utils/embedding/__pycache__/mockembedder.cpython-311.opt-2.pyc +0 -0
  167. sage/middleware/utils/embedding/__pycache__/mockembedder.cpython-311.pyc +0 -0
  168. sage/middleware/utils/embedding/__pycache__/nvidia_openai.cpython-311.opt-2.pyc +0 -0
  169. sage/middleware/utils/embedding/__pycache__/nvidia_openai.cpython-311.pyc +0 -0
  170. sage/middleware/utils/embedding/__pycache__/ollama.cpython-311.opt-2.pyc +0 -0
  171. sage/middleware/utils/embedding/__pycache__/ollama.cpython-311.pyc +0 -0
  172. sage/middleware/utils/embedding/__pycache__/openai.cpython-311.opt-2.pyc +0 -0
  173. sage/middleware/utils/embedding/__pycache__/openai.cpython-311.pyc +0 -0
  174. sage/middleware/utils/embedding/__pycache__/siliconcloud.cpython-311.opt-2.pyc +0 -0
  175. sage/middleware/utils/embedding/__pycache__/siliconcloud.cpython-311.pyc +0 -0
  176. sage/middleware/utils/embedding/__pycache__/zhipu.cpython-311.opt-2.pyc +0 -0
  177. sage/middleware/utils/embedding/__pycache__/zhipu.cpython-311.pyc +0 -0
  178. sage/middleware/utils/embedding/_cohere.py +68 -0
  179. sage/middleware/utils/embedding/bedrock.py +174 -0
  180. sage/middleware/utils/embedding/embedding_api.py +12 -0
  181. sage/middleware/utils/embedding/embedding_model.py +150 -0
  182. sage/middleware/utils/embedding/hf.py +90 -0
  183. sage/middleware/utils/embedding/instructor.py +10 -0
  184. sage/middleware/utils/embedding/jina.py +115 -0
  185. sage/middleware/utils/embedding/lollms.py +100 -0
  186. sage/middleware/utils/embedding/mockembedder.py +46 -0
  187. sage/middleware/utils/embedding/nvidia_openai.py +97 -0
  188. sage/middleware/utils/embedding/ollama.py +97 -0
  189. sage/middleware/utils/embedding/openai.py +112 -0
  190. sage/middleware/utils/embedding/siliconcloud.py +133 -0
  191. sage/middleware/utils/embedding/zhipu.py +85 -0
@@ -0,0 +1,461 @@
1
+ # file sage/core/sage.service.memory./search_engine/vdb_index/faiss_index.py
2
+ # python -m sage.core.sage.service.memory..search_engine.vdb_index.faiss_index
3
+
4
+ import json
5
+ import faiss
6
+ import pickle
7
+ import numpy as np
8
+ from typing import Optional, List, Dict, Any
9
+ from sage.common.utils.logging.custom_logger import CustomLogger
10
+ from sage.middleware.services.vdb.search_engine.base_vdb_index import BaseVDBIndex
11
+ import os
12
+
13
+ class FaissIndex(BaseVDBIndex):
14
+ def __init__(
15
+ self,
16
+ name: str,
17
+ dim: int,
18
+ vectors: Optional[List[np.ndarray]] = None,
19
+ ids: Optional[List[str]] = None,
20
+ config: Optional[dict] = None,
21
+ load_path: Optional[str] = None,
22
+ ):
23
+ """
24
+ 初始化 FaissIndex 实例,设置索引名称、维度,并可选性地加载初始向量和ID
25
+ Initialize the FaissIndex instance with name, dimension, and optionally preload vectors and ids.
26
+ """
27
+ self.index_name = name
28
+ self.dim = dim
29
+ self.config = config or {}
30
+ self.id_map: Dict[int, str] = {}
31
+ self.rev_map: Dict[str, int] = {}
32
+ self.next_id: int = 1
33
+ self.tombstones: set[str] = set()
34
+ self._deletion_supported = True
35
+ self.index = None
36
+ self.logger = CustomLogger()
37
+ if load_path is not None:
38
+ self._load(load_path)
39
+ else:
40
+ self.index, self._deletion_supported = self._init_index()
41
+ if vectors is not None and ids is not None:
42
+ self._build_index(vectors, ids)
43
+
44
+
45
+ def _init_index(self):
46
+ config = self.config # 保持全程都叫config
47
+ index_type = config.get("index_type", "IndexFlatL2")
48
+
49
+ # 基础索引
50
+ if index_type == "IndexFlatL2":
51
+ return faiss.IndexFlatL2(self.dim), True
52
+
53
+ elif index_type == "IndexFlatIP":
54
+ return faiss.IndexFlatIP(self.dim), True
55
+
56
+ # HNSW
57
+ elif index_type == "IndexHNSWFlat":
58
+ hnsw_m = int(config.get("HNSW_M", 32))
59
+ ef_construction = int(config.get("HNSW_EF_CONSTRUCTION", 200))
60
+ index = faiss.IndexHNSWFlat(self.dim, hnsw_m)
61
+ index.hnsw.efConstruction = ef_construction
62
+ if "HNSW_EF_SEARCH" in config:
63
+ index.hnsw.efSearch = int(config["HNSW_EF_SEARCH"])
64
+ return index, False
65
+
66
+ # IVF Flat
67
+ elif index_type == "IndexIVFFlat":
68
+ nlist = int(config.get("IVF_NLIST", 100))
69
+ nprobe = int(config.get("IVF_NPROBE", 10))
70
+ metric = self._get_metric(config.get("IVF_METRIC", "L2"))
71
+ quantizer = faiss.IndexFlatL2(self.dim)
72
+ index = faiss.IndexIVFFlat(quantizer, self.dim, nlist, metric)
73
+ index.nprobe = nprobe
74
+ return index, True
75
+
76
+ # IVF PQ
77
+ elif index_type == "IndexIVFPQ":
78
+ nlist = int(config.get("IVF_NLIST", 100))
79
+ nprobe = int(config.get("IVF_NPROBE", 10))
80
+ m = int(config.get("PQ_M", 8))
81
+ nbits = int(config.get("PQ_NBITS", 8))
82
+ metric = self._get_metric(config.get("IVF_METRIC", "L2"))
83
+ quantizer = faiss.IndexFlatL2(self.dim)
84
+ index = faiss.IndexIVFPQ(quantizer, self.dim, nlist, m, nbits, metric)
85
+ index.nprobe = nprobe
86
+ return index, True
87
+
88
+ # IVF ScalarQuantizer
89
+ elif index_type == "IndexIVFScalarQuantizer":
90
+ nlist = int(config.get("IVF_NLIST", 100))
91
+ nprobe = int(config.get("IVF_NPROBE", 10))
92
+ qtype_str = config.get("SQ_TYPE", "QT_8bit")
93
+ qtype = getattr(faiss.ScalarQuantizer, qtype_str)
94
+ metric = self._get_metric(config.get("IVF_METRIC", "L2"))
95
+ quantizer = faiss.IndexFlatL2(self.dim)
96
+ index = faiss.IndexIVFScalarQuantizer(quantizer, self.dim, nlist, qtype, metric)
97
+ index.nprobe = nprobe
98
+ return index, True
99
+
100
+ # LSH
101
+ elif index_type == "IndexLSH":
102
+ nbits = int(config.get("LSH_NBITS", 512))
103
+ rotate_data = bool(config.get("LSH_ROTATE_DATA", True))
104
+ train_thresholds = bool(config.get("LSH_TRAIN_THRESHOLDS", False))
105
+ index = faiss.IndexLSH(self.dim, nbits, rotate_data, train_thresholds)
106
+ return index, False
107
+
108
+ # PQ
109
+ elif index_type == "IndexPQ":
110
+ m = int(config.get("PQ_M", 8))
111
+ nbits = int(config.get("PQ_NBITS", 8))
112
+ metric = self._get_metric(config.get("PQ_METRIC", "L2"))
113
+ return faiss.IndexPQ(self.dim, m, nbits, metric), False
114
+
115
+ # ScalarQuantizer
116
+ elif index_type == "IndexScalarQuantizer":
117
+ qtype_str = config.get("SQ_TYPE", "QT_8bit")
118
+ qtype = getattr(faiss.ScalarQuantizer, qtype_str)
119
+ metric = self._get_metric(config.get("SQ_METRIC", "L2"))
120
+ return faiss.IndexScalarQuantizer(self.dim, qtype, metric), True
121
+
122
+ # RefineFlat
123
+ elif index_type == "IndexRefineFlat":
124
+ base_type = config.get("FAISS_BASE_INDEX_TYPE", "IndexFlatL2")
125
+ # 临时切换 index_type, 递归用 config 初始化
126
+ orig_type = config.get("index_type", None)
127
+ config["index_type"] = base_type
128
+ base_index, base_deletion_supported = self._init_index()
129
+ if orig_type is not None:
130
+ config["index_type"] = orig_type
131
+ k_factor = float(config.get("REFINE_K_FACTOR", 1.0))
132
+ return faiss.IndexRefineFlat(base_index, k_factor), True
133
+
134
+ # IndexIDMap
135
+ elif index_type == "IndexIDMap":
136
+ base_type = config.get("FAISS_BASE_INDEX_TYPE", "IndexFlatL2")
137
+ orig_type = config.get("index_type", None)
138
+ config["index_type"] = base_type
139
+ base_index, base_deletion_supported = self._init_index()
140
+ if orig_type is not None:
141
+ config["index_type"] = orig_type
142
+ return faiss.IndexIDMap(base_index), base_deletion_supported
143
+
144
+ else:
145
+ raise ValueError(f"Unsupported FAISS index type: {index_type}")
146
+
147
+ def _init_base_index(self):
148
+ """
149
+ 用于 IndexIDMap / IndexRefineFlat 的基础索引初始化
150
+ Initialize base index for IndexIDMap or IndexRefineFlat
151
+ """
152
+ base_type = os.getenv("FAISS_BASE_INDEX_TYPE", "IndexFlatL2")
153
+
154
+ original_type = os.getenv("FAISS_INDEX_TYPE")
155
+ os.environ["FAISS_INDEX_TYPE"] = base_type
156
+ index = self._init_index()
157
+ if original_type:
158
+ os.environ["FAISS_INDEX_TYPE"] = original_type
159
+ return index
160
+
161
+ def _get_metric(self, metric_str):
162
+ """
163
+ 获取距离度量方式:L2 或 Inner Product
164
+ Get distance metric: L2 or Inner Product
165
+ """
166
+ return faiss.METRIC_L2 if metric_str == "L2" else faiss.METRIC_INNER_PRODUCT
167
+
168
+ def _build_index(self, vectors: List[np.ndarray], ids: List[str]):
169
+ """
170
+ 构建初始索引并绑定 string ID → int ID 映射关系
171
+ Build initial index and bind string ID to int ID mapping
172
+ """
173
+ np_vectors = np.vstack(vectors).astype("float32")
174
+ int_ids = []
175
+
176
+ for string_id in ids:
177
+ if string_id in self.rev_map:
178
+ int_id = self.rev_map[string_id]
179
+ else:
180
+ int_id = self.next_id
181
+ self.next_id += 1
182
+ self.rev_map[string_id] = int_id
183
+ self.id_map[int_id] = string_id
184
+ int_ids.append(int_id)
185
+
186
+ int_ids_np = np.array(int_ids, dtype=np.int64)
187
+ if not isinstance(self.index, faiss.IndexIDMap):
188
+ self.logger.info("Wrapping index with IndexIDMap")
189
+ self.index = faiss.IndexIDMap(self.index) # 仅当未包装时才包装
190
+ self.index.add_with_ids(np_vectors, int_ids_np) # type: ignore
191
+
192
+ def delete(self, string_id: str):
193
+ """
194
+ 删除指定ID(物理删除或墓碑标记)
195
+ Delete by ID (physical removal or tombstone marking)
196
+ """
197
+ if string_id not in self.rev_map:
198
+ return
199
+
200
+ int_id = self.rev_map[string_id]
201
+
202
+ if self._deletion_supported:
203
+ try:
204
+ id_vector = np.array([int_id], dtype=np.int64)
205
+ self.index.remove_ids(id_vector) # type: ignore
206
+ except Exception as e:
207
+ print(f"删除失败,转为墓碑标记 / Deletion failed, fallback to tombstone: {e}")
208
+ self.tombstones.add(string_id)
209
+ else:
210
+ self.tombstones.add(string_id)
211
+
212
+ def update(self, string_id: str, new_vector: np.ndarray):
213
+ """
214
+ 更新指定 ID 的向量:保持原有映射关系,仅替换向量内容
215
+ Update the vector for the given ID, preserving the existing ID mapping.
216
+ """
217
+ if string_id not in self.rev_map:
218
+ # 如果ID不存在,直接插入
219
+ return self.insert(new_vector, string_id)
220
+
221
+ int_id = self.rev_map[string_id]
222
+
223
+ if self._deletion_supported:
224
+ try:
225
+ # 删除旧向量并插入新向量 / Remove old vector and insert new one
226
+ id_vector = np.array([int_id], dtype=np.int64)
227
+ self.index.remove_ids(id_vector) # type: ignore
228
+ vector = np.expand_dims(new_vector.astype("float32"), axis=0)
229
+ int_id_np = np.array([int_id], dtype=np.int64)
230
+ self.index.add_with_ids(vector, int_id_np) # type: ignore
231
+ except Exception as e:
232
+ print(f"更新失败 / Update failed: {e}")
233
+ self.insert(new_vector, string_id)
234
+ else:
235
+ if string_id in self.rev_map:
236
+ old_int_id = self.rev_map[string_id]
237
+ if old_int_id in self.id_map:
238
+ del self.id_map[old_int_id]
239
+ del self.rev_map[string_id]
240
+
241
+ new_int_id = self.next_id
242
+ self.next_id += 1
243
+ self.rev_map[string_id] = new_int_id
244
+ self.id_map[new_int_id] = string_id
245
+ vector = np.expand_dims(new_vector.astype("float32"), axis=0)
246
+ int_id_np = np.array([new_int_id], dtype=np.int64)
247
+ self.index.add_with_ids(vector, int_id_np) # type: ignore
248
+
249
+ def search(self, query_vector: np.ndarray, topk: int = 10):
250
+ """
251
+ 向量检索 / Vector search
252
+ 返回top_k结果(过滤墓碑) / Return top_k results (filter tombstones)
253
+ """
254
+ query_vector = np.expand_dims(query_vector.astype("float32"), axis=0)
255
+ distances, int_ids = self.index.search(query_vector, topk + len(self.tombstones)) # 多查一些结果 # type: ignore
256
+
257
+ results = []
258
+ filtered_distances = []
259
+
260
+ for i, dist in zip(int_ids[0], distances[0]):
261
+ if i == -1: # FAISS 空槽位标记
262
+ continue
263
+ string_id = self.id_map.get(i)
264
+ if string_id and string_id not in self.tombstones:
265
+ results.append(string_id)
266
+ filtered_distances.append(float(dist)) # 显式转为Python float
267
+ if len(results) >= topk:
268
+ break
269
+
270
+ return results, filtered_distances
271
+
272
+ def insert(self, vector: np.ndarray, string_id: str):
273
+ """
274
+ 插入单个向量及其字符串 ID 到索引中
275
+ Insert a single vector and its string ID into the index
276
+ """
277
+ if string_id in self.rev_map:
278
+ int_id = self.rev_map[string_id]
279
+ else:
280
+ int_id = self.next_id
281
+ self.next_id += 1
282
+ self.rev_map[string_id] = int_id
283
+ self.id_map[int_id] = string_id
284
+
285
+ vector = np.expand_dims(vector.astype("float32"), axis=0)
286
+ int_id_np = np.array([int_id], dtype=np.int64)
287
+ self.index.add_with_ids(vector, int_id_np) # type: ignore
288
+
289
+ def batch_insert(self, vectors: List[np.ndarray], string_ids: List[str]):
290
+ """
291
+ 批量插入多个向量及其对应的 string_id
292
+ Batch insert multiple vectors and their corresponding string_id
293
+ """
294
+ assert len(vectors) == len(string_ids), "Vectors and IDs must match in length"
295
+ np_vectors = np.vstack(vectors).astype("float32")
296
+ int_ids = []
297
+
298
+ for string_id in string_ids:
299
+ if string_id in self.rev_map:
300
+ int_id = self.rev_map[string_id]
301
+ else:
302
+ int_id = self.next_id
303
+ self.next_id += 1
304
+ self.rev_map[string_id] = int_id
305
+ self.id_map[int_id] = string_id
306
+ int_ids.append(int_id)
307
+
308
+ int_ids_np = np.array(int_ids, dtype=np.int64)
309
+ self.index.add_with_ids(np_vectors, int_ids_np) # type: ignore
310
+
311
+ def store(self, dir_path: str) -> Dict[str, Any]:
312
+ """
313
+ 将FAISS索引、参数和映射全部保存到指定目录。
314
+ """
315
+ os.makedirs(dir_path, exist_ok=True)
316
+ # 1. 保存faiss主索引
317
+ faiss.write_index(self.index, os.path.join(dir_path, "faiss.index"))
318
+ # 2. 保存id映射
319
+ with open(os.path.join(dir_path, "id_map.pkl"), "wb") as f:
320
+ pickle.dump(self.id_map, f)
321
+ with open(os.path.join(dir_path, "rev_map.pkl"), "wb") as f:
322
+ pickle.dump(self.rev_map, f)
323
+ with open(os.path.join(dir_path, "tombstones.pkl"), "wb") as f:
324
+ pickle.dump(self.tombstones, f)
325
+ # 3. 保存参数(如dim、下一个ID、自定义config等)
326
+ meta = {
327
+ "index_name": self.index_name,
328
+ "dim": self.dim,
329
+ "next_id": self.next_id,
330
+ "deletion_supported": self._deletion_supported,
331
+ "config": getattr(self, "config", {}), # 若有config则保存
332
+ }
333
+ with open(os.path.join(dir_path, "meta.json"), "w", encoding="utf-8") as f:
334
+ json.dump(meta, f, ensure_ascii=False, indent=2)
335
+ return {"index_path": dir_path}
336
+
337
+ def _load(self, dir_path: str):
338
+ """
339
+ 从目录恢复索引、映射与参数。仅供类方法load调用。
340
+ """
341
+ self.index = faiss.read_index(os.path.join(dir_path, "faiss.index"))
342
+ with open(os.path.join(dir_path, "id_map.pkl"), "rb") as f:
343
+ self.id_map = pickle.load(f)
344
+ with open(os.path.join(dir_path, "rev_map.pkl"), "rb") as f:
345
+ self.rev_map = pickle.load(f)
346
+ with open(os.path.join(dir_path, "tombstones.pkl"), "rb") as f:
347
+ self.tombstones = pickle.load(f)
348
+ with open(os.path.join(dir_path, "meta.json"), "r", encoding="utf-8") as f:
349
+ meta = json.load(f)
350
+ self.index_name = meta["index_name"]
351
+ self.dim = meta["dim"]
352
+ self.next_id = meta["next_id"]
353
+ self._deletion_supported = meta.get("deletion_supported", True)
354
+ self.config = meta.get("config", {})
355
+
356
+ @classmethod
357
+ def load(cls, name: str, load_path: str) -> "FaissIndex":
358
+ """
359
+ 推荐方式,等价于 BM25sIndex.load
360
+ """
361
+ return cls(name=name, dim=0, load_path=load_path) # dim会被_load覆盖
362
+
363
+ if __name__ == "__main__":
364
+ import os
365
+ import shutil
366
+ import numpy as np
367
+
368
+ def colored(text, color):
369
+ # color: "green", "red", "yellow"
370
+ colors = {"green": "\033[92m", "red": "\033[91m", "yellow": "\033[93m", "reset": "\033[0m"}
371
+ return colors.get(color, "") + text + colors["reset"]
372
+
373
+ def print_test_case(desc, expected_ids, expected_dists, actual_ids, actual_dists, digits=4):
374
+ ids_pass = list(expected_ids) == list(actual_ids)
375
+ dists_pass = all(abs(e-a) < 10**-digits for e,a in zip(expected_dists, actual_dists))
376
+ status = "通过" if ids_pass and dists_pass else "不通过"
377
+ color = "green" if status == "通过" else "red"
378
+ print(f"【{desc}】")
379
+ print(f"预期IDs:{expected_ids}")
380
+ print(f"实际IDs:{actual_ids}")
381
+ print(f"预期距离:{expected_dists}")
382
+ print(f"实际距离:{[round(x, digits) for x in actual_dists]}")
383
+ print(f"测试情况:{colored(status, color)}\n")
384
+
385
+ # ==== 基础数据 ====
386
+ dim = 4
387
+ index_name = "test_index"
388
+ root_dir = "./faiss_index_test"
389
+ if os.path.exists(root_dir):
390
+ shutil.rmtree(root_dir)
391
+ os.makedirs(root_dir, exist_ok=True)
392
+
393
+ vectors = [
394
+ np.array([1.0, 0.0, 0.0, 0.0]),
395
+ np.array([0.0, 1.0, 0.0, 0.0]),
396
+ np.array([0.0, 0.0, 1.0, 0.0])
397
+ ]
398
+ ids = ["id1", "id2", "id3"]
399
+
400
+ index = FaissIndex(name=index_name, dim=dim, vectors=vectors, ids=ids)
401
+ # 1. 检索
402
+ q1 = np.array([1.0, 0.0, 0.0, 0.0])
403
+ r_ids, r_dists = index.search(q1, 3)
404
+ print_test_case("基础检索", ["id1", "id2", "id3"], [0.0, 2.0, 2.0], r_ids, r_dists)
405
+
406
+ # 2. 插入新向量
407
+ index.insert(np.array([0.0, 0.0, 0.0, 1.0]), "id4")
408
+ q2 = np.array([0.0, 0.0, 0.0, 1.0])
409
+ r_ids, r_dists = index.search(q2, 4)
410
+ print_test_case("插入后检索", ["id4", "id1", "id2", "id3"], [0.0, 2.0, 2.0, 2.0], r_ids, r_dists)
411
+
412
+ # 3. 更新向量
413
+ index.update("id1", np.array([0.5, 0.5, 0.0, 0.0]))
414
+ q3 = np.array([0.5, 0.5, 0.0, 0.0])
415
+ r_ids, r_dists = index.search(q3, 4)
416
+ print_test_case("更新后检索", ['id1', 'id2', 'id3', 'id4'], [0.0, 0.5, 1.5, 1.5], r_ids, r_dists)
417
+
418
+ # 4. 删除向量
419
+ index.delete("id2")
420
+ q4 = np.array([1.0, 0.0, 0.0, 0.0])
421
+ r_ids, r_dists = index.search(q4, 4)
422
+ print_test_case("删除后检索", ['id1', 'id3', 'id4'], [0.5, 2.0, 2.0], r_ids, r_dists)
423
+
424
+ # 5. 批量插入
425
+ index.batch_insert([
426
+ np.array([0.1, 0.1, 0.1, 0.1]),
427
+ np.array([0.2, 0.2, 0.2, 0.2])
428
+ ], ["id5", "id6"])
429
+ q5 = np.array([0.1, 0.1, 0.1, 0.1])
430
+ r_ids, r_dists = index.search(q5, 6)
431
+ print_test_case("批量插入后检索", ['id5', 'id6', 'id1', 'id3', 'id4'], [0.0, 0.04, 0.34, 0.84, 0.84], r_ids[:5], r_dists[:5], 2)
432
+
433
+ # ==== 持久化保存 ====
434
+ print(colored("\n--- 保存索引到磁盘 ---", "yellow"))
435
+ index.store(root_dir)
436
+ print(colored(f"数据已保存到目录: {root_dir}", "yellow"))
437
+
438
+ # ==== 内存对象清空 ====
439
+ del index
440
+ print(colored("内存对象已清除。", "yellow"))
441
+
442
+ # ==== 读取并检索 ====
443
+ user_input = input(colored("输入 yes 加载刚才保存的数据: ", "yellow"))
444
+ if user_input.strip().lower() == "yes":
445
+ index2 = FaissIndex.load(index_name, root_dir)
446
+ print(colored("数据已从磁盘恢复!", "green"))
447
+
448
+ r_ids, r_dists = index2.search(np.array([0.1, 0.1, 0.1, 0.1]), 5)
449
+ print_test_case("恢复后检索", ["id5", "id6", "id1", "id3", "id4"], [0.0, 0.04, 0.34, 0.84, 0.84], r_ids, r_dists, 2)
450
+ else:
451
+ print(colored("跳过加载测试。", "yellow"))
452
+
453
+ # ==== 清除磁盘数据 ====
454
+ user_input = input(colored("输入 yes 删除磁盘所有数据: ", "yellow"))
455
+ if user_input.strip().lower() == "yes":
456
+ shutil.rmtree(root_dir)
457
+ print(colored("所有数据已删除!", "green"))
458
+ else:
459
+ print(colored("未执行删除。", "yellow"))
460
+
461
+