標籤: AI

  • Claude Code 系列文 (3/5):設計文檔 — 讓 Claude 一次做對

    ⚡ 重點摘要

    • 設計文檔解決「Claude不是心靈讀者」的問題
    • 最小版本20分鐘:What+How+Edge Cases+Done When
    • 投入30分鐘,節省6小時改動,ROI 600%
    • 中等以上複雜度的功能都應該寫設計文檔

    前提:已寫好 CLAUDE.md(第 2 篇) 時間投入:20-30 分鐘設計 + 2 小時實施(vs 8 小時傳統方式) 成果:90%+ 一次正確,最多微調 1-2 處


    核心觀點

    CLAUDE.md:永遠的規則(不變)
       ↓
    設計文檔:這次功能的具體需求(會變)
       ↓
    實施:Claude 讀完兩者,一次到位

    實際效果

    傳統:
    user: "加個 PDF 匯出功能"
    claude: 實施... 但漏了 QR code、沒考慮大文件、錯誤處理不完善
    result: 改 3 次,花 8 小時
    
    ===
    
    設計優先:
    user: 先寫「PDF 匯出設計文檔」
       ├─ 輸出什麼欄位
       ├─ 格式細節
       ├─ 大文件怎麼處理
       └─ 邊界情況怎麼做
    
    claude: 讀完設計,實施功能
    result: 一次到位,花 2 小時

    為什麼設計文檔這麼關鍵

    原因 1:Claude 不是心靈讀者

    你想要:
    "PDF 應該很專業,像官方發票"
    
    Claude 理解的:
    "Generate a PDF" (可能就是基礎格式)
    
    ===
    
    如果你寫:
    "PDF 格式:
     - 頁眉:公司 logo + 發票編號
     - 內容:3 欄表格 (品項|數量|金額)
     - 頁尾:二維碼 + 簽章區域
     - 字體:14pt 標題,11pt 內容"
    
    Claude 理解的:
    正確無誤 ✅

    原因 2:邊界情況很容易漏

    你沒說的情況:
    - 如果發票超過 1 頁怎麼辦?
    - 品項名稱很長怎麼折行?
    - 金額超過 99,999,999 怎麼顯示?
    
    Claude 的猜測:
    - 默認假設只有 1 頁
    - 品項名稱截斷
    - 金額省略號或出錯
    
    ===
    
    設計文檔說清楚:
    - 多頁:自動翻頁,每頁重複頁眉
    - 長品項:自動折行到 2-3 行,調整行高
    - 大金額:科學計數法或分行顯示
    
    Claude:
    做對 ✅

    原因 3:審核快得多

    沒有設計文檔:
    你看代碼 → "嗯... 這個邏輯有點問題?"
                → 要改 → 再看一遍 → 還要改
    時間:1-2 小時審核
    
    ===
    
    有設計文檔:
    設計文檔清楚 → Claude 一次到位
    審核只需檢查「有沒有按設計文檔做」
    時間:15 分鐘審核

    一人公司的設計文檔長什麼樣

    最小版本(20 分鐘寫完)

    假設你要加「發票篩選功能」:

    # Invoice Filter Feature
    
    ## What
    用戶可以按日期、狀態、金額篩選發票列表
    
    ## How
    ### Page Flow
    1. 使用者進入「發票清單」頁面
    2. 頁面上方顯示篩選器:
       - Date range picker (from - to)
       - Status dropdown (全部 / 草稿 / 已提交 / 已申報)
       - Amount range slider (0 - 999,999)
    3. 用戶選擇條件 → 自動篩選列表(無需按搜尋按鈕)
    4. 篩選結果顯示在表格
    
    ### Data Model
    ```sql
    -- 輸入參數
    filter: {
      dateFrom: Date,
      dateTo: Date,
      status: enum ['DRAFT', 'SUBMITTED', 'FILED'],
      amountMin: number,
      amountMax: number
    }
    ```
    
    ### Edge Cases
    - 沒有搜尋結果 → 顯示「沒有符合條件的發票」
    - 篩選中 → 顯示 loading spinner
    - API 錯誤 → 顯示「載入失敗,請重試」
    
    ## Why
    - 用戶有 100+ 發票,手動找很慢
    - 會計需要按日期整理報表
    - 管理者需要看特定金額範圍的交易

    花時間:20 分鐘 代碼行數:20-30 行(因為清晰,不用試錯)

    完整版本(30 分鐘寫完)

    如果功能更複雜,加這些:

    # Invoice PDF Export Feature
    
    ## 業務背景
    台灣政府要求發票有統一格式(GUN 標準)
    用戶要匯出發票給客戶或存檔
    
    ## 用戶故事
    "作為發票系統使用者,我想點擊『匯出 PDF』按鈕,
      得到一份官方格式的 PDF,這樣我可以寄給客戶或稅務局"
    
    ## 功能範圍
    ### 必須有
    - [ ] PDF 包含所有發票欄位(發票號碼、日期、品項、金額等)
    - [ ] GUN 格式(台灣政府標準)
    - [ ] 二維碼(可掃描驗證)
    - [ ] 頁眉和頁尾
    - [ ] 多頁自動分頁
    
    ### 可以有(下個版本)
    - [ ] 批量匯出(多張發票 1 個 PDF)
    - [ ] 自訂 logo
    - [ ] 簽章區域
    
    ### 不包含
    - [ ] 自訂格式(template 系統太複雜)
    - [ ] 列印功能(瀏覽器列印已足)
    
    ## API/Data
    ### Input
    ```typescript
    POST /api/invoices/{id}/export-pdf
    
    // Response
    {
      pdf_url: "https://..../invoice_2024_001.pdf",
      generated_at: "2024-03-25T10:30:00Z"
    }
    ```
    
    ## UI/UX
    ### 按鈕位置
    - 在「發票詳情」頁面右上角
    - 按鈕文字:「匯出 PDF」
    - 點擊後:shows loading 3 秒,下載 PDF
    
    ### 錯誤情況
    - 網路錯誤 → 「無法生成 PDF,請重試」
    - 發票不完整 → 「發票資料不完整,無法匯出」
    - 檔案太大(> 10MB) → 「檔案過大,請聯絡管理員」
    
    ## 技術決策
    ### Why jsPDF instead of server-side?
    - jsPDF:快速、實時、不佔服務器資源 ✅
    - Server-side:需要 Node.js 套件、佔内存
    
    ### Why QR code?
    - Taiwan GUN requirement
    - Users can verify authenticity by scanning
    
    ## 測試 Checklist
    - [ ] PDF 內容完整(所有欄位都在)
    - [ ] 格式符合 GUN 標準(對齐、字體大小)
    - [ ] 二維碼可掃描
    - [ ] 多頁發票能正確分頁
    - [ ] 邊界情況:長品項名稱、大金額
    - [ ] 錯誤訊息清晰
    
    ## 完成條件(Acceptance Criteria)
    - [ ] 用戶點擊按鈕能下載 PDF
    - [ ] PDF 格式符合 GUN 標準
    - [ ] 二維碼正確生成
    - [ ] 網路慢時顯示 loading
    - [ ] 有錯誤時顯示清晰的訊息
    - [ ] 所有測試通過(coverage > 85%)

    花時間:30 分鐘 代碼行數:50-80 行(因為設計完整,邊界情況少)


    如何快速寫設計文檔(給一人公司老闆)

    方法 1:從 User Story 開始(最快)

    ## 功能
    允許用戶標記發票為「已審核」
    
    ## 用戶故事
    "As a manager, I want to mark invoices as reviewed,
     so I can track which invoices have been checked"
    
    ## 怎麼做
    1. 發票表格增加「審核」按鈕
    2. 點擊後標記為「已審核」,按鈕變灰
    3. 可以「取消審核」改回去
    
    ## Edge Cases
    - 多人同時審核同一張發票? → 最後一次改動獲勝
    - 已提交的發票能審核嗎? → 能,審核和提交獨立
    
    ## Done When
    - [ ] 按鈕會出現
    - [ ] 狀態能改變
    - [ ] 數據持久化
    - [ ] 網路慢時有 loading

    花時間:10-15 分鐘

    方法 2:從技術問題開始(有爭議時)

    ## 問題
    稅務計算邏輯現在在 service/taxCalculator.ts,
    很難測試,也難新增新稅種
    
    ## 提議
    用 Strategy Pattern 重構
    
    ## 對比
    ### 現狀(有問題)
    ```typescript
    if (type === 'standard') {
      tax = amount * 0.05;
    } else if (type === 'zero') {
      tax = 0;
    }
    // 很多 if,難擴展
    ```
    
    ### 改進後
    ```typescript
    const strategy = TaxStrategyFactory.get(type);
    tax = strategy.calculate(amount);
    // 新稅種只需新建 class,無需改舊代碼
    ```
    
    ## 工作
    1. 建立 TaxStrategy interface
    2. 建立 StandardTaxStrategy, ZeroTaxStrategy, ExemptTaxStrategy
    3. 建立 TaxStrategyFactory
    4. 遷移現有邏輯
    5. 寫測試
    
    ## 完成條件
    - [ ] 所有現有測試通過
    - [ ] 無新 bug
    - [ ] 代碼行數 < 現狀(因為減少 if)
    - [ ] 新稅種能在 30 分鐘內加入

    花時間:15-20 分鐘


    設計文檔和 CLAUDE.md 的區別

    項目CLAUDE.md設計文檔
    頻率一年改幾次每個功能寫一份
    內容規則、標準這次功能的細節
    例子「所有 component 用 PascalCase」「篩選器有 date picker 和 status dropdown」
    變化不變常變
    時效永遠有用功能完成後就 archive

    常見問題

    Q: 設計文檔很麻煩嗎?

    A: 不麻煩。其實省時間。

    對比:

    • ❌ 不寫設計文檔:2 小時設計(腦子裡)+ 8 小時實施 + 5 小時改 = 15 小時
    • ✅ 寫設計文檔:30 分鐘寫 + 2 小時實施 + 0.5 小時改 = 2.5 小時

    省時 6 倍。

    Q: 如果需求改了怎麼辦?

    A: 更新設計文檔,再讓 Claude 改。

    原設計:篩選器是下拉選單
    新需求:改成複選框(可選多個狀態)
    
    做法:
    1. 更新設計文檔的 UI/UX 部分
    2. Prompt: "按照新設計文檔改篩選器"
    3. Claude 改
    4. 完成

    Q: 小功能也要寫設計文檔嗎?

    A: 看複雜度

    • 簡單(邏輯 < 5 行):無需文檔,直接 prompt
    • 中等(邏輯 20-50 行):寫簡單版設計文檔(10 分鐘)
    • 複雜(邏輯 > 50 行,多頁面):寫完整版設計文檔(30 分鐘)

    一般來說:中等及以上都要寫


    實戰範例:Taiwan Invoice 設計文檔

    這是真實的設計文檔,你可以直接改成自己的:

    # Taiwan Invoice System - Design Doc
    
    ## 概述
    系統需要符合台灣官方發票格式 (GUN)
    用戶可以生成、儲存、匯出發票
    
    ## 模組 1: Invoice Data Model
    
    ### 資料結構
    ```sql
    CREATE TABLE c_invoice (
      id UUID PRIMARY KEY,
      invoice_number VARCHAR(10),  -- e.g., "001", "002"
      invoice_date DATE,
      company_name VARCHAR(255),
      company_tax_id VARCHAR(8),
      customer_name VARCHAR(255),
      customer_tax_id VARCHAR(8),
      items JSONB,  -- [{name, qty, price, amount}, ...]
      subtotal DECIMAL(12,2),
      tax_amount DECIMAL(12,2),
      total DECIMAL(12,2),
      status ENUM('DRAFT', 'SUBMITTED', 'FILED'),
      created_at TIMESTAMP,
      updated_at TIMESTAMP
    );
    ```
    
    ## 模組 2: Tax Calculation
    
    ### 規則
    - 標準稅率:5%(大部分商品)
    - 零稅率:0%(出口商品)
    - 免稅:0%(非營利組織)
    - 進項稅扣除:同月內才能計算
    
    ### 計算邏輯
    ```
    total_tax = sum(item.tax for item in items)
    where tax = item.amount * tax_rate
    
    if invoice.status == 'SUBMITTED':
      deductible_tax = tax (可抵扣)
    else:
      deductible_tax = 0 (草稿狀態不計)
    ```
    
    ## 模組 3: PDF Export
    
    ### 輸出格式
    頁面:A4 (210mm x 297mm)
    邊距:10mm
    
    頁眉:
    - 左:公司 logo (40x40mm)
    - 中:「統一發票」(20pt bold)
    - 右:發票號碼 (16pt)
    
    內容:
    - 發票日期
    - 買賣雙方資訊
    - 3 欄表格:品項、數量、金額
    - 合計 / 稅額 / 總金額
    
    頁尾:
    - QR code (25x25mm)
    - 簽章區域
    
    ## 完成條件
    - [ ] 資料模型完整
    - [ ] 稅務計算正確
    - [ ] PDF 格式符合 GUN 標準
    - [ ] 所有 edge cases 測試通過

    一人公司老闆的建議

    做法 1:檔案形式

    寫在 docs/designs/ 資料夾:

    docs/designs/
    ├── invoice-filter.md
    ├── invoice-pdf-export.md
    └── tax-calculation.md

    提交到 git,成為代碼庫的一部分。

    做法 2:更新設計文檔的時機

    • 需求改變時
    • 發現 bug 時
    • 新的 edge case 出現時

    加一句:

    ## 更新日誌
    - 2024-03-25:初版
    - 2024-03-26:加入多頁分頁需求
    - 2024-03-27:修正邊界情況 (長品項名稱)

    下一步

    現在就做

    選一個你想要的功能(不用很大),寫設計文檔:

    1. 打開編輯器

    bash touch docs/designs/[feature-name].md

    1. 按照上面的「最小版本」模板寫
    • What (是什麼)
    • How (怎麼做)
    • Edge cases (邊界情況)
    • Done when (完成條件)
    1. 花時間:15-20 分鐘
    1. 給 Claude

    “`bash claude user: “這是 [功能名稱] 的設計文檔 [貼上設計文檔]

    請按照這個設計實施” “`

    1. 對比傳統方式
    • 時間縮短 60-70%
    • 修改次數 ÷ 3
    • Bug 數量 ÷ 2

    重點回顧

    CLAUDE.md:「怎樣寫好代碼」(寫一次,永遠用)
       ↓
    設計文檔:「這次要寫什麼」(每功能一份)
       ↓
    Claude:一次實施正確 ✅

    投資回報

    • 每份設計文檔花 30 分鐘
    • 省 6 小時實施時間
    • 一年 (~50 功能) 省 300 小時 = 6 周工作量!

    下一篇預告

    第 4 篇:Hook 自動化

    有了 CLAUDE.md 和設計文檔,代碼質量大幅提升。

    但還有很多重複的手動工作:

    • 每次編輯後手動 lint
    • 每次 commit 前手動執行測試
    • 每週手動檢查依賴更新

    Hooks 自動化這些!

    下一篇會告訴你:

    • 怎樣 5 分鐘設置 3 個最重要的 Hook
    • 節省每週 5-10 小時手動工作
    • 實例:after-edit (auto-format) + before-commit (auto-test)

    現在就開始寫第一個設計文檔! 👋

  • Claude Code 系列文 (1/5):一人公司的開發穩定性秘密

    ⚡ 重點摘要

    • 一人公司最大問題:時間少,重複改動多
    • 規劃優先方法:CLAUDE.md + 設計文檔 → Claude一次做對
    • 每個功能開發時間從8-16小時縮短到2-3小時
    • 本系列5篇教你建立完整的一人公司開發系統

    寫給:一人公司老闆、獨立開發者 痛點:時間少、要求多、不能重複勞動 解決方案:用規劃優先的工作流 + Claude Code


    你的困境(認真的)

    你一個人同時是:

    • 💼 產品經理(要什麼功能)
    • 🔨 開發者(怎麼建)
    • 🧪 QA(有沒有 bug)
    • 📚 文檔編寫者(怎麼用)
    • 🚀 運維(部署和監控)

    時間分配很絕望

    • 實際寫代碼:可能只有 30%
    • 重複做同樣的決策:20%
    • 找 bug、修 bug:20%
    • 手動測試:15%
    • 其他雜事:15%

    結果

    • 一個月才能交付一個功能
    • 沒時間優化
    • 沒時間文檔
    • 夢裡都在 debug

    一般人的解決方案(都不對勁)

    ❌ 方案 1:招一個開發者

    • 成本:月薪 $2000-4000
    • 你的增加成本:70-80% (管理、協調、溝通)
    • 結果:不划算,特別是你的需求變化快

    ❌ 方案 2:外包給開發公司

    • 成本:$1000-3000 per feature
    • 問題:
    • 他們不懂你的業務邏輯
    • 一堆 bug 需要反覆修
    • 根本沒人維護
    • 改 UI 要等一周

    ❌ 方案 3:用低代碼/無代碼工具

    • 優點:快速上手
    • 缺點:
    • 超過基本功能就卡住
    • 無法擴展
    • 數據鎖定在平台
    • 月費永不停止

    ✅ 我的方案:Claude Code + 規劃優先

    這個方案的核心理念:

    少 prompt(提示詞)→ 多文檔(規劃文件)→ 穩定開發

    運作邏輯

    傳統 Claude Code 用法:
    user: "加個登入功能"
    claude: "好,現在實施..."
    結果: 80% 對,20% 要改,改 3 次才對 ❌
    
    ===
    
    我的方法:
    user: 先寫設計文檔
       ├─ 登入流程圖
       ├─ 數據模型
       ├─ API 規格
       ├─ 錯誤情況
       └─ 安全檢查點
    
    claude: 讀完設計文檔
           → 一次到位 ✅
           → 90%+ 對
           → 最多微調 1-2 次

    為什麼效果這麼好?

    1. 你花 2 小時規劃,省 8 小時改
    • Claude 少猜測 80%
    • 實施清晰,bug 少
    1. 從此不重複決策
    • 設計一次,可重用
    • 新人接手也看得懂
    • 6 個月後改需求,有據可查
    1. Claude 更聰明
    • 它讀懂需求精細度 ↑ → 代碼品質 ↑
    • 不再「我猜你想要」
    • 自動找出邊界情況
    1. 自動化率提高
    • Hooks 自動 lint + format
    • 測試自動跑
    • 手動操作 ↓ 50%

    一人公司的黃金公式

    CLAUDE.md (5 min 寫)
      ↓
    設計文檔 (20-30 min 寫)
      ↓
    Claude Code 實施 (1-2 hours)
      ↓
    驗收 (15 min)
      ↓
    上線 (5 min)
      ↓
    自動化監控 (0 min,Hook 搞定)
    
    總時間:2-3 hours per feature ✅

    vs

    傳統方式:
    實施 → 測試 → 修改 1 → 測試 → 修改 2 → ...
    總時間:8-16 hours per feature ❌

    這個系列會教你什麼

    📖 第 1 篇 (本篇):為什麼規劃優先

    📖 第 2 篇:建立你的「編碼憲法」

    • CLAUDE.md 怎麼寫
    • 一次性規則,永遠生效
    • 讓 Claude 記住你的風格和要求

    你寫一次:「所有 API 返回都要帶 error_code」
    之後:Claude 永遠自動加這個,不用提醒

    📖 第 3 篇:設計文檔模板

    • Invoice 系統設計文檔怎麼寫
    • 從業務需求到技術規格
    • 讓 Claude 能一次實施正確

    📖 第 4 篇:自動化 Hook

    • 自動格式化、自動測試、自動檢查
    • 從此沒有手動 lint 這種事
    • 節省每週 5-10 小時

    📖 第 5 篇:實戰案例 – Taiwan Invoice System

    • 完整的一人公司開發流程
    • 從零到部署,3 天內完成
    • 如何用 Plan Mode 少 prompt

    一人公司老闆應該知道的三件事

    事實 1:你的時間不是資源,是稀缺商品

    你的時間 ≠ 開發者的時間

    你 1 小時的時間價值 = 你一個小時的營收機會成本

    所以

    • ✅ 花 30 分鐘規劃(你做)
    • ❌ 不要花 8 小時反覆改(讓 Claude 做,正確率 90%+)

    事實 2:文檔是最好的投資

    一個一人公司最怕什麼?

    • 不是 bug
    • 不是需求改變
    • 是「我忘了為什麼這樣設計」

    3 個月後

    • ❌ 沒文檔:「為什麼税務計算這樣寫?誰知道… 反正改不了」
    • ✅ 有文檔:「哦,這是因為台灣稅法 §96,我記得」

    事實 3:規劃優先,自動化第二

    很多人想著「如何自動化我的工作」

    正確的順序是「如何規劃好我的工作」才能「自動化」

    類比

    • 製造業 1.0:手工 → 效率低
    • 製造業 2.0:流程化 + 自動化 → 效率高
    • 開發 1.0:隨意寫代碼 + 手動測試 → 慢
    • 開發 2.0:規劃 + 自動化 → 快

    你應該期待什麼結果

    Week 1(學習)

    • 讀完這個系列(2 小時)
    • 寫出你的 CLAUDE.md(1 小時)
    • 設置 Hook(30 分鐘)
    • 第一個功能用新方法(3 小時)

    Week 2-4(習慣)

    • 每個功能都有設計文檔(快速發現問題)
    • 自動化 lint/test(省去手動操作)
    • 改需求不再是「全部改」,而是「增量改」

    Month 2(收穫)

    • 開發速度 ×3
    • bug 率 ÷2
    • 個人壓力 ÷3
    • 有時間優化產品

    Month 3+(習慣)

    • 文檔成為資產(不是負擔)
    • 新功能開發時間可預測(不再「要多久」→「大概 2 天」)
    • 可以考慮擴招(有文檔,新人能快速上手)

    下一篇預告

    第 2 篇:CLAUDE.md – 你的編碼憲法

    我會教你怎樣用一份文檔,讓 Claude 永遠記住:

    • 你的代碼風格
    • 你的業務邏輯
    • 你的命名約定
    • 你的安全要求

    寫一次,永遠受用。


    現在就開始

    問自己三個問題:

    1. 我上週浪費了多少時間在「改 bug」上?
    • < 3 小時:你還不夠痛苦,可以緩一緩
    • 3-10 小時:現在該改了
    • \> 10 小時:你應該昨天就開始
    1. 我有沒有寫過設計文檔?
    • 沒有:你損失了 80% 的時間
    • 有,但是手寫的:轉換成模板化的吧
    1. 我願不願意花 2 小時規劃,換 8 小時省下來?
    • 願意:到第 2 篇見!
    • 不願意:你可能不是一人公司老闆該有的效率思維

    系列文快速導航

    篇章標題重點你需要做什麼
    1️⃣為什麼規劃優先理解痛點讀完,認同理念
    2️⃣CLAUDE.md 編碼憲法寫一份文檔複製模板,填入自己的規則
    3️⃣設計文檔模板學會規劃為下個功能寫設計文檔
    4️⃣Hook 自動化節省時間設置 3-5 個 Hook
    5️⃣實戰:Taiwan Invoice看完整案例套用到自己的項目

    核心觀念一句話

    「規劃優先,少 prompt;文檔優先,少反覆。」

    多花 30% 規劃時間,省 70% 改動時間。

    一人公司的職責太多,不能靠「反覆試錯」活著。

    下一篇見!👋

  • 舊系統不死,AI 讓它進化:不重寫也能持續成長

    重點摘要

    • 舊系統不是問題,缺乏 AI 輔助才是問題——AI 讓「不重寫、持續演進」成為可行選項
    • 歷史證明:超過 80% 的「重寫計畫」以失敗或兩個系統都要維護收場
    • AI 最確定的價值是讀懂舊代碼、補文件、補測試,讓團隊敢繼續開發
    • 「AI 能不能讓大規模翻新變安全」——這是尚待驗證的命題,不宜過度樂觀

    你的公司有一套跑了十年的系統。它能動,它撐起了整個業務,但沒有人敢碰它。文件不齊、邏輯散落在各處、原始開發者早就離職了。每次有人提議「重寫」,討論就會陷入沉默——因為大家心裡都知道,這條路走過很多次,沒幾次是成功的。

    AI 的出現,讓這個困境有了新的解法。但不是你想像中的那種解法。

    為什麼重寫這麼難?歷史給了殘酷的答案

    軟體界有個著名的現象叫做 Second System Effect(第二系統效應),由《人月神話》作者 Fred Brooks 提出:工程師在重寫時,往往會把所有「本來想做卻沒做」的功能都塞進去,結果新系統比舊系統更複雜、更難維護。

    但更根本的問題是:舊系統裡有隱藏的業務邏輯,它們沒有寫在文件裡,只存在於代碼的行為中——某個奇怪的判斷式、某個例外處理、某個只在特定情境才觸發的路徑。這些邏輯,是十年來無數個「為什麼這樣做?」的答案。

    重寫計畫的典型失敗模式

    • 兩個系統並行期拉太長:新舊系統同時維護,工程師精力分散,bug 在兩邊都出現
    • 上線那天發現漏了邏輯:舊系統某個角落的行為,新系統根本沒有對應
    • 商業壓力中斷重寫:計畫進行到一半,業務需求改變,只能把新舊系統黏在一起
    • 重寫後反而更慢:新架構雖然「漂亮」,但少了十年累積的效能優化細節

    這不是悲觀,這是現實。重寫不是不可能,但它的成功率遠低於大多數人的預期。在 AI 出現之前,面對舊系統,企業只有兩條路:硬撐,或者賭一把。

    AI 帶來了第三條路:輔助成長

    AI 最確定的價值,不是幫你重寫系統,而是讓「不重寫、持續演進」這件事變得可持續。

    過去,舊系統的最大問題不是代碼本身,而是「沒有人理解它」。原始開發者離職了,知識沒有傳承;文件過時了,沒有人有時間更新;要改一個功能,必須花三天理解上下文,才敢動一行代碼。

    AI 改變了這個方程式。

    AI 能為舊系統做什麼?

    挑戰 過去的困境 AI 輔助後
    理解舊代碼 要花幾天甚至幾週閱讀 AI 幾分鐘內產出架構圖和流程說明
    補充文件 沒時間寫、寫了也快過時 AI 根據現有代碼生成,每次修改後更新
    補充測試 舊系統通常 0 測試,改動沒有安全網 AI 針對現有行為補寫測試,改動有保護
    修復 bug 怕改了 A 壞了 B,只敢最小化改動 AI 追蹤影響範圍,降低連帶破壞風險
    新人上手 要跟著老人學幾個月才敢動 AI 隨時解釋任何一段代碼的邏輯和背景
    新增功能 不知道該插在哪裡,怕破壞現有邏輯 AI 建議最小侵入式的擴充點

    實際做法:AI 如何輔助舊系統成長

    第一步:讓 AI 讀懂系統,產出活的文件

    不要急著改代碼。第一件事是讓 AI 理解現有系統,然後把理解結果固化成文件。

    # 讓 AI 讀整個 codebase,產出架構說明
    # 在 Claude Code 中,直接描述你的需求:
    
    「請閱讀這個專案的所有代碼,並產出:
    1. 系統架構圖(模組與模組之間的關係)
    2. 核心業務流程說明(從使用者角度描述主要流程)
    3. 高風險區域列表(邏輯最複雜、最不敢動的地方)
    4. 技術債清單(有哪些地方明顯需要改善)」

    這份文件不是給外部人看的,是給你的團隊每天使用的工作手冊。它會隨著系統改動而更新——這一點很重要,因為過去文件之所以沒用,是因為沒有人有時間維護它。AI 讓維護文件的成本降低了 90%。

    第二步:為現有行為補測試,建立安全網

    舊系統的問題不是「代碼爛」,而是「沒有測試保護」。任何一個修改都是在沒有安全網的情況下走鋼絲。

    AI 可以閱讀現有代碼,理解它的行為,然後為這些行為寫測試——即使這些行為從來沒有文件。

    # 範例:讓 AI 為現有函式補測試
    「這個函式 calculateDiscount() 已經跑了八年,
    請分析它的所有分支條件,為每個分支寫一個測試案例,
    包括正常情況、邊界值和異常情況。
    不要改動現有邏輯,只補充測試。」

    有了測試,團隊才敢改動。改動有安全網,系統才能持續演進而不是不斷累積技術債。

    第三步:最小侵入式地新增功能

    新增功能不等於重構整個模組。AI 擅長找到「最小侵入式的擴充點」——在不動現有邏輯的前提下,把新功能插進去。

    這個原則來自 Open/Closed Principle(開放封閉原則):對擴充開放,對修改封閉。即使舊系統沒有遵循這個原則,AI 也可以建議如何在外圍包一層,讓新功能不影響舊邏輯。

    第四步:漸進式現代化,而非大爆炸式重寫

    如果真的有部分需要改善,AI 輔助的方式是:一次只動一個模組,改完之後讓它穩定跑一段時間,確認沒有問題再動下一個。

    這不是「重寫」,這是「漸進式現代化」。兩者的關鍵差異:

    重寫 漸進式現代化
    範圍 全部 一次一個模組
    風險 集中在上線日 分散,每步都可以回滾
    業務中斷 長期並行維護兩個系統 系統持續運作,局部更新
    AI 的角色 「幫我重新實作這一切」 「幫我安全地改善這一塊」

    那麼,「重寫」這條路呢?

    這是一個需要誠實面對的問題。

    過去的答案很清楚:重寫計畫成功率低,風險高,通常不是好選擇。大多數成功的案例,仔細看都是「漸進式替換」而不是「一次性重寫」。

    AI 會改變這個答案嗎?

    這是一個尚待驗證的命題。AI 確實讓理解舊系統更容易,讓知識遷移成本降低,理論上應該讓重寫的準備工作做得更完整。但「做得更完整的準備」不等於「執行時不會出問題」。隱藏的業務邏輯、時序問題、效能細節——這些在代碼裡只有在跑了幾百萬筆資料之後才會浮現。

    更誠實的說法是:

    • AI 讓「輔助成長」這條路變得可行——這是現在就可以驗證的事
    • AI 讓「重寫」變得更安全——這是有可能的,但還需要更多實際案例來驗證
    • AI 能取代「漸進式替換」的謹慎原則——不太可能,這個原則的價值在於限制風險暴露,而不是技術能力

    所以,如果有人告訴你「有了 AI,重寫就不危險了」——保持懷疑。如果有人告訴你「AI 讓你不需要擔心舊系統的技術債了」——同樣保持懷疑。

    如何判斷你的系統需要什麼?

    面對舊系統,用這個框架來判斷方向:

    優先考慮 AI 輔助成長,如果:

    • 系統仍然在提供商業價值,只是難以維護
    • 核心業務邏輯複雜,沒有人完整理解
    • 團隊規模小,無法支撐兩個系統並行
    • 業務需求變化頻繁,不能停下來等重寫完成

    可以考慮漸進式替換(不是重寫),如果:

    • 某個模組已經明顯成為瓶頸,且邊界清晰
    • 有足夠的測試保護現有行為
    • 可以部署影子流量,讓新舊模組並行驗證
    • 有明確的回滾機制

    謹慎考慮大規模重寫,只有當:

    • 現有系統在技術上已經無法繼續擴充(不只是難,而是真的不可能)
    • 有足夠的資源支撐至少 18 個月的並行期
    • 有完整的行為規格文件(或 AI 幫你產出的等效文件)
    • 組織願意接受在過渡期期間功能停滯

    結語:舊系統不是問題,缺乏支援才是

    回到最初的問題:那套跑了十年的系統,它的問題不是年齡,而是孤立。沒有人理解它,沒有測試保護它,沒有文件說明它,改動它需要承擔巨大的個人風險。

    AI 能做的,是讓這個系統不再孤立。它可以成為每個工程師的「老前輩」——隨時解釋任何一段邏輯,隨時分析改動的影響,隨時生成測試保護現有行為。

    這不是重寫的故事,這是陪伴成長的故事。

    至於重寫——如果未來真的需要,AI 會讓你準備得更充分。但那是另一個故事,而且它的結局還沒有寫完。

  • 10 年舊系統如何安全導入 AI 開發:Strangler Fig 遷移方法論

    重點摘要

    • 10 年的舊系統能跑就是最有價值的資產,不要試圖先修好再遷移
    • 核心方法論:Strangler Fig 模式 — 新軌道在旁邊長起來,舊系統自然退場
    • AI 第一件事不是寫 code,而是讀懂 10 年的系統邏輯,再動手
    • 四個階段:快照現況 → 建平行新軌 → 一次搬一個服務 → 封存舊系統

    你的系統跑了 10 年。它很髒、沒有文件、CI/CD 靠手動、密碼可能在 .env 裡或者在某個工程師的腦袋裡。但它能跑,而且在服務真實的用戶。

    現在你想引入 AI 輔助開發,想現代化整個工作流。問題來了:要從哪裡開始? 要先把舊的修乾淨,還是直接用新方法?

    錯誤的答案是:「先把舊的修好。」正確的答案是:不要動正在跑的東西,在旁邊建一條新軌道。

    為什麼「先修好再用新方法」行不通?

    這個直覺很自然,但在實際工程上幾乎都會失敗,原因有三:

    1. 無法停止開發等你修 — 業務不會暫停,新需求還是會進來,你邊修邊開發,舊問題永遠追不完
    2. 「修好」的定義會不斷移動 — 一開始說只要加 .gitignore,結果發現歷史有密碼,要 filter-repo,然後發現測試覆蓋率是零…沒有終點
    3. 你在修一個不完全理解的系統 — 10 年的系統有太多隱性知識,修的過程中很容易把「能跑的」改成「不能跑的」

    工程界有一個著名的模式專門解決這個問題,叫做 Strangler Fig(絞殺榕)模式

    Strangler Fig 模式:不砍舊樹,讓新藤蔓長過去

    絞殺榕是一種熱帶植物。它的種子落在老樹上,慢慢向下長出根,包住舊樹,最後舊樹自然退場,絞殺榕站立在原位。整個過程中,舊樹從未停止「提供支撐」,直到新系統完全就緒。

    應用到 DevOps 遷移:

    ❌ 錯誤思維:
    舊系統(停機)→ 修好 → 接新流程 → 恢復服務
    
    ✅ 正確思維(Strangler Fig):
    舊系統(持續運行,不動)
        ↓
    新軌道在旁邊建立(不影響舊系統)
        ↓
    一次搬一個服務,驗證後切換流量
        ↓
    所有服務搬完,舊系統自然退場

    關鍵洞察:能跑的系統是你最有價值的資產,不是問題的來源。遷移的目標是「讓它繼續跑,同時讓新系統在旁邊成長」,不是「讓它停下來修好再說」。

    四個階段的完整遷移方法論

    階段一:快照現況(不動任何東西)

    第一步不是改 code,不是設定 CI/CD,而是把「現在是怎麼跑起來的」完整記錄下來。這份快照是整個遷移過程的地基。

    為什麼要快照?因為在 10 年的系統裡,repo 裡的 .env 可能是舊的,文件可能是錯的,只有正在跑的進程才是真相:

    # 從正在跑的容器抽出真實的環境變數
    docker inspect <container_name> \
      --format='{{range .Config.Env}}{{println .}}{{end}}' \
      > /tmp/real-env-snapshot.txt
    
    # 或直接讀進程的環境變數
    cat /proc/$(pgrep java)/environ | tr '\0' '\n' | grep -E "DB_|API_|SECRET_"
    
    # K8s 環境
    kubectl get pods -n production -o name | while read pod; do
      echo "=== $pod ==="
      kubectl exec $pod -n production -- env 2>/dev/null
    done > /tmp/real-k8s-env-snapshot.txt

    同時盤點服務清單和依賴關係:

    # 有哪些服務在跑
    docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
    
    # 服務之間怎麼通訊
    docker network inspect bridge
    
    # 對外開放哪些 port
    ss -tlnp | grep LISTEN

    這個階段的產出是一份真實架構圖和一份真實密碼清單(妥善保管,不進任何 repo)。

    階段二:AI 讀懂你的系統

    這是大多數人忽略的步驟,也是決定 AI 協作能否成功的關鍵。

    AI 第一件事不是寫 code。

    在 AI 動手之前,它需要讀你 10 年的系統。這個過程大概需要幾天,但會產出你可能從未有過的東西:

    AI 讀完後的產出 為什麼重要
    系統架構圖 你們可能自己也沒有,新人上手和遷移規劃的基礎
    模組依賴關係 知道改哪個地方會影響哪些服務
    高風險區域標記 「這段 code 10 個人改過,有 3 個已知 bug 的修復」
    技術債清單(按影響排序) 知道先解決什麼,不是看到髒的就改
    新人上手文件 從 code 反推出來,不需要老工程師口傳

    這個階段同樣不動任何 code。AI 只是閱讀和理解。等到真正開始寫新功能時,AI 已經知道你的系統慣例是什麼、有哪些地雷不能踩。

    階段三:建立平行的新軌道

    新建一個乾淨的 repo,在旁邊建立完整的現代化工作流,舊 repo 繼續照舊運作

    舊 GitLab repo(繼續跑,不動)
         │
         │  正在服務用戶的系統
         │
    新 repo(乾淨起點)
         │
         ├─ 正確的 .gitignore(.env 全部排除)
         ├─ CI/CD pipeline(gitleaks + build + sign)
         ├─ K8s Secrets(從快照搬進來)
         └─ Branch Protection Rules

    把真實密碼從快照搬到 K8s Secrets(不是從舊 repo 搬,是從正在跑的進程抽出來):

    # 從快照建立 K8s Secrets
    kubectl create secret generic app-prod-creds \
      --from-literal=DB_PASSWORD="$(grep DB_PASSWORD /tmp/real-env-snapshot.txt | cut -d= -f2)" \
      --from-literal=SHOPEE_KEY="$(grep SHOPEE_KEY /tmp/real-env-snapshot.txt | cut -d= -f2)" \
      -n production
    
    # 建立完成後,安全刪除快照
    shred -u /tmp/real-env-snapshot.txt

    階段四:一次搬一個服務

    這是遷移的主體。原則是:每次只搬一個服務,驗證通過才搬下一個

    服務搬移的優先順序建議:

    優先順序 選擇原則 理由
    第一批 流量最小的非核心服務 風險最低,可以放心試錯
    第二批 獨立性高、依賴少的服務 不會牽一髮動全身
    最後 核心業務邏輯(訂單、付款) 等前幾批證明新流程可靠後再動

    每個服務的搬移步驟:

    1. AI 在新 repo 重寫該服務的乾淨版本(理解舊 code 後重寫,不是 copy paste)
    2. Jenkins 構建新鏡像,部署到 Staging
    3. 用影子流量驗證新舊行為一致(新舊同時收請求,比對回應)
    4. 確認無誤,切換這個服務的流量到新系統
    5. 觀察 24-48 小時
    6. 舊服務下線

    影子流量驗證(用 nginx 實現,不影響用戶):

    # nginx 配置:新舊同時收請求,比對行為
    location /api/orders {
        mirror /mirror-new;       # 複製請求到新系統
        proxy_pass http://old-api; # 用戶實際收到舊系統的回應
    }
    
    location = /mirror-new {
        internal;
        proxy_pass http://new-api;  # 新系統的回應只寫 log,不回傳用戶
    }

    AI 在整個過程中的實際角色

    理解了遷移方法論後,AI 在這個過程中的職責非常清楚:

    AI 能做(且做得很好):
      ✅ 讀 10 年的舊 code,產出架構文件
      ✅ 識別技術債,按優先順序排列
      ✅ 用「符合你們系統慣例」的方式寫新功能
      ✅ 在 feature branch 開發,開 MR,等你 review
      ✅ 補測試(補現有 code 的測試覆蓋率)
      ✅ Debug(分析 log,找根因)
      ✅ 在新服務和舊系統的接縫處寫 adapter
    
    AI 不能做(也不應該做):
      ❌ 直接 push 到 main 或 production branch
      ❌ 決定什麼時候切換流量
      ❌ 接觸任何密碼或 production secrets
      ❌ 在沒有充分理解舊系統的情況下動手

    一個典型的 AI 協作對話長這樣:

    你:「我要加一個批次出貨的功能」
    
    AI:「讓我先讀一下現有的出貨相關 code...
         我看到 OrderService.java 的 shipOrder()
         和 ShipmentRepository,還有一個舊的
         BatchJob 在 legacy/ 目錄。
    
         注意:legacy/BatchJob 有個已知的
         transaction 問題(commit a3f2c1b 的修復紀錄),
         新功能要避開。
    
         建議的實作方向是...你確認嗎?」
    
    你:「方向對,去做吧」
    
    AI:「好,我開 feature/batch-shipment,
         預計改動:
         - 新增 BatchShipmentService(核心邏輯)
         - 修改 OrderController(加 API endpoint)
         - 不動 legacy/ 目錄
         
         完成後開 MR 給你 review」

    AI 因為讀過 10 年的系統,不會做出「不符合你們慣例」的 code,也不會踩進已知的地雷。

    最常遇到的三個現實問題

    問題一:「不知道哪個 .env 是現在真正在用的」

    10 年的系統通常有多個 .env 版本,有的是舊的,有的是工程師自己改過的。以正在跑的進程為準,不要相信文件

    # 找到 Java 進程真正使用的環境變數
    cat /proc/$(pgrep java)/environ | tr '\0' '\n' | grep -E "DB_|API_|SECRET_"
    
    # 不要用
    cat .env  # 這可能是 6 個月前的版本

    問題二:「團隊還在開發,不能凍結」

    不需要凍結。舊 repo 繼續用,新 repo 並行開發。重要的 bugfix 透過 cherry-pick 同步:

    舊 repo: feature → dev → main(繼續照舊)
                  │
                  └─ cherry-pick 重要修復
                          │
    新 repo:              └─ feature → dev → main(新流程)

    問題三:「歷史 commit 有密碼怎麼辦」

    分兩步處理:

    1. 立即輪換所有洩露的密碼 — 因為 GitHub/GitLab 可能已有快取,這一步不能等
    2. 舊 repo 等到遷移完成後再 archive — 不需要現在重寫歷史,新 repo 一開始就是乾淨的

    注意:很多人以為 git rm --cached .env 就安全了,但舊 commit 裡的內容仍然可以被 git show <old-commit>:.env 讀出。唯一的技術修復是 git filter-repo,但遷移方法論讓你可以跳過這一步——因為所有新開發都在新的乾淨 repo 上進行。

    時間軸規劃

    時間 工作 風險
    第 1 週 快照現況、輪換外部 API Key、AI 開始讀系統 零(不動任何 code)
    第 2-4 週 建新 repo、CI/CD pipeline、K8s Secrets、第一個服務搬過去 低(影子流量驗證)
    第 1-3 個月 逐服務遷移,每個驗證 24-48 小時後才繼續 中(每次只影響一個服務)
    遷移完成後 舊 repo archive、完整安全強化(RBAC、Audit Log、鏡像簽名) 低(新系統已穩定)

    決策樹:你現在該從哪裡開始

    你的系統現在能跑嗎?
      │
      ├─ 能跑 → 用 Strangler Fig 模式(本文的方法)
      │           │
      │           ├─ 步驟 1:快照現況(本週就做)
      │           ├─ 步驟 2:AI 讀系統(同步進行)
      │           ├─ 步驟 3:建新軌道(第 2-4 週)
      │           └─ 步驟 4:逐服務遷移(之後)
      │
      └─ 不能跑 → 先讓它跑起來,再回到這裡

    這套方法論的本質

    Strangler Fig 模式應用在 AI 輔助開發遷移上,核心洞察只有一個:

    「10 年的技術債是過去的決策的結果,你無法在不破壞現有價值的情況下一次消除它。但你可以選擇:從今天開始,所有新的工作都用正確的方式做。」

    舊系統是你團隊的集體記憶,AI 有能力閱讀並理解這些記憶,然後用現代化的方式繼續往前走。不需要推倒重建,也不需要凍結開發去修舊債——只需要一條平行的新軌道,和耐心地一次移動一個服務。

    想了解新軌道的 CI/CD 具體設計,可以參考上一篇文章:AI 輔助開發 CI/CD 工作流:Jenkins、K8s、ISO 27001 完整設計

  • AI 輔助開發 CI/CD 工作流:Jenkins、K8s、ISO 27001 完整設計

    重點摘要

    • AI 只負責寫 code、提 PR,不碰版本決策和 Production 部署,人類保留最終控制權
    • 透過 Git tag 觸發 Jenkins,Staging 全自動部署、Production 手動 helm 執行,兩階段驗證才上線
    • 敏感資訊三層隔離:.gitignore → K8s Secrets → etcd 加密,密碼永遠不進 repo
    • 補齊 RBAC、Audit Log、鏡像簽名、Secrets Scan 四大安全缺口,達到 ISO 27001 合規

    AI 輔助開發越來越普遍,但大多數團隊面臨同一個問題:AI 寫的 code 要怎麼安全地上線? 誰決定部署時機?密碼怎麼管?如果 AI 出錯了,有什麼防護網?

    本篇文章完整說明 ONEEC OMS 系統實際採用的 AI 協作工作流設計,包含完整的 User Story、Jenkins Pipeline 架構、三環境部署策略,以及通過安全審查後補齊的 RBAC、Audit Log、鏡像簽名等安全強化配置。

    核心設計理念:人類掌控節奏,AI 加速執行

    這套工作流的核心原則只有一句話:AI 是高效能的執行者,不是決策者。具體體現在以下四點:

    • AI 負責:寫 code、建 Dockerfile、提 PR、提供 Jenkins script 和 Helm chart
    • 用戶負責:code review、創建 git tag(決定版本和部署時機)、手動 helm 部署到 Production
    • 運維負責:管理 K8s Secrets、設定 Jenkins credentials、維護集群
    • 敏感資訊:密碼、API Key、SSL 憑證永遠不進入 Git repo

    完整 User Story:從需求到上線的 10 個步驟

    以下用一個真實場景說明整個流程:場景:優化訂單 API 的查詢效能

    Step 1:AI 開發(feature branch)

    AI 從 dev 分支切出 feature branch,完成開發後推送 PR:

    # AI 執行
    git checkout dev && git pull origin dev
    git checkout -b feature/order-api-optimize
    
    # 編寫程式碼...
    
    # 本地驗證
    docker-compose up -d
    curl http://localhost:8080/api/orders?status=pending
    # ✅ 回傳正確,效能提升 30%
    
    # 提交並推送
    git add . && git commit -m "feat(order-api): optimize query performance"
    git push origin feature/order-api-optimize
    # 建立 PR → dev

    Step 2:用戶 Code Review & Merge

    用戶在 GitHub UI 審查 PR:確認邏輯正確、有測試覆蓋、無敏感資訊後 approve 並 merge 到 dev。此時沒有任何自動化觸發,代碼靜靜等待部署決策。

    Step 3:用戶創建 Staging Tag → Jenkins 自動觸發

    用戶決定要部署到測試環境時,創建一個 staging-v* tag:

    # 用戶執行
    git tag staging-v1.0.1
    git push origin staging-v1.0.1
    
    # GitHub Webhook → Jenkins 自動執行:
    # ├─ Secrets 掃描(gitleaks)
    # ├─ docker build(所有 pods)
    # ├─ cosign 簽名鏡像
    # ├─ docker push to registry
    # ├─ helm deploy to Staging K8s(使用 values-staging.yaml)
    # └─ 通知用戶:Staging v1.0.1 is live

    Step 4:用戶在 Staging 驗證

    kubectl get pods -n staging
    curl https://staging-api.example.com/api/orders?status=pending
    # ✅ 功能正常,效能優化生效
    # ✅ 錯誤率 0%
    # ✅ 回應時間 < 100ms

    Step 5:用戶創建 Production Tag → Jenkins 構建正式鏡像

    # 用戶執行(確認 Staging 無誤後)
    git tag v1.0.1
    git push origin v1.0.1
    
    # Jenkins 執行:
    # ├─ Secrets 掃描
    # ├─ docker build(所有 pods,tag 改為 v1.0.1)
    # ├─ cosign 簽名鏡像
    # ├─ docker push to registry
    # ├─ 生成 Helm values(不含敏感資訊)
    # └─ 通知用戶:Images ready, run helm command

    Step 6:用戶手動部署到 Production

    Production 部署是整個流程中唯一純手動的步驟,這是刻意設計的——確保每一次正式上線都有人類判斷:

    # 用戶在本機執行
    helm upgrade --install order-api \
      /path/to/your/prod-configs/order-api/values-prod.yaml \
      --set image.tag=v1.0.1 \
      -n production
    
    # K8s 自動從 Secrets 注入密碼、API Key
    # Kyverno 自動驗證鏡像簽名(未簽名直接拒絕)
    # Deployment 完成 ✅

    Step 7:監控確認上線成功

    kubectl get pods -n production
    curl https://api.example.com/api/orders?status=pending
    # ✅ 正式環境驗證通過,上線成功

    三個部署環境的定義與分工

    環境 用途 部署方式 配置來源 觸發者
    Dev 本地開發驗證 docker-compose up .env.dev AI(開發時)
    Staging 測試環境(K8s) Jenkins 自動部署 values-staging.yaml(在 repo) 用戶(tag 觸發)
    Production 正式環境(K8s) 手動 helm 部署 values-prod.yaml(用戶維護)+ K8s Secrets 用戶(手動執行)

    Jenkins Pipeline 完整架構

    Jenkins Pipeline 由 GitHub Webhook(tag push)觸發,整個流程分為 6 個 Stage:

    Stage 0:Secrets 掃描(安全門控)

    這是整個 Pipeline 的第一道防線,也是最重要的安全門控。使用 gitleaks 掃描 repo 中是否含有密碼、API Key 等敏感資訊,發現即中止構建並通知安全告警

    stage('Secrets Scan') {
        steps {
            sh '''
                gitleaks detect \
                  --source . \
                  --config .gitleaks.toml \
                  --exit-code 1 \
                  --report-format json \
                  --report-path gitleaks-report.json
            '''
        }
        post {
            failure {
                sh 'sh scripts/notify-security-alert.sh ${TAG_NAME} gitleaks-report.json'
                error('❌ Secrets 掃描發現敏感資訊,構建中止!')
            }
        }
    }

    Stage 1:Tag 偵測(決定部署目標)

    根據 tag 名稱判斷本次構建的部署目標:

    stage('Detect Tag') {
        steps {
            script {
                if (env.TAG_NAME =~ /^staging-v.*/) {
                    env.DEPLOYMENT_ENV = 'staging'
                } else if (env.TAG_NAME =~ /^v.*/) {
                    env.DEPLOYMENT_ENV = 'production'
                } else {
                    error("❌ 未知 tag 格式: ${env.TAG_NAME}")
                }
            }
        }
    }

    Stage 2:Build Images

    構建所有 Pod 的 Docker 鏡像。鏡像本身不含任何配置、密碼、API Key,這是配置與代碼分離的核心原則:

    #!/bin/bash
    # scripts/build-docker.sh
    TAG=$1
    
    docker build -t registry.example.com/order-api:${TAG} ./simpleec-api
    docker build -t registry.example.com/user-app:${TAG} ./user-app
    docker build -t registry.example.com/channel-job:${TAG} ./simpleec-channel-job
    # ... 所有 pods

    Stage 3:Sign Images(供應鏈安全)

    使用 cosign 為每個鏡像簽名,確保 Production 只能部署來自 Jenkins 的受信任鏡像:

    stage('Sign Images') {
        steps {
            withCredentials([file(credentialsId: 'cosign-private-key', variable: 'COSIGN_KEY')]) {
                sh 'sh scripts/sign-docker.sh ${TAG_NAME} ${COSIGN_KEY}'
            }
        }
    }
    
    # scripts/sign-docker.sh
    for IMAGE in "${IMAGES[@]}"; do
        cosign sign --key "${COSIGN_KEY}" \
          --tlog-upload=false \
          "${IMAGE}"
    done

    Stage 4:Push Images

    推送到 Docker Registry。Registry 啟用 Immutable Tags,同一個 tag 無法被覆蓋,確保版本不可篡改:

    stage('Push Images') {
        steps {
            withCredentials([usernamePassword(
                credentialsId: 'docker-registry-creds',
                usernameVariable: 'REGISTRY_USER',
                passwordVariable: 'REGISTRY_PASS'
            )]) {
                sh 'sh scripts/push-docker.sh ${TAG_NAME}'
            }
        }
    }

    Stage 5a(Staging):自動部署到 Staging K8s

    stage('Deploy to Staging') {
        when { expression { env.DEPLOYMENT_ENV == 'staging' } }
        steps {
            withCredentials([file(credentialsId: 'kubeconfig-staging', variable: 'KUBECONFIG')]) {
                sh '''
                    helm upgrade --install order-api ./k8s/helm/order-api \
                      --values ./k8s/helm/order-api/values-staging.yaml \
                      --set image.tag=${TAG_NAME} \
                      -n staging
                '''
            }
        }
    }

    Stage 5b(Production):生成 Helm Values,通知用戶手動部署

    對於 Production tag,Jenkins 不自動部署,而是生成配置檔並通知用戶手動執行:

    stage('Generate Helm Values') {
        when { expression { env.DEPLOYMENT_ENV == 'production' } }
        steps {
            sh 'sh scripts/generate-helm-values.sh ${TAG_NAME}'
            // 生成 values-v${TAG_NAME}.yaml(不含敏感資訊)
            // 通知用戶:Images ready, run helm command
        }
    }

    Helm 配置隔離:敏感資訊三層防護

    配置分為三層,層層隔離:

    第一層:values-staging.yaml(在 repo,測試配置)

    # 主機名用占位符,從 Jenkins 環境變數注入,不硬編碼內網地址
    env:
      DATABASE_HOST: "${POSTGRES_STAGING_HOST}"
      DATABASE_NAME: simpleec_test
      REDIS_HOST: "${REDIS_STAGING_HOST}"
      API_LOG_LEVEL: DEBUG

    第二層:values-prod.yaml(用戶本機維護,不進 repo)

    # 用戶的私密文件,只在本機
    env:
      DATABASE_HOST: postgres-prod.example.com
      API_LOG_LEVEL: WARN
      # ⚠️ 資料庫密碼不在這裡!從 K8s Secrets 注入
    
    envFrom:
      - secretRef:
          name: database-prod-creds  # K8s Secret(運維管理)
      - secretRef:
          name: api-keys-prod        # K8s Secret(運維管理)

    第三層:K8s Secrets + etcd 加密

    # 運維在 Production K8s 上創建
    kubectl create secret generic database-prod-creds \
      --from-literal=username=prod_user \
      --from-literal=password=<secure-password> \
      -n production
    
    # K8s 預設 Secrets 以 base64 存在 etcd(並非加密!)
    # 必須啟用 encryption at rest
    # /etc/kubernetes/encryption-config.yaml
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources: ["secrets"]
        providers:
          - aescbc:
              keys:
                - name: key1
                  secret: <base64-encoded-32-byte-key>

    安全強化:補齊四大缺口

    原始設計經過安全審查後,發現四個必須在投產前補足的缺口:

    缺口一:K8s RBAC 未定義

    三個角色各有最小權限(文件放在 k8s/rbac/):

    角色 允許操作 明確禁止
    Jenkins SA(staging) update/patch Deployments, get Pods 讀取任何 Secrets
    用戶(production) helm 部署相關資源 讀取業務 Secrets(DB 密碼、API Key)
    運維(production) Secrets 完整管理權
    # 驗證 Jenkins SA 無法讀取 Secrets(應輸出 no)
    kubectl auth can-i get secrets \
      --as=system:serviceaccount:staging:jenkins-deployer \
      -n staging

    缺口二:K8s Audit Log 未配置

    ISO 27001 A.12.4.1 要求所有敏感操作都要有日誌。以下 Audit Policy 至少記錄 Secrets 訪問和 Deployment 變更:

    # /etc/kubernetes/audit-policy.yaml
    apiVersion: audit.k8s.io/v1
    kind: Policy
    rules:
      - level: Metadata
        resources:
          - group: ""
            resources: ["secrets"]  # 所有 Secrets 訪問都記錄
    
      - level: Request
        verbs: ["create", "update", "delete", "patch"]
        resources:
          - group: "apps"
            resources: ["deployments"]
    
      - level: None
        users: ["system:kube-proxy"]
        verbs: ["watch", "list"]

    缺口三:鏡像簽名驗證(Kyverno 準入控制)

    確保集群只能部署來自 Jenkins 簽名的鏡像,防止鏡像替換攻擊:

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: verify-image-signatures
    spec:
      validationFailureAction: Enforce  # 未簽名鏡像直接拒絕
      rules:
        - name: check-image-signature
          match:
            any:
              - resources:
                  kinds: ["Pod"]
                  namespaces: ["staging", "production"]
          verifyImages:
            - imageReferences:
                - "registry.example.com/*"
              attestors:
                - count: 1
                  entries:
                    - keys:
                        publicKeys: |-
                          -----BEGIN PUBLIC KEY-----
                          # cosign.pub 內容
                          -----END PUBLIC KEY-----

    缺口四:GitHub Branch Protection 口頭約定 → 技術強制

    分支 Required Reviews CI 必須通過 Push 限制
    main 2 人 approve ✅ jenkins-build + secrets-scan 僅 team-lead
    staging 1 人 approve ✅ jenkins-build + secrets-scan 僅 team-lead
    dev 1 人 approve 必須透過 PR(AI 不能直接 push)

    Git 分支策略與 Tag 命名規範

    整個工作流的分支拓撲如下:

    main                     # Production 對應,受嚴格保護
     └─ tag: v1.0.0, v1.0.1  # 觸發 Jenkins 構建 Production 鏡像
    
    staging                  # 測試環境,中度保護
     └─ tag: staging-v1.0.0  # 觸發 Jenkins 自動部署到 Staging K8s
    
    dev                      # 開發積累,AI 透過 PR 提交
     └─ 來源:feature/* 合入
    
    feature/*                # AI 的工作分支(每個功能一個)
     ├─ feature/user-auth
     ├─ feature/order-api
     └─ feature/channel-job-momo

    敏感資訊完整隔離架構

    存放位置 可以存什麼 絕對不能存什麼 管理者
    Git Repository 代碼、Dockerfile、values-staging.yaml、Helm chart 模板 密碼、API Key、SSL 憑證、values-prod.yaml AI + 用戶
    Docker Registry 不含配置的乾淨鏡像(cosign 簽名) 任何敏感資訊 Jenkins(push)
    K8s Secrets(etcd 加密) database-prod-creds、api-keys-prod、SSL 憑證 運維
    Jenkins Credentials GitHub token、Registry credentials、cosign key、kubeconfig 運維

    回滾策略

    Staging 環境回滾

    # 快速回滾到上一個版本
    helm rollback order-api 0 -n staging
    
    # 或指定版本
    helm upgrade order-api ./k8s/helm/order-api \
      --values ./k8s/helm/order-api/values-staging.yaml \
      --set image.tag=staging-v1.0.0 \
      -n staging

    Production 環境回滾

    # 查看部署歷史
    helm history order-api -n production
    
    # 回滾到上一個版本
    helm rollback order-api 0 -n production
    
    # 所有 tag 在 Git 可追溯
    git log --oneline --all | grep "v1.0"

    投產前安全檢查清單

    在正式上線前,以下所有項目必須確認通過:

    代碼倉庫安全

    • ✅ .gitignore 包含 .env, .env.dev, **/values-prod.yaml
    • ✅ repo 根目錄存在 .gitleaks.toml 配置文件
    • ✅ pre-commit hook 已安裝
    • ✅ git log –all — ‘*.env’ 確認歷史中無敏感文件

    Jenkins Pipeline

    • ✅ 第一個 Stage 為 Secrets Scan(gitleaks)
    • ✅ Sign Images Stage 已配置(cosign)
    • ✅ Push Images 使用 Jenkins Credentials(非明文)
    • ✅ GitHub Webhook Secret 已配置(Jenkins + GitHub 雙端)

    K8s 訪問控制

    • ✅ k8s/rbac/ 三個 RBAC 文件已 apply
    • ✅ Jenkins SA 驗證:kubectl auth can-i get secrets … → no
    • ✅ Kyverno 已安裝,鏡像簽名驗證策略已 apply
    • ✅ etcd encryption at rest 已啟用(運維確認)

    審計和監控

    • ✅ K8s Audit Log 已配置(audit-policy.yaml)
    • ✅ Audit Log 保留策略 ≥ 90 天
    • ✅ 告警規則已配置(部署失敗、Secrets 掃描失敗)

    總結:這套工作流解決了什麼問題?

    AI 輔助開發的核心挑戰不是技術,而是信任邊界:誰能做什麼?誰為每個決定負責?這套工作流的答案很清楚:

    • AI 的邊界:寫 code、提 PR、建 Docker image — 技術執行層
    • 用戶的邊界:review 代碼、創建 tag、手動部署 Production — 決策層
    • 運維的邊界:管理 Secrets、維護集群、配置 credentials — 基礎設施層
    • 自動化的邊界:Jenkins 在 tag 觸發後執行既定腳本 — 不越界,不決策

    這種分層設計讓 AI 協作既高效又安全,每一個部署都有完整的審計軌跡,每一個敏感操作都需要人類授權。

  • Agent SDK vs 傳統 If-Else:客服系統從 500 行代碼到 10 行的蛻變

    📥 Download Jupyter Notebook

    Download complete Jupyter Notebook


    Agent SDK vs 傳統 If-Else:客服系統從 500 行代碼到 10 行的蛻變

    📋 快速摘要

    • Agent SDK 將 500+ 行 if-else 邏輯簡化為 ~10 行配置
    • 維護成本從「高」降到「低」—— 新增規則只需改提示,無需改代碼
    • 適用場景:客服回覆、內容審核、工單分類
    • 不適用場景:金融交易、爬蟲、醫療診斷(需要 100% 確定性)
    • 本文包含可運行的 Jupyter Notebook 對比演示

    爲什麽選擇 Agent SDK 而非傳統 If-Else?

    想象你在維護一個客服系統。每當新的詢問類型出現,你就得添加 5-10 行 if-elif-else 代碼。6 個月後,代碼變成 500+ 行的怪獸,充滿邊界情況處理。

    現在有一個更好的方式:用 Agent SDK 讓 AI 自動理解客戶意圖,不需要每次都改代碼。

    實例對比:傳統方式 vs Agent SDK

    ❌ 傳統方式:500+ 行代碼

    典型的 if-else 實現(簡化版本):

    def traditional_customer_service(inquiry: str) -> str:
        if "退貨" in inquiry or "return" in inquiry.lower():
            if "7天" in inquiry or "7 days" in inquiry.lower():
                return "✓ 好消息!7 天內的商品我們接受退貨。請提供訂單號和退貨原因。"
            elif "30天" in inquiry or "30 days" in inquiry.lower():
                return "⚠ 30 天內的商品需要評估狀況。請寄回商品,我們會判斷是否可退。"
            else:
                return "✗ 超過退貨期限(30天),無法退貨。詳見購買頁面。"
        # ... 還有物流、商品、預設回覆,加起來 500+ 行
        else:
            return "感謝您的詢問。請告訴我更多細節,我會盡快幫您解決。"

    問題:

    • 新增詢問類型 → 需要改代碼 → 需要測試 → 需要重新部署
    • 邊界情況處理複雜(用戶說「退貨」的 10 種不同表達方式)
    • 代碼量隨詢問類型線性增長
    • 維護成本高、Bug 多

    ✨ Agent SDK 方式:~10 行配置

    from claude_agent_sdk import query, ClaudeAgentOptions
    
    async for message in query(
        prompt="You are a customer service agent. Understand customer inquiries and help with returns, invoices, shipping, and product issues.",
        options=ClaudeAgentOptions(
            allowed_tools=["Retrieve Policy", "Query Order", "Update Status"],
            permission_mode="acceptEdits"
        )
    ):
        if isinstance(message, ResultMessage):
            print(message.result)  # AI-generated response

    優勢:

    • 新增規則 → 只需改系統提示,無需改代碼
    • 自動理解變通說法
    • 代碼量固定,不隨規則增加而增加
    • 維護成本低、Bug 少

    真實測試結果

    5 個實際客戶詢問測試:

    測試 傳統方式 Agent SDK 勝者
    回覆時間 0.03ms 150ms If-Else (快 5000x)
    代碼行數 500+ ~10 ✨ Agent SDK (簡潔 50x)
    新增規則難度 高(改代碼→測試→部署) 低(改提示→即時生效) ✨ Agent SDK
    維護成本 ✨ Agent SDK
    用戶體驗 生硬、標準化 自然、親切 ✨ Agent SDK

    何時使用 Agent SDK vs If-Else

    ✅ 適合 Agent SDK 的場景

    • 客服回覆 — 自然理解客戶意圖
    • 內容審核 — 判斷垃圾信息、仇恨言論
    • 工單分類 — 自動分類優先級
    • 推薦系統 — 推薦相關商品
    • 數據提取 — 從非結構化文本提取信息

    ❌ 不適合 Agent SDK 的場景

    • 金融交易 — 需要 100% 確定性
    • 醫療診斷 — 需要絕對準確
    • 法律判決 — 需要精確遵循規則
    • 實時系統 — AI 延遲 100-500ms
    • Web 爬蟲 — 需要精確控制

    Claude API vs Agent SDK:核心差異

    這是很多人困惑的地方。讓我用實例說明。

    場景:客服回答「我的訂單呢?」

    系統需要:

    1. 查訂單資料庫
    2. 查物流資料庫
    3. 組合信息回答客戶

    ❌ Claude API(你控制流程)

    from anthropic import Anthropic
    
    client = Anthropic()
    
    # 第 1 次調用
    response1 = client.messages.create(
        model="claude-opus-4-6",
        messages=[{
            "role": "user",
            "content": "客戶說:我 3 月 10 號買的商品,現在還沒收到"
        }]
    )
    # Claude:「我需要查一下你的訂單...」
    
    # ❌ 你必須手動查資料庫
    order = query_order_db(customer_id, "2026-03-10")
    
    # ❌ 第 2 次調用 Claude
    response2 = client.messages.create(
        model="claude-opus-4-6",
        messages=[{
            "role": "user",
            "content": f"""訂單信息:{order}
    
    請回答客戶。"""
        }]
    )
    
    # ❌ 如果客戶追問,又要第 3、4 次調用...

    你的應用程式在做決策:

    • 「我應該先查訂單」
    • 「我應該問 Claude 什麼問題」
    • 「我應該再查一次物流」

    ✅ Agent SDK(Claude 自動控制流程)

    from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
    
    # 就一次調用,Claude 自動做一切
    async for message in query(
        prompt="""你是客服系統。根據客戶問題,查詢訂單和物流信息,給出完整回答。
    
    客戶問題:「我 3 月 10 號買的商品,現在還沒收到,怎麼辦?」
    
    工具說明:
    - query_order(customer_id, date):查訂單
    - query_shipping(tracking_id):查物流
    
    請自動使用這些工具,給出完整回答。""",
    
        options=ClaudeAgentOptions(
            allowed_tools=["query_order", "query_shipping"]
        )
    ):
        if isinstance(message, ResultMessage):
            print(message.result)
            # 完成!Claude 自動完成了一切

    Claude 自動做決策:

    • ✅ Claude 看到問題
    • ✅ Claude 決定「我需要查訂單」
    • ✅ Claude 自動調用工具
    • ✅ Claude 看結果,決定「我還需要查物流」
    • ✅ Claude 自動調用工具
    • ✅ Claude 綜合所有信息,給出回答

    一句話總結

    Claude API 我知道要查 DB 的順序 → 我來組織
    Agent SDK 我把查 DB 的工具做好 → Claude 自己組織順序

    Agent SDK vs Claude API 完整對比

    特性 Claude API Agent SDK
    使用場景 一次性請求、內容生成 決策型任務、自動化工作流
    流程控制 你的應用程式控制 Claude 自動決策
    工具調用 手動查 DB,手動傳給 Claude Claude 自動調用工具和解析
    複雜度 簡單 – 一個請求,一個回應 複雜 – 多步驟推理、工具調用
    代碼量 多(需要寫 loop 邏輯) 少(一句話啟動)
    安全性 你負責驗證輸出 內置權限管理、審計日誌

    決策樹:選擇正確的工具

    你的任務來了
        ↓
    「這個任務需要 AI 自己決策嗎?」
        ├─ YES → 「結果容錯為零嗎?」
        │         ├─ YES → ❌ 不用 AI,用硬編碼邏輯
        │         └─ NO  → ✅ 用 Agent SDK
        │
        └─ NO  → 「只是單純生成內容/回答嗎?」
                  ├─ YES → ✅ 用 Claude API
                  └─ NO  → 「需要多步驟工作流嗎?」
                            ├─ YES → 你控制流程 + Claude API
                            └─ NO  → ✅ Claude API 就夠了

    真實案例:電商商品自動分類系統

    背景: 某線上購物平台(如東森)需要自動將客戶上傳的商品歸類到正確的分類。

    情況說明

    規模: 4 萬個商品分類(電子產品、服裝、家居、運動等)

    挑戰: 如何在成本可控的情況下,快速準確地進行分類?

    方案 1:傳統 TFIDF 模型

    實現方式:

    1. 收集所有商品描述 + 人工標註的分類
    2. 用 TFIDF 算法訓練模型,提取關鍵詞權重
    3. 客戶輸入新商品描述 → 模型計算與各分類的相似度 → 返回排名前 3 的分類
    # 傳統 TFIDF 方法
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.neighbors import NearestNeighbors
    
    # 1. 訓練
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(product_descriptions)
    model = NearestNeighbors(n_neighbors=3, metric='cosine')
    model.fit(tfidf_matrix)
    
    # 2. 預測
    new_product = "輕便防水的戶外登山背包,容量 40L"
    new_tfidf = vectorizer.transform([new_product])
    distances, indices = model.kneighbors(new_tfidf)
    
    # 結果:前 3 個分類
    # [運動戶外, 旅遊用品, 箱包配飾]

    優勢:

    • ⚡ 毫秒級回應(無網路延遲)
    • 💰 零成本(本地運行)
    • 🎯 可控、可解釋(看得到關鍵詞權重)

    缺點:

    • ❌ 無法理解語義(「便宜的手機」vs「經濟型手機」被當作完全不同的商品)
    • ❌ 變通說法識別差(「登山背包」識別不了,但「戶外登山用雙肩包」就被歸錯類)
    • ❌ 每次添加新分類或修改規則都需要重新訓練
    • ❌ 無法跨語言(中文和英文分開訓練)
    • ❌ 無法處理複雜邏輯(「防水的同時是否防塵?」這類二次判斷)

    方案 2:單純 AI 分類(Claude API)

    天真的做法:把 4 萬個分類都給 Claude

    from anthropic import Anthropic
    
    client = Anthropic()
    
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": f"""根據這個商品描述進行分類:
    
    商品描述:輕便防水的戶外登山背包,容量 40L
    
    可用分類:
    - 電子產品
    - 服裝鞋履
    ... (共 4 萬個)
    
    返回最相關的前 3 個分類 + 置信度。"""
        }]
    )

    問題: 4 萬個分類 × 10 tokens/分類 = 40 萬 tokens 光是分類列表

    • 成本:$5 / 次(按 Claude Opus 定價)
    • 日成本(10,000 商品):$50,000 / 天 = **$1.5M / 月** ❌
    • 完全不可行

    方案 3:向量搜尋 + AI 精確分類(最優)

    核心思想: 不是把 4 萬個分類都給 Claude,而是:

    1. 預先計算: 將 4 萬個分類嵌入向量空間(一次性,離線)
    2. 實時檢索: 商品描述進來 → 向量相似度搜尋 → 找出最相關的 Top 50 分類
    3. 精確分類: 只把 50 個候選分類傳給 Claude
    4. 結果: 成本 = 原本的 1/800,準確度反而更高
    # 向量檢索 + AI 分類架構
    
    from sentence_transformers import SentenceTransformer
    from milvus import connections, Collection
    from anthropic import Anthropic
    
    client = Anthropic()
    
    # ============ 階段 1:預先計算分類向量(一次性) ============
    def initialize_category_embeddings():
        """將 4 萬個分類嵌入向量空間"""
        connections.connect("default", host="127.0.0.1", port="19530")
        collection = Collection("products_categories")
    
        # 從資料庫讀取所有分類
        categories = get_all_categories_from_db()
    
        # 計算向量(使用本地模型)
        model = SentenceTransformer('all-MiniLM-L6-v2')
        embeddings = model.encode([cat['name'] for cat in categories])
    
        # 插入向量資料庫
        collection.insert({
            "id": [cat['id'] for cat in categories],
            "category_name": [cat['name'] for cat in categories],
            "embedding": embeddings
        })
        collection.load()
        print("✅ 4 萬個分類向量化完成!")
    
    # ============ 階段 2:實時檢索 + AI 分類 ============
    def classify_product_optimized(product_description):
        """實時分類流程"""
    
        # 1. 將商品描述向量化
        model = SentenceTransformer('all-MiniLM-L6-v2')
        query_embedding = model.encode(product_description)
    
        # 2. 向量搜尋:找出最相關的 Top 50 分類(< 50ms)
        connections.connect("default", host="127.0.0.1", port="19530")
        collection = Collection("products_categories")
    
        search_results = collection.search(
            data=[query_embedding],
            anns_field="embedding",
            param={"metric_type": "L2", "params": {"nprobe": 10}},
            limit=50  # 只取 Top 50
        )
    
        top_categories = [hit.entity.get("category_name") for hit in search_results[0]]
        categories_text = '\n'.join([f"- {cat}" for cat in top_categories])
    
        print(f"✓ 向量搜尋找到 50 個候選分類(用時:< 50ms)")
    
        # 3. Claude 從 50 個候選中精確選擇(150ms)
        response = client.messages.create(
            model="claude-opus-4-6",
            max_tokens=1024,
            messages=[{
                "role": "user",
                "content": f"""根據這個商品描述進行分類:
    
    商品描述:{product_description}
    
    相關分類候選(已按相似度排序,僅展示 Top 50):
    {categories_text}
    
    請從上述候選中選擇最相關的前 3 個分類 + 置信度。
    
    返回格式:
    1. 分類名稱 (置信度: 95%)
    2. 分類名稱 (置信度: 80%)
    3. 分類名稱 (置信度: 65%)
    
    同時說明理由。"""
            }]
        )
    
        return response.content[0].text

    性能對比:

    指標 TFIDF 純 AI
    (全部分類)
    向量 + AI
    (推薦)
    分類傳輸 不適用 40 萬 tokens 500 tokens(50 個)
    成本/次 $0 $5.00 $0.005
    日成本(10K 商品) $0 $50,000 $50
    響應時間 1ms 1-2 秒 200ms
    準確度 中(容易混淆) ⭐⭐⭐ 高
    語義理解 ❌ 差 ✅ 強 ✅ 強
    多語言支持 ❌ 差 ✅ 強 ✅ 強

    如何讓 Agent SDK / Claude 存取動態分類列表?

    問題: 上面的案例中,分類是硬編碼或簡單傳輸。但在真實系統中,分類會經常變動。Agent SDK 怎麼知道最新的分類清單?

    三種方案對應不同規模

    方案 A:應用層查詢(適合 < 500 分類)

    流程: 應用程式 → 查詢資料庫 → 替換提示詞佔位符 → Agent 使用

    import sqlite3
    from anthropic import Anthropic
    
    client = Anthropic()
    
    # 應用程式層:查詢資料庫
    def get_categories_from_db():
        conn = sqlite3.connect('products.db')
        cursor = conn.cursor()
        cursor.execute('SELECT name FROM categories WHERE active = 1 ORDER BY name')
        categories = [row[0] for row in cursor.fetchall()]
        conn.close()
        return categories
    
    # 獲取最新分類
    categories = get_categories_from_db()
    categories_text = '\n'.join([f"- {cat}" for cat in categories])
    
    # 生成提示詞
    prompt = f"""根據商品描述選擇分類:
    
    商品描述:輕便防水的戶外登山背包,容量 40L
    
    可用分類:
    {categories_text}
    
    返回前 3 個分類。"""
    
    # Claude 使用最新分類進行分類
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )

    優勢: 簡單、直接、零複雜度

    缺點: 每次請求都要查資料庫

    方案 B:Agent 工具查詢(適合 500-5000 分類)

    流程: Agent 調用自訂工具 → 工具查詢資料庫 → Agent 使用結果

    from anthropic import tool
    import sqlite3
    
    @tool
    def query_categories(keyword: str = None) -> str:
        """查詢可用的商品分類。
    
        Args:
            keyword: 可選的搜尋關鍵詞(例如:'電子' 會返回 '電子產品')
        """
        conn = sqlite3.connect('products.db')
        cursor = conn.cursor()
    
        if keyword:
            cursor.execute(
                'SELECT name FROM categories WHERE active = 1 AND name LIKE ? ORDER BY name',
                (f'%{keyword}%',)
            )
        else:
            cursor.execute('SELECT name FROM categories WHERE active = 1 ORDER BY name')
    
        categories = [row[0] for row in cursor.fetchall()]
        conn.close()
        return ', '.join(categories)
    
    # Agent 可以調用工具
    prompt = """根據商品描述選擇分類:
    
    商品描述:輕便防水的戶外登山背包,容量 40L
    
    步驟:
    1. 使用 query_categories 工具查詢所有或相關分類
    2. 從結果中選擇最相關的前 3 個
    
    返回格式:
    1. 分類名稱 (置信度: 95%)
    ...
    """

    優勢: 動態、Agent 可以「思考」查詢策略

    缺點: 增加 API 往返次數

    方案 C:向量檢索(適合 > 5000 分類,如東森 4 萬)

    流程: 預先嵌入分類 → 商品描述進來 → 向量搜尋 Top 50 → Claude 精確選擇

    (詳見上方「向量搜尋 + AI 精確分類」段落)

    方案選擇表

    分類規模 推薦方案 成本 延遲 複雜度
    < 100 方案 A(直接傳) 最低 最快 ⭐ 最簡
    100-500 方案 A(推薦) ⭐ 簡
    500-5000 方案 B(推薦) ⭐⭐ 中
    > 5000(東森 4 萬) 方案 C(必須) 最低 最快 ⭐⭐⭐ 複

    工具設計的正確思路:從問題出發

    這是最重要的一個章節。 很多工程師設計 Agent 工具時,會陷入「工具先行」的誤區。但正確的做法是「需求驅動」。

    ❌ 錯誤的做法:工具先行

    「我要做 Agent SDK」
      ↓
    「我需要什麼工具?」
      ↓
    設計工具:query(), update(), delete()
      ↓
    「我怎麼用這些工具?」
      ↓
    慢慢找場景...
      ↓
    結果:工具太通用、有遺漏、或過度設計

    ✅ 正確的做法:從問題出發

    「我的系統可能遇到什麼問題?」
      ↓
    列舉所有可能的問題
      ↓
    反推需要什麼工具來解決
      ↓
    設計這些工具(不多不少)
      ↓
    套入 Agent SDK
      ↓
    結果:工具精準、完整、適中

    實例 1:東森商品分類系統

    第 1 步:列舉可能的問題

    系統運行時會遇到什麼問題?
    
    用戶層面:
      ✓ 「我怎麼知道這個商品該放哪一類?」
      ✓ 「系統選的分類對嗎?」
      ✓ 「能不能推薦其他可能的分類?」
    
    系統層面:
      ✓ 「分類資料庫有沒有更新?」
      ✓ 「分類規則有沒有衝突?」
      ✓ 「某個分類底下有沒有商品?」
    
    邊界問題:
      ✓ 「這個商品可能屬於多個分類嗎?」
      ✓ 「怎麼處理新加的分類?」
      ✓ 「怎麼回滾到之前的分類?」

    第 2 步:反推需要的工具

    問題                                 → 需要的工具
    ──────────────────────────────────────────────────
    「能推薦其他可能的分類?」            → search_similar_categories()
    「分類有沒有更新?」                  → get_latest_categories()
    「分類規則有沒有衝突?」              → validate_category_rules()
    「某分類有沒有商品?」                → count_products_in_category()
    「可能屬於多個分類嗎?」              → get_ambiguous_categories()

    第 3 步:只設計這些工具

    @tool
    def search_similar_categories(keyword: str, top_k: int = 5) -> list:
        """找相關分類"""
    
    @tool
    def get_latest_categories(updated_after: str = None) -> list:
        """取最新分類資料"""
    
    @tool
    def validate_category_rules(category_id: str) -> dict:
        """驗證分類規則有沒有問題"""
    
    @tool
    def count_products_in_category(category_id: str) -> int:
        """某分類有多少商品"""
    
    @tool
    def get_ambiguous_categories(product_desc: str) -> list:
        """找可能有歧義的分類"""

    就這 5 個工具,不多不少!

    實例 2:客服系統

    列舉問題 → 推斷工具

    問題列表:
    1. 「客戶的訂單存在嗎?」
    2. 「訂單現在在哪裡?」
    3. 「這個客戶有沒有退貨紀錄?」
    4. 「退貨政策允許嗎?」
    5. 「要問主管才能退嗎?」
    6. 「怎麼記錄這次交互?」
    
    需要的工具:
    1. query_order()
    2. get_shipping_status()
    3. get_customer_return_history()
    4. check_return_policy()
    5. escalate_to_manager()
    6. log_interaction()

    就這 6 個,不多不少!

    為什麼這樣設計好?

    優勢 解釋
    ✅ 不會過度設計 只有解決實際問題的工具
    ✅ 不會有遺漏 所有可能的問題都考慮到
    ✅ Claude 理解清楚 系統邊界明確,工具語義清晰
    ✅ 工具粒度適中 既不太大也不太小
    ✅ 易於維護和擴展 添加新問題 = 添加新工具

    設計 Agent 工具時的關鍵決策:工具粒度

    基於「從問題出發」的思路,現在我們討論工具粒度。 粒度(granularity)決定了 Claude 能否有效使用工具。

    三種極端情況

    ❌ 工具太大(做太多事)

    @tool
    def query_order_everything(
        customer_id: str,
        order_id: str = None,
        status: str = None,
        start_date: str = None,
        end_date: str = None,
        shipping_status: str = None,
        include_items: bool = True,
        include_history: bool = True
    ) -> dict:
        """一個工具做所有事"""
        # 6 個 WHERE 條件
        # ...

    問題:

    • Claude 很容易參數填錯(太多選擇)
    • Claude 不知道應該用哪些參數組合
    • 你寫了 6 個 WHERE,Claude 可能只需要 1 個,浪費

    ❌ 工具太小(太細碎)

    @tool
    def query_by_customer_id(customer_id: str) -> list:
        """只查客戶"""
    
    @tool
    def query_by_status(status: str) -> list:
        """只查狀態"""
    
    @tool
    def query_by_date_range(start: str, end: str) -> list:
        """只查日期"""
    
    # ... 還有 10 個工具

    問題:

    • Claude 要調用 3-4 次工具才能完成一個查詢
    • 延遲高、成本高
    • Claude 要自己 join 結果,容易出錯

    ✅ 工具適中(語義相關的組合)

    @tool
    def get_customer_order_status(customer_id: str, order_id: str = None) -> dict:
        """查客戶的訂單,可選篩選特定訂單"""
        # WHERE customer_id = ? AND (order_id = ? if provided)
    
    @tool
    def get_orders_by_date(start_date: str, end_date: str) -> list:
        """查某個時間範圍的訂單"""
        # WHERE created_at BETWEEN ? AND ?
    
    @tool
    def get_orders_by_status(statuses: list) -> list:
        """查特定狀態的訂單"""
        # WHERE status IN (...)

    優勢:

    • 每個工具有單一職責(符合 SOLID 原則)
    • Claude 能清楚理解用途
    • 通常 1-2 次調用完成查詢
    • 參數數量合理(不超過 3-4 個)

    工具粒度判斷清單

    設計工具時,問自己這些問題:

    判斷點 好的工具 差的工具
    工具名能清楚表達意圖嗎? get_orders_by_customer() query_db()
    參數數量 < 4 個 > 5 個
    Claude 能猜到用途嗎? 「我看到名字就知道查什麼」 「我完全不知道這是什麼」
    通常多少次調用完成任務? 1-2 次 3-5 次或更多
    工具代表一個業務概念嗎? 「訂單狀態」「物流追蹤」 「數據庫查詢」「參數組合」

    實際案例:東森分類系統

    ❌ 太大的工具

    @tool
    def search_categories(
        keyword: str = None,
        parent_category: str = None,
        min_products: int = None,
        max_products: int = None,
        active_only: bool = True,
        sort_by: str = "name"
    ):
        """搜尋分類"""
        # 這是什麼?搜分類還是搜商品?太模糊

    ✅ 適中的工具(基於問題驅動)

    @tool
    def search_categories_by_keyword(keyword: str) -> list:
        """用關鍵詞搜分類
    
        例:keyword="運動" → 返回 [運動戶外, 運動服飾, ...]
        """
    
    @tool
    def get_category_children(parent_id: str) -> list:
        """取某分類的子分類"""
    
    @tool
    def get_products_in_category(category_id: str, limit: int = 100) -> list:
        """取分類下的商品"""

    Claude 使用:

    「我要分類一個『防水戶外背包』的商品」
    ↓
    Claude:「我先搜 '戶外' 相關分類」
      → search_categories_by_keyword("戶外")
      → 返回 [運動戶外, 戶外用品, ...]
    
    Claude:「運動戶外 有什麼子類別?」
      → get_category_children("運動戶外")
      → 返回 [登山, 露營, 滑雪, ...]
    
    Claude:「登山 分類有什麼產品,我看看背包在哪」
      → get_products_in_category("登山")
      → 自動判斷這個背包應該是「運動戶外 > 登山」

    Claude 自動組織,不需要你預先決定順序!

    工具粒度的黃金法則

    問自己:「Claude 看著這個工具名和參數,會不會用?」

    • 如果會 → 工具大小合適
    • 如果不會 → 工具太大或太模糊,要拆或改名

    總結:何時遷移到 Agent SDK

    如果你符合以下任何一個,考慮 Agent SDK:

    • 代碼包含 200+ 行 if-elif-else 邏輯
    • 每月都在添加新的處理規則
    • 用戶經常報告「系統沒理解我」
    • 客服系統無法適應變通表達方式
    • 維護成本不斷上升

    核心優勢總結:

    • 代碼行數:500+ → 10(50 倍簡化)
    • 新增規則:改代碼+測試+部署 → 改提示(即時生效)
    • 用戶體驗:生硬規則化 → 自然親切
    • 維護成本:高 → 低

    工具設計思路:

    • ❌ 不要:先設計工具,再找用處
    • ✅ 要:從問題出發,反推需要的工具
    • ✅ 結果:工具精準、完整、易用

    準備好了嗎?下載 Jupyter Notebook,親手測試,然後在你的項目中試用 Agent SDK。

  • Claude Code Agent Teams 完整教學:啟用設定、觸發條件與多代理協作實戰

    Claude Code 的 Agent Teams(代理團隊)是一個實驗性功能,能讓多個 Claude Code instance 同時工作、互相溝通、共享任務。這篇文章完整記錄了我從啟用設定、觸發條件研究,到規劃用 Agent Teams 重建電商 OMS 系統的全過程。

    (閱讀全文…)