前幾天的 Visual Learning Lab,已經可以從 PDF 找到教材內容、建立互動模型,讓使用者調整參數,再回到原始教材確認出處。功能逐漸完整後,我開始想一件事:如果真的把這個網站公開,讓陌生人使用,會發生什麼事?
一開始我想得很簡單。反正只要把 GitHub 接到 Streamlit Cloud,再放入 OpenAI API Key,網站應該就可以用了。
但仔細想想,這其實有點可怕。假設有人不斷上傳 PDF、重複要求 AI 產生內容,費用會算在我的帳號上。更不用說使用者上傳的 PDF 不一定正常,AI 也可能產生不符合規格的資料。
所以 Day 29 我決定暫停增加新功能,改做一次比較完整的安全檢查。目標不是證明網站百分之百安全,而是先找出那些一公開就可能出問題的地方。
Visual Learning Lab 使用 OpenAI Responses API,目前的模型設定是 gpt-6.1-sol,reasoning effort 為 medium。
在自己的電腦上測試時,我知道每次按下按鈕會發生什麼事,費用也比較容易控制。但如果變成公開網站,事情就不同了。
例如使用者上傳一份 PDF,系統可能需要先分析內容,之後再呼叫 AI 建立 Learning World、Source Atlas 或其他視覺化。雖然互動模型建立完成後,拖曳和調整參數通常不必重新呼叫 AI,但初次生成仍然有成本。
我請 Codex 盤點所有可能產生付費請求的位置,最後整理出八條生成路徑,包括教材分析、概念解釋、複習、視覺化編譯、Source Atlas、Learning World、Analogy 和 Dynamic Simulation。
這次不是單純在按鈕上加一個使用次數限制,而是檢查每條路徑到底有沒有被同一套安全規則管理。
Day 29 先完成了幾件事:限制輸入資料和回應大小、避免 SDK 自動重試、設定請求逾時,以及在伺服器端阻擋未獲允許的付費生成。
不過,我們也發現一個很重要的差別。
限制單次請求的大小,不等於限制整個網站的總費用。
就算每次請求都有上限,只要公開網站允許無限制的使用者反覆發送請求,總金額還是可能持續增加。
因此這一天的決定是:先讓公開展示版本只提供已準備好的範例,新的付費生成必須等到使用者識別、總額控制和其他保護措施完成後才能開放。
老實說,這個決定有點不符合我原本想讓所有人直接玩的期待,但至少比網站上線後才發現帳單失控好。
前面幾天一直在想辦法讓 AI 更理解教材,但到了今天,我把問題反過來問:
如果教材本身有奇怪的內容,或者 AI 回傳不合法的資料,系統會怎樣?
例如 PDF 裡可能夾雜要求模型忽略原本指令的文字;AI 生成的公式可能不符合我們的運算規則;圖形節點也可能引用不存在的來源或語意物件。
這裡要區分兩件事。
第一,教材的內容可以提供給模型理解,但不能因此取得控制程式的權限。
第二,AI 回傳的結果可以是視覺化的候選規格,但不能直接當成可信任的程式來執行。
這其實延續了前面幾天的設計。Visual Learning Lab 不是讓 AI 產生一段 Python 或 JavaScript,再直接執行,而是讓模型輸出結構化資料,經過驗證之後,才交給受限制的 runtime。
今天的 Red Team 測試,就是針對這個邊界繼續找問題。
測試內容包括惡意文字、格式錯誤的 JSON、超出範圍的數值、不合法的語意引用,以及狀態已經改變卻又收到舊事件的情況。
結果也不是每個測試都找到了真正的漏洞。有些是用來確認現有防護能否正確拒絕不合法輸入,不能把它們寫成「成功阻止了真實攻擊」。
不過,這次確實找到一個值得修正的程式行為。
原本某些分析流程會在新結果成功產生之前,就先清除舊的學習狀態。假設新的 AI 請求失敗,使用者可能連原本已經建立好的內容都失去了。
因此我們把這部分改成比較接近交易式更新的方式:先取得並驗證新結果,成功後才取代舊狀態。
這不是什麼華麗的新功能,但對真的使用網站的人來說,可能比新增一種動畫還重要。
這次另一個花時間處理的問題是 PDF 套件授權。
前面為了讓使用者可以回到原始 PDF、看到頁面內容和來源位置,我們使用了 PyMuPDF 的部分功能。
程式確實能正常執行,但準備公開部署時,還必須確認相依套件的授權條件。
檢查後,我們決定把正式執行環境的 PDF 渲染部分改成 pypdfium2,另外建立 pdf_renderer.py,由這個 adapter 負責渲染與座標轉換。
要替換它並不是把套件名稱改掉就好。
PDF 頁面可能旋轉 90、180 或 270 度,也可能存在裁切區域。如果座標轉換不正確,使用者點選的公式位置就可能跟原始 PDF 對不上。
所以這次也重新測試了不同頁面旋轉角度、文字框位置、公式顯示、多頁切換,以及損壞 PDF 的拒絕處理。
最後確認正式環境不需要安裝 PyMuPDF,相關測試仍可以使用它進行比較。
但這裡不能說成所有授權問題都已經解決。過去的發行版本、第三方元件的完整授權義務,以及展示教材本身的使用條件,仍然需要分開確認。
這次使用的 MIT OpenCourseWare 教材也有自己的授權條件,因此公開展示時需要保留來源標示,並遵守非商業使用等限制。
這是我以前比較少注意的地方。原本以為開源套件能安裝、能執行就沒什麼問題,實際準備部署才發現,工程上的「可以跑」和公開發行的「可以用」是兩回事。


