Use the Azure OpenAI Decisions API

The Azure OpenAI Decisions API evaluates text, images, or both and returns typed answers instead of generated text. Ask whether a condition is true, select one option from a fixed set, or score an input against a rubric. Use the answers to classify content, route requests, and prioritize work in your application.

The Decisions API is a dedicated endpoint, /openai/v1/decisions, that you call with the OpenAI SDKs or REST. Currently, gpt-6-luna is the only supported model.

Note

The Decisions API is rolling out to regions where gpt-6-luna is available. East US 2 is excluded from the rollout. For model availability by region, see Region availability for Foundry Models sold by Azure.

Prerequisites

  • An Azure subscription. If you don't have one, create a free account.

  • An Azure OpenAI resource. The Decisions API is available in offerings and regions where gpt-6-luna is available. For model availability, see Azure OpenAI models.

  • A gpt-6-luna model deployment. For more information, see Deploy Microsoft Foundry Models and Deployment types.

  • For the examples in this article, the API key for your resource, stored in the AZURE_OPENAI_API_KEY environment variable. The Decisions API supports both API-key authentication and Microsoft Entra ID authentication.

  • For the Python examples, the OpenAI Python library version 3.26.0 or later.

    pip install "openai>=3.26.0"
    
  • For the JavaScript examples, Node.js and the OpenAI JavaScript library version 7.30.0 or later. Save the examples as .mjs files so that top-level await works.

    npm install "openai@>=7.30.0"
    

In the examples, replace YOUR-RESOURCE-NAME with your Azure OpenAI resource name. The model parameter takes your deployment name, not the underlying model name. The examples use gpt-6-luna as the deployment name. If you named your deployment something else, use that name instead.

Understand how decisions work

A Decisions API request has three parts:

Field Purpose
model The name of your gpt-6-luna deployment.
input The shared evidence that every question evaluates: a text string, or one or more user messages. A message's content can be a string or a list of input_text and input_image parts.
questions What to evaluate. Each question has a type, instructions, and any allowed choices or score levels.

The response contains an answers array, in the same order as your questions, and token usage for the request. Give each question a unique name so you can match each answer to its question. The API returns that name in the answer. For pricing information, see Azure OpenAI pricing.

Choose a question type based on the answer your application needs:

Type Use it to Main result
predicate Check whether a condition is true, such as whether a photo shows visible damage. probability: an estimate from 0 to 1 that the condition is true.
choice Select one option without an order, such as a department or content category. choice: one of the values you supplied, with a probability for each option.
score Rate an input against ordered levels, such as issue severity. score: the probability-weighted average of the level indices, with a probability for each level.

Use the Decisions API when your application needs one of these answer types. To generate an object that follows your own JSON schema, such as extracted fields or a written explanation, use structured outputs with the Responses API. To have a model request a tool call with arguments, use function calling.

Make your first decision

Use a predicate question to check whether a condition is true. This example checks whether a customer message asks for a refund.

import os
from openai import OpenAI

endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"

# Create a client for your Azure OpenAI resource
openai = OpenAI(
    base_url=endpoint,
    api_key=os.environ["AZURE_OPENAI_API_KEY"],
)

# Ask whether the message requests a refund
decision = openai.decisions.create(
    model="gpt-6-luna",
    input="I was charged twice for my order. Please return the extra charge.",
    questions=[
        {
            "type": "predicate",
            "name": "refund_requested",
            "instructions": "Does the customer ask for a refund?",
        }
    ],
)

# Print the probability, or the refusal
answer = decision.answers[0]
if answer.type == "predicate":
    print(f"Refund requested probability: {answer.probability}")
elif answer.type == "refusal":
    print(f"Refused: {answer.name}")

The SDK examples print the probability that the condition is true:

Refund requested probability: <probability>

The REST response contains the answer and token usage. The following response is illustrative; your values differ.

{
  "model": "<model>",
  "answers": [
    { "type": "predicate", "name": "refund_requested", "probability": 0.95 }
  ],
  "usage": {
    "input_tokens": 162,
    "input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
    "output_tokens": 0,
    "output_tokens_details": { "reasoning_tokens": 0 },
    "total_tokens": 162
  }
}

The probability is the model's estimate, from 0 to 1, that the condition is true. Compare it to a threshold that you choose for your application. For guidance, see Interpret answers and handle refusals.

Select from fixed options

A choice question selects one value from the options you provide. Use distinct values, and add descriptions that explain when each option applies. This example routes a customer complaint to a department.

import os
from openai import OpenAI

endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
openai = OpenAI(base_url=endpoint, api_key=os.environ["AZURE_OPENAI_API_KEY"])

# Select one department from a fixed set of options
decision = openai.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 and refunds."},
                {"value": "technical", "description": "Product problems."},
                {"value": "shipping", "description": "Delivery and tracking."},
                {"value": "other", "description": "All other requests."},
            ],
        }
    ],
)

# Print the selected option and its confidence
answer = decision.answers[0]
if answer.type == "choice":
    print(f"Department: {answer.choice} (confidence: {answer.confidence})")
