iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0

最後一天。

我原本打算只寫回顧,但翻完這段日子的筆記之後改了主意——有一件事一直
只寫在文件裡,而這三十天講最多次的一句話就是「寫在文件裡的不算數」。

不把它做掉就收尾,整個系列會自己打自己的臉。

那句話

sandbox.py 的模組開頭、設計文件的限制清單、app.py 的註解,三個地方
都寫著同一句:

公開部署前必須改成 runner=None,否則任何訪客都能執行任意程式碼。

寫得很清楚,位置也很顯眼。但它的執行方式是:有人在某天記得。

這系統會真的在本機執行學生寫的 Python。擋得住無窮迴圈(逾時中止)與崩潰,
擋不住記憶體耗盡、網路、讀取使用者其他的檔案、fork 炸彈。

  • 學生在自己的電腦跑自己寫的程式 → 沒有新增任何風險,他本來就跑得了
  • 同一份程式部署到雲端 → 任何訪客都能在我的伺服器上執行任意程式碼

兩者的差別不在程式碼,在它跑在哪裡。而程式碼原本對這件事一無所知。

改法:把方向反過來

第一個想法是偵測環境——看看是不是跑在 Streamlit Cloud 上,是的話就關掉。

想了五分鐘就否決了。偵測是猜的,而猜錯的代價是遠端任意程式碼執行。

所以反過來:

EXECUTION_ENV_VAR = "CPE_ALLOW_EXECUTION"

def execution_allowed(env=None) -> bool:
    """這台機器允不允許執行學生的程式。"""
    if env is None:
        env = os.environ
    return env.get(EXECUTION_ENV_VAR, "").strip().lower() in {"1", "true", "yes"}

def runner_if_allowed(env=None):
    """允許就給執行器,不允許就給 None(評測會降級成推測)。"""
    return run_all if execution_allowed(env) else None

預設不執行,要開得自己明講。 這個方向沒有猜的餘地:忘記設定的後果只是
降級成 LLM 推測——那是安全的失敗方向。

同一個原則也用在值的判讀上:只有 1/true/yes 算開,其他一律當關。
「設了個東西」跟「明確要求執行」是兩回事,拼錯字不該把閘門打開。

真正重要的那一行改動

-def make_grade_node(client, runner=sandbox.run_all):
+def make_grade_node(client, runner=None):

原本的預設值是「執行」。也就是說,安全與否取決於呼叫端有沒有記得覆寫它。

一個預設值就是一個關於「忘記的時候該怎樣」的決定。而忘記的方向,
不該是執行任意程式碼。

這行改完,app.py 那段長長的警告註解也可以縮短了——它原本在提醒人類,
現在有東西在擋著。

順手分開兩件看起來一樣的事

左欄原本在降級時只說一句「以下是推測」。但降級有兩種原因:

  • 執行器壞了(建不出子行程、結果數量對不上)
  • 這台機器刻意不執行(閘門關著)

畫面上看起來一模一樣,但一個要修、一個不用。所以多了一句:

elif not sandbox.execution_allowed():
    st.caption("(這台機器沒有開啟執行功能,所以只能推測。"
               "`sandbox` 不是安全沙箱,預設關閉。)")

這是 Day 21 那條原則的又一次應用:看起來一樣但意義不同的兩件事,
要分得出來。

現在這個系統是什麼樣子

三個 agent,Firestore 當黑板,形成一個閉環:

導師(看程式碼、給引導、記錄弱點)
   ↓ 寫進 students/{學號}/misconceptions
黑板
   ↓ 讀出來當挑題依據
出題(從 18 題的題庫挑一題針對弱點的)
   ↓
評測(真的執行 → 事實;模型解讀失敗原因 → 一句人話)
   ↓ 全部通過
solved(記下題號,之後不再挑到)→ 導師說一句肯定
  • 導師有四級提示階梯,最低級不准出現任何程式概念的名詞
  • 評測判定來自子行程,模型只負責「為什麼」;兩者分開存,來源明講
  • 出題分兩層:純比對粗篩不需要 LLM,模型只做最後一步的挑選與說明
  • 387 支測試全綠,全部可在無 API key、無 Firestore、無 Streamlit 下跑完
  • 題庫 18 題,每題附參考解答,由執行器證明測資自洽

還沒做完的(照實寫)

  1. 真正的沙箱隔離。 今天的閘門把「預設危險」變成「預設安全」,但它不是
    隔離。把 .env 整包貼到雲端一樣會破。這是最大的一塊未完成工作。
  2. 學號未驗證。 任何人可以填別人的學號讀寫其紀錄。

一條限制消失常常換來一條新的(Day 28 學到的)。所以我不寫「已完成」,
寫的是現在卡在哪裡。

如果只帶走一句

一句寫下來的保證,跟一個擋得住的機制,中間的距離就是整個系統的可信度。

寫文件很便宜,寫註解很便宜,在心裡記著更便宜。三十天裡我犯的每一個錯,
幾乎都是把這三種便宜的東西,當成了貴的那種。

而最後一天的改動只有兩個函式、一個預設值——貴的那種,其實也沒那麼貴。

謝謝看到這裡的每一位。



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

尚未有邦友留言

立即登入留言