Skip to main content

Choice

机器翻译译自英文(docs.typesafe.ai)本页 40 段已全部译完

文档索引

在以下地址获取完整的文档索引:https://docs.typesafe.ai/llms.txt 使用此文件可以在进一步探索之前发现所有可用页面。

Choice

Choice 是一种 System One 问题类型,用于从预定义集合中选择一项选项。答案包含所选选项、每个选项的概率以及置信度。

当答案来自固定选项集时,请使用 Choice。例如,哪个团队处理工单、产品属于哪个类别,或代码片段使用哪种语言编写。如果答案是光谱上的一个位置,请使用 Score。如果是是非题,请使用 Noul。选择问题类型 比较了这三种类型。

Choice 答案是 choice 字段中选中的选项。模型还会通过 probabilities 返回每个选项的概率,以及针对所选选项的 confidence 值。

示例问题:

"What programming language is this code written in"
  → options: python, javascript, typescript, go, rust, other

"What type of meeting is this based on the title and description"
  → options: standup, planning, retrospective, one on one, brainstorm, none of the above

"Which product category does this item belong to"
  → options: electronics, clothing, home garden, food and beverage

请求结构

发送至 TypeSafe API 的 POST 请求体具有特定结构。顶级包含三个字段:state(待评估的内容);model;以及 questions(从你选择的问题 ID 到问题对象的映射)。每个 Choice 问题包含以下字段:

  • type:始终为 "choice"。
  • instructions:模型需要回答的问题。
  • criteria:答案选项,以映射形式表示。每个键为选项名称,每个值为该选项的描述。

以下是一个请求,其中状态是某在线鞋店的售后工单,问题为哪个团队应该处理它:

{
  "state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    }
  }
}

问题 ID 由你决定,在此例中为 department。答案将以相同 ID 返回。模型不会看到问题 ID。选项名称及其描述都会发送给模型,因此请撰写能明确区分各选项的描述。

我们的 客户端 SDK 提供类型化的问题。在 Python 中,相同的问题是一个 Choice:

from typesafe_sdk import Choice, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
        questions={
            "department": Choice(
                instructions="Which team should handle this?",
                criteria={
                    "returns": "Exchanges, wrong or damaged items",
                    "shipping": "Delivery status, delays, lost packages",
                    "billing": "Charges, invoices, payment problems",
                },
            ),
        },
    )

    print(response.answers["department"].choice)

使用 system_one 方法或 https://api.typesafe.ai/v1/systemone 端点调用 System One 模型。model 字段用于选择处理请求的模型。如何构建 TypeSafe 应用 介绍了在代码中何处进行调用。

使用我们的 客户端 SDK 或直接调用 HTTP API。如果由 coding agent 为你编写集成,请先安装 TypeSafe agent skill,以便其了解请求和响应的结构。

Note

instructions 和 criteria 中的每个条目可以是字符串、对象或数组。建议从字符串开始。当描述需要多种指导(例如选项涵盖范围、未涵盖范围及示例)时,请使用对象。详见下方的 结构化指令与准则 以及 API 参考。

响应结构

响应的 answers 中每个问题都有一个条目,对应请求中的 ID。这是对上述示例请求的响应:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 1.0,
      "probabilities": {
        "shipping": 0.0,
        "returns": 1.0,
        "billing": 0.0
      }
    }
  },
  "usage": {
    "input_tokens": 328,
    "output_tokens": 34
  }
}

除了 type 之外,每个 Choice 答案还有三个值:

  • choice:概率最高的选项。
  • probabilities:涵盖所有选项的完整概率分布。所有值之和为 1。
  • confidence:一个从 0 到 1 的数字,根据 probabilities 的分散程度计算得出。概率分散在多个选项上的平坦形态表示低置信度;单一选项出现峰值则表示高置信度。

这张工单很简单,因此所有概率都集中在 returns 上,置信度为 1.0。若工单提到尺码错误且退款未到,则概率会在 returns 和 billing 之间分散,置信度也会下降。

最佳实践:每次调用询问多个问题

在一次请求中提出所有代码可能需要的 Choice 问题,而不是为每个问题单独发送请求。问题是并行评估的。添加问题几乎不会改变响应时间,而代码可以忽略不需要回答的部分。额外的提问依然会产生 Token 费用。一次性询问多个问题 对此有完整说明;下一部分展示了一次调用中的五个 Choice 问题。

同一逻辑也适用于单个 Choice 问题内部的选项。Choice 问题最多接受 255 个选项,且每项选项仅消耗少量 tokens,因此应向模型提供完整的团队、类别或产品列表,而非简短列表。当列表可能无法覆盖所有输入时,请添加 other 或 none of the above 选项,以便模型能够表明其他选项均不适用。

