在上一篇文章中,我们通过微调(Fine-tuning)教会了 AI 如何“说话”。今天,我们要教 AI 如何“读书”——让它能够阅读和理解我们提供的私有文档,并基于这些文档回答问题。

这项技术就是检索增强生成(Retrieval-Augmented Generation, RAG)

与微调不同,RAG 并不改变模型本身的“性格”或“技能”,而是给模型外挂一个“知识库”。当用户提问时,系统会先从这个知识库中检索出最相关的信息,然后把这些信息连同问题一起交给 AI,让它基于给定的材料来组织答案。

这极大地扩展了 AI 的能力,让它能回答关于特定、私有或最新信息的问题。

目标:构建一个基于我博客文章的问答机器人

我们的目标是创建一个 Python 脚本,它可以:

  1. 读取我 content/posts/tech/ 目录下的所有 Markdown 文章。
  2. 将这些文章处理成一个可供检索的知识库。
  3. 接收我的提问,比如“如何微调一个模型?”,并根据我之前写的《AI也能“私人订制”…》那篇文章,给出精准的回答。

第一步:环境准备与工具选择

我们将使用一些强大的开源库来简化开发过程:

  • LangChain: 一个流行的 AI 应用开发框架,它极大地简化了构建 RAG 流程的复杂性。
  • FAISS: 来自 Facebook AI 的高效向量检索引擎。它能帮助我们快速找到与问题最相关的文档片段。
  • SentenceTransformers: 一个用于生成高质量文本嵌入(Embeddings)的库。

首先,安装所有必要的依赖:

pip install langchain langchain_community sentence-transformers faiss-cpu

faiss-cpu 是在CPU上运行的版本。如果你的机器有支持CUDA的NVIDIA显卡,可以安装 faiss-gpu 以获得更好的性能。

第二步:加载和分割文档

首先,我们需要加载我们的知识(也就是所有的 .md 文件),并把它们切分成更小的、易于处理的块(Chunks)。因为一次性把整篇文章丢给模型,效果通常不好。

# rag_bot.py
import os
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

def load_documents(path):
    """加载指定路径下的所有 .md 文件"""
    print(f"正在从 {path} 加载文档...")
    # 我们只加载 .md 文件
    loader = DirectoryLoader(path, glob="**/*.md", loader_cls=TextLoader, loader_kwargs={'encoding': 'utf-8'})
    documents = loader.load()
    print(f"成功加载 {len(documents)} 篇文档。")
    return documents

def split_documents(documents):
    """将文档分割成小块"""
    print("正在分割文档...")
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=800,  # 每个块的最大字符数
        chunk_overlap=100, # 块之间的重叠字符数
    )
    splitted_docs = text_splitter.split_documents(documents)
    print(f"成功将文档分割成 {len(splitted_docs)} 个小块。")
    return splitted_docs

RecursiveCharacterTextSplitter 是一个很智能的分割器,它会尝试按段落、句子等方式来切分文本,以保持语义的完整性。

第三步:创建向量存储(Vector Store)

这是 RAG 的核心。我们需要把分割好的文档块“向量化”——即将文本转换成一串数字(向量),这些数字能代表文本的语义。然后,我们将这些向量存入 FAISS 中,以便后续的检索。

# rag_bot.py (续)
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS

def create_vector_store(splitted_docs, model_name="sentence-transformers/all-MiniLM-L6-v2"):
    """为文档块创建向量存储"""
    print("正在创建向量嵌入并存入 FAISS...")
    # 选择一个嵌入模型
    embeddings = HuggingFaceEmbeddings(model_name=model_name)
    # 使用 FAISS 作为向量数据库
    vector_store = FAISS.from_documents(splitted_docs, embeddings)
    print("向量存储创建成功!")
    return vector_store

all-MiniLM-L6-v2 是一个非常流行的小型嵌入模型,它在性能和速度之间取得了很好的平衡。

第四步:构建问答链(QA Chain)

现在,知识库已经准备好了。我们需要构建一个“链”(Chain),它能把提问、检索、生成答案这几个步骤串联起来。

# rag_bot.py (续)
from langchain.chains import RetrievalQA
from langchain_community.llms import Ollama # 举例,使用本地Ollama模型

def create_qa_chain(vector_store):
    """创建并返回一个问答链"""
    print("正在创建问答链...")
    # 这里我们使用一个本地运行的Ollama模型作为例子
    # 你也可以换成 OpenAI, Anthropic 等任何 LangChain 支持的模型
    llm = Ollama(model="qwen:0.5b") 

    # 从向量存储中创建一个检索器
    retriever = vector_store.as_retriever(search_kwargs={"k": 3}) # k=3表示每次检索返回3个最相关的文档块

    # 创建问答链
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff", # "stuff" 模式会把所有检索到的文档块塞进一个Prompt里
        retriever=retriever,
        return_source_documents=True, # 同时返回源文档,方便溯源
    )
    print("问答链创建成功!")
    return qa_chain

注意:上面的代码使用了 Ollamaqwen:0.5b 模型作为示例,这需要你本地已经安装并运行了 Ollama。你可以很轻松地把它换成 from langchain_openai import ChatOpenAI 等其他模型。

第五步:开始提问!

万事俱备,只欠提问。我们将所有步骤整合起来。

# rag_bot.py (完整脚本)
# ... (前面步骤的代码) ...

def main():
    doc_path = "./content/posts/tech" # 指向你的博客文章目录

    # 完整流程
    documents = load_documents(doc_path)
    splitted_docs = split_documents(documents)
    vector_store = create_vector_store(splitted_docs)
    qa_chain = create_qa_chain(vector_store)

    # 开始交互式问答
    print("\n\n你好!我是你的博客知识库机器人。输入 'exit' 退出。")
    while True:
        question = input("\n请输入你的问题: ")
        if question.lower() == 'exit':
            break
        
        print("正在思考...")
        result = qa_chain.invoke({"query": question})
        
        print("\n答案:")
        print(result["result"])
        
        print("\n参考来源:")
        for doc in result["source_documents"]:
            # 打印源文件名
            print(f"- {doc.metadata.get('source', 'N/A')}")

if __name__ == "__main__":
    main()

现在,运行 python rag_bot.py,然后向它提问:“如何微调一个模型?”。你会惊喜地发现,它会根据你写的那篇关于 Fine-tuning 的文章,给出一个条理清晰的回答,并且还会列出参考的文章来源。

结语

通过 RAG,我们成功地让 AI 成为了一个能够学习和利用我们私有知识的强大助手。这项技术的前景非常广阔,从企业内部知识库、智能客服到个人笔记的智能检索,无处不能看到它的身影。

相比于需要大量数据和计算资源的微调,RAG 的实现门槛更低,效果也更可控。快去用它为你自己的知识库赋能吧!