9_LangChain基于RAG实现文档问答

什么是RAG?

RAG(检索增强生成)是一种让AI更聪明的技术,就像给AI配备了一个智能"小抄"。

想象一下:普通的大语言模型(如ChatGPT)只知道它训练时学到的知识,而且这些知识有"截止日期"。如果你想让AI回答关于你公司内部文档、最新研究报告或个人笔记的问题,普通AI就无能为力了。

这时RAG就派上用场了:它能让AI在回答问题前先"查阅资料",就像学生做开卷考试一样。AI会从你提供的文档中找出相关信息,然后基于这些信息生成答案。

简单来说,RAG = 检索(Retrieval) + 生成(Generation),它让AI变得更加实用,能够利用你提供的专业知识来回答问题。

RAG的工作原理

RAG系统的工作方式就像一个高效的图书馆助手,主要分为两大步骤:

1. 索引阶段(准备知识库)

这个阶段就像是整理一个智能图书馆,通常在系统启动前就完成:

  • 加载文档:首先,我们把各种文档(PDF、Word、网页等)导入系统中
  • 分割文本:把长文档切成小块(想象成便利贴大小的内容),因为AI一次处理不了太多内容
  • 转换为向量:使用"嵌入模型"把每个文本块转换成数字向量(可以理解为给每段文本创建一个"数字指纹")
  • 存储向量:把这些向量存入向量数据库,方便以后快速查找

2. 检索和生成阶段(回答问题)

当用户提问时,系统会:

  • 处理问题:把用户问题也转换成向量
  • 检索相关内容:在向量数据库中找出与问题最相似的几个文本块
  • 组合信息:把问题和检索到的相关文本块组合起来
  • 生成回答:让大语言模型基于这些信息生成一个准确、相关的回答

文档问答应用实现流程

让我们用生活化的语言来解释如何构建一个文档问答应用:

  1. 上传文档:用户通过网页上传一个或多个文档(比如PDF、TXT文件等)
  2. 接收并存储:服务器接收这些文档并暂时保存
  3. 读取文档:程序打开并读取文档内容
  4. 切块处理:把文档内容分成小块(就像把一本书分成多个段落)
  5. 创建向量索引:把每个文本块转换为向量并存入数据库
  6. 准备回答问题:此时系统已经"学习"了文档内容,准备好回答问题
  7. 用户提问:用户输入与文档相关的问题
  8. 智能回答:系统找出相关文本块,让AI生成回答

代码实现

下面我们将使用LangChain和Streamlit构建一个简单的文档问答应用。这个应用允许用户上传文本文件,然后向其提问。

1. 安装必要的库

首先,我们需要安装所需的Python库:

bash 复制代码
pip install langchain langchain-openai streamlit faiss-cpu

2. 创建Streamlit应用

创建一个名为app.py的文件,并添加以下代码:

python 复制代码
import os
import tempfile
import streamlit as st
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.tools.retriever import create_retriever_tool
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import AIMessage, HumanMessage
from langchain.callbacks.streamlit import StreamlitCallbackHandler

# 设置页面标题
st.set_page_config(page_title="文档问答助手", page_icon="📚")
st.title("📚 文档问答助手")

# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = st.sidebar.text_input("OpenAI API Key", type="password")

# 初始化会话状态
if "retriever" not in st.session_state:
    st.session_state.retriever = None
if "chat_history" not in st.session_state:
    st.session_state.chat_history = []

# 文件上传区域
uploaded_file = st.sidebar.file_uploader("上传文档", type=["txt"])

# 处理文件上传
if uploaded_file and st.session_state.retriever is None:
    with st.spinner("正在处理文档..."):
        # 创建临时文件
        with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
            tmp_file.write(uploaded_file.getvalue())
            tmp_file_path = tmp_file.name
        
        # 读取文件内容
        with open(tmp_file_path, 'r', encoding='utf-8') as f:
            text = f.read()
        
        # 删除临时文件
        os.unlink(tmp_file_path)
        
        # 文本分割
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,  # 每块文本的大小
            chunk_overlap=100,  # 块之间的重叠部分
            separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""],  # 分割符号
        )
        chunks = text_splitter.split_text(text)
        
        # 创建向量存储
        embeddings = OpenAIEmbeddings()
        vectorstore = FAISS.from_texts(chunks, embeddings)
        
        # 创建检索器
        st.session_state.retriever = vectorstore.as_retriever(
            search_type="similarity",  # 相似度搜索
            search_kwargs={"k": 3}  # 返回前3个最相关的结果
        )
        
        st.sidebar.success(f"文档处理完成!共分割为{len(chunks)}个文本块")