若要透过深层层级结构或大型分类体系对文件进行分类,请逐层串联 Choice 问题。层次分类 cookbook 展示了如何对 Choice 概率执行束搜索(beam search),在每一层级保留最佳的 K 个候选路径,而非锁定单条贪婪路径。

更复杂的示例

上述基础示例将工单路由至某个团队。更大的支持系统可能还需要退货原因、配送问题、客户期望以及客户语气。

下方的请求针对一张比前者更模糊的工单提出了五个 Choice 问题:它涉及三个团队,且未说明客户想要什么。

{
  "state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges of $120 on my card. What are you going to do about this?",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    },
    "return_reason": {
      "type": "choice",
      "instructions": "If the customer wants to return something, why?",
      "criteria": {
        "wrong_size": "The item doesn't fit",
        "wrong_item": "A different product was delivered",
        "damaged": "The item arrived broken or faulty",
        "changed_mind": "The item is fine, the customer no longer wants it",
        "other": "A return reason that fits none of the above"
      }
    },
    "shipping_issue": {
      "type": "choice",
      "instructions": "If this is a shipping problem, which kind is it?",
      "criteria": {
        "not_delivered": "The package never arrived",
        "delayed": "The package is late but still on its way",
        "wrong_address": "The package went to the wrong place",
        "damaged_in_transit": "The package arrived damaged",
        "other": "A shipping problem that fits none of the above"
      }
    },
    "requested_resolution": {
      "type": "choice",
      "instructions": "What does the customer want to happen?",
      "criteria": {
        "exchange": "Swap the item for a different one",
        "refund": "Money back",
        "replacement": "The same item sent again",
        "information": "Just an answer, no action needed"
      }
    },
    "tone": {
      "type": "choice",
      "instructions": "What is the customer's tone?",
      "criteria": {
        "calm": null,
        "frustrated": null,
        "angry": null
      }
    }
  }
}

其中两个 Choice 问题是推测性的:return_reason 仅在 department 为 returns 时才重要,而 shipping_issue 仅在部门是 shipping 时才重要。tone 问题使用 null 描述,因为选项名称本身已足够清晰。

TypeSafe 的响应:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 0.42,
      "probabilities": {
        "shipping": 0.04,
        "billing": 0.35,
        "returns": 0.61
      }
    },
    "return_reason": {
      "type": "choice",
      "choice": "wrong_size",
      "confidence": 1.0,
      "probabilities": {
        "other": 0.0,
        "wrong_size": 1.0,
        "changed_mind": 0.0,
        "damaged": 0.0,
        "wrong_item": 0.0
      }
    },
    "shipping_issue": {
      "type": "choice",
      "choice": "delayed",
      "confidence": 0.67,
      "probabilities": {
        "wrong_address": 0.0,
        "other": 0.26,
        "not_delivered": 0.0,
        "damaged_in_transit": 0.0,
        "delayed": 0.74
      }
    },
    "requested_resolution": {
      "type": "choice",
      "choice": "refund",
      "confidence": 0.2,
      "probabilities": {
        "replacement": 0.34,
        "refund": 0.4,
        "information": 0.02,
        "exchange": 0.24
      }
    },
    "tone": {
      "type": "choice",
      "choice": "frustrated",
      "confidence": 0.76,
      "probabilities": {
        "frustrated": 0.84,
        "angry": 0.16,
        "calm": 0.0
      }
    }
  },
  "usage": {
    "input_tokens": 589,
    "output_tokens": 212
  }
}

每个问题都针对工单独立作答:

  • department 答案为 returns,概率为 0.61,但由于存在重复收费,billing 的概率为 0.35。该工单属于两个团队,0.42 的分裂置信度反映了这一点。
  • return_reason 为 wrong_size,置信度为 1.0,这在工单中明确说明,符合预期。
  • shipping_issue 答案在 delayed 和 other 之间分裂。这是一个推测性问题,且 department 未返回为 shipping,因此代码可以忽略它,如下方示例代码片段所示。
  • requested_resolution 答案倾向于 refund(0.40),replacement 和 exchange 分享大部分剩余概率,置信度为 0.20。重复收费暗示退款,尺码错误暗示换货,但客户从未说明他们想要哪种。
  • tone 答案为 frustrated,概率为 0.84,置信度为 0.76。

下方的示例代码读取所需答案,忽略其余内容,并将低置信度的答案视为询问而非采取行动的理由:

from typesafe_sdk import Choice, TypeSafeClient

