18_LangChain整合维基百科实现问答系统

77 阅读8分钟

LangChain整合维基百科实现问答系统

引言

随着大语言模型(LLM)的快速发展,结合外部知识源的问答系统变得越来越受欢迎。本教程将介绍如何使用LangChain框架整合维基百科数据,构建一个能够回答有关特定主题问题的问答系统。通过这个系统,用户可以输入关键词获取维基百科内容,然后针对这些内容提出问题,获得准确的回答。

1. 系统架构

我们将构建的问答系统包含以下几个核心组件:

  1. 数据获取:从维基百科爬取特定主题的内容
  2. 文本处理:将获取的内容分割成适当大小的块
  3. 向量化存储:使用FAISS向量数据库存储文本嵌入
  4. 问答链:利用LangChain的问答链处理用户查询
  5. 用户界面:使用Streamlit构建简单友好的交互界面

系统架构图

2. 环境准备

首先,我们需要安装必要的依赖库:

pip install langchain langchain-openai faiss-cpu bs4 wikipedia streamlit python-dotenv requests

3. 导入依赖库

接下来,我们导入所需的库:

import os
import requests
from bs4 import BeautifulSoup
import wikipedia
import streamlit as st
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain.chains.question_answering import load_qa_chain
from langchain.prompts import PromptTemplate

# 加载环境变量
load_dotenv()

4. 爬取维基百科内容

我们首先实现从维基百科获取内容的功能:

def get_wiki(query):
    """
    获取维基百科内容
    
    Args:
        query: 搜索关键词
        
    Returns:
        tuple: (页面内容, 摘要)
    """
    try:
        # 获取维基百科摘要
        summary = wikipedia.summary(query)
        
        # 获取维基百科页面URL
        page = wikipedia.page(query)
        url = page.url
        
        # 使用requests获取完整页面内容
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 提取正文内容
        content_div = soup.find('div', {'id': 'mw-content-text'})
        paragraphs = content_div.find_all('p')
        
        # 合并所有段落文本
        full_content = '\n'.join([p.text for p in paragraphs])
        
        return full_content, summary
    except wikipedia.exceptions.DisambiguationError as e:
        # 处理歧义词条
        st.error(f"找到多个匹配项,请尝试更具体的搜索词: {', '.join(e.options[:5])}")
        return None, None
    except wikipedia.exceptions.PageError:
        # 处理页面不存在的情况
        st.error(f"找不到与'{query}'相关的维基百科页面")
        return None, None
    except Exception as e:
        st.error(f"发生错误: {str(e)}")
        return None, None

5. 设置用户界面

使用Streamlit创建简单易用的用户界面:

def main():
    # 设置页面标题
    st.title("基于维基百科的问答系统")
    
    # 侧边栏 - 输入OpenAI API密钥
    with st.sidebar:
        st.header("配置")
        api_key = st.text_input("输入您的OpenAI API密钥", type="password")
        if api_key:
            os.environ["OPENAI_API_KEY"] = api_key
    
    # 主界面 - 搜索维基百科
    st.subheader("第一步: 搜索维基百科")
    query = st.text_input("请输入要检索的关键词")
    
    if st.button("获取维基百科内容"):
        if not query:
            st.warning("请输入搜索关键词")
            return
            
        with st.spinner("正在从维基百科获取内容..."):
            full_content, summary = get_wiki(query)
            
            if full_content and summary:
                st.session_state.wiki_content = full_content
                st.session_state.wiki_summary = summary
                st.success("内容获取成功!")
                
                # 显示摘要
                st.subheader("维基百科摘要")
                st.write(summary)
                
                # 处理文本并创建向量存储
                process_text_and_create_index(full_content)
    
    # 如果已经创建了向量存储,显示问答界面
    if 'vectorstore' in st.session_state:
        st.subheader("第二步: 提问")
        question = st.text_input("请输入您的问题")
        
        if st.button("提交问题"):
            if not question:
                st.warning("请输入问题")
                return
                
            if not api_key:
                st.warning("请先输入OpenAI API密钥")
                return
                
            with st.spinner("正在思考..."):
                answer = answer_question(question)
                
                st.subheader("回答")
                st.write(answer)

6. 文本处理和向量存储

接下来,我们需要处理获取的文本并创建向量存储:

