Auto Agent System - Part 1 - 系統總覽:一個 CrewAI 多代理自動化平台如何運作

大部分人寫 AI Agent:把 prompt 丟給 OpenAI SDK,拿到字串,json.loads(),能跑就好。 出事了才發現:模型偶爾回 markdown 包住的 JSON、偶爾 429、偶爾一本正經地亂編——每個任務都要各自處理一次。 這個專案的答案是:把「跟 LLM 打交道的所有髒活」抽成一層 Harness,讓業務邏輯乾乾淨淨。 這一系列會帶你從架構總覽,一路讀到它每一個 merged PR 背後的工程決策。


本系列以 yennanliu/agent_auto_system 這個開源專案為主角,一共五篇:

  • Part 1(本篇):系統總覽、架構、資料流
  • Part 2:Harness 引擎——多模型容錯、自我修正、LLM 評審、成本追蹤
  • Part 3:自動化任務實戰——Shopee 爬蟲、Google Maps 名單、Tasker 自動提案
  • Part 4:生產化之路——Langfuse 可觀測性、Docker 瘦身、AWS 部署、權限系統
  • Part 5:前端體驗與 Pipeline 編排——SSE 即時串流、多步驟任務鏈

一、這個系統到底在做什麼

一句話:它是一個「AI 自動化任務的執行平台」。你在網頁上選一個任務(例如「爬 Shopee 賣家」「從 Google Maps 收集潛在客戶名單」「自動幫我在 tasker.com.tw 投標」),填幾個欄位,選一個 LLM 供應商與模型,按下執行——系統就會在背景跑一整個 AI 代理流程,並把進度即時串流回你的畫面。

它不是一個聊天機器人,而是一個把「重複性的知識工作」變成一鍵可執行任務的平台。目前內建 11 種任務類型:

任務類型                     做什麼
──────────────────────────────────────────────────────────────
google_form_fill        AI 檢視並自動填寫/提交 Google 表單
web_scraper             抓取網址,回傳結構化摘要
google_sheet_reader     讀取公開 Google Sheet,做欄位/統計分析
shopee_seller_scraper   從熱門商品收集賣家清單(最多 100 筆)
profit_health_check     四個 agent 協作,分析 Shopee CSV,輸出 PDF 報告
x_scraper               抓取公開 X(Twitter)帳號的近期貼文
hacker_news_digest      把 HN 熱門文章整理成摘要
email_collect           Google Maps 漏斗:找商家 → 抓 email → 驗證 → 分級
tasker_apply            自動在 tasker.com.tw 投標,AI 撰寫提案文案
email_sender            透過 Gmail SMTP 寄信(非 LLM 任務)
pipeline                把上述任務串成多步驟工作流

這些任務背後有一個共同點:它們都得跟 LLM 打交道,而跟 LLM 打交道本身就充滿不確定性。這正是整個專案設計的出發點。


二、核心設計哲學:Harness Engineering

先講一個很多人踩過的坑。

當你只有一個 AI 功能時,程式碼長這樣很正常:

1resp = client.chat.completions.create(model="gpt-4o", messages=[...])
2data = json.loads(resp.choices[0].message.content)
3return data

但當你有 11 種任務、3 家供應商、每個任務都需要重試/驗證/成本追蹤時,如果每個任務都各寫一遍上面的邏輯,你會得到 11 份互相不一致、各自有 bug 的「跟 LLM 打交道」的程式碼。

這個專案的核心決策是:把所有跟基礎設施相關的髒活,抽成一層叫 Harness 的東西,和業務邏輯(「這個任務要做什麼」)徹底分開。

        ┌─────────────────────────────────────────────┐
        │            業務邏輯(Flows / Crews)          │
        │   「爬 Shopee」「投標」「分析利潤」——只管做事  │
        └───────────────────────┬─────────────────────┘
                                 │ 完全不碰以下這些
        ┌───────────────────────▼─────────────────────┐
        │                  Harness 層                   │
        │  ┌──────────┐ ┌──────────┐ ┌──────────────┐  │
        │  │ Provider │ │Validator │ │  Evaluator   │  │
        │  │ 選模型    │ │ 驗證結果  │ │ LLM 評審打分  │  │
        │  └──────────┘ └──────────┘ └──────────────┘  │
        │  ┌──────────┐ ┌──────────────────────────┐   │
        │  │  Costs   │ │  Langfuse Tracer(可觀測) │   │
        │  │ 算 token  │ │  一次 run 一條 trace       │   │
        │  └──────────┘ └──────────────────────────┘   │
        └───────────────────────────────────────────────┘

