Move PRD, design specs, architecture docs, and TDD from .autoflow/ (personal/per-branch layer) to docs/autoflow/ (shared layer that goes into git) per the new Autoflow workspace layout. Files moved: - 02-prd/PRD.md - 03-design/design-review.md - 03-design/user-flow-cross-system.md - 04-architecture/TDD.md - 04-architecture/design-doc.md - 04-architecture/security.md The originals were never tracked, so git mv reduced to a filesystem rename with no history to preserve. .autoflow/ remains for personal notes (progress.md, review reports, testing logs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
461 lines
28 KiB
Markdown
461 lines
28 KiB
Markdown
# Design Review — API 對外開放 L 級功能
|
||
|
||
> **審查範圍**:2026-04-25 的 L 級新功能「開放 Kneron Model Converter 轉檔能力為對外 REST API 供 VisionA 使用」。
|
||
>
|
||
> **審查者**:Design Agent(Autoflow)
|
||
>
|
||
> **審查時機**:三方聯合討論階段(與 PM Agent / Architect Agent 同步作業)
|
||
>
|
||
> **特別聲明**:這次任務的直接產出物是 **後端 API 介面**,不是新 UI 畫面。本次 Review 的焦點為:
|
||
> 1. 對既有 Web UI 使用者體驗的間接影響
|
||
> 2. API 設計對下游消費者(visionA-backend)能做出什麼樣的終端 UX 的限制
|
||
> 3. 從 UX 觀點給 PM / Architect 的建議
|
||
>
|
||
> **不在本次範圍**:新 UI 畫面設計、Wireframe、Prototype、Design Tokens 調整。這些本次都不需要做。
|
||
|
||
## 變更歷程
|
||
|
||
| 日期 | 變更內容 |
|
||
|------|---------|
|
||
| 2026-04-25 | 原始模型上傳路徑改為 visionA-backend → Converter multipart 直連(不經過 File Access Agent)。同步更新:(1)錯誤情境表移除「原始模型在 FAA 找不到 `input_object_not_found`」,改為 multipart 相關錯誤 `invalid_multipart` / `file_too_large`;(2)相依圖中 Converter 對 FAA 的依賴只保留 promote PUT,移除讀原始模型的箭頭;(3)衝突 response 範例保持不變(不受上傳路徑影響)。|
|
||
|
||
---
|
||
|
||
## 1. 摘要
|
||
|
||
### 1.1 整體評估結論
|
||
|
||
**Phase 1 對既有 Web UI 使用者體驗的影響:幾乎無感(✅ 優秀)**
|
||
|
||
- 既有 Web UI 走的 API 路徑(`/jobs`、`/jobs/:id`、`/jobs/:id/events`、`/jobs/:id/download/:filename`)**不改動**
|
||
- 對外 API 走全新路徑 `/api/v1/*`,兩套並存
|
||
- 既有 Web UI 使用者繼續以 multipart 上傳、SSE 追蹤、GET 下載,**流程零改動**
|
||
|
||
**主要 UX 風險:集中在「我們給 visionA 前端團隊的操作彈性」上(⚠️ 要注意)**
|
||
|
||
我們不實作 VisionA 的 UI,但 **我們的 API 設計會限制他們能做出什麼樣的體驗**。如果 API 設計得太死板,visionA 前端團隊就算想做更好的 UX 也做不到。本次 Review 會針對這點給 Architect 一份「API 設計的 UX 彈性需求」清單。
|
||
|
||
### 1.2 Phase 1 vs Phase 2 對使用者體驗的差異
|
||
|
||
| 情境 | Phase 1 體驗 | Phase 2 體驗 | UX 差距 |
|
||
|------|------------|------------|---------|
|
||
| VisionA 使用者上傳模型並轉檔 | ✅ 在 VisionA 平台內完成 | ✅ 同 Phase 1 | 無差 |
|
||
| VisionA 使用者把轉檔結果加進模型庫 | ✅ 按一下按鈕即完成 | ✅ 同 Phase 1 | 無差 |
|
||
| VisionA 使用者下載已轉檔的模型 | ⚠️ **阻塞 / 折衷方案** — VisionA 前端目前能做的:(a) 顯示「下載功能即將上線」占位;(b) 從 VisionA backend proxy 下載(違反原架構);(c) 導向舊的 Kneron Converter Web UI 下載(會混亂) | ✅ 瀏覽器直連 File Access Agent,速度快、省流量 | **中等** — 這是 UX 上的明顯缺口,必須在 Phase 1 上線時和 VisionA 產品團隊協調 messaging |
|
||
|
||
**Design Agent 的強烈建議**:Phase 1 上線時必須有「使用者下載 messaging 策略」,不能讓使用者上傳完轉檔後發現東西下載不了(UX 上的死胡同)。**這是需要使用者決策的議題 #1,詳見第 6 節。**
|
||
|
||
---
|
||
|
||
## 2. 既有 Web UI 影響分析
|
||
|
||
### 2.1 既有 Web UI 使用者旅程是否改變?
|
||
|
||
**結論:Phase 1 完全不改,Web UI 使用者無感。**
|
||
|
||
| 旅程階段 | 既有行為(會繼續保留)| 是否受影響 |
|
||
|---------|----------------------|----------|
|
||
| 進入 Web UI 主頁 | 直接打開網頁,不需登入([推測] 現況)| ❌ 不受影響 |
|
||
| 上傳模型(multipart)| 瀏覽器表單 → `POST /jobs` with FormData | ❌ 不受影響 |
|
||
| 追蹤進度(SSE)| 瀏覽器開 SSE 連線 `GET /jobs/:id/events`,自動 fallback 到每 3 秒 polling | ❌ 不受影響 |
|
||
| 下載結果 | 直接點 `GET /jobs/:id/download/:filename` | ❌ 不受影響 |
|
||
| 任務列表 | `GET /jobs` 列出全部 job | ❌ 不受影響(但查不到 VisionA 使用者透過新 API 建立的 job — **見 2.3**)|
|
||
|
||
**核心設計選擇(已由 PM 決策)**:
|
||
- 既有 `/jobs` 路徑保留,**不**加 OAuth 驗證
|
||
- 對外 API 走新路徑 `/api/v1/*`,**獨立**加 OAuth middleware
|
||
- 兩套 API 並存,互不影響
|
||
|
||
✅ **Design Agent 同意此決策**,理由:
|
||
1. 避免破壞現有使用者(零 regression 風險)
|
||
2. 讓 Web UI 和對外 API 的演進節奏脫鉤(Web UI 是內部工具,對外 API 需要穩定性承諾)
|
||
3. 未來若要合併,可在 Phase 3 評估,不用現在壓時程決定
|
||
|
||
### 2.2 既有 Web UI 的 API 呼叫路徑是否受影響?
|
||
|
||
**結論:不受影響。**
|
||
|
||
既有 Web UI 呼叫的所有端點(在 `apps/task-scheduler/server.js` 中驗證過):
|
||
|
||
```
|
||
POST /jobs (multipart 上傳)
|
||
GET /jobs (列表)
|
||
GET /jobs/:jobId (查詢單一 job)
|
||
GET /jobs/:jobId/events (SSE)
|
||
GET /jobs/:jobId/download/:filename (下載)
|
||
GET /health
|
||
```
|
||
|
||
這些端點 Phase 1 **保留原樣**。對外 API 是新增,不是取代。
|
||
|
||
### 2.3 既有 Web UI 要不要也改走新 OAuth 流程?
|
||
|
||
**Design Agent 建議:Phase 1 不改,維持現況。** 但有一個 UX 面的警示要給使用者知道。
|
||
|
||
#### 建議不改的理由(UX 觀點)
|
||
|
||
1. **內部工具的 UX 定位**:既有 Web UI 的 persona 是 **Kneron 內部 AI 工程師**,在可信網段內使用,不需要 OAuth 這層摩擦
|
||
2. **加上 OAuth 等於要加登入流程**:會多出「登入頁 → 選 tenant → 授權」等步驟,對內部工具體驗是倒退
|
||
3. **風險可控**:Web UI 目前應該是在內網 / VPN 後面運行,不直接暴露公網(這需要由 Architect 與 DevOps 確認,見下方警示)
|
||
|
||
#### ⚠️ 警示 — 需要使用者與 Architect 釐清
|
||
|
||
**如果 Web UI 和對外 API 部署在同一個 Task Scheduler instance 上**(例如同一個 Express app 綁兩套 route),那麼:
|
||
|
||
- 對外 API 的 public endpoint(`/api/v1/*`)會和 Web UI 用的 `/jobs` **共用同一個 TCP port / Nginx vhost**
|
||
- 若未來公開 `/api/v1/*` 到公網,`/jobs` 路徑也會曝光
|
||
- 有心人可以直接打 `POST /jobs`(不需 OAuth)繞過對外 API 的 rate limit / scope 檢查
|
||
|
||
**緩解方案(建議交給 Architect 評估)**:
|
||
- 方案 A:**部署層級隔離** — Web UI 的 `/jobs` 只綁 internal network interface,`/api/v1/*` 綁 public interface
|
||
- 方案 B:**Nginx 路由控制** — 同一個 Task Scheduler,但 public Nginx 只 proxy `/api/v1/*`,internal Nginx 才 proxy `/jobs`
|
||
- 方案 C:**在 `/jobs` 加基本 IP allowlist**(內網 CIDR)
|
||
|
||
### 2.4 既有 SSE (`/jobs/:id/events`) 機制在新架構下是否保留?
|
||
|
||
**結論:保留,但只給 Web UI 用。對外 API 走 polling,不對外開放 SSE。**
|
||
|
||
#### Design Agent 的觀察
|
||
|
||
1. **Web UI 繼續用 SSE**:既有 Web UI 的即時性體驗(看到 stage 切換、進度百分比即時跳動)是 UX 亮點,不能砍
|
||
2. **對外 API 不做 SSE**(PM 已決策):polling 已足夠,理由充分:
|
||
- 下游消費者是另一個 backend 服務(visionA-backend),polling 對它們而言是標準做法
|
||
- 不做 Webhook / SSE 能簡化 Phase 1 的 surface area
|
||
- visionA-backend 自己再決定要不要把進度以 SSE / WebSocket 推給 VisionA 前端
|
||
3. **但要確保 SSE endpoint 不意外被對外 API 消費者呼叫**(沒意義且資源浪費):
|
||
- `/jobs/:id/events` 保留在非 OAuth 路徑下,visionA-backend 沒有理由呼叫它
|
||
- 若要防禦,可以在 Nginx 層封堵 public access 到 `/jobs/*/events`
|
||
|
||
#### UX 風險監測
|
||
|
||
visionA-backend 的 polling 策略會直接影響 VisionA 使用者看到的進度「順暢度」。建議 API 規格中明確給出指引(見第 4 節「給 Architect 的建議」):
|
||
- 建議的 polling 間隔(2-5 秒)
|
||
- 鼓勵 `stage_changed` 時立即 poll(讓 VisionA 能快速反應階段切換)
|
||
- 回應中的 `etag` / `updated_at` 讓消費者知道何時真的有變
|
||
|
||
---
|
||
|
||
## 3. 新呼叫方(visionA-backend)間接影響分析
|
||
|
||
### 3.1 間接 UX 影響總覽
|
||
|
||
雖然我們不設計 VisionA 的 UI,但以下 API 設計決策會 **直接限制** visionA 前端能做出的體驗:
|
||
|
||
| Converter API 設計點 | 對 VisionA 終端使用者 UX 的影響 | Design Agent 建議 |
|
||
|--------------------|----------------------------|------------------|
|
||
| 同使用者同時一個轉檔限制(US-11)| 使用者想同時轉兩個不同模型會被擋 | 回 409 時附清晰的 `active_job_id` + 人類可讀的訊息,讓 VisionA 能做「你有一個轉檔進行中,要切過去看嗎?」的 UX |
|
||
| Polling 模式(不做 Webhook)| 進度更新延遲 = polling 間隔 | API 文件明示建議 polling 間隔;回應要快(p95 < 200ms)避免 VisionA 排隊等待 |
|
||
| `POST /api/v1/jobs` 採 multipart/form-data(原始模型直接上傳)| 大檔上傳期間使用者需要「可取消 / 可重試」的體感 | API 文件建議 visionA-backend 在上傳到 Converter 時使用支援 progress event 的 HTTP client(原生 `net/http` + `io.TeeReader` 或等效),並在 visionA 前端以 progress bar 呈現上傳百分比;建議 Architect 在 TDD 中明確:收檔失敗 / 網路中斷時回 4xx/5xx 足以讓 visionA-backend 判斷是否重試(不要只 reset connection)|
|
||
| 原始模型檔案大小上限 500MB | 超過上限的使用者會被擋在建 job 前 | API 在 413 `file_too_large` 回應中附 `details.limit_bytes` / `details.actual_bytes`,讓 visionA 前端能顯示具體原因;VisionA 前端可在上傳前做 client-side 大小檢查提前攔截 |
|
||
| promote 需要另一個 API 呼叫 | 使用者要按兩次(轉完 + 加進模型庫)| **建議 visionA 前端把兩步驟做成一次操作**(使用者按一次「轉檔並加進模型庫」,VisionA backend 內部自動 chain),但這是 VisionA 側的決定 |
|
||
| Converter Bucket 7 天 lifecycle | 使用者轉完沒 promote,7 天後檔案消失 | API 回應中要暴露 `expires_at`,讓 VisionA 能在 UI 顯示「檔案將於 X 天後自動清除」|
|
||
| Recovery API 是 list 模式 | 使用者離開後回來 → VisionA 前端要決定是否自動跳轉到 job 頁 | API 要回足夠多資訊(job_id、stage、progress、created_at),讓 VisionA 前端做智慧決策 |
|
||
| Phase 1 沒有 delegated download | 使用者在 VisionA 模型庫看到模型,但不能下載 | **嚴重 UX 缺口,見第 6 節議題 #1** |
|
||
|
||
### 3.2 錯誤情境的 UX 投射
|
||
|
||
visionA 前端能把 Converter 回的錯誤轉成什麼樣的訊息,完全取決於我們回了什麼。以下是 Design Agent 建議 API 要清楚區分的錯誤類型:
|
||
|
||
| 情境 | HTTP 狀態 | 錯誤碼建議 | VisionA 能做出的 UX |
|
||
|------|---------|----------|------------------|
|
||
| token 無效 / 過期 | 401 | `unauthorized` | 背景 refresh token 重試 |
|
||
| token 有效但 scope 不足 | 403 | `insufficient_scope` + `required_scope` | 提示 admin 去 Member Center 補授權 |
|
||
| 已有 in-progress job | 409 | `user_has_active_job` + `active_job_id` | 「你有一個轉檔進行中,要切過去看嗎?」 |
|
||
| job 狀態不對(promote 時 job 還沒 completed)| 409 | `job_not_ready_for_promote` + `current_status` | 「轉檔還沒完成,請等進度條到 100% 再加進模型庫」 |
|
||
| multipart body 格式錯 / `model` 檔案欄位缺失 / 必填 field 缺失(例如 `user_id`、`model_id`)| 400 | `invalid_multipart` + `details.missing_field` 或 `details.reason` | 「上傳失敗,請確認檔案格式與欄位」+ 具體指出缺哪個欄位 |
|
||
| 原始模型超過 500MB 上限 | 413 | `file_too_large` + `details.limit_bytes` + `details.actual_bytes` | 「檔案過大(限制 500MB),請確認上傳的模型大小」|
|
||
| 轉檔失敗(模型本身的問題)| — | `job.error.reason` 要人類可讀 | 「在 BIE 量化階段失敗:[具體原因],建議檢查參考圖片是否足夠」 |
|
||
| File Access Agent 不可用(promote 時)| 502 | `file_gateway_unavailable` | 「模型庫服務暫時不可用,我們會自動重試」+ 顯示 retry 按鈕 |
|
||
| Member Center JWKS 取用失敗 | 503 | `auth_service_unavailable` | 系統層錯誤,顯示 maintenance banner |
|
||
|
||
> **備註**:Phase 1 的原始模型改由 visionA-backend 以 multipart 直接上傳到 Converter,Converter 不再從 File Access Agent 拉原始模型,因此原本的 `input_object_not_found`(422)錯誤碼在 Phase 1 **不會出現**。取而代之的是上傳階段的 `invalid_multipart`(400)與 `file_too_large`(413)。File Access Agent 相關錯誤在 Phase 1 只會發生在 promote 階段。
|
||
|
||
### 3.3 API 設計上要給 visionA 留下哪些彈性?
|
||
|
||
為了讓 visionA 前端能做出更好的 UX,API 要預留以下能力(即使 Phase 1 可以先不實作,但 schema 要設計得可擴展):
|
||
|
||
#### 必要(Phase 1)
|
||
1. **狀態細節**:不只 `status`,要有 `stage` + `progress`(0-100)
|
||
2. **錯誤碼結構化**:`error.code` + `error.message` + `error.details`(可擴展)
|
||
3. **時間戳完整**:`created_at`、`updated_at`、各階段的 `stage_started_at` / `stage_completed_at`(用於 VisionA 做階段耗時顯示)
|
||
4. **保留欄位**:`metadata: {}`(讓未來能加欄位不破壞 API 契約)
|
||
5. **job_id 可識別**:使用者若要截圖 / 反映問題,能清楚引用 job_id
|
||
|
||
#### 建議(未必 Phase 1,但設計時要預留)
|
||
6. **ETA 欄位**:`estimated_completion_at`(Phase 1 可回 null,未來再實作)
|
||
7. **取消能力**:即使 Phase 1 不實作,API 路徑 `DELETE /api/v1/jobs/:id` 要預留(避免 Phase 2 需要時得改契約)
|
||
8. **Webhook 註冊**:同上,即使 Phase 1 不實作,資料模型要考量未來可擴展
|
||
9. **progress 的顆粒度**:每個 stage 內部的 progress(例如「BIE 階段 45%」而不只是「整體 33%」)
|
||
|
||
---
|
||
|
||
## 4. 建議與風險
|
||
|
||
### 4.1 給 Architect 的 API 設計建議(UX 觀點)
|
||
|
||
以下建議從「下游消費者能做出什麼樣的 UX」角度出發,請 Architect 在 TDD 中採納或說明取捨。
|
||
|
||
#### 4.1.1 Response Schema 建議
|
||
|
||
**`GET /api/v1/jobs/:id` 回應範例(建議)**:
|
||
|
||
```json
|
||
{
|
||
"job_id": "uuid-v4",
|
||
"user_id": "visionA-user-id",
|
||
"status": "running", // created / running / completed / failed
|
||
"stage": "bie", // onnx / bie / nef / null
|
||
"progress": 45, // 0-100(整體)
|
||
"stage_progress": 60, // 0-100(當前階段內)—— 建議有
|
||
"created_at": "2026-04-25T12:00:00Z",
|
||
"updated_at": "2026-04-25T12:05:30Z",
|
||
"stage_timings": { // 建議有,讓 VisionA 能顯示階段耗時
|
||
"onnx": {"started_at": "...", "completed_at": "..."},
|
||
"bie": {"started_at": "...", "completed_at": null},
|
||
"nef": null
|
||
},
|
||
"estimated_completion_at": null, // Phase 1 可為 null
|
||
"result_object_keys": null, // completed 時才有
|
||
"expires_at": "2026-05-02T12:00:00Z", // Converter Bucket 7 天後過期
|
||
"error": null, // 失敗時結構化錯誤
|
||
"metadata": {}
|
||
}
|
||
```
|
||
|
||
**`POST /api/v1/jobs` 衝突回應(建議)**:
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "user_has_active_job",
|
||
"message": "使用者目前已有進行中的轉檔任務",
|
||
"details": {
|
||
"active_job_id": "uuid-v4",
|
||
"active_job_status": "running",
|
||
"active_job_stage": "bie",
|
||
"active_job_progress": 45
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
這讓 visionA 前端能直接顯示「你有一個轉檔進行中(BIE 階段 45%),要切過去看嗎?」而不用再多打一次 API 查詳情。
|
||
|
||
#### 4.1.2 Polling 效能考量
|
||
|
||
- **p95 < 200ms**(已在 PRD §9.2.1):visionA-backend 可能每 2-5 秒 polling 一次,如果 API 慢 visionA 的 UI 會跟著卡
|
||
- **建議加 `ETag` / `If-None-Match` 支援**:visionA-backend 可以在 304 時跳過資料傳輸,省流量
|
||
- **避免無必要的 DB 查詢**:`GET /api/v1/jobs/:id` 應該只讀 Redis,不做任何外部 HTTP 呼叫(否則 polling × N 個使用者會變擴增 load)
|
||
|
||
#### 4.1.3 promote API 的 UX 考量
|
||
|
||
**建議 `POST /api/v1/jobs/:id/promote` 是同步呼叫並回等候 result**:
|
||
- 好處:visionA 前端可以直接顯示 loading → 成功 / 失敗
|
||
- 風險:PUT 到 File Access Agent 若慢(大檔),API 會 block 幾秒(p95 < 3s 已在 SLA)
|
||
- 備案:若 PUT 超過某個 timeout(例如 10s),API 回 202 + 新的 `promote_job_id`,visionA polling 查 promote 進度
|
||
- Architect 決策:Phase 1 建議先做同步版本(簡單),失敗率觀察後再決定是否需要 async 模式
|
||
|
||
#### 4.1.4 其他
|
||
|
||
- **版本化**:`/api/v1/` 是對的,建議 OpenAPI spec 明確標注「breaking change 會走 `/api/v2/`」
|
||
- **error response 統一格式**:所有 4xx/5xx 都用同一個 `{error: {code, message, details}}` 結構,避免 visionA 要寫多套 parser
|
||
- **job_id 格式固定**:建議用 UUID v4,不要用 snowflake 之類的自訂 ID(visionA 端的 log 比較好看)
|
||
|
||
### 4.2 給 PM 的需求補強建議
|
||
|
||
PM 的 PRD 已經相當完整,以下是 Design Agent 從 UX 觀點發現可以補強的地方:
|
||
|
||
#### 4.2.1 建議新增:使用者下載 messaging 策略(§15.1 或 §12.2)
|
||
|
||
Phase 1 上線時 VisionA 使用者會遇到「我的模型在模型庫裡但下不下來」的情境。建議在 PRD 中增加一段:
|
||
|
||
> **Phase 1 使用者下載缺口的 UX 處理方案(待確認)**:
|
||
> Phase 1 上線時,delegated download 尚未可用。VisionA 需在其 UI 中明確告知使用者「下載功能於 Phase 2 上線」,或採 fallback proxy 方案。此取捨由 VisionA 產品團隊主導,Kneron Converter 不需額外做事,但雙方需在 Phase 1 上線 kickoff 前對齊 messaging。
|
||
|
||
#### 4.2.2 建議新增:VisionA 前端 UX 對我們 API 的期望(§4.4 或新增 §4.5)
|
||
|
||
目前 PRD §4.4 的 User Story 是從「visionA-backend」視角寫的。建議補一組 **從 VisionA 終端使用者視角** 的 UX acceptance criteria,例如:
|
||
|
||
- 「轉檔成功後,使用者能在 5 秒內看到模型出現在自己的模型庫」
|
||
- 「同使用者同時轉檔限制觸發時,使用者能在 2 秒內看到『你有一個轉檔進行中』的提示,並能一鍵跳過去」
|
||
- 「轉檔失敗時,使用者能看到具體原因(不只是 generic error)」
|
||
|
||
這些是 UX 標準,不是技術標準,寫進 PRD 可以讓 Architect 在設計 API 時有依據。
|
||
|
||
#### 4.2.3 建議補強:`[推測]` 標記的清理策略
|
||
|
||
PRD 中保留了既有的 `[推測]` 標記,但 §1.2 新增的 Persona C 和 §4.3~4.4 的 User Stories 沒有 `[推測]` 標記(因為是基於本次討論確認的)。建議使用者審閱時:
|
||
- 新增章節(§1.2 Persona C、§4.3~4.4、§14、§15):請使用者確認後正式定案
|
||
- 舊章節的 `[推測]` 標記:獨立一個工作項,逐條請使用者確認或刪除
|
||
|
||
這不是本次 L 級範圍,但建議 PM 在下一版 PRD 中處理。
|
||
|
||
### 4.3 給 Architect 的一般性 UX 風險提醒
|
||
|
||
#### 4.3.1 Polling 間隔太短可能讓 VisionA UI 卡頓?
|
||
|
||
**風險等級:低**
|
||
- visionA-backend 是 polling 方(不是 visionA 前端直接 polling Converter),所以 Converter 端壓力可控
|
||
- 但若有很多 VisionA 使用者同時轉檔,visionA-backend 可能每秒對 Converter 打幾十次 `GET /jobs/:id`
|
||
- **建議**:Architect 在 TDD 中明確給出 rate limit 策略(對 visionA-backend 的 client_credentials token 設 rate limit),並在 API 文件中建議 polling 間隔
|
||
|
||
#### 4.3.2 同時一個轉檔的限制可能引起使用者困惑
|
||
|
||
**風險等級:中**
|
||
- 「我剛剛明明建立了一個 job,為什麼再按一次就 409」可能讓使用者以為系統壞了
|
||
- **建議**:429 或 409 的 response body 要帶完整的 active job 資訊(見 4.1.1),讓 visionA 前端能做出「你有 X 在轉中,要看嗎?」的友善提示
|
||
- **護欄指標**:PRD §9.2.2 已經設計了「同使用者 409 比率 < 5%」的指標追蹤,Design Agent 同意這個目標
|
||
|
||
#### 4.3.3 Converter Bucket 7 天 lifecycle 和使用者期待不符
|
||
|
||
**風險等級:中**
|
||
- 使用者可能誤以為「轉完就在那裡了」,7 天後突然不見會很驚訝
|
||
- **建議**:
|
||
1. API 回應明確標注 `expires_at`(見 4.1.1)
|
||
2. visionA 前端在轉檔結果頁顯示「請於 X 天內加進模型庫,否則檔案將被自動清除」
|
||
3. `POST /promote` 應該是非常顯眼的 primary action(但這是 VisionA 的 UI 決定)
|
||
|
||
#### 4.3.4 使用者中途關掉頁面再回來(Recovery)是否會遺失 SSE?
|
||
|
||
**風險等級:低(已由 PM 決策接受)**
|
||
- PRD 已明確 Phase 1 對外 API 是 polling 模式,visionA-backend 重新進入頁面時會呼叫 `GET /api/v1/jobs?user_id=...&status=in_progress`
|
||
- Converter 本身的 Redis 狀態在這段時間是持續更新的,所以 recovery 不會因為瀏覽器關掉而遺失資料
|
||
- **風險**:若 Converter 在使用者離開期間 Crash(符合設計哲學),使用者回來會看不到任何 job
|
||
- PRD 已在 US-12 明示這個限制(「Crash 即 Reset」不保證跨 Crash recovery)
|
||
- Design Agent 接受這個設計取捨,但建議 API 回應中區分「沒有 in-progress job」vs「job 已不存在(可能因為 Crash / 過期)」讓 visionA 能給使用者不同提示
|
||
|
||
### 4.4 潛在 UX 風險清單(彙整)
|
||
|
||
| # | 風險 | 嚴重度 | 建議處理 |
|
||
|---|------|--------|---------|
|
||
| 1 | Phase 1 使用者下載功能缺口 | **高** | 需 VisionA 產品團隊協調 messaging 策略(見議題 #1) |
|
||
| 2 | Web UI `/jobs` 路徑在公網意外曝露 | 中 | 部署層或 Nginx 層隔離(見 2.3 警示) |
|
||
| 3 | 7 天 lifecycle 讓使用者驚訝 | 中 | API 要暴露 `expires_at`,visionA UI 要顯示 |
|
||
| 4 | 同使用者 409 的使用者困惑 | 中 | 回應要帶完整 active job 資訊 |
|
||
| 5 | 錯誤訊息不夠人類可讀 | 中 | 結構化 error code + 可讀 message |
|
||
| 6 | Polling 效能對 visionA 的影響 | 低 | API p95 < 200ms + 建議 ETag |
|
||
| 7 | Crash 後 job 消失 | 低(已接受)| API 能區分「無 job」vs「job 不存在」 |
|
||
| 8 | 轉檔階段耗時資訊不足,使用者無法預期 | 低 | 回應中帶 `stage_timings`,未來可做 ETA |
|
||
|
||
---
|
||
|
||
## 5. 其他觀察
|
||
|
||
### 5.1 既有 Web UI 的非本次議題(延伸觀察,不在本次範圍)
|
||
|
||
在審查過程中 Design Agent 觀察到既有 Web UI 有以下 UX 債,但 **不屬於本次 L 級範圍**,僅作記錄供未來討論:
|
||
|
||
1. **前後端 API 契約不一致**(已在 PRD §7.3 列出):Web UI 的單階段表單呼叫的 `/api/onnx/upload` 等端點後端沒實作。UX 影響:使用者按了按鈕會失敗
|
||
2. **無時間預估**:使用者只能看到「BIE 階段 45%」,不知道總共還要多久
|
||
3. **無批次上傳**:一次只能轉一個模型
|
||
4. **錯誤訊息品質**:`job.error.reason` 欄位存在但文字品質未知
|
||
5. **無登入系統**:內部工具現況,但若未來要加 OAuth 要從頭設計登入 UX
|
||
|
||
### 5.2 Mermaid 相依圖(從 UX 視角)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph UsersWorld["使用者世界"]
|
||
KneronUser["Kneron 內部 AI 工程師<br/>(既有 Web UI 使用者)"]
|
||
VisionAUser["VisionA 終端使用者<br/>(新的 間接受益者)"]
|
||
end
|
||
|
||
subgraph InnovedusEcosystem["Innovedus 生態"]
|
||
WebUI["Kneron Converter Web UI<br/>(保留不動)"]
|
||
VisionAFrontend["VisionA 前端<br/>(另一個團隊實作)"]
|
||
VisionABackend["visionA-backend<br/>(Persona C)"]
|
||
MemberCenter["Member Center"]
|
||
FileAccessAgent["File Access Agent"]
|
||
end
|
||
|
||
subgraph Converter["Kneron Model Converter(本專案)"]
|
||
OldAPI["舊 API<br/>POST /jobs<br/>GET /jobs/:id<br/>GET /jobs/:id/events (SSE)<br/>不加 OAuth"]
|
||
NewAPI["新 API<br/>POST /api/v1/jobs<br/>GET /api/v1/jobs/:id<br/>POST /api/v1/jobs/:id/promote<br/>OAuth2 Bearer"]
|
||
end
|
||
|
||
KneronUser -->|multipart 上傳 / SSE 看進度| WebUI
|
||
WebUI -->|保持既有呼叫| OldAPI
|
||
|
||
VisionAUser -->|平台內操作| VisionAFrontend
|
||
VisionAFrontend -->|VisionA 自家協議| VisionABackend
|
||
VisionABackend -->|取 service token| MemberCenter
|
||
VisionABackend -->|Bearer token + multipart<br/>(含原始模型 + ref_images)| NewAPI
|
||
|
||
NewAPI -.->|promote PUT 結果檔| FileAccessAgent
|
||
NewAPI -.->|驗 token / 取 token| MemberCenter
|
||
|
||
classDef newPath fill:#e1f5ff,stroke:#0288d1,stroke-width:2px
|
||
classDef oldPath fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
|
||
class NewAPI,VisionAUser,VisionAFrontend,VisionABackend,MemberCenter,FileAccessAgent newPath
|
||
class OldAPI,KneronUser,WebUI oldPath
|
||
```
|
||
|
||
**圖例**:黃色 = Phase 1 保留不動的既有路徑;藍色 = Phase 1 新增 / 整合的部分。
|
||
|
||
---
|
||
|
||
## 6. 需要使用者決策的 UX 議題
|
||
|
||
### 議題 #1(最重要):Phase 1 使用者下載的 messaging 策略
|
||
|
||
**背景**:Phase 1 上線後,VisionA 使用者能完成「上傳 → 轉檔 → 加進模型庫」,但 **不能下載** 已搬進模型庫的模型檔。這是因為 Phase 2 的 delegated download 阻塞於 Member Center 未實作 `POST /file-access/download-tokens`。
|
||
|
||
**為什麼這是重要 UX 議題**:
|
||
- 使用者的心智模型會是「我的模型在我的模型庫裡 → 我當然可以下載它」
|
||
- 上線時如果下載按鈕不能按、或按了沒反應,會是明顯的 UX 死胡同
|
||
- 使用者可能繞道去舊 Kneron Web UI 下載,但那邊資料和 VisionA 模型庫不同步(混亂)
|
||
|
||
**可選方案**:
|
||
|
||
| 方案 | 做法 | UX 優劣 | 工程成本 |
|
||
|------|------|--------|---------|
|
||
| A | 隱藏下載按鈕,標示「下載功能 Phase 2 上線」| UX 誠實,但缺口明顯 | 低(VisionA 前端) |
|
||
| B | VisionA backend 做 proxy 下載(VisionA backend 從 File Access Agent 拉檔再回給瀏覽器)| 暫時堪用,但違反原架構(大檔過 VisionA backend)| 中(VisionA backend) |
|
||
| C | 使用者點下載 → 跳轉到舊 Kneron Converter Web UI 下載 | 技術最簡單,但資料不同步,使用者會混亂 | 低(導流)|
|
||
| D | 等 Member Center 實作完才上線 Phase 1 | UX 完整,但時程被別人卡住 | 阻塞 |
|
||
|
||
**Design Agent 傾向方案 A**(隱藏 + messaging),但這需要 **使用者 + VisionA 產品團隊** 一起決定,不是 Converter 單方能決定。
|
||
|
||
**建議 Orchestrator 協調**:Phase 1 kickoff 前召一次 VisionA 產品團隊 + Kneron Converter PM + Member Center owner 的跨團隊會議,明確 messaging 策略與時程。
|
||
|
||
### 議題 #2(次要):既有 Web UI 的公開曝光風險
|
||
|
||
**背景**:見第 2.3 節。若未來 `/api/v1/*` 對外公開而 `/jobs` 沒做任何隔離,可能被繞過。
|
||
|
||
**需要使用者確認**:
|
||
1. 本專案部署目標是什麼?(純內網 / VPN 後面 / 公網)
|
||
2. Web UI 是否會和對外 API 在同一個 public entry 後面?
|
||
|
||
這個議題交由 Architect Agent 在 TDD 中提出具體部署策略,使用者確認即可。Design Agent 只提出警示。
|
||
|
||
### 議題 #3(低優先):既有 PRD 的 `[推測]` 標記清理
|
||
|
||
見 4.2.3。建議在本次 L 級之後單獨開一個工作項處理。不阻塞 Phase 1。
|
||
|
||
---
|
||
|
||
## 7. 結論
|
||
|
||
### 7.1 對三方聯合討論的 Design 立場
|
||
|
||
- **✅ 同意** PM 的 Phase 1 / Phase 2 切分(§15)
|
||
- **✅ 同意** user_id 以 multipart form field 帶入(方式 A),而非放 token claim
|
||
- **✅ 同意** 原始模型採 multipart 直連上傳(visionA-backend → Converter),不經過 File Access Agent(2026-04-25 更新)
|
||
- **✅ 同意** polling 模式不做 Webhook
|
||
- **✅ 同意** Web UI 不改(Phase 1 保留既有路徑和 UX)
|
||
- **✅ 同意** 搬檔做法 2(Converter 自己 PUT 到 File Access Agent,僅限 promote 階段)
|
||
- **⚠️ 要求補強**:Phase 1 使用者下載 messaging 策略(議題 #1)
|
||
- **⚠️ 要求補強**:API response schema 要符合第 4.1 節的 UX 期望(錯誤結構化、stage_timings、expires_at 等)
|
||
- **⚠️ 新增 UX 關切**:multipart 上傳的 progress bar 呈現與 `invalid_multipart` / `file_too_large` 錯誤碼細節結構化(見 3.1、3.2)
|
||
|
||
### 7.2 Design Agent 對其他兩方文件的審閱(待三方產出後執行)
|
||
|
||
- 審閱 PRD 時的重點:Persona C 是否清楚描述服務對服務的互動?終端使用者的 UX 期望是否納入 acceptance criteria?
|
||
- 審閱 TDD 時的重點:API response schema 是否提供足夠彈性讓 visionA 做好 UX?錯誤碼是否結構化?polling 效能是否達 SLA?
|
||
|
||
### 7.3 本次 Design Review 的直接產出
|
||
|
||
1. 本份 `design-review.md`
|
||
2. `user-flow-cross-system.md`(跨系統使用者流程圖)
|
||
3. **不產出** Wireframe、Prototype、Design Tokens(本次無新 UI 需求)
|