我們在前兩天講了一些概念。Day1 先談 Stateless,Day2 再把 Token、Message 和 Context 串起來。今天我們來開始寫程式吧!
今天的目標是:用 Python 呼叫一次 LLM API,然後把它改造成可以在 Terminal 裡持續對話的 Chatbot。
不過今天做出來的 Chatbot 還很單純,雖然可以回答我們的問題,卻不會記得我上一句說過什麼。也就是說,我們要正式做出這個系列的第一個版本:Stateless Chatbot
先不碰網頁、不碰資料庫,也不碰任何 Agent Framework。今天的架構只有這樣:
User
↓
Python
↓
LLM API
↓
Response
↓
Terminal
我輸入一句話,Python 把 Request 送到 LLM API,模型產生 Response,再把結果印到 Terminal。完成後,我希望可以直接這樣使用:
You: 什麼是 present perfect?
AI: 現在完成式是用來描述過去發生,
但和現在仍然有關係的事情……
You: exit
Bye!
看起來已經很像 Chatbot 了。等等我們會順便測試它到底有沒有「記憶」。
我們先建立一個新的資料夾:
mkdir memora-agent
cd memora-agent
接著建立 Python 虛擬環境:
python -m venv .venv
macOS / Linux 啟用:
source .venv/bin/activate
Windows PowerShell 則可以使用:
.venv\Scripts\Activate.ps1
接著安裝 OpenAI 官方 Python SDK:
pip install openai
這個系列的重點不是 OpenAI 本身,所以未來我們先把 LLM Provider 抽離,讓模型可以替換。不過現在剛開始,我需要先選一個具體的 API 把整個 Request → Response 流程跑通,因此先使用 OpenAI Responses API 作為第一個實作。
OpenAI 官方目前的 Quickstart 也是透過 SDK 建立 client,再使用 Responses API 送出模型請求。目前專案先保持非常簡單:
memora-agent/
│
├── .venv/
├── chatbot.py
└── .gitignore
.gitignore 可以先放:
.venv/
__pycache__/
後面的資料夾真的用到了再新增,不需要第一天就建立二十個還沒有內容的模組。
在呼叫 API 之前,我需要一把 API Key,讓 OpenAI 知道這個 Request 是誰送出的。最直覺但也是不好的寫法會是:
api_key = "sk-xxxxxxxxxxxxxxxx"
如果之後把程式 Push 到 GitHub,API Key 就有可能一起被公開。所以我不會把 Key 寫死在程式碼裡,而是放進 Environment Variable。macOS / Linux 可以先在 Terminal 設定:
export OPENAI_API_KEY="你的_API_Key"
Windows PowerShell:
$env:OPENAI_API_KEY="你的_API_Key"
OpenAI 官方 SDK 預設就可以從 OPENAI_API_KEY 這個環境變數讀取 API Key,因此程式碼裡不需要再放真正的 Key。這樣程式裡只需要:
from openai import OpenAI
client = OpenAI()
而不是:
client = OpenAI(
api_key="真正的 API Key"
)
這也是初學者從第一天 Coding 就該養成的習慣:
Secret 和 Source Code 分開管理。
API Key、Database Password 等資訊都不應該直接出現在 Git Repository 裡。
接下來建立:
chatbot.py
先寫最小版本:
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5-mini",
input="請用簡單的方式解釋什麼是大型語言模型。"
)
print(response.output_text)
執行:
python chatbot.py
如果設定都正確,就會看到模型回傳的文字。
OpenAI 的 Responses API 可以透過 client.responses.create() 建立模型回應,而 response.output_text 可以取得 SDK 整理後的文字輸出;官方模型文件目前也將 GPT-5 mini 定位為較快速、成本效率較高的 GPT-5 系列模型之一,所以這裡用它作為教學範例。
先不管回答內容,光是這幾行其實就已經完成一個很重要的流程:
Python
↓
Request
↓
OpenAI API
↓
LLM
↓
Response
↓
Python
昨天講的那些概念,現在開始真的出現在程式裡囉!
來仔細看這一段:
response = client.responses.create(
model="gpt-5-mini",
input="請用簡單的方式解釋什麼是大型語言模型。"
)
這裡最重要的其實只有兩件事情:
model
input
model 指定這一次要使用哪一個模型。
model="gpt-5-mini"
input 則是這一次要交給模型的內容:
input="請用簡單的方式解釋什麼是大型語言模型。"
因此可以把這次 Request 想成:
Request
├── model
│ └── gpt-5-mini
│
└── input
└── 請用簡單的方式解釋什麼是大型語言模型。
API 收到 Request 後,再把它交給指定模型處理。
模型產生結果後,API 回傳一個 Response。
Request
↓
LLM API
↓
Model
↓
Response
所以我們常常說「呼叫 LLM」,實際上程式做的事情比較接近:
建立一個 Request,透過 API 把資料送出去,再接收一個 Response。
這也是今天標題裡「LLM API」真正扮演的角色。
剛才我們用了:
print(response.output_text)
所以畫面只看到模型的回答。但 response 本身其實不是單純的一個 Python 字串,而是一個包含多種資訊的 Response Object。我可以先試著:
print(response)
你會發現裡面除了文字之外,還有其他資料。只是目前我們還不需要處理全部內容,所以先利用 SDK 提供的:
response.output_text
取得最終的文字輸出即可。官方 Responses API 也會回傳具有識別資訊與輸出內容的 Response,而 SDK 提供 output_text 這類方便取出文字結果的介面。
因此:
response = client.responses.create(...)
拿到的是:
Response Object
而:
response.output_text
才是我們目前真正想印出來的:
AI 回答文字
這個區分後面會越來越重要。因為當 Agent 開始使用 Tool Calling 時,Response 裡不一定只有單純的文字。不過那是後面的事情。現在先把 Chatbot 做完就好。
現在最大的問題是:
input="請用簡單的方式解釋什麼是大型語言模型。"
問題被我寫死在程式裡了。真正的 Chatbot 當然要讓使用者自己輸入。那該怎麼改呢?
我們可以改成:
from openai import OpenAI
client = OpenAI()
user_input = input("You: ")
response = client.responses.create(
model="gpt-5-mini",
input=user_input
)
print("AI:", response.output_text)
現在執行:
python chatbot.py
就可以自己輸入:
You: 請用簡單的例子解釋 Python 的 list
然後得到:
AI: Python 的 list 可以想成一個可以裝很多東西的盒子……
到這裡,我們已經有第一個真正可以輸入問題的 LLM Application 了。但它還只能問一次。回答完,程式就結束。
如果希望可以一直聊天,最簡單的方法就是加入 Python 的 while。
from openai import OpenAI
client = OpenAI()
while True:
user_input = input("You: ")
if user_input.lower() == "exit":
print("Bye!")
break
response = client.responses.create(
model="gpt-5-mini",
input=user_input
)
print("AI:", response.output_text)
現在再執行:
python chatbot.py
就可以一直輸入:
You: 什麼是 present perfect?
AI: Present perfect 是……
You: Python 的 dictionary 是什麼?
AI: Python dictionary 是……
You: exit
Bye!
到這裡,第一個 Chatbot 正式完成。完整流程變成:
┌─────────────┐
│ User Input │
└──────┬──────┘
↓
Create Request
↓
LLM API
↓
Response
↓
Print Response
↓
Ask Again
│
└──────→ Loop
看起來已經很像一個正常的聊天程式了!!接下來來測試一下這個Chatbot有沒有記憶能力。
我先輸入一段很難靠猜測得到的資訊:
You: 我的秘密代碼是 ZQ-731,請記住。
AI 可能回答:
AI: 好的,你的秘密代碼是 ZQ-731。
接著我立刻問:
You: 我的秘密代碼是什麼?
照我們平常使用聊天 AI 的經驗,可能會期待:
AI: 你的秘密代碼是 ZQ-731。
但現在這個程式的第二次 Request 實際上只有:
response = client.responses.create(
model="gpt-5-mini",
input="我的秘密代碼是什麼?"
)
它沒有:
我的秘密代碼是 ZQ-731。
也沒有:
Previous Messages
甚至沒有任何:
Conversation History
所以兩次 API Call 實際上是:
Request 1
──────────────────────────
我的秘密代碼是 ZQ-731。
──────────────────────────
↓
LLM
Request 2
──────────────────────────
我的秘密代碼是什麼?
──────────────────────────
↓
LLM
這兩個 Request 在我們現在的程式裡完全是分開的。
雖然畫面長得像:
You
AI
You
AI
但我們根本沒有把第一輪對話放進第二次 Request。所以這個程式其實是一個:
while Loop 不等於 Conversation Memory這是今天最容易搞混的一件事情。因為我們加上:
while True:
之後,使用者真的可以一直聊天。畫面看起來也會像:
You: ...
AI: ...
You: ...
AI: ...
You: ...
AI: ...
很容易產生一種感覺:
我已經做出多輪對話了。
但其實沒有。while 只負責讓程式一直執行。它並沒有幫我們保存:
User Message 1
Assistant Message 1
User Message 2
Assistant Message 2
目前每次真正交給 LLM 的仍然只有:
Current User Input
也就是:
while Loop
≠
Conversation History
這個差別對後面的 Memory 非常重要。
到這裡,可以重新看看我們目前做出的系統。
┌───────────────┐
│ User │
└───────┬───────┘
↓
┌───────────────┐
│ Python App │
│ │
│ input() │
│ while loop │
└───────┬───────┘
↓
┌───────────────┐
│ LLM API │
└───────┬───────┘
↓
┌───────────────┐
│ LLM │
└───────┬───────┘
↓
┌───────────────┐
│ Response │
└───────────────┘
它已經有:
但還沒有:
甚至它目前連上一句話都沒有真正保存。
不過如果今天就直接使用一個 Framework,把 Conversation State 全部幫我管理好,我反而不容易看出後面每一層到底增加了什麼。
目前 chatbot.py 最後會長這樣:
from openai import OpenAI
client = OpenAI()
print("Memora v0.1")
print("輸入 exit 可以結束對話。")
while True:
user_input = input("\nYou: ")
if user_input.lower() == "exit":
print("Bye!")
break
response = client.responses.create(
model="gpt-5-mini",
input=user_input
)
print("AI:", response.output_text)
現在的專案:
memora-agent/
│
├── .venv/
├── chatbot.py
└── .gitignore
雖然只有十幾行 Python,但已經建立了我們之後整個 Memora 的最初版本。我會先把它叫做:
Memora v0.1
Stateless Chatbot
現在的 Memora 其實非常單純:
Day 03
User
↓
Current Message
↓
LLM
↓
Response
但接下來它會慢慢變成:
User
↓
Conversation History
↓
Short-term Memory
↓
Relevant Long-term Memory
↓
User Profile
↓
Tools
↓
Agent
↓
LLM
↓
Response
所以今天的程式保留下來也是有意義的。因為等到 Day 30 再回頭看這十幾行 Code,應該會很明顯看到一個「會回答問題的 LLM」和一個「會記得、會找資料、會採取行動的 Agent」到底差在哪裡哦!
今天終於完成這個系列第一個真的可以執行的 AI 程式。我們先用 Python 和 LLM API 建立了最基本的:
User Input
↓
Request
↓
LLM API
↓
Response
同時也碰到了幾個之後會一直出現的重要概念。
API Key 用來驗證 API Request,因此不直接寫進 Source Code,而是透過 Environment Variable 管理。OpenAI 官方 Python SDK 可以直接從 OPENAI_API_KEY 環境變數取得金鑰。
Request 是程式送給 API 的資料;Response 則是 API 處理後回傳給程式的結果。今天使用的是 OpenAI Responses API,透過 client.responses.create() 建立 Response,再從 response.output_text 取得文字輸出。
而今天雖然成功串上了 API,但是這個 Chatbot 完全不知道上一輪聊了什麼。
目前:
Request 1
↓
Response 1
Request 2
↓
Response 2
彼此沒有 Conversation History。所以即使畫面看起來可以一直聊天,它依然是:
Stateless Chatbot
這也替接下來幾天留下一個很重要的問題。如果 LLM 每次只會根據現在收到的內容回答,那我要怎麼控制它的回答方式?又要怎麼讓它逐漸變成我真正想要的 Personal Learning Agent呢?
明天先處理第一個問題。
今天我們只是把 User Input 原封不動地交給模型:
input=user_input
所以模型要用什麼語氣、扮演什麼角色、回答多詳細,目前幾乎都沒有特別控制。
明天我們來加入 System Prompt,比較看看同一個 User Message 在不同 Prompt 下會產生什麼差異,也會開始把這個普通 Chatbot 慢慢調整成我們想要的 Personal Learning Assistant。
至於它為什麼還是記不住上一句?那個問題我們留到後天再討論吧!