Felo API PlatformFelo API Platform

客服

把客服工单分流成有依据的回复草稿

先脱敏工单,用 Chat 判断意图和优先级,从文档里检索能回答的段落,检查是否有公开事故讨论,再留下一份带路由的回复草稿给人工审批。

构建时间20 分钟难度进阶更新于2026-10-30
适合谁客服负责人Helpdesk Agent支持工程师

复制 prompt

把方括号里的字段替换掉,然后粘贴到 Claude Code、Codex 或任何能访问终端的 AI。它会安装 Felo skill、在花额度前询问,并执行这套 workflow。

对这个支持工单做分诊,并写一份供人工审阅的回复草稿:[粘贴工单,先删除姓名、邮箱、账号 ID、支付数据和其他可识别信息]。

产品领域:[产品]。套餐或地区:[仅在回答需要时]。文档来源:[帮助中心 URL 或 LiveDoc short_id]。
审批路径:[谁审阅]。升级队列:[团队或渠道]。

输出:
1. 意图:一个类别,加一句理由。
2. 优先级:紧急 / 普通 / 低,附适用规则。
3. 依据:能回答工单的文档段落,附标题和相关度。
4. 故障检查:公开报告是否暗示大范围故障。附链接和日期,或写“无公开信号”。
5. 路由:起草回复、向客户补充信息,或升级——并说明原因。
6. 回复草稿:两到四句,只基于证据,并点名所依据的来源。

规则:
- 不要把姓名、邮箱、订单号、令牌或支付数据放进 API 调用或输出。只处理已脱敏的工单。
- 只根据检索到的文档回答。没有依据时升级,不要猜测。
- 不要承诺退款、折扣、时限或账户变更;这些转给升级流程。
- 公开故障传闻是信号,不是证据。引用帖子和日期。
- 消耗额度前,先展示计划执行的调用并等待我批准。
- 不要发送回复、更新工单或操作账户。把草稿返回给我审阅。
- 将结果写入 ./support-triage.md,并在对话中总结。

复制的内容也包含安装步骤和安全规则。

你会得到什么

一次运行的示例输出,不是实时生成。

分诊示例(节选)

分诊 — 示例工单

意图

账单 / 续费问题。客户询问为什么方案又续费了。

优先级

普通。这是账单问题,不是故障,客户没有被阻塞。

依据

  • “账单常见问题” — 续费时间和扣款失败重试。相关度:高。
  • “方案变更” — 续费之间的降级。相关度:中。

故障检查

过去 48 小时没有公开报告。

路由

起草回复。文档覆盖了这个问题。

回复草稿

你好——你的方案在每月 1 日续费,银行卡扣款失败后会重试三天,然后席位暂停。你可以在账单设置中更换银行卡;账单常见问题 有分步说明。如果需要更改续费日期,请回复,我们会升级给账单团队。

审核备注

批准、编辑,或升级到账单队列。

实际调用

给开发者:同一套 workflow 的可复制代码,按执行顺序排列。

展开实际 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 检索出的段落。没有命中时 workflow 会升级,而不是猜。

它会直接回复或关闭工单吗?

不会。它只生成草稿和路由,发送和关闭必须由人工或你的系统完成。

优先级怎么定?

由你定义的规则决定;模型给出建议和理由,最终优先级由路由规则确认。

文档没有覆盖时怎么办?

升级到人工队列,并在分流包里注明“未覆盖”。

一次分流要花多少?

Chat、检索和 X 搜索按用量计费;当前费率在价格页面,prompt 会先请求确认。

相关 workflow

改成你的版本

同一套配方,按你实际的交付方式调整。

只做分类

不生成回复,只输出意图、优先级和建议队列。

接入工单系统

通过你自己的 webhook 把分流包写进工单,不自动发送。

升级为知识库回答

文档命中时,交给客服知识库 workflow 生成更完整的回答。

这套配方背后的参考页面,以及下一步可以看什么。