iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
AI Security

打造 AI Security Lab:從攻擊 LLM 到建立自己的 AI 防線系列 第 3

Day 3|用 Python + FastAPI 打造第一個本地 LLM Application

  • 分享至 

  • xImage
  •  

前言

Day 2 我已經成功在本機用 Ollama 跑起 qwen3:4b

但目前的使用方式還只是:

User
 ↓
Ollama Terminal
 ↓
qwen3:4b

也就是說,我只能直接在 Terminal 裡跟模型聊天。

這樣當然可以測模型,但如果後面我要加入:

  • System Prompt
  • Security Logging
  • Prompt Injection Detection
  • Input Filter
  • Output Filter

就會很難控制整個流程。

所以 Day 3 的目標很明確:

把 Local LLM 包成自己的 Application。

今天預計用:

Python
+
FastAPI
+
Ollama API

把流程改成:

User
 ↓
FastAPI
 ↓
Ollama API
 ↓
qwen3:4b
 ↓
Response

這樣之後我就可以慢慢在 FastAPI 和 LLM 中間加入自己的 Security Logic。


先建立 Python 虛擬環境

因為之後這個專案會裝不少 Python 套件,所以我先建立虛擬環境。

在專案根目錄:

C:\Users\user\Desktop\AI-Security-Lab

執行:

python -m venv .venv

建立完成後,再啟動:

.\.venv\Scripts\Activate.ps1

正常啟動後,Terminal 前面會看到:

(.venv)

例如:

(.venv) PS C:\Users\user\Desktop\AI-Security-Lab>

代表目前已經進入這個專案自己的 Python 環境。
https://ithelp.ithome.com.tw/upload/images/20260915/20178893h32fdoJcGJ.png


第一個小失誤:虛擬環境啟動指令打錯

這邊其實有踩一個很小的雷。

我一開始輸入指令的時候,最前面不小心多出一個中文字:

所以 PowerShell 回我:

無法辨識 'ㄍ' 詞彙是否為 Cmdlet、函數、指令檔或可執行程式的名稱。

後來改成正確的:

.\.venv\Scripts\Activate.ps1

就正常了。

這種問題雖然很小,但實際操作的時候真的滿容易因為輸入法卡住。


安裝 FastAPI

接著安裝今天會用到的套件:

pip install fastapi uvicorn requests

這三個套件分別負責:

FastAPI
→ 建立 API

Uvicorn
→ 啟動 FastAPI Server

requests
→ Python 呼叫 Ollama API

目前我不打算一次塞太多框架。

因為我想先看懂:

我的 Request 到底是怎麼一路送到 LLM,再把 Response 拿回來。


先讓 FastAPI 跑起來

我先在:

app/main.py

建立最基本的 FastAPI Application。

內容:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {
        "message": "AI Security Lab is running"
    }

接著在 Terminal 啟動:

uvicorn app.main:app --reload

成功後看到:

Uvicorn running on http://127.0.0.1:8000

而且:

Application startup complete.

https://ithelp.ithome.com.tw/upload/images/20260915/20178893f7Gyu5cQKY.png

這表示 FastAPI 已經正常跑起來了。


第一個 API 成功

接著打開瀏覽器:

http://127.0.0.1:8000

可以看到:

{
  "message": "AI Security Lab is running"
}

這時候其實還沒有 AI。

只是先確認:

Browser
 ↓
FastAPI

這一段是通的。
https://ithelp.ithome.com.tw/upload/images/20260915/20178893cg9kTOkFU5.png


FastAPI 自帶的 Swagger

FastAPI 有一個我覺得滿方便的地方。

直接打開:

http://127.0.0.1:8000/docs

就會自動產生 API 文件。

目前可以看到:

GET /

後面新增 /chat 之後,也會直接出現在這裡。

對這次專案來說滿方便的,因為我不一定每次都要另外開 Postman。

直接在 /docs 裡就可以測 API。


接下來測 Ollama API

FastAPI 可以跑之後,下一步就是確認:

Ollama 能不能被程式呼叫?

因為 Day 2 的時候,我是直接用:

ollama run qwen3:4b

跟模型對話。

今天希望改成:

HTTP Request
 ↓
Ollama API
 ↓
qwen3:4b

PowerShell 測試 Ollama API

一開始我先用 PowerShell 的:

Invoke-RestMethod

來測試。

先建立 Request Body:

$body = '{"model":"qwen3:4b","messages":[{"role":"user","content":"你好"}],"stream":false,"think":false}'

接著:

$response = Invoke-RestMethod -Uri ("http" + "://" + "localhost:11434/api/chat") -Method Post -ContentType "application/json" -Body $body

最後:

$response.message.content

成功拿到模型回覆。

也就是說:

PowerShell
 ↓
Ollama API
 ↓
qwen3:4b

這一段也成功了。


但又出現一個奇怪的問題

雖然 API 有成功回覆,但模型的回答有點奇怪。

我明明送的是:

你好

但模型的 reasoning 卻寫:

The user sent "??"

看起來它沒有正確收到中文。

這讓我懷疑是:

PowerShell 傳 JSON 時的中文編碼問題。

不過因為今天的目標不是研究 PowerShell 編碼,所以我沒有繼續卡在這裡。

反正最後真正的 Application 會用 Python 呼叫 Ollama。


正式建立 /chat

接著把 app/main.py 改成:

from fastapi import FastAPI
from pydantic import BaseModel
import requests