TRIAGE_QUESTIONS = {
    "department": Choice(
        instructions="Which team should handle this?",
        criteria={
            "returns": "Exchanges, wrong or damaged items",
            "shipping": "Delivery status, delays, lost packages",
            "billing": "Charges, invoices, payment problems",
        },
    ),
    "return_reason": Choice(
        instructions="If the customer wants to return something, why?",
        criteria={
            "wrong_size": "The item doesn't fit",
            "wrong_item": "A different product was delivered",
            "damaged": "The item arrived broken or faulty",
            "changed_mind": "The item is fine, the customer no longer wants it",
            "other": "A return reason that fits none of the above",
        },
    ),
    "shipping_issue": Choice(
        instructions="If this is a shipping problem, which kind is it?",
        criteria={
            "not_delivered": "The package never arrived",
            "delayed": "The package is late but still on its way",
            "wrong_address": "The package went to the wrong place",
            "damaged_in_transit": "The package arrived damaged",
            "other": "A shipping problem that fits none of the above",
        },
    ),
    "requested_resolution": Choice(
        instructions="What does the customer want to happen?",
        criteria={
            "exchange": "Swap the item for a different one",
            "refund": "Money back",
            "replacement": "The same item sent again",
            "information": "Just an answer, no action needed",
        },
    ),
    "tone": Choice(
        instructions="What is the customer's tone?",
        criteria={"calm": None, "frustrated": None, "angry": None},
    ),
}


def triage(ticket: str) -> None:
    with TypeSafeClient() as client:
        response = client.system_one(
            state=ticket,
            questions=TRIAGE_QUESTIONS,
        )
    answers = response.answers

    department = answers["department"]
    if department.confidence < 0.3:
        # Not clear which team to send to. Let a person decide.
        send_to_manual_triage(ticket)
        return

    if department.choice == "returns":
        # return_reason answer is only used here
        assign(ticket, team="returns", issue=answers["return_reason"].choice)
    elif department.choice == "shipping":
        # shipping_issue answer is only used here
        assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
    else:
        assign(ticket, team="billing")

    # A second team with a real share of the probability gets a copy
    for team, probability in department.probabilities.items():
        if team != department.choice and probability > 0.25:
            notify(ticket, team=team)

    resolution = answers["requested_resolution"]
    if resolution.confidence < 0.5:
        # The customer hasn't said what they want. Ask, don't guess.
        ask_customer_what_they_want(ticket)
    elif resolution.choice == "refund":
        flag_for_refund_approval(ticket)

    if answers["tone"].choice == "angry":
        flag_for_senior_agent(ticket)

针对上述工单,这将工单分配给退货团队,问题为 wrong_size,由于 0.35 的占比超过 0.25 的阈值,还会向账单团队发送副本,并询问客户他们的期望,因为解决方案的置信度 0.20 低于 0.5。代码不使用 shipping_issue 答案。

一个请求,五个答案,路由逻辑是普通的 if 语句。如果后续需要知道客户的语言或工单涉及的产品,只需在 TRIAGE_QUESTIONS 中添加另一个 Choice 问题;请求数量仍为一个。

智能家居助手演示 一次性将每个用户请求与大量 Choice 问题进行比对:请求类别、房间、设备和操作。其中大多数问题与任何单一请求无关,代码会忽略它们。

结构化指令与准则

每个选项从单行描述开始。当两个选项相似且模型经常混淆它们时,请使用对象而非字符串来描述每个选项。为其提供该选项涵盖的内容、属于相邻选项的内容以及几个示例输入等字段。

下方的两个答案选项 return_policy 和 return_status 容易混淆。关于这两者的工单都可能提到退货和退款,因此每个选项都说明了其不适用的情况。

{
  "state": "I sent the shoes back a week ago. When do I get my money?",
  "questions": {
    "return_topic": {
      "type": "choice",
      "instructions": {
        "question": "Which returns topic is the customer asking about?",
        "focus": "Classify the information the customer wants."
      },
      "criteria": {
        "return_policy": {
          "what": "Whether and how an item can be returned",
          "not_for": "Progress of a return already sent",
          "examples": [
            "Can I return shoes I've worn once?",
            "How long do I have to return an order?"
          ]
        },
        "return_status": {
          "what": "Progress of a return already sent",
          "not_for": "Whether and how an item can be returned",
          "examples": [
            "Has my return arrived yet?",
            "When will my refund be paid?"
          ]
        }
      }
    }
  }
}

响应为 return_status,置信度为 1.0:

{
  "model": "jev-1.13.0",
  "answers": {
    "return_topic": {
      "type": "choice",
      "choice": "return_status",
      "confidence": 1.0,
      "probabilities": {
        "return_policy": 0.0,
        "return_status": 1.0
      }
    }
  },
  "usage": {
    "input_tokens": 407,
    "output_tokens": 32
  }
}

字段名称 question、focus、what、not_for 和 examples 并非 API 的一部分,也非保留字。你可以自行选择它们,就像选择选项名称一样。模型会看到名称及其对应的值,因此请使用能标注后续内容的简短名称。