elif answer.type == "refusal":
    print(f"Refused: {answer.name}")

The SDK examples print the selected department and the answer's confidence:

Department: billing (confidence: <confidence>)

The following answer is illustrative; your values differ.

{
  "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
}

The choice field contains one of the values you supplied. The answer also includes a probability for each option and a separate confidence value. Include a fallback option, such as other, when your categories don't cover every possible input.

Score against a rubric

A score question evaluates an input against ordered levels. Define criteria for each level, and list the levels from lowest to highest. This example rates the severity of a reported issue.

import os
from openai import OpenAI

endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
openai = OpenAI(base_url=endpoint, api_key=os.environ["AZURE_OPENAI_API_KEY"])

# Rate the issue against ordered severity levels
decision = openai.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": "No lost functionality."},
                {"label": "Workaround available",
                 "description": "A task fails, but another way works."},
                {"label": "Fully blocked",
                 "description": "A task fails with no workaround."},
            ],
        }
    ],
)

# Print the score and its confidence
answer = decision.answers[0]
if answer.type == "score":
    print(f"Severity: {answer.score} (confidence: {answer.confidence})")
elif answer.type == "refusal":
    print(f"Refused: {answer.name}")

The SDK examples print the score and the answer's confidence:

Severity: <score> (confidence: <confidence>)

The following answer is illustrative; your values differ.

{
  "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
}

Level indices start at 0. In this example, 0 means cosmetic, 1 means a workaround is available, and 2 means fully blocked. The score is a probability-weighted average of the indices, so it can fall between levels. Probabilities of 0.1, 0.7, and 0.2 produce a score of 1.1. To select a single category instead of a position on a scale, use a choice question.

Evaluate an image

Combine input_text and input_image parts in a user message to evaluate an image together with instructions or other context. The Decisions API supports PNG, JPG, WebP, and GIF images. Each request supports up to 128 images with a maximum combined image size of 32 MB.

This example checks a product photo, product.png, for visible damage.

import base64
import os
from pathlib import Path
from openai import OpenAI

endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
openai = OpenAI(base_url=endpoint, api_key=os.environ["AZURE_OPENAI_API_KEY"])

# Encode the image as a base64 data URL
image = base64.b64encode(Path("product.png").read_bytes()).decode("ascii")
image_url = f"data:image/png;base64,{image}"

# Ask whether the product in the photo is damaged
decision = openai.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": image_url},
    ]}],
    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.",
    }],
)

# Print the probability of visible damage
answer = decision.answers[0]
if answer.type == "predicate":
    print(f"Visible damage probability: {answer.probability}")

The SDK examples print the probability that the product has visible damage:

Visible damage probability: <probability>

The image_url value can be a base64 data URL, as in this example, or a publicly accessible HTTPS URL. Don't pass a local file name. The service must be able to download a URL-based image without access to a private network or your browser's cookies.

To evaluate more than one image, add multiple input_image parts to the content list. Label each image with an input_text part so that your questions can refer to it. Every question in the request can see every image.

"content": [
  {"type": "input_text", "text": "First image:"},
  {"type": "input_image", "image_url": "data:image/png;base64,<IMAGE_1>"},
  {"type": "input_text", "text": "Second image:"},
  {"type": "input_image", "image_url": "https://contoso.com/photo2.jpg"}
]

Ask multiple questions

To evaluate the same input in different ways, put independent questions in one questions array. Each request supports up to 400 questions. Each question can use a different type. For example, one request can check a product photo for damage and classify the product's category.

The following questions array combines the department and severity questions from the previous sections:

"questions": [
  {
    "type": "choice",
    "name": "department",
    "instructions": "Which department should handle this complaint?",
    "choices": [
      {"value": "billing", "description": "Payments and refunds."},
      {"value": "technical", "description": "Product problems."},
      {"value": "other", "description": "All other requests."}
    ]
  },
  {
    "type": "score",
    "name": "severity",
    "instructions": "How severe is this issue?",
    "levels": [
      {"label": "Cosmetic", "description": "No lost functionality."},
      {"label": "Workaround available", "description": "Another way works."},
      {"label": "Fully blocked", "description": "No workaround."}
    ]
  }
]

The answers array returns one answer for each question, in the same order. Use each answer's name to match it to its question.

When a decision depends on an earlier answer, send separate requests. For example, check for damage first, and then request a repair category only for damaged products.

Write questions around observable criteria. Put different concerns in different questions, give choices distinct meanings, and define score levels so that adjacent levels have distinct criteria.

Interpret answers and handle refusals

A predicate answer returns the estimated probability that the condition is true. choice and score answers return a probability for each option or level, plus a separate confidence value.

To set thresholds for routing, filtering, or human review, test with labeled examples from your application. Base each threshold on the cost of false positives and false negatives in your scenario.

Any question can return a refusal instead of an answer, while other questions in the same request still receive answers. A refusal has the type refusal and the question's name. If the question doesn't have a name, the name value is null. Check each answer's type before you read its result, and decide how your application handles refusals, such as by sending the input for human review. A refusal is different from a predicate answer with a low probability.