利用LLM、Flask、LangChain實作聊天機器人chat bot網站
日期:2024-06-12
利用 Ollama LLM 和 Flask,結合 LangChain 架構,打造一個響應式的聊天機器人網站,實現即時對話功能。
會用到的資料集為 GitHub Repository - llama flask langchain ChatbotWeb
技術介紹
- Ollama LLM:提供強大的自然語言處理能力。
- Flask:輕量級的 Python 網頁框架,適合快速開發應用。
- LangChain:用於構建和操作語言模型應用的框架。
- Tailwind CSS:實現高度可定制和響應式設計的 CSS 框架。
- Jinja2:在 Flask 中預設的模板引擎,用於渲染 HTML 模板。
- Server-Sent Events:一種 Web 技術,用於從伺服器向客戶端推送即f時數據。
此文章也有發表在Medium上 >>
網站功能概覽

主頁
在主頁上,你可以看到一個我設計的漂亮Logo,因為我們使用的是Llama3作為LLM,所以在Logo中間放置了一隻可愛的羊駝。

導航欄
你可以通過導航欄連接到我的所有資源連結,包括LinkedIn - Weiberson、Medium - Weiberson和GitHub - weitsung50110。

輸入區域

這裡是你與Llama3對話的地方。你可以問它各種問題,例如:
你是誰?

你愛我嗎?

Loading字樣

即時時間

