使用 LangChain 和 Pinecone 矢量數(shù)據(jù)庫(kù)構(gòu)建自定義問(wèn)答應(yīng)用程序
構(gòu)建自定義聊天機(jī)器人,以使用 LangChain、OpenAI 和 PineconeDB 從任何數(shù)據(jù)源開(kāi)發(fā)問(wèn)答應(yīng)用程序
介紹
大型語(yǔ)言模型的出現(xiàn)是我們這個(gè)時(shí)代最令人興奮的技術(shù)發(fā)展之一。它為人工智能領(lǐng)域開(kāi)辟了無(wú)限可能,為各行業(yè)的現(xiàn)實(shí)問(wèn)題提供了解決方案。這些模型最有趣的應(yīng)用之一是開(kāi)發(fā)來(lái)自個(gè)人或組織數(shù)據(jù)源的自定義問(wèn)答或聊天機(jī)器人。然而,由于LLMS接受的是公開(kāi)可用的一般數(shù)據(jù)的培訓(xùn),因此他們的答案可能并不總是具體或?qū)ψ罱K用戶有用。為了解決這個(gè)問(wèn)題,我們可以使用LangChain等框架來(lái)開(kāi)發(fā)自定義聊天機(jī)器人,根據(jù)我們的數(shù)據(jù)提供特定的答案。在本文中,我們將學(xué)習(xí)如何構(gòu)建自定義問(wèn)答應(yīng)用程序并部署在 Streamlit Cloud 上。那么讓我們開(kāi)始吧!
學(xué)習(xí)目標(biāo)
- 了解為什么自定義問(wèn)答應(yīng)用程序比微調(diào)語(yǔ)言模型更好
- 學(xué)習(xí)使用 OpenAI 和 Pinecone 開(kāi)發(fā)語(yǔ)義搜索管道
- 開(kāi)發(fā)自定義問(wèn)答應(yīng)用程序并將其部署在 Streamlit 云上。
目錄
- 問(wèn)答應(yīng)用概述
- 什么是 Pinecone 矢量數(shù)據(jù)庫(kù)?
- 使用 OpenAI 和 Pinecone 構(gòu)建語(yǔ)義搜索管道
- 帶 Streamlit 的自定義問(wèn)答應(yīng)用程序
- 結(jié)論
- 常見(jiàn)問(wèn)題解答
問(wèn)答應(yīng)用概述
問(wèn)答或“通過(guò)數(shù)據(jù)聊天”是LLMs 和 LangChain 的一個(gè)流行用例。LangChain 提供了一系列組件來(lái)加載您可以為您的用例找到的任何數(shù)據(jù)源。它支持大量數(shù)據(jù)源和轉(zhuǎn)換器轉(zhuǎn)換為一系列字符串以存儲(chǔ)在矢量數(shù)據(jù)庫(kù)中。一旦數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫(kù)中,就可以使用稱為檢索器的組件查詢數(shù)據(jù)庫(kù)。此外,通過(guò)使用LLMS,我們可以像聊天機(jī)器人一樣獲得準(zhǔn)確的答案,而無(wú)需處理大量文檔。
LangChain支持以下數(shù)據(jù)源。如圖所示,它允許超過(guò) 120 個(gè)集成來(lái)連接您可能擁有的每個(gè)數(shù)據(jù)源。
圖片
問(wèn)答應(yīng)用程序工作流程
我們了解了LangChain支持的數(shù)據(jù)源,這使我們能夠使用LangChain中可用的組件開(kāi)發(fā)問(wèn)答管道。以下是 LLM 用于文檔加載、存儲(chǔ)、檢索和生成輸出的組件。
- 文檔加載器:加載用戶文檔以進(jìn)行矢量化和存儲(chǔ)
- 文本分割器:這些是文檔轉(zhuǎn)換器,可將文檔轉(zhuǎn)換為固定的塊長(zhǎng)度以有效地存儲(chǔ)它們
- 矢量存儲(chǔ):矢量數(shù)據(jù)庫(kù)集成,用于存儲(chǔ)輸入文本的矢量嵌入
- 文檔檢索:根據(jù)用戶對(duì)數(shù)據(jù)庫(kù)的查詢來(lái)檢索文本。他們使用相似性搜索技術(shù)來(lái)檢索相同的內(nèi)容。
- 模型輸出:根據(jù)查詢的輸入提示和檢索到的文本生成的用戶查詢的最終模型輸出。
這是問(wèn)答管道的高級(jí)工作流程,可以解決多種類型的現(xiàn)實(shí)問(wèn)題。我沒(méi)有深入研究每個(gè) LangChain 組件
圖片
自定義問(wèn)答相對(duì)于模型微調(diào)的優(yōu)勢(shì)
- 針對(duì)具體情況的答案
- 適應(yīng)新的輸入文檔
- 無(wú)需對(duì)模型進(jìn)行微調(diào),節(jié)省模型訓(xùn)練成本
- 比一般答案更準(zhǔn)確和具體的答案
什么是Pinecone 矢量數(shù)據(jù)庫(kù)?
Pinecone
Pinecone 是一種流行的矢量數(shù)據(jù)庫(kù),用于構(gòu)建 LLM 支持的應(yīng)用程序。它具有多功能性和可擴(kuò)展性,適用于高性能人工智能應(yīng)用。它是一個(gè)完全托管的云原生矢量數(shù)據(jù)庫(kù),不會(huì)給用戶帶來(lái)任何基礎(chǔ)設(shè)施麻煩。
LLMS基礎(chǔ)應(yīng)用程序涉及大量非結(jié)構(gòu)化數(shù)據(jù),需要復(fù)雜的長(zhǎng)期記憶才能以最大準(zhǔn)確度檢索信息。生成式人工智能應(yīng)用程序依靠向量嵌入的語(yǔ)義搜索來(lái)根據(jù)用戶輸入返回合適的上下文。
Pinecone 非常適合此類應(yīng)用程序,并經(jīng)過(guò)優(yōu)化以低延遲存儲(chǔ)和查詢大量向量,以構(gòu)建用戶友好的應(yīng)用程序。讓我們學(xué)習(xí)如何為我們的問(wèn)答應(yīng)用程序設(shè)置松果矢量數(shù)據(jù)庫(kù)。
# install pinecone-client
pip install pinecone-client
# 導(dǎo)入 pinecone 并使用您的 API 密鑰和環(huán)境名稱進(jìn)行初始化
import pinecone
pinecone.init(api_key= "YOUR_API_KEY" ,envirnotallow= "YOUR_ENVIRONMENT" )
# 創(chuàng)建您的第一個(gè)索引以開(kāi)始存儲(chǔ)Vectors
pinecone.create_index( "first_index" ,Dimension= 8 , metric= "cosine" )
# 更新插入樣本數(shù)據(jù)(5個(gè)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() 方法調(diào)用 db 中可用的多個(gè)索引
pinecone.list_indexes()
[Output]>>> [ 'first_index' ]
在上面的演示中,我們安裝了一個(gè)pinecone客戶端來(lái)初始化我們項(xiàng)目環(huán)境中的矢量數(shù)據(jù)庫(kù)。初始化向量數(shù)據(jù)庫(kù)后,我們可以創(chuàng)建具有所需維度和度量的索引,以將向量嵌入插入到向量數(shù)據(jù)庫(kù)中。在下一節(jié)中,我們將使用 Pinecone 和 LangChain 為我們的應(yīng)用程序開(kāi)發(fā)語(yǔ)義搜索管道。
使用 OpenAI 和 Pinecone 構(gòu)建語(yǔ)義搜索管道
我們了解到問(wèn)答應(yīng)用程序工作流程有 5 個(gè)步驟。在本節(jié)中,我們將執(zhí)行前 4 個(gè)步驟,即文檔加載器、文本拆分器、向量存儲(chǔ)和文檔檢索。
要在本地環(huán)境或云基礎(chǔ)筆記本環(huán)境(例如 Google Colab)中執(zhí)行這些步驟,您需要安裝一些庫(kù)并在 OpenAI 和 Pinecone 上創(chuàng)建一個(gè)帳戶以分別獲取它們的 API 密鑰。讓我們從環(huán)境設(shè)置開(kāi)始:
安裝所需的庫(kù)
# install langchain and openai with other dependencies
!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/detectron2.git@v0.6#egg=detectron2 -q
!apt-get install poppler-utils
!pip install pinecone-client -q
!pip install tiktoken -q
# setup openai environment
import os
os.environ["OPENAI_API_KEY"] = "YOUR-API-KEY"
# importing libraries
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
安裝設(shè)置完成后,導(dǎo)入上述代碼片段中提到的所有庫(kù)。然后,按照以下步驟操作:
加載文檔
在此步驟中,我們將從目錄加載文檔作為 AI 項(xiàng)目管道的起點(diǎn)。我們的目錄中有 2 個(gè)文檔,我們將把它們加載到項(xiàng)目環(huán)境中。
#load the documents from content/data dir
directory = '/content/data'
# load_docs functions to load documents using langchain function
def load_docs(directory):
loader = DirectoryLoader(directory)
documents = loader.load()
return documents
documents = load_docs(directory)
len(documents)
[Output]>>> 5
分割文本數(shù)據(jù)
如果每個(gè)文檔的長(zhǎng)度固定,文本嵌入和LLMS的性能會(huì)更好。因此,對(duì)于任何LLMS用例來(lái)說(shuō),將文本分割成相等長(zhǎng)度的塊是必要的。我們將使用“RecursiveCharacterTextSplitter”將文檔轉(zhuǎn)換為與文本文檔相同的大小。
# split the docs using recursive text splitter
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
# split the docs
docs = split_docs(documents)
print(len(docs))
[Output]>>>12
將數(shù)據(jù)存儲(chǔ)在向量存儲(chǔ)中
一旦文檔被分割,我們將使用 OpenAI 嵌入將它們的嵌入存儲(chǔ)在向量數(shù)據(jù)庫(kù)中。
# embedding example on random word
embeddings = OpenAIEmbeddings()
# initiate pinecondb
pinecone.init(
api_key="YOUR-API-KEY",
envirnotallow="YOUR-ENV"
)
# define index name
index_name = "langchain-project"
# store the data and embeddings into pinecone index
index = Pinecone.from_documents(docs, embeddings, index_name=index_name)
從向量數(shù)據(jù)庫(kù)中檢索數(shù)據(jù)
在此階段,我們將使用語(yǔ)義搜索從矢量數(shù)據(jù)庫(kù)中檢索文檔。我們將向量存儲(chǔ)在名為“l(fā)angchain-project”的索引中,一旦我們查詢到與下面相同的內(nèi)容,我們就會(huì)從數(shù)據(jù)庫(kù)中獲得最相似的文檔。
# An example query to our database
query = "What are the different types of pet animals are there?"
# do a similarity search and store the documents in result variable
result = index.similarity_search(
query, # our search query
k=3 # return 3 most relevant docs
)
-
--------------------------------[Output]--------------------------------------
result
[Document(page_cnotallow='Small mammals like hamsters, guinea pigs,
and rabbits are often chosen for their
low maintenance needs. Birds offer beauty and song,
and reptiles like turtles and lizards can make intriguing pets.',
metadata={'source': '/content/data/Different Types of Pet Animals.txt'}),
Document(page_cnotallow='Pet animals come in all shapes and sizes, each suited
to different lifestyles and home environments. Dogs and cats are the most
common, known for their companionship and unique personalities. Small',
metadata={'source': '/content/data/Different Types of Pet Animals.txt'}),
Document(page_cnotallow='intriguing pets. Even fish, with their calming presence
, can be wonderful pets.',
metadata={'source': '/content/data/Different Types of Pet Animals.txt'})]
我們可以根據(jù)相似性搜索從向量存儲(chǔ)中檢索文檔。
帶 Streamlit 的自定義問(wèn)答應(yīng)用程序
在問(wèn)答應(yīng)用程序的最后階段,我們將集成工作流程的每個(gè)組件來(lái)構(gòu)建自定義問(wèn)答應(yīng)用程序,該應(yīng)用程序允許用戶輸入各種數(shù)據(jù)源(例如基于網(wǎng)絡(luò)的文章、PDF、CSV 等)與其聊天。從而使他們?cè)谌粘;顒?dòng)中富有成效。我們需要?jiǎng)?chuàng)建一個(gè) GitHub 存儲(chǔ)庫(kù)并將以下文件添加到其中。
圖片
GitHub 倉(cāng)庫(kù)結(jié)構(gòu)
需要添加的項(xiàng)目文件:
- main.py — 包含流式前端代碼的 python 文件
- qanda.py — 提示設(shè)計(jì)和模型輸出函數(shù),返回用戶查詢的答案
- utils.py — 加載和分割輸入文檔的實(shí)用函數(shù)
- vector_search.py — 文本嵌入和向量存儲(chǔ)函數(shù)
- requirements.txt - 在 Streamlit 公共云中運(yùn)行應(yīng)用程序的項(xiàng)目依賴項(xiàng)
我們?cè)诖隧?xiàng)目演示中支持兩種類型的數(shù)據(jù)源:
- 基于 Web URL 的文本數(shù)據(jù)
- 在線 PDF 文件
這兩種類型包含廣泛的文本數(shù)據(jù),并且在許多用例中最常見(jiàn)。您可以查看下面的main.py python 代碼來(lái)了解應(yīng)用程序的用戶界面。
# import necessary libraries
import streamlit as st
import openai
import qanda
from vector_search import *
from utils import *
from io import StringIO
# take openai api key in
api_key = st.sidebar.text_input("Enter your OpenAI API key:", type='password')
# open ai key
openai.api_key = str(api_key)
# header of the app
_ , col2,_ = st.columns([1,7,1])
with col2:
col2 = st.header("Simplchat: Chat with your data")
url = False
query = False
pdf = False
data = False
# select option based on user need
options = st.selectbox("Select the type of data source",
optinotallow=['Web URL','PDF','Existing data source'])
#ask a query based on options of data sources
if options == 'Web URL':
url = st.text_input("Enter the URL of the data source")
query = st.text_input("Enter your query")
button = st.button("Submit")
elif options == 'PDF':
pdf = st.text_input("Enter your PDF link here")
query = st.text_input("Enter your query")
button = st.button("Submit")
elif options == 'Existing data source':
data= True
query = st.text_input("Enter your query")
button = st.button("Submit")
# write code to get the output based on given query and data sources
if button and url:
with st.spinner("Updating the database..."):
corpusData = scrape_text(url)
encodeaddData(corpusData,url=url,pdf=False)
st.success("Database Updated")
with st.spinner("Finding an answer..."):
title, res = find_k_best_match(query,2)
context = "\n\n".join(res)
st.expander("Context").write(context)
prompt = qanda.prompt(context,query)
answer = qanda.get_answer(prompt)
st.success("Answer: "+ answer)
# write a code to get output on given query and data sources
if button and pdf:
with st.spinner("Updating the database..."):
corpusData = pdf_text(pdf=pdf)
encodeaddData(corpusData,pdf=pdf,url=False)
st.success("Database Updated")
with st.spinner("Finding an answer..."):
title, res = find_k_best_match(query,2)
context = "\n\n".join(res)
st.expander("Context").write(context)
prompt = qanda.prompt(context,query)
answer = qanda.get_answer(prompt)
st.success("Answer: "+ answer)
if button and data:
with st.spinner("Finding an answer..."):
title, res = find_k_best_match(query,2)
context = "\n\n".join(res)
st.expander("Context").write(context)
prompt = qanda.prompt(context,query)
answer = qanda.get_answer(prompt)
st.success("Answer: "+ answer)
# delete the vectors from the database
st.expander("Delete the indexes from the database")
button1 = st.button("Delete the current vectors")
if button1 == True:
index.delete(deleteAll='true')
在streamlit云上部署問(wèn)答應(yīng)用程序
圖片
應(yīng)用程序用戶界面
Streamlit 提供社區(qū)云來(lái)免費(fèi)托管應(yīng)用程序。此外,streamlit 由于其自動(dòng)化 CI/CD 管道功能而易于使用。
結(jié)論
總之,我們探索了使用 LangChain 和 Pinecone 矢量數(shù)據(jù)庫(kù)構(gòu)建自定義問(wèn)答應(yīng)用程序的令人興奮的可能性。本博客向我們介紹了基本概念,從問(wèn)答應(yīng)用程序的概述開(kāi)始,到了解 Pinecone 矢量數(shù)據(jù)庫(kù)的功能。通過(guò)將 OpenAI 語(yǔ)義搜索管道的強(qiáng)大功能與 Pinecone 高效的索引和檢索系統(tǒng)相結(jié)合,我們充分利用了利用 Streamlit 創(chuàng)建強(qiáng)大且準(zhǔn)確的問(wèn)答解決方案的潛力。
常見(jiàn)問(wèn)題解答
Q1:什么是Pinecone和LangChain ?
答:Pinecone 是一個(gè)可擴(kuò)展的長(zhǎng)期記憶向量數(shù)據(jù)庫(kù),用于存儲(chǔ) LLM 支持的應(yīng)用程序的文本嵌入,而 LangChain 是一個(gè)允許開(kāi)發(fā)人員構(gòu)建 LLM 支持的應(yīng)用程序的框架
Q2:NLP問(wèn)答有什么應(yīng)用?
答:?jiǎn)柎饝?yīng)用程序用于客戶支持聊天機(jī)器人、學(xué)術(shù)研究、電子學(xué)習(xí)等。
Q3:為什么要使用LangChain ?
答:與LLMS合作可能會(huì)很復(fù)雜。LangChain允許開(kāi)發(fā)人員使用各種組件以對(duì)開(kāi)發(fā)人員最友好的方式集成這些LLM,從而更快地交付產(chǎn)品。
Q4:構(gòu)建問(wèn)答應(yīng)用程序的步驟是什么?
A:構(gòu)建問(wèn)答應(yīng)用的步驟如下:文檔加載、文本分割、向量存儲(chǔ)、檢索、模型輸出。
Q5:LangChain 工具有哪些?
答:LangChain 有以下工具:文檔加載器、文檔轉(zhuǎn)換器、向量存儲(chǔ)、鏈、內(nèi)存和代理。