iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0

我們在前兩天講了一些概念。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 了。等等我們會順便測試它到底有沒有「記憶」。


一、先建立 Python 專案

我們先建立一個新的資料夾:

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 Key 不要直接寫進程式

在呼叫 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 裡。


三、送出我的第一個 LLM Request

接下來建立:

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

昨天講的那些概念,現在開始真的出現在程式裡囉!


四、Request 到底送了什麼?

來仔細看這一段:

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」真正扮演的角色。


五、Response 不只是一串文字

剛才我們用了:

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 了。但它還只能問一次。回答完,程式就結束。


七、加入 while Loop,讓它真的像 Chatbot

如果希望可以一直聊天,最簡單的方法就是加入 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有沒有記憶能力。


八、這個 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。所以這個程式其實是一個:

Stateless Chatbot


九、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 非常重要。


十、現在的 Chatbot 裡到底有什麼?

到這裡,可以重新看看我們目前做出的系統。

┌───────────────┐
│     User      │
└───────┬───────┘
        ↓
┌───────────────┐
│ Python App    │
│               │
│ input()       │
│ while loop    │
└───────┬───────┘
        ↓
┌───────────────┐
│   LLM API     │
└───────┬───────┘
        ↓
┌───────────────┐
│     LLM       │
└───────┬───────┘
        ↓
┌───────────────┐
│   Response    │
└───────────────┘

它已經有:

  • User Input
  • LLM API
  • Request
  • Response
  • Terminal Chat Interface

但還沒有:

  • Conversation History
  • Session
  • Short-term Memory
  • Long-term Memory
  • Database
  • Vector Search
  • Agent

甚至它目前連上一句話都沒有真正保存。

不過如果今天就直接使用一個 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

十二、今天的架構和 Day 30 會差多少?

現在的 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」到底差在哪裡哦!


Day 3 小結

今天終於完成這個系列第一個真的可以執行的 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呢?

明天先處理第一個問題。

Day 04|Prompt 如何改變 AI?System Prompt 到 Prompt Engineering

今天我們只是把 User Input 原封不動地交給模型:

input=user_input

所以模型要用什麼語氣、扮演什麼角色、回答多詳細,目前幾乎都沒有特別控制。

明天我們來加入 System Prompt,比較看看同一個 User Message 在不同 Prompt 下會產生什麼差異,也會開始把這個普通 Chatbot 慢慢調整成我們想要的 Personal Learning Assistant

至於它為什麼還是記不住上一句?那個問題我們留到後天再討論吧!


上一篇
Day 02|LLM 到底怎麼聊天?從 Token、Message 到 Context
下一篇
Day 04|Prompt 如何改變 AI?System Prompt 到 Prompt Engineering
系列文
從 Stateless LLM 到 Agentic Memory:30 天打造會記憶的 AI Agent7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言