提示:RAG、检索增强生成、知识库问答、FAISS、Milvus、bge 嵌入、重排、RAGAS、FastAPI、OpenAI 兼容
文章目录
前言
提示:
- 用 Python 做一个会先查资料再回答的中文问答系统,几步就能跑起来。
- 我把怎么找资料、怎么排序、怎么生成答案、怎么评测好不好都写成了能直接用的代码。
- 还给了上线时常见的坑和优化方法,帮你解决查不准、瞎编、速度慢这些问题。
你将收获到
- 一个能直接部署的中文知识库问答系统(把你自己的文档放进去就能问)。
- 选型不纠结:用哪个嵌入模型、哪个向量库、要不要重排、怎么接大模型。
- 一键评测:用 RAGAS 量化回答质量,知道哪里需要改。
- 生产经验:稳定性、速度、成本怎么平衡,常见问题怎么排查。
适用人群
-
想把公司文档/FAQ 变成能问能答的小助手。
-
已经有 LLM Demo,想真正在项目里落地的开发者。
提示:以下是本篇文章正文内容,下面案例可供参考
一、为什么选RAG,而不选 微调
- 说白了微调就类似于“强行让模型背书”。贵、慢都不计较了,但是内容一变就得再背。
- RAG就显得比较优雅,就类似于“先翻你给的资料再进行回答”。你改文档答案就跟着变,还能标注出处;
好处就不言而喻了,便宜,更新快,不瞎编,能追溯来源,便于审核。
二、这系统长什么样呢?
- 你把文档放到 data/ 里(比如 .md/.txt)。
- 我们把文档切成小段,转成向量,存到 FAISS(向量索引)。
- 用户提问时:先“召回”相关段落(向量检索 + BM25),再“重排”(更准),最后让大模型“只根据这些段落”组织一个靠谱的答案,并在结尾标注来源编号。
- 想象一下它的步骤:
- 嵌入模型:BAAI/bge-small-zh-v1.5(中文友好,够轻)
- 向量库:本地用 FAISS,生产可换 Milvus/PGVector
- 重排:BAAI/bge-reranker-base,把前几条按相关度再洗一遍
- 生成:用 OpenAI 兼容 API(OpenAI/DeepSeek/智谱等都行)
- 评测:RAGAS,量化“靠谱度、相关性、上下文命中”
三、快速上手 Demo(完整代码直接照做)
1、安装依赖
pip install fastapi uvicorn sentence-transformers faiss-cpu rank-bm25 openai tiktoken pydantic ragas datasets
2、创建目录
rag-demo/
├─ data/ # 你的文档
├─ index/ # 生成的索引
├─ ingest.py # 文档切分+嵌入+建索引
├─ rag_service.py # 服务:检索->重排->生成
└─ evaluate_rag.py # 评测(RAGAS)
3、实现 ingest.py
import os
import glob
import pickle
from typing import List, Dict
from sentence_transformers import SentenceTransformer
import faiss
CHUNK_SIZE = 500
CHUNK_OVERLAP = 100
EMBEDDING_MODEL = "BAAI/bge-small-zh-v1.5"
INDEX_DIR = "index"
DATA_DIR = "data"
def read_files(data_dir: str) -> List[Dict]:
files = glob.glob(os.path.join(data_dir, "**", "*.*"), recursive=True)
docs = []
for f in files:
if f.lower().endswith((".md", ".txt")):
with open(f, "r", encoding="utf-8", errors="ignore") as rf:
docs.append({
"path": f, "text": rf.read()})
return docs
def split_text(text: str, chunk_size: int, overlap: int) -> List[str]:
chunks = []
start = 0
while start < len(text):
end = min(len(text), start + chunk_size)
chunks.append(text[start:end])
start = end - overlap
if start < 0:
start = 0
if start >= len(text):
break
return chunks
def build_corpus(docs: List[Dict]) -> List[Dict]:
corpus = []
for d in docs:
for c in split_text(d["text"], CHUNK_SIZE, CHUNK_OVERLAP):
corpus.append({