我构建了一个 RAG 管道。随后我意识到检索才是真正的模型
Source: Dev.to
大家都在谈论 LLM。GPT‑4、Claude、Gemini——它们是明星。但在构建我的第一个真实 RAG 流水线后,我学到了一件令人谦卑的事:LLM 是可互换的部件,检索系统才是真正的工作者。
让我来给你展示一下我的意思。
我们都在复制的四步流水线
- Ingest – 将文档切分为块
- Embed – 将块转化为向量
- Retrieve – 找到前 k 个相似块
- Generate – LLM 使用该上下文生成答案
它能工作。我的机器人可以用引用来回答公司政策问题。我感到很聪明。
然后我问道:“我可以为数字产品退款吗?”
LLM 给出了一个漂亮且自信的答案,但完全错误。因为我的检索返回了一段关于实体退货(30 天,原包装)的内容,完全错过了位于两段之后的数字产品例外。
LLM 完美地完成了它的工作。检索失败了。
为什么检索才是真正的模型
| 你认为重要的 | 实际重要的 |
|---|---|
| 使用的 LLM | 文档切分方式 |
| Prompt 工程 | 嵌入质量 |
| 系统提示 | 检索后的重新排序 |
LLM 只负责格式化答案。检索决定答案是否真实。
修复我的流水线的代码
语义搜索单独使用时会遗漏像 “non‑refundable after download” 这样的精确短语。关键词搜索单独使用时会忽略语义。混合搜索将两者结合。下面是核心实现(使用 FAISS + BM25):
from sentence_transformers import SentenceTransformer
import faiss, numpy as np
from rank_bm25 import BM25Okapi
# 1. Load documents and embed
docs = [
"Refund within 30 days, physical items only.",
"Digital products: non-refundable after download.",
"Contact support for defective digital items."
]
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(docs)
index = faiss.IndexFlatL2(embeddings.shape[1])
index.add(np.array(embeddings, dtype='float32'))
# 2. BM25 keyword index (tokenized)
tokenized_docs = [doc.lower().split() for doc in docs]
bm25 = BM25Okapi(tokenized_docs)
# 3. Hybrid search function
def hybrid_search(query, top_k=2, alpha=0.5):
# Semantic score (distance -> similarity)
query_vec = model.encode([query])
distances, indices = index.search(query_vec, top_k)
semantic_scores = 1 / (1 + distances[0])
# Keyword score
query_tokens = query.lower().split()
bm25_scores = bm25.get_scores(query_tokens)
top_bm25_idx = np.argsort(bm25_scores)[-top_k:][::-1]
keyword_scores = [bm25_scores[i] for i in top_bm25_idx]
# Combine (normalized)
combined = {}
for i, idx in enumerate(indices[0]):
combined[idx] = alpha * semantic_scores[i]
for i, idx in enumerate(top_bm25_idx):
combined[idx] = combined.get(idx, 0) + (1 - alpha) * (keyword_scores[i] / max(keyword_scores))
return sorted(combined.items(), key=lambda x: x[1], reverse=True)[:top_k]
# 4. Test
query = "Can I get my money back for a digital product?"
results = hybrid_search(query)
for idx, score in results:
print(f"Score: {score:.2f} | {docs[idx]}")
# Output: Score: 0.92 | Digital products: non-refundable after download.alpha=0.5 使意义和精确措辞之间取得平衡。若不使用混合搜索,数字产品的那段内容会被忽略;使用后,它会作为最高结果出现。
让我的流水线提升 10 倍的三项改动
- 块大小不是默认值 – 改为使用重叠块(200 个 token,重叠 50 个 token)。
- 仅使用语义搜索会误导 – 添加了 BM25 混合搜索(见上方代码)。
- 重新排序改变了一切 – 使用小型交叉编码器对前 10 个块重新打分,使准确率从 72 % 提升到 91 %。
大多数人犯的错误
我们把 RAG 当作 LLM 问题来处理。因此我们会调整提示、切换模型、添加系统指令。
但 LLM 被迫 使用你提供的任何上下文。如果你喂给它错误的片段,它会自信地产生幻觉。如果你喂给它正确的片段,即使是小模型也能正确回答。
瓶颈几乎从来不是 LLM,而是检索器。
我现在的不同做法
在编写任何一行代理代码之前,我会先问三个问题:
- “如果我手动搜索我的向量数据库,我会找到准确回答此问题的句子吗?”
- “我的检索是否同时适用于同义词 and 精确关键词?” → 如果没有,使用混合搜索。
- “检索到的排名第一的片段真的最优吗?” → 如果没有,添加重新排序器。
结论
AI 产业向你推销模型。但在生产环境的 RAG 系统中,模型是最便宜、最容易替换的组件。难点——也是区分真正可用机器人和演示软件的关键——在于把正确的信息放入上下文窗口。
LLM 是笔。检索是记忆。而记忆让系统变得有用。
所以,下次你的 RAG 机器人出错时,别怪 GPT。检查一下你检索到了什么。真正的问题就出在这里。