「Harness」原意是「馬具/挽具」——套在馬身上、讓你能安全駕馭一匹力氣很大但不受控的野獸。用來形容 LLM 再貼切不過了。這個譬喻貫穿整個專案,也是我們 Part 2 會深入的重點。


三、整體架構:從瀏覽器到外部 API

先看全貌,一層一層由上往下:

┌───────────────────────────────────────────────────────────┐
│  瀏覽器 UI(需登入)  Vanilla JS + HTML/CSS                  │
│  選任務 → 填表單 → 選模型 → 按執行 → 看即時進度              │
└──────────────────────────┬────────────────────────────────┘
                           │  HTTP / SSE(Server-Sent Events)
┌──────────────────────────▼────────────────────────────────┐
│  FastAPI 後端                                                │
│  routers: auth / admin / jobs / runs / system / uploads    │
└──────────────────────────┬────────────────────────────────┘
                           │
┌──────────────────────────▼────────────────────────────────┐
│  Harness Executor 層(central orchestration)               │
│  正規化 → 派發 → 驗證 → 重試 → 評分 → 記錄成本               │
└──────────────────────────┬────────────────────────────────┘
                           │
┌──────────────────────────▼────────────────────────────────┐
│  CrewAI:Flows → Crews → Tools                              │
│  每種任務一個 Flow;每個 Crew 由若干 agent 組成              │
└──────────────────────────┬────────────────────────────────┘
                           │
┌──────────────────────────▼────────────────────────────────┐
│  外部 API                                                    │
│  OpenAI / Anthropic / Gemini / Gmail / Maps / Playwright   │
└───────────────────────────────────────────────────────────┘

技術棧一覽:

技術為什麼
後端框架FastAPI原生 async、SSE 支援好、型別友善
資料層SQLModel + SQLite(或 PostgreSQL)開發零設定,上線可換 Postgres
代理編排CrewAI多 agent 協作、內建 crew/task 抽象
瀏覽器自動化Playwright需要真的登入/點擊的任務(Shopee、Tasker)
PDF 產出WeasyPrint純 Python、不需要 Chromium(Part 4 會講為什麼)
前端Vanilla JS無建置步驟,SSE 直接用 EventSource

值得注意的是:前端刻意不用 React/Vue,而是純 Vanilla JS。對一個「內部工具型」平台來說,少一整套 build pipeline、直接用瀏覽器原生的 EventSource 接 SSE,反而更輕、更好維護。


四、CrewAI 的三層抽象:Flow、Crew、Tool

這個專案的業務邏輯全部建立在 CrewAI 上,理解它的三層抽象是關鍵:

Flow(流程)
  │  一種任務類型 = 一個 Flow 子類別
  │  掌管「這個任務分幾步、狀態怎麼走」
  ▼
Crew(團隊)
  │  一群 agent 組成的團隊,協作完成一個子目標
  │  例:profit_health_check 有「驗證/分析/建議/彙整」四個 agent
  ▼
Tool(工具)
     可重複使用的能力:web scraper、form inspector、
     email 寄送、maps 查詢……agent 呼叫 tool 去實際做事

profit_health_check(利潤健檢)當例子最清楚。你上傳一份 Shopee 的銷售 CSV,系統跑一個由四個 agent 組成的 crew:

   CSV 上傳
      │
      ▼
┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│  Agent 1    │──▶│  Agent 2    │──▶│  Agent 3    │──▶│  Agent 4    │
│  資料驗證    │   │  利潤分析    │   │  提出建議    │   │  彙整成報告  │
│ 欄位對不對?  │   │ 毛利/趨勢?  │   │ 該怎麼改善?  │   │ → HTML → PDF │
└─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘
                                                              │
                                                              ▼
                                                        PDF 報告下載

這裡有一個很細但很重要的工程決策:Crews 是「純類別」,不用 CrewAI 的 @CrewBase 裝飾器

為什麼?因為 @CrewBase 會做一些快取,導致 LLM 實例被 stale 地快取起來——當你這次 run 想換一個模型時,它可能還在用上一次的。這個專案選擇每次 run 都重新建一個乾淨的 crew,把已解析好的 LLM 注入進去,徹底避開這個坑。這種「寧可多花一點建立成本,也要換來可預測性」的取捨,在整個 codebase 隨處可見。


五、一個請求的完整生命週期

現在把所有東西串起來。假設你要跑一個「爬 Shopee 賣家」的任務,從按下按鈕到看到結果,實際發生了什麼:

①  使用者選任務、填表單、選 provider/model,按「執行」
        │
        ▼
②  POST /api/jobs          → 建立一筆 Job 紀錄(存 DB)
    POST /api/jobs/{id}/run → 立刻回 202 Accepted
        │                     並用 asyncio.create_task 把任務丟到背景
        ▼
③  Executor 正規化 provider/model 字串(此時還不建 LLM 實例)
        │
        ▼
④  派發到對應的 Flow;Flow 建一個全新的 CrewAI crew,
    注入解析好的 LLM,開始執行 tools
        │
        ▼
⑤  Validator 檢查結果:欄位齊不齊?內容夠不夠長?
        │
        ├─ 失敗 → 把 previous_error 注入 payload,重跑讓 LLM 自我修正
        └─ 成功 ↓
        ▼
⑥  獨立的 Evaluator(LLM 評審)幫這次輸出打 0–100 分
        │
        ▼
⑦  Executor 記錄所有 harness 指標:token 數、估算美元成本、
    評分、重試次數,寫回 Run 紀錄
        │
        ▼
⑧  SSE 串流(GET /api/runs/{id}/stream)每 0.5 秒推一次最新
    狀態與 log 給 UI,直到任務進入終態
        │
        ▼
⑨  (可選)發出一條 Langfuse trace
        ▼
⑩  依任務類型匯出結果:JSON / CSV / PDF

這裡有幾個關鍵設計值得停下來看:

1. 為什麼 /run 立刻回 202,而不是等結果?

因為一個 AI 任務可能跑 30 秒到好幾分鐘。如果同步等待,HTTP 連線會被卡住、容易 timeout。所以採用「立即回 202 + 背景 asyncio task + SSE 串流進度」的非同步模式。任務被註冊在一個 registry.py 的 task registry 裡,這樣使用者中途按「取消」時才有辦法真的停掉它。

2. normalize()resolve() 為什麼要分兩步?

normalize("gpt4o")  →  ("openai", "gpt-4o")   # 純字串處理,不呼叫 API
resolve(...)        →  建立實際的 CrewAI LLM 物件  # 這步才真的花錢/連線

正規化只是把使用者輸入的雜亂字串(gpt4oGPT-4Ogpt-4o)整理成標準的 (provider, model),完全不碰網路。真正建立 LLM 物件的 resolve() 延後到最後一刻才做。這讓「驗證輸入是否合法」變得又快又便宜。

3. SSE 為什麼用「每 0.5 秒 poll DB」而不是用 pub/sub?

因為進度 log 本來就寫在 DB 裡(用原子性的 SQL json_insert append,避免 read-modify-write 的競態)。SSE handler 只要每 0.5 秒讀一次 DB、把新的 log 推給前端就好。對這個規模的系統來說,這比架一套 Redis pub/sub 簡單太多,而且天然就有持久化——就算使用者重新整理頁面,進度也不會消失。


六、專案結構速覽

