什么是 RAG
假设现在需要构建一个智能客服,需要解答某个专业领域下的知识,那么,有以下两种方式可以将知识库喂给大模型(LLM):
System Prompt
如果知识量很少,可以在 System Prompt 中直接将该知识给 LLM,这种方式无疑是最简单的。如:
from langchain.prompts import ChatPromptTemplate knowledge = """ 商品保养知识: - 纯棉衣物:冷水手洗,避免暴晒 - 真丝衣物:干洗,不可机洗 退换货政策: - 质量问题:免费退换 - 非质量问题:吊牌完好可退 """ prompt = ChatPromptTemplate.from_messages([ ("system", f"你是一个电商客服助手,请根据以下知识回答问题:\n{knowledge}"), ("human", "用户问题:{question}") ])
RAG
当知识库很大时,直接放在 System Prompt 中可能会导致 token 溢出,导致无法正确回答(就算不溢出,巨长的 Prompt 也会导致 LLM 响应缓慢,影响体验)
此时,可以采用 RAG(检索增强生成),这种方式拆解下来也很简单,主要分为以下几步:
将知识库切片、向量化,放入向量数据库
为什么要切片(Chunking)?更好的建立索引,比如在 b 站有些视频,会将视频切为多段,用户就可以根据自己的需求精准跳转。放到 RAG 也是一样的,RAG 也可以通过切片对用户查询的内容进行准确的区间命中
放入向量数据库时,需要进行 embedding(词嵌入),说通俗一点,就是需要把词语转换为数字形式存储,以便后续能进行向量比对(比如将
苹果
向量化为(1.5, 3, 5.1)
)用户问题向量化
RAG 系统也需要对用户问题进行向量化,然后到向量数据库中进行比对(可以使用一些最近邻算法,具体我会另写一篇文章讨论),比对后选出几个最相近的向量,返回给 LLM
如,用户输入的问题中有
香蕉
,那么 RAG 系统就会将香蕉
也进行 embedding,然后进入某一个切片后的区间,在该区间内与所有向量进行最近邻匹配,找出相似度最高的几个向量并返回(比如通过香蕉
找到了之前定义的苹果
,因为它们都属于水果,所以向量化后可能在某一特征上存在相似)将检索后的结果返回给 LLM
很多人都会错误的以为是 RAG 系统直接将检索结果返回给用户的,其实,大部分场景下,仍然需要 LLM 作为中转再返回给用户(一些场景是例外的,比如豆包的相关视频推荐,我猜测是直接通过 RAG 系统返回结果给用户)
LangChain
通过 LangChain,可以自动化地完成上面的操作,下面是一张 LangChain 的官方流程图
可以看到,流程分为了
Load
、Split
、Embed
、Store
,与上文所说的一致案例
下面,我将用一个简单的例子来完成 LangChain + LLM + RAG 的案例(参考了 Hugging Face 教程)
准备知识库文档
./ecommerce_faq.txt
[商品类目] 服装 - 问题:纯棉衣服会褪色吗? 答案:纯棉衣物首次洗涤可能有轻微浮色,建议单独洗涤,使用冷水手洗 [政策] 退换货 - 问题:退货流程是什么? 答案:1. 提交申请 2. 寄回商品 3. 验货后3个工作日内退款
此处也可以使用 pdf
环境配置
安装所需库
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 链
知识库文档切片
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】元数据:{'[': '商品类目', '-': '问题'} 内容预览:纯棉衣服会褪色吗? 答案:纯棉衣物首次洗涤可能有轻微浮色...
向量化
# 初始化 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
构建 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 # 调试时显示来源 )
测试回答
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 也同样集成了相关方法,只需要填写模型名称即可(也支持填入本地路径)