利用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 風格的控制結構。以下是一些常用的控制結構:
- 條件判斷
if
1
({% if %})
- 條件判斷允許我們根據變數的值來決定是否渲染某些內容。
1
2
3
4
5
6
7{% if condition %}
<!-- 當條件為真時渲染這段內容 -->
{% elif other_condition %}
<!-- 當另一個條件為真時渲染這段內容 -->
{% else %}
<!-- 當所有條件都不成立時渲染這段內容 -->
{% endif %}
- 迴圈
for
1
({% 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 語法。