从 0 到 1:检索问答 DEMO(含代码)
用最小组件把 RAG 串起来:切分 → 嵌入 → 索引 → 召回 → 重写。 注:本文为“1 月份重点篇”,给出可复制的 DEMO 代码。
🎯 文章目标
- 用最小依赖构建一个可运行的 RAG Demo
- 展示段落切分、嵌入、索引与召回的关键点
- 提供上线前的最小评估清单
📚 背景/前置
- 嵌入模型:选质量稳定、成本可控者(如 bge/multilingual-e5/nomic-embed)
- 向量库:Chroma/Qdrant/Weaviate,本文用 Chroma 演示
- 检索策略:HNSW/Flat;Top-K + 重写/重排
🔧 核心内容
1) 数据预处理与切分
- 语种/编码清洗、去重、去格式噪声
- 段落切分:按句/段 + 滑动窗口,保留上下文
2) 嵌入与索引
python
# requirements: chromadb sentence-transformers openai
import os, glob, chromadb
from sentence_transformers import SentenceTransformer
from openai import OpenAI
EMBED = os.getenv('EMBEDDING_MODEL', 'sentence-transformers/all-MiniLM-L6-v2')
CHAT = os.getenv('CHAT_MODEL', 'qwen2.5-7b-instruct')
cli = OpenAI(base_url=os.getenv('OPENAI_API_BASE'), api_key=os.getenv('OPENAI_API_KEY'))
texts = []
for f in glob.glob('docs/*.md'):
with open(f,'r') as fp:
texts += [t.strip() for t in fp.read().split('\n\n') if len(t.strip())>50]
m = SentenceTransformer(EMBED)
vecs = m.encode(texts).tolist()
chroma = chromadb.Client(); col = chroma.get_or_create_collection('kb')
col.add(ids=[str(i) for i in range(len(texts))], documents=texts, embeddings=vecs)
3) 检索与回答
python
q = '如何给 RAG 做段落切分?'
hits = col.query(query_texts=[q], n_results=5)
ctx = '\n'.join(hits['documents'][0])
msg = [
{"role":"system","content":"仅根据上下文回答,引用出处,若无依据请说不知道"},
{"role":"user","content":f"上下文:\n{ctx}\n\n问题:{q}"}
]
ans = cli.chat.completions.create(model=CHAT, messages=msg)
print(ans.choices[0].message.content)
4) 最小评估清单
- 正确性:抽查 20 个问题,统计“引用+事实一致”比例
- 可执行性:输出格式稳定(JSON/Markdown),可直接被下游解析
- 延迟/成本:记录 tokens 与耗时,观察最慢 5% 请求
💡 实战示例:Node.js 端一键检索
javascript
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: process.env.OPENAI_API_BASE, apiKey: process.env.OPENAI_API_KEY })
const q = 'RAG 的重排有什么常见方式?'
const ctx = 'BM25 + 向量融合;候选重排;窗口策略;'
const messages = [
{ role: 'system', content: '只基于上下文回答,必须给出要点式答案' },
{ role: 'user', content: `上下文:\n\${ctx}\n\n问题:\${q}` }
]
console.log((await client.chat.completions.create({ model: process.env.CHAT_MODEL, messages })).choices[0].message.content)
📊 对比/取舍(速查)
- 召回优先:先把召回做稳,再谈生成“写得好”
- 模型大小:RAG 常优于盲目加大模型
- 指标闭环:以“引用 + 正确率 + 可执行性 + 延迟/成本”组合视角看结果
🧪 踩坑与经验
- 文档更新:增量索引/归档策略,避免“旧知识污染”
- 片段切分:过短会丢上下文,过长会拖慢与引入噪声
- 嵌入漂移:版本更换要回归评测
📎 参考与延伸
- 切分策略与窗口、重排与融合检索
- 向量库与索引结构(HNSW/IVF/Flat)
- RAG 测评方法与在线观测
💭 总结
- 先串通“切分→嵌入→索引→召回→生成”,小步快跑
- 以“可执行性+引用一致性+延迟/成本”衡量是否“能上线”