Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions knowledge/experience.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<dep>`),CI 用 mock WS 跑綠就能上線。
- **實際發生**(階段 32):部署後 admin 按「測試模型」回**裸 `HTTP 400`**。litellm/Azure 文件常見的 `deployment=<dep>` 慣例對 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 內探測揭露)
24 changes: 18 additions & 6 deletions knowledge/vision.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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、可發行可撤回的憑證。
Expand Down Expand Up @@ -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、每日上限(見上方各自的「不做」說明)。