使用Docker設置環境
1:下載 Docker 映像
首先,下載我們已經配置好的 Docker 映像:
docker pull weitsung50110/ollama_flask
2:運行Image以生成容器
將主機的~/trans_project目錄掛載到容器內的/app資料夾下:
docker run -d \
-v ollama:/root/.ollama \
-v ~/trans_project:/app \
-p 5066:5000 \
-p 11466:11434 \
--name ollama_flask \
weitsung50110/ollama_flask:1.0
端口映射講解
-p 5066:5000:將主機的5066端口映射到容器內部的5000端口,用於Flask。-p 11466:11434:將主機的11466端口映射到容器內部的11434端口,用於Ollama。
3:進入容器
進入剛剛創建的ollama_flask容器:
docker exec -it ollama_flask /bin/bash
4:啟動Llama3
進入容器後,先運行以下指令啟動Llama3:
ollama run llama3
這一步確保Llama3已經安裝並運行,否則Flask將無法打開。
5:啟動Flask應用
進入/app目錄:
cd app/
運行Flask應用:
python3 app.py
確保指定了port,這樣你才能通過127.0.0.1:xxxx訪問Flask應用:
docker run -d -v ollama:/root/.ollama -v ~/trans_project:/app -p 5066:5000 -p 11466:11434 --name ollama_flask weitsung50110/ollama_flask:1.0
6:確認成功運行
如果一切順利,你應該會看到如下輸出:
Running on http://127.0.0.1:5000
根據你在docker run時指定的port來訪問應用,例如:
- 如果port指定為
5066:5000,則訪問127.0.0.1:5066。
重點程式講解
Server
你在資料集中的app.py,也就是server看到render_template代表要渲染網頁了,這時我有把一些值傳到index.html網頁當中。
return render_template('index.html', query_input=query_input, output=output)
- query_input : 使用者詢問LLM所輸入的問題
- output : LLM的回應
Web
Jinja2 提供了一些強大的模板語法,讓我們可以在 HTML 文件中使用 Python 風格的控制結構。
可以看到我在html藉由jinja2使用條件判斷。
<div class="mt-6">
{% if query_input %}
{{ query_input }}
{% endif %}
{% if output %}
{{ output }}
{% endif %}
</div>
接下來我們將探討 Jinja2 模板語法中的控制結構,特別是條件判斷和迴圈語法,我舉幾個簡單的例子給大家,。
Jinja2 模板語法
Jinja2 提供了一些強大的模板語法,讓我們可以在 HTML 文件中使用 Python 風格的控制結構。以下是一些常用的控制結構:
- 條件判斷
if1
({% if %})
- 條件判斷允許我們根據變數的值來決定是否渲染某些內容。
1
2
3
4
5
6
7{% if condition %}
<!-- 當條件為真時渲染這段內容 -->
{% elif other_condition %}
<!-- 當另一個條件為真時渲染這段內容 -->
{% else %}
<!-- 當所有條件都不成立時渲染這段內容 -->
{% endif %}
- 迴圈
for1
({% for %})
- 迴圈允許我們遍歷列表或其他可迭代對象,並渲染每個元素。
1
2
3
4
5
6
7{% for item in items %}
<!-- 渲染每個 item 的內容 -->
{% endfor %}
```
3. 範圍迴圈 `for i in range`
```jinja
({% for i in range(start, end) %}) - 我們也可以使用範圍迴圈來遍歷一個數字範圍:
1
2
3{% for i in range(1, 11) %}
<p>數字:{{ i }}</p>
{% endfor %}
在 Jinja2 模板語法中,所有的控制結構(如條件判斷和迴圈)都必須以相應的 {% end %} 語句來結束。這是為了明確定義控制結構的範圍,避免代碼混淆。
我們可以看到,if 條件判斷用 {% endif %} 結束,for 迴圈用 {% endfor %} 結束。這些結束標記是必不可少的,否則模板引擎會無法正確解析模板,並且會拋出錯誤。
Server-Sent Events (SSE)
是一種 Web 技術,用於從伺服器向客戶端推送即時數據。
生成器函數 generate() 詳解:
def generate():
while True:
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 取得當前時間並格式化
data = f"data: {current_time}\n\n" # 根據SSE格式要求構建數據字符串
yield data # 通過生成器返回數據給調用者(客戶端)
time.sleep(1) # 每秒推送一次數據
generate()函數使用了一個無窮循環 (while True),每次循環都取得當前時間 (datetime.now()),並將其格式化為%Y-%m-%d %H:%M:%S的字串形式。使用 f-string 將格式化後的時間插入到
data字串中,並按照 SSE 的規範構建數據。通過
yield返回data給調用者(客戶端),使得生成器可以被迭代並逐步返回新的數據。time.sleep(1)使生成器每秒鐘推送一次數據,實現即時更新效果。
使用 \n\n 來結束事件
在 Server-Sent Events (SSE,伺服器推送事件) 中,事件的數據部分使用 \n\n 來標記每個事件的結束。
- 第一個
\n表示數據的結束。 - 第二個
\n表示兩個事件之間的分隔。
在 SSE 中,每個事件的數據應當以 data: ... 開頭,然後使用 \n\n 來結束這個事件。
data: 2024-06-13 15:30:00\n\n
SSE 路由 (/stream) 的設置:
在 Flask 應用中設置 /stream 路由,當客戶端訪問該路由時,會返回一個 Response 對象,其內容由 generate() 函數生成,並設置 mimetype='text/event-stream' 以指定這是一個 SSE 流。
@app.route('/stream')
def stream():
return Response(generate(), mimetype='text/event-stream')
當客戶端通過訪問 /stream 路由來訂閱事件流時,伺服器會每秒鐘推送一次當前時間,並由客戶端進行處理和顯示。這樣的應用場景包括即時股票價格更新、即時聊天消息等需要即時更新的應用。
mimetype='text/event-stream' 是在使用 Server-Sent Events (SSE,伺服器推送事件) 時用來指定 HTTP 響應的內容類型(Content-Type)。
text/event-stream是一種特殊的 MIME 類型,它指示了服務器將通過此響應傳送一系列事件給客戶端。這些事件是使用 SSE 標準格式傳送的,每個事件以data:開頭,並以兩個連續的新行\n\n結束。Content-Type是 HTTP 標頭的一部分,用來描述 HTTP 響應的內容類型。在這種情況下,mimetype='text/event-stream'告訴瀏覽器或客戶端,這個響應包含了 SSE 格式的數據流,它應該按照 SSE 的規範來處理和解析這些數據。
使用 mimetype='text/event-stream' 是確保伺服器正確地傳送 SSE 數據流到客戶端的關鍵。
JavaScript 說明:
JavaScript 代碼中,使用了 EventSource 對象來實現 Server-Sent Events (SSE,伺服器推送事件) 的客戶端訂閱和接收。
<script>
const eventSource = new EventSource('/stream');
eventSource.onmessage = function(event) {
document.getElementById('datetime').innerHTML = event.data;
};
</script>
- const eventSource = new EventSource(‘/stream’);
EventSource是 HTML5 中引入的一種 API,它允許網頁從服務器端接收推送的事件。new EventSource('/stream')創建了一個新的EventSource對象,並指定了要訂閱的服務器端端點/stream。這意味著客戶端將會向/stream發起 HTTP GET 請求來訂閱事件流。
- eventSource.onmessage = function(event) { … };
一旦客戶端訂閱成功,當服務器端向
/stream發送一條新的事件消息時,JavaScript 會自動觸發onmessage事件處理器函數。event參數包含了從服務器端發送來的事件消息的相關信息,包括數據內容。
- document.getElementById(‘datetime’).innerHTML = event.data;
在
onmessage事件處理器函數內部,這行代碼將服務器端發送來的數據event.data更新到 HTML 文檔中具有id="datetime"的元素內。通常情況下,
event.data是一段文本數據,它包含了服務器端發送的即時信息,例如時間戳、消息內容等。
工作流程
頁面加載
當頁面加載時,JavaScript 代碼會創建一個EventSource對象,並發起對/stream的 HTTP GET 請求。服務器端推送事件
一旦服務器端有新的事件消息到來,它會將該消息推送到所有訂閱了/stream的客戶端(即這裡的網頁)。更新 HTML 元素
客戶端接收到來自服務器端的事件消息後,透過onmessage事件處理器函數將消息內容更新到 HTML 中的指定元素(這裡是id="datetime"的元素)。
這樣就實現了一個基本的 SSE 客戶端,用於接收服務器端推送的事件消息並即時更新到網頁上。
結語
通過這篇教學文章,我們學習了如何使用Ollama NLP LLM結合Flask來構建智能對話機器人網站。希望你能成功搭建並享受與Llama3的互動過程!
未來會把記憶性的聊天機器人, RAG取得PDF和DOC, 等等的應用結合到Flask的Web介面中,敬請期待!!🥰
喜歡 好崴寶 Weibert Weiberson 的文章嗎?在這裡留下你的評論!本留言區支援 Markdown 語法。