前面在自己的開發環境裡,已經累積很多測試套件、資料檔案和暫存內容。
但我的電腦能正常跑,不代表其他環境也能正常執行。
因此 Day 29 另外建立了乾淨的 Python 3.12 環境,只安裝正式環境需要的依賴,不放測試資料或本機私人檔案,重新執行網站。
這次也檢查正式範例是否能在沒有 OpenAI API Key 的情況下開啟。
結果 Prepared Demo 可以正常載入,從原始教材進入互動模型、調整參數,再回到來源頁面,整個流程都不需要新的付費請求。
這很重要,因為我希望即使將來 AI 功能暫時關閉,使用者還是能理解 Visual Learning Lab 到底在做什麼。
本機量測的部分結果如下:
| 操作 | 本機測得時間 |
|---|---|
| 首次開啟首頁 | 2.160 秒 |
| 再次載入首頁 | 0.309 秒 |
| 開啟 Prepared Demo | 1.419 秒 |
| 切換至來源 | 1.275 秒 |
| 調整互動參數 | 0.344 秒 |
這些數字是特定本機測試環境的結果,不代表公開網站的速度,更不是大量使用者同時操作時的效能保證。
不過,它至少讓我比較確定:目前的系統不需要每次移動滑桿都重新呼叫模型,而是能用已建立的學習狀態進行操作。
這也是 Visual Learning Lab 和一般聊天式 AI 使用方式最大的差別之一。
這次完整回歸測試結果是:
| 項目 | 結果 |
|---|---|
| 完整回歸測試 | 515 / 515 通過 |
| 測試執行時間 | 約 356 秒 |
| Day 29 實際 OpenAI API 呼叫 | 0 次 |
| 付費生成預設狀態 | 關閉 |
| Prepared Demo 本機流程 | 通過 |
| 真正 Streamlit Cloud 公開部署 | 尚未驗證 |
另外也檢查了 Git 歷史中的部分檔案與常見金鑰特徵,以及正式環境的相依套件安全公告。結果在這次設定的檢查範圍內沒有發現對應問題,但這當然不代表不存在未知漏洞。
我也讓兩個獨立瀏覽器工作階段操作準備好的範例,確認彼此的探索狀態沒有直接混在一起。不過,大量使用者、惡意 PDF 的完整程序隔離,以及真實模型攻擊測試,仍然是尚未完成的部分。
最後,我們把 Day 29 的發布結論分成兩種情況:
第一種是 Prepared Demo。 已經可以作為受限、非商業展示版本的候選方案,提供教材來源、互動模型與來源回溯功能,但仍須完成實際雲端驗收和授權確認。
第二種是開放使用者自己上傳 PDF、由我的 API Key 支付生成費用。 這還不能直接公開,因為帳號識別、全站預算和雲端持久化控制都需要額外驗證。
所以今天最終並不是完成了公開部署,而是釐清了哪些部分可以準備發布,哪些部分還不能冒險開放。
Day 29 跟前面幾天很不一樣。
之前每一天,我都在想怎麼讓 Visual Learning Lab 多理解一些教材、多呈現一種圖形,或者讓操作更直覺。
今天卻是在問:如果 AI 不照預期回答、使用者上傳奇怪的東西、API 請求失敗,甚至網站被很多人同時使用,原本做好的功能還能不能正常運作?
這次讓我發現,做出一個能在自己電腦上展示的 AI 專案,和做出一個敢交給陌生人使用的服務,中間還差了不少工程工作。
我也更理解為什麼不能只看測試數量。515 個測試通過是有意義的,但它證明的是這次被測試的情境符合預期,不代表真實部署已經完成,也不能保證每一份教材都會被模型正確理解。
現在的 Visual Learning Lab 至少已經不只是把 PDF 丟進 AI,再把回答顯示出來。它有自己的來源資料、經過驗證的互動規格、執行狀態,以及處理失敗和限制付費請求的機制。
明天就是 Day 30。
最後一天,我不打算再增加一堆新功能,而是把這 30 天的開發、失敗、修正、測試與成果整理成一個完整的作品,讓別人看得懂我到底做了什麼,也能自己判斷它目前真正做得到哪些事。
GitHub: https://github.com/f24131128-lgtm/visual-learning-lab