diff --git a/knowledge/experience.md b/knowledge/experience.md index 7e1ab1c..0ebf6ab 100644 --- a/knowledge/experience.md +++ b/knowledge/experience.md @@ -600,3 +600,19 @@ - **解決方式**:build-vs-adopt **不以「功能清單重疊度」判,以「領域第一公民是否同軸」判**。同軸 → adopt(我們 library form 只呼叫 `acompletion`/`aresponses` 就是把 litellm 當邊緣 adapter);不同軸 → 採用它等於要嘛放棄自己的領域模型、要嘛在它外面再套一層(雙核心、必 drift,違原則 5)。且對**已上線、已兌現價值**的系統,門檻再升一級:問題不是「它能不能做基本款」(通常能),而是「把已經服務了原則 6 + 配額池 + 課堂的這些,遷到它的 key/user/team 模型上重做,值不值」——幾乎都不值。 - **教訓**:現成通用工具功能與你高度重疊時,最危險的不是「它不行」,而是**誤把「功能重疊」當成「該 adopt」**。真正該問的是:你的價值核心建在哪個第一公民上?它的呢?**不同軸時,adopt 它=換掉你實作原則的地基,省下的是管線、賠掉的是領域貼合**(本案:Proxy 省掉 realtime WS 管線,卻接不回「歸戶到分配」這個真正的核心工作)。這延伸「build vs adopt 評估要在 specify 之前做」——形態選定後,**遇到該形態的誘人場景(如 realtime 之於 Proxy form)時,回到第一公民判準,別被單一功能拉走**重評整個地基。呼應原則 5(單一管理路徑;雙核心並行必 drift)+ 原則 7(library form =核心穩定、邊緣快變、單一 adapter 隔開;Proxy form 會讓快變的 litellm 變成核心,方向反了)。 - **來源**:realtime 端點 build-vs-adopt 討論(2026-06-12,chat-only);vision 階段 29 計費段「不採 litellm Proxy 的計費系統(不認得我們的『分配』模型)」、架構段「library only,不啟用 Proxy server form」;延伸自上方「build vs adopt 評估要在 specify 之前做」。 + +### realtime 轉錄是「能力軸」不是 litellm mode——別用 mode 推斷,讀 `supported_endpoints` + +- **理論說**:模型走哪個端點看 litellm `mode`(chat/embedding/audio_transcription…),所以「realtime 即時轉錄」也該有個 `mode=realtime`,`model_kind` 照 mode 對映就好。 +- **實際發生**(階段 32):一開始把 `/v1/realtime` 的判定建在 `mode==realtime` 上。但實測 litellm(PR #29775)把 `gpt-realtime-whisper` 標 **`mode=audio_transcription`**(跟 `whisper-1` 同 mode!),realtime 能力是放在 **`supported_endpoints` 含 `/v1/realtime`**;整張 model_cost 唯一 `mode=realtime` 的只有兩個 Gemini live。照 mode 判 → 沒有任何 Azure 轉錄模型會被認成 realtime、端點閘門全擋。且 `whisper-1`(批次-only)與 `gpt-realtime-whisper`(可 realtime)**同 mode、不同端點能力**,光看 mode 根本分不出。 +- **解決方式**:`model_kind` 改**能力優先**——`litellm_sync.raw.supported_endpoints` 含 `/v1/realtime`(或 admin 在能力欄標 `realtime`,`realtime:blocked` 強制關)→ `realtime`,否則才落 mode 對映。與階段 25 `responses_support`(responses 能力是獨立軸、非 mode)**同形狀**。 +- **教訓**:「模型能走哪個端點」常被誤當成 mode 的同義詞,但 mode 是「原生 API 型態」(軸①)、端點能力是另一條軸(軸③,原則 7 守軸正交)。litellm 自己就把 realtime 轉錄能力放在 `supported_endpoints` 而非另開 mode——**採用一個外部分類前,先確認你要的語意它擺在哪個欄位**,別假設「具備 X 能力 ⟺ mode==X」。同 mode 可有不同端點能力(whisper-1 vs gpt-realtime-whisper);凡「同類但能力有別」就是該獨立成軸、別 overload 既有欄位的訊號(呼應「把 responses 從 mode 推導」那個 latent bug 的同根教訓)。 +- **來源**:`src/ai_api/services/model_kind.py` `_is_realtime_capable`;litellm PR BerriAI/litellm#29775(`gpt-realtime-whisper`:`mode=audio_transcription` + `supported_endpoints`);階段 32 + +### 採上游「即時/WS 協定端點」前,用真憑證探測一次——並把 provider 的拒絕 body 帶出來當診斷 + +- **理論說**:照 litellm/官方文件的 URL 慣例組 Azure realtime WS URL(`/openai/realtime?api-version=…&deployment=`),CI 用 mock WS 跑綠就能上線。 +- **實際發生**(階段 32):部署後 admin 按「測試模型」回**裸 `HTTP 400`**。litellm/Azure 文件常見的 `deployment=` 慣例對 realtime **轉錄**不適用——帶 `deployment=` 會被 Azure 路由成**對話型** realtime session,轉錄模型不支援 → `400 OperationNotSupported「realtime operation does not work with the specified model」`;正解是 **`intent=transcription` 且不帶 `deployment=`**(模型由 client 的 `session.update` 指定),且 api-version 要 `2025-04-01-preview`(`2024-10-01-preview` 不行)。這些光看文件 / CI mock 全測不出來。**定位關鍵兩步**:① 在 `open_realtime_ws` 把握手拒絕的 **status + body** 帶出來(原本只看到裸 400,等於沒線索);② 用真憑證在 backend pod 內 `kubectl exec` 直接探測各 URL 組合,Azure 的 error body 直接說明問題(連帶用 data-plane `GET /openai/deployments` 確認 deployment 真的存在)。 +- **解決方式**:`_build_realtime_url` 去 `deployment=`、加 `intent=transcription`、版本 `2025-04-01-preview`(env `AZURE_REALTIME_API_VERSION` 可覆寫);`open_realtime_ws` 捕捉握手拒絕、`raise RuntimeError(status+body)`;smoke 改純連線等首事件(連上即 `transcription_session.created`,不必送 session.update)。**部署後在新 pod 內跑實際 `realtime_smoke` 對真 Azure 回 `{'ok':True}` 才算驗收**。 +- **教訓**:呼應「採用 SDK 前先印一次真實回傳值」「新端點光壞 token→401 不算驗過」——對**即時/串流/WebSocket 協定**再升一級:(a) 文件慣例(尤其跨 OpenAI↔Azure)常有端點別的細節差異(`deployment=` vs `intent=`、api-version 世代),**採用前用真憑證對真端點探測一次**,別只信文件;(b) 把 **provider 的拒絕 status+body 主動帶出**到錯誤訊息/日誌——裸 `HTTP 400` 無從下手,有 body 一次定位(這個診斷本身就是定位本 bug 的關鍵);(c) WS/即時這類 CI 只能 mock 的邊界,真打驗證要排進部署煙霧——admin「測試模型」按鈕正是把它變成隨手可做的動作(它揭露此 bug 就是它的價值證明,同 `/v1/ocr` 那條)。 +- **來源**:`src/ai_api/proxy/upstream.py` `_build_realtime_url` / `open_realtime_ws` / `_realtime_reject_detail` / `realtime_smoke`;階段 32(spec 044,rev 95;測試按鈕真打 + cluster 內探測揭露) diff --git a/knowledge/vision.md b/knowledge/vision.md index 6f9fc19..5de95a0 100644 --- a/knowledge/vision.md +++ b/knowledge/vision.md @@ -48,13 +48,18 @@ `history/completed-phases-detail.md`。本檔〈核心想法〉與〈架構〉段反映平台現行設計; 本段只記「跑在哪、目前未完成的是什麼」。 -階段 1–31 均已上線(最新 rev 93)。憑證模型一路演進:階段 18(每分配多 per-device 憑證)→ 19(一鍵安裝 Codex + device-flow)→ 20(scoped application credentials,credential↔allocation M:N)→ 21(憑證 UI 術語與層級收斂:統一「應用金鑰」、單一管理處、可改名)→ 22(會員介面分頁化:頂部導覽拆 金鑰/分配/用量 + 精簡儀表板 + 一句解釋)→ 23/24(模型目錄 ↔ LiteLLM 對接 + admin 體驗整合)→ 25(responses 雙來源判定,三軸解耦)→ 26(admin 依種類測模型)→ 27/28(應用分頁 → 應用商店化,Codex 第一個應用)。**階段 19 三平台真機(Windows/macOS/Linux)已驗收完成(2026-06-08)**;驗收暴露並修掉三個 Codex 安裝坑(預設模型 pin、選錯模型可操作錯誤、bare-slug alias 讓 /model 看得到成員模型;rev 60→62)。**階段 25 後另有一輪 UI 用語一致性 polish(rev 71→73)**:中英混雜統一成繁中、後端列舉/狀態值一律過 label 函式(`status-label.ts`/`facetLabel`)中文化、facet 加白話 hover 說明、金鑰清單含已撤回開關(對應原則 6 可達性)。**階段 26–28(rev 74→80)**:依種類測模型、應用分頁/商店化、能力 facet 詞彙正規化、推理模型測試 max_tokens 修正、新增**原則 7 演進性**。**階段 29–31(rev 82→93)**:多端點全開(embedding / OCR / 圖片 / rerank / TTS / STT / moderation / search / image_edit)+ 計費一般化(migration 0019、token/page/query/character/image 單位)+ **資料驅動端點 registry**(`proxy/engine.py`+`endpoint_spec.py`+`registry.py`,加端點=加一筆資料);成員批次管理 + ORM 顯式安全刪除(階段 30);「測試模型」從 if/elif 演化成**資料驅動 recipe 表**(`services/model_test.py`,「能不能測 ⟺ 有沒有 recipe」單一真理,rev 90–93),補齊 ocr/stt/image_edit/search 真分支、並修掉實測揭露的**生產 `/v1/ocr` provider 路由 bug**(litellm OCR 不認 `azure/`、需 `azure_ai/`)。**無進行中大主題**——剩餘端點 video / realtime / vector_store 按需評估、多半 descope;image_edit/search 真分支待接非 Azure provider 才能實測。 +階段 1–32 均已上線(最新 rev 95)。憑證模型一路演進:階段 18(每分配多 per-device 憑證)→ 19(一鍵安裝 Codex + device-flow)→ 20(scoped application credentials,credential↔allocation M:N)→ 21(憑證 UI 術語與層級收斂:統一「應用金鑰」、單一管理處、可改名)→ 22(會員介面分頁化:頂部導覽拆 金鑰/分配/用量 + 精簡儀表板 + 一句解釋)→ 23/24(模型目錄 ↔ LiteLLM 對接 + admin 體驗整合)→ 25(responses 雙來源判定,三軸解耦)→ 26(admin 依種類測模型)→ 27/28(應用分頁 → 應用商店化,Codex 第一個應用)。**階段 19 三平台真機(Windows/macOS/Linux)已驗收完成(2026-06-08)**;驗收暴露並修掉三個 Codex 安裝坑(預設模型 pin、選錯模型可操作錯誤、bare-slug alias 讓 /model 看得到成員模型;rev 60→62)。**階段 25 後另有一輪 UI 用語一致性 polish(rev 71→73)**:中英混雜統一成繁中、後端列舉/狀態值一律過 label 函式(`status-label.ts`/`facetLabel`)中文化、facet 加白話 hover 說明、金鑰清單含已撤回開關(對應原則 6 可達性)。**階段 26–28(rev 74→80)**:依種類測模型、應用分頁/商店化、能力 facet 詞彙正規化、推理模型測試 max_tokens 修正、新增**原則 7 演進性**。**階段 29–31(rev 82→93)**:多端點全開(embedding / OCR / 圖片 / rerank / TTS / STT / moderation / search / image_edit)+ 計費一般化(migration 0019、token/page/query/character/image 單位)+ **資料驅動端點 registry**(`proxy/engine.py`+`endpoint_spec.py`+`registry.py`,加端點=加一筆資料);成員批次管理 + ORM 顯式安全刪除(階段 30);「測試模型」從 if/elif 演化成**資料驅動 recipe 表**(`services/model_test.py`,「能不能測 ⟺ 有沒有 recipe」單一真理,rev 90–93),補齊 ocr/stt/image_edit/search 真分支、並修掉實測揭露的**生產 `/v1/ocr` provider 路由 bug**(litellm OCR 不認 `azure/`、需 `azure_ai/`)。**無進行中大主題**——realtime 即時字幕已於**階段 32**上線(rev 95,直連供應商 WS、不經 litellm);剩餘端點 video / vector_store 按需評估、多半 descope;image_edit/search 真分支待接非 Azure provider 才能實測。 ## 架構 - **底層**:自製 FastAPI gateway;上游接入採 `litellm`(library only, 不啟用其 Proxy server form)作為多 provider 抽象層——library form 的 - CVE 集中度遠低於 Proxy form,且涵蓋 100+ provider 不必逐家自寫 adapter + CVE 集中度遠低於 Proxy form,且涵蓋 100+ provider 不必逐家自寫 adapter。 + **刻意例外:`/v1/realtime`(階段 32 即時字幕)直連供應商 WebSocket(直接依賴 + `websockets`),不經 litellm**——litellm 的 realtime 是 Proxy form / client 直連、 + 音訊繞過 gateway,會失去「歸戶到分配」與「即時撤回」(build-vs-adopt 以**領域第一公民 + 是否同軸**判,非功能重疊度;見 experience「功能重疊 ≠ adopt」)。借其 `RealTimeStreaming` + relay 結構自寫薄轉送,計費/分配核心仍守在我們這邊。 - **Provider credential 儲存**:DB(Fernet 加密 at rest),加密金鑰由 K8s Secret 提供;建立時一次性顯示明文(同 allocation token 模式),事後僅 顯示 fingerprint @@ -106,7 +111,7 @@ > 已完成階段只列標題、完成標記與「交付」一句;**細部成功標準 / 核心原則 / > 明確排除已封存於 [`knowledge/history/completed-phases-detail.md`](history/completed-phases-detail.md)**。 -> 階段 1–31 皆已上線(最新 rev 93);階段 19 三平台真機驗收已完成(2026-06-08)。 +> 階段 1–32 皆已上線(最新 rev 95);階段 19 三平台真機驗收已完成(2026-06-08)。 ### 階段 1:分流核心可運作 ✅ - [x] 完成(2026-05-21;本機 + k3s-tew 叢集全 SC 達標)— 自製 gateway 可代理 Azure OpenAI、可發行可撤回的憑證。 @@ -487,17 +492,24 @@ top spenders 等決策圖;對外觀感「像成品」差距明顯。但**圖 - **端點分級(誠實面對「全部」)**: - **同步推論(一請求一回應一筆帳)**:chat/embedding/image/ocr/rerank/tts/stt/**moderation**/**search**/**image_edit** → 三層架構完美涵蓋;moderation 最簡單(純 JSON)。 - **非同步 job**:**video_generation** → 破壞「同步記帳」假設(送出回 job id、要 poll、用量等 job 完成才算);需 job 狀態子系統,**獨立子專案**、不綁進來。 - - **WebSocket**:**realtime** → 雙向串流、與 request/response + 計費架構完全不同;大投資,**先確認真實需求再決定,很可能 descope**。 + - **WebSocket**:**realtime** → ✅ **已於階段 32 上線**(即時字幕轉錄)。雙向串流、與 request/response + 計費架構不同;原評估「大投資、很可能 descope」,實作後確認以「**借 litellm `RealTimeStreaming` relay 結構、但不經 litellm**」可控成本達成(自寫薄 WS relay,計量按秒/分鐘歸戶分配)。 - **資源管理(非推論)**:**vector_store** → 有狀態、跨多次呼叫,不符「per-call 計量歸戶」的 gateway 模型,**可能誠實地不做**。 - **實作切法(別 big-bang)**:① 先**重構**(零新功能)——7 個既有端點收斂成「引擎 + 形態 handler + 7 筆 spec」,**硬約束:既有端點 + 計費全程零回歸**(既有 contract 測試當金鋼罩);② 資料化補同步推論端點(moderation → search → image_edit);③ video(async)獨立評估;④ realtime / vector_store 確認需求後多半 descope。 - **核心約束(目錄誠實延續)**:未開的 mode 由 `model_kind` 判 `unknown` → 成員端不誤開、admin 詳情顯「未知」(誠實機制已在運作,[[reference]] 階段 29③)。 - 對應**原則 7 演進性**(資料勝於程式 + 註冊表 + 軸正交 + 適配層,四手法齊發)+ **原則 6 可達性**(按需把全部適合的端點開給成員)+ **原則 2 可追蹤性**(每端點計量歸戶不變)。 +### 階段 32:即時字幕端點 `/v1/realtime`(realtime transcription WebSocket)✅(rev 95 上線,2026-06-12) +- [x] 已完成(spec 043 + 044;無 migration、+1 直接依賴 `websockets`)—— OpenAI 相容的 realtime transcription **WebSocket** 端點:客戶端串流音訊、即時收文字,用量**按秒/分鐘**計、歸戶分配、連線中可即時撤回。**自寫薄雙向 relay**(`proxy/realtime.py`,借 litellm `RealTimeStreaming` 結構但**不經 litellm**——它的 realtime 是 Proxy form、音訊繞過 gateway,會失去分配歸戶 + 即時撤回,見 experience「功能重疊 ≠ adopt」),含旁路週期協程 re-check 撤回。**計量**自算 client `input_audio_buffer.append` 的 PCM bytes → 時長,**任何斷線路徑(正常/異常/撤回)都落一筆帳**(不漏記);單位依 PriceList(litellm 按 `input_cost_per_second` 計 → 預設秒、亦支援分鐘),沿用 0019 的 `call_records.{quantity,unit}`,**零 migration**。 + - **realtime 是能力軸、非 litellm mode**:litellm(PR #29775)把 `gpt-realtime-whisper` 標 `mode=audio_transcription` + `supported_endpoints` 含 `/v1/realtime`;`model_kind` 據此(或 admin `realtime` 能力標記)判 realtime——與階段 25 `responses_support` 同形狀(原則 7 守軸正交)。可從 admin「測試模型」跑 **WS 煙霧**(連線即收 `transcription_session.created`)當部署後協定真打;nginx `/v1/realtime` 加 WS upgrade。 + - **Azure URL 之坑(實作真打才現)**:必須 `intent=transcription` 且**不帶 `deployment=`**(帶了會被路由成對話型 realtime、轉錄模型不支援 → 400 OperationNotSupported),api-version `2025-04-01-preview`;用真憑證在 cluster 內探測 + 把 provider 拒絕 body 帶出才定位(rev 95)。詳見 experience「realtime 能力∈supported_endpoints」「採上游 WS 協定前用真憑證探測」。 + - **測試**:744 後端 + 164 前端測試綠;契約測試以 **mock provider WS** 在 CI 跑全 relay/計量/撤回路徑(engine 綁 pytest event loop、TestClient portal 會撞 asyncpg/aiosqlite,故直接呼叫 `handle_realtime` 注入 fake WS);真連 Azure 以部署後「測試模型」按鈕驗。 + - 對應**原則 1/2**(歸戶分配、可追蹤)+ **原則 3**(連線中即時撤回,旁路協程 re-check 非只建立時檢查)+ **原則 6**(願景「主流工具開箱即用」延伸到即時字幕)+ **原則 7**(借結構不借帳本、能力軸正交、薄 adapter 把直連 WS 鎖在邊緣)。細節見 `specs/043-realtime-transcription/`。 + > **不做:每日上限(Daily Cap)** — 曾於 2026-06-03 列為候選(源於外部回饋的延伸推導, > 非核心需求),後評估認為現有「月配額 + 異常偵測器自動隔離 + 暫停機制」已足夠覆蓋「單一使用者 > 吃掉共享配額」的風險,每日粒度的硬上限非必要,故撤下不做。階段 13 通知系統當時預埋的 > `allocation_daily_cap_exceeded` event type 與 email 範本也一併移除(2026-06-03),未來若真需要再重建。 -> **狀態**:階段 1–31 皆已上線(最新 rev 93;逐階段 rev / 細節見〈現狀〉與 `history/completed-phases-detail.md`)。**多端點主題收尾**——含階段 31 資料驅動 registry + rev 90–93「測試模型」recipe 表單一真理化(補齊 ocr/stt/image_edit/search 真分支、修生產 `/v1/ocr` azure_ai 路由)。 -> 規劃中:(無進行中大主題;剩餘端點 video/realtime/vector_store 按需評估、多半 descope;image_edit/search 真分支待接非 Azure provider 才能實測。下一步見 `/knowie-next`)。 +> **狀態**:階段 1–32 皆已上線(最新 rev 95;逐階段 rev / 細節見〈現狀〉與 `history/completed-phases-detail.md`)。**多端點主題收尾**——含階段 31 資料驅動 registry + rev 90–93「測試模型」recipe 表單一真理化(補齊 ocr/stt/image_edit/search 真分支、修生產 `/v1/ocr` azure_ai 路由)+ **階段 32 realtime 即時字幕**(直連供應商 WS、不經 litellm;rev 95)。 +> 規劃中:(無進行中大主題;剩餘端點 video/vector_store 按需評估、多半 descope;image_edit/search 真分支待接非 Azure provider 才能實測。下一步見 `/knowie-next`)。 > 已 descope:3b.7 Playwright E2E、每日上限(見上方各自的「不做」說明)。