通過檢索增強生成(RAG) 增強LLM的實戰演練 原創
本文主要介紹如何通過檢索增強生成(RAG)增強LLM,并使用LlamaIndex和LangChain作為數據場景,將應用程序部署到Heroku。
擁有正確的數據來支持用例對于在任何業務中成功采用大型語言模型(LLM)都是至關重要的。雖然大多數現成的LLM在完成一般任務上表現出色,但它們在處理特定的業務問題時可能會遇到困難。它們沒有針對開發人員的業務問題進行數據訓練,因此沒有足夠的場景來解決問題。
企業通常擁有大量的內部數據和文檔,可以滿足特定場景的需求。但是有一個問題:如何將所有這些有用的數據(場景)集成到LLM中,而不需要進行資源密集型和耗時的再培訓或微調LLM?
其答案是檢索增強生成(RAG),這是一種通過實時檢索緊密場景信息來增強LLM的技術。
本文將介紹如何使用LlamaIndex和LangChain來實現LLM場景數據的存儲和檢索。將通過使用LlamaIndex解決一個特定于場景的RAG問題,然后將解決方案輕松地部署到Heroku。
在開始編碼之前,首先簡要介紹一下核心概念。
RAG和LlamaIndex簡介
當向LLM提出一個需要場景來回答的問題時,RAG會檢索場景數據幫助LLM給出更準確、更具體的回答。這就像讓廚師迅速前往農貿市場去采購儲藏室沒有的最新鮮的食材一樣,這樣廚師長就可以采用所有必要的食材烹制出完美的菜肴。
RAG工作流如何提供場景的一個關鍵是使用矢量數據庫和矢量搜索索引。以下了解一些核心概念以及實現這一切所包含的內容。
- 向量是一組編碼數字,表示一段文本(例如單詞、短語、句子,甚至整個文檔)的含義和場景。
- 嵌入是向量中的實際數值,但大多數人傾向于交替使用 “向量”和“嵌入”這兩個術語。
- 已經在文檔上訓練了嵌入模型,以便它可以將新輸入的文本轉換為向量。并非所有的文本都以相同的方式談論相同的事情——考慮學術研究論文與營銷材料的不同。因此,有不同的嵌入模型——每個模型都在特定的數據集上訓練,并且考慮特定的目標。
- 使用嵌入模型,可以從文檔中創建嵌入,將這些文檔中的文本分解為編碼數字。創建嵌入可能涉及到文檔分塊這樣的策略,它將大文檔分成更小的、可管理的部分。從那里,每個塊被轉換成一個嵌入。
- 當查詢一個向量數據庫時,其問題被轉換成一個嵌入,并與存儲在矢量數據庫中的所有其他嵌入進行比較。
- 當建立一個向量搜索索引,可以執行非常快速和準確的向量搜索(也稱為相似性搜索)。使用矢量數據庫可以執行快速而準確的搜索——不僅僅是像在傳統數據庫中那樣匹配特定字符串的存在,而且還可以匹配與使用的單詞在意義上相似的文檔。
在RAG場景中,使用原始提示符對矢量數據庫中的所有文檔執行矢量搜索。然后,將這些匹配的文檔作為場景發送到LLM應用程序。LLM現在有一組詳細的注釋,在對原始提示進行回答時可以參考。
LlamaIndex是一個關鍵的框架,它簡化了集成、組織和檢索私有或專用數據的過程。它將幫助開發人員創建文檔嵌入和矢量搜索索引。然后,將依靠LangChain將其拼湊在一起,執行相似性搜索并將結果發送到LLM以獲取響應。LlamaIndex和LangChain共同為處理RAG工作流提供了一個安全可靠的解決方案。
準備好做些什么了嗎?現在開始吧!
演示項目簡介
使用LlamaIndex和Heroku學習RAG的最好方法是構建一個小型的示例應用程序。出于開發人員的目的,假設正在與“古登堡計劃”(Project Gutenberg)合作,這是一個擁有70000多本免費電子書的圖書館。如果想要構建一個基于LLM的聊天機器人,它可以回答關于項目中免費書籍的特定問題。
這是使用RAG的完美用例,可以使用LlamaIndex獲得的大量書籍文本。為了使項目簡單,將使用公元401年問世的《圣奧古斯丁的懺悔錄》書中的內容。
完成的項目代碼庫可以在這個GitHub存儲庫(https://github.com/alvinslee/llamaindex-gutenberg-demo)中找到。如果愿意的話,可以克隆repo并將應用程序部署到Heroku?;蛘撸梢灾鸩搅私馊绾潍@得代碼。
構建這個演示項目將遵循以下一些步驟:
- 設置項目。
- 加載數據。
- 構建索引。
- 存儲索引。
- 集成LangChain。
- 部署到Heroku。
步驟1:設置項目
為Python項目創建一個新文件夾,然后激活venv并安裝需要的初始依賴項。
Shell
1 (venv) ~/project$ pip install llama-index langchain langchain-openai
接下來,將加載要索引的數據。
步驟2:加載數據
在構建用于RAG的內部數據索引時,必須將所有數據(文本)收集到一個地方。在這個例子中,該項目采用了《圣奧古斯丁的懺悔錄》的文本。將使用LlamaIndex將這個場景轉換為嵌入的矢量索引。
在典型的用例中,其場景數據將是適合試圖解決的業務問題的大型文本語料庫。
對于這個小型演示項目,將創建一個名為data的子文件夾,然后將該書作為單個文件下載到該文件夾中。
Shell
1(venv) ~/project$ mkdir data
2
3 (venv) ~/project$ curl \
4 https://www.gutenberg.org/cache/epub/3296/pg3296.txt \
5 -o data/confessions.txt
6
7
8 (venv) ~/project$ ls data
9 confessions.txt
步驟3:構建索引
在一個目錄中收集了所有數據之后,就可以構建索引了。將編寫一個簡單的Python應用程序,它將使用LlamaIndex為數據建立索引,然后查詢索引。
為此,需要一個OpenAI帳戶和API密鑰。這是因為LlamaIndex使用OpenAI的text-embedding-3-small作為默認嵌入模型 (更改這些默認值超出了本文的討論范圍) 。
Python
1 # index.py
2
3 from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
4 import os
5
6 if os.environ.get('OPENAI_API_KEY') is None:
7 exit('You must provide an OPENAI_API_KEY env var.')
8
9 documents = SimpleDirectoryReader("data").load_data()
10 index = VectorStoreIndex.from_documents(documents)
11
12 query_engine = index.as_query_engine()
13 response = query_engine.query("In which city is Saint Augustine the Bishop?")
14 print(response)
運行文件并收到預期的響應:
Shell
1 (venv) ~/project$ OPENAI_API_KEY=sk-******** python index.py
2 Hippo
當然,可以再次檢查數據??纯催@本書的前幾行,可以看到:
Shell
1 THE CONFESSIONS OF SAINT AUGUSTINE
2
3 By Saint Augustine
4
5 Bishop of Hippo
正如人們所看到的,LlamaIndex完成了它的工作。Python應用程序完全按照開發人員對向量索引數據的期望回答了問題。
步驟4:存儲索引
需要注意的是,在上面的示例中,只將索引數據存儲在內存中,而不是磁盤上。索引(現在是內存中的一系列向量嵌入)將在調用OpenAI模型并完成工作流后完全丟失。
為文本創建向量索引(嵌入)不是免費的,所以不想每次調用模型時都重新計算這些結果。最好有一個單獨的工作流將索引持久化到磁盤。然后,可以在任何時候引用它。
一種常見的方法是將嵌入存儲在PostgreSQL數據庫中,并使用pgvector執行相似性搜索。為了使演示簡單,只將索引數據存儲為平面文件。
因此,將這個簡單的步驟添加到index.py文件中:
Python
1 PERSIST_DIR='./my_vector_indexes/gutenberg/'
2 index.storage_context.persist(persist_dir=PERSIST_DIR)
現在,在運行文件之后,可以檢查存儲的索引。
Shell
1 (venv) ~/project$ OPENAI_API_KEY=sk-******** python index.py
2 Hippo
3
4 (venv) ~/project$ tree
5 .
6├── data
7│ └── confessions.txt
8├── index.py
9└── my_vector_indexes
10 └── gutenberg
11 ├── default__vector_store.json
12 ├── docstore.json
13 ├── graph_store.json
14 ├── image__vector_store.json
15 └── index_store.json
16
17 3 directories, 7 files
步驟5:整合LangChain
已經了解矢量索引存儲的基礎知識,以及構建一個矢量索引存儲是多么容易。但是,為了真正構建一個將所有內容鏈接在一起的端到端應用程序,可以使用LangChain。這樣,就可以將解決方案部署為API。可以重寫index.py代碼,使其更適合生產環境。
以下將展示代碼,然后解釋接下來要做的事情。它可能看起來像很多代碼,但只添加了一些新步驟。
Python
1 # index.py
2
3 import os
4 from langchain_openai import ChatOpenAI
5 from langchain.chains import ConversationalRetrievalChain
6 from langchain.memory import ConversationBufferWindowMemory
7 from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
8 from langchain_community.retrievers import LlamaIndexRetriever
9 from fastapi import FastAPI
10 from pydantic import BaseModel
11
?12 if os.environ.get('OPENAI_API_KEY') is None:
13 exit('You must provide an OPENAI_API_KEY env var.')
14
15 documents = SimpleDirectoryReader("data").load_data()
16 index = VectorStoreIndex.from_documents(documents)
17
18 # For this demo, we will not persist the index.
19
20
21 retriever = LlamaIndexRetriever(index=index.as_query_engine())
22
23 llm = ChatOpenAI(model_name="gpt-3.5-turbo", max_tokens=2048)
24
25 memory = ConversationBufferWindowMemory(
26 memory_key='chat_history',
27 return_messages=True,
28 k=3
29 )
30
?31 conversation = ConversationalRetrievalChain.from_llm(
32 llm=llm,
33 retriever=retriever,
34 memory=memory,
35 max_tokens_limit=1536
36 )
37
38 class Prompt(BaseModel):
39 question: str
40
41 app = FastAPI()
42
43 @app.post("/prompt")
44 async def query_chatbot(prompt: Prompt):
45 response = conversation.invoke({'question': prompt.question})
46 return response['answer']
47
48 if __name__=='__main__':
49 import uvicorn
50 uvicorn.run(app, host="localhost", port=8000)
首先,要注意的是,現在直接使用了LangChain和OpenAI。將LLM與一些內存一起設置,以便在后續查詢中“記住”對話。現在有一個真正的聊天機器人,可以與之互動。
從這里,使用FastAPI創建一個API服務器,該服務器監聽/prompt端點上的POST請求。對該端點的請求預計具有帶有問題的請求體,然后將其(連同來自向量索引的場景)傳遞給LLM。
使用uvicorn在端口8000上啟動服務器。
在啟動服務器之前,添加這些新的Python依賴項:
Shell
1(venv) ~/project$ pip install fastapi pydantic uvicorn
現在是測試的時候了。首先啟動服務器。
Shell
1 (venv) ~/project$ OPENAI_API_KEY=sk-******** python index.py
2INFO: Started server process [1101807]
3 INFO: Waiting for application startup.
4 INFO: Application startup complete.
5 INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
在另一個終端中,向端點發送一個curl請求。
Shell
1 $ curl -X POST \
2 --header "Content-type:application/json" \
3 --data '{"question":"Who is Ambrose?"}' \
4 http://localhost:8000/prompt
5
6
7 "Ambrose is a person mentioned in the text provided. He is described as a respected
8 and celibate man who was esteemed by the author. Ambrose is depicted as a figure of
9 great honor and excellence, particularly known for his dedication to reading and
10 studying."
11
獲得成功!向量索引似乎已經啟動并運行,這個聊天機器人功能齊全,是部署的時候了。
步驟6:部署到Heroku
在完成了主要的工作之后,只需要采取幾個簡單的步驟將應用程序部署到Heroku。
(1)用Python依賴項創建requirements.txt文件
Heroku需要知道在構建項目時要安裝哪些Python依賴項。它在一個名為requirements.txt的文件中查找這個列表。可以用下面的命令輕松地生成:
Shell
1(venv) ~/project$ pip freeze > requirements.txt
(2)創建Procfile
還需要告訴Heroku如何啟動Python應用程序。在一個名為Procfile的文件中執行這一操作。
Shell
1 (venv) ~/project$ echo \
2 'web: uvicorn index:app --host=0.0.0.0 --port=${PORT}' > Procfile
(3)創建runtime.txt文件
最后,runtime.txt將告訴Heroku希望使用哪種Python運行時版本。
Shell
1 (venv) ~/project$ echo 'python-3.11.8' > runtime.txt
這些都是需要的文件。這時項目文件夾結構應該看起來像(已經刪除了持久化的矢量索引):
Shell
1 ~/project$ tree
2 .
3├── data
4│ └── confessions.txt
5├── index.py
6├── Procfile
7├── requirements.txt
8└── runtime.txt
9
10 1 directory, 5 files
如果開發人員是從頭開始工作,并且沒有為這個演示項目克隆GitHub倉庫,那么將這些文件提交到自己的Git存儲庫。
(4)創建Heroku應用程序
下載并安裝Heroku CLI后,執行如下命令。開發人員可以為其應用程序選擇任何名字,需要提供唯一的OpenAI API密鑰。
Shell
1 ~/project$ heroku login
2
3 ~/project$ heroku apps:create my-llamaindex-app
4
5 ~/project$ heroku git:remote -a my-llamaindex-app
6
7 ~/project$ heroku config:add OPENAI_API_KEY=replaceme -a my-llamaindex-app
8
9 ~/project$ git push heroku main
10 …
11 remote: -----> Building on the Heroku-22 stack
12 remote: -----> Determining which buildpack to use for this app
13 remote: -----> Python app detected
14 remote: -----> Using Python version specified in runtime.txt
15 …
16 remote: -----> Launching...
17 remote: Released v4
18 remote: https://my-llamaindex-app-6b48faa3ee6a.herokuapp.com/ deployed to Heroku
19
部署應用程序后,通過向API服務器發送curl請求進行測試:
Shell
1 $ curl -X POST \
2 --header "Content-type:application/json" \
3 --data '{"question":"Who is Ambrose?"}' \
4 https://my-llamaindex-app-6b48faa3ee6a.herokuapp.com/prompt
5
6 "Ambrose is a significant figure in the text provided. He is being described as a
7 respected and happy man, known for his celibacy and his dedication to reading and
8 studying. He is referred to as a holy oracle and a person of great influence and
9 wisdom."
10
需要記住的是,上面的curl調用在部署中使用了唯一的Heroku應用URL。
現在已經在Heroku上運行了!
結論
現在已經清楚地了解了LlamaIndex的強大功能,以及它在構建RAG應用程序與LLM交互時所扮演的重要角色。當可以很容易地添加特定的數據源作為LLM的場景,而不需采用成本昂貴的模型再訓練時,這是一個巨大的勝利。而對于希望進一步推進LLM工作流程的公司和開發人員來說,這也是一個勝利。
將LlamaIndex與其他LangChain工具集結合起來也是無縫且直接的,構建聊天機器人只需要幾行額外的代碼。最后,能夠快速輕松地將解決方案部署到Heroku,使應用程序可以立即訪問,而不會有任何麻煩。像這樣的簡單部署使開發人員能夠專注于構建基于LLM的解決方案這一更復雜、更重要的任務。
原文標題:How To Implement RAG: A Simple Walkthrough,作者:Alvin Lee
鏈接:https://dzone.com/articles/how-to-implement-rag-a-simple-walkthrough。
