利用LLM、Flask、LangChain實作聊天機器人chat bot網站

Weibert Weiberson
13 min readJun 12, 2024

--

我們將介紹如何使用Ollama LLM結合Flask來構建一個聊天機器人chat bot網站。本教學將會使用Llama3作為我們的LLM,並且採用LangChain, , JavaScript, Jinja2和Tailwind CSS框架,確保你在任何設備上都能獲得最佳的響應式設計(RWD)體驗。

會用到的資料集為ollama_flask_langchain_web

目錄
技術介紹
網站功能概覽
- 主頁
- 導航欄
- 輸入區域(和LLM聊天互動)
Docker設置環境
重點程式講解
- Server
- Web
- Jinja2模板語法
- Server-Sent Events(SSE)
結語
requirements.txt
langchian-core
langchain-community
flask

技術介紹

  • Ollama LLM:提供強大的自然語言處理能力。
  • Flask:輕量級的Python網頁框架。
  • LangChain:用於構建和操作語言模型應用的框架。
  • Tailwind CSS:實現高度可定制和響應式設計的CSS框架。
  • Jinja2:在 Flask 中,Jinja2 是預設的模板引擎,用於渲染 HTML 模板。
  • Server-Sent Events:是一種 Web 技術,用於從伺服器向客戶端推送即時數據。

網站功能概覽

主頁

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

導航欄

你可以通過導航欄連接到我的所有資源連結,包括LinkedInMediumGitHub

輸入區域

這裡是你與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 %}
<div class="bg-gray-100 p-4 rounded mb-4">
<p class="text-gray-700">{{ query_input }}</p>
</div>
{% endif %}
{% if output %}
<div class="bg-gray-100 p-4 rounded">
<p class="text-gray-700">{{ output }}</p> <!-- Use the safe filter to render HTML from output -->
</div>
{% endif %}
</div>

接下來我們將探討 Jinja2 模板語法中的控制結構,特別是條件判斷和迴圈語法,我舉幾個簡單的例子給大家,。

Jinja2 模板語法

Jinja2 提供了一些強大的模板語法,讓我們可以在 HTML 文件中使用 Python 風格的控制結構。以下是一些常用的控制結構:

1. 條件判斷({% if %}

條件判斷允許我們根據變數的值來決定是否渲染某些內容。

{% if condition %}
<!-- 當條件為真時渲染這段內容 -->
{% elif other_condition %}
<!-- 當另一個條件為真時渲染這段內容 -->
{% else %}
<!-- 當所有條件都不成立時渲染這段內容 -->
{% endif %}

2. 迴圈({% for %}

迴圈允許我們遍歷列表或其他可迭代對象,並渲染每個元素。

{% for item in items %}
<!-- 渲染每個 item 的內容 -->
{% endfor %}

3. 範圍迴圈({% for i in range(start, end) %}

我們也可以使用範圍迴圈來遍歷一個數字範圍:

{% 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) 使生成器每秒鐘推送一次數據,實現即時更新效果。

在 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>

1. const eventSource = new EventSource('/stream');

  • EventSource 是 HTML5 中引入的一種 API,它允許網頁從服務器端接收推送的事件。
  • new EventSource('/stream') 創建了一個新的 EventSource 對象,並指定了要訂閱的服務器端端點 /stream。這意味著客戶端將會向 /stream 發起 HTTP GET 請求來訂閱事件流。

2. eventSource.onmessage = function(event) { ... };

  • 一旦客戶端訂閱成功,當服務器端向 /stream 發送一條新的事件消息時,JavaScript 會自動觸發 onmessage 事件處理器函數。
  • event 參數包含了從服務器端發送來的事件消息的相關信息,包括數據內容。

3. document.getElementById('datetime').innerHTML = event.data;

  • onmessage 事件處理器函數內部,這行代碼將服務器端發送來的數據 event.data 更新到 HTML 文檔中具有 id="datetime" 的元素內。
  • 通常情況下,event.data 是一段文本數據,它包含了服務器端發送的即時信息,例如時間戳、消息內容等。

工作流程:

  • 當頁面加載時,JavaScript 代碼會創建一個 EventSource 對象,並發起對 /stream 的 HTTP GET 請求。
  • 一旦服務器端有新的事件消息到來,它會將該消息推送到所有訂閱了 /stream 的客戶端(即這裡的網頁)。
  • 客戶端接收到來自服務器端的事件消息後,透過 onmessage 事件處理器函數將消息內容更新到 HTML 中的指定元素(這裡是 id="datetime" 的元素)。

這樣就實現了一個基本的 SSE 客戶端,用於接收服務器端推送的事件消息並即時更新到網頁上。

結語

通過這篇教學文章,我們學習了如何使用Ollama NLP LLM結合Flask來構建智能對話機器人網站。希望你能成功搭建並享受與Llama3的互動過程!

未來會把記憶性的聊天機器人, RAG取得PDF和DOC, 等等的應用結合到Flask的Web介面中,敬請期待!!🥰

--

--