用 OpenAI Agents API(測試版)規劃的「古典書院海報平台」:後台怎麼隔離權限、記稽核、做效能,再怎麼轉成讓 AI Agent 也能安全操作的 Skill 技術包。兩份講義共 27 頁,加一張資料庫架構圖,拆成七課,跟著課程地圖一課一課看下去。
老師這次分享的是一套用 OpenAI Agents API(測試版)規劃出來、可以治理的海報製作系統。系統自訂了 sandbox(以 OpenAI client 建出來的虛擬平台),能管理、預覽、監控,並且有運行管理與稽核管理兩塊。老師接著把整套系統轉成 agent skill framework,可以配置到 GPT work、Claude cowork,或是自建的 agents 平台。他強調的重點是:這是一個可以監控、可以管理的 LLM client sandbox VM。整套系統用 Sonnet 5.5 開發。
「我沒有使用 xhigh,只使用 High。」
編者註:xhigh、High 是模型的推理強度檔位。
「全自動化配置 skills workflow 讓他跑一整個晚上,完成九個完整系統範例,以及錄影與教案文件以及海報製作。(我有一套完整的 skill framework)」
老師分享的影片(在 YouTube 開啟)
08 古典後臺儀表板與畫廊式海報圖庫
youtu.be/MkgkPaPgyNQ・艾立克程式設計學院


投影片上的字在手機上比較小,每一張圖都可以點一下放大。
第 01 課・稽核講義 p.02–04
古典書院海報平台的畫廊,有兩種人在用同一個服務:一般使用者只想看自己做的海報,管理者要看全站。第一課先看講義怎麼回答它的核心問題:同一個服務入口,要怎麼確保資料與權限絕對隔離。

左邊是個人畫廊,範圍標成 own:一般使用者只看得到自己的作品,講義強調絕對隱私,就算有人在網址後面硬加 ?user=admin 這種越權參數,也會被無視。右邊是全站監控,範圍標成 all:管理者看全局統計並處理異常,而且每個動作都強制留下稽核紀錄。中間那塊提出整份講義的核心問題,答案在下一張圖。

講義的答案是在請求進來的路上放三道關卡。第一關在路由層,用 Depends(require_admin) 擋下沒有授權的身分;第二關在服務層,再確認一次角色是不是 scope="all",就算路由層那一關被誰不小心拿掉,服務層還是會丟出 GalleryDenied。第三關在查詢層,範圍是 own 時,系統直接在 SQL 裡綁死 PosterJob.user_id = viewer.id,前端傳來什麼篩選條件都不採用。這種做法叫縱深防禦:任何一道單獨失效,另外兩道還守得住底線。

這張矩陣把「誰打哪條路由會得到什麼回應」列成表。沒登入的匿名者得到 303 或 401(303 是轉址、401 是未授權),登入了但不是管理者的一般會員得到 403,只有管理者拿到 200。圖上列了三條路由,另外還有 17 條以上的敏感路由,都在同一張矩陣的自動化測試看守之下。講義另外提到「破壞式驗證」:測試時故意把某條路由的保護拿掉,看測試會不會真的報錯,用這種方式證明測試本身抓得到問題。
| 路由 | 匿名者 | 一般會員 | 管理者 |
|---|---|---|---|
/admin/gallery | 303 / 401 | 403 | 200 |
/api/admin/stats | 303 / 401 | 403 | 200 |
/admin/jobs/{id}/cancel | 303 / 401 | 403 | 200 |
| 其他 17 條以上敏感路由 | 303 / 401 | 403 | 200 |
上表是把 p.04 的矩陣照圖重打成文字,方便手機閱讀與搜尋。
own(只看自己)與 all(全站)兩種範圍,使用者傳來的越權參數一律無效。/admin 路由都有自動化矩陣測試,而且測試會故意拆掉保護來確認它抓得到。第 02 課・稽核講義 p.05–08
畫廊要快,儀表板上的數字要可信。這一課看講義怎麼用縮圖與查詢技巧把畫廊變輕,又怎麼把成功率、平均處理時間這些數字的算法先定義清楚。