熟悉一下目錄,後面幾篇會不斷回來看這些檔案:

src/
├── main.py                # FastAPI app、lifespan hooks
├── database.py            # SQLModel engine、migration
├── models.py              # User / Job / Run / Setting 資料表
├── auth.py                # 登入、RBAC 權限控管
├── settings_store.py      # 任務目錄、加密 API key、評審模型設定
├── routers/               # API endpoints
└── automation/
    ├── executor.py        # ★ 中央調度:正規化/派發/驗證/重試/評分
    ├── pipeline.py        # 多步驟 pipeline,支援 {{steps.N.result}} 模板
    ├── progress.py        # 用 SQL json_insert 原子性 append log
    ├── registry.py        # 可取消任務的 asyncio task registry
    ├── report_render.py   # PDF 產出(WeasyPrint)
    ├── harness/           # ★ Provider / Validator / Evaluator / Costs / Langfuse
    ├── flows/             # 每種任務一個 Flow 子類別
    ├── crews/             # 每個任務的 crew(純類別)
    └── tools/             # 可重用工具(爬蟲、表單檢視、email、maps…)

tests/                     # 380 個單元 + 整合測試(26 unit + 10 integration 檔)
ui/                        # HTML、Vanilla JS、CSS
doc/                       # 架構、部署、開發筆記

打星號的 executor.pyharness/ 是整個系統的心臟,也是 Part 2 的主角。

順帶一提:380 個測試對一個個人專案來說是相當高的紀律。CI 分三段——host 層 lint/test、Docker build 跑完整測試、再對 runtime image 做 smoke test。這種「基礎設施可靠度」的重視,和 Harness 的設計哲學是一脈相承的。


七、權限與管理:不是玩具,是可上線的東西

這個系統從第一天就是「登入才能用」的。第一次啟動會 seed 一個預設 admin(admin/admin,上線前必須改掉)。Admin 可以:

  • 管理使用者
  • 指派每個使用者能用哪些任務(per-user allowlist)
  • 全域開關某個任務類型
  • 把加密後的 API key 存在 DB 裡(而不是散落在環境變數)
  • 選擇「評審用的 LLM 模型」
       ┌──────────┐
       │  Admin   │
       └────┬─────┘
            │ 指派
   ┌────────┼────────┐
   ▼        ▼        ▼
┌──────┐ ┌──────┐ ┌──────┐
│User A│ │User B│ │User C│
│可用: │ │可用: │ │可用: │
│爬蟲   │ │全部  │ │投標   │
│分析   │ │      │ │      │
└──────┘ └──────┘ └──────┘

這一整套 auth/admin 設計,我們留到 Part 4 和部署一起講。


八、小結:為什麼這個專案值得一讀

如果你只想做「一個能跑的 AI Demo」,你不需要這麼多東西。但如果你想做「一個多人用、多任務、多模型、還要能追成本和品質的 AI 平台」,這個專案示範了幾個關鍵的工程判斷:

判斷這個專案的選擇一般 Demo 的做法差別
LLM 打交道的髒活抽成 Harness 層每個功能各寫一遍一致性、可測試性
任務執行202 + 背景 task + SSE同步等待不 timeout、可取消
進度串流poll DB + json_insertRedis pub/sub更簡單、天然持久化
Crew 建立每次重建純類別@CrewBase 快取避開 stale LLM
模型解析normalize / resolve 分離直接建 client驗證快又便宜

下一篇 Part 2,我們鑽進整個系統的心臟——Harness 引擎。你會看到它如何在 LLM 429 時自動換一個 sibling 模型重試、如何在結果驗證失敗時把錯誤訊息餵回去讓 LLM 自我修正、如何用一個獨立的評審模型幫每次輸出打分,以及一個看似無害卻讓無數人踩坑的問題:LLM 回傳的 JSON 被 markdown code fence 包起來時,你該怎麼優雅地解出來(這正是這個專案的第一個 PR)。


系列導覽

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

專案原始碼:github.com/yennanliu/agent_auto_system

Yen

Yen

Yen