Felo API PlatformFelo API Platform

客服

把客服工單分流成有依據的回覆草稿

先脫敏工單,用 Chat 判斷意圖和優先順序,從文件裡檢索能回答的段落,檢查是否有公開事故討論,再留下一份帶路由的回覆草稿給人工審批。

建置時間20 分鐘難度進階更新於2026-10-30
適合誰客服負責人Helpdesk Agent支援工程師

複製 prompt

把方括號裡的欄位替換掉,然後貼到 Claude Code、Codex 或任何能存取終端機的 AI。它會安裝 Felo skill、在花額度前詢問,並執行這套工作流程。

對這個支援工單做分診,並寫一份供人工審閱的回覆草稿:[貼上工單,先刪除姓名、電子郵件、帳號 ID、支付資料和其他可識別資訊]。

產品領域:[產品]。方案或地區:[僅在回答需要時]。文件來源:[幫助中心 URL 或 LiveDoc short_id]。
核准路徑:[誰審閱]。升級佇列:[團隊或頻道]。

輸出:
1. 意圖:一個類別,加一句理由。
2. 優先級:緊急 / 一般 / 低,附適用規則。
3. 依據:能回答工單的文件段落,附標題和相關度。
4. 故障檢查:公開報告是否暗示大範圍故障。附連結和日期,或寫「無公開訊號」。
5. 路由:起草回覆、向客戶補資料,或升級——並說明原因。
6. 回覆草稿:兩到四句,只基於證據,並點名所依據的來源。

規則:
- 不要把姓名、電子郵件、訂單號、權杖或支付資料放進 API 呼叫或輸出。只處理已去識別化的工單。
- 只根據檢索到的文件回答。沒有依據時升級,不要猜測。
- 不要承諾退款、折扣、時限或帳戶變更;這些轉給升級流程。
- 公開故障傳聞是訊號,不是證據。引用貼文和日期。
- 消耗額度前,先展示預計執行的呼叫並等待我批准。
- 不要傳送回覆、更新工單或操作帳戶。把草稿回傳給我審閱。
- 將結果寫入 ./support-triage.md,並在對話中總結。

複製的內容也包含安裝步驟和安全規則。

你會得到什麼

一次執行的範例輸出,不是即時生成。

分診範例(節錄)

分診 — 範例工單

意圖

帳單 / 續約問題。客戶詢問為什麼方案又續約了。

優先級

一般。這是帳單問題,不是故障,客戶沒有被阻擋。

依據

  • 「帳單常見問題」 — 續約時間與扣款失敗重試。相關度:高。
  • 「方案變更」 — 續約之間的降級。相關度:中。

故障檢查

過去 48 小時沒有公開報告。

路由

起草回覆。文件涵蓋了這個問題。

回覆草稿

你好——你的方案在每月 1 日續約,信用卡扣款失敗後會重試三天,然後席位暫停。你可以在帳單設定中更換信用卡;帳單常見問題 有逐步說明。如果需要更改續約日期,請回覆,我們會升級給帳單團隊。

審核備註

批准、編輯,或升級到帳單佇列。

實際呼叫

給開發者:同一套工作流程的可複製程式碼,依執行順序排列。

