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的工作原理,并开始构建自己的文档问答应用!

相关推荐
都叫我大帅哥12 小时前
当RAG学会“思考”:Agentic RAG架构完全指南
langchain·ai编程
麦兜*1 天前
LangChain4j终极指南:Spring Boot构建企业级Agent框架
java·spring boot·spring·spring cloud·ai·langchain·ai编程
都叫我大帅哥1 天前
🧠 LangGraph状态管理:让AI工作流拥有“记忆”的超能力
python·langchain·ai编程
都叫我大帅哥2 天前
LangGraph条件判断:让AI工作流"聪明"起来
python·langchain
六毛的毛2 天前
LangChain入门:内存、记录聊天历史 ChatMessageHistory、模型、提示 ( Prompt )、模式 ( Schema )
人工智能·langchain·prompt
简放2 天前
智能机票助手-接入Ollama本地模型-Spring-AI-Alibaba
spring boot·langchain·ollama
六毛的毛3 天前
Langchain入门:构建一个基于SQL数据的问答系统
数据库·sql·langchain
Stefan的技术笔记3 天前
LangChain入门指南:5大核心组件解析,快速上手AI应用开发!
前端·langchain
hayson3 天前
langchaingo用法详解及源码解析(二)
langchain·llm