ドキュメント

Choice

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、そして自分で選んだ質問 ID から質問オブジェクトへのマップである questions です。各 Choice 質問は次のフィールドを持ちます:

  • type:常に "choice"。
  • instructions:モデルが答える質問。
  • criteria:答えの選択肢をマップとして。各キーが選択肢の名前で、各値がその選択肢の説明です。

以下は、状態がオンライン靴店のサポートチケットで、質問がどのチームが扱うべきかであるリクエストです:

request
{
  "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 を直接呼び出します。コーディングエージェントに連携コードを書いてもらう場合は、先に TypeSafe agent skill をインストールして、リクエストと応答の形を把握させてください。

応答の構造

応答は、リクエストの ID の下に、質問ごとに answers のエントリを一つ持ちます。これは上の例のリクエストへの応答です:

{
  "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:probabilities がどれほど広がっているかから計算される 0 から 1 の数値。確率が複数の選択肢に広がった平たい形は低い信頼度を意味します。一つの選択肢に単一のピークがあると高い信頼度を意味します。

このチケットは簡単なので、確率はすべて returns にあり、信頼度は 1.0 です。サイズ違いと返金漏れに言及したチケットは、確率を returns と billing に分け、信頼度が下がるでしょう。

良い実践:一回の呼び出しで複数の質問をする

Choice 質問を、コードが必要とするかもしれないすべて、質問ごとに一つのリクエストではなく一つのリクエストで尋ねます。質問は並列に評価されます。質問を増やしても応答時間はほとんど変わらず、コードは不要な答えを無視できます。余分な質問にもトークンのコストはかかります。複数の質問をまとめて尋ねるで詳しく説明しています。次の節では、五つの Choice 質問を一つの呼び出しで示します。

同じ論理は、一つの Choice 質問内の選択肢にも当てはまります。Choice 質問は最大 255 の選択肢を受け付け、選択肢を追加するとそれぞれ数トークンのコストがかかるので、チーム、カテゴリ、製品の完全なリストをモデルに与え、候補リストに絞らないようにします。リストがあらゆる入力をカバーしない可能性があるときは other や none of the above の選択肢を加え、モデルがどれも当てはまらないと言えるようにします。

文書を深い階層や大きな分類体系を通じて分類するには、Choice 質問をレベルごとに連鎖させます。階層分類クックブックでは、Choice の確率に対してビームサーチを実行し、単一の貪欲な経路にコミットせず各レベルで最良の K 個の候補経路を保持する方法を示しています。

より複雑な例

上の基本例はチケットをチームにルーティングします。より大きなサポートシステムでは、返品理由、配送の問題、顧客が望むこと、顧客の口調も必要かもしれません。

下のリクエストは、最初のものより曖昧なチケットについて五つの Choice 質問を尋ねます。それは三つのチームに関わり、顧客が何を望んでいるかを述べていません。

request
{
  "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 の答えは確率 0.61 で returns ですが、二重請求のため billing が 0.35 あります。チケットは二つのチームに属し、分かれた信頼度 0.42 がそれを反映しています。
  • return_reason は信頼度 1.0 で wrong_size です。チケットにこれが明確に書かれているので予想どおりです。
  • shipping_issue の答えは delayed と other に分かれています。これは投機的な質問で、department は shipping として返らなかったので、下のコード例が示すようにコードが無視できます。
  • requested_resolution の答えは 0.40 で refund に傾き、replacement と exchange が残りのほとんどを分け合い、信頼度は 0.20 です。二重請求は返金を示唆し、サイズ違いは交換を示唆し、顧客はどちらを望むかを一度も言っていません。
  • tone の答えは確率 0.84、信頼度 0.76 で frustrated です。

下のコード例は、必要な答えを読み、残りを無視し、低信頼度の答えを行動ではなく問い合わせの理由として扱います:

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)

上のチケットに対して、これはチケットを returns チームに issue wrong_size で割り当て、billing チームの 0.35 の取り分が 0.25 のしきい値を超えているのでそのコピーを送り、resolution の信頼度 0.20 が 0.5 未満なので顧客に何を望むか尋ねます。コードは shipping_issue の答えを使いません。

一つのリクエスト、五つの答え、そしてルーティングロジックは普通の if 文です。後で顧客の言語や、チケットがどの製品についてかを知る必要が出たら、TRIAGE_QUESTIONS に別の Choice 質問を加えます。リクエスト数は 1 のままです。

スマートホームアシスタントのデモは、すべてのユーザーリクエストを、長い Choice 質問のリストに対して一度の呼び出しで評価します。リクエストのカテゴリ、部屋、デバイス、アクションです。それらの質問のほとんどはどの一つのリクエストにも無関係で、コードはそれらを無視します。

構造化された instructions と criteria

まず選択肢ごとに一行の説明から始めます。二つの選択肢が似ていてモデルが混同し続けるときは、それぞれを文字列ではなくオブジェクトで説明します。その選択肢がカバーするもの、隣の選択肢に属するもの、いくつかの入力例のフィールドを与えます。

下の二つの選択肢、return_policy と return_status は混同しやすいです。どちらについてのチケットも返品と返金に言及しうるので、各選択肢が何のためでないかを述べます。

request
{
  "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?"
          ]
        }
      }
    }
  }
}

応答は信頼度 1.0 で return_status です:

{
  "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 の一部ではなく、どれも予約されていません。選択肢の名前を選ぶのと同じように、自分で選びます。モデルは名前を値と一緒に見るので、後に続くものを示す短い名前を使ってください。