用 Jev 分流客服工单:四类队列、缺失信息与人工复核

搭建一个可测试的小型工单分流器:用 Choice 判断账单、技术、销售或其他,用 Noul 标记缺失信息,再由代码决定何时交给人工。

本文范围:请求格式与应用代码依据 2026-09-25 核对的 TypeSafe 官方文档。文中的工单、标注和阈值都是演示业务规则的合成示例。本页不提供模型输出、准确率、速度或费用对比;上线前请用你自己的标注数据测量。

1. 先划定分流器能做什么

这里的分流器只做两件事:选择处理队列、标记缺失信息。它不写回复、不退款、也不修改账户,这些操作应继续放在正常权限控制之后。Jev 返回类型化答案:Choice 返回选中的选项、每个选项的概率和 confidence;Noul 返回答案为“是”的概率,取值 0 到 1。有了这种结构,下面的判断就能交给代码可靠地执行。

2. 写清四个队列的边界

下表是用于说明的示例业务规则,请按你们的实际分工修改。

队列负责转交条件
billing已发生的扣款、发票、退款、付款方式、续费失败客户在购买前询问价格(转 sales)
technical故障、服务中断、错误提示、登录问题、集成报错问题本质是一笔已经发生的扣款(转 billing)
sales价格咨询、套餐比较、新购买、报价客户已在付费,询问的是某笔扣款(转 billing)
other合作、媒体采访、求职、一般意见不自动关闭,必须有人阅读

把这些边界写进选项描述里。TypeSafe 的 Jev 1.13 已知局限页面指出,Jev 会按字面理解指令。看到错误答案时,如果你想解释“其实我的意思是……”,那句解释就是 criteria 里缺少的内容。一定要保留 other 这个真实选项,这样哪一类都不符合的工单不会被硬塞进最接近的队列。

3. 从请求实验室的示例开始

打开实验室里的 support-routing 示例,会载入下面这个请求。你可以修改内容,复制 JSON 或 cURL;只有真正发送时才需要自己的 API 密钥。实验室示例使用英文内容,与本站英文版一致;换成中文工单前,请先读第 6 节关于语言测试的说明。

{
  "model": "jev-1.13.0",
  "state": {
    "message": "I was charged twice for the same subscription this month."
  },
  "questions": {
    "decision": {
      "type": "choice",
      "instructions": "Which team should handle this request? Choose other if none of the specific teams fits.",
      "criteria": {
        "billing": "Payments, charges, invoices or refunds",
        "technical": "Bugs, outages or integration errors",
        "sales": "Pricing or a new purchase",
        "other": "A request outside these teams"
      }
    }
  }
}

cURL 从环境变量读取密钥,请求体通过带引号的 heredoc 传入:

curl --fail-with-body --max-time 20 https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @- <<'JEV_ATLAS_REQUEST'
{
  "model": "jev-1.13.0",
  "state": {
    "message": "I was charged twice for the same subscription this month."
  },
  "questions": {
    "decision": {
      "type": "choice",
      "instructions": "Which team should handle this request? Choose other if none of the specific teams fits.",
      "criteria": {
        "billing": "Payments, charges, invoices or refunds",
        "technical": "Bugs, outages or integration errors",
        "sales": "Pricing or a new purchase",
        "other": "A request outside these teams"
      }
    }
  }
}
JEV_ATLAS_REQUEST

在生产环境中,把队列判断和缺失信息判断放在同一次请求里。两个问题会针对同一个 state 各自独立评估,一个答案不会成为另一个问题的上下文,组合逻辑由代码负责。teamneeds_details 是你自己定义的 ID,答案会用同样的 ID 返回。

{
  "model": "jev-1.13.0",
  "state": {
    "subject": "Charged twice this month",
    "message": "I was charged twice for the same subscription this month. The invoice numbers end in 4471 and 4472.",
    "channel": "email"
  },
  "questions": {
    "team": {
      "type": "choice",
      "instructions": "Which team should handle this support request? Judge the customer's main request. Choose other if none of the specific teams fits.",
      "criteria": {
        "billing": "Existing charges, invoices, refunds, payment methods or failed payments on an existing account",
        "technical": "Bugs, outages, error messages, login problems or integration failures",
        "sales": "Pricing questions before purchase, plan comparisons, new purchases or quotes",
        "other": "Anything outside these three teams, such as partnerships, press or general feedback"
      }
    },
    "needs_details": {
      "type": "noul",
      "instructions": "Does this request lack a detail that a support agent needs before starting work, such as which account, order, product or error it is about?",
      "criteria": {
        "true": "At least one detail needed to start work is missing",
        "false": "The request identifies what it is about well enough to start work"
      }
    }
  }
}

