Felo API PlatformFelo API Platform

客服

用你自己的文档回答客服问题

创建一个 LiveDoc,导入帮助中心和内部 runbook,检索能回答工单的段落,再让模型只根据这些证据回答。

构建时间20 分钟难度入门更新于2026-10-29
适合谁客服团队客户成功Helpdesk 构建者

复制 prompt

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

只用我们的文档回答这个支持问题:[客户问题]。

知识库:[帮助中心 URL 或 LiveDoc short_id]。升级路径:[负责人或队列]。
可用客户上下文:[套餐、地区、产品](不要索取密码或支付数据)。

输出:
1. 两到四句直接回答。
2. 支撑回答的文档段落,附资源标题。
3. 客户的下一步。
4. 内部备注:使用的资源和相关度分数。

规则:
- 只根据检索到的段落回答。没有命中或相关度低时升级,绝不猜测。
- 不要编造政策、退款、定价或账户操作。
- 不要引入客户私密数据、凭证或支付信息。
- 在消耗额度做数据导入或检索前先询问。
- 不要把回答发送给客户;返回给我审阅。

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

你会得到什么

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

回答示例(节选)

回答:你的方案在每月 1 日续费。银行卡扣款失败后会重试三天,然后席位暂停。

来源:“账单常见问题” - https://example.com/help/billing

如果这里没有覆盖:带上发票 ID 升级到账单队列。

实际调用

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

展开实际 API 调用
  1. 创建知识库

    用 LiveDoc API 创建一个知识库,保存 short_id。

    support.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>;
    }
    
    type LiveDocData = { data: { short_id: string } };
    
    const kb = await felo<LiveDocData>("/v2/livedocs", {
      name: "Support knowledge base",
      description: "Help center, policy pages, and internal runbooks",
    });
    
    const kbId = kb.data.short_id; // save this in your config

    示例输出

    {
      "short_id": "PvyKouzJirXjFdst4uKRK3",
      "name": "Support knowledge base",
      "description": "Help center, policy pages, and internal runbooks"
    }
  2. 导入公开帮助中心

    把帮助中心 URL 作为资源加进 LiveDoc。

    support.ts
    type UrlResult = {
      data: {
        url: string;
        status: string;
        fail_reason: string | null;
        resource_id: string | null;
      }[];
    };
    
    const added = await felo<UrlResult>(
      `/v2/livedocs/${kbId}/resources/urls`,
      {
        urls: [
          "https://example.com/help/getting-started",
          { url: "https://example.com/help/billing", title: "Billing FAQ" },
        ],
      },
    );
    
    const failed = added.data.filter((item) => item.status === "failed");

    示例输出

    [
      { "url": "https://example.com/help/getting-started", "status": "success", "resource_id": "res_003" },
      { "url": "https://example.com/help/billing", "status": "existed", "resource_id": "res_004" }
    ]
  3. 补充非公开文档

    把内部 runbook 作为文档资源加进去。

    support.ts
    type ResourceData = { data: { id: string } };
    
    const runbook = `# Refund policy
    
    Refunds are available within 30 days of purchase. Requests after 30 days
    need a manual review by the billing team.
    `;
    
    const policy = await felo<ResourceData>(
      `/v2/livedocs/${kbId}/resources/doc`,
      { title: "Refund policy", content: runbook },
    );
    
    console.log(policy.data.id); // use this for updates
  4. 检索问题相关证据

    用语义检索拿到最相关的段落和分数。

    support.ts
    type Hit = { id: string; title: string; content: string; score: number };
    
    async function findEvidence(question: string) {
      const res = await felo<{ data: Hit[] }>(
        `/v2/livedocs/${kbId}/resources/retrieve`,
        { query: question },
      );
      return res.data.filter((hit) => hit.score >= 0.7).slice(0, 4);
    }
    
    const question = "Can I get a refund after 45 days?";
    const hits = await findEvidence(question);

    示例输出

    [
      {
        "id": "res_005",
        "title": "Refund policy",
        "content": "Refunds are available within 30 days of purchase…",
        "score": 0.92
      }
    ]
  5. 只基于证据回答

    让模型只使用检索到的段落,并在没有命中时升级。

    support.ts
    type Completion = {
      choices: { message: { content: string | null } }[];
      usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
    };
    
    const context = hits
      .map((hit) => `### ${hit.title}\n${hit.content}`)
      .join("\n\n");
    
    const completion = await felo<Completion>("/api/v1/chat/completions", {
      model: "anthropic/claude-haiku-5.5",
      messages: [
        {
          role: "system",
          content:
            "Answer only from the context. If the context does not cover the question, say so and hand the ticket to a human.",
        },
        {
          role: "user",
          content: `Context:\n${context}\n\nQuestion: ${question}`,
        },
      ],
    });
    
    console.log(completion.choices[0].message.content);
    console.log(completion.usage.total_tokens);

    示例输出

    "No. The Refund policy says refunds are available within 30 days of purchase, so a request at 45 days needs a manual review by the billing team."

包含什么

  • 由帮助中心和内部 runbook 构建的 LiveDoc
  • 带段落和标题的语义检索
  • 有依据、可引用的回答
  • 文档不覆盖时明确的升级路径

不做什么

  • 只从你的文档回答,不浏览开放网络
  • 不执行账户操作、退款或改套餐
  • 不能把私有客户数据或凭证放进知识库
  • 文档过时会导致回答过时,需要定期刷新
  • 它只起草回答,不直接发给客户

常见问题

回答从哪里来?

只来自你构建的 LiveDoc:帮助中心页面和 runbook。检索返回段落,agent 再基于段落写回答。

文档没有覆盖时怎么办?

升级。没有命中或相关度过低就视为未覆盖,而不是允许模型猜。

怎么保持知识库最新?

政策变化时重新添加或更新 URL 和文档资源;workflow 包含更新和审计命令。

可以接进工单队列吗?

可以,作为起草步骤。发送给客户之前保留人工审批。

能处理账户特定问题吗?

不能只靠文档。账户数据在你的系统里;只传 agent 需要且安全的信息,绝不要传凭证。

一次要花多少?

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

相关 workflow

改成你的版本

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

只导入一个产品线

把范围缩到一个产品,知识库更干净。

加入工单分类

在回答前先按问题类型分类,再检索对应文档。

定期审计

每月检查一次失效链接和旧政策。

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