使用LangChain和Pinecone向量数据库构建定制的问答应用程序

使用LangChain和Pinecone构建定制问答应用程序

介绍

大语言模型的出现是我们这个时代最令人兴奋的技术发展之一。它在人工智能领域开辟了无尽的可能性,为各个行业的真实问题提供了解决方案。这些模型的一个令人着迷的应用是开发根据个人或组织数据源提供特定答案的自定义问答或聊天机器人。然而,由于大语言模型是基于公开可用的通用数据进行训练的,它们的答案不总是对最终用户具体或有用。我们可以使用诸如LangChain之类的框架来解决这个问题,以开发根据我们的数据提供特定答案的自定义聊天机器人。在本文中,我们将学习如何使用Streamlit Cloud构建自定义问答应用程序。

学习目标

在深入了解本文之前,让我们概述一下主要的学习目标:

  • 了解自定义问答的整个工作流程以及工作流程中每个组件的作用
  • 了解问答应用相对于精调自定义大语言模型的优势
  • 学习Pinecone向量数据库的基础知识,以存储和检索向量
  • 使用OpenAI大语言模型、LangChain和Pinecone向量数据库构建语义搜索流程,开发一个Streamlit应用程序。

本文是Data Science Blogathon的一部分。

问答应用程序概述

来源:ScienceSoft

问答或“与您的数据聊天”是大语言模型和LangChain的广泛应用案例。LangChain提供了一系列组件,可加载您用于特定用例的任何数据源。它支持许多数据源和转换器,将其转换为一系列字符串以存储在向量数据库中。一旦数据存储在数据库中,我们可以使用称为retrievers的组件来查询数据库。此外,通过使用大语言模型,我们可以像聊天机器人一样获得准确的答案,而无需查阅大量文档。

LangChain支持以下数据源。如图所示,它允许超过120个集成,可连接您可能拥有的每个数据源。

来源:LangChain文档

问答应用程序工作流程

我们了解了LangChain支持的数据源,它允许我们使用LangChain中提供的组件开发问答流程。下面是用于加载文档、存储、检索和生成LLM输出的组件。

  1. 文档加载器:用于加载用户文档进行矢量化和存储
  2. 文本分割器:这些是将文档转换为固定长度块以有效存储的文档转换器
  3. 向量存储:向量数据库集成,用于存储输入文本的矢量嵌入
  4. 文档检索:根据用户查询从数据库中检索文本。它们使用相似性搜索技术进行检索。
  5. 模型输出:从查询输入提示和检索到的文本生成的最终模型输出。

这是问答流程的高级工作流程,可以解决许多现实世界的问题。我没有深入研究每个LangChain组件,但如果您想了解更多信息,请查看我在Analytics Vidhya上发表的先前文章(链接:点击此处)。

问答应用程序-工作流程图(作者:Image by Author)

自定义问答应用的优势与模型微调相比

  1. 特定上下文的答案
  2. 适应新的输入文档
  3. 无需对模型进行微调,节省了模型训练的成本
  4. 提供更准确和具体的答案,而不是一般性答案

什么是Pinecone向量数据库?

Pinecone是用于构建基于LLM的应用程序的流行向量数据库。它是一种多功能且可扩展的高性能AI应用程序。它是一个完全托管的云原生向量数据库,用户无需处理基础设施的麻烦。

LLM(长期记忆模型)基于大量的非结构化数据,需要使用复杂的长期记忆来以最大的准确性检索信息。生成式AI应用程序依赖于基于向量嵌入的语义搜索,以根据用户输入返回合适的上下文。

Pinecone非常适合这类应用程序,并经过优化以存储和查询许多向量,并具有低延迟,以构建用户友好的应用程序。让我们学习如何为我们的问答应用程序创建一个Pinecone向量数据库。

# 安装pinecone-client
pip install pinecone-client