4. 明确处理缺失信息

needs-clarification 示例 用一个 Noul 问:这条请求是否缺少必要信息?

{
  "model": "jev-1.13.0",
  "state": {
    "request": "Please send it to the other address.",
    "known_context": "No document or recipient address has been specified."
  },
  "questions": {
    "decision": {
      "type": "noul",
      "instructions": "Does this request need additional context to identify both what should be sent and its destination?",
      "criteria": {
        "true": "At least one required detail is missing",
        "false": "Both the item and destination are explicitly identified"
      }
    }
  }
}

Noul 返回的是“答案为是”的概率估计,取值 0 到 1,没有 confidence。数值偏高时,不要让模型猜队列,而是针对缺少的内容向客户追问。能确定判断的事情交给代码:比如表单要求填写订单号或账户邮箱,就在调用模型之前用代码检查这个字段。

5. 阈值与人工复核由代码决定

// Node 18+ or any runtime with fetch. Example thresholds: calibrate them on your
// own labeled tickets; they are starting points, not measured values.
const ENDPOINT = 'https://api.typesafe.ai/v1/systemone';
const ROUTE_AT_CONFIDENCE = 0.8;
const ASK_AT_MISSING = 0.7;
const TEAMS = new Set(['billing', 'technical', 'sales']);

export async function classifyTicket(request, apiKey) {
  const response = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(request),
    signal: AbortSignal.timeout(20_000),
  });
  if (response.status === 429 || response.status === 529) {
    // Back off; honor retry-after when present. Do not retry in a tight loop.
    const error = new Error('typesafe_retry_later');
    error.retryAfter = response.headers.get('retry-after');
    throw error;
  }
  if (!response.ok) throw new Error(`typesafe_http_${response.status}`);
  return response.json();
}

export function decideRoute(result) {
  const team = result.answers.team;
  const missing = result.answers.needs_details.noul;
  if (missing >= ASK_AT_MISSING) return { action: 'ask_customer', reason: 'missing_details' };
  if (!TEAMS.has(team.choice)) return { action: 'human_triage', reason: 'no_specific_team' };
  if (team.confidence < ROUTE_AT_CONFIDENCE) {
    return { action: 'human_triage', reason: 'low_confidence', suggestion: team.choice };
  }
  // Log the versioned model that answered, so threshold changes stay traceable.
  return { action: 'route', queue: team.choice, confidence: team.confidence, model: result.model };
}

这三个阈值只是起点示例,不是推荐值。TypeSafe 建议把 confidence 划为高、中、低三段,并按风险设置不同阈值。分错队列的代价有限,自动退款这类影响更大的动作就需要更严格的门槛。用标注数据调整阈值,每次判断都记录 result.model;阈值调好后,固定使用 jev-1.13.0 这样的具体版本,因为 jev-latest 可能切换到新模型。

6. 先固定一小份标注集,再决定是否信任

先写下期望结果,再运行请求做对比。下表是合成工单,以及按示例规则应得的结果,并不是模型输出。

合成工单期望队列(示例规则)缺信息?
请退还发票 INV-2291 的费用,我在续费日前已经取消。billing
今天早上更新后,导出按钮一直返回 500 错误。technical
年付方案买 40 个席位有折扣吗?sales
付款页面超时了,但我的卡还是被扣款了。billing(规则:已发生扣款归 billing)是:哪一笔扣款
我能把现有套餐改成年付吗?今天会扣款吗?billing(规则:现有客户归 billing)
我想为杂志文章采访你们团队。other
用不了。暂不判断:先追问
请把它发到另一个地址。暂不判断:先追问是(needs-clarification 示例)

对每条工单记录:选中的队列、confidence、Noul 数值,以及代码最终是直接分流、追问客户还是转人工。按队列统计不一致的情况并逐条阅读,通常会发现边界描述不清,或者某条规则本该由代码处理。标注集要包含你实际收到的语言:TypeSafe 模型页面(2026-09-25 核对)说明英语是主要训练语言,其他语言需要用自己的内容测试。

采用类似做法的项目

以上条目于 2026-09-18 依据各自 README 核对来源。Jev Atlas 没有运行、测评或做安全审计。

资料来源

TypeSafe 文档核对日期:2026-09-25。模型、限额和价格可能变化,接入前请以官方为准。

动手试试

在实验室打开 support-routing · 打开 needs-clarification · 如何写出好判断的问题