昨天我們已經找到了 Memora 接不上前一句的原因。問題不是 LLM 把資訊忘掉了,而是我們每次呼叫 API 時,只送出當下的 user_input,沒有把先前的對話一起放進新的 Request。
所以今天不再重新分析一次 Stateless,而是直接從 Day 5 的程式繼續修改,替 Memora 加入第一種最基本的「記憶」:
Conversation History。
它的概念很直接:把每一輪的對話保存下來,下一次呼叫模型時,再連同新的訊息一起送出去。
假設我和 Memora 有這樣一段對話:
User:
我的英文程度是 B1。
Assistant:
了解,我會以 B1 程度協助你。
User:
幫我出五題適合我的英文題目。
如果只把所有句子存成純文字,模型可能不知道每一句話是誰說的。因此,Conversation History 不只要保存內容,還要保留每一則 Message 的 role:
conversation_history = [
{
"role": "user",
"content": "我的英文程度是 B1。"
},
{
"role": "assistant",
"content": "了解,我會以 B1 程度協助你。"
},
{
"role": "user",
"content": "幫我出五題適合我的英文題目。"
}
]
其中:
role = user
代表這是使用者說的話;而:
role = assistant
則代表這是模型先前的回答。
所以 Conversation History 的本質就是:
一串按照時間順序排列,並保留說話者角色的 Message。
昨天的 Memora 已經有:
OpenAI Client
System Prompt
while Loop
exit 指令
今天不更換原本的架構,只加入三個步驟:
1. 建立 conversation_history
2. 把 User 與 Assistant Message 存進去
3. 下一次 Request 時,把整段 History 交給模型
修改後的完整程式如下:
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.3")
print("輸入 exit 可以結束對話。")
conversation_history = []
while True:
user_input = input("\nYou: ")
if user_input.lower() == "exit":
print("Bye!")
break
conversation_history.append(
{
"role": "user",
"content": user_input
}
)
response = client.responses.create(
model="gpt-5-mini",
instructions=SYSTEM_PROMPT,
input=conversation_history
)
assistant_reply = response.output_text
conversation_history.append(
{
"role": "assistant",
"content": assistant_reply
}
)
print("Memora:", assistant_reply)
和昨天相比,原本的 while Loop、System Prompt 與 API 呼叫方式都還在。真正新增的部分,只有 Conversation History 的建立、更新與傳入。
OpenAI 的 Responses API 可以接受由多則 user、assistant Message 組成的 input,因此我們可以用這種方式手動管理 Conversation State。相關格式也可以參考 OpenAI 官方文件。
程式啟動時,先建立一個空的 List:
conversation_history = []
這時 Memora 還沒有任何對話紀錄。
當使用者輸入訊息後,先把新的 User Message 加進去:
conversation_history.append(
{
"role": "user",
"content": user_input
}
)
接著,Day 5原本傳入的是:
input=user_input
今天則改成:
input=conversation_history
因此模型收到的不再只有目前這一句,而是到目前為止保存的整段 Conversation History。模型回答後,我們再把這次回答存進同一個 List:
assistant_reply = response.output_text
conversation_history.append(
{
"role": "assistant",
"content": assistant_reply
}
)
下一輪開始時,新的 User Message 會繼續加在後面,Conversation History 也就會隨著對話一輪一輪成長。
整個流程可以整理成:
取得 User Message
↓
加入 Conversation History
↓
將 History 送給 LLM
↓
取得 Assistant Message
↓
加入 Conversation History
↓
進入下一輪
現在可以重新測試昨天留下來的問題。
先輸入:
You:
我的秘密代碼是 ZQ-731-MANGO,請記住。
Memora:
好的,我記住了你的秘密代碼是 ZQ-731-MANGO。
接著再問:
You:
我的秘密代碼是什麼?
Memora:
你的秘密代碼是 ZQ-731-MANGO。
這一次 Memora 能夠回答,是因為第二次呼叫 API 時,conversation_history 已經包含:
[
{
"role": "user",
"content": "我的秘密代碼是 ZQ-731-MANGO,請記住。"
},
{
"role": "assistant",
"content": "好的,我記住了你的秘密代碼是 ZQ-731-MANGO。"
},
{
"role": "user",
"content": "我的秘密代碼是什麼?"
}
]
模型在產生第二次回答時,真的看得到第一輪的內容。
因此,Conversation History 做的事情可以理解成:
把過去的 Conversation 重新放進現在的 Context。
看到這裡可能會有一個疑問:如果只是希望 AI 記得使用者說過什麼,只保存 User Message 不就好了嗎?
考慮下面這段對話:
User:
請給我三個適合 B1 程度的英文單字。
Assistant:
travel
journey
abroad
User:
第二個是什麼意思?
最後一句的「第二個」,指的是 Assistant 上一輪回答裡的 journey。
如果 History 只保存 User Message,模型看到的會是:
請給我三個適合 B1 程度的英文單字。
第二個是什麼意思?
它知道自己曾經被要求提供三個單字,卻不知道當時實際回答了哪三個。
因此,完整的 Conversation History 通常會交替保存:
User
Assistant
User
Assistant
...
Conversation State不只包含使用者提供的資訊,也包含模型先前做出的回應。
昨天曾經透過 Debug 檢查每一次 Request 裡有什麼。今天也可以加入一個簡單的 history 指令,直接查看目前保存的 Conversation History。
把下面這段程式放在 exit 判斷之後、加入 User Message 之前:
if user_input.lower() == "history":
print("\n--- Conversation History ---")
for message in conversation_history:
print(f"{message['role']}: {message['content']}")
print("----------------------------")
continue
也就是:
if user_input.lower() == "exit":
print("Bye!")
break
if user_input.lower() == "history":
print("\n--- Conversation History ---")
for message in conversation_history:
print(f"{message['role']}: {message['content']}")
print("----------------------------")
continue
conversation_history.append(
{
"role": "user",
"content": user_input
}
)
聊了幾輪之後輸入:
You:
history
就可能看到:
--- Conversation History ---
user: 我的英文程度是 B1。
assistant: 了解,我會以 B1 程度協助你。
user: 幫我出五題適合我的英文題目。
assistant: 當然可以,以下是五題……
----------------------------
這個 List 就是目前 Memora 保存的Conversation State。
從使用者的角度來看,Memora 現在確實「記得」上一句了。
不過從系統架構來看,真正保存資料的是:
conversation_history
也就是我們的 Python Application。
每一次 API Request 本身仍然是獨立的,只是 Application 會在呼叫模型之前,重新把先前的 Message 放進 input。
實際流程是:
Python Application
保存 Conversation History
↓
下一次 Request 重新帶入
↓
LLM 看見先前的 Message
↓
產生符合上下文的回答
因此,現在 Memora 表現出的「記憶能力」,可以更精確地寫成:
Application State + Context Reconstruction
也就是由 Application 保存狀態,再替模型重建這一輪需要的 Context。
接下來可以做另一個測試。先告訴Memora:
You:
我的英文程度是 B1。
接著確認它還記得:
You:
我的英文程度是多少?
Memora:
你的英文程度是 B1。
然後輸入:
exit
關閉程式,再重新執行:
python chatbot.py
這時候:
conversation_history = []
會重新建立一個空的 List,剛才的對話也就消失了。所以目前的 Memora 可以記住:
同一次程式執行中的對話
但還不能跨越程式重啟或不同 Conversation 保存資訊。這也實際驗證了昨天提過的界線:Conversation History 能讓 AI 記住「這次對話」,但還不是後面要處理的 Long-term Memory。
previous_response_id?昨天提過,Responses API 本身也提供 previous_response_id,另外還有 Conversations API 可以協助管理 Conversation State。
這些方法確實更方便,但目前先不使用,因為後面的章節還會繼續處理:
Sliding Window
Conversation Summary
Context Management
先自己管理 conversation_history,我們才能直接觀察哪些 Message 被保存、哪些 Message 被送進 Context,以及後面該如何刪除、保留或整理它們。
等理解這些底層機制後,再使用 API 或 Framework 提供的功能,就會更清楚它們替我們處理了什麼。
Day 5 的 Memora 是:
Current User Input
↓
LLM
↓
Response
今天則變成:
Conversation History
+
Current User Input
↓
LLM
↓
Assistant Response
↓
更新 Conversation History
因此,從 Application 的角度來看,Memora 已經從 Stateless Chatbot 跨出了第一步,成為一個最基本的 Stateful Chatbot。
不過新的問題也馬上出現了。我們現在每一次呼叫 API,都會執行:
input=conversation_history
假設聊了 3 輪,History 大約有 6 則 Message;聊了 100 輪,就可能累積到 200 則 Message。
也就是說:
第 1 輪
History 很短
第 10 輪
History 逐漸變長
第 100 輪
History 非常龐大
而 Day 2 已經提過,模型處理的文字最後都會轉換成 Token。
所以現在很自然會產生下一個問題:
Conversation History 可以無限累積嗎?
答案當然不是。
今天我們延續昨天的程式,加入了三個關鍵步驟:
保存 User Message
↓
把 Conversation History 送給模型
↓
保存 Assistant Message
程式裡最重要的改變是:
conversation_history = []
以及:
input=conversation_history
但真正需要理解的並不是 Python List 本身,而是:
Conversation History 會保存先前的 Message,並在下一輪重新把它們帶回 Context。
目前 Memora 的演進來到:
Day 03
Stateless Chatbot
↓
Day 04
加入 System Prompt
↓
Day 05
理解 Stateless
↓
Day 06
加入 Conversation History
↓
Stateful Chatbot
今天我們解決了「AI 怎麼接得上上一句」,同時也產生了第一個 Context Management 問題:
如果 Conversation History 一直累積,模型到底能放進多少內容?
這就是明天要討論的主題。
下一篇,我會把Day 2提過的 Token 再拿回來討論討論,進一步看看:
今天我們讓 Memora 學會記住這次對話。明天開始面對第一個 Memory Engineering 問題:記得越多,真的越好嗎?