左邊是沒做縮圖的下場:一頁 12 張原圖、每張約 2 MB,一次要傳 24 MB,畫廊一打開就把伺服器的頻寬與記憶體吃光。右邊是講義的做法:背景 Worker 先把圖轉成 RGB,縮到長邊 360px,存成 82% 品質的 JPEG,再寫進資料庫的 BLOB 欄位;12 張縮圖每張約 20 KB,一頁不到 250 KB。圖底的流程是:列表只讀縮圖,使用者點擊放大時,才走 /gallery/{id}/image 去載入原圖。

第一,一頁 12 筆的清單查詢,刻意不撈 content 與 thumb 這兩個大型二進位欄位(延遲載入 BLOB),翻頁時就不會搬大檔。第二,page = min(max(1, page), pages) 讓使用者輸入 page=99 這種超出範圍的頁碼時,自動收斂到最後一頁,不會白畫面,也不會 500 錯誤。第三,舊資料沒有縮圖時,現場補做並存回資料庫(Lazy Generation),第二次讀取就生效,新舊資料不用另外搬遷。

成功率的公式是「已完成 ÷(已完成+失敗)」,取消與排隊中的工作不算進去,因為它們還沒有結果,或是被人為中斷,不該拉低系統真實的能力指標;沒有資料時顯示「—」而不是「0%」,免得被誤讀成全部失敗。平均處理時間只算成功的工作,公式是「完成時間 − 最後一次認領時間」,也就是扣掉排隊時間,失敗的耗時不混進來。圖上的 82.4% 與 24.5s 是版面上的示例數值,講義沒有註明是否為實測。

上半張是每日工作量長條圖(圖上畫 Oct 01 到 Oct 05)。SQL 的 GROUP BY 只回傳「有資料的日期」,沒工作的那一天整根長條會消失,時間軸就出現斷層,所以講義讓 Python 應用層動態補 0,圖上 Oct 03 的 0 就是補出來的。下半張是失敗原因分佈,有 Connection Error、Timeout、Data Invalid、Processing Fail 四類,其中 Connection Error 最多。長條用紅色斜線填滿,是無障礙設計(a11y)的做法:不能只靠顏色區分,色覺障礙的人也要看得出失敗狀態。
第 03 課・稽核講義 p.09–12
稽核是這份講義的收尾:一筆紀錄由什麼組成、什麼動作該記、哪些邊界要特別小心,最後把前三課拼成一個整體。

每一筆紀錄由四個欄位組成。Actor 存的是操作者名稱的快照,管理員日後就算被刪除,紀錄裡還留得住當時是誰,不會變成沒有主人的空值。Action 寫入前要對照固定清單,拼錯的動作名稱會直接丟 ValueError,不會悄悄進資料庫。Target 指出被影響的資源(圖上舉例是海報 #15),Result/Detail 則精確標記 ok 或 denied。最底下的核心守則是只增不改(Append-Only):系統不提供修改或刪除的 API,對 /admin/audit 送 PUT、PATCH、DELETE 一律回 405。

目標找不到(404)就不寫紀錄,避免製造垃圾雜訊。找得到、業務規則也允許(200),就執行動作並寫入稽核,結果標 OK。找得到但業務規則不允許(409,例如想取消已經完成的工作),拒絕動作,但仍然寫入稽核,結果標 Denied。講義的看法是:「管理者想做但被系統擋下」這件事本身,就是很有價值的資安追查線索。圖底橫幅的最後一句在「拒絕,也」處被截斷,後半句圖中文字未能辨識,這裡不推測。

第一格談「檢視」與「下載」:管理者只是看別人的圖不記錄,免得日誌被淹沒;下載別人的圖才記錄,因為那等於把敏感資料帶出系統。第二格談設定遮罩:設定頁面絕對唯讀,金鑰欄位(SECRET_FIELDS)只顯示「已設定」,並用破壞式測試確認畫面真的印不出金鑰字串。第三格談強制撤銷:管理員可以一鍵撤銷使用者的 Session,系統更新資料庫的 revoked_at、寫一筆稽核,並讓前端 Cookie 立刻失效。

最後一張把前面拼成一個整體:Gallery(畫面)、3-Gate Defense(Route/Service/Query 三道關卡)、Audit Watcher(稽核監看)與 Optimized Storage(優化過的資料庫)互相連接。右下的架構師檢查表有四項:畫面與資料的權限絕對隔離、統計數字有極端防呆、效能優化放在資料儲存的時機、每一個越權意圖都被不可逆地記錄。左下的結語是:好的後台系統不只是資料的 CRUD,它用嚴格的口徑傳達真相,用縱深防禦與稽核建立信任,再用底層優化撐住使用體驗。
第 04 課・技術包 p.02–05+資料庫架構圖
第二份講義換個角度:前三課那套海報平台,怎麼讓 AI Agent 也能操作,而且守同一套規則。這一課先看全景:Skill 與網站共用同一個後端、資料存在哪、技術包怎麼分層、Agent 怎麼少讀文件、九個模組長什麼樣子。

講義的重點是「單一事實來源」:Skill 不是另外寫一套實作,而是專案的「第二個呼叫端」,授權、雜湊、冪等、稽核這些規則自動和網站一致,腳本絕對不直接寫 SQL。介面雖然不同,後端是同一個,所以沒有重複的核心邏輯,安全性也只需要顧一份。

這張圖把資料表分成四區:身分與授權區(users、email_verifications、auth_sessions、connector_connections)、AI 記憶區(agent_sessions、agent_events、sandbox_instances)、數位資產區(source_files、prompt_templates、poster_jobs,以及存圖片的 poster_images,標示為 BLOB),還有稽核監控區的 audit_logs。底部的「架構亮點」說,背景 Worker 採用「短交易」機制,等外部 API 產圖的那段時間,絕不鎖死資料庫寫入;白話講,就是資料庫交易只在真的要寫入的瞬間才開,不會一邊等外部服務回應一邊握著交易不放。

技術包由上往下分三層。第一層是 Skill 知識層(.claude/skills/poster-*),放 SKILL.md(入口與路由)、workflows/(怎麼做)、references/(規則)。第二層是腳本層(scripts/*.py),處理命令列參數、護欄(--yes、--live)與輸出的隱私遮蔽。第三層是專案層(poster_app),有 services/、repositories/、models/ 與 app.db(WAL 模式)。設計鐵律是相依方向嚴格向下:腳本層取代了原本 Web Router 的位置,規則只有一份。

Agent 收到「重試失敗的工作」,先比對各 Skill 的 description,載入一份 SKILL.md;再依路由表只讀 workflows/cancel-or-retry-job.md;workflow 提示需要確認狀態機時,才讀 references/job-lifecycle.md;最後呼叫 scripts/jobs.py retry 產出結果。傳統作法是一次載入 69 份文件,Token 成本高,也容易產生幻覺(對照上一張,50 份 workflows 加 19 份 references 剛好是 69)。漸進式的結果是 Agent 每次只負擔 1 份 SKILL、1 份 workflow,必要時再加 1 份 reference。

中心是 01 foundation,由 poster_env.py 當共用啟動器,管環境與資料庫;外圈八個是 02 accounts、03 sources-prompts、04 agent-session、05 sandbox、06 connector、07 image-jobs、08 admin-console、09 release。核心定律是 foundation 必須與其他 skill 放在同一個目錄下,它是系統運作的「絕對地基」,決定腳本要操作哪一個專案範例與資料庫。
| 模組 | 在後面哪幾頁看得到 |
|---|---|
01 foundation | 技術包 p.05(共用啟動器)、p.10 第 1 條泳道(init_db 建庫) |
02 accounts | p.10 第 1 條泳道(register alice 註冊驗證) |
03 sources-prompts | p.06(檔案存取防護網、四層提示詞)、p.10 第 2 條泳道(allow/select/compose) |
04 agent-session | p.07(Agent Session 狀態機) |
05 sandbox | p.07(Sandbox 環境狀態機) |
06 connector | p.08 右側(Connector MCP 護欄) |
07 image-jobs | p.08(Worker 生命週期)、p.10 第 3 條泳道、p.12(images.py verify) |
08 admin-console | p.09(獨立狀態檢視)、p.12(取消/重試、匯出圖片) |
09 release | p.09(Release Preflight)、p.12(backup.py reconcile) |
這張對照表是編者依各頁圖上出現的名稱整理的,講義本身沒有附這張表。
01 foundation 為地基,必須和其他 skill 放在同一個目錄。第 05 課・技術包 p.06–08
進到模組內部:Agent 讀使用者檔案要過哪幾關、提示詞怎麼組、Session 與 Sandbox 各有什麼狀態,以及 Worker 把一張海報做出來的生命週期。

Agent 要讀使用者提供的來源文件,不是想讀哪就讀哪,要過三道關:Gate 1 檢查路徑(resolve_source_path,防越界);Gate 2 檢查文件(read_document,確認存在且是 UTF-8);Gate 3 比對白名單(select_for_user,管理者核可)。三關都過,結果才會變成 source_files,存內容快照與 SHA-256。右邊的提示詞由四層疊成:1. 系統規則、2. 風格範本、3. 工作參數、4. 驗收條件,正中央是來源快照。底下的安全提醒:憑證與密碼只存雜湊(scrypt/SHA-256);提示詞發布(publish --yes)是不可逆行為,產出帶不可逆的版本號與雜湊。

Agent Session 平時在 idle(目前沒在做事)與 running 之間來回,一輪結束會落到 completed、failed 或 cancelled。講義特別「破除迷思」:工具失敗不等於整個回合失敗,idle 只代表現在沒在做事,成敗要看 last_outcome。Sandbox 環境從 pending 走到 connected,之後可能變成 disconnected 或 expired;它的狀態是「觀測值」,不是即時同步,要靠 refresh 向遠端重新觀測。底部的護欄提醒:用假網關(MOCK_OPENAI=true)時,遠端狀態只存在單一行程的記憶體,跨腳本呼叫無法追加或取消。

Worker 做一件工作分四步:1. 認領(Claim,租約約 600 秒,先佔住資源);2. 呼叫圖像網關(Call,這段不持有資料庫交易);3. 驗證圖片(Validate,解碼並檢查尺寸);4. 寫入與回存(Commit,同一個交易裡改狀態並寫入 BLOB)。這和上一課資料庫圖講的「短交易」可以對照著看:等外部產圖的那段不握交易,只在最後一步一次寫完。底部的圍籬機制(Fencing)說:Worker 當機導致租約過期,下一輪由別的 Worker 接手;工作若被取消,已經完成的結果與檔案會被丟棄,確保資料一致。右側的 Connector 護欄,是 ChatGPT 請求進來時先過範圍守衛:documents:read、jobs:write、jobs:read。
last_outcome;Sandbox 狀態是觀測值,要 refresh。第 06 課・技術包 p.09–12
模組各自就位之後,要看它們怎麼接力:管理台怎麼看全局、上線前怎麼檢查與備份、一張海報從註冊到驗收經過哪些 Skill,以及三種下單來源怎麼收斂到同一個入口。

上半是四個獨立的狀態儀表:網站登入 Session、Agent Session、Sandbox 實例、海報工作(Jobs),儀表下方標著數量(圖上如 ACTIVE USERS 52、RUNNING 12/TOTAL 15、HEALTHY 8/TOTAL 8、PROCESSING 4/COMPLETED 200、FAILED 1,是版面上的示例數值)。黃框提醒:稽核日誌 audit_logs 只能新增,無法修改或刪除。下半是上線前流程:先跑 13 項 Preflight 檢查(含 HTTPS、密碼、空間,圖上 PASS 13/13),沒過就阻擋上線;通過後用 SQLite 線上 API 備份,輸出快照加資料夾與 manifest.json,最後立刻驗證備份。維護心法是「腳本比網頁嚴格」:網頁可能忽略錯誤的篩選條件,Admin 腳本遇到錯誤格式一律報錯(結束碼 2)。

圖中的維護者(或 Agent)代表使用者 Alice,依序走過四條泳道。Lane 1(Foundation & Accounts):init_db 建庫、register alice 註冊驗證。Lane 2(Sources & Prompts):allow 管理員白名單、select Alice 選定快照、compose 組合提示詞。Lane 3(Image Jobs):submit 提交佇列、worker_run once 產圖並寫入 BLOB。最後是 Admin & Release:gallery search 全站圖庫查驗、backup create 上線前備份、run_tests 驗收。講義的收束句是「每個 Skill 只負責專屬領域邏輯,組合成高度自動化的無縫生產線」。

下單的來源有三種:網站表單(origin=web)、內建 Agent(內建工具,origin=agent)、ChatGPT(MCP 授權,origin=chatgpt)。三條路最後都收斂到唯一入口 JobService.create_job,在這裡一次做完執行冪等檢查、擁有者檢查與進行中上限檢查,再放進 poster_jobs 佇列,由單一 Worker(python -m app.worker)處理。好處是新增一個 Agent 或 ChatGPT 介面,完全不必重寫核心業務邏輯,維護成本與漏洞風險都跟著降低。

講義用三個情境把責任切開。BLOB 與檔案不一致時:image-jobs 只負責檢查並報錯,release 才負責真正動手修復。取消或重試工作時:image-jobs 帶 --user,以擁有者權限執行、不寫稽核;admin-console 不帶 --user,以管理者權限執行,強制寫入 job.cancel 稽核。匯出圖片時:image-jobs 只讀資料庫匯出自己的圖、不寫稽核;admin-console 匯出他人原圖,強制補寫 image.download。這和第 03 課「看別人的圖不記、下載別人的圖才記」是同一個邏輯。
| 情境 | image-jobs | admin-console/release |
|---|---|---|
| BLOB 與檔案不一致 | images.py verify:只檢查並報錯 | release 的 backup.py reconcile:真正動手修復 |
| 取消/重試工作 | 帶 --user:擁有者權限,不寫稽核 | admin-console 不帶 --user:管理者權限,強制寫入 job.cancel 稽核 |
| 匯出圖片 | 讀 DB 匯出自己的圖,不寫稽核 | admin-console 匯出他人原圖,強制補寫 image.download |
上表是把 p.12 的矩陣照圖重打成文字。
JobService.create_job,檢查只寫一份。第 07 課・技術包 p.13–15
最後一課是整份技術包的安全收尾:指令依危險程度分四級、哪些部分已經自動驗證過、哪些還需要人工驗收,以及給維護者與 AI Agent 共同遵守的五條規範。

指令按危險程度分四級,越往上越要多一道確認。L0 是唯讀查詢(list、show、check),不需要特殊旗標;L1 是可復原的寫入(register、submit),終端機會顯示「模式=寫入」;L2 是破壞性或不可逆的動作(publish、cancel、revoke),必須加 --yes,否則拒絕;L3 是產品線環境,最高警報,必須加 --confirm-production 而且事先備份。金字塔底下的預設基石是 MOCK_OPENAI=true(離線假網關),不花額度,也絕對隱私遮蔽(密碼雜湊、金鑰不印)。
| 級別 | 性質 | 例子 | 要加什麼 |
|---|---|---|---|
| L0 | 唯讀查詢 | list、show、check | 不需特殊旗標 |
| L1 | 可復原寫入 | register、submit | 終端機顯示「模式=寫入」 |
| L2 | 破壞性/不可逆 | publish、cancel、revoke | --yes,否則拒絕 |
| L3 | 產品線環境 | (圖上未舉例) | --confirm-production,且事先備份 |
上表是把 p.13 的金字塔照圖重打成文字。

左邊已經自動驗證的有四項:假網關行為、備份與還原流程、RWD 顯示、系統與腳本互動(Preflight checks)。右邊還需要人工驗收的也有四項:真實的 Agents API(未用真實帳號實測)、真實的 Sandbox/Computer Use(未用真實環境跑過)、真實的 ChatGPT Connector(OAuth 轉址與授權畫面)、真實的高解析度圖像生成(API 耗時、審核錯誤)。講義的系統宣告是:誠實標示邊界,是高品質工程文件的體現;腳本涵蓋業務邏輯,但與真實外部世界的接口仍需要人工驗收。

最後一張是給維護者、也是給 AI Agent 共同遵守的五條:資料庫一律用 open_context 取得,不自己寫 SQL 或自己建 engine;所有時間一律用不帶時區的 UTC,不混用本地時間;輸出絕不印出 BLOB、憑證明文或完整的 .env 金鑰;寫入類指令確認要加 --yes(不可逆)或 --confirm-production;結束碼嚴格遵守 0(成功)、1(檢查未過)、2(用法或前置錯誤)。底部的結語是:這不只是一套海報產出系統,更是與 AI Agent 安全協作的標準工程規範(SOP)。
--yes)、產品線(--confirm-production 加事先備份)。MOCK_OPENAI=true,不花額度、不印密碼雜湊與金鑰。open_context 取資料庫、一律 UTC、不印祕密、寫入要確認旗標、結束碼 0/1/2。