def process_text_and_create_index(text):
    """
    处理文本并创建向量索引
    
    Args:
        text: 要处理的文本内容
    """
    try:
        # 文本分割
        text_splitter = CharacterTextSplitter(
            separator="\n",
            chunk_size=1000,
            chunk_overlap=200,
            length_function=len
        )
        chunks = text_splitter.split_text(text)
        
        st.info(f"文本已分割成 {len(chunks)} 个块")
        
        # 创建向量存储
        with st.spinner("正在创建向量索引..."):
            embeddings = OpenAIEmbeddings()
            vectorstore = FAISS.from_texts(chunks, embeddings)
            st.session_state.vectorstore = vectorstore
            st.success("向量索引创建成功!")
    except Exception as e:
        st.error(f"创建索引时出错: {str(e)}")

7. 实现问答功能

最后,我们实现问答功能:

def answer_question(question):
    """
    回答问题
    
    Args:
        question: 用户问题
        
    Returns:
        str: 回答内容
    """
    try:
        # 获取相关文档
        vectorstore = st.session_state.vectorstore
        docs = vectorstore.similarity_search(question, k=4)
        
        # 创建自定义提示模板
        template = """
        你是一个有帮助的AI助手,专门回答关于维基百科内容的问题。
        使用以下上下文来回答问题。如果你不知道答案,就说你不知道,不要编造答案。
        尽量给出简洁、准确的回答。
        
        上下文: {context}
        
        问题: {question}
        
        回答:
        """
        
        PROMPT = PromptTemplate(
            template=template,
            input_variables=["context", "question"]
        )
        
        # 创建问答链
        chain = load_qa_chain(
            ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo"),
            chain_type="stuff",
            prompt=PROMPT
        )
        
        # 获取回答
        response = chain.run(input_documents=docs, question=question)
        return response
    except Exception as e:
        return f"回答问题时出错: {str(e)}"

8. 运行应用程序

最后,我们添加主函数入口:

if __name__ == "__main__":
    main()

将上述代码保存为app.py,然后运行:

streamlit run app.py

9. 完整代码

下面是完整的应用程序代码:

import os
import requests
from bs4 import BeautifulSoup
import wikipedia
import streamlit as st
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain.chains.question_answering import load_qa_chain
from langchain.prompts import PromptTemplate

# 加载环境变量
load_dotenv()

def get_wiki(query):
    """
    获取维基百科内容
    
    Args:
        query: 搜索关键词
        
    Returns:
        tuple: (页面内容, 摘要)
    """
    try:
        # 获取维基百科摘要
        summary = wikipedia.summary(query)
        
        # 获取维基百科页面URL
        page = wikipedia.page(query)
        url = page.url
        
        # 使用requests获取完整页面内容
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 提取正文内容
        content_div = soup.find('div', {'id': 'mw-content-text'})
        paragraphs = content_div.find_all('p')
        
        # 合并所有段落文本
        full_content = '\n'.join([p.text for p in paragraphs])
        
        return full_content, summary
    except wikipedia.exceptions.DisambiguationError as e:
        # 处理歧义词条
        st.error(f"找到多个匹配项,请尝试更具体的搜索词: {', '.join(e.options[:5])}")
        return None, None
    except wikipedia.exceptions.PageError:
        # 处理页面不存在的情况
        st.error(f"找不到与'{query}'相关的维基百科页面")
        return None, None
    except Exception as e:
        st.error(f"发生错误: {str(e)}")
        return None, None

def process_text_and_create_index(text):
    """
    处理文本并创建向量索引
    
    Args:
        text: 要处理的文本内容
    """
    try:
        # 文本分割
        text_splitter = CharacterTextSplitter(
            separator="\n",
            chunk_size=1000,
            chunk_overlap=200,
            length_function=len
        )
        chunks = text_splitter.split_text(text)
        
        st.info(f"文本已分割成 {len(chunks)} 个块")
        
        # 创建向量存储
        with st.spinner("正在创建向量索引..."):
            embeddings = OpenAIEmbeddings()
            vectorstore = FAISS.from_texts(chunks, embeddings)
            st.session_state.vectorstore = vectorstore
            st.success("向量索引创建成功!")
    except Exception as e:
        st.error(f"创建索引时出错: {str(e)}")