# 导入pinecone并使用您的API密钥和环境名称进行初始化
import pinecone
pinecone.init(api_key="YOUR_API_KEY", environment="YOUR_ENVIRONMENT")

# 创建第一个索引,以开始存储向量
pinecone.create_index("first_index", dimension=8, metric="cosine")

# 插入示例数据(5个8维向量)
index.upsert([
    ("A", [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]),
    ("B", [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]),
    ("C", [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]),
    ("D", [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]),
    ("E", [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])
])

# 使用list_indexes()方法来获取数据库中可用的索引列表
pinecone.list_indexes()

[Output]>>> ['first_index']

在上面的演示中,我们安装了一个pinecone客户端来初始化我们项目环境中的向量数据库。一旦向量数据库初始化完成,我们就可以使用所需的维度和度量创建索引,将向量嵌入插入向量数据库。在下一节中,我们将使用Pinecone和LangChain构建一个语义搜索流水线来完成我们的应用程序。

使用OpenAI和Pinecone构建语义搜索流水线

我们了解到问答应用程序工作流程中有5个步骤。在本节中,我们将执行前4个步骤:文档加载器、文本分割器、向量存储和文档检索。

要在本地环境或云端笔记本环境(如Google Colab)中执行这些步骤,您必须安装一些库并分别在OpenAI和Pinecone上创建帐户以获取它们的API密钥。让我们从环境设置开始:

安装所需的库

# 安装langchain和openai及其他依赖项
!pip install --upgrade langchain openai -q
!pip install pillow==6.2.2
!pip install unstructured -q
!pip install unstructured[local-inference] -q
!pip install detectron2@git+https://github.com/facebookresearch/[email protected] /
                                            #egg=detectron2 -q
!apt-get install poppler-utils
!pip install pinecone-client -q
!pip install tiktoken -q

# 设置openai环境
import os
os.environ["OPENAI_API_KEY"] = "YOUR-API-KEY"

# 导入库
import os
import openai
import pinecone
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Pinecone
from langchain.llms import OpenAI
from langchain.chains.question_answering import load_qa_chain

在安装设置完成后,导入上面代码片段中提到的所有库。然后,按照以下步骤进行操作:

加载文档

在这一步中,我们将从目录中加载文档作为AI项目流程的起点。我们的目录中有2个文档,我们将加载到项目环境中。

#从content/data目录加载文档
directory = '/content/data'

#使用langchain函数加载文档的load_docs函数
def load_docs(directory):
  loader = DirectoryLoader(directory)
  documents = loader.load()
  return documents

documents = load_docs(directory)
len(documents)
[输出]>>> 5

拆分文本数据

如果每个文档具有固定长度,则文本嵌入和LLMs的性能更好。因此,对于任何LLM用例,将文本拆分为相等长度的块是必要的。我们将使用“RecursiveCharacterTextSplitter”将文档转换为与文本文档大小相同的形式。

#使用递归文本拆分器拆分文档
def split_docs(documents, chunk_size=200, chunk_overlap=20):
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
  docs = text_splitter.split_documents(documents)
  return docs

#拆分文档
docs = split_docs(documents)
print(len(docs))
[输出]>>>12

将数据存储在向量存储中

拆分文档后,我们将使用OpenAI嵌入将它们的嵌入存储在向量数据库中。

#随机单词的嵌入示例
embeddings = OpenAIEmbeddings()

#初始化pinecondb
pinecone.init(
    api_key="YOUR-API-KEY",
    environment="YOUR-ENV"
)

#定义索引名称
index_name = "langchain-project"

#将数据和嵌入存储到pinecone索引中
index = Pinecone.from_documents(docs, embeddings, index_name=index_name)

从向量数据库检索数据

我们将使用向量数据库进行语义搜索来检索文档。我们在一个名为“langchain-project”的索引中存储了向量,一旦我们查询它,就会从数据库中获取最相似的文档。

