iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0

昨天我們替 Memora 加上了 System Prompt,讓原本什麼都能聊的 Chatbot,開始有一個比較明確的角色。

目前它已經知道自己是一個 Personal English Learning Assistant,也知道回答時要盡量簡單、聚焦,必要時提供例子。但有一個問題從Day 3到現在都沒有解決。假設我先告訴它:

You:
我的英文程度是 B1,請記住。

Memora:
好的,我會記住你的英文程度是 B1。

接著馬上問:

You:
我的英文程度是多少?

它卻不一定答得出來。明明上一句才說「我會記住」,為什麼下一句就忘了?

今天我們來把這件事情真正拆開來看。因為理解這個問題之後,下一章才能正式開始替 Memora 加上第一種「記憶」。


一、先釐清一件事:不是所有 LLM API 都「一定沒有狀態」

這篇的標題叫做「理解 Stateless API」,但這裡要先講得精確一點。

現在有些 LLM API 已經提供保存或延續 Conversation State 的機制。例如 OpenAI Responses API 支援透過 previous_response_id 延續多輪對話,也有 Conversations API 可以保存並管理跨多次 Response 的 Conversation State。

所以嚴格來說:

不是「所有 LLM API 天生都一定 Stateless」,而是我們目前寫出的 Memora 是 Stateless。

這個差別很重要。從 Day 3 開始,我每一次都這樣呼叫模型:

response = client.responses.create(
    model="gpt-5-mini",
    instructions=SYSTEM_PROMPT,
    input=user_input
)

我沒有提供:

Conversation History
previous_response_id
Conversation ID
Database中的歷史訊息

所以站在我們目前這個 Application 的角度,每一次 Request 都是獨立的。

今天要研究的,就是這種最基本的 Stateless 寫法到底發生了什麼!


二、先重新看看目前的 Memora

昨天完成的版本大概是:

from openai import OpenAI

client = OpenAI()

SYSTEM_PROMPT = """
You are Memora, a personal English learning assistant.

Your goal is to help the user learn English clearly and efficiently.

Guidelines:
- Explain concepts in simple language.
- Keep answers focused on the user's question.
- Use short examples when helpful.
- Avoid unnecessary technical grammar terminology.
"""

print("Memora v0.2")
print("輸入 exit 可以結束對話。")

while True:
    user_input = input("\nYou: ")

    if user_input.lower() == "exit":
        print("Bye!")
        break

    response = client.responses.create(
        model="gpt-5-mini",
        instructions=SYSTEM_PROMPT,
        input=user_input
    )

    print("Memora:", response.output_text)

從 Terminal 看起來,它非常像一個正常的聊天程式:

You: Hello!

Memora: Hello! How can I help you with English today?

You: Explain present perfect.

Memora: The present perfect is used when...

You: Give me another example.

Memora: ...

問題就在這裡。

畫面看起來是連續的,不代表送給模型的資料也是連續的。


三、while Loop 只是讓程式一直跑

我們的程式裡有:

while True:

所以回答完之後,程式不會結束,而是繼續等待下一個輸入。

這讓 Terminal 呈現出:

User
↓
AI
↓
User
↓
AI
↓
User
↓
AI

很容易讓人產生一種感覺:

既然程式一直沒有關掉,它應該知道剛剛聊過什麼吧?

while Loop 做的事情只有:

讓同一段程式重複執行。

它並沒有自動建立:

conversation_history = [...]

也沒有幫我保存:

User Message 1
Assistant Message 1
User Message 2
Assistant Message 2

所以:

while loop
≠
Conversation History

這是理解目前 Memora 為什麼沒有記憶的第一個關鍵。


四、直接把每一次 Request 攤開來看

假設我第一次輸入:

我的秘密代碼是 ZQ-731-MANGO,請記住。

程式真正執行的是:

response = client.responses.create(
    model="gpt-5-mini",
    instructions=SYSTEM_PROMPT,
    input="我的秘密代碼是 ZQ-731-MANGO,請記住。"
)

這就是 Request 1

模型看到的資料可以先簡化成:

Instructions:
You are Memora...

Input:
我的秘密代碼是 ZQ-731-MANGO,請記住。

接著模型回答:

好的,我會記住你的秘密代碼是 ZQ-731-MANGO。

目前看起來都沒問題。

接下來我再輸入:

我的秘密代碼是什麼?

程式又重新執行:

response = client.responses.create(
    model="gpt-5-mini",
    instructions=SYSTEM_PROMPT,
    input="我的秘密代碼是什麼?"
)

注意看這一次 Request 裡有什麼。

有:

System Instructions

有:

我的秘密代碼是什麼?

但是沒有:

我的秘密代碼是 ZQ-731-MANGO。

也沒有上一輪 Assistant 的回答。所以真正的情況其實是:

Request 1
────────────────────────
System Prompt

我的秘密代碼是
ZQ-731-MANGO,請記住。
────────────────────────
            ↓
           LLM
            ↓
        Response 1

