Auto Agent System - Part 5 - 前端體驗與 Pipeline 編排:SSE 即時串流與多步驟任務鏈

後端做得再精巧,使用者感受到的只有兩件事: 「我按下去之後,看得到它在動嗎?」以及「這些任務能不能串起來自動接力?」 前者是即時進度串流,後者是 pipeline 編排。 這一篇——系列最終回——談的就是這條「從按鈕到體驗」的最後一哩路。


四篇走下來:架構(Part 1)、可靠性引擎(Part 2)、實戰任務(Part 3)、生產化(Part 4)。這些都是使用者看不見的底層。最後這一篇,我們談使用者唯一真正碰得到的東西:前端體驗,以及把任務串起來的 Pipeline。


一、即時進度串流:AI 任務為什麼非有它不可

先講「為什麼」。一個 AI 任務可能跑 30 秒到好幾分鐘。如果使用者按下「執行」後盯著一個沒有任何反應的畫面……

   沒有即時進度
   ─────────────────────────────
   [執行] → (轉圈圈…30 秒…) → 使用者以為當機 → 重新整理 → 更慘

   有即時進度
   ─────────────────────────────
   [執行] → 「登入中…」→「掃描案件…」→「撰寫第 3 篇提案…」
         → 「送出中…」→「完成 ✅」
         使用者全程知道它活著、在做什麼、還要多久

對 AI 任務來說,即時進度不是體驗加分,是信任的基礎。因為任務久、又是黑箱,不給回饋,使用者就會焦慮、就會亂按。

這個系統用 SSE(Server-Sent Events) 解決,Part 1 提過機制,這裡完整拆開。


二、SSE 串流的完整運作:四個零件

┌───────────────────────────────────────────────────────────┐
│  ①  背景任務寫 log                                          │
│     Flow 執行中,每完成一小步就 append 一條 log 到 DB        │
│     progress.py 用「原子的 SQL json_insert」寫入            │
│     (不是 read-modify-write,避免併發競態)                 │
└───────────────────────┬───────────────────────────────────┘
                        ▼
┌───────────────────────────────────────────────────────────┐
│  ②  DB 是「單一事實來源」                                    │
│     Run 紀錄裡有一個 JSON 陣列的 log + 當前 status           │
└───────────────────────┬───────────────────────────────────┘
                        ▼
┌───────────────────────────────────────────────────────────┐
│  ③  SSE handler 每 0.5 秒 poll 一次 DB                      │
│     GET /api/runs/{id}/stream                              │
│     讀出「比上次多的新 log」+ 最新 status,推給前端          │
│     直到 status 進入終態(success/failed)才關閉串流         │
└───────────────────────┬───────────────────────────────────┘
                        ▼
┌───────────────────────────────────────────────────────────┐
│  ④  前端用原生 EventSource 接收                             │
│     const es = new EventSource(`/api/runs/${id}/stream`)   │
│     es.onmessage = e => appendLogLine(JSON.parse(e.data))  │
│     零函式庫、瀏覽器原生支援                                 │
└───────────────────────────────────────────────────────────┘

三個設計決策值得深究。

決策 1:為什麼是「poll DB」而不是 WebSocket + pub/sub?

選擇              poll DB 的理由                   WebSocket+Redis 的問題
────────────────────────────────────────────────────────────────
SSE + poll DB     log 本來就寫在 DB(要持久化);   要多架一個 Redis;
(這個專案)      poll 只是多讀一次;              WebSocket 是雙向的,
                  天然抗重整——重連就從 DB          但進度只需單向推送
                  接著讀;實作極簡                 (殺雞用牛刀)
WebSocket+pub/sub  即時性更高(毫秒級)            架構複雜、多一個故障點

關鍵洞察:進度串流是「伺服器單向推給瀏覽器」——這正是 SSE 的主場,不需要 WebSocket 的雙向能力。而且 log 反正要寫進 DB(才能事後查、才能重整不丟),那 SSE handler「順便每 0.5 秒讀一下」幾乎是免費的。用剛好夠的工具,別為了「聽起來高級」而上 WebSocket + Redis。

決策 2:為什麼 0.5 秒,不是 0.1 秒或 2 秒?

這是一個延遲 vs 負載的平衡:

   0.1 秒:太頻繁,DB 被 poll 爆(每個在跑的 run 每秒 10 次查詢)
   2.0 秒:太慢,使用者感覺「卡卡的」、不夠即時
   0.5 秒:人眼幾乎感覺不到延遲,DB 負載也可接受 ← 甜蜜點

