概要 / primitive

Choice

「この中のどれ?」に答えるプリミティブ。あなたが定義した選択肢の集合から1つを選び、全選択肢の確率分布confidence を一緒に返します。

基本の形

靴のオンラインストアに届いた問い合わせを、どのチームが対応すべきか判定する例です。

requestPOST /v1/systemone
{
  "state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
  "model": "jev-latest",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, refunds, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    }
  }
}
response200 OK
{
  "model": "jev-latest",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 1.0,
      "probabilities": {
        "shipping": 0.0,
        "returns": 1.0,
        "billing": 0.0
      }
    }
  },
  "usage": { "input_tokens": 330, "output_tokens": 34 }
}
  • criteria「選択肢名 → 説明」のマップ。名前も説明もモデルに送られるので、選択肢同士を区別できる説明を書く。名前だけで明確なら説明は null でよい。
  • choice確率が最も高い選択肢。
  • probabilities全選択肢にわたる確率分布。合計は 1。
  • confidence分布の「尖り具合」から計算される 0〜1 の値。1つに集中していれば高く、複数に散っていれば低い。

見るべきは「1位が何か」だけでなく「分布の形」

同じ choice でも、分布の形によって取るべき行動は変わります。下の3つはすべて、次の節で扱う1件の曖昧なチケットに対する実際の回答です。

尖った分布 · conf 1.00
wrong_size1.00
wrong_item0.00
damaged0.00
changed_mind0.00
other0.00
返品理由。チケットに「サイズ違い」と明記されている。→ そのまま自動処理。
二峰の分布 · conf 0.39
returns0.60
billing0.38
shipping0.02
担当チーム。1位は返品だが、二重請求のせいで請求も 0.38。2位はノイズではない。→ 返品へ回し、請求チームにもコピー。
平らな分布 · conf 0.16
exchange0.37
refund0.29
replacement0.24
information0.10
希望する解決策。顧客が何を望むか書いていない。→ 推測せず、顧客に尋ねる。

実例:1件の曖昧なチケットに、5つの Choice を同時に

3チームにまたがり、しかも要望が書かれていないチケットです。

state
“Shoes arrived two weeks late and in the wrong size. Also I see two charges on my card. What are you going to do about this?”
(靴が2週間遅れて届き、サイズも違う。カードには2回請求されている。どうしてくれるのか?)
質問 ID問いchoiceconfidence読み方
departmentどのチームが対応すべきかreturns 0.600.39billing が 0.38。行動はできるが、2位も本物
return_reason返品したいなら、その理由は 投機的wrong_size 1.001.00チケットに明記されている
shipping_issue配送の問題なら、どの種類か 投機的delayed 0.630.53担当が配送にならなかったので、コードは使わない
requested_resolution顧客は何を望んでいるかexchange 0.370.16平らな分布。顧客は希望を述べていない
tone顧客の口調はfrustrated 0.920.88選択肢名が自明なので説明は null

投機的な質問とは、「担当が返品だった場合にだけ意味を持つ」ような、使うかどうか分からない質問のこと。質問は並列に評価されるので、先に全部聞いておき、使うかどうかはコードが後で決めます。使用トークンは入力 588・出力 212 で、リクエストは1回です。

コードでの使い方

ルーティングのロジックは、ごく普通の if 文になります。確信の低い答えは「行動する理由」ではなく「尋ねる理由」として扱います。

triage.py公式ドキュメントより(抜粋)
answers = response.answers

department = answers["department"]
if department.confidence < 0.3:
    # どのチームか不明瞭。人に決めてもらう
    send_to_manual_triage(ticket)
    return

if department.choice == "returns":
    # return_reason の答えはここでだけ使う
    assign(ticket, team="returns", issue=answers["return_reason"].choice)
elif department.choice == "shipping":
    # shipping_issue の答えはここでだけ使う
    assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
else:
    assign(ticket, team="billing")

# 確率をそれなりに持つ2番手のチームにもコピーを送る
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:
    # 顧客は希望を述べていない。推測せず、尋ねる
    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)
このチケットで実際に起きること
返品チームに割り当てissue = wrong_size
請求チームにコピーを通知billing の確率 0.38 > 0.25
顧客に希望を尋ねるrequested_resolution の confidence 0.16 < 0.5
shipping_issue は無視配送の分岐に入らなかったため
後から「顧客の言語」や「対象の製品」も知りたくなったら、質問を1つ足すだけ。リクエスト回数は1回のままです。

書き方のコツ

選択肢は絞らず、全部渡す

1つの Choice は最大 255 個の選択肢を受け付け、1つ足すコストは数トークン。候補を事前に絞り込まず、チーム・カテゴリ・製品の全リストを渡す。

逃げ道を用意する

リストが入力を網羅しないかもしれないなら othernone of the above を足す。モデルが「どれにも当てはまらない」と言えるようになる。

似た選択肢は対比で書く

取り違えが起きるなら、説明を文字列からオブジェクトに変え、what(何を含むか)、not_for(隣の選択肢のもの)、examples を書く。詳しくは 構造化した書き方

深い階層はレベルごとに連鎖させる

巨大なタクソノミーは、Choice を階層ごとに連鎖。各レベルで確率上位 K 本の経路を残すビームサーチにすれば、1本道の貪欲法で早々に間違える事態を避けられる(階層分類クックブック)。

他の型との使い分け答えがスペクトラム上の位置なら Score、Yes/No なら Noul。複数のラベルが同時に当てはまりうるなら、1つの Choice ではなくラベルごとに Noul を1問ずつ。