接著:

Request 2
────────────────────────
System Prompt

我的秘密代碼是什麼?
────────────────────────
            ↓
           LLM
            ↓
        Response 2

Request 2 裡根本沒有 Request 1。

這就是目前 Memora「失憶」的真正原因。


五、它不是忘記,而是根本沒收到

這裡我覺得有一個用詞差異很重要。

我們平常很容易說:

AI 忘記了。

但以目前這個程式來說,更精確的說法其實是:

這一次 Request 根本沒有把上一輪的資訊提供給模型。

模型不是先拿到:

ZQ-731-MANGO

然後把它弄丟。

而是第二次呼叫時,它根本沒有收到這段資訊。

所以目前的架構其實是:

Request 1
Current Input
    ↓
   LLM
    ↓
Response 1


Request 2
Current Input
    ↓
   LLM
    ↓
Response 2

而不是:

Request 1
    ↓
保存
    ↓
Request 2
    ↓
帶入過去資料
    ↓
LLM

這就是 Stateless 在我們目前 Application 裡最直接的樣子。


六、那為什麼有時候它好像還是答得出來?

這裡還有一個滿有趣的陷阱。

假設我第一句說:

我的英文程度是 B1。

第二句問:

我的英文程度是多少?

有時候模型可能會回答:

我不知道,請告訴我你的英文程度。

但也有可能出現一些看起來像是「它還記得」的回答。這時不能只靠輸出結果判斷系統有沒有 Memory。因為 LLM 是生成模型,它有可能根據語言模式猜測,也有可能在資訊不足時產生一個看似合理的答案。

所以要判斷系統到底有沒有保存 State,最可靠的方法不是問:

「模型這次答對了嗎?」

而是直接檢查:

「這次 Request 到底包含了什麼?」

這也是為什麼我在這個系列裡會一直把 Application 和 LLM 分開來看。模型的回答有一定的不確定性,但程式送了什麼資料,是我們可以直接檢查的。


七、加一個 Debug,直接看每次送了什麼

為了讓這件事情更明顯,我可以稍微修改程式,在每次呼叫 API 前把 Request 的主要內容印出來。

from openai import OpenAI

client = OpenAI()

SYSTEM_PROMPT = """
You are Memora, a personal English learning assistant.

Your goal is to help the user learn English clearly and efficiently.

Guidelines:
- Explain concepts in simple language.
- Keep answers focused on the user's question.
- Use short examples when helpful.
"""

request_count = 1

while True:
    user_input = input("\nYou: ")

    if user_input.lower() == "exit":
        print("Bye!")
        break

    print(f"\n--- Request {request_count} ---")
    print("Input:", user_input)
    print("-------------------")

    response = client.responses.create(
        model="gpt-5-mini",
        instructions=SYSTEM_PROMPT,
        input=user_input
    )

    print("Memora:", response.output_text)

    request_count += 1

接著輸入:

You:
我的秘密代碼是 ZQ-731-MANGO。

Terminal 會顯示:

--- Request 1 ---
Input: 我的秘密代碼是 ZQ-731-MANGO。
-------------------

下一次輸入:

You:
我的秘密代碼是什麼?

則會看到:

--- Request 2 ---
Input: 我的秘密代碼是什麼?
-------------------

這時就非常清楚了。

第二次 Request 裡沒有:

我的秘密代碼是 ZQ-731-MANGO。

所以問題不是 LLM Memory 壞掉,而是:

我們根本還沒有寫 Memory。


八、State 到底是什麼?

既然一直在講 Stateless,那 State 到底是什麼?

在這個 Chatbot 的情境裡,我可以先把 State 理解成:

需要從前一次互動保留下來,並影響後續互動的資訊。

例如:

使用者之前說過什麼
Assistant 之前回答什麼
目前是哪一段 Conversation
使用者的設定
現在做到哪一個任務

最簡單的 Conversation State 可能長這樣:

messages = [
    {
        "role": "user",
        "content": "我的英文程度是 B1。"
    },
    {
        "role": "assistant",
        "content": "了解。"
    }
]

第二次 User 再說:

幫我出適合我的題目。

我們就把它加入:

messages = [
    {
        "role": "user",
        "content": "我的英文程度是 B1。"
    },
    {
        "role": "assistant",
        "content": "了解。"
    },
    {
        "role": "user",
        "content": "幫我出適合我的題目。"
    }
]

再把這些內容交給模型。

這時第二個 Request 才真正和第一個 Request 產生關係。

也就是:

Stateless

Current Message
      ↓
     LLM

開始變成:

Stateful

Previous Messages
       +
Current Message
       ↓
      LLM

這就是明天要真正實作的東西。


九、State 可以放在哪裡?

Conversation State 其實不一定只有一種保存方式。

例如最簡單的做法,可以存在 Python 記憶體裡:

messages = []

程式關掉之後就消失。

也可以之後存進:

SQLite
PostgreSQL
Redis

讓程式重新啟動後還能讀回來。