翻轉條件:當同時在跑的任務數暴增(例如上千個並發 run),每個都 0.5 秒 poll 一次 DB 會變成負擔——那時才值得升級到 pub/sub 或 LISTEN/NOTIFY。在那之前,poll 的簡單性完勝。

決策 3:為什麼用原子 json_insert 寫 log?

因為背景任務在併發地產生 log。如果用「讀出整個陣列 → append 一筆 → 寫回」(read-modify-write),兩個併發寫入會互相覆蓋、掉 log。

   危險做法(read-modify-write):
   任務A 讀 [1,2] ──┐
   任務B 讀 [1,2] ──┤ 兩個都讀到 [1,2]
   任務A 寫 [1,2,3] ┘
   任務B 寫 [1,2,4] → 蓋掉 A!log 3 消失 💥

   安全做法(原子 json_insert):
   直接叫 DB「在陣列尾巴插一筆」,由 DB 保證原子性
   → 不管多少併發寫入,一筆都不掉 ✅

這是一個很小但很專業的細節:把併發安全性下推給 DB,而不是在應用層自己用鎖去搞——後者又複雜又容易錯。


三、前端:為什麼一個 AI 平台選擇「不用框架」

這個專案的前端是 Vanilla JS + HTML/CSS,沒有 React、沒有 Vue、沒有 build step。在 2026 年這聽起來像逆流,但對這個專案是對的選擇。

選擇          Vanilla JS 的理由                 React/Vue 的代價
────────────────────────────────────────────────────────────────
Vanilla JS    無 build pipeline(改完存檔重整   要 node/npm/webpack/vite;
(這個專案)   即可看);EventSource 原生支援;    build 產物要打進 image;
              頁面少、互動不複雜,框架的         對「幾個表單 + 一條 log
              好處用不太上                      串流」是殺雞用牛刀
React/Vue     複雜 SPA、大量共享狀態、           對這個規模是純負擔
              團隊協作大型前端時才划算

判斷準則很簡單:這個前端有多少「複雜的共享狀態」? 這個平台的前端主要是「選任務 → 填表單 → 看一條即時 log」。狀態很淺、頁面很少。框架最大的價值(複雜狀態管理、組件複用)在這裡幾乎用不到,卻要付出整套 build 工具鏈的代價。

翻轉條件:當前端長出複雜的互動(拖拉式的 pipeline 編輯器、大量即時協作、深層狀態樹)時,框架才開始回本。這也提醒我們:技術選型要看「這個專案現在是什麼」,不是「業界流行什麼」。


四、兩個 UI 的 PR:landing page 與 Waymo 主題

對應 PR #6:feat(ui): add landing page and refine typography/header 對應 PR #15:redesign(ui): Waymo cinematic daylight theme

PR #6:landing page 與排版

一個工具型平台,第一印象很重要。這個 PR 加了 landing page,並打磨了 typography 與 header。這件事常被工程師輕視,但排版與第一眼觀感,直接影響使用者對「這東西專不專業」的判斷——尤其當你想給非工程師(例如業務、行銷)使用時。

PR #15:Waymo 電影感日光主題

這是一次完整的 UI 重新設計,採用一套「Waymo Cinematic」規格:

   Waymo Cinematic Daylight 主題
   ─────────────────────────────
   • 明亮的 light theme(日光感)
   • Waymo 藍的色票(clean、科技、可信)
   • 電影感的 hero 漸層 + 環境感知光暈(ambient perception glow)

為什麼一個個人專案要花力氣做這麼完整的視覺語言?因為一致的設計系統會讓產品「感覺是真的」。零散、東拼西湊的樣式,會讓使用者潛意識覺得「這是個玩具、不太可靠」。一套有명確色票、字體、光影規則的主題,傳遞的訊息是「這是認真做的東西」——而這份信任,會延伸到他們對「AI 跑出來的結果可不可信」的判斷。

UI 不只是好看:對一個要處理金錢(成本)、操作外部帳號(投標、寄信)的平台,視覺上的「專業感」是建立信任的一環。


五、Pipeline 編排:把任務串成自動化工作流

到目前為止,每個任務都是「單獨跑一次」。但真正的自動化威力,來自把任務串起來——一個任務的輸出,變成下一個任務的輸入。這就是 pipeline 任務類型。

{{steps.N.result}} 模板語法

