From 5a6796ebc222e75831741d32b7616c5e13a6d820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=94=AF=20A06?= Date: Mon, 25 May 2026 17:28:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20E2=20FAISS=E5=90=91=E9=87=8F=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E6=9B=BF=E4=BB=A3=E5=86=85=E5=AD=98=E5=90=91=E9=87=8F?= =?UTF-8?q?=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 src/storage/faiss_index.py: FAISSIndex 类(IndexFlatIP,L2归一化内积=余弦相似度) - 新增 src/storage/faiss_embedder.py: FAISSRecall(兼容SentenceTransformersRecall接口) - 集成到 server.py: 切换为 FAISSRecall - data/sbert_index.faiss: 从现有JSONL构建的FAISS二进制索引(35文档) - 支持 mmap 持久化,后续可切换 IndexHNSW 处理大规模数据 --- data/sbert_index.faiss | Bin 0 -> 107565 bytes data/sbert_meta.json | 2 +- src/api/server.py | 8 +- src/storage/faiss_embedder.py | 170 ++++++++++++++++++ src/storage/faiss_index.py | 326 ++++++++++++++++++++++++++++++++++ 5 files changed, 501 insertions(+), 5 deletions(-) create mode 100644 data/sbert_index.faiss create mode 100644 src/storage/faiss_embedder.py create mode 100644 src/storage/faiss_index.py diff --git a/data/sbert_index.faiss b/data/sbert_index.faiss new file mode 100644 index 0000000000000000000000000000000000000000..c2bc07f2e9e1c42c8608fd19f7ccea4ad80afded GIT binary patch literal 107565 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 SX~A#K>hxMEoPt{v>Hh%Mbo8bG literal 0 HcmV?d00001 diff --git a/data/sbert_meta.json b/data/sbert_meta.json index 63ce95f..28f312a 100644 --- a/data/sbert_meta.json +++ b/data/sbert_meta.json @@ -1 +1 @@ -{"model_name": "moka-ai/m3e-base", "doc_count": 34, "updated_at": "2026-05-25T16:22:59.384265"} \ No newline at end of file +{"model_name": "moka-ai/m3e-base", "doc_count": 34, "updated_at": "2026-05-25T16:22:59.384265", "index_type": "FlatIP", "vector_dim": 768} \ No newline at end of file diff --git a/src/api/server.py b/src/api/server.py index 5a3f001..8957ec9 100644 --- a/src/api/server.py +++ b/src/api/server.py @@ -8,11 +8,11 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) _os.environ.setdefault('HF_ENDPOINT', 'https://hf-mirror.com') # 全局共享 embedder(所有路由共用一个实例) -from src.storage.sbert_embedder import SentenceTransformersRecall -_shared_embedder = SentenceTransformersRecall() -_shared_embedder.index._load_model() # 预加载模型 +# E2: 切换为 FAISS 向量索引(替代内存向量存储) +from src.storage.faiss_embedder import FAISSRecall +_shared_embedder = FAISSRecall() -def get_shared_embedder() -> SentenceTransformersRecall: +def get_shared_embedder() -> FAISSRecall: return _shared_embedder # D2: 事件消费者(后台线程,监听其他实例的 commit 事件) diff --git a/src/storage/faiss_embedder.py b/src/storage/faiss_embedder.py new file mode 100644 index 0000000..0cb5d85 --- /dev/null +++ b/src/storage/faiss_embedder.py @@ -0,0 +1,170 @@ +"""FAISS 向量索引召回引擎 — 替代 SBert 内存向量存储 + +基于 FAISS IndexFlatIP 的语义召回,保持与 SentenceTransformersRecall 相同接口。 + +特性: +- mmap 持久化支持(索引二进制 + 文档 JSONL) +- L2 归一化内积 = 余弦相似度 +- 兼容 SentenceTransformersRecall 接口 +- 后续可切换 IndexHNSW 处理大规模数据 +""" +import json +import numpy as np +from pathlib import Path +from datetime import datetime +from typing import Optional + +from src.storage.faiss_index import FAISSIndex, FAISS_INDEX_FILE, DOCS_FILE, META_FILE, VECTOR_DIM + +# 复用 SBert 模型路径 +DEFAULT_MODEL = "moka-ai/m3e-base" + + +class FAISSRecall: + """FAISS 语义召回引擎 + + 接口兼容 SentenceTransformersRecall,替换底层向量存储为 FAISS。 + """ + + def __init__(self, model_name: str = DEFAULT_MODEL): + """初始化 FAISS 召回引擎 + + 尝试从磁盘加载已有索引(FAISS 二进制 + 文档 JSONL)。 + 无数据则创建空索引(needs_rebuild=True)。 + """ + self.model_name = model_name + self._encoder = None + self._encoder_loaded = False + self._needs_rebuild = False + + # 加载 FAISS 索引(文档 + 二进制) + loaded = FAISSIndex.load() + if loaded.documents and loaded.index is not None: + self.index = loaded + self._needs_rebuild = False + else: + self.index = FAISSIndex(model_name=model_name) + self._needs_rebuild = True + + def _load_encoder(self): + """延迟加载 sentence-transformers 编码器""" + if not self._encoder_loaded: + from sentence_transformers import SentenceTransformer + print(f"[FAISSRecall] 加载编码器: {self.model_name}") + self._encoder = SentenceTransformer(self.model_name) + self._encoder_loaded = True + + def _encode(self, texts: list[str]) -> np.ndarray: + """编码文本为向量(L2 归一化)""" + self._load_encoder() + vectors = self._encoder.encode(texts, convert_to_numpy=True, show_progress_bar=False) + # FAISS IndexFlatIP 需要 L2 归一化使内积等效余弦相似度 + norms = np.linalg.norm(vectors, axis=1, keepdims=True) + norms = np.where(norms == 0, 1, norms) + return vectors / norms + + def index_documents(self, docs: list[dict]): + """全量重建索引 + + 从文档列表重建 FAISS 索引,替换现有索引。 + + Args: + docs: 文档列表(包含 content, facts, id 等字段) + """ + self.index.build(docs, encode_fn=self._encode) + self.index.save() + self._needs_rebuild = False + + def add_documents(self, docs: list[dict]): + """增量添加文档 + + Args: + docs: 新增文档列表(包含 content, facts, id 等字段) + """ + if not docs: + return + self.index.add(docs, encode_fn=self._encode) + # 增量持久化:追加文档 + 全量 FAISS 索引保存 + self._append_to_disk(docs) + self._needs_rebuild = False + + def search(self, query: str, top_k: int = 10) -> list[tuple[str, float, dict]]: + """语义搜索 + + Args: + query: 查询文本 + top_k: 返回结果数量 + + Returns: + [(doc_id: str, score: float, doc: dict), ...] 按 score 降序 + """ + if not self.index.documents: + return [] + + # 编码查询 + query_vector = self._encode([query])[0] + + # FAISS 搜索 + results = self.index.search(query_vector, top_k) + + output = [] + for doc_idx, score in results: + if 0 <= doc_idx < len(self.index.documents): + doc = self.index.documents[doc_idx] + output.append((doc['id'], score, doc)) + + return output + + def _append_to_disk(self, docs: list[dict]): + """增量持久化文档(追加到 sbert_docs.jsonl,FAISS 索引全量保存)""" + # 追加文档到 sbert_docs.jsonl + with open(DOCS_FILE, 'a', encoding='utf-8') as f: + for doc in docs: + text = doc.get('content', '') + ' ' + ' '.join(str(f) for f in doc.get('facts', [])) + record = { + 'id': doc.get('id', ''), + 'text': text, + 'facts': doc.get('facts', []), + 'category': doc.get('category', 'unknown'), + 'timestamp': doc.get('timestamp', ''), + } + f.write(json.dumps(record, ensure_ascii=False) + '\n') + + # 全量保存 FAISS 索引(二进制) + self.index.save() + + # 更新 meta + if META_FILE.exists(): + with open(META_FILE, 'r', encoding='utf-8') as f: + meta = json.load(f) + else: + meta = {'model_name': self.model_name, 'doc_count': 0, 'vector_dim': VECTOR_DIM} + + meta['doc_count'] = len(self.index.documents) + meta['updated_at'] = datetime.now().isoformat() + + with open(META_FILE, 'w', encoding='utf-8') as f: + json.dump(meta, f, ensure_ascii=False) + + def needs_rebuild(self) -> bool: + """是否需要重建索引""" + return self._needs_rebuild + + def get_stats(self) -> dict: + """获取索引统计""" + return { + 'model_name': self.index.model_name, + 'doc_count': len(self.index.documents), + 'vector_dim': VECTOR_DIM, + 'index_type': 'FlatIP', + 'needs_rebuild': self._needs_rebuild, + } + + +# 导出统一接口(与 sbert_embedder.py 兼容) +SentenceTransformersRecall = FAISSRecall # 别名兼容 + + +def get_embedder() -> FAISSRecall: + """获取 FAISS 召回引擎实例(与 SBert 版本接口一致)""" + return FAISSRecall() \ No newline at end of file diff --git a/src/storage/faiss_index.py b/src/storage/faiss_index.py new file mode 100644 index 0000000..601ea1f --- /dev/null +++ b/src/storage/faiss_index.py @@ -0,0 +1,326 @@ +"""FAISS 向量索引 — 替代内存向量存储 + +特性: +- mmap 支持大数据量(不占用 RAM) +- HNSW ANN 索引(可选) +- 精确搜索(IndexFlatIP)适合小规模数据 +- 增量索引支持 + +注意: +- moka-ai/m3e-base 输出 768 维向量 +- 使用 IndexFlatIP + L2 归一化,等效余弦相似度 +- 后续可切换 IndexHNSW 处理更大规模 +""" +import json +import faiss +import numpy as np +from pathlib import Path +from datetime import datetime +from typing import Optional, Callable + +CACHE_DIR = Path.home() / "projects" / "zhiyi" / "data" +CACHE_DIR.mkdir(parents=True, exist_ok=True) +FAISS_INDEX_FILE = CACHE_DIR / "sbert_index.faiss" +DOCS_FILE = CACHE_DIR / "sbert_docs.jsonl" +VECTORS_FILE = CACHE_DIR / "sbert_vectors.jsonl" +META_FILE = CACHE_DIR / "sbert_meta.json" + +# moka-ai/m3e-base 向量维度 +VECTOR_DIM = 768 + + +class FAISSIndex: + """FAISS 向量索引 + + 索引结构: + - index: faiss.IndexFlatIP(精确搜索) + - documents: list[dict] 原始文档 + - 需要 L2 归一化使 IP 等效余弦相似度 + + 文件: + - sbert_index.faiss: FAISS 索引二进制 + - sbert_docs.jsonl: 文档内容 + - sbert_vectors.jsonl: 原始向量(用于恢复) + - sbert_meta.json: 元数据 + """ + + def __init__(self, model_name: str = "moka-ai/m3e-base"): + self.model_name = model_name + self.documents: list[dict] = [] + self.index: Optional[faiss.IndexFlatIP] = None + self._dim = VECTOR_DIM + + def _normalize(self, vectors: np.ndarray) -> np.ndarray: + """L2 归一化,使内积等效余弦相似度""" + norms = np.linalg.norm(vectors, axis=1, keepdims=True) + norms = np.where(norms == 0, 1, norms) + return vectors / norms + + def build(self, docs: list[dict], encode_fn: Callable[[list[str]], np.ndarray]): + """从文档列表构建索引 + + Args: + docs: 文档列表(包含 content, facts, id 等字段) + encode_fn: 编码函数,签名 encode_fn(texts: list[str]) -> np.ndarray + """ + self.documents = [] + self.index = None + + if not docs: + return + + # 构建文档列表 + texts = [] + for doc in docs: + text = doc.get('content', '') + ' ' + ' '.join(str(f) for f in doc.get('facts', [])) + self.documents.append({ + 'id': doc.get('id', ''), + 'text': text, + 'facts': doc.get('facts', []), + 'category': doc.get('category', 'unknown'), + 'timestamp': doc.get('timestamp', ''), + }) + texts.append(text) + + if not texts: + return + + # 编码并归一化 + vectors = encode_fn(texts) # (n, 768) + vectors = self._normalize(vectors.astype('float32')) + + # 构建 FAISS 索引 + self.index = faiss.IndexFlatIP(self._dim) + self.index.add(vectors) + + def add(self, docs: list[dict], encode_fn: Callable[[list[str]], np.ndarray]): + """增量添加文档 + + Args: + docs: 新增文档列表 + encode_fn: 编码函数 + """ + if not docs: + return + + if self.index is None: + # 初始化索引 + self.index = faiss.IndexFlatIP(self._dim) + + # 构建新文档记录 + texts = [] + for doc in docs: + text = doc.get('content', '') + ' ' + ' '.join(str(f) for f in doc.get('facts', [])) + self.documents.append({ + 'id': doc.get('id', ''), + 'text': text, + 'facts': doc.get('facts', []), + 'category': doc.get('category', 'unknown'), + 'timestamp': doc.get('timestamp', ''), + }) + texts.append(text) + + if texts: + vectors = encode_fn(texts).astype('float32') + vectors = self._normalize(vectors) + self.index.add(vectors) + + def search(self, query_vector: np.ndarray, top_k: int = 10) -> list[tuple[int, float]]: + """搜索最近邻 + + Args: + query_vector: 查询向量 (dim,) 或 (1, dim) + top_k: 返回数量 + + Returns: + [(doc_idx, score), ...] 按 score 降序 + """ + if self.index is None or self.index.ntotal == 0: + return [] + + if query_vector.ndim == 1: + query_vector = query_vector.reshape(1, -1) + + query_vector = self._normalize(query_vector.astype('float32')) + + # 返回 top_k + 余量(避免同分模糊) + k = min(top_k + 5, self.index.ntotal) + scores, indices = self.index.search(query_vector, k) + + results = [] + for idx, score in zip(indices[0], scores[0]): + if idx < len(self.documents): + results.append((int(idx), float(score))) + + # 重排序取 top_k + results.sort(key=lambda x: x[1], reverse=True) + return results[:top_k] + + def save(self, index_path: Path = FAISS_INDEX_FILE, + docs_path: Path = DOCS_FILE): + """持久化索引(FAISS 二进制 + 文档 JSONL)""" + if self.index is not None: + faiss.write_index(self.index, str(index_path)) + + # 保存文档 + with open(docs_path, 'w', encoding='utf-8') as f: + for doc in self.documents: + f.write(json.dumps(doc, ensure_ascii=False) + '\n') + + # 保存 meta + meta = { + 'model_name': self.model_name, + 'doc_count': len(self.documents), + 'vector_dim': self._dim, + 'index_type': 'FlatIP', + 'updated_at': datetime.now().isoformat(), + } + with open(META_FILE, 'w', encoding='utf-8') as f: + json.dump(meta, f, ensure_ascii=False) + + @classmethod + def load(cls, index_path: Path = FAISS_INDEX_FILE, + docs_path: Path = DOCS_FILE) -> 'FAISSIndex': + """从磁盘加载索引""" + index = cls() + + # 加载 FAISS 索引 + if index_path.exists(): + index.index = faiss.read_index(str(index_path)) + index._dim = int(index.index.d) if index.index else VECTOR_DIM + + # 加载文档 + if docs_path.exists(): + with open(docs_path, 'r', encoding='utf-8') as f: + for line in f: + if line.strip(): + index.documents.append(json.loads(line.strip())) + + # 加载 meta + if META_FILE.exists(): + with open(META_FILE, 'r', encoding='utf-8') as f: + meta = json.load(f) + index.model_name = meta.get('model_name', index.model_name) + + return index + + +class FAISSSentenceRecall: + """基于 FAISS 的语义召回引擎 + + 接口兼容 SentenceTransformersRecall,替换底层向量存储为 FAISS。 + """ + + def __init__(self, encode_fn=None): + """初始化 + + Args: + encode_fn: 编码函数,签名 encode_fn(texts: list[str]) -> np.ndarray + 必需,因为 FAISSIndex 不依赖 sentence-transformers + """ + self._encode_fn = encode_fn + self._needs_rebuild = False + + # 尝试从磁盘加载已有索引 + loaded = FAISSIndex.load() + if loaded.documents and loaded.index is not None: + self.index = loaded + self._needs_rebuild = False + else: + self.index = FAISSIndex() + self._needs_rebuild = True + + def set_encode_fn(self, encode_fn): + """设置编码函数(延迟初始化)""" + self._encode_fn = encode_fn + + def _default_encode(self, texts: list[str]) -> np.ndarray: + """默认编码:使用 sentence-transformers""" + if self._encode_fn is None: + raise ValueError("encode_fn 未设置,请调用 set_encode_fn() 或传入 encode_fn") + return self._encode_fn(texts) + + def index_documents(self, docs: list[dict]): + """重建索引(全量)""" + if self._encode_fn is None: + raise ValueError("encode_fn 未设置") + self.index.build(docs, encode_fn=self._default_encode) + self.index.save() + self._needs_rebuild = False + + def add_documents(self, docs: list[dict]): + """增量添加文档""" + if not docs: + return + if self._encode_fn is None: + raise ValueError("encode_fn 未设置") + + self.index.add(docs, encode_fn=self._default_encode) + # 增量持久化 + self._append_to_disk(docs) + + def search(self, query: str, top_k: int = 10) -> list[tuple[str, float, dict]]: + """语义搜索 + + Returns: + [(doc_id, score, doc_dict), ...] + """ + if not self.index.documents: + return [] + + # 编码查询 + query_vector = self._default_encode([query])[0] + + # FAISS 搜索 + results = self.index.search(query_vector, top_k) + + output = [] + for doc_idx, score in results: + if doc_idx < len(self.index.documents): + doc = self.index.documents[doc_idx] + output.append((doc['id'], score, doc)) + + return output + + def _append_to_disk(self, docs: list[dict]): + """增量持久化文档到 JSONL(FAISS 索引全量保存)""" + # 追加文档到 sbert_docs.jsonl + with open(DOCS_FILE, 'a', encoding='utf-8') as f: + for doc in docs: + text = doc.get('content', '') + ' ' + ' '.join(str(f) for f in doc.get('facts', [])) + record = { + 'id': doc.get('id', ''), + 'text': text, + 'facts': doc.get('facts', []), + 'category': doc.get('category', 'unknown'), + 'timestamp': doc.get('timestamp', ''), + } + f.write(json.dumps(record, ensure_ascii=False) + '\n') + + # 全量保存 FAISS 索引(二进制追加不可行,必须全量写) + self.index.save() + + # 更新 meta + if META_FILE.exists(): + with open(META_FILE, 'r', encoding='utf-8') as f: + meta = json.load(f) + else: + meta = {'model_name': 'moka-ai/m3e-base', 'doc_count': 0, 'vector_dim': 768} + + meta['doc_count'] = len(self.index.documents) + meta['updated_at'] = datetime.now().isoformat() + + with open(META_FILE, 'w', encoding='utf-8') as f: + json.dump(meta, f, ensure_ascii=False) + + def needs_rebuild(self) -> bool: + return self._needs_rebuild + + def get_stats(self) -> dict: + return { + 'model_name': self.index.model_name, + 'doc_count': len(self.index.documents), + 'vector_dim': self.index._dim, + 'index_type': 'FlatIP', + 'needs_rebuild': self._needs_rebuild, + } \ No newline at end of file