01 为什么不做另一个 RAG
最近,我在推演一个叫「重力空间」的个人化模型架构。
它的核心直觉很简单:思考层不产指令,只维护地形图;行动层不自己决策,只从地形图读信息。整个系统不是「智能体规划然后执行」,而是「在向量空间中形成引力场,检索就是在场中导航」。
这个想法很有美感,但一直卡在「怎么落地」上。
直到暑假,我决定拿它做一个真正的产品------一个本地知识问答系统。不是命令行玩具,是别人能直接拿来用、能部署、能演示的东西。
立项的时候我列了一张功能表:
- 文件上传、切片、嵌入、搜索...... ✅ 这是基础
- 场域路由、多样性排序...... ✅ 这是差异化
- 连接表、反思期、可视化...... ⌛ 这是未来
但做到一半我停下来问了自己一个问题:
「这些东西,别的知识库有没有?」
答案是有。或者说,它们都有替代方案。BM25 替代质量场,HyDE 替代引力漂移,RAPTOR 替代层次索引,Cross-encoder 替代重排序。
那我为什么还要做呢?
因为我想试试:当你在向量空间中已经有了聚类、场域、质心这些基础设施,那些「额外模块」能不能用几何运算替代?
这个项目就是答案。
02 它不是 RAG,是重力知识库
先快速说一下它长什么样。
打开浏览器,进入 localhost:8765,你会看到一个白色背景的搜索框:
传一份 PDF,等几秒钟,然后提问。
系统做的事:
css
你的问题 → 嵌入向量 → 簇级路由 → 引力漂移 → FAISS 粗搜
→ 质量场融合 → 四层排序 → Top-5 上下文 → LLM 回答
整个链路的时长大约 2.1 秒,其中 2 秒是嵌入(本地 Ollama nomic-embed-text),真正的检索和排序不到 100 毫秒。
它知道你的知识库分了 5 个簇,知道每个问题该优先去哪个簇里找,知道哪些结果太相似了要压一压,知道你之前问过什么。
而且所有这些,不依赖任何外部 API。纯本地,零费用。
03 四个设计
我不想罗列功能,我想说四个设计点。这四个点的共同特征是:它们都有标准答案,但我没走标准答案。
一、质量场替代 BM25 混合检索
标准做法是 BM25 + Dense Retrieval,两个索引各自召回再做 RRF 融合。
我的做法:每个球体入库时自动提取关键词权重(TF 归一化),存在球体的 term_weights 字段里。检索时:
最终得分 = 0.7 × 语义相似度 + 0.3 × 术语引力
术语引力就是查询词在球体关键词中的命中率。就是个加权词袋,但不需要维护倒排索引,不需要独立的 BM25 系统。关键词是球体的属性,不是另一个索引。
python
# 关键词提取,纯 re + Counter,20 行
def extract_keywords(text):
tokens = re.findall(r'[\u4e00-\u9fff]{2,}|[a-zA-Z]{2,}', text.lower())
counter = Counter(tokens)
total = sum(counter.values())
return {word: count/total for word, count in counter.most_common(50)}
零外部依赖。效果不如 BM25 + 精排,但够用------在有聚类和场域路由的情况下,0.3 的术语权重已经能有效拉回关键词精准匹配的结果。
二、引力漂移替代 HyDE
HyDE 的做法:用 LLM 生成一段「假想文档」,然后用假想文档去搜。如果 LLM 写偏了,检索就被带歪。
引力漂移的做法:
erlang
① 快速初检 5 个结果 → 统计它们落在哪个簇
② 如果 ≥3 个在同一个簇 → 查询向量向该簇质心微调 15%
③ 重新归一化 → 第二次检索
不生成任何 token,不依赖 LLM。只是在第一次检索后根据几何反馈修正查询向量的位置。
代码不到 30 行:
python
probe_ids = faiss.search(query_vector, top_k=30)
cluster_counts = count_clusters_of(probe_ids[:5])
for cluster, count in cluster_counts.items():
if count >= 3:
centroid = field_detector.centroids[cluster]
query_vector = query_vector + 0.15 * centroid
query_vector = query_vector / norm(query_vector) # re-normalize
break
它解决的是和 HyDE 一样的问题------查询向量在错误的位置,需要往真实意图的方向推一把。但走的是几何路径,不是生成路径。
三、簇级索引替代 RAPTOR
RAPTOR 的做法:用 LLM 生成文档摘要,建一棵树,搜索时先找叶子再找根。
我有的基础设施:K-Means 聚类自动把 1460 个球体分成 5 个簇,每个簇有一个质心。
簇级索引的做法:
css
① 查询向量和 5 个簇质心做余弦 → 选前 2 个簇
② FAISS 粗搜 200 个 → 过滤出属于前 2 簇的结果 → 优先排在前面
③ MMR 从排好序的候选中选 Top-5
不存额外的树结构,不生成摘要。簇中心就是天然的树根。
四、冗余惩罚替代 Cross-encoder
Cross-encoder 的做法:对每个 (query, doc) 对跑一次推理,判断相关性。精度高,但慢。
冗余惩罚的做法:
对每个候选球体:
计算它与同簇内所有其他候选的平均相似度
平均相似度高 = 它在簇内的密集区域 = 信息冗余
得分扣减: -0.05 × 平均相似度
不是「判断相关性」,而是「判断信息量」。一个球体如果和簇内其他球体高度相似,那它带来的新信息就少。
这不是 Cross-encoder 的替代品------它们解决的不是同一个问题。Cross-encoder 解决「这个文档回答了问题吗」,冗余惩罚解决「这个文档带来了新信息吗」。但在实际检索中,后者和前者的贡献一样大。
04 效果
目前知识库里有 1460 个球体,来自 3 个月的技术笔记、对话记录和测试文件。
自动聚类把它们分成了 5 个簇,Silhouette Score 确认这是最优分法。
一条完整检索链路的耗时:
arduino
嵌入 ~2100ms(Ollama nomic-embed-text,本地单次)
簇级路由 ~0ms(质心 cosine,毫秒级)
引力漂移 ~3ms(30 个 probe + 统计)
FAISS 搜索 ~3ms(200 个候选)
质量场融合 ~1ms(关键词匹配 + 加权融合)
四层排序 ~5ms(MMR + 冗余惩罚 + 场域 + 来源)
生成回答 ~4000ms(qwen2.5:7b,本地)
检索本身 15 毫秒,瓶颈全在模型推理上。
05 开源
项目地址:github.com/M-xiaoy/loc...
一键部署:
bash
git clone https://github.com/M-xiaoy/local-kb.git
cd local-kb
pip install -r requirements.txt
ollama pull nomic-embed-text
python -m uvicorn api.main:app --host 0.0.0.0 --port 8765
# 打开 http://localhost:8765
如果你有 DeepSeek API Key,可以设环境变量然后选 DeepSeek 模型来回答:
bash
export DEEPSEEK_API_KEY=sk-xxxx
06 最后
这个项目不是一个「更好的 RAG」。它是一个实验:当你在向量空间中有了重力场这个基础设施,你能不能重写一遍检索的规则?
至少对我来说,答案是可以。
聚类是自动的,簇是自我发现的,质心是天然存在的。在这个几何框架内,BM25、HyDE、RAPTOR、Cross-encoder 这些「额外模块」都有了自己的几何翻译------更轻、更快、不需要额外维护。
它不一定比原版更好。但它证明了一件事:基础设施决定了你能做什么,而你选择做什么决定了你是否需要那些额外的东西。
不过之前的重力空间内容还没有发布在这个平台,感兴趣的可以到GitHub仓库里看哈
项目地址:github.com/M-xiaoy/loc... 欢迎 Star、提 Issue、一起玩
(完)