Skip to content

基于langchain RAG问答应用实战

来源:AiGC面试宝典 作者:宁静致远 日期:2024年02月08日


一、前言

1.1 介绍

本次选用百度百科——藜麦数据(https://baike.baidu.com/item/藜麦/5843874)模拟个人或企业私域数据,并基于langchain开发框架,实现一种简单的RAG问答应用示例。

📝通俗解释:RAG(Retrieval-Augmented Generation,检索增强生成)是一种让AI回答问题更准确的技术。简单来说,就是先从你的资料库里找到相关内容,然后把找到的内容和问题一起交给AI,让AI根据这些真实资料来回答,而不是凭空编造。这就像考试时允许翻书查资料一样。

1.2 软件资源

  • CUDA 11.7
  • Python 3.10
  • pytorch 1.13.1+cu117
  • langchain

二、环境搭建

2.1 下载代码

bash
$ git clone https://github.com/xxx/rag-demo.git
$ cd rag-demo

📝通俗解释:这里的代码是演示项目,实际使用时需要从GitHub等平台下载对应的RAG示例代码。

2.2 构建环境

bash
$ conda create -n py310_chat python=3.10        # 创建新环境
$ source activate py310_chat                    # 激活环境

📝通俗解释:使用conda创建独立的Python环境,避免和其他项目冲突。类似于给这个项目分配一个独立的"工作房间"。

2.3 安装依赖

bash
$ pip install datasets langchain sentence_transformers tqdm chromadb langchain_wenxin

📝通俗解释:这些是开发RAG应用需要的核心库:

  • langchain:AI应用开发框架
  • sentence_transformers:用于把文字转换成向量
  • chromadb:向量数据库,用来存储和搜索文本的"数字指纹"
  • langchain_wenxin:百度文心一言的接口

三、RAG问答应用实战

3.1 数据准备

将藜麦数据(https://baike.baidu.com/item/藜麦/5843874)保存到 藜麦.txt 文件中。

📝通俗解释:这就是你的"知识库",RAG系统会从这个文件里查找答案。实际应用中可以是PDF、Word、网页等各种格式的文档。

3.2 本地数据加载

python
from langchain.document_loaders import TextLoader
loader = TextLoader("./藜麦.txt")
documents = loader.load()
documents

输出示例:

[Document(page_content='藜(读音lí)麦(Chenopodium quinoa Willd.)是藜科藜属植物。穗部可呈红、紫、黄,植株形状类似灰灰菜,成熟后穗部类似高粱穗。植株大小受环境及遗传因素影响较大,从0.3-3米不等...', metadata={'source': './藜麦.txt'})]

📝通俗解释:TextLoader负责读取文本文件,把文件内容转换成Document对象。每个Document包含page_content(实际内容)和metadata(来源信息)。

3.3 文档分割

文档分割,借助langchain的字符分割器,这里采用固定字符长度分割chunk_size=128。

python
# 文档分割
from langchain.text_splitter import CharacterTextSplitter

# 创建拆分器,chunk_size=128表示每128个字符分成一段
text_splitter = CharacterTextSplitter(chunk_size=128, chunk_overlap=0)
# 拆分文档
documents = text_splitter.split_documents(documents)
documents

📝通俗解释:为什么要分割文档?因为AI每次只能处理有限长度的内容。把长文档切成小块后,可以更精准地找到相关内容。就像把一本书拆成一页页的,这样找答案时只翻相关的那几页,而不是整本书都翻一遍。

3.4 向量化&数据入库

接下来对分割后的数据进行embedding(向量化),并写入数据库。这里选用m3e-base作为embedding模型,向量数据库选用Chroma。

python
from langchain.embeddings import HuggingFaceBgeEmbeddings
from langchain.vectorstores import Chroma

# embedding model: m3e-base
model_name = "moka-ai/m3e-base"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
embedding = HuggingFaceBgeEmbeddings(
    model_name=model_name,
    model_kwargs=model_kwargs,
    encode_kwargs=encode_kwargs,
    query_instruction="为文本生成向量表示用于文本检索"
)

# 将数据加载到Chroma向量数据库
db = Chroma.from_documents(documents, embedding)

# 测试相似度搜索
db.similarity_search("藜麦一般在几月播种?")

📝通俗解释:

  • 向量化(Embedding):把文字转换成一串数字(向量)。相似的文字会有相似的向量,这样就可以用数学方法找到"相似的内容"。
  • 向量数据库:专门用来存储和搜索向量的数据库,查询速度非常快。
  • m3e-base:一个开源的中文Embedding模型,负责把中文文本转换成向量。

3.5 Prompt设计

Prompt设计,这里只是一个prompt的简单示意,在实际业务场景中需要针对场景特点针对性调优。

python
template = '''
【任务描述】
请根据用户输入的上下文回答问题,并遵守回答要求。

【背景知识】
{{context}}

【回答要求】
- 你需要严格根据背景知识的内容回答,禁止根据常识和已知信息回答问题。
- 对于不知道的信息,直接回答"未找到相关答案"
----------
{question}
'''

📝通俗解释:Prompt就是给AI的"指令模板"。这里的会被替换成从知识库检索到的相关内容,{question}是用户的问题。这样AI就能"看着资料"回答问题了。

3.6 RetrievalQA Chain构建

这里采用ConversationalRetrievalChain,支持多轮对话。

python
from langchain import LLMChain
from langchain_wenxin.llms import Wenxin
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate

# LLM选型:使用百度文心一言
llm = Wenxin(model="ernie-bot", baidu_api_key="your_api_key", baidu_secret_key="your_secret_key")

# 获取检索器
retriever = db.as_retriever()

# 创建对话记忆,保存聊天历史
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 构建ConversationalRetrievalChain
qa = ConversationalRetrievalChain.from_llm(llm, retriever, memory=memory)

# 测试问答
qa({"question": "藜麦怎么防治虫害?"})

输出示例:

python
{'question': '藜麦怎么防治虫害?',
 'chat_history': [HumanMessage(content='藜麦怎么防治虫害?'),
  AIMessage(content='藜麦常见虫害有象甲虫、金针虫、蝼蛄、黄条跳甲、横纹菜蝽、蒿蓄齿胫叶甲、潜叶蝇、蚜虫、夜蛾等。防治方法:可每亩用3%的辛硫磷颗粒剂2-2.5千克于耕地前均匀撒施,随耕地翻入土中。也可以每亩用40%的辛硫磷乳油250毫升,加水1-2千克,拌细土20-25千克配成毒土,撒施地面翻入土中,防治地下害虫。')],
 'answer': '藜麦常见虫害有象甲虫、金针虫、蝼蛄、黄条跳甲、横纹菜蝽、蒿蓄齿胫叶甲、潜叶蝇、蚜虫、夜蛾等。防治方法:可每亩用3%的辛硫磷颗粒剂2-2.5千克于耕地前均匀撒施,随耕地翻入土中。也可以每亩用40%的辛硫磷乳油250毫升,加水1-2千克,拌细土20-25千克配成毒土,撒施地面翻入土中,防治地下害虫。'}

📝通俗解释:

  • ConversationalRetrievalChain:RAG的核心链条,它的工作流程是:接收用户问题 → 从知识库检索相关内容 → 把内容和问题一起给AI → AI生成答案
  • ConversationBufferMemory:负责记住聊天历史,这样AI能理解"它刚才说的"、"继续刚才的话题"这类上下文
  • retriever:检索器,负责从向量数据库找到最相关的内容

3.7 高级用法

针对多轮对话场景,增加 question_generator 对历史对话记录进行压缩生成新的question,增加 combine_docs_chain 对检索得到的文本进一步融合。

python
from langchain import LLMChain
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain, StuffDocumentsChain
from langchain.chains.qa_with_sources import load_qa_with_sources_chain
from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate

# 构建初始 messages 列表
messages = [
    SystemMessagePromptTemplate.from_template(qa_template),
    HumanMessagePromptTemplate.from_template('{question}')
]

# 初始化 prompt 对象
prompt = ChatPromptTemplate.from_messages(messages)
llm_chain = LLMChain(llm=llm, prompt=prompt)

# 文档合并链
combine_docs_chain = StuffDocumentsChain(
    llm_chain=llm_chain,
    document_separator="\n\n",
    document_variable_name="context",
)

# 问题生成链(用于压缩历史问题)
q_gen_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(qa_condense_template))

# 构建高级问答链
qa = ConversationalRetrievalChain(
    combine_docs_chain=combine_docs_chain,
    question_generator=q_gen_chain,
    return_source_documents=True,
    return_generated_question=True,
    retriever=retriever
)

# 测试问答
print(qa({'question': "藜麦怎么防治虫害?", "chat_history": []}))

输出示例:

python
{
    'question': '藜麦怎么防治虫害?',
    'chat_history': [],
    'answer': '根据背景知识,藜麦常见虫害有象甲虫、金针虫、蝼蛄、黄条跳甲、横纹菜蝽、蒿蓄齿胫叶甲、潜叶蝇、蚜虫、夜蛾等。防治方法如下:\n\n1. 可每亩用3%的辛硫磷颗粒剂2-2.5千克于耕地前均匀撒施,随耕地翻入土中。\n2. 也可以每亩用40%的辛硫磷乳油250毫升,加水1-2千克,拌细土20-25千克配成毒土,撒施地面翻入土中,防治地下害虫。\n\n以上内容仅供参考,如果需要更多信息,可以阅读农业相关书籍或请教农业专家。',
    'source_documents': [
        Document(page_content='病害:主要防治叶斑病,使用12.5%的烯唑醇可湿性粉剂3000-4000倍液喷雾防治...', metadata={'source': './藜麦.txt'}),
        Document(page_content='中期管理:在藜麦8叶龄时,将行中杂草、病株及残株拔掉,提高整齐度...', metadata={'source': './藜麦.txt'})
    ],
    'generated_question': '藜麦怎么防治虫害?'
}

📝通俗解释:

  • question_generator:当有多轮对话时,把历史问题和当前问题合并,生成一个"完整的问题"去检索。这样即使问"它怎么防治",AI也能理解是问藜麦的病虫害防治
  • combine_docs_chain:如果一次检索到多段相关内容,这个链会把它们合并成一段完整的背景知识
  • return_source_documents=True:返回用到了哪些资料来源,方便核实答案的准确性

总结

本文展示了基于LangChain构建RAG问答应用的完整流程:

  1. 数据准备:加载私有知识库文档
  2. 文档分割:将长文档拆分成小块
  3. 向量化:使用Embedding模型将文本转换为向量
  4. 向量存储:存入Chroma向量数据库
  5. 问答构建:使用ConversationalRetrievalChain实现问答

📝通俗解释:整个RAG系统就像一个"智能图书管理员"。用户问问题时,管理员不是凭记忆回答,而是先去书架找到相关的书(检索),然后根据书上的内容回答(生成)。这样保证了答案的准确性,不会AI胡编乱造。

基于 MIT 许可发布