展開實際 API 呼叫
  1. 脫敏後再分類

    用程式碼去掉電子郵件、電話、訂單號等 PII,再用 Chat 判斷意圖和優先順序。

    triage.ts
    const BASE = "https://openapi.felo.ai";
    const headers = {
      Authorization: `Bearer ${process.env.FELO_API_KEY}`,
      "Content-Type": "application/json",
    };
    
    async function felo<T>(path: string, body?: unknown): Promise<T> {
      const res = await fetch(`${BASE}${path}`, {
        method: body ? "POST" : "GET",
        headers,
        body: body ? JSON.stringify(body) : undefined,
      });
      if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
      return res.json() as Promise<T>;
    }
    
    const redact = (text: string) =>
      text
        .replace(/[\w.+-]+@[\w.-]+\.\w+/g, "[email]")
        .replace(/\b(?:acct|account|order|invoice)[-_ ]?\w+\b/gi, "[id]")
        .replace(/\b(?:\d[ -]?){13,19}\b/g, "[card]");
    
    type Ticket = {
      id: string;
      product: string;
      subject: string;
      body: string;
      plan?: string;
      sensitive?: boolean;
    };
    
    const ticket: Ticket = {
      id: "T-1042",
      product: "Acme Workspace",
      subject: "Why did my plan renew?",
      body: "I was charged again and cannot find the renewal date on my account.",
      plan: "team",
    };
    
    const summary = redact(`${ticket.subject}\n\n${ticket.body}`).slice(0, 1500);
    
    type ChatData = {
      data: {
        answer: string;
        resources: { link: string; title: string; snippet: string }[];
      };
    };
    
    const triage = await felo<ChatData>("/v2/chat", {
      query: `Classify this support question by intent and priority, and list what is missing:\n\n${summary}`,
    });
    
    console.log(triage.data.answer);

    範例輸出

    {
      "answer": "Intent: billing / renewal question. Priority: normal. Missing: the renewal date the customer expects.",
      "resources": [
        { "link": "https://example.com/billing-faq", "title": "Billing FAQ", "snippet": "…" }
      ]
    }
  2. 檢索文件證據

    從客服知識庫 LiveDoc 檢索相關段落,保留標題和分數。

    triage.ts
    type Hit = { title: string; content: string; score: number };
    
    const KB = process.env.SUPPORT_LIVEDOC_ID!; // the help-center LiveDoc short_id
    
    const retrieved = await felo<{ data: Hit[] }>(
      `/v2/livedocs/${KB}/resources/retrieve`,
      { query: summary },
    );
    
    const hits = retrieved.data.filter((hit) => hit.score >= 0.7).slice(0, 4);
    
    if (hits.length === 0) {
      console.log("No grounded evidence: escalate instead of drafting");
    }

    範例輸出

    [
      {
        "title": "Billing FAQ",
        "content": "Your plan renews on the first of the month; a failed card retries for three days…",
        "score": 0.91
      }
    ]
  3. 檢查公開事故

    在 X 搜產品名和「故障/中斷」等詞,限定 48 小時,只作為公開訊號。

    triage.ts
    type XData = {
      data: {
        tweets: {
          content: string;
          author: { username: string };
          created_at: string;
          url: string;
        }[];
      };
    };
    
    const since = Math.floor((Date.now() - 48 * 60 * 60 * 1000) / 1000);
    
    const incident = await felo<XData>("/v2/x/tweet/search", {
      query: `"${ticket.product}" (outage OR "not working" OR error)`,
      limit: 20,
      since_time: since,
    });
    
    const reports = incident.data.tweets.map((post) => ({
      text: post.content,
      handle: post.author.username,
      at: post.created_at.slice(0, 10),
      link: post.url,
    }));

    範例輸出

    [
      {
        "text": "Workspace login is failing for my whole team.",
        "handle": "example_user",
        "at": "2026-10-30",
        "link": "https://x.com/example_user/status/..."
      }
    ]
  4. 定路由並組裝回覆

    依規則決定佇列,把證據、下一步和回覆草稿打包。

    triage.ts
    type Route = "draft" | "ask-customer" | "escalate";
    
    function decideRoute(): { route: Route; reason: string } {
      if (hits.length === 0) {
        return { route: "escalate", reason: "no grounded evidence in the knowledge base" };
      }
      if (ticket.sensitive) {
        return { route: "escalate", reason: "an account or billing action is requested" };
      }
      if (reports.length >= 3) {
        return { route: "escalate", reason: "public reports suggest a wider issue" };
      }
      if (!ticket.plan) {
        return { route: "ask-customer", reason: "the plan is needed to answer the question" };
      }
      return { route: "draft", reason: "the documentation covers the question" };
    }
    
    const decision = decideRoute();
    
    const packet = [
      `# Triage — ${ticket.id}`,
      "",
      `Route: ${decision.route} (${decision.reason})`,
      "",
      "## Intent and priority",
      triage.data.answer,
      "",
      "## Evidence",
      ...hits.map((hit) => `- ${hit.title} (score ${hit.score})`),
      "",
      "## Public signal",
      ...(reports.length
        ? reports.map((report) => `- @${report.handle} (${report.at}): ${report.text} — ${report.link}`)
        : ["No public reports in the last 48 hours."]),
      "",
      "## Reply draft",
      "[Written by the host Agent from the evidence above — human review required]",
    ].join("\n");
    
    console.log(packet);

    範例輸出

    # Triage — T-1042
    
    Route: draft (the documentation covers the question)
    
    ## Intent and priority
    Intent: billing / renewal question. Priority: normal.
    
    ## Evidence
    - Billing FAQ (score 0.91)
  5. 記錄分流

    把分流結果寫進 LiveDoc,方便復盤和稽核。

    triage.ts
    type LiveDocData = { data: { short_id: string } };
    
    const log = await felo<LiveDocData>("/v2/livedocs", {
      name: "Support triage log",
      description: "One document resource per triaged ticket",
    });
    
    await felo(`/v2/livedocs/${log.data.short_id}/resources/doc`, {
      title: `Triage — ${ticket.id}`,
      content: packet,
    });
    
    console.log(log.data.short_id); // reuse it for the next ticket

    範例輸出

    { "short_id": "PvyKouzJirXjFdst4uKRK3" }

包含什麼

  • 脫敏後的意圖和優先順序分類
  • 帶分數的文件證據
  • 公開事故檢查結果
  • 帶路由的回覆草稿
  • 分流記錄 LiveDoc

不做什麼

  • 不發送回覆、不關閉工單、不做帳戶操作
  • 不要把密碼、付款資訊或完整客戶資料發給 API
  • 沒有文件命中時必須升級
  • X 只用於公開事故訊號,不代表客服系統狀態
  • 優先順序規則由你定義,模型只給建議

常見問題

可以發送哪些工單資料?

只發送脫敏後的產品、問題描述、方案和地區等必要上下文。不要發送密碼、付款資訊、完整地址或私人對話。

回覆從哪裡來?

來自你的文件 LiveDoc 檢索出的段落。沒有命中時工作流程會升級,而不是猜。

它會直接回覆或關閉工單嗎?

不會。它只生成草稿和路由,發送和關閉必須由人工或你的系統完成。

優先順序怎麼定?

由你定義的規則決定;模型給出建議和理由,最終優先順序由路由規則確認。

文件沒有涵蓋時怎麼辦?

升級到人工佇列,並在分流包裡註明「未涵蓋」。

一次分流要花多少?

Chat、檢索和 X 搜尋按用量計費;目前費率在價格頁面,prompt 會先請求確認。

相關工作流程

改成你的版本

同一套配方,依你實際的交付方式調整。

只做分類

不生成回覆,只輸出意圖、優先順序和建議佇列。

接入工單系統

透過你自己的 webhook 把分流包寫進工單,不自動發送。

升級為知識庫回答

文件命中時,交給客服知識庫工作流程生成更完整的回答。

這套配方背後的參考頁面,以及下一步可以看什麼。