def answer_question(question):
    """
    回答问题
    
    Args:
        question: 用户问题
        
    Returns:
        str: 回答内容
    """
    try:
        # 获取相关文档
        vectorstore = st.session_state.vectorstore
        docs = vectorstore.similarity_search(question, k=4)
        
        # 创建自定义提示模板
        template = """
        你是一个有帮助的AI助手,专门回答关于维基百科内容的问题。
        使用以下上下文来回答问题。如果你不知道答案,就说你不知道,不要编造答案。
        尽量给出简洁、准确的回答。
        
        上下文: {context}
        
        问题: {question}
        
        回答:
        """
        
        PROMPT = PromptTemplate(
            template=template,
            input_variables=["context", "question"]
        )
        
        # 创建问答链
        chain = load_qa_chain(
            ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo"),
            chain_type="stuff",
            prompt=PROMPT
        )
        
        # 获取回答
        response = chain.run(input_documents=docs, question=question)
        return response
    except Exception as e:
        return f"回答问题时出错: {str(e)}"

def main():
    # 设置页面标题
    st.title("基于维基百科的问答系统")
    
    # 侧边栏 - 输入OpenAI API密钥
    with st.sidebar:
        st.header("配置")
        api_key = st.text_input("输入您的OpenAI API密钥", type="password")
        if api_key:
            os.environ["OPENAI_API_KEY"] = api_key
    
    # 主界面 - 搜索维基百科
    st.subheader("第一步: 搜索维基百科")
    query = st.text_input("请输入要检索的关键词")
    
    if st.button("获取维基百科内容"):
        if not query:
            st.warning("请输入搜索关键词")
            return
            
        with st.spinner("正在从维基百科获取内容..."):
            full_content, summary = get_wiki(query)
            
            if full_content and summary:
                st.session_state.wiki_content = full_content
                st.session_state.wiki_summary = summary
                st.success("内容获取成功!")
                
                # 显示摘要
                st.subheader("维基百科摘要")
                st.write(summary)
                
                # 处理文本并创建向量存储
                process_text_and_create_index(full_content)
    
    # 如果已经创建了向量存储,显示问答界面
    if 'vectorstore' in st.session_state:
        st.subheader("第二步: 提问")
        question = st.text_input("请输入您的问题")
        
        if st.button("提交问题"):
            if not question:
                st.warning("请输入问题")
                return
                
            if not api_key:
                st.warning("请先输入OpenAI API密钥")
                return
                
            with st.spinner("正在思考..."):
                answer = answer_question(question)
                
                st.subheader("回答")
                st.write(answer)

if __name__ == "__main__":
    main()

10. 使用示例

示例1:查询"黄河"

  1. 在搜索框中输入"黄河"并点击"获取维基百科内容"
  2. 系统会显示黄河的维基百科摘要
  3. 在问题框中输入"黄河为什么是世界上含沙量最高的河流?"
  4. 系统会根据维基百科内容回答这个问题

示例2:查询其他主题

您可以查询任何维基百科上有的主题,例如:

  • 历史人物(如"孔子"、"牛顿")
  • 科学概念(如"相对论"、"量子力学")
  • 地理位置(如"长江"、"珠穆朗玛峰")
  • 文化现象(如"京剧"、"中国功夫")

11. 系统优化

为了进一步提升系统性能,您可以考虑以下优化:

  1. 多语言支持:通过设置wikipedia库的语言参数,支持不同语言的维基百科

    wikipedia.set_lang("zh")  # 设置为中文维基百科
    
  2. 缓存机制:使用Streamlit的缓存功能减少重复API调用

    @st.cache_data(ttl=3600)  # 缓存1小时
    def get_wiki_cached(query):
        return get_wiki(query)
    
  3. 错误处理:增强错误处理机制,提供更友好的用户体验

  4. 高级提示工程:优化提示模板,使回答更准确、更自然

  5. 多模型支持:允许用户选择不同的LLM模型

12. 总结

本教程展示了如何使用LangChain框架整合维基百科数据,构建一个功能完善的问答系统。通过这个系统,用户可以轻松获取维基百科内容并提出相关问题,得到准确的回答。

这个项目结合了网络爬虫、文本处理、向量存储和大语言模型等技术,是一个很好的LangChain实践案例。您可以在此基础上进行扩展,例如添加更多数据源、优化用户界面、增强问答能力等,打造更强大的知识问答系统。