#对我们的数据库进行示例查询
query = "有哪些不同类型的宠物动物?"

#进行相似度搜索并将文档存储在结果变量中
result = index.similarity_search(
    query,  #我们的搜索查询
    k=3  #返回3个最相关的文档
)
-
--------------------------------[输出]--------------------------------------
result
[Document(page_content='常常选择小型哺乳动物,如仓鼠、豚鼠和兔子,因为它们的维护需求较低。鸟类具有美丽和歌声,爬行动物如乌龟和蜥蜴也可以成为引人入胜的宠物。', 
metadata={'source': '/content/data/Different Types of Pet Animals.txt'}),
 Document(page_content='宠物动物有各种各样的形状和大小,每种都适合不同的生活方式和家庭环境。狗和猫是最常见的,以其陪伴和独特的个性而闻名。小型', 
metadata={'source': '/content/data/Different Types of Pet Animals.txt'}),
 Document(page_content='引人入胜的宠物。甚至鱼类以其镇定的存在也可以成为美妙的宠物。', 
metadata={'source': '/content/data/Different Types of Pet Animals.txt'})]

我们可以根据向量存储中的相似度搜索检索文档,如上面的代码片段所示。如果您想了解更多关于语义搜索应用的信息,我强烈推荐阅读我之前关于这个主题的文章(链接:点击这里)

使用Streamlit构建自定义问题回答应用程序

在问题回答应用程序的最后阶段,我们将整合每个工作流组件,构建一个自定义的问答应用程序,允许用户输入各种数据源,如基于网络的文章、PDF、CSV等,进行交流。从而使他们在日常活动中更加高效。我们需要创建一个GitHub存储库,并添加以下文件。

存储库结构

添加这些项目文件

  1. main.py—一个包含streamlit前端代码的python文件
  2. qanda.py—提示设计和模型输出函数,用于返回用户查询的答案
  3. utils.py—加载和拆分输入文档的实用函数
  4. vector_search.py—文本嵌入和向量存储函数
  5. requirements.txt—在streamlit公共云中运行应用程序所需的项目依赖项

在此项目演示中,我们支持两种类型的数据源:

  1. 基于Web URL的文本数据
  2. 在线PDF文件

这两种类型包含各种文本数据,对于许多用例来说非常常见。您可以查看下面的main.py python代码以了解应用程序的用户界面。

# import necessary libraries
import streamlit as st
import openai
import qanda
from vector_search import *
from utils import *
from io  import StringIO

# 获取OpenAI API密钥
api_key = st.sidebar.text_input("请输入您的OpenAI API密钥:", type='password')
# 设置OpenAI密钥
openai.api_key = str(api_key)

# 应用程序标题
_ , col2,_ = st.columns([1,7,1])
with col2:
    col2 = st.header("Simplchat: 与您的数据进行聊天")
    url = False
    query = False
    pdf = False
    data = False
    # 根据用户需求选择选项
    options = st.selectbox("选择数据源类型",
                            options=['Web URL','PDF','现有数据源'])
    # 根据数据源选项提出查询
    if options == 'Web URL':
        url = st.text_input("输入数据源的URL")
        query = st.text_input("输入您的查询")
        button = st.button("提交")
    elif options == 'PDF':
        pdf = st.text_input("在此输入您的PDF链接") 
        query = st.text_input("输入您的查询")
        button = st.button("提交")
    elif options == '现有数据源':
        data= True
        query = st.text_input("输入您的查询")
        button = st.button("提交") 

# 根据给定的查询和数据源编写代码以获取输出   
if button and url:
    with st.spinner("正在更新数据库..."):
        corpusData = scrape_text(url)
        encodeaddData(corpusData,url=url,pdf=False)
        st.success("数据库已更新")
    with st.spinner("正在查找答案..."):
        title, res = find_k_best_match(query,2)
        context = "\n\n".join(res)
        st.expander("上下文").write(context)
        prompt = qanda.prompt(context,query)
        answer = qanda.get_answer(prompt)
        st.success("答案:"+ answer)