另外,一些 LLM API 本身也提供 Conversation State 的管理方式。以目前的 OpenAI Responses API 為例,可以使用 previous_response_id 建立多輪 Conversation,也可以使用 Conversations API 保存並取得跨 Response 的 Conversation State。

也就是說,State 大致可以由不同地方管理:

Application Memory

Database

LLM Provider 的 Conversation API

那為什麼我明天不直接用 previous_response_id 就好?因為這個系列的目標不是只讓 Chatbot「可以聊天」。我們更應該先把底層邏輯弄清楚。

所以 Day 6 會先自己管理:

Conversation History

等理解 State 到底是怎麼被保存和送回模型之後,再來看 API 或 Framework 幫我們包掉了哪些事情。


十、Conversation History 還不是 Long-term Memory

這裡也要先提醒一件後面重要的事。假設明天我成功建立:

messages = []

然後讓 AI 可以記得:

User:
我的英文程度是 B1。

Assistant:
了解。

User:
我的英文程度是多少?

Assistant:
B1。

這是不是就代表 Memora 已經有 Long-term Memory勒?

還不是。

因為這時我們只是保存:

這一段 Conversation 裡發生過什麼。

而且可能程式一關掉:

messages = []

就全部消失。後面的 Long-term Memory 要解決的是更複雜的問題,例如:

哪些對話值得留下?

跨不同 Conversation 還要記得嗎?

一千則訊息裡現在需要哪一則?

使用者的偏好改變怎麼辦?

哪些資訊應該忘記?

所以接下來的路線會是:

Day 05
Stateless
      ↓
Day 06
Conversation History
      ↓
Day 10
Short-term Memory
      ↓
Day 17
Long-term Memory
      ↓
Day 29
Agentic Memory

不是「把 messages 存起來」就直接跳到長期記憶。


十一、現在的 Memora 到底缺什麼?

到今天為止,我們已經有:

Python
LLM API
System Prompt
User Input
Response
while Loop

目前架構是:

┌───────────────────┐
│   System Prompt   │
└─────────┬─────────┘
          │
          │
┌─────────▼─────────┐
│ Current User Input│
└─────────┬─────────┘
          ↓
        LLM API
          ↓
       Response

現在唯一跟「聊天」相關、但我們還沒有做的就是:

Previous Messages

也就是:

┌───────────────────┐
│   System Prompt   │
├───────────────────┤
│ Previous Messages │ ← 還沒有
├───────────────────┤
│ Current Message   │
└─────────┬─────────┘
          ↓
         LLM

而這一塊,正好就是 Chapter 2 的起點。


Day 5 小結

Chapter 1 到這裡差不多完成了。

我們從 Day 1 的「AI 到底有沒有記憶」,一路拆到今天,已經可以比較精確地回答這個問題。

目前 Memora 會忘記上一輪,不是因為模型突然失憶,而是因為我們每次呼叫 API 時,只把 Current User Input 放進新的 Request,沒有把 Previous Messages 一起帶進去。

所以目前的架構是:

Request 1
↓
Response 1

Request 2
↓
Response 2

Request 3
↓
Response 3

各自獨立。

另外也要記住,現代 LLM API 本身可能提供延續 Conversation State 的功能,因此「LLM API 一定是 Stateless」這句話並不精確。OpenAI Responses API 目前就支援 previous_response_id,也提供 Conversations API 管理 Conversation State。

但我們目前刻意沒有使用這些機制。因為接下來,我要自己把 State 加進 Memora。

今天的內容總結:

能一直呼叫 LLM,不代表有 Conversation;真正讓多輪對話成立的,是上一輪的資訊有沒有被保存,並再次帶進下一輪。

現在的 Memora 是:

Day 03
LLM API
↓
Stateless Chatbot

Day 04
System Prompt
↓
Prompted Stateless Chatbot

Day 05
看懂 Request
↓
確認它為什麼 Stateless

Chapter 1 到這裡,我們已經做出了一個可以聊天、知道自己該怎麼回答,卻完全沒有 Conversation State 的 AI。

接下來可以開始替它加入第一種真正的「記憶」囉!

Day 06|Conversation History 是什麼?讓 AI 接得上上一句

明天我們來正式建立:

messages = []

把每一輪:

User Message
Assistant Message

保存下來,再在下一次 Request 時一起提供給模型。

到時候我會重新做今天的秘密代碼實驗:

You:
我的秘密代碼是 ZQ-731-MANGO。

You:
我的秘密代碼是什麼?

看看加入 Conversation History 之後,結果會發生什麼變化。

從明天開始,Memora 終於要從:Stateless Chatbot正式跨出第一步,走向:Stateful Chatbot!


上一篇
Day 04|Prompt 如何改變 AI?System Prompt 到 Prompt Engineering
下一篇
Day 06|Conversation History 是什麼?讓 AI 接得上前一句
系列文
從 Stateless LLM 到 Agentic Memory:30 天打造會記憶的 AI Agent7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言