動手學大模型應用開發,第6天:前后端搭建
第一章 項目代碼簡析
根據前面講解的內容,我們逐步學習了如何調用不同的 LLM API,如何處理知識庫文檔搭建向量數據庫,再如何設計 Prompt 搭建基于數據庫的檢索問答鏈。現在,我們可以回顧前面學過的所有內容,結合項目設計思路,將上述學習內容實現成代碼,并按項目層次封裝,來支持后續部署的調用。在這一章,我們會結合項目代碼與前面學習的內容,講解我們是如何封裝項目代碼以接口的形式為部署層提供核心功能的。
一、LLM 調用
基于前文內容,我們可以將百度文心、訊飛星火、智譜 GLM 等國內大模型接口封裝成自定義的 LLM,然后接入到 LangChain 架構中。由于我們的項目需要將上述三種大模型接口都進行封裝來統一到我們的項目框架中,為了項目的簡潔,我們首先定義了一個自定義 LLM 的基類:
class Self_LLM(LLM):
# 自定義 LLM
# 繼承自 langchain.llms.base.LLM
# 原生接口地址
url : str = None
# 默認選用 GPT-3.5 模型,即目前一般所說的百度文心大模型
model_name: str = "gpt-3.5-turbo"
# 訪問時延上限
request_timeout: float = None
# 溫度系數
temperature: float = 0.1
# API_Key
api_key: str = None
# 必備的可選參數
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
Self_LLM 類定義在 /llm/self_llm.py 文件中,抽出調用三種大模型共同需要的參數(例如模型調用網址、使用模型名字、溫度系數、API_Key 等)。在 Self_LLM 類基礎上,我們分別繼承了三個大模型 API 的自定義 LLM:Wenxin_LLM(/llm/wenxin_llm.py)、Spark_LLM(/llm/spark_llm.py)、ZhipuAILLM(/llm/zhipuai_llm.py),三個子類分別定義了本 API 所獨有的參數,并基于 API 調用方式重寫了 _call 方法。如果對封裝方法有所疑問,可以詳細閱讀第二章,我們有講解每一種 API 的調用方法。封裝的自定義 LLM,實則就是將各自的調用方法按照給定的結構重寫到 _call 方法中。
通過如上封裝,在上一層檢索問答鏈搭建時可以直接調用各個自定義 LLM 類,從而無需關注不同 API 調用的細節。
同時,為調用方便,我們也封裝了統一的 get_completion 函數(/llm/call_llm.py):
def get_completion(prompt :str, model :str, temperature=0.1,api_key=None,
secret_key=None, access_token=None, appid=None,
api_secret=None, max_tokens=2048) -> str
該函數將四種模型 API 的原生接口封裝在一起,旨在通過這一個函數來調用所有的模型。在這個函數內部,我們解析了 model 參數的值,分別映射到不同的 API 調用函數中:
if model in ["gpt-3.5-turbo", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-0613", "gpt-4", "gpt-4-32k"]:
return get_completion_gpt(prompt, model, temperature, api_key, max_tokens)
elif model in ["ERNIE-Bot", "ERNIE-Bot-4", "ERNIE-Bot-turbo"]:
return get_completion_wenxin(prompt, model, temperature, api_key, secret_key)
elif model in ["Spark-1.5", "Spark-2.0"]:
return get_completion_spark(prompt, model, temperature, api_key, appid, api_secret, max_tokens)
elif model in ["chatglm_pro", "chatglm_std", "chatglm_lite"]:
return get_completion_glm(prompt, model, temperature, api_key, max_tokens)
else:
return "不正確的模型"
對于其中映射的每一個子函數(包括 get_completion_gpt、get_completion_wenxin 等),我們都以類似于第二章講解的方式進行了封裝。在后續調用中,可以直接使用 get_completion 函數,通過傳入不同的模型參數和 API_KEY 認證,可以隱藏掉 API 調用細節。
二、數據庫構建
我們將在第四章中講解過的構建項目數據庫方法封裝成 create_db 函數,函數中封裝了各種文件類型的源文件處理方法和最終向量數據庫的構建。如果本地沒有構建過向量數據庫,可以直接調用該方法:
def create_db(files=DEFAULT_DB_PATH, persist_directory=DEFAULT_PERSIST_PATH, embeddings="openai"):
"""
該函數用于加載源數據文件,切分文檔,生成文檔的嵌入向量,創建向量數據庫。
參數:
file: 存放文件的路徑。
embeddings: 用于生產 Embedding 的模型
返回:
vectordb: 創建的數據庫。
"""
在該函數內部,我們構造了一個文件加載映射函數,該函數會針對源文件類型分配不同的文件加載器,從而實現對不通過文件的處理:
def file_loader(file, loaders):
# 對于多種文檔的 FileLoader 映射
if isinstance(file, tempfile._TemporaryFileWrapper):
file = file.name
if not os.path.isfile(file):
[file_loader(os.path.join(file, f), loaders) for f in os.listdir(file)]
return
file_type = file.split('.')[-1]
if file_type == 'pdf':
loaders.append(PyMuPDFLoader(file))
elif file_type == 'md':
loaders.append(UnstructuredMarkdownLoader(file))
elif file_type == 'txt':
loaders.append(UnstructuredFileLoader(file))
return
同時,針對已構造向量數據庫需要調用的情況,我們也封裝了 get_vectordb 函數,在檢索問答鏈中只需直接調用該函數獲取已構建的數據庫即可:
def get_vectordb(file_path:str=None, persist_path:str=None, embedding = "openai",embedding_key:str=None):
"""
返回向量數據庫對象
輸入參數:
question:
llm:
vectordb:向量數據庫(必要參數),一個對象
template:提示模版(可選參數)可以自己設計一個提示模版,也有默認使用的
embedding:可以使用zhipuai等embeddin,不輸入該參數則默認使用 openai embedding,注意此時api_key不要輸錯
"""
三、檢索問答鏈
基于 LLM 層與 Database 層的封裝,我們可以在應用層搭建自定義的檢索問答鏈,封裝實現項目的核心功能。
首先我們封裝了一個 LLM 映射函數,該函數會根據傳入 model 參數的不同映射到不同的 LLM 對象,從而實現不同 API 來源 LLM 的切換:
def model_to_llm(model:str=None, temperature:float=0.0, appid:str=None, api_key:str=None,Spark_api_secret:str=None,Wenxin_secret_key:str=None):
"""
星火:model,temperature,appid,api_key,api_secret
百度文心:model,temperature,api_key,api_secret
智譜:model,temperature,api_key
OpenAI:model,temperature,api_key
"""
if model in ["gpt-3.5-turbo", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-0613", "gpt-4", "gpt-4-32k"]:
if api_key == None:
api_key = parse_llm_api_key("openai")
llm = ChatOpenAI(model_name = model, temperature = temperature , openai_api_key = api_key)
elif model in ["ERNIE-Bot", "ERNIE-Bot-4", "ERNIE-Bot-turbo"]:
if api_key == None or Wenxin_secret_key == None:
api_key, Wenxin_secret_key = parse_llm_api_key("wenxin")
llm = Wenxin_LLM(model=model, temperature = temperature, api_key=api_key, secret_key=Wenxin_secret_key)
elif model in ["Spark-1.5", "Spark-2.0"]:
if api_key == None or appid == None and Spark_api_secret == None:
api_key, appid, Spark_api_secret = parse_llm_api_key("spark")
llm = Spark_LLM(model=model, temperature = temperature, appid=appid, api_secret=Spark_api_secret, api_key=api_key)
elif model in ["chatglm_pro", "chatglm_std", "chatglm_lite"]:
if api_key == None:
api_key = parse_llm_api_key("zhipuai")
llm = ZhipuAILLM(model=model, zhipuai_api_key=api_key, temperature = temperature)
else:
raise ValueError(f"model{model} not support!!!")
return llm
在該映射器的基礎上,我們構建了我們的自定義檢索問答鏈。我們分別構建了兩種檢索問答鏈,QA_chain_self(/qa_chain/QA_chain_self.py)和 Chat_QA_chain_self(/qa_chain/Chat_QA_chain_self.py),分別對應普通的檢索問答鏈和加入歷史會話的檢索問答鏈。兩種自定義檢索問答鏈內部實現細節類似,只是調用了不同的 LangChain 鏈。
在自定義檢索問答鏈內部,我們首先在構造函數中定義了長期參數的賦值,并且調用了 LLM 映射器和數據庫構建(或加載)函數,從而實現了調用問答鏈的所有準備工作。然后我們定義了 answer 函數,該函數會對用戶的問題調用檢索問答鏈并輸出回答,同時,在每一次調用 answer 的時候我們都可以動態改變溫度系數和 top_k 參數:
def answer(self, question:str=None, temperature = None, top_k = 4):
""""
核心方法,調用問答鏈
arguments:
- question:用戶提問
"""
if len(question) == 0:
return ""
if temperature == None:
temperature = self.temperature
if top_k == None:
top_k = self.top_k
result = self.qa_chain({"query": question, "temperature": temperature, "top_k": top_k})
return result["result"]
在完成上述封裝之后,我們無需關注底層細節,只需實例化一個自定義檢索問答鏈對象,并調用其 answer 方法即可實現本項目的核心功能。同時,實例化時無需關注不同 API 的調用差異,直接傳入需要調用的模型參數即可。
后續的服務層部署,我們會直接在上述封裝代碼的基礎上進行調用,即直接調用自定義檢索問答鏈,而不再從頭實現全部過程。
第二章、Gradio 的介紹與前端界面的搭建 ??
我們對知識庫和LLM已經有了基本的理解,現在是將它們巧妙地融合并打造成一個富有視覺效果的界面的時候了。這樣的界面不僅對操作更加便捷,還能便于與他人分享。
Gradio 是一種快速便捷的方法,可以直接在 Python 中通過友好的 Web 界面演示機器學習模型。在本課程中,我們將學習如何使用它為生成式人工智能應用程序構建用戶界面。在構建了應用程序的機器學習或生成式人工智能后,如果你想構建一個demo給其他人看,也許是為了獲得反饋并推動系統的改進,或者只是因為你覺得這個系統很酷,所以想演示一下:Gradio 可以讓您通過 Python 接口程序快速實現這一目標,而無需編寫任何前端、網頁或 JavaScript 代碼。 加載 HF API 密鑰和相關 Python 庫
一、Gradio 簡介
Gradio 可以包裝幾乎任何 Python 函數為易于使用的用戶界面。
常用的基礎模塊構成如下:
- 應用界面:gradio.Interface(簡易場景), gradio.Blocks(定制化場景)
- 輸入輸出:gradio.Image(圖像), gradio.Textbox(文本框), gradio.DataFrame(數據框), gradio.Dropdown(下拉選項), gradio.Number(數字), gradio.Markdown(Markdown), gradio.Files(文件)
- 控制組件:gradio.Button(按鈕)
- 布局組件:gradio.Tab(標簽頁), gradio.Row(行布局), gradio.Column(列布局)
大部分功能模塊都可以通過以下三個參數進行初始化:
- fn:包裝的函數
- inputs:輸入組件類型,(例如:“text”、"image)
- ouputs:輸出組件類型,(例如:“text”、"image)
1.1 gradio.Interface() 搭建界面
# 導入所需的庫
import gradio as gr # 用于創建 Web 界面
import os # 用于與操作系統交互,如讀取環境變量
# 定義一個函數來根據輸入生成文本
def generate(input, temperature):
"""
該函數用于根據輸入生成文本。
參數:
input: 輸入內容。
temperature: LLM 的溫度系數。
返回:
output: 生成的文本。
"""
# 使用預定義的 client 對象的 predict 方法,從輸入生成文本
# slider 的值限制生成的token的數量
output = llm.predict(input, temperature=temperature)
return output # 返回生成的文本
# 創建一個 Web 界面
# 輸入:一個文本框和一個滑塊
# 輸出:一個文本框顯示生成的文本
demo = gr.Interface(
fn=generate,
inputs=[
gr.Textbox(label="Prompt"), # 文本輸入框
gr.Slider(label="Temperature", value=0, maximum=1, minimum=0) # 滑塊用于選擇模型的 temperature
],
outputs=[gr.Textbox(label="Completion")], # 顯示生成文本的文本框
title="Chat Robot", # 界面標題
description="Local Knowledge Base Q&A with llm", # 界面描述
# allow_flagging="never",
)
# 關閉可能已經啟動的任何先前的 gradio 實例
gr.close_all()
# 啟動 Web 界面
# 使用環境變量 PORT1 作為服務器的端口號
# demo.launch(share=True, server_port=int(os.environ['PORT1']))
demo.launch() # 直接啟動頁面
Running on local URL: http://127.0.0.1:7860
To create a public link, set `share=True` in `launch()`.
fn=generate: 這是用于處理輸入的函數,即文本生成函數 generate。inputs=[ gr.Textbox(label="Prompt"), gr.Slider(label="Temperature", value=0, maximum=1, minimum=0) ]: 這定義了模型的輸入。
使用 gr.Textbox 部件來以文本框的形式顯示輸入的內容描述,label 參數設置了輸入部件的標簽為 prompt。
使用 gr.Slider 部件以滑動條的形式來顯示輸入的內容描述,label 參數設置了輸入部件的標簽為 temperature。outputs=[gr.Textbox(label="Caption")]: 這定義了輸出部分。使用 gr.Textbox 部件來顯示生成的內容描述,label 參數設置了輸出部件的標簽。title="Chat Robot": 這是界面的標題,將顯示在界面的頂部。description="Local Knowledge Base Q&A with llm ": 這是界面的描述,提供有關界面功能的更多信息。allow_flagging="never": 這設置了不允許標記內容,確保不會顯示標記不恰當內容的選項。
通過 demo.launch() 啟動整個可視化前端界面。
我們可以對demo.launch 中的參數進行配置:
demo.launch(share=True, server_port=8080))
- share=True 表示生成外部可訪問的鏈接
- server_port=8080 表示運行的端口
這樣,外部的用戶也可以通過生成的鏈接直接訪問我們的界面。
現在我們已經搭建了一個非常簡單的 Gradio 界面,它有一個文本框輸入和一個輸出。我們已經可以非常簡單地向 LLM 提問。但我們還是不能對話,因為如果你再問一個后續問題,它就無法理解或保留上下文。
因此,基本上我們要做的是,向模型發送我們之前的問題、它自己的回答以及后續問題。但建立所有這些都有點麻煩。這就是 Gradio 聊天機器人組件的作用所在,因為它允許我們簡化向模型發送對話歷史記錄的過程。
因此,我們要解決這個問題。為此,我們將引入一個新的 Gradio 組件--Gradio Chatbot。
二、使用 gradio.Chatbot() 來助力聊天!
讓我們開始使用 Gradio Chatbot 組件。這里實例化了一個帶有文本框 prompt 和提交按鈕的 Gradle ChatBot 組件,是一個非常簡單的用戶界面。但我們現在還不是在和 LLM 聊天。
我們必須格式化聊天 prompt。此處正在定義這個格式化聊天 prompt 函數。 在這里,我們要做的就是使其包含聊天歷史記錄,這樣 LLM 就能知道上下文。 但這還不夠。我們還需要告訴它,哪些信息來自用戶,哪些信息來自 LLM 本身,也就是我們正在調用的助手。 因此,我們設置了格式聊天 prompt 功能,在聊天記錄的每一輪中,都包含一條用戶信息和一條助手信息,以便我們的模型能準確回答后續問題。 現在,我們要將格式化的 prompt 傳遞給我們的 API。
相比 Interface,Blocks 提供了一個低級別的 API,用于設計具有更靈活布局和數據流的網絡應用。Blocks 允許控制組件在頁面上出現的位置,處理復雜的數據流(例如,輸出可以作為其他函數的輸入),并根據用戶交互更新組件的屬性可見性。可以定制更多組件。
# 定義一個函數,用于格式化聊天 prompt。
def format_chat_prompt(message, chat_history):
"""
該函數用于格式化聊天 prompt。
參數:
message: 當前的用戶消息。
chat_history: 聊天歷史記錄。
返回:
prompt: 格式化后的 prompt。
"""
# 初始化一個空字符串,用于存放格式化后的聊天 prompt。
prompt = ""
# 遍歷聊天歷史記錄。
for turn in chat_history:
# 從聊天記錄中提取用戶和機器人的消息。
user_message, bot_message = turn
# 更新 prompt,加入用戶和機器人的消息。
prompt = f"{prompt}\nUser: {user_message}\nAssistant: {bot_message}"
# 將當前的用戶消息也加入到 prompt中,并預留一個位置給機器人的回復。
prompt = f"{prompt}\nUser: {message}\nAssistant:"
# 返回格式化后的 prompt。
return prompt
# 定義一個函數,用于生成機器人的回復。
def respond(message, chat_history):
"""
該函數用于生成機器人的回復。
參數:
message: 當前的用戶消息。
chat_history: 聊天歷史記錄。
返回:
"": 空字符串表示沒有內容需要顯示在界面上,可以替換為真正的機器人回復。
chat_history: 更新后的聊天歷史記錄
"""
# 調用上面的函數,將用戶的消息和聊天歷史記錄格式化為一個 prompt。
formatted_prompt = format_chat_prompt(message, chat_history)
# 使用llm對象的predict方法生成機器人的回復(注意:llm對象在此代碼中并未定義)。
bot_message = llm.predict(formatted_prompt,
max_new_tokens=1024,
stop_sequences=["\nUser:", ""])
# 將用戶的消息和機器人的回復加入到聊天歷史記錄中。
chat_history.append((message, bot_message))
# 返回一個空字符串和更新后的聊天歷史記錄(這里的空字符串可以替換為真正的機器人回復,如果需要顯示在界面上)。
return "", chat_history
# 下面的代碼是設置Gradio界面的部分。
# 使用Gradio的Blocks功能定義一個代碼塊。
with gr.Blocks() as demo:
# 創建一個Gradio聊天機器人組件,設置其高度為240。
chatbot = gr.Chatbot(height=240)
# 創建一個文本框組件,用于輸入 prompt。
msg = gr.Textbox(label="Prompt")
# 創建一個提交按鈕。
btn = gr.Button("Submit")
# 創建一個清除按鈕,用于清除文本框和聊天機器人組件的內容。
clear = gr.ClearButton(components=[msg, chatbot], value="Clear console")
# 設置按鈕的點擊事件。當點擊時,調用上面定義的respond函數,并傳入用戶的消息和聊天歷史記錄,然后更新文本框和聊天機器人組件。
btn.click(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
# 設置文本框的提交事件(即按下Enter鍵時)。功能與上面的按鈕點擊事件相同。
msg.submit(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
# 關閉所有已經存在的 Gradio 實例。
gr.close_all()
# 啟動新的 Gradio 應用,設置分享功能為 True,并使用環境變量 PORT1 指定服務器端口。
# demo.launch(share=True, server_port=int(os.environ['PORT1']))
demo.launch()
Closing server running on port: 7860
Running on local URL: http://127.0.0.1:7860
To create a public link, set `share=True` in `launch()`.
現在,我們的問答機器人可以回答后續問題了。 我們可以看到,我們向它發送了上下文。我們向它發送了信息,然后要求它完成。一旦我們進入另一個迭代循環,我們就會向它發送我們的整個上下文,然后要求它完成。這很酷。但是,如果我們一直這樣迭代下去,那么模型在一次對話中所能接受的信息量就會達到極限,因為我們總是給它越來越多的之前對話的內容。
這里,我們創建了一個簡單但功能強大的用戶界面,用于與LLM聊天。如果需要進一步Gradio 所能提供的最佳功能,我們可以創建一個包含更多功能的用戶界面。
三、 接入本地知識庫進行回答
3.1 綁定已封裝的函數
現在我們可以將本地知識庫的內容接入進來,讓 llm 運用本地數據庫進行回答。
我們之前已經學習過如何運用 LLM 對本地知識庫進行問答,現在讓我們將這個功能加入到我們的前端界面中。 回憶一下,我們學習了兩種運用本地知識庫進行問答的方式:
- Chat_QA_chain_self:記憶歷史記錄的問答
- QA_chain_self:直接進行問答,即沒有歷史記錄
我們需要創建兩個按鈕,分別與對應的函數相綁定。
初始化按鈕
db_with_his_btn = gr.Button("Chat db with history")
db_wo_his_btn = gr.Button("Chat db without history")
將按鈕與函數進行綁定,并且配置相應的輸入和輸出
# 設置按鈕的點擊事件。當點擊時,調用上面定義的 Chat_QA_chain_self 函數,并傳入用戶的消息和聊天歷史記錄,然后更新文本框和聊天機器人組件。
db_with_his_btn.click(Chat_QA_chain_self.answer, inputs=[msg, chatbot, llm, embeddings, history_len, top_k, temperature], outputs=[msg, chatbot])
# 設置按鈕的點擊事件。當點擊時,調用上面定義的 QA_chain_self 函數,并傳入用戶的消息和聊天歷史記錄,然后更新文本框和聊天機器人組件。
db_wo_his_btn.click(QA_chain_self.answer, inputs=[msg, chatbot, llm, embeddings, top_k, temperature], outputs=[msg, chatbot])
四、 豐富前端界面
4.1 gradio.File() 上傳文件
這里我們是直接運用前面章節生成好的向量數據庫。
當用戶想要在前端頁面上傳自己的文件生成新的數據庫時,gradio.File 可以很方便的完成這部分功能。
File 用于創建一個文件組件,允許用戶上傳通用文件(用作輸入)或顯示通用文件(用作輸出)。
- 作為輸入時,File 模塊文件按照 file_count 以 tempfile._TemporaryFileWrapper 或 List[tempfile._TemporaryFileWrapper] 的格式傳遞給綁定的函數,或者按照 type 的值轉化為 bytes/List[bytes]。
- 作為輸出時,File 模塊希望函數返回一個文件的路徑/包含文件路徑的列表(str / List[str])。
file_count 允許用戶上傳文件的數量,返回類型將根據 "multiple" 或 "directory" 的情況為每個文件返回一個列表。
- "single",允許用戶上傳一個文件。
- "multiple",允許用戶上傳多個文件。
- "directory",允許用戶上傳所選目錄中的所有文件。
file_types: 要上傳的文件擴展名或文件類型的列表(例如[‘image’,‘.json’,‘.mp4’])。字符串表示支持上傳的文件類型,格式后綴表示支持上傳的文件格式。
- "file": 允許上傳任何文件
- "image": 僅允許上傳圖像文件
- "audio": 僅允許上傳音頻文件
- "video": 僅允許上傳視頻文件
- "text": 僅允許上傳文本文件
- ".pdf": 僅允許上傳 pdf 文件
- ".md": 僅允許上傳 txt 文件
注意:當 file_count 為 "directory" 時,會忽略 "file_types" 的參數配置。
gr.File(label='請選擇知識庫目錄',file_count='directory',
file_types=['.txt', '.md', '.docx', '.pdf'])
4.2 gradio.Slider() 配置滑動條
對于本項目來說,存在很多可以配置的參數,比如 LLM 的溫度系數(temperature),這個參數的取值范圍為 0-1,控制著 LLM 生成內容的穩定性。
溫度基本上就是希望模型的變化程度。因此,如果將溫度設為零,模型就會傾向于始終對相同的輸入做出相同的反應。所以同樣的問題,同樣的答案。溫度越高,信息的變化就越多。但如果溫度過高,它就會開始給出無意義的答案。
我們想通過前端來進行參數的配置,但同時希望將參數限制在一個區間內,這時 gr.text 無法滿足我們的需求。gradio.Slider 是可以勝任這個任務的組件。
gradio.Slider 允許用戶通過滑動滑塊在指定的范圍內選擇一個值
- minimum:滑塊的最小值,默認為 0。
- maximum:滑塊的最大值,默認為 100。
- value: 滑塊的默認值,即作為可調用對象時的默認值。
- step:滑塊的步長,即每次滑動的增量,默認為None。
- label:組件在界面中顯示的名稱,默認為None。
- interactive: 滑塊是否可調節。如果未提供,則根據組件是用作輸入還是輸出進行推斷。
temperature = gr.Slider(0,
1,
value=0.00,
step=0.01,
label="llm temperature",
interactive=True)
我們可以將其他類似的參數采用相同的構建方式,如:
- 向量檢索的數量(top_k):從向量數據庫返回的最相關文檔的數量,LLM 會根據返回的文檔生成回答。
- 聊天歷史的長度(history_len):使聊天歷史保持一定的長度,防止過大消耗過多的 token。
4.3 gradio.Dropdown() 建立下拉列表
剛剛我們介紹了 gradio 對于連續值的選擇方法,現在我們來介紹下對于離散值的選擇方法。
我們可以切換不同的模型,嘗試不同模型的效果。我們用 gradio.Dropdown 來建立一個下拉列表,讓用戶從提供的模型中選擇一個模型。
- choices:可供選擇的選項列表。
- value:默認選中的值。如果為 None,則沒有默認選中值。如果為可調用對象,則在應用程序加載時調用該函數以設置組件的初始值。
- type:組件返回的值的類型。"value(返回選擇的字符串),"index"(返回選擇的索引)。
- multiselect:是否可以選擇多個選項。
- max_choices:可選擇的最大選項數。如果為None,則不限制數量。
- interactive: 滑塊是否可調節。如果未提供,則根據組件是用作輸入還是輸出進行推斷。
llm = gr.Dropdown(
llm_model_list,
label="large language model",
value=init_llm,
interactive=True)
同樣,我們可以對生成 Embedding 的模型進行對應的配置。
將組件作為輸入綁定在對應的函數中,即可完成對應參數的切換。
4.4 gradio.Accordion() 可折疊組件
對于 llm 和 Embedding 來說,通常我們選擇一次后就不會再做調整,一直將整個組件展示在界面中顯得占位置,我們可以用 gradio.Accordion 組件來將其折疊。
- open:控制默認展開折疊組件。
gradio.Accordion 默認是展開的,對于組件內容提供折疊按鈕。對于不需要的組件我們可以配置 open="False", 將其設置為默認折疊的狀態。
我們先初始化 gradio.Accordion 組件,再將需要折疊的內容用 with 包起來即可。
model_select = gr.Accordion("模型選擇")
with model_select:
llm = gr.Dropdown(...)
embedding = gr.Dropdown(...)
4.5 gradio.Markdown() 編寫 Markdown 模塊
之前介紹的都是交互式組件,我們可以用 markdown 為我們的界面加一些說明,使整個界面看起來更加美觀。同時可以增加一些輔助信息。
- value:要在Markdown組件中顯示的值。如果可調用,則每當加載應用程序以設置組件的初始值時,都會調用該函數。
- rtl(bool):是否從右到左呈現文本。如果為True,則將渲染文本的方向設置為從右到左。默認值為False,它從左到右呈現文本。
- latex_delimiters(list[dict[str,str|bool]]):將用于呈現 LaTeX 表達式的形式{“left”:打開分隔符(str)、“right”:關閉分隔符(str)、“display”:是否以換行符(bool)顯示}形式的dict列表。如果未提供,則將
latex_delimitters'設置為","right":"","right":"[{ "left": "","right":"","right":"", "display": False }]`,因此只有包含在 $ 分隔符中且在同一行中的表達式才會呈現為 latex。傳入一個空列表以禁用 LaTeX 渲染。有關更多信息,請參閱KaTeX文檔。
gr.Markdown("""<h1><center>Chat Robot</center></h1>
<center>Local Knowledge Base Q&A with llm</center>
""")
4.6 gradio.Row() 和 gradio.Column() 調整布局
現在我們已經有了足夠多的組件,是時候將他們按照一定的布局格式進行調整了。
gradio.Row() 和 gradio.Column() 分別是新建一行和新建一列的組件。我們將界面劃分成不同的行列區域,將所需組件擺放在對應位置即可。
gradio.Row() 的常用參數
- equal_height(bool): 是否將每個子元素的高度設置為相等。
- variant(Literal[('default', 'panel', 'compact')]): 行類型。“default”(無背景)、“panel”(灰色背景色和圓角)或“compact”(圓角和無內部間隙)。
gradio.Column() 的常用參數:
- scale:與相鄰列相比的相對寬度。例如,如果列 A 的 scale 為 2,而列 B 的 scale 為1,則 A 的寬度將是 B 的兩倍。
- min_width: Column 的最小像素寬度,如果沒有足夠的屏幕空間,則將換行。如果某個 scale 值導致列比 min_width 窄,則首先考慮 min_widght 參數。
- variant: 同 gradio.Row()
例如,我們可以將所有的對話組件放在一行中。將所有參數配置放在一列, 并將 chatBot 和參數配置以 4:1 的比例進行布置。
with gr.Row():
# 創建提交按鈕。
db_with_his_btn = gr.Button("Chat db with history")
db_wo_his_btn = gr.Button("Chat db without history")
llm_btn = gr.Button("Chat with llm")
with gr.Column(scale=4):
chatbot = gr.Chatbot(height=480)
...
with gr.Column(scale=1):
...
model_argument = gr.Accordion("參數配置", open=False)
with model_argument:
...
model_select = gr.Accordion("模型選擇")
with model_select:
...
當項目部署時,可能同一時間有多個用戶進行訪問,這時我們可以將 demo.queue(concurrency_count=3) 進行配置,表示用三個線程來并行。
現在是時候將我們的界面分享給別人了
下面是 demo.launch() 的幾種場景分享配置
- 如果你的運行環境是在容器中運行,需要指定與當初創建容器時的端口一致,才能將端口映射出來 假設容器端口是8080,
- demo.launch(server_name="0.0.0.0", server_port=8080)
- 如果是外部環境,非容器內部,則任意端口均可。
- demo.launch(server_name="0.0.0.0", server_port="任意端口")
- 若想分享給局域網之外的其他人,則設置 share=True,可免費分享3天
- demo.launch(share=True)
現在我們已經實現了 《llm 通過本地數據庫進行回答》的基本功能和界面。快去進行自己的嘗試吧。
第三章、Fast api 進行前后端分離 ??
目前我們已經完成了基本的可視化頁面,并且可以實現對應的功能。
為了方便整個項目的管理,現有的項目通常采用前后端分離的方式搭建,前后端數據通過 json 的格式進行傳輸。
FastAPI 是一個用于構建 API 的現代、快速(高性能)的 web 框架,非常方便用于搭建我們的前后端分離的應用。
我們首先需要將我們用到的后端函數進行 FastAPI 的封裝。封裝 API 與前文中講過將大模型 API 封裝成本地 API 的方法類似,我們首先導入第三方庫并創建一個 API 對象:
from fastapi import FastAPI
from pydantic import BaseModel
import os
app = FastAPI() # 創建 api 對象
本地 API 一般通過 POST 方式進行訪問,即參數會附加在 POST 請求中,我們需要定義一個數據模型來接收 POST 請求中的數據:
# 定義一個數據模型,用于接收POST請求中的數據
class Item(BaseModel):
prompt : str # 用戶 prompt
model : str = "gpt-3.5-turbo"# 使用的模型
temperature : float = 0.1# 溫度系數
if_history : bool = False # 是否使用歷史對話功能
# API_Key
api_key: str = None
# Secret_Key
secret_key : str = None
# access_token
access_token: str = None
# APPID
appid : str = None
# APISecret
api_secret : str = None
# 數據庫路徑
db_path : str = "../../data_base/vector_db/chroma"
# 源文件路徑
file_path : str = "../../data_base/knowledge_db"
# prompt template
prompt_template : str = template
# Template 變量
input_variables : list = ["context","question"]
# Embdding
embedding : str = "openai"
# Top K
top_k : int = 5
# embedding_key
embedding_key : str = api_key
在上面的類中,我們定義了要調用我們已封裝的 QA_chain 所需要傳入的參數,對于非必須參數,我們都設置了默認參數來保證調用的簡潔性,接下來我們就可以創建一個 POST 請求的 API 端點:
@app.post("/answer/")
async def get_response(item: Item):
# 首先確定需要調用的鏈
if not item.if_history:
# 調用 Chat 鏈
chain = QA_chain_self(model=item.model, temperature=item.temperature, top_k=item.top_k, file_path=item.file_path, persist_path=item.db_path,
appid=item.appid, api_key=item.api_key, embedding=item.embedding, template=template, api_secret=item.api_secret, embedding_key=item.embedding_key)
response = chain.answer(question = item.prompt)
return response
# 由于 API 存在即時性問題,不能支持歷史鏈
else:
return "API 不支持歷史鏈"
上述端點的業務邏輯很簡單,即調用我們已封裝的 QA_chain_self 對象進行實例化與回答即可。通過這一個端點啟動,我們便可通過訪問本地 8000 端口來調用個人知識庫助手的服務啦,我們只需要通過下列命令啟動:
uvicorn app:app
到此介紹完畢!
優秀不夠,你是否無可替代
軟件測試交流QQ群:721256703,期待你的加入!!
歡迎關注我的微信公眾號:軟件測試君


浙公網安備 33010602011771號