Decisions API の一般提供を開始しました。パブリックベータ以降の新機能をご確認ください。
Decisions API はテキスト、画像、JSON を評価し、Responses API の約 10 倍の速さで型付きの回答を返します。条件が成立する確率、所定の選択肢から選んだ値、評価基準に沿ったスコアを取得できます。アプリケーションでは、これらの回答を使ってコンテンツを分類したり、リクエストを振り分けたり、作業の優先順位を決めたりできます。
コードを書く前に、Playground で Decisions API にさまざまな質問や入力を渡して試せます。
リクエストの送信先は POST /v1/decisions です。モデルには、現在唯一サポートされている gpt-6-luna を指定してください。
新機能
パブリックベータの開始以降、Decisions API に次の機能が加わりました。
リクエストは次の 3 つのフィールドで構成されます。
| フィールド |
用途 |
model |
リクエストを評価するモデルです。現在サポートされているのは gpt-6-luna のみです。 |
input |
各質問の判定に共通して使うデータです。テキスト文字列、またはメッセージ、ツール呼び出し、ツール出力を順に並べた配列を指定します。 |
questions |
評価する内容です。各質問の型、指示、選択可能な値やスコアの段階を指定します。 |
レスポンスには answers 配列が含まれます。各質問に一意の name を付けてください。API はレスポンスに同じ名前を含めるので、どの質問への回答かを識別できます。
| 型 |
用途 |
主な結果 |
predicate |
目に見える損傷があるか、文章の一節が質問に関連するかなど、条件の成否を判定します。 |
probability:条件が成立する確率の推定値(0~1)。 |
choice |
部署やコンテンツのカテゴリなど、選択肢から 1 つを選びます。 |
choice:指定した値のうちの 1 つ。 |
score |
問題の重大度など、順序のある段階に照らして入力を評価します。 |
score:各段階のインデックスを確率で重み付けした平均値。 |
choice と score はどちらも、個々の選択肢に対する確率を返します。部署のように順序のないカテゴリには choice を使います。重大度のように順序のある段階には score を使います。各段階の数値インデックスを確率で重み付けして平均を求めるため、スコアは段階の中間の値を取ることもあります。
アプリケーションでこれらの型の回答が必要な場合は Decisions を使います。抽出したフィールドや文章による説明など、独自の JSON スキーマに従うオブジェクトを生成する場合は、Responses API の構造化出力を使います。モデルに引数付きのツール呼び出しを要求させる場合は Function Calling を使います。
会話の評価
会話の文脈を踏まえてリクエストを評価するには、input に会話履歴を渡します。メッセージのほか、関数やカスタムツールの呼び出しとその出力があれば、発生順に含めます。各ツール出力は call_id で対応する呼び出しに関連付けます。
サポートされる内容は、メッセージのロールまたは項目の種類によって異なります。
| 入力項目 |
サポートされる内容 |
| ユーザーメッセージ |
テキスト、画像、JSON |
| 開発者メッセージまたはシステムメッセージ |
入力テキストのみ。システムメッセージは開発者メッセージとして扱われます。 |
| アシスタントメッセージ |
出力テキストまたは回答の拒否 |
| 関数またはカスタムツールの出力 |
対応するツール呼び出しに関連付けられたテキストと画像 |
ツール呼び出しとその出力は、質問に答える際の判断材料になります。Decisions が型付きの回答を返し、その回答に基づいて選ばれたアクションをアプリケーションが実行します。ファイル、音声、項目への参照はサポートされていません。指定できる入力フィールドは、判定作成の API リファレンスをご覧ください。
JSON データの評価
input_json コンテンツパートを使うと、構造化された判定用データを文字列に変換せずに渡せます。JSON オブジェクトまたは配列を data に格納し、ユーザーメッセージ内にテキストや画像のパートと並べて指定します。メッセージのコンテンツで input_json を使えるのは、ロールが user の場合だけです。
たとえば、次の JSON リクエストボディを POST /v1/decisions に送信すると、サポートチケットの内容を注文記録と照合できます。
12345678910111213141516171819202122232425262728{
"model": "gpt-6-luna",
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "Evaluate the ticket using the order evidence."
},
{
"type": "input_json",
"data": {
"ticket": { "body": "Please refund the duplicate charge." },
"order": { "charges": [49, 49] }
}
}
]
}
],
"questions": [
{
"type": "predicate",
"name": "duplicate_charge",
"instructions": "Does the ticket describe a duplicate charge supported by the order?"
}
]
}
role や image_url などの JSON キーも、data の中では通常のデータとして扱われます。これらのキーによってメッセージが作成されたり、画像が取得されたりすることはありません。回答形式は選択した質問の型で決まり、JSON 入力を使っても変わりません。
画像に写った損傷の判定
predicate 型の質問を使って、商品写真に損傷が写っているかを判定します。このリクエストでは、画像とともに、ひび割れ、破れ、へこみがないかを確認するよう指示します。
次回の更新前にサブスクリプションを解約してください。
今のプランのまま、年払いに変更できますか?
もう使わないので、今後の請求を止めたいです。どうすればいいですか?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22IMAGE_BASE64="$(base64 < product.png | tr -d '\r\n')"
curl https://api.openai.com/v1/decisions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"model": "gpt-6-luna",
"input": [{
"role": "user",
"content": [
{"type": "input_text", "text": "Inspect the product in this photo."},
{"type": "input_image", "image_url": "data:image/png;base64,$IMAGE_BASE64"}
]
}],
"questions": [{
"type": "predicate",
"name": "visible_damage",
"instructions": "Does the product have visible damage, such as a crack, tear, or dent? Ignore shadows and damage to the packaging."
}]
}
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import { readFile } from "node:fs/promises";
import OpenAI from "openai";
const client = new OpenAI();
const imageBase64 = (await readFile("product.png")).toString("base64");
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: [
{
role: "user",
content: [
{ type: "input_text", text: "Inspect the product in this photo." },
{
type: "input_image",
image_url: `data:image/png;base64,${imageBase64}`,
},
],
},
],
questions: [
{
type: "predicate",
name: "visible_damage",
instructions:
"Does the product have visible damage, such as a crack, tear, or dent? Ignore shadows and damage to the packaging.",
},
],
});
const answer = decision.answers[0];
if (answer.type === "refusal") {
console.log(`Refused: ${answer.name}`);
} else if (answer.type === "predicate") {
console.log(`Visible damage probability: ${answer.probability}`);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import base64
from pathlib import Path
from openai import OpenAI
client = OpenAI()
image_base64 = base64.b64encode(Path("product.png").read_bytes()).decode("ascii")
decision = client.decisions.create(
model="gpt-6-luna",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "Inspect the product in this photo."},
{
"type": "input_image",
"image_url": f"data:image/png;base64,{image_base64}",
},
],
}
],
questions=[
{
"type": "predicate",
"name": "visible_damage",
"instructions": (
"Does the product have visible damage, such as a crack, tear, or dent? "
"Ignore shadows and damage to the packaging."
),
}
],
)
answer = decision.answers[0]
if answer.type == "refusal":
print(f"Refused: {answer.name}")
elif answer.type == "predicate":
print(f"Visible damage probability: {answer.probability}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52package main
import (
"context"
"encoding/base64"
"fmt"
"os"
"github.com/openai/openai-go/v3"
)
func main() {
image, err := os.ReadFile("product.png")
if err != nil {
panic(err)
}
client := openai.NewClient()
decision, err := client.Decisions.New(context.Background(), openai.DecisionNewParams{
Model: "gpt-6-luna",
Input: openai.DecisionNewParamsInputUnion{
OfDecisionInputMessageArray: []openai.DecisionInputMessageParam{{
Content: openai.DecisionInputMessageContentUnionParam{
OfParts: []openai.DecisionInputPartUnionParam{
{OfInputText: &openai.DecisionInputTextParam{Text: "Inspect the product in this photo."}},
{OfInputImage: &openai.DecisionInputImageParam{
ImageURL: "data:image/png;base64," + base64.StdEncoding.EncodeToString(image),
}},
},
},
}},
},
Questions: []openai.DecisionNewParamsQuestionUnion{{
OfPredicate: &openai.DecisionNewParamsQuestionPredicate{
Name: openai.String("visible_damage"),
Instructions: openai.DecisionNewParamsQuestionPredicateInstructionsUnion{
OfString: openai.String("Does the product have visible damage, such as a crack, tear, or dent? Ignore shadows and damage to the packaging."),
},
},
}},
})
if err != nil {
panic(err)
}
switch answer := decision.Answers[0].AsAny().(type) {
case openai.DecisionAnswerPredicate:
fmt.Println(answer.Probability)
case openai.DecisionAnswerRefusal:
fmt.Printf("Decision refused for %s\n", answer.Name)
default:
panic("unexpected answer type")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51import com.openai.models.decisions.DecisionCreateParams;
import com.openai.models.decisions.DecisionInputImage;
import com.openai.models.decisions.DecisionInputItem;
import com.openai.models.decisions.DecisionInputMessage;
import com.openai.models.decisions.DecisionInputPart;
import com.openai.models.decisions.DecisionInputText;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.List;
String imageBase64 =
Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("product.png")));
var message =
DecisionInputMessage.User.builder()
.contentOfParts(
List.of(
DecisionInputPart.ofInputText(
DecisionInputText.builder()
.text("Inspect the product in this photo.")
.build()),
DecisionInputPart.ofInputImage(
DecisionInputImage.builder()
.imageUrl("data:image/png;base64," + imageBase64)
.build())))
.build();
var decision =
client
.decisions()
.create(
DecisionCreateParams.builder()
.model("gpt-6-luna")
.inputOfDecisionInputItems(
List.of(
DecisionInputItem.ofDecisionInputMessage(
DecisionInputMessage.ofUser(message))))
.addQuestion(
DecisionCreateParams.Question.Predicate.builder()
.name("visible_damage")
.instructions(
"Does the product have visible damage, such as a crack, tear, or"
+ " dent? Ignore shadows and damage to the packaging.")
.build())
.build());
var answer = decision.answers().get(0);
if (answer.isRefusal()) {
System.out.println("Refused: " + answer.asRefusal().name().orElse("visible_damage"));
} else {
System.out.println(answer.asPredicate().probability());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41require "base64"
require "openai"
image_base64 = Base64.strict_encode64(File.binread("product.png"))
client = OpenAI::Client.new
decision = client.decisions.create(
model: "gpt-6-luna",
input: [
{
role: :user,
content: [
{
type: :input_text,
text: "Inspect the product in this photo."
},
{
type: :input_image,
image_url: "data:image/png;base64,#{image_base64}"
}
]
}
],
questions: [
{
type: :predicate,
name: "visible_damage",
instructions: "Does the product have visible damage, such as a crack, tear, or dent? Ignore shadows and damage to the packaging."
}
]
)
answer = decision.answers.fetch(0)
case answer
when OpenAI::Models::Decision::Answer::Predicate
puts(answer.probability)
when OpenAI::Models::Decision::Answer::Refusal
warn("Decision refused for #{answer.name}")
else
raise("Unexpected answer type: #{answer.type}")
end
レスポンス例の抜粋です。
123456789{
"answers": [
{
"type": "predicate",
"name": "visible_damage",
"probability": 0.92
}
]
}
probability は、条件が成立する確率をモデルが推定した値です。任意のしきい値を設定し、この値を基にレビュー対象の写真を選べます。
画像はインラインの base64 データ URL、または公開されている HTTP か HTTPS の URL で指定します。このエンドポイントは file_id による入力に対応していません。ユーザーメッセージ内で input_text と input_image のパートを組み合わせると、指示やその他の文脈を踏まえて画像を評価できます。
質問ごとの指示への画像の追加
すべての質問に共通する判定用データは input に入れます。質問ごとに異なる画像を渡すには、その質問の instructions に、input_text と input_image のパートからなる空でない配列を指定します。条件判定、選択、スコアのどの型の質問でも使えます。テキストのみの指示は文字列のままで構いません。
たとえば、次の JSON リクエストボディを POST /v1/decisions に送信すると、候補画像を共通の参照画像と比較できます。例にある 2 つの画像 URL は、公開されている画像の URL または base64 の画像データ URL に置き換えてください。
12345678910111213141516171819202122232425262728293031323334{
"model": "gpt-6-luna",
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "The shared image is reference image A."
},
{
"type": "input_image",
"image_url": "https://example.com/reference.jpg"
}
]
}
],
"questions": [
{
"type": "predicate",
"name": "candidate_matches_reference",
"instructions": [
{
"type": "input_text",
"text": "Is candidate image B a derivative or variant of reference image A, including recompression or resizing?"
},
{
"type": "input_image",
"image_url": "https://example.com/candidate.jpg"
}
]
}
]
}
質問の指示に画像を追加する際は、次の点に注意してください。
- 候補画像ごとに質問を追加します。各質問では、その候補画像を同じ参照画像と比較します。
- コンテンツパートは
instructions に直接指定します。メッセージで包んだり、role を指定したりしないでください。
- リクエストあたりの画像の制限は、質問内の画像と
input 内の画像を合わせて適用されます。
choice 型の質問では、指定した選択肢から値を 1 つ選びます。それぞれに異なる値を指定し、どのような場合にその選択肢が当てはまるかを説明してください。
今月分の料金が二重に請求されました。余分に支払った分を返金してもらえますか?
CSV をエクスポートしようとすると毎回エラーになります。ページを再読み込みしても同じです。
スマートフォンをなくしてしまい、2 段階認証から先に進めません。
請求1.00
テクニカルサポート0.00
アカウントへのアクセス0.00
NORTHLINE STUDIO № 1042
請求先:Cedar & Co.
デザイン業務$1,200.00
ご請求額$1,200.00
NORTHLINE STUDIO № 6853
ご購入ありがとうございます。
デザイン業務$1,200.00
お支払い総額$1,200.00
NORTHLINE STUDIO 01 / 03
契約当事者:Northline Studio および Cedar & Co.
受託者は、12 か月間にわたりデザイン業務を行うことに同意します。
受託者署名委託者署名
次のリクエストは、お客様からの苦情を担当部署に振り分けます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18curl https://api.openai.com/v1/decisions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna",
"input": "I was charged twice for my order.",
"questions": [{
"type": "choice",
"name": "department",
"instructions": "Which department should handle this complaint?",
"choices": [
{"value": "billing", "description": "Payments, invoices, and refunds."},
{"value": "technical", "description": "Problems using the product."},
{"value": "shipping", "description": "Delivery and tracking."},
{"value": "other", "description": "Requests outside these categories."}
]
}]
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import OpenAI from "openai";
const client = new OpenAI();
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: "I was charged twice for my order.",
questions: [
{
type: "choice",
name: "department",
instructions: "Which department should handle this complaint?",
choices: [
{ value: "billing", description: "Payments, invoices, and refunds." },
{ value: "technical", description: "Problems using the product." },
{ value: "shipping", description: "Delivery and tracking." },
{ value: "other", description: "Requests outside these categories." },
],
},
],
});
const answer = decision.answers[0];
if (answer.type === "refusal") {
console.log(`Refused: ${answer.name}`);
} else if (answer.type === "choice") {
console.log(
`Department: ${answer.choice} (confidence: ${answer.confidence})`
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26from openai import OpenAI
client = OpenAI()
decision = client.decisions.create(
model="gpt-6-luna",
input="I was charged twice for my order.",
questions=[
{
"type": "choice",
"name": "department",
"instructions": "Which department should handle this complaint?",
"choices": [
{"value": "billing", "description": "Payments, invoices, and refunds."},
{"value": "technical", "description": "Problems using the product."},
{"value": "shipping", "description": "Delivery and tracking."},
{"value": "other", "description": "Requests outside these categories."},
],
}
],
)
answer = decision.answers[0]
if answer.type == "refusal":
print(f"Refused: {answer.name}")
elif answer.type == "choice":
print(f"Department: {answer.choice} (confidence: {answer.confidence})")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
)
func main() {
client := openai.NewClient()
decision, err := client.Decisions.New(context.Background(), openai.DecisionNewParams{
Model: "gpt-6-luna",
Input: openai.DecisionNewParamsInputUnion{OfString: openai.String("I was charged twice for my order.")},
Questions: []openai.DecisionNewParamsQuestionUnion{{
OfChoice: &openai.DecisionNewParamsQuestionChoice{
Name: openai.String("department"),
Instructions: openai.DecisionNewParamsQuestionChoiceInstructionsUnion{
OfString: openai.String("Which department should handle this complaint?"),
},
Choices: []openai.DecisionNewParamsQuestionChoiceChoice{
{
Value: openai.DecisionNewParamsQuestionChoiceChoiceValueUnion{OfString: openai.String("billing")},
Description: openai.String("Payments, invoices, and refunds."),
},
{
Value: openai.DecisionNewParamsQuestionChoiceChoiceValueUnion{OfString: openai.String("technical")},
Description: openai.String("Problems using the product."),
},
{
Value: openai.DecisionNewParamsQuestionChoiceChoiceValueUnion{OfString: openai.String("shipping")},
Description: openai.String("Delivery and tracking."),
},
{
Value: openai.DecisionNewParamsQuestionChoiceChoiceValueUnion{OfString: openai.String("other")},
Description: openai.String("Requests outside these categories."),
},
},
},
}},
})
if err != nil {
panic(err)
}
switch answer := decision.Answers[0].AsAny().(type) {
case openai.DecisionAnswerChoice:
fmt.Println(answer.Choice.AsString(), answer.Confidence, answer.Probabilities)
case openai.DecisionAnswerRefusal:
fmt.Printf("Decision refused for %s\n", answer.Name)
default:
panic("unexpected answer type")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47import com.openai.models.decisions.DecisionChoiceOption;
import com.openai.models.decisions.DecisionCreateParams;
import com.openai.models.decisions.DecisionCreateParams.Question.Choice;
var decision =
client
.decisions()
.create(
DecisionCreateParams.builder()
.model("gpt-6-luna")
.input("I was charged twice for my order.")
.addQuestion(
Choice.builder()
.name("department")
.instructions("Which department should handle this complaint?")
.addChoice(
DecisionChoiceOption.builder()
.value("billing")
.description("Payments, invoices, and refunds.")
.build())
.addChoice(
DecisionChoiceOption.builder()
.value("technical")
.description("Problems using the product.")
.build())
.addChoice(
DecisionChoiceOption.builder()
.value("shipping")
.description("Delivery and tracking.")
.build())
.addChoice(
DecisionChoiceOption.builder()
.value("other")
.description("Requests outside these categories.")
.build())
.build())
.build());
var answer = decision.answers().get(0);
if (answer.isRefusal()) {
System.out.println("Refused: " + answer.asRefusal().name().orElse("department"));
} else {
var choice = answer.asChoice();
System.out.println(choice.choice().asString());
System.out.println(choice.probabilities());
System.out.println(choice.confidence());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42require "openai"
client = OpenAI::Client.new
decision = client.decisions.create(
model: "gpt-6-luna",
input: "I was charged twice for my order.",
questions: [
{
type: :choice,
name: "department",
instructions: "Which department should handle this complaint?",
choices: [
{
value: "billing",
description: "Payments, invoices, and refunds."
},
{
value: "technical",
description: "Problems using the product."
},
{
value: "shipping",
description: "Delivery and tracking."
},
{
value: "other",
description: "Requests outside these categories."
}
]
}
]
)
answer = decision.answers.fetch(0)
case answer
when OpenAI::Models::Decision::Answer::Choice
puts(answer.choice, answer.confidence, answer.probabilities)
when OpenAI::Models::Decision::Answer::Refusal
warn("Decision refused for #{answer.name}")
else
raise("Unexpected answer type: #{answer.type}")
end
レスポンス例の抜粋です。
12345678910111213141516{
"answers": [
{
"type": "choice",
"name": "department",
"choice": "billing",
"probabilities": [
{ "value": "billing", "probability": 0.95 },
{ "value": "technical", "probability": 0.02 },
{ "value": "shipping", "probability": 0.01 },
{ "value": "other", "probability": 0.02 }
],
"confidence": 0.93
}
]
}
回答の choice フィールドには、指定した選択肢のうちの 1 つが入ります。この例では "billing" です。回答には各選択肢の確率を示す probabilities 配列と confidence フィールドも含まれます。しきい値の設定については、回答の解釈をご覧ください。
どのカテゴリにも当てはまらない入力があり得る場合は、"other" などの選択肢を用意します。その値が返った場合は、アプリケーションから一般レビュー用のキューに送ることができます。
score 型の質問では、2~10 段階の順序付き levels に照らして入力を評価します。各段階の基準を定義し、低い順に並べてください。
0.00/ 2
損傷なし1.00
軽微な損傷0.00
大きな損傷0.00
ダークモードを追加してもらえますか?夜に作業するときに助かります。
チームのメンバーのうち 1 人だけ、CSV のエクスポートが何度試しても失敗します。他のメンバーは今もエクスポートできています。
すべてのお客様が購入手続きでエラーになっています。この 20 分間、注文を 1 件も処理できていません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17curl https://api.openai.com/v1/decisions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna",
"input": "Export fails in Safari but works in Chrome.",
"questions": [{
"type": "score",
"name": "severity",
"instructions": "How severe is this issue?",
"levels": [
{"label": "Cosmetic", "description": "Appearance only; no lost functionality."},
{"label": "Workaround available", "description": "A task fails, but another way works."},
{"label": "Fully blocked", "description": "A task fails with no workaround."}
]
}]
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import OpenAI from "openai";
const client = new OpenAI();
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: "Export fails in Safari but works in Chrome.",
questions: [
{
type: "score",
name: "severity",
instructions: "How severe is this issue?",
levels: [
{
label: "Cosmetic",
description: "Appearance only; no lost functionality.",
},
{
label: "Workaround available",
description: "A task fails, but another way works.",
},
{
label: "Fully blocked",
description: "A task fails with no workaround.",
},
],
},
],
});
const answer = decision.answers[0];
if (answer.type === "refusal") {
console.log(`Refused: ${answer.name}`);
} else if (answer.type === "score") {
console.log(`Severity: ${answer.score} (confidence: ${answer.confidence})`);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34from openai import OpenAI
client = OpenAI()
decision = client.decisions.create(
model="gpt-6-luna",
input="Export fails in Safari but works in Chrome.",
questions=[
{
"type": "score",
"name": "severity",
"instructions": "How severe is this issue?",
"levels": [
{
"label": "Cosmetic",
"description": "Appearance only; no lost functionality.",
},
{
"label": "Workaround available",
"description": "A task fails, but another way works.",
},
{
"label": "Fully blocked",
"description": "A task fails with no workaround.",
},
],
}
],
)
answer = decision.answers[0]
if answer.type == "refusal":
print(f"Refused: {answer.name}")
elif answer.type == "score":
print(f"Severity: {answer.score} (confidence: {answer.confidence})")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
)
func main() {
client := openai.NewClient()
decision, err := client.Decisions.New(context.Background(), openai.DecisionNewParams{
Model: "gpt-6-luna",
Input: openai.DecisionNewParamsInputUnion{OfString: openai.String("Export fails in Safari but works in Chrome.")},
Questions: []openai.DecisionNewParamsQuestionUnion{{
OfScore: &openai.DecisionNewParamsQuestionScore{
Name: openai.String("severity"),
Instructions: openai.DecisionNewParamsQuestionScoreInstructionsUnion{
OfString: openai.String("How severe is this issue?"),
},
Levels: []openai.DecisionNewParamsQuestionScoreLevel{
{Label: "Cosmetic", Description: openai.String("Appearance only; no lost functionality.")},
{Label: "Workaround available", Description: openai.String("A task fails, but another way works.")},
{Label: "Fully blocked", Description: openai.String("A task fails with no workaround.")},
},
},
}},
})
if err != nil {
panic(err)
}
switch answer := decision.Answers[0].AsAny().(type) {
case openai.DecisionAnswerScore:
fmt.Println(answer.Score, answer.Confidence, answer.Probabilities)
case openai.DecisionAnswerRefusal:
fmt.Printf("Decision refused for %s\n", answer.Name)
default:
panic("unexpected answer type")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import com.openai.models.decisions.DecisionCreateParams;
import com.openai.models.decisions.DecisionCreateParams.Question.Score;
var decision =
client
.decisions()
.create(
DecisionCreateParams.builder()
.model("gpt-6-luna")
.input("Export fails in Safari but works in Chrome.")
.addQuestion(
Score.builder()
.name("severity")
.instructions("How severe is this issue?")
.addLevel(
Score.Level.builder()
.label("Cosmetic")
.description("Appearance only; no lost functionality.")
.build())
.addLevel(
Score.Level.builder()
.label("Workaround available")
.description("A task fails, but another way works.")
.build())
.addLevel(
Score.Level.builder()
.label("Fully blocked")
.description("A task fails with no workaround.")
.build())
.build())
.build());
var answer = decision.answers().get(0);
if (answer.isRefusal()) {
System.out.println("Refused: " + answer.asRefusal().name().orElse("severity"));
} else {
var score = answer.asScore();
System.out.println(score.score());
System.out.println(score.probabilities());
System.out.println(score.confidence());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38require "openai"
client = OpenAI::Client.new
decision = client.decisions.create(
model: "gpt-6-luna",
input: "Export fails in Safari but works in Chrome.",
questions: [
{
type: :score,
name: "severity",
instructions: "How severe is this issue?",
levels: [
{
label: "Cosmetic",
description: "Appearance only; no lost functionality."
},
{
label: "Workaround available",
description: "A task fails, but another way works."
},
{
label: "Fully blocked",
description: "A task fails with no workaround."
}
]
}
]
)
answer = decision.answers.fetch(0)
case answer
when OpenAI::Models::Decision::Answer::Score
puts(answer.score, answer.confidence, answer.probabilities)
when OpenAI::Models::Decision::Answer::Refusal
warn("Decision refused for #{answer.name}")
else
raise("Unexpected answer type: #{answer.type}")
end
レスポンス例の抜粋です。
123456789101112131415{
"answers": [
{
"type": "score",
"name": "severity",
"score": 1.1,
"probabilities": [
{ "value": 0, "label": "Cosmetic", "probability": 0.1 },
{ "value": 1, "label": "Workaround available", "probability": 0.7 },
{ "value": 2, "label": "Fully blocked", "probability": 0.2 }
],
"confidence": 0.55
}
]
}
段階のインデックスは 0 から始まります。この例では、0 は見た目だけの問題、1 は回避策がある状態、2 は作業をまったく進められない状態を表します。返される score は確率で重み付けした平均値なので、段階の中間の値も取ります。この例では各段階の確率が順に 0.1、0.7、0.2 なので、スコアは 1.1 です。
回答には confidence と、段階ごとの確率を示す probabilities も含まれます。スコアは各段階の確率分布を 1 つの数値にまとめたものです。カテゴリを 1 つ選ぶ場合は choice を使います。
複数の質問の指定
1 回のリクエストに 1~400 件の質問を含められます。共通の入力を評価するには、互いに独立した質問を同じ questions 配列に入れます。たとえば商品写真があれば、1 回のリクエストで損傷の有無を確認し、商品のカテゴリも判定できます。質問ごとに異なる型を使えます。
先の回答に依存する判定は、リクエストを分けてください。たとえば、まず損傷の有無を確認し、その結果を基に、修理の種類について質問するかどうかを決めます。
質問は、観察できる事実に基づいて判定できるように書きます。確認したい点が異なる場合は質問を分け、選択肢ごとの意味を明確に区別してください。スコアの段階を定義する際は、隣り合う段階を区別できる基準を設けてください。
回答の解釈
条件判定の質問では、条件が成立する確率の推定値が返ります。選択型とスコア型の質問では、回答に確率分布が含まれ、信頼度は別の confidence フィールドで返ります。
振り分け、フィルタリング、レビューのしきい値を設定するには、アプリケーションの実例に正解ラベルを付けたデータを使います。偽陽性と偽陰性がそれぞれ生じた場合のコストを考慮して、しきい値を決めてください。
プロンプトキャッシュを使うと、リクエストを繰り返す際に、共通の input の先頭から一致する部分(プレフィックス)を再利用でき、キャッシュ読み取りの割引料金が適用されます。たとえば、参照文書や画像を input に固定し、リクエストごとに質問を変えられます。質問固有の画像を含め、questions 内のコンテンツは再利用可能なプレフィックスの対象外です。
デフォルトでは、Decisions は共通の input の末尾に暗黙のキャッシュブレークポイントを設定します。既存のリクエストでも、リクエストボディを変更せずにキャッシュを利用できます。再利用可能なプレフィックスには少なくとも 1,024 トークンが必要です。質問の内容はこのトークン数に含まれません。再利用するには、リクエスト間でプレフィックスを同一にしてください。
prompt_cache_key を使うと、呼び出し元が指定するキーごとにキャッシュの再利用範囲を分けられます。境界を自分で指定するには、prompt_cache_breakpoint: {"mode": "explicit"} を共通の input 内のテキストまたは画像のコンテンツパートに追加します。明示した境界のみを使うには、prompt_cache_options.mode を "explicit" に設定してください。このモードでは、境界を指定していないリクエストにプロンプトキャッシュは適用されません。質問の指示内にはブレークポイントを設定できません。
キャッシュから読み取ったトークン数は usage.input_tokens_details.cached_tokens、書き込んだトークン数は usage.input_tokens_details.cache_write_tokens で確認できます。どちらも usage.input_tokens に含まれているので、この合計に加算しないでください。キャッシュの仕組みはプロンプトキャッシュのガイド、リクエストで指定できる設定は Decisions リファレンスをご覧ください。
使用状況の確認
GET /v1/organization/usage/decisions を使うと、組織の Decisions トークン使用量とリクエスト数を取得できます。認証には api.usage.read 権限のある管理者 API キーを使います。
対象期間と集計間隔を指定し、結果をプロジェクト、API キー、モデルで絞り込むか、グループ化します。num_model_requests フィールドはリクエスト単位で集計するため、複数の質問を含むリクエストも 1 件として数えます。フィルターとページネーションについては、Decisions 使用状況の API リファレンスをご覧ください。
gpt-6-luna では、キャッシュされていない入力の料金は 100 万トークンあたり 0.10 米ドルです。プロンプトキャッシュにヒットした入力には割引料金が適用され、キャッシュへの書き込みにも料金がかかります。キャッシュの読み取りと書き込みには、gpt-6-luna の標準プロンプトキャッシュ料金が適用されます。出力トークンは無料です。
リージョン内処理には追加料金が、長いコンテキストの入力には料金倍率が適用されます。これらは /v1/decisions の料金です。gpt-6-luna を使うその他のリクエストには、該当するモデルおよび処理ティアの料金が適用されます。
Decisions API は、利用条件を満たすお客様向けにゼロデータ保持(ZDR)と HIPAA に準拠した利用をサポートしています。データレジデンシーは、API がサポートするすべてのリージョンで利用できます。リージョン内処理は米国と欧州(EEA およびスイス)でサポートされています。利用条件、必要な契約、制限事項については、データ管理をご覧ください。
音声を Decisions に接続すると、音声で伝えられた依頼に応じてアクションを選んだり、ユーザーや GPT-Live の発話に合わせて反応したりできます。