pipeline.py 用一個簡單的模板語法,讓後面的步驟引用前面步驟的結果:

   Pipeline 定義(概念)
   ─────────────────────────────
   步驟 0:shopee_seller_scraper   → 爬出一批賣家
   步驟 1:web_scraper             → 輸入 = {{steps.0.result}}
                                     (拿步驟 0 的賣家清單去逐一抓資料)
   步驟 2:email_sender            → 輸入 = {{steps.1.result}}
                                     (把整理好的資料寄出)
┌──────────┐   result   ┌──────────┐   result   ┌──────────┐
│ 步驟 0    │──────────▶│ 步驟 1    │──────────▶│ 步驟 2    │
│ 爬賣家    │ {{steps    │ 抓資料    │ {{steps    │ 寄信      │
│          │  .0.result}}│          │  .1.result}}│          │
└──────────┘            └──────────┘            └──────────┘
     每一步都是「一個完整的任務」,享有 Part 2 的全套 Harness
     (重試、驗證、評分、記帳)——pipeline 只負責把它們接起來

為什麼是「字串模板」而不是視覺化拖拉編輯器?

選擇              字串模板的理由                   視覺化編輯器的代價
────────────────────────────────────────────────────────────────
{{steps.N.result}} 實作極簡;pipeline 可存成       要做一整套前端 DAG
(這個專案)       純 JSON;好版本控管、好複製      編輯器,工程量巨大
視覺化 DAG        對非工程師友善、直觀             殺雞用牛刀(現階段)

這又是一次「複雜度剛好」的取捨。字串模板雖然不如拖拉介面漂亮,但它把 pipeline 變成一份可序列化的 JSON——可以存、可以版本控管、可以複製貼上分享。對現階段的使用者(多半懂技術)這完全夠用。翻轉條件:當目標使用者變成完全不懂技術的人、且 pipeline 變得很長很複雜時,視覺化編輯器的投資才開始值得。

Pipeline 的美妙之處:組合的威力

pipeline 讓 11 個任務不再是 11 個孤島,而是可以自由組合的積木:

   潛在客戶開發 pipeline:
   Google Maps 名單 → email 驗證分級 → 個人化寄信

   競品研究 pipeline:
   Shopee 爬賣家 → web_scraper 抓各賣家詳情 → 彙整報告

   內容監控 pipeline:
   HN digest → X 爬文 → 摘要整理 → 寄給自己

而且——每一個步驟都自動繼承了整套 Harness(Part 2):步驟裡的任何一步 LLM 掛了會自動 fallback、結果不合格會自我修正、每步都被評分記帳。pipeline 本身只做一件事:把結果從一步傳到下一步。可靠性?底層早就包辦了。

這正是整個系列的主題在最後收束:因為地基(Harness)打得好,上層的組合(pipeline)才能又簡單又可靠。


六、五篇回顧:這個專案到底教了我們什麼

走完五篇,我們把 agent_auto_system 從裡到外看了一遍。如果要濃縮成幾條可帶走的原則:

主題核心原則
Part 1 架構把「跟 LLM 打交道的髒活」抽成 Harness,業務邏輯才乾淨
Part 2 可靠性LLM 預設不可靠;分清「值得重試」與「重試也沒用」;品質要有客觀分數
Part 3 任務沒有唯一的 AI 寫法;讓 LLM 少做、把對的步驟交給對的工具
Part 4 生產化為「短命基礎設施 + 多人使用」而設計;可觀測性掛在唯一漏斗
Part 5 前端用剛好夠的工具(SSE 不 WebSocket、Vanilla 不框架);組合的威力來自扎實的地基

貫穿全部的一條線是:「複雜度要花在刀口上」。這個專案在該講究的地方(Harness 可靠性、可觀測性、權限)毫不妥協;在不必要的地方(前端框架、pub/sub、視覺化編輯器)果斷選擇簡單。這種判斷力——知道什麼時候該複雜、什麼時候該簡單——才是把一個 AI Demo 做成一個 AI 產品,真正的分水嶺。


系列導覽

  • Part 1:系統總覽、架構、資料流
  • Part 2:Harness 引擎——多模型容錯、自我修正、LLM 評審、成本追蹤
  • Part 3:自動化任務實戰——Shopee、Google Maps、Tasker、利潤健檢
  • Part 4:生產化之路——Langfuse、Docker、AWS、權限
  • Part 5(本篇):前端體驗與 Pipeline 編排

對應的 PR:#6 landing page#15 Waymo 主題 專案原始碼:github.com/yennanliu/agent_auto_system

感謝一路讀到這裡。如果這個系列對你有幫助,不妨去 clone 這個 repo,自己跑一次——最好的學習,永遠是把它跑起來、然後試著加一個你自己的任務。

Yen

Yen

Yen