基本の形
靴のオンラインストアに届いた問い合わせを、どのチームが対応すべきか判定する例です。
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件の曖昧なチケットに対する実際の回答です。
実例: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 | 問い | choice | confidence | 読み方 |
|---|---|---|---|---|
department | どのチームが対応すべきか | returns 0.60 | 0.39 | billing が 0.38。行動はできるが、2位も本物 |
return_reason | 返品したいなら、その理由は 投機的 | wrong_size 1.00 | 1.00 | チケットに明記されている |
shipping_issue | 配送の問題なら、どの種類か 投機的 | delayed 0.63 | 0.53 | 担当が配送にならなかったので、コードは使わない |
requested_resolution | 顧客は何を望んでいるか | exchange 0.37 | 0.16 | 平らな分布。顧客は希望を述べていない |
tone | 顧客の口調は | frustrated 0.92 | 0.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.5shipping_issue は無視配送の分岐に入らなかったため書き方のコツ
選択肢は絞らず、全部渡す
1つの Choice は最大 255 個の選択肢を受け付け、1つ足すコストは数トークン。候補を事前に絞り込まず、チーム・カテゴリ・製品の全リストを渡す。
逃げ道を用意する
リストが入力を網羅しないかもしれないなら other や none of the above を足す。モデルが「どれにも当てはまらない」と言えるようになる。
似た選択肢は対比で書く
取り違えが起きるなら、説明を文字列からオブジェクトに変え、what(何を含むか)、not_for(隣の選択肢のもの)、examples を書く。詳しくは 構造化した書き方。
深い階層はレベルごとに連鎖させる
巨大なタクソノミーは、Choice を階層ごとに連鎖。各レベルで確率上位 K 本の経路を残すビームサーチにすれば、1本道の貪欲法で早々に間違える事態を避けられる(階層分類クックブック)。