Day 2 我已經成功在本機用 Ollama 跑起 qwen3:4b。
但目前的使用方式還只是:
User
↓
Ollama Terminal
↓
qwen3:4b
也就是說,我只能直接在 Terminal 裡跟模型聊天。
這樣當然可以測模型,但如果後面我要加入:
就會很難控制整個流程。
所以 Day 3 的目標很明確:
把 Local LLM 包成自己的 Application。
今天預計用:
Python
+
FastAPI
+
Ollama API
把流程改成:
User
↓
FastAPI
↓
Ollama API
↓
qwen3:4b
↓
Response
這樣之後我就可以慢慢在 FastAPI 和 LLM 中間加入自己的 Security Logic。
因為之後這個專案會裝不少 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 環境。
這邊其實有踩一個很小的雷。
我一開始輸入指令的時候,最前面不小心多出一個中文字:
ㄍ
所以 PowerShell 回我:
無法辨識 'ㄍ' 詞彙是否為 Cmdlet、函數、指令檔或可執行程式的名稱。
後來改成正確的:
.\.venv\Scripts\Activate.ps1
就正常了。
這種問題雖然很小,但實際操作的時候真的滿容易因為輸入法卡住。
接著安裝今天會用到的套件:
pip install fastapi uvicorn requests
這三個套件分別負責:
FastAPI
→ 建立 API
Uvicorn
→ 啟動 FastAPI Server
requests
→ Python 呼叫 Ollama API
目前我不打算一次塞太多框架。
因為我想先看懂:
我的 Request 到底是怎麼一路送到 LLM,再把 Response 拿回來。
我先在:
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.

這表示 FastAPI 已經正常跑起來了。
接著打開瀏覽器:
http://127.0.0.1:8000
可以看到:
{
"message": "AI Security Lab is running"
}
這時候其實還沒有 AI。
只是先確認:
Browser
↓
FastAPI
這一段是通的。
FastAPI 有一個我覺得滿方便的地方。
直接打開:
http://127.0.0.1:8000/docs
就會自動產生 API 文件。
目前可以看到:
GET /
後面新增 /chat 之後,也會直接出現在這裡。
對這次專案來說滿方便的,因為我不一定每次都要另外開 Postman。
直接在 /docs 裡就可以測 API。
FastAPI 可以跑之後,下一步就是確認:
Ollama 能不能被程式呼叫?
因為 Day 2 的時候,我是直接用:
ollama run qwen3:4b
跟模型對話。
今天希望改成:
HTTP Request
↓
Ollama API
↓
qwen3:4b
一開始我先用 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

先看:
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
/chat接著重新打開:
http://127.0.0.1:8000/docs
這次已經可以看到:
GET /
POST /chat
點開:
POST /chat
輸入:
{
"message": "什麼是 Prompt Injection?請用繁體中文簡短回答。"
}
然後 Execute。
成功後就可以看到 FastAPI 回傳:
{
"model": "qwen3:4b",
"response": "..."
}

這時候代表今天真正的目標完成了。
不過這裡還有一個滿明顯的現象。
第一次按 Execute 的時候,我一度以為程式卡住了。
因為 Swagger 一直 Loading。
後來等了一段時間之後,它其實有正常回傳。
所以問題不是 FastAPI 壞掉。
比較像是:
FastAPI
↓
Ollama
↓
模型載入
↓
Local Inference
↓
Response
本地模型第一次被呼叫時,本來就可能比較慢。
尤其模型需要載入記憶體,再開始推理。
所以後面如果要做成比較完整的 Application,回應時間也是我可能會需要觀察的一個地方。
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 真正會開始有東西的地方。
最後我把目前的 Python 套件存起來:
pip freeze > requirements.txt
之後如果換電腦或重新建立環境,只要:
pip install -r requirements.txt
就可以把套件裝回來。

今天其實比 Day 2 多踩了一些雷。
.venv 啟動指令輸入錯誤因為輸入法多打一個字,PowerShell 直接認不得指令。
網址格式、Markdown、反引號都有讓指令跑不起來。
後來乾脆全部改成單行才比較穩。
??PowerShell 傳 JSON 時,看起來中文有編碼問題。
最後改用 Python requests 後,才比較符合真正 Application 的使用方式。
Swagger 一開始 Loading 很久。
本來以為卡住,結果最後還是正常回傳。
這也是本地模型跟直接用雲端 API 很不一樣的地方。
今天終於把:
FastAPI
+
Ollama
+
qwen3:4b
串在一起了。
目前已經可以透過:
POST /chat
送問題給自己的 Local LLM。
現在整個 Lab 已經從:
「我有一個模型」
變成:
「我有一個自己的 AI Application」
雖然它現在還完全沒有安全機制。
甚至可以說:
現在這個 Application 就是一個裸奔的 LLM。
但這反而正好。
因為後面我們才有東西可以慢慢加。
下一篇:
Day 4|System Prompt 真的安全嗎?先替我的 LLM 加上角色與規則