分類: 技術-開發

  • 多通路電商系統架構:用工廠模式整合 17 個平台

    商業價值:這篇介紹的工廠模式讓「新增平台從 2-3 個月縮短到 2-3 週」,直接影響 導讀篇提到的 80% 擴展成本降低

    前言:當你要同時對接 17 個電商平台

    在多通路電商系統中,我們需要整合多個平台:

    平台類型 範例 特性
    綜合電商 蝦皮、Momo、Yahoo、PChome 訂單量大、API 複雜
    開店平台 Shopify、Shopline、91APP 彈性高、客製化多
    國際平台 樂天、Coupang、Amazon 多語系、跨境物流
    問題:每個平台的 API 格式、認證方式、資料結構都不同。如果用 if-else 判斷,程式碼會變成災難。

    解決方案:工廠模式 + 策略模式

    核心思想:定義統一介面,每個平台各自實作。新增平台時,只需要新增一個實作類別。

    Step 1:定義統一介面

    所有平台都必須實作這個介面:

    /**
    * 電商平台動作介面
    * 所有平台整合都必須實作這個介面
    */

    public interface ChannelAction {

    // 取得平台設定(API URL、版本等)
    ChannelSetting getSetting();

    // 取得平台授權 Token
    TokenResult getAccessToken(Merchant merchant);

    // 驗證必要資料是否齊全
    boolean validateRequired(ActionRequest request);

    // 執行實際動作(同步訂單、更新庫存等)
    ActionResult execute(ActionRequest request);
    }

    Step 2:每個平台各自實作

    蝦皮實作範例:

    @Component
    public class ShopeeAction implements ChannelAction {

    @Override
    public ChannelSetting getSetting() {
    return ChannelSetting.builder()
    .apiUrl(“https://partner.shopeemobile.com”)
    .version(“v2”)
    .authType(AuthType.OAUTH2)
    .build();
    }

    @Override
    public TokenResult getAccessToken(Merchant merchant) {
    // 蝦皮使用 OAuth2 + 簽章驗證
    String signature = generateSignature(merchant);
    return callShopeeAuthAPI(merchant, signature);
    }

    @Override
    public ActionResult execute(ActionRequest request) {
    // 呼叫蝦皮 API 執行動作
    return callShopeeAPI(request);
    }
    }

    Momo 實作範例:

    @Component
    public class MomoAction implements ChannelAction {

    @Override
    public ChannelSetting getSetting() {
    return ChannelSetting.builder()
    .apiUrl(“https://api.momo.com.tw”)
    .version(“v1”)
    .authType(AuthType.API_KEY)
    .build();
    }

    @Override
    public TokenResult getAccessToken(Merchant merchant) {
    // Momo 使用 API Key 驗證
    return TokenResult.of(merchant.getMomoApiKey());
    }

    @Override
    public ActionResult execute(ActionRequest request) {
    // 呼叫 Momo API 執行動作
    return callMomoAPI(request);
    }
    }

    Step 3:工廠類別統一管理

    @Component
    public class ChannelFactory {

    private final Map<ChannelType, ChannelAction> actionMap;

    // Spring 自動注入所有 ChannelAction 實作
    public ChannelFactory(List<ChannelAction> actions) {
    this.actionMap = actions.stream()
    .collect(Collectors.toMap(
    action -> action.getSetting().getChannelType(),
    action -> action
    ));
    }

    /**
    * 根據通路類型取得對應的實作
    */

    public ChannelAction getAction(ChannelType channelType) {
    ChannelAction action = actionMap.get(channelType);
    if (action == null) {
    throw new UnsupportedChannelException(
    “不支援的通路: “ + channelType
    );
    }
    return action;
    }
    }


    使用方式

    業務邏輯層只需要這樣呼叫:

    @Service
    public class OrderSyncService {

    @Autowired
    private ChannelFactory channelFactory;

    public SyncResult syncOrders(ChannelType channel, Merchant merchant) {
    // 1. 取得對應的通路實作
    ChannelAction action = channelFactory.getAction(channel);

    // 2. 取得授權 Token
    TokenResult token = action.getAccessToken(merchant);

    // 3. 執行同步
    ActionRequest request = ActionRequest.builder()
    .merchant(merchant)
    .token(token)
    .actionType(ActionType.SYNC_ORDERS)
    .build();

    return action.execute(request);
    }
    }

    優點:不管是蝦皮、Momo、Yahoo 還是其他平台,呼叫方式完全一樣。新增平台時,業務邏輯層完全不用改。

    新增平台有多簡單?

    假設要新增 Coupang 韓國平台:

    @Component
    public class CoupangAction implements ChannelAction {

    @Override
    public ChannelSetting getSetting() {
    return ChannelSetting.builder()
    .apiUrl(“https://api-gateway.coupang.com”)
    .version(“v2”)
    .authType(AuthType.HMAC)
    .build();
    }

    // … 實作其他方法
    }

    步驟 工作內容 影響範圍
    1 建立 CoupangAction 類別 只有新檔案
    2 實作 ChannelAction 介面 只有新檔案
    3 加上 @Component 註解 只有新檔案
    4 完成!Spring 自動註冊 零修改現有程式

    設計模式總結

    模式 用途 在這裡的應用
    策略模式 定義演算法家族,讓它們可互換 每個平台是一個策略
    工廠模式 封裝物件建立邏輯 根據通路類型取得實作
    依賴注入 解耦合 Spring 自動管理
    效益:

    • 新增通路:從 2-3 個月縮短到 2-3 週
    • 維護成本:改一個平台不影響其他平台
    • 測試:每個平台可以獨立單元測試

    為什麼不用其他方案?

    方案 優點 缺點 結論
    if-else 判斷 簡單直接 每次加平台要改核心程式碼 小規模可用,超過 3 個平台就很痛苦
    Switch Case 比 if-else 清楚 還是要改核心程式碼 同上
    反射 + 設定檔 完全不改程式碼 除錯困難、IDE 無法追蹤 過度設計,維護成本高
    工廠 + 策略 新增只加檔案、Spring 自動註冊 需要理解設計模式 中大型系統的最佳平衡

    實戰踩坑

    坑 1:平台 API 變更沒通知

    蝦皮某次 API 升級,回傳欄位名稱從 order_sn 改成 ordersn。因為每個平台有獨立的 Action 類別,我們只需要改 ShopeeAction,其他 16 個平台完全不受影響。如果用 if-else,改錯一行就全部爆炸。

    坑 2:忘記加 @Component

    新人寫好 CoupangAction 卻沒加 @Component,Spring 沒註冊到 Factory。呼叫時直接噴 UnsupportedChannelException。後來在程式碼審查加入檢查項:「確認新 Action 有 @Component」。

    坑 3:介面設計太死

    最初 ChannelAction 只有 execute() 一個方法。後來發現有些平台需要 OAuth 刷新 Token、有些需要 Webhook 處理。介面改了三次才穩定。教訓:先做 3-5 個平台再抽象,別一開始就過度設計


    系列導航

    ◀ 上一篇
    導讀篇
    📚 返回目錄 下一篇 ▶
    Kafka 事件驅動
  • 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。

  • WordPress REST API 調試實戰:從 NNNN 字符到完整修復

     

    WordPress REST API 調試實戰:從 NNNN 字符到完整修復

     

    🎯 重點摘要

    • 問題根源:WordPress 資料庫中存在字面 ‘n’ 字符(0x6E),而非換行符(0x0A)
    • 表現症狀:REST API 返回 NNNN、n< 模式、表格損壞、內容亂碼
    • 根本原因:多層架構的信息轉換導致錯誤的故障假設和調試方向偏離
    • 解決方案:使用 od -c 檢查二進位數據、多層驗證、直接在資料庫層修復

    問題是如何出現的?

    WordPress REST API 調試中最常見的陷阱就是 症狀與原因的巨大落差。你在前端看到 NNNN 字符和 n< 模式,但實際問題可能在完全不同的地方。

    這篇文章根據真實的 WordPress 修復案例(超過 1,000 個 NNNN 字符、16 個表格損壞),詳細解析多層故障排除流程。

    第 1 層:表面症狀 vs 實際原因

    當 REST API 返回異常內容時,最危險的假設就是直接指責過濾器或編碼問題。實際上,以下三層都可能是問題來源:

    你看到的 期望的原因 實際原因 解決難度
    REST API 顯示 n< 過濾器損壞內容 資料庫中有字面 ‘n’ 字符 ⭐⭐⭐⭐
    NNNN 字符出現 轉義或編碼問題 ‘nn’ 模式(字面n + 換行) ⭐⭐⭐⭐⭐
    表格消失或亂碼 HTML 結構破壞 字面 ‘n’ 阻斷了 HTML 標籤解析 ⭐⭐⭐⭐

    表 1:WordPress 調試常見誤判清單 — 本表格列出 REST API 常見症狀、直觀的誤判原因,以及實際根本原因。這些誤判會導致調試花費 2-4 小時無果。

    第 2 層:多層架構的信息失真

    WordPress 資料從資料庫到瀏覽器經過多個轉換層,每一層都會改變你看到的表現形式:

    層級 你看到的 實際的字節 驗證方式
    MySQL 命令列 n(轉義序列) 0x0A(真實)或 0x6E(’n’ 字符) od -c
    PHP 讀取 實際換行或字面 ‘n’ 二進位正確表示 strpos($str, “n”)
    REST API JSON n 字符或 n JSON 正確轉義 jq + od -c
    瀏覽器顯示 NNNN、亂碼或正常 HTML 渲染結果 DevTools 檢查

    表 2:多層架構信息轉換對比 — 同一份資料在不同層級呈現出不同的表現。MySQL 命令列使用轉義表示,PHP 使用二進位,REST API 使用 JSON,瀏覽器進行 HTML 渲染。如果不理解這些轉換,很容易做出錯誤的根因判斷。

    第 3 層:正確的調試順序

    大多數 WordPress 調試問題都是因為調試順序錯誤。正確的調試順序應該是:

    1. 直接檢查二進位資料(od -c)— 這是源頭事實,必須第一步做
    2. 對比 DB ↔ Filter ↔ REST API 的三層輸出 — 縮小問題範圍
    3. 假設反轉 — 如果不是編碼問題,那是資料損壞嗎?
    4. 定位損壞位置 — 哪一層引入的?是資料庫本身還是更新時損壞?
    5. 追蹤操作歷史 — 之前做過什麼導致損壞?

    在真實案例中,調試花費了大量時間的原因是:第 1 次調查順序是 2 → 3 → 1 → 4 → 5,而正確順序應該是 1 → 2 → 3 → 4 → 5。

    第 4 層:實際的修復步驟

    步驟 1:使用 od -c 檢查資料庫的實際字節

    docker exec wordpress mysql -u wpuser -pwp_password wordpress -e 
      "SELECT SUBSTRING(post_content, POSITION('' IN post_content), 50) 
       FROM wp_posts WHERE ID = 984;" | tail -1 | od -c | head -20

    輸出應該顯示:

    !   -   -   >   n      n   <   !   -   -
                        ^   ^
                字面'n'  實際換行

    如果看到這個模式,你已經找到了根本原因:資料庫中有字面 ‘n’ 字符

    步驟 2:修復資料庫損壞

    docker exec wordpress mysql -u wpuser -pwp_password wordpress -e "
    UPDATE wp_posts
    SET post_content = REPLACE(post_content, CONCAT('n', CHAR(10)), CHAR(10))
    WHERE ID = 984;
    "

    這個 SQL 語句移除所有「字面 ‘n’ + 換行符」的組合,只保留實際的換行符。

    步驟 3:驗證修復

    curl -s http://localhost:8001/wp-json/wp/v2/posts/984 | jq -r '.content.rendered' | grep -o 'n<' | wc -l
    # 應該返回 0

    第 5 層:為什麼調試這麼困難?

    困難點 為什麼 解決方案
    信息不對稱 MySQL 顯示 n、PHP 顯示實際換行、REST API 顯示 n 字符 建立單一源頭(od -c),在那層定位問題
    問題來源不清 用戶說「做表格後出現 NNNN」,但不知道之前對資料做過什麼 追蹤操作歷史,理解損壞何時引入
    多層架構複雜 Database → Filter(6 個) → REST API → Browser 逐層檢查,縮小問題範圍到特定層級
    工具轉換多次 MySQL CLI → od -c → PHP → curl → jq → JSON 固定驗證工具,避免多次轉換導致的失真

    表 3:WordPress REST API 調試困難點分析 — 列出調試過程中的四個主要困難,以及每個困難對應的解決方案。這些都是基於真實的修復案例總結出來的。

    第 6 層:最佳實踐清單

    • 第一步永遠是 od -c — 不要猜測,直接看二進位數據
    • 建立多層驗證 — 不要只檢查一層,Database + Filter + REST API 都要查
    • 假設反轉 — 一個方向卡住了,立即反轉假設方向
    • 追蹤操作歷史 — 理解「之前發生了什麼」比「現在看起來怎樣」更重要
    • 表格要有邊框 — 使用 inline style: style="border: 1px solid #333; padding: 8px;"
    • 保存配置檔 — WordPress API 認證信息應該存在 ~/.claude/projects/project-name/wordpress-config.env

    常見問題(FAQ)

    總結

    WordPress REST API 調試的關鍵是理解 多層架構中的信息失真。症狀永遠不等於原因,你看到的 NNNN 字符只是冰山一角。

    記住這個優先順序:

    1. od -c 檢查二進位(源頭事實)
    2. 逐層驗證(Database → Filter → REST API)
    3. 假設反轉(卡住時反向思考)
    4. 追蹤歷史(理解根本原因)
    5. 修復並驗證(修完要驗證三層)

    下次遇到 WordPress REST API 問題時,不要急著改過濾器或重建資料庫。先用 od -c 看看真正的二進位數據,一切就清楚了。

     

  • EPS 超跌策略回測:P/E 低於 10 買進真的能賺嗎?162 筆實測數據完整分析

    「本益比低於 10 就是便宜,買進穩賺」——這是許多投資人相信的法則。但實際上真的如此嗎?

    (閱讀全文…)

  • Claude Code 實戰:用 AI 開發完整台股分析系統(含量化因子、回測引擎、Web Dashboard)

    進展更新 (2026-03-11 歷史數據回填完成)

    重大進展:成功回填 6 個月的歷史市場數據!系統現在包含 227,221 筆完整的股票交易記錄,涵蓋 2025 年 9 月至 2026 年 3 月。

    (閱讀全文…)

  • git flow說明

    git flow

    網路版本

    我方版本

    gitGraph
           commit id: "main"
           branch develop
           commit id: "init"
           branch feature1
           commit id: "feature1"
           checkout develop
           branch feature2
           commit id: "feature2"
           checkout feature2
           branch mergeFeature
           merge  feature1
           commit id: "merge12ToDev"
           commit tag:"dev-0.0.1"
           branch ga
           commit id: "toPro"
           commit tag:"ga-0.0.1"
           checkout main
           merge  ga
           commit id: "mergeAllFeature"
           checkout develop
           merge  main
           commit id: "202301XX"
           checkout develop
           branch feature3
           commit id: "feature3"
           checkout develop
           branch feature4
           commit id: "feature4"
           checkout mergeFeature
           merge  feature3
           merge  feature4
           commit id: "merge34ToDev"
    
    

    主要的幾個分支

    main(有時候是master)

    基本上每一次上板後都要merge回這一個分支,此分支為主要基底,方向只有ga可以改回來他,平常不可改動他,任何情境下都不可以

    (閱讀全文…)