app = FastAPI()

OLLAMA_URL = "http://localhost:11434/api/chat"
MODEL_NAME = "qwen3:4b"


class ChatRequest(BaseModel):
    message: str


@app.get("/")
def root():
    return {
        "message": "AI Security Lab is running"
    }


@app.post("/chat")
def chat(request: ChatRequest):
    payload = {
        "model": MODEL_NAME,
        "messages": [
            {
                "role": "user",
                "content": request.message
            }
        ],
        "stream": False,
        "think": False
    }

    response = requests.post(
        OLLAMA_URL,
        json=payload
    )

    response.raise_for_status()

    data = response.json()

    return {
        "model": MODEL_NAME,
        "response": data["message"]["content"]
    }

這樣就多了一個:

POST /chat

https://ithelp.ithome.com.tw/upload/images/20260915/20178893qb79nxa6mv.png


這段程式在做什麼?

先看:

class ChatRequest(BaseModel):
    message: str

這代表 /chat 會接收:

{
  "message": "你好"
}

接著:

payload = {
    "model": MODEL_NAME,
    "messages": [
        {
            "role": "user",
            "content": request.message
        }
    ],
    "stream": False,
    "think": False
}

把使用者輸入整理成 Ollama API 可以理解的格式。

然後:

response = requests.post(
    OLLAMA_URL,
    json=payload
)

就是 Python 真正去呼叫 Ollama。

最後:

data["message"]["content"]

把模型真正回答的內容拿出來。

整個流程可以簡化成:

User Input
   ↓
FastAPI
   ↓
整理成 JSON
   ↓
Ollama API
   ↓
qwen3:4b
   ↓
取出 Response

用 Swagger 測 /chat

接著重新打開:

http://127.0.0.1:8000/docs

這次已經可以看到:

GET /
POST /chat

點開:

POST /chat

輸入:

{
  "message": "什麼是 Prompt Injection?請用繁體中文簡短回答。"
}

然後 Execute。

成功後就可以看到 FastAPI 回傳:

{
  "model": "qwen3:4b",
  "response": "..."
}

https://ithelp.ithome.com.tw/upload/images/20260915/20178893YCZuLJocfm.png

這時候代表今天真正的目標完成了。


第一次呼叫 Loading 超久

不過這裡還有一個滿明顯的現象。

第一次按 Execute 的時候,我一度以為程式卡住了。

因為 Swagger 一直 Loading。

後來等了一段時間之後,它其實有正常回傳。

所以問題不是 FastAPI 壞掉。

比較像是:

FastAPI
 ↓
Ollama
 ↓
模型載入
 ↓
Local Inference
 ↓
Response

本地模型第一次被呼叫時,本來就可能比較慢。

尤其模型需要載入記憶體,再開始推理。

所以後面如果要做成比較完整的 Application,回應時間也是我可能會需要觀察的一個地方。


Day 2 和 Day 3 的差別

Day 2 的架構:

User
 ↓
Ollama
 ↓
qwen3:4b

今天則變成:

User
 ↓
FastAPI
 ↓
Ollama API
 ↓
qwen3:4b
 ↓
Response

乍看之下只是多了一層 FastAPI。

但我覺得這層其實非常重要。

因為從現在開始,我終於可以在:

User
 ↓
FastAPI
 ↓
LLM

中間開始加入自己的邏輯。

例如:

Input Filter
Security Logging
Prompt Detection
Risk Score
Output Filter

這也是後面 AI Security Lab 真正會開始有東西的地方。


建立 requirements.txt

最後我把目前的 Python 套件存起來:

pip freeze > requirements.txt

之後如果換電腦或重新建立環境,只要:

pip install -r requirements.txt

就可以把套件裝回來。

目前專案大概變成:
https://ithelp.ithome.com.tw/upload/images/20260915/20178893bNeZlozZiE.png

今天踩到的幾個問題

今天其實比 Day 2 多踩了一些雷。

1. .venv 啟動指令輸入錯誤

因為輸入法多打一個字,PowerShell 直接認不得指令。


2. PowerShell API 測試格式一直出問題

網址格式、Markdown、反引號都有讓指令跑不起來。

後來乾脆全部改成單行才比較穩。


3. 中文被模型看成 ??

PowerShell 傳 JSON 時,看起來中文有編碼問題。

最後改用 Python requests 後,才比較符合真正 Application 的使用方式。


4. 第一次 API 呼叫很慢

Swagger 一開始 Loading 很久。

本來以為卡住,結果最後還是正常回傳。

這也是本地模型跟直接用雲端 API 很不一樣的地方。


Day 3 小結

今天終於把:

FastAPI
+
Ollama
+
qwen3:4b

串在一起了。

目前已經可以透過:

POST /chat

送問題給自己的 Local LLM。

現在整個 Lab 已經從:

「我有一個模型」

變成:

「我有一個自己的 AI Application」

雖然它現在還完全沒有安全機制。

甚至可以說:

現在這個 Application 就是一個裸奔的 LLM。

但這反而正好。

因為後面我們才有東西可以慢慢加。

下一篇:

Day 4|System Prompt 真的安全嗎?先替我的 LLM 加上角色與規則


上一篇
Day 2|用 Ollama 建立本地 LLM 實驗環境
下一篇
Day 4|System Prompt 真的安全嗎?先替我的 LLM 加上角色與規則
系列文
打造 AI Security Lab:從攻擊 LLM 到建立自己的 AI 防線6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言