【RAG 專案實戰 06】使用 LangChain 結合 Chainlit 實現文件問答
NLP Github 專案:
-
NLP 專案實踐:fasterai/nlp-project-practice
介紹:該倉庫圍繞著 NLP 任務模型的設計、訓練、最佳化、部署和應用,分享大模型演算法工程師的日常工作和實戰經驗
-
AI 藏經閣:https://gitee.com/fasterai/ai-e-book
介紹:該倉庫主要分享了數百本 AI 領域電子書
-
AI 演算法面經:fasterai/nlp-interview-handbook#面經
介紹:該倉庫一網打盡網際網路大廠NLP演算法面經,演算法求職必備神器
-
NLP 劍指Offer:https://gitee.com/fasterai/nlp-interview-handbook
介紹:該倉庫彙總了 NLP 演算法工程師高頻面題
[!NOTE] 問題彙總
- 如何新增rag-demo能力
- 如何將rag-demo的單輪問答轉化為多輪問答
- 如何改造rag-demo的提示詞:將 StringPrompt 轉化為 ChatPrompt
- 如何拼接和組合不同的Prompt
- 如何修改Hub中的Prompt
一、文件問答流程
檢索式問答的功能示意圖:
檢索式問答的系統流程圖:
二、文件問答系統的核心模組
2.1 環境配置
# @Author:青松
# 公眾號:FasterAI
# Python, version 3.10.14
# Pytorch, version 2.3.0
# Chainlit, version 1.1.301
2.2 文件分塊
使用文件分割器 RecursiveCharacterTextSplitter
將使用者上傳的檔案分割文字塊
# 配置檔案分割器,每個塊1000個token,重複100個
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
files = None
# 等待使用者上傳檔案
while files is None:
files = await cl.AskFileMessage(
content="Please upload a text file to begin!",
accept=["text/plain"],
max_size_mb=20,
timeout=180,
).send()
file = files[0]
# 傳送處理檔案的訊息
msg = cl.Message(content=f"Processing `{file.name}`...", disable_feedback=True)
await msg.send()
with open(file.path, "r", encoding="utf-8") as f:
text = f.read()
# 將檔案分割成文字塊
texts = text_splitter.split_text(text)
2.3 建立索引
# 將檔案分割成文字塊
texts = text_splitter.split_text(text)
# 為每個文字塊新增後設資料資訊
metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]
# 使用非同步方式建立 Chroma 向量資料庫
vectorstore = await cl.make_async(Chroma.from_texts)(
texts, embeddings_model, metadatas=metadatas
)
2.4 建立RAG鏈
# 建立會話歷史記錄
message_history = ChatMessageHistory()
# 使用 ConversationBufferMemory 記憶元件儲存歷史記錄
memory = ConversationBufferMemory(
memory_key="chat_history",
output_key="answer",
chat_memory=message_history,
return_messages=True,
)
# todo:即將棄用,使用 create_history_aware_retriever 結合 create_retrieval_chain 替換
# 使用 Chroma vector store 建立一個檢索鏈
chain = ConversationalRetrievalChain.from_llm(
llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(),
memory=memory,
return_source_documents=True,
)
cl.user_session.set("chain", chain)
2.5 檢索生成
@cl.on_message
async def on_message(message: cl.Message):
""" 監聽使用者訊息事件 """
chain = cl.user_session.get("chain") # type: ConversationalRetrievalChain
cb = cl.AsyncLangchainCallbackHandler()
# todo 即將棄用,需要替換
res = await chain.acall(message.content, callbacks=[cb])
# 大模型檢索生成的答案
answer = res["answer"]
await cl.Message(content=answer).send()
三、效果展示
啟動程式:
chainlit run rag_app.py -w
系統截圖:
四、完整程式碼
# @Author:青松
# 公眾號:FasterAI
# Python, version 3.10.14
# Pytorch, version 2.3.0
# Chainlit, version 1.1.301
import chainlit as cl
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ChatMessageHistory, ConversationBufferMemory
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
import llm_util
from common import Constants
# 獲取大模型例項
llm = llm_util.get_llm(Constants.MODEL_NAME['QianFan'])
# 獲取文字嵌入模型
model_name = "BAAI/bge-small-zh-v1.5"
encode_kwargs = {"normalize_embeddings": True}
embeddings_model = HuggingFaceBgeEmbeddings(
model_name=model_name, encode_kwargs=encode_kwargs
)
# 配置檔案分割器,每個塊1000個token,重複100個
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
@cl.on_chat_start
async def on_chat_start():
""" 監聽會話開始事件 """
await send_welcome_msg()
files = None
# 等待使用者上傳檔案
while files is None:
files = await cl.AskFileMessage(
content="Please upload a text file to begin!",
accept=["text/plain"],
max_size_mb=20,
timeout=180,
).send()
file = files[0]
# 傳送處理檔案的訊息
msg = cl.Message(content=f"Processing `{file.name}`...", disable_feedback=True)
await msg.send()
with open(file.path, "r", encoding="utf-8") as f:
text = f.read()
# 將檔案分割成文字塊
texts = text_splitter.split_text(text)
# 為每個文字塊新增後設資料資訊
metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]
# 使用非同步方式建立 Chroma 向量資料庫
vectorstore = await cl.make_async(Chroma.from_texts)(
texts, embeddings_model, metadatas=metadatas
)
# 建立會話歷史記錄
message_history = ChatMessageHistory()
# 使用 ConversationBufferMemory 記憶元件儲存歷史記錄
memory = ConversationBufferMemory(
memory_key="chat_history",
output_key="answer",
chat_memory=message_history,
return_messages=True,
)
# todo:即將棄用,使用 create_history_aware_retriever 結合 create_retrieval_chain 替換
# 使用 Chroma vector store 建立一個檢索鏈
chain = ConversationalRetrievalChain.from_llm(
llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(),
memory=memory,
return_source_documents=True,
)
# 通知使用者檔案已處理完成,更新當前視窗的內容
msg.content = f"Processing `{file.name}` done. You can now ask questions!"
await msg.update()
cl.user_session.set("chain", chain)
@cl.on_message
async def on_message(message: cl.Message):
""" 監聽使用者訊息事件 """
chain = cl.user_session.get("chain") # type: ConversationalRetrievalChain
cb = cl.AsyncLangchainCallbackHandler()
# todo 即將棄用
res = await chain.acall(message.content, callbacks=[cb])
# 大模型的回答
answer = res["answer"]
await cl.Message(content=answer).send()
async def send_welcome_msg():
image = cl.Image(url="https://qingsong-1257401904.cos.ap-nanjing.myqcloud.com/wecaht.png")
# 傳送一個圖片
await cl.Message(
content="**青松** 邀你關注 **FasterAI**, 讓每個人的 AI 學習之路走的更容易些!立刻掃碼開啟 AI 學習、面試快車道 **(^_^)** ",
elements=[image],
).send()
@cl.password_auth_callback
def auth_callback(username: str, password: str):
""" 持久化客戶端聊天曆史程式碼,不需要請刪除 """
if (username, password) == ("admin", "admin"):
return cl.User(
identifier="admin", metadata={"role": "admin", "provider": "credentials"}
)
else:
return None
【動手學 RAG】系列文章:
- 【RAG 專案實戰 01】在 LangChain 中整合 Chainlit
- 【RAG 專案實戰 02】Chainlit 持久化對話歷史
- 【RAG 專案實戰 03】優雅的管理環境變數
- 【RAG 專案實戰 04】新增多輪對話能力
- 【RAG 專案實戰 05】重構:封裝程式碼
- 【RAG 專案實戰 06】使用 LangChain 結合 Chainlit 實現文件問答
- 【RAG 專案實戰 07】替換 ConversationalRetrievalChain(單輪問答)
- 【RAG 專案實戰 08】為 RAG 新增歷史對話能力
- More...
【動手部署大模型】系列文章:
- 【模型部署】vLLM 部署 Qwen2-VL 踩坑記 01 - 環境安裝
- 【模型部署】vLLM 部署 Qwen2-VL 踩坑記 02 - 推理加速
- 【模型部署】vLLM 部署 Qwen2-VL 踩坑記 03 - 多圖支援和輸入格式問題
- More...
本文由mdnice多平臺釋出