# 如果检索器已创建,显示聊天界面
if st.session_state.retriever is not None:
    # 创建检索工具
    retriever_tool = create_retriever_tool(
        st.session_state.retriever,
        "document_search",
        "在文档中搜索信息,用于回答关于文档内容的问题。"
    )
    
    # 创建语言模型
    llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
    
    # 创建提示模板
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是一个专业的文档助手,可以回答用户关于文档内容的问题。
        使用提供的工具来搜索文档并回答问题。
        如果你不知道答案,请诚实地说你不知道,不要编造信息。
        始终使用中文回答问题。
        """),
        ("human", "{input}")
    ])
    
    # 创建React代理
    agent = create_react_agent(llm, [retriever_tool], prompt)
    agent_executor = AgentExecutor.from_agent_and_tools(
        agent=agent,
        tools=[retriever_tool],
        verbose=True,
        handle_parsing_errors=True
    )
    
    # 显示聊天历史
    for message in st.session_state.chat_history:
        if isinstance(message, HumanMessage):
            with st.chat_message("user"):
                st.write(message.content)
        else:
            with st.chat_message("assistant"):
                st.write(message.content)
    
    # 获取用户输入
    user_input = st.chat_input("请输入您的问题...")
    
    # 处理用户输入
    if user_input:
        # 显示用户问题
        with st.chat_message("user"):
            st.write(user_input)
        
        # 添加到聊天历史
        st.session_state.chat_history.append(HumanMessage(content=user_input))
        
        # 显示AI思考过程
        with st.chat_message("assistant"):
            st_callback = StreamlitCallbackHandler(st.container())
            response = agent_executor.invoke(
                {"input": user_input},
                {"callbacks": [st_callback]}
            )
            answer = response["output"]
            st.write(answer)
        
        # 添加到聊天历史
        st.session_state.chat_history.append(AIMessage(content=answer))
else:
    # 如果还没上传文件,显示说明
    st.info("👈 请先上传一个文本文件")

3. 运行应用

在命令行中运行以下命令启动应用:

bash 复制代码
streamlit run app.py

4. 使用应用

  1. 在侧边栏输入你的OpenAI API密钥
  2. 上传一个文本文件(比如公司手册、研究论文等)
  3. 等待文件处理完成
  4. 在聊天框中输入与文档相关的问题
  5. 观察AI如何回答你的问题

代码解析

让我们来解释上面代码的关键部分:

文档处理流程

python 复制代码
# 文本分割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,  # 每块文本的大小
    chunk_overlap=100,  # 块之间的重叠部分
    separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""],  # 分割符号
)
chunks = text_splitter.split_text(text)

这段代码将长文本分割成小块,每块约1000个字符,相邻块之间重叠100个字符(避免切割导致的信息丢失)。分割时优先按照段落、句子等自然边界进行。

python 复制代码
# 创建向量存储
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_texts(chunks, embeddings)

这里使用OpenAI的嵌入模型将文本块转换为向量,并存储在FAISS向量数据库中。FAISS是一个高效的向量搜索库,专为相似性搜索而设计。

检索工具创建

python 复制代码
retriever_tool = create_retriever_tool(
    st.session_state.retriever,
    "document_search",
    "在文档中搜索信息,用于回答关于文档内容的问题。"
)

这段代码创建了一个检索工具,它可以被代理用来搜索文档。工具名称为"document_search",描述告诉AI这个工具的用途。

React代理创建

python 复制代码
agent = create_react_agent(llm, [retriever_tool], prompt)
agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent,
    tools=[retriever_tool],
    verbose=True,
    handle_parsing_errors=True
)

这里创建了一个React代理,它使用ReAct(Reasoning and Acting)方法,能够在回答问题前先思考应该采取什么行动。verbose=True让我们能看到代理的思考过程,handle_parsing_errors=True使代理能够处理解析错误。

进阶优化建议

  1. 支持更多文件类型:扩展应用以支持PDF、Word、Excel等更多文件格式
  2. 记忆对话历史:让AI记住之前的对话,能够进行连续对话
  3. 多文档管理:允许用户上传多个文档并在它们之间切换
  4. 高级检索方法:实现混合搜索或重排序,提高检索质量
  5. 自定义模型选择:让用户选择不同的语言模型和嵌入模型

总结

RAG技术让我们能够构建"有记忆"的AI应用,它可以基于特定文档回答问题,而不仅仅依赖于预训练知识。这对于构建企业知识库、个人助手、研究工具等应用非常有价值。

通过LangChain,我们可以轻松实现RAG系统的各个组件:文档加载、文本分割、向量存储、检索和生成。而Streamlit则提供了一个简单的方式来创建交互式Web界面。

希望这个教程能帮助你理解RAG的工作原理,并开始构建自己的文档问答应用!

相关推荐
一切尽在,你来4 小时前
1.2 LangChain 1.2.7 版本核心特性与升级点
人工智能·langchain
Bruk.Liu4 小时前
(LangChain 实战14):基于 ChatMessageHistory 自定义实现对话记忆功能
人工智能·python·langchain·agent
JaydenAI10 小时前
[拆解LangChain执行引擎] ManagedValue——一种特殊的只读虚拟通道
python·langchain
OPEN-Source11 小时前
大模型实战:搭建一张“看得懂”的大模型应用可观测看板
人工智能·python·langchain·rag·deepseek
一切尽在,你来13 小时前
1.4 LangChain 1.2.7 核心架构概览
人工智能·langchain·ai编程
一切尽在,你来13 小时前
1.3 环境搭建
人工智能·ai·langchain·ai编程
蛇皮划水怪20 小时前
深入浅出LangChain4J
java·langchain·llm
、BeYourself21 小时前
LangChain4j 流式响应
langchain
、BeYourself21 小时前
LangChain4j之Chat and Language
langchain
qfljg1 天前
langchain usage
langchain