什么是 RAG

假设现在需要构建一个智能客服,需要解答某个专业领域下的知识,那么,有以下两种方式可以将知识库喂给大模型(LLM):

  • System Prompt

    如果知识量很少,可以在 System Prompt 中直接将该知识给 LLM,这种方式无疑是最简单的。如:

    from langchain.prompts import ChatPromptTemplate
    
    knowledge = """
    商品保养知识:
    - 纯棉衣物:冷水手洗,避免暴晒
    - 真丝衣物:干洗,不可机洗
    退换货政策:
    - 质量问题:免费退换
    - 非质量问题:吊牌完好可退
    """
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", f"你是一个电商客服助手,请根据以下知识回答问题:\n{knowledge}"),
        ("human", "用户问题:{question}")
    ])
  • RAG

    RAG 流程图
    RAG 流程图

    当知识库很大时,直接放在 System Prompt 中可能会导致 token 溢出,导致无法正确回答(就算不溢出,巨长的 Prompt 也会导致 LLM 响应缓慢,影响体验)

    此时,可以采用 RAG(检索增强生成),这种方式拆解下来也很简单,主要分为以下几步:

    1. 将知识库切片、向量化,放入向量数据库

      为什么要切片(Chunking)?更好的建立索引,比如在 b 站有些视频,会将视频切为多段,用户就可以根据自己的需求精准跳转。放到 RAG 也是一样的,RAG 也可以通过切片对用户查询的内容进行准确的区间命中

      放入向量数据库时,需要进行 embedding(词嵌入),说通俗一点,就是需要把词语转换为数字形式存储,以便后续能进行向量比对(比如将 苹果 向量化为 (1.5, 3, 5.1)

    2. 用户问题向量化

      RAG 系统也需要对用户问题进行向量化,然后到向量数据库中进行比对(可以使用一些最近邻算法,具体我会另写一篇文章讨论),比对后选出几个最相近的向量,返回给 LLM

      如,用户输入的问题中有 香蕉,那么 RAG 系统就会将 香蕉 也进行 embedding,然后进入某一个切片后的区间,在该区间内与所有向量进行最近邻匹配,找出相似度最高的几个向量并返回(比如通过 香蕉 找到了之前定义的 苹果,因为它们都属于水果,所以向量化后可能在某一特征上存在相似)

    3. 将检索后的结果返回给 LLM

      很多人都会错误的以为是 RAG 系统直接将检索结果返回给用户的,其实,大部分场景下,仍然需要 LLM 作为中转再返回给用户(一些场景是例外的,比如豆包的相关视频推荐,我猜测是直接通过 RAG 系统返回结果给用户)

    LangChain

    通过 LangChain,可以自动化地完成上面的操作,下面是一张 LangChain 的官方流程图

    LangChain 流程图
    LangChain 流程图

    可以看到,流程分为了 LoadSplitEmbedStore,与上文所说的一致

    案例

    下面,我将用一个简单的例子来完成 LangChain + LLM + RAG 的案例(参考了 Hugging Face 教程)

    1. 准备知识库文档 ./ecommerce_faq.txt

      [商品类目] 服装
      - 问题:纯棉衣服会褪色吗?
        答案:纯棉衣物首次洗涤可能有轻微浮色,建议单独洗涤,使用冷水手洗
      
      [政策] 退换货
      - 问题:退货流程是什么?
        答案:1. 提交申请 2. 寄回商品 3. 验货后3个工作日内退款

      此处也可以使用 pdf

    2. 环境配置

      • 安装所需库

        pip install langchain openai chromadb tiktoken python-dotenv
      • 配置 api-key

        from dotenv import load_dotenv
        import os
        
        # 加载环境变量(在项目根目录创建 .env 文件存放 API_KEY)
        load_dotenv()
      • 模块导入

        from langchain.document_loaders import TextLoader  # 文档加载
        from langchain.text_splitter import MarkdownHeaderTextSplitter  # 电商文档分块
        from langchain.embeddings import OpenAIEmbeddings  # 文本向量化
        from langchain.vectorstores import Chroma  # 向量数据库,也可以选用 Milvus 等
        from langchain.chat_models import ChatOpenAI  # 聊天模型
        from langchain.chains import RetrievalQA  # RAG 链
    3. 知识库文档切片

      loader = TextLoader("./ecommerce_faq.txt")
      documents = loader.load()  # 加载文档
      
      # 初始化电商特化的切片器(按 Markdown 标题分割)
      text_splitter = MarkdownHeaderTextSplitter(
          headers_to_split_on=[
              ("[", "商品类目"),  # 识别'[商品类目]'标题
              ("-", "问题")      # 识别'- 问题'子标题
          ],
          strip_headers=False  # 保留标题文本
      )
      
      # 执行文档切片
      texts = text_splitter.split_text(documents[0].page_content)
      
      # 打印结果
      print(f"\n生成的文本块数量:{len(texts)}")
      for i, text in enumerate(texts[:2]):  # 仅展示前 2 块
          print(f"【块{i+1}】元数据:{text.metadata}")
          print(f"内容预览:{text.page_content[:60]}...\n")

      此时输出:

      生成的文本块数量:5
      【块1】元数据:{'[': '商品类目', '-': '问题'}
      内容预览:纯棉衣服会褪色吗?
        答案:纯棉衣物首次洗涤可能有轻微浮色...
    4. 向量化

      # 初始化 OpenAI 的嵌入模型,也可以选用别的词嵌入模型,比如通义的
      embeddings = OpenAIEmbeddings(
          model="text-embedding-3-small",
          chunk_size=512  # 切片尺寸
      )
      
      # 模拟一个示例文本的向量化过程
      sample_text = "纯棉衣物退换货政策"
      sample_vector = embeddings.embed_query(sample_text)  # 生成向量
      
      print(f"\n向量化模拟演示(前5维):")
      print(f"文本:'{sample_text}'")
      print(f"向量:{sample_vector[:5]}...")  # 只打印前 5 维
      print(f"向量长度:{len(sample_vector)}维")
      
      # 正式向量化所有切片
      vectorstore = Chroma.from_documents(
          documents=texts,
          embedding=embeddings,
          persist_directory="./chroma_db",  # 持久化存储路径
          collection_metadata={
              "description": "电商客服知识库",  # 添加业务标识
              "scene": "ecommerce"
          }
      )
      
      # 打印向量库详情
      print(f"\n向量库详情:")
      print(f"- 切片数量:{vectorstore._collection.count()}")
      print(f"- 向量维度:{len(sample_vector)}维")  # 实际维度
      print(f"- 存储路径:{os.path.abspath('./chroma_db')}")

      此时输出:

      向量化模拟演示(前5维):
      文本:'纯棉衣物退换货政策'
      向量:[0.12, -0.05, 0.31, -0.44, 0.27]...
      向量长度:1536维
      
      向量库详情:
      - 切片数量:47
      - 向量维度:1536维
      - 存储路径:/projects/ecommerce/chroma_db
    5. 构建 RAG 链

      qa_chain = RetrievalQA.from_chain_type(
          llm=llm,
          retriever=vectorstore.as_retriever(
              search_type="similarity_score_threshold",  # 带相似度阈值
              search_kwargs={
                  "k": 3,  # 返回 3 个最相关片段
                  "score_threshold": 0.65  # 电商场景需要较高匹配精度
              }
          ),
          chain_type="stuff",
          return_source_documents=True  # 调试时显示来源
      )
    6. 测试回答

      test_questions = [
          "纯棉衣服怎么保养?",
          "退货的运费谁承担?",
          "订单多久能发货?"
      ]
      
      for question in test_questions:
          print(f"\n用户提问:{question}")
          
          # 获取回答和参考来源
          result = qa_chain({"query": question})
          
          print(f"客服回答:{result['result']}")
          print(f"参考来源:{result['source_documents'][0].page_content[:60]}...")

      此时输出:

      用户提问:纯棉衣服怎么保养?
      客服回答:亲,纯棉衣物建议冷水手洗,避免暴晒...
      参考来源:纯棉衣服会褪色吗?
        答案:纯棉衣物首次洗涤可能有轻微浮色...

    Hugging Face 模型

    如果不想调 LLM 接口,想使用 Hugging Face 或者本地的模型,LangChain 也同样集成了相关方法,只需要填写模型名称即可(也支持填入本地路径)

    详情参考:ChatHuggingFace | LangChain中文网