Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈 - L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合) - 文档三级总结与 2.5 级回退 - query 解析路由与分类 - /admin 管理页面
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""BM25 轻量近似稀疏向量编码器
|
||||
|
||||
零第三方依赖的稀疏编码实现:无全局 IDF 的 BM25 近似(仅用词频 tf 加权),
|
||||
输出 Qdrant SparseVector 所需的 indices/values 格式。
|
||||
后续可替换为 SPLADE / BM42 等更强的稀疏编码器。
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import math
|
||||
import re
|
||||
|
||||
# 稀疏向量维度(哈希空间大小)
|
||||
SPARSE_DIM = 2**18
|
||||
|
||||
# 连续 CJK 字符段(一-鿿)
|
||||
_CJK_RE = re.compile(r"[一-鿿]+")
|
||||
# CJK 段或英文/数字连续段
|
||||
_TOKEN_RE = re.compile(r"[一-鿿]+|[a-zA-Z0-9]+")
|
||||
|
||||
|
||||
def _tokenize(text: str) -> list[str]:
|
||||
"""分词(纯正则实现)
|
||||
|
||||
- 连续 CJK 字符段:长度 1 时保留 unigram,长度 >= 2 时生成字符 bigram
|
||||
- 英文/数字连续段:小写化后作为整词
|
||||
"""
|
||||
tokens: list[str] = []
|
||||
for match in _TOKEN_RE.finditer(text):
|
||||
seg = match.group()
|
||||
if _CJK_RE.fullmatch(seg):
|
||||
if len(seg) == 1:
|
||||
tokens.append(seg)
|
||||
else:
|
||||
tokens.extend(seg[i : i + 2] for i in range(len(seg) - 1))
|
||||
else:
|
||||
tokens.append(seg.lower())
|
||||
return tokens
|
||||
|
||||
|
||||
def _hash(token: str) -> int:
|
||||
"""将词哈希到 [0, SPARSE_DIM) 的索引空间"""
|
||||
digest = hashlib.blake2b(token.encode("utf-8"), digest_size=8).digest()
|
||||
return int.from_bytes(digest, "big") % SPARSE_DIM
|
||||
|
||||
|
||||
class SparseEncoder:
|
||||
"""稀疏向量编码器,输出 Qdrant SparseVector 的 indices/values"""
|
||||
|
||||
def encode(self, text: str) -> tuple[list[int], list[float]]:
|
||||
"""编码单条文本
|
||||
|
||||
权重 = 1 + log(tf),词经哈希到 [0, SPARSE_DIM),哈希冲突时权重累加。
|
||||
|
||||
Returns:
|
||||
(indices, values):indices 升序且无重复,与 values 等长
|
||||
"""
|
||||
# 按哈希索引累计词频,天然处理哈希冲突
|
||||
tf: dict[int, float] = {}
|
||||
for token in _tokenize(text):
|
||||
idx = _hash(token)
|
||||
tf[idx] = tf.get(idx, 0.0) + 1.0
|
||||
indices = sorted(tf)
|
||||
values = [1.0 + math.log(tf[idx]) for idx in indices]
|
||||
return indices, values
|
||||
|
||||
def encode_batch(self, texts: list[str]) -> list[tuple[list[int], list[float]]]:
|
||||
"""批量编码,与逐条 encode 结果一致"""
|
||||
return [self.encode(text) for text in texts]
|
||||
Reference in New Issue
Block a user