# 编写代码以获取给定查询和数据源的输出
if button and pdf:
    with st.spinner("正在更新数据库..."):
        corpusData = pdf_text(pdf=pdf)
        encodeaddData(corpusData,pdf=pdf,url=False)
        st.success("数据库已更新")
    with st.spinner("正在查找答案..."):
        title, res = find_k_best_match(query,2)
        context = "\n\n".join(res)
        st.expander("上下文").write(context)
        prompt = qanda.prompt(context,query)
        answer = qanda.get_answer(prompt)
        st.success("答案:"+ answer)
        
if button and data:
    with st.spinner("正在查找答案..."):
        title, res = find_k_best_match(query,2)
        context = "\n\n".join(res)
        st.expander("上下文").write(context)
        prompt = qanda.prompt(context,query)
        answer = qanda.get_answer(prompt)
        st.success("答案:"+ answer)
        
        
# 从数据库中删除向量
st.expander("从数据库中删除索引")
button1 = st.button("删除当前向量")
if button1 == True:
    index.delete(deleteAll='true')

要查看其他代码文件,请访问该项目的GitHub存储库。 (链接:点击这里)

在Streamlit Cloud上部署问答应用程序

应用程序界面

Streamlit提供了一个社区云,免费托管应用程序。此外,由于其自动化的CI/CD流水线功能,Streamlit易于使用。要了解更多关于Streamlit构建应用程序的信息,请访问我在Analytics Vidya上写的先前文章(链接:点击这里)

定制问答应用的行业用例

随着这一领域出现新的创新用例,许多行业采用定制问答应用程序。让我们来看看这些用例:

客户支持辅助

随着LLMs的崛起,客户支持的革命已经开始。无论是电子商务、电信还是金融行业,基于公司文件开发的客户服务机器人可以帮助客户做出更快速、更明智的决策,从而增加收入。

医疗保健行业

对于患者来说,及时获得信息对于获得某些疾病的及时治疗至关重要。医疗保健公司可以开发交互式聊天机器人,以自然语言提供医疗信息、药物信息、症状解释和治疗指南,而无需实际人员介入。

律师处理大量的法律信息和文件以解决法庭案件。使用这些大量数据开发的定制LLM应用程序可以帮助律师更高效地解决案件。

技术行业

问答应用程序的最大改变游戏的用例是编程辅助。技术公司可以基于其内部代码库构建此类应用程序,帮助程序员解决问题、理解代码语法、调试错误和实现特定功能。

政府和公共服务

政府政策和计划包含大量信息,可能使许多人感到不知所措。通过为此类政府服务开发定制应用程序,公民可以获取有关政府计划和法规的信息。它还可以帮助正确填写政府表格和申请。

结论

总之,我们已经探索了使用LangChain和Pinecone向量数据库构建定制问答应用程序的令人兴奋的可能性。本博客带领我们了解了从问答应用程序概述到了解Pinecone向量数据库功能的基本概念。通过将OpenAI的语义搜索流水线与Pinecone的高效索引和检索系统相结合,我们已经利用了在streamlit上创建强大而准确的问答解决方案的潜力。让我们来看看本文的主要要点:

主要要点

  • 大型语言模型(LLMs)已经改变了人工智能,使得多样化的应用成为可能。使用个人或组织数据定制聊天机器人是一种强大的方法。
  • 虽然通用LLMs能够广泛理解语言,但定制的问答应用程序由于其灵活性和成本效益,在细调个性化LLMs上具有明显优势。
  • 通过融合Pinecone向量数据库、OpenAI LLMs和LangChain,我们学会了如何开发语义搜索流水线,并将其部署在像streamlit这样的云平台上。

常见问题

本文中显示的媒体不归Analytics Vidhya所有,仅由作者自行决定使用。