RAG 实战:从零搭建一个本地知识库问答系统

站长 2026-09-29 0 约 2 分钟 413 字
#Python#RAG#LangChain#向量数据库#Embedding

为什么需要 RAG

大模型有两个天然的短板:

  1. 知识截止:训练数据有截止日期,之后的事它不知道
  2. 不懂私有信息:你公司的内部文档、你的产品手册,它从没见过

把全部文档塞进提示词?不行——一份几百页的手册远超上下文窗口限制,而且费用爆炸。

RAG(检索增强生成) 的思路很朴素:先从文档库里找出相关的几段,再让 AI 基于这几段内容回答。相当于开卷考试——AI 不需要背下整本书,只要会查资料就行。

RAG 工作原理拆解

整个系统分两个阶段:

入库阶段(离线做一次):

文档 → 切分成小块 → 每块算一个向量(Embedding)→ 存进向量数据库

问答阶段(每次提问):

用户问题 → 算问题的向量 → 在向量库里找最相似的几个文档块
→ 把「问题 + 找到的文档块」一起发给大模型 → 生成回答

核心概念只有三个:

  • Embedding(向量嵌入):把一段文字变成一串数字(如 1536 维的向量),语义相近的文字向量距离也近。「猫」和「猫咪」的向量很近,「猫」和「汽车」很远
  • 向量数据库:专门存向量并支持「找最相似」查询的数据库,轻量级的有 Chroma、FAISS
  • 检索召回:根据问题找出最相关的 top-k 个文档块(一般取 3-5 个)

环境准备

pip install langchain langchain-community langchain-text-splitters \
    chromadb sentence-transformers openai

说明:

  • sentence-transformers:本地跑 Embedding,免费且不用调 API
  • chromadb:轻量向量数据库,数据存本地文件,零配置
  • openai:调用大模型(可换成 DeepSeek 等兼容接口,下文以 DeepSeek 为例,便宜)

完整代码(约 60 行,可直接运行)

第一步:文档入库

# build_kb.py —— 把 docs/ 目录下的所有 txt/md 文档存入向量库
from pathlib import Path

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 1. 读取本地文档
docs_dir = Path("docs")  # 把你的 txt/md 文件放这个目录
texts, metadatas = [], []
for f in docs_dir.rglob("*"):
    if f.suffix in (".txt", ".md"):
        texts.append(f.read_text(encoding="utf-8"))
        metadatas.append({"source": f.name})

# 2. 切分文档:每块约 500 字,块间重叠 50 字防止切断语义
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?"]
)
all_chunks = []
for text, meta in zip(texts, metadatas):
    for chunk in splitter.split_text(text):
        all_chunks.append((chunk, meta))

# 3. 加载本地 Embedding 模型(首次运行自动下载,约 400MB)
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")

# 4. 写入向量数据库(持久化到 ./chroma_db 目录)
db = Chroma(
    collection_name="my_kb",
    embedding_function=embeddings,
    persist_directory="./chroma_db",
)
db.add_texts(
    texts=[c for c, _ in all_chunks],
    metadatas=[m for _, m in all_chunks],
)
print(f"入库完成,共 {len(all_chunks)} 个文档块")

运行一次即可,之后问答不需要重新入库。

第二步:问答

# ask.py —— 基于知识库回答问题
from openai import OpenAI
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

# 加载已有的向量库
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")
db = Chroma(
    collection_name="my_kb",
    embedding_function=embeddings,
    persist_directory="./chroma_db",
)

# DeepSeek 的兼容 OpenAI 接口(换成你自己的 key)
client = OpenAI(
    api_key="sk-你的key",
    base_url="https://api.deepseek.com",
)

def ask(question: str) -> str:
    # 1. 检索:找最相关的 4 个文档块
    docs = db.similarity_search(question, k=4)
    context = "\n\n".join(
        f"【来源:{d.metadata.get('source', '未知')}】\n{d.page_content}"
        for d in docs
    )

    # 2. 组装提示词:把检索结果作为参考资料
    prompt = f"""请仅根据下面的参考资料回答问题。
如果资料里没有相关信息,直接回答「知识库中没有相关内容」,不要编造。

【参考资料】
{context}

【问题】{question}

回答时请注明信息来源文件名。"""

    # 3. 调用大模型生成回答
    resp = client.chat.completions.create(
        model="deepseek-chat",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.1,  # 低温减少发挥,让答案更贴资料
    )
    return resp.choices[0].message.content

if __name__ == "__main__":
    while True:
        q = input("\n请输入问题(q 退出):").strip()
        if q.lower() == "q":
            break
        print("\n" + ask(q))

实测效果

把本站的 40 篇 AI 工具介绍文档放进 docs/ 目录入库后:

  • 问「有哪些免费的 AI 绘画工具?」→ 回答准确列出免费工具并注明来源文件
  • 问「Midjourney 多少钱一个月?」→ 从文档里找到价格信息作答
  • 问「怎么申请高新技术企业认定?」→ 如实回答「知识库中没有相关内容」——这正是我们要的效果,宁可说不知道,也不许瞎编

调优方向(效果不满意时看这里)

检索不准 → 调切分策略:chunk_size 太小会切断语义,太大会稀释相关性,500 字左右通常合适;也可以换更强的 Embedding 模型(如 bge-large-zh-v1.5)。

回答啰嗦/跑题 → 调提示词:强调「仅根据参考资料」;把 temperature 降到 0。

召回的内容不全 → 把 k=4 调大到 6-8,注意别超过模型上下文。

想支持 PDF/Word → 用 langchain-community 的文档加载器:

from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader

docs = PyPDFLoader("手册.pdf").load()        # 加载 PDF
docs = Docx2txtLoader("文档.docx").load()    # 加载 Word

生产环境还需要什么

上面的 60 行代码是「能跑的 demo」,真正上线还要考虑:

  • 权限隔离:不同用户只能检索自己有权限的文档(按 metadata 过滤)
  • 增量更新:文档变更后只更新对应块,不要全量重建
  • 混合检索:向量检索 + 关键词检索(BM25)融合,兼顾语义和精确匹配
  • 评估体系:准备一批标准问答对,量化每次调优的效果

但对个人知识库、小团队内部问答场景,本文这套方案已经够用。把代码跑起来,喂给它你的第一批文档吧。

评论 (0)

我的头像

还没有评论,快来抢沙发吧~