欢迎光临
我们一直在努力

从 0 到 1 搭建一个能用的 RAG 知识库:分块、向量化与向量库实战

一、为什么这一篇很重要?

在系列一里,我们把整条技术栈的地图画了一遍:

知识库 RAG(底座) → MCP(连接外部系统) → Agent Skills(封装业务能力)

但那篇更多是「概念盘点 + 技术路线」,很多人看完会有三个典型疑问:

  • 讲了这么多 RAG,到底怎么从 0 跑起来一个能用的知识库?​
  • 文档分块、向量化、向量库这些细节,有一份可以直接抄的工程模板吗?​
  • 以后我想接上 Skills / MCP,现在这套 RAG 还用得上吗,还是要推翻重来?​
  • 这一篇就只做一件事:

    给你一套「最小可用 RAG 知识库」的完整实现:
    从文档加载 → 清洗与分块 → 向量化 → 向量库 → 简单问答,全打通。

    风格上我会尽量延续第一篇那种节奏:

    • 前面先用表格把关键选型说清楚;
    • 中间给你一份可以直接运行的最小示例代码;
    • 后半部分再拆解细节 + 踩坑建议,让你可以慢慢迭代成生产可用版本。

    二、整体方案先看清:这一篇我们只关注哪一段?

    先把本篇的技术范围圈出来,避免「一篇文章想干完所有事」:

    graph LR
    A[原始文档/PDF/Word/Markdown] –> B[解析 + 基础清洗]
    B –> C[分块 Chunking]
    C –> D[文本向量化 Embedding]
    D –> E[向量数据库存储]
    F[用户 Query] –> G[Query 向量化]
    G –> H[相似度检索 TopK]
    H –> I[拼接上下文 + Prompt]
    I –> J[LLM 生成回答]

    这一篇重点只在粗体部分:

    • 文档加载 & 清洗
    • 分块(Chunking)策略
    • 向量化(Embedding 模型选型)
    • 向量库存储与检索
    • 串成一个最小 RAG 问答链

    MCP / Agent Skills 在后面篇章,暂时不掺进来,免得心智负担太大。


    三、核心选型一张表说清(2025–2026 这波比较稳的组合)

    3.1 技术选型总览

    环节推荐方案说明
    编程语言 Python 3.10+ 生态成熟,教程资源多
    框架 LangChain / langchain-community Loader / Splitter / VectorStore 封装齐全
    向量模型 BAAI/bge-m3 免费、多语言,中文表现好,RAG 新主流
    向量库(入门) FAISS 内存型,本地 PoC 非常好用
    向量库(生产) Milvus / pgvector 可持久化、可扩展,适合企业环境
    LLM 任意对话模型(OpenAI、DeepSeek、本地 LLM) 本篇不深挖,只负责「读上下文 + 回答」

    3.2 分块策略对比表

    策略类型推荐 chunk_sizechunk_overlap适用文档特点
    字符递归分块 400–800 字符 80–150 常规说明文、FAQ、教程文档 实现简单,通用性最强
    Markdown 标题分块 按标题切后再 400–800 50–100 有明确层级结构的技术文档、规范 保留章节结构,更贴近人类阅读
    Token 分块 256–512 token 50–80 对上下文窗口比较敏感的模型 更贴近实际 token 限制

    3.3 Embedding 模型快速对比

    模型语言支持开源/付费适用场景
    BAAI/bge-m3 多语言(含中文) 开源免费 中文为主、多语言混合的 RAG 场景
    m3e 系列 中文 开源免费 纯中文业务文档
    text-embedding-ada-002 英文偏好 付费 英文内容为主 + 已在用 OpenAI 生态

    如果你目前没太多约束,直接上 BGE-M3 是 2025–2026 比较保险的选型。


    四、先给你一份「能跑起来」的最小 Demo

    这一节只做一件事:

    让你 10 分钟内起一个 RAG 知识库,对自己的几份文档做问答。

    4.1 目录结构

    rag-demo/
    ├── data/ # 放你的 PDF / DOCX / MD 文档
    │ ├── doc1.pdf
    │ └── spec.md
    └── rag_minimal.py # 单文件最小 Demo

    4.2 安装依赖

    pip install langchain langchain-community
    pip install pypdf python-docx unstructured markdown
    pip install sentence-transformers
    pip install faiss-cpu

    你如果要用 OpenAI 模型,还需:

    pip install openai
    # 或者新版的 openai>=1.x 已经集成 ChatCompletion,下面代码会说明

    4.3 最小可运行代码(单文件版)

    # rag_minimal.py
    import os
    from langchain_community.document_loaders import (
    PyPDFLoader,
    Docx2txtLoader,
    UnstructuredMarkdownLoader,
    )
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain_community.embeddings import HuggingFaceEmbeddings
    from langchain_community.vectorstores import FAISS
    from langchain.chains import RetrievalQA
    from langchain_community.chat_models import ChatOpenAI # 兼容 openai 新版

    DATA_DIR = "./data"

    def load_docs(data_dir: str):
    """多格式文档加载:pdf / docx / md"""
    docs = []
    for fname in os.listdir(data_dir):
    fpath = os.path.join(data_dir, fname)
    if not os.path.isfile(fpath):
    continue

    if fname.lower().endswith(".pdf"):
    loader = PyPDFLoader(fpath)
    elif fname.lower().endswith(".docx"):
    loader = Docx2txtLoader(fpath)
    elif fname.lower().endswith(".md"):
    loader = UnstructuredMarkdownLoader(fpath)
    else:
    continue

    file_docs = loader.load()
    for d in file_docs:
    d.metadata["source"] = fname
    docs.extend(file_docs)

    return docs

    def build_rag():
    # 1. 文档加载
    docs = load_docs(DATA_DIR)
    print(f"[INFO] 加载原始文档片段: {len(docs)}")

    # 2. 分块
    splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=100,
    separators=["\\n\\n", "\\n", "。", "!", "?", ",", " "],
    )
    chunks = splitter.split_documents(docs)
    print(f"[INFO] 分块后总块数: {len(chunks)}")

    # 3. 向量化
    embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-m3",
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True},
    )

    # 4. 构建向量库(FAISS)
    vectordb = FAISS.from_documents(chunks, embeddings)
    retriever = vectordb.as_retriever(search_kwargs={"k": 5})

    # 5. 问答链
    llm = ChatOpenAI(
    model="gpt-4o-mini", # 或者你常用的模型名
    temperature=0,
    # 如果用 OpenAI,需要设置环境变量 OPENAI_API_KEY
    )

    qa = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=retriever,
    return_source_documents=True,
    chain_type="stuff", # 简单拼接上下文
    )
    return qa

    def main():
    qa = build_rag()

    while True:
    query = input("\\n请输入你的问题(q 退出):").strip()
    if query.lower() in {"q", "quit", "exit"}:
    break

    result = qa.invoke({"query": query})
    print("\\n[回答]")
    print(result["result"])

    print("\\n[参考片段]")
    for i, d in enumerate(result["source_documents"], 1):
    print(f"— 片段 {i} (来自 {d.metadata.get('source')})—")
    print(d.page_content[:120].replace("\\n", " ") + "…\\n")

    if __name__ == "__main__":
    main()

    放几份你自己的 PDF/Word/Markdown 到 data/ 目录下,运行:

    python rag_minimal.py

    终端里直接问:

    • 「这份规范里 API 认证流程是怎么定义的?」
    • 「XXX 系统出问题时,官方推荐的排查步骤是什么?」

    你就已经有了一个完全基于你文档的问答机器人。

    接下来才是这一篇真正的价值:把这个最小 Demo 拆开讲透。​


    五、分块(Chunking):RAG 里最容易被忽视、又最影响效果的一步

    5.1 目标是「语义自洽」

    一段好的 chunk 应该满足两点:

  • 长度适中:放进 LLM 的上下文不会太大,但信息又足够完整;
  • 语义自洽:哪怕单独拿出这一块给人看,也大概能明白在讲什么。
  • 经验值(结合中文场景):

    文档类型建议 chunk_sizechunk_overlap说明
    FAQ / 问答手册 300–500 字符 50–100 一个问答对通常不长
    技术规范 / API 文档 500–800 字符 100–150 一个接口说明或一小节规范
    长报告 / 案例分析 800–1200 字符 200–300 一个完整的小节或案例片段

    如果你不想一开始搞太多策略,上一篇 Demo 里那套 RecursiveCharacterTextSplitter 对大多数情况已经够用。

    5.2 Markdown 标题感知分块(进阶一点)

    技术文档、规范文档往往有清晰的标题层级。用 Markdown 头部 + 二次分块,会比单纯按字符分块更「聪明」。

    示例代码:

    from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
    from langchain.schema import Document

    headers_to_split_on = [
    ("#", "一级标题"),
    ("##", "二级标题"),
    ("###", "三级标题"),
    ]

    md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
    char_splitter = RecursiveCharacterTextSplitter(
    chunk_size=700,
    chunk_overlap=150,
    separators=["\\n\\n", "\\n", "。", "!", "?", ",", " "],
    )

    def split_markdown_doc(doc: Document):
    """针对 Markdown 文档的两级分块"""
    md_docs = md_splitter.split_text(doc.page_content)
    wrapped_docs = [
    Document(
    page_content=d["content"],
    metadata={**doc.metadata, **d["metadata"]},
    )
    for d in md_docs
    ]
    return char_splitter.split_documents(wrapped_docs)

    你可以在实际工程里:

    • 对 filetype == md 的文档,用这套策略;
    • 对其他类型文档,用普通字符分块。

    六、向量化(Embedding):为什么大家都推 BGE-M3?

    BGE-M3 在 RAG 圈子里火起来,是因为它同时满足了几个现实需求:

  • 多语言,包括中文:不用再为「中文向量质量」单独找模型;
  • 支持稠密 / 稀疏 / 多向量:后面做混合检索、精排都有空间;
  • 开源免费,社区生态成熟:教程、博客、实践案例很多,踩坑成本低。
  • 在本篇这种「最小可用」场景,我们只用它的稠密向量能力就够了:

    from langchain_community.embeddings import HuggingFaceEmbeddings

    embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-m3",
    model_kwargs={"device": "cpu"}, # 有 GPU 可以改成 "cuda"
    encode_kwargs={"normalize_embeddings": True},
    )

    后面你想做「混合检索」「多向量」之类的进阶玩法,换用原生 FlagEmbedding 库也不迟。


    七、向量库:FAISS 入门,Milvus/pgvector 升级

    你可以简单按「规模 + 运维成本」选:

    规模 / 阶段推荐向量库特点
    个人 PoC / 小项目 FAISS 纯内存,部署简单,Python 里直接用
    小团队 / 内部 Demo Chroma 简单好用,配 LangChain 很方便
    企业内网 / 生产 Milvus 专门的向量数据库,扩展性好
    已有 Postgres 集群 pgvector 直接在 PG 上扩展,易于接入运维

    本篇 Demo 用的是 FAISS,原因很简单:

    • 你不需要再多部署一个服务;
    • 可移植性很好,后续替换成 Milvus/pgvector,只要改一小段封装代码。

    八、调优 & 踩坑:从「能跑」到「能用」

    这里挑几个你大概率会遇到的问题,给出直接可执行的调整策略。

    8.1 问:为什么模型总在说「从提供的文档看不出来」?

    可能原因:

  • 检索结果确实没包含有用信息

    • 检查你的 data/ 目录里文档是不是太少、不完整;
    • 检查分块大小,是否把一整段逻辑拆得太碎,导致检索不到完整上下文。
  • top_k 太小

    • 默认 k=3~5;可以尝试从 3 调到 5 或 8,看回答质量是否提升。
  • Embedding 模型没加载成功/在瞎跑

    • 打印一下向量维度(bge-m3 默认 1024 维),确认是不是都为 0 或形状异常。
  • 8.2 问:模型「答非所问」、看起来在乱编?

    建议一步步排查:

  • 打印检索到的 source_documents 内容(上面 Demo 已经在打印了),看看:

    • 文档内容是否真的和问题相关;
    • 是否包含了关键细节。
  • 在 Prompt 里增加更强约束,例如:

  • from langchain.chains import RetrievalQA
    from langchain.prompts import PromptTemplate

    prompt_template = """
    你是一个知识库问答助手,只能根据提供的“文档片段”回答问题。

    如果文档中没有相关信息,请明确回答:
    “文档中没有提到相关内容,我无法确定。”

    # 文档内容:
    {context}

    # 用户问题:
    {question}

    请用中文回答,尽量引用文档中的关键信息。
    """

    PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"],
    )

    qa = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=retriever,
    return_source_documents=True,
    chain_type="stuff",
    chain_type_kwargs={"prompt": PROMPT},
    )

  • 对较复杂问题,可以适度提高 k,但不要一次塞太多上下文,避免「信息稀释」。
  • 8.3 问:RAG 结果不稳定,以后接 Skills/MCP 会不会更乱?

    不会,只要你现在把底层这几件事做扎实:

    • 分块粒度适中,chunk 有明确语义;
    • 向量模型选得对,至少能找到相关片段;
    • 向量库封装干净,retriever 接口稳定。

    后面 Skills/MCP 其实都是:

    • 在更高层面调度「检索 → 决策 → 工具调用」;
    • 用 Skills 把「怎么问知识库」「拿到结果后怎么处理」这些流程固化下来。

    也就是说,这一篇打的是整个系列的「工程地基」​,地基稳了,你后面怎么加技能、加工具,都只是换上层建筑而已。


    九、本篇小结 

    到这里,这一篇你应该已经:

    • 有一份可以直接运行的 RAG Demo(rag_minimal.py + data/ 目录);
    • 理解了分块策略、Embedding 选型、向量库选型背后的取舍逻辑;
    • 知道了常见坑(分块、top_k、Prompt 约束)该怎么快速调整。

    用一句话概括这一篇的定位:

    从「会调 LLM API」升级到「会搭一个能用的 RAG 知识库」。​

    赞(0)
    未经允许不得转载:171主机测评 » 从 0 到 1 搭建一个能用的 RAG 知识库:分块、向量化与向量库实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址