接力隊第五棒、最後一棒 curator_agent(策展人)。它拿到的是 Day 13 由 Pack 用幾何算好的「熱點候選」,要做的是挑、排、寫,不是決定位置。
| 比喻 | 術語 | 白話 |
|---|---|---|
| 策展人 | curator_agent(LlmAgent) |
接力隊最後一棒 |
| 候選名單 | build_scene 回的 hotspot_candidates(hs_01…hs_08) |
場景包依體積挑出的熱點,已附鏡頭與錨點 |
| 挑、排、寫 | write_hotspots |
從候選裡選、排序、寫 title/body/英文版 |
| 現場問答 | write_faq |
寫 12–20 題 {q, a, lang} |
| 候選名單從哪來 | exhibit Pack 的 suggest_hotspots(公開純函式) |
純資料運算,不需要 AI |
tools=[catalog.list_parts, catalog.build_scene, catalog.write_hotspots, catalog.write_faq]
list_parts 看零件樹。build_scene:Pack 給一批熱點候選(相機與錨點由幾何決定,不由你決定)。write_hotspots:從候選裡挑 3–8 個,依參觀動線排序,每個寫 title/body(繁體中文,兩到三句,講功能與看點,不寫規格表)與 title_en/body_en;只能用候選給的 id。write_faq:寫 12–20 題(lang 用 zh-TW),只根據零件樹與 QA 數字回答得出來的問題;不知道的規格不要編。| 誰做 | 需要 AI? | |
|---|---|---|
挑哪些零件當熱點候選(依體積由大到小取前 8)、錨點、鏡頭角度、導覽 tour、互動設定 |
exhibit Pack 的 build_scene → suggest_hotspots(Day 13) |
不需要 |
| 從候選裡選 3–8 個、排序、寫標題/介紹/英文、寫 FAQ | curator_agent |
需要 |
build_scene 工具寫 scenes/main.scene.json 並跑契約驗證,回 hotspot_candidates: [{id, title, part_id}]。exhibit.set_camera 這個 Pack 工具只在 manifest 宣告,尚未實作(X1 HANDOFF:待 W5)。
write_hotspots 做的事scenes/main.scene.json;任何 id 不在候選裡 → 整批拒絕(unknown_hotspot,「只能用 build_scene 給的候選 id」)。title/body,order 依你給的順序重編(1、2、3…)。scene["hotspots"] 換成被選中的那幾個(沒被選的候選就此消失)。scene["tour"] 重新產生:{"enabled": true, "steps": [選中的 id 依序], "dwell_s": 8, "loop": true}。extras/exhibit/hotspots.json:{asset_id, count, hotspots: [{id, title, body, title_en, body_en, anchor}]}。這份 extras/exhibit/hotspots.json 不是 Day 14 離線包裡那份 hotspots.json:離線包是 exhibit-kiosk build 時從 scene.json 抽出 title/body/media 另外生成的。英文欄位只存在前者。
write_faq 做的事doc = {"asset_id": asset_id, "generated": bool(faq), "count": len(faq), "items": list(faq)}
if not faq:
doc["reason"] = "no_model_credentials"
寫到 extras/exhibit/faq.json;空清單就明確標「沒有生成,原因是沒有模型憑證」,不是留空白檔。
def plan_curator(state):
if not state.get("scene"): return [("build_scene", ...)]
if "hotspots" not in outputs: return [("write_hotspots", {"hotspots": []})] # 空 list = 沿用候選,不寫文案
if "faq" not in outputs: return [("write_faq", {"faq": []})]
空 list 的 write_hotspots:沿用 scene 目前的熱點,不加文案;tour 照樣重新產生。
ast_day22,exhibit.kiosk)scene.tour = {"enabled": true, "steps": ["hs_01".."hs_08"], "dwell_s": 8, "loop": true}
hotspots(依序)= Housing_Body、Base_Plate、Motor、Output_Gear、Housing_Cover、ROT_Output_Shaft、Motor_Flange、ROT_Input_Shaft
(title 即零件英文原名;每個都沒有 body)
extras/exhibit/hotspots.json = {count: 8, hotspots[0]: {id: hs_01, title: Housing_Body, body: null, anchor: {part: p_0004}}}
extras/exhibit/faq.json = {generated: false, count: 0, items: [], reason: "no_model_credentials"}
這 8 個就是 Day 14 展場離線包的 8 個熱點與導覽路線。
用我標註「(測試)」的假文字呼叫 write_hotspots(不是 AI 輸出):
| 輸入 | 結果 |
|---|---|
只挑 2 個(hs_03、hs_01) |
收下;scene.hotspots 只剩這 2 個、order 為 1、2;tour.steps = [hs_03, hs_01];英文欄位寫進 extras |
同一個 id(hs_01)寫兩次 |
收下;contract_errors: [];scene.hotspots 出現兩筆 hs_01、tour.steps = [hs_01, hs_01] |
| 先縮成 2 個之後再呼叫空 list | 維持 2 個(「沿用」的是 scene 現有的熱點,不是原本 8 個候選) |
「3–8 個」只在 docstring 與 instruction 裡,程式沒有檢查數量,也沒有檢查重複 id;scene 契約沒有 id 唯一性約束,所以驗證不會擋。這兩點已記進交接稿。
title_en/body_en 目前沒有任何程式讀取(viewer、kiosk 都沒有語言切換)。faq.json 目前沒有任何消費者:docent(W5)與 kiosk 的斷網降級 FAQ 都還沒做。curator_hotspots_and_faq 的期望軌跡是離線的那幾步(build_scene → write_hotspots → write_faq),中文回答比對要真金鑰(Day 25)。