iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0

從第 1 期開始,這個系統的 checkpointer 一直是 MemorySaver。

它做得很好——Streamlit 每次互動都從第 1 行重跑整份 script,
靠它才能把對話、stuck_count、提示階梯帶過每一次 rerun。
第 1 期那個「@st.cache_resource 忘記包就全毀」的坑,講的也是它。

但它有個名字就寫著的限制:Memory。

Ctrl+C 一按、改一行程式碼觸發 reload、機器重開——
學生跟導師講了十輪的過程,連同他卡到 level 2 的那個階梯,全部歸零。
下次進來,導師從「你覺得這段程式碼想做什麼呢」重新開始問。

今天把它換成 SQLite。

換掉很簡單,難的是驗收

程式碼的改動只有一行:

checkpointer=SqliteSaver(_checkpoint_conn())

真正花時間的是測試。因為現有那條 checkpointer 測試長這樣:

graph.invoke(_base_state(), config)
out = graph.invoke({}, config)      # 第二次不重傳整份 state
assert out["stuck_count"] == 2

這條測試換成 SQLite 照樣會過,換回 MemorySaver 也會過。
它驗的是「同一個 graph 物件記得上一次」,而那正是 MemorySaver 本來就會的事。

要驗「跨行程」,就得在測試裡模擬一次重啟:

graph, conn = _sqlite_graph(db_path)
graph.invoke(_base_state(), config)
conn.close()
del graph                                   # graph 物件沒了

graph, conn = _sqlite_graph(db_path)        # 「重啟」:新連線、新 graph
out = graph.invoke({}, config)
assert out["stuck_count"] == 2              # 不是 1

只有那個檔案路徑是共用的,其餘全部重建。

寫完一次過,而「一次過」本身就該懷疑。所以我把 saver 臨時換回 MemorySaver
跑了一次——紅了。確認過會紅的測試,才知道它在守東西。
(這是 Day 23 那個教訓的另一面:綠燈要能解釋。)

另外補了兩條:不同 thread_id 在同一個檔案裡不會互相污染、
新學生第一次進來拿到的是乾淨狀態而不是別人留下的。
從前 MemorySaver 時代這些隔離「顯然成立」,
現在所有人的狀態裝在同一個檔案裡,它才開始有代價。

三個實作細節

check_same_thread=False。 SQLite 的 Python 綁定預設禁止跨 thread 使用
同一條連線,而 Streamlit 的 script 不保證跑在同一個 thread。不關掉的話,
第二次互動就直接拋例外。

連線不關。 它由 cache_resource 持有到行程結束——生命週期就是 app 的
生命週期,沒有「用完」的那一刻。

那段註解要改。 原本寫著:

MemorySaver 尤其重要——每次 rerun 都新建一個的話,checkpointer 等於沒用

現在不成立了。state 在檔案裡,不快取的後果從「正確性壞掉」降級成
「每次 rerun 重開一條連線」,是效能問題。註解要跟著改,不然它會變成一句
曾經為真、現在騙人的話——而這種註解比沒有註解更糟,因為讀的人會信。

一件不能含糊的事

Streamlit Community Cloud 的磁碟是暫時的,重新部署就沒了。

所以這個改動解決的是本機重啟,不是雲端持久化。README 上我寫死了這句,
沒有含糊成「支援持久化」。要雲端也保住得換 Postgres saver 或把 state 寫進
Firestore,那是另一件事,今天沒做。

這跟前幾天的「不可以謊稱跑過測試」是同一條紀律,只是對象從學生換成了未來的我。

昨天那條保守預設,今天變成真的了

Day 24 加了一條規則:判定裡沒有 graded_code 欄位就當它過期。

當時那是假想情況——MemorySaver 一重啟就什麼都沒了,哪來的舊 checkpoint。
今天換成 SQLite 之後,舊格式的 state 真的會躺在檔案裡被讀回來。

昨天寫的時候我只是照原則辦事,今天它變成了一個真的會走到的分支。
這件事值得記下來:「防禦未來」的程式碼,有時候一天之後就不再是防禦了。

今天學到的

  1. 測試要驗的是新能力,不是舊能力。 換儲存層的時候,原本那條測試
    換了實作照樣過——它從來沒在測我今天想要的東西。
  2. 一次就綠的測試要懷疑。 臨時把實作改壞,確認它會紅。
  3. 前提變了,註解會從「有用」變成「有害」。 它不會報錯,只會騙人。
  4. 限制要寫死在 README 裡,不要寫得模稜兩可。 「本機重啟不會歸零」
    和「支援持久化」差很多。

測試 324 → 327 支,全綠。另外真的把 app 跑起來確認開得起來、DB 檔生得出來。


上一篇
Day 24:延遲對話
系列文
基於 Multi-Agent 協作之蘇格拉底式程式學習與自動化評測系統 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言