For the last few years, enterprise AI architecture has harbored an embarrassing secret: we’ve been using hundred-billion-parameter prose engines as glorified if/else statements.
Need to route a support ticket? Send a prompt to a chat endpoint, beg the model to "Respond ONLY with valid JSON and no markdown backticks," cross your fingers, deserialize the string, and pray a temperature spike doesn't inject a polite conversational preamble that crashes your parser. Even with Structured Outputs and constrained decoding, using a generative endpoint for classification or triage means paying generative latency and output-token taxes just to get a boolean or an enum.
With the October 2026 release of Microsoft Foundry, the Decisions API (/openai/v1/decisions) is officially Generally Available. Powered by gpt-6-luna, it decouples evaluating and routing from generating prose—running roughly 10x faster than the standard Responses API with zero output-token charges.
When you call POST /openai/v1/decisions, the model doesn't generate chat completions or stream tokens. Instead, you pass shared evidence (input—which can include text, up to 128 images, raw JSON, or full conversation and tool-call histories) alongside an array of up to 400 bounded questions in a single request.
Look closely at the usage block returned by a Decisions API call:
{ "model": "gpt-6-luna", "answers": [ { "type": "predicate", "name": "refund_requested", "probability": 0.95 } ], "usage": { "input_tokens": 162, "output_tokens": 0, "total_tokens": 162 } }
output_tokens: 0. You are billed strictly on input tokens (around $0.10 per 1M input tokens, with prompt caching discounts when reusing shared context across requests). Instead of sampling a verbose explanation, your application gets calibrated probabilities that deterministic code can threshold and act on immediately.
predicate, choice, and scoreThe Decisions API replaces fragile prompt-engineering templates with three strongly typed question primitives. Every question requires a unique name and explicit instructions, and returns either the typed result or a typed refusal if safety filters trip.
|
Primitive |
What It Replaces |
What It Evaluates |
Returned Payload |
|
predicate |
"Reply YES or NO..." |
Whether an observable condition is true (e.g., PII present, visible product damage in a photo, reversible tool action). |
probability (0.0 to 1.0 estimate that the condition holds). |
|
choice |
"Pick one category from [A, B, C]..." |
Unordered categorical routing (e.g., department triage, intent classification, model routing). |
Selected choice, discrete probabilities per option, and an overall confidence metric. |
|
score |
"Rate this from 1 to 5..." |
Ordered rubric evaluation (e.g., ticket severity, PR risk level,rag chunk relevance). |
score (probability-weighted average across level indices), per-level probabilities, and confidence. |
score Beats LLM Number GuessingAsk a standard chat model to rate severity from 1 to 5, and it will overwhelmingly pick 3 or 4 due to token frequency bias in its training data.
The score primitive fixes this by requiring an ordered array of rubric levels (lowest to highest, each with a clear description). Under the hood, gpt-6-luna calculates the probability distribution across every rubric tier and computes the probability-weighted average of the level indices. Instead of an arbitrary integer, your router gets a continuous, nuanced float (e.g., 2.74) backed by the exact probability breakdown across the rubric.
If you're building on .NET 8/10+, support is baked into the OpenAI NuGet package (v2.15.0+) under the OpenAI.Decisions namespace. Because the typed DecisionsClient is newly minted, you opt in with #pragma warning disable OPENAI001.
Instead of chaining three separate LLM calls or stuffing a massive JSON schema into a prompt, you can evaluate a customer message (and an attached image) across multiple dimensions in a single round-trip:
using System; using System.ClientModel; using OpenAI.Decisions; #pragma warning disable OPENAI001 string endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"; string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new InvalidOperationException("Missing AZURE_OPENAI_API_KEY."); var client = new DecisionsClient( new ApiKeyCredential(apiKey), new DecisionsClientOptions { Endpoint = new Uri(endpoint) }); // 1. Shared Evidence: Evaluated once across all questions var input = new DecisionsUserMessageItem( [ new DecisionsInputTextContentPart( "I was charged twice for our enterprise license renewal, and now our production tenant is locked out!") ]); // 2. Define Typed Questions (Predicate + Choice) var refundCheck = new DecisionsPredicateQuestion( [ new DecisionsInputTextInstructionsPart("Does the customer explicitly ask for a refund or billing reversal?") ]) { Name = "needs_billing_reversal" }; var departmentRoute = new DecisionsChoiceQuestion( instructions: [new DecisionsInputTextInstructionsPart("Which team should own primary triage?")], choices: [ new DecisionsChoiceOption("billing", "Payment disputes, double charges, and invoices."), new DecisionsChoiceOption("tenant_ops", "Production outages, locked tenants, and access emergencies."), new DecisionsChoiceOption("general_support", "Standard how-to questions and feature requests.") ]) { Name = "triage_owner" }; // 3. Execute in a single zero-generation pass DecisionResource decision = await client.CreateDecisionAsync( new DecisionCreationOptions([input], "gpt-6-luna", [refundCheck, departmentRoute])); // 4. Handle deterministic, strongly-typed answers foreach (var item in decision.Answers) { switch (item) { case DecisionsPredicateAnswer predicate when predicate.Name == "needs_billing_reversal": Console.WriteLine($"[Predicate] Billing Reversal Probability: {predicate.Probability:P1}"); if (predicate.Probability >= 0.85) { // Trigger automated ledger lookup before a human even sees the ticket } break; case DecisionsChoiceAnswer choice when choice.Name == "triage_owner": Console.WriteLine($"[Choice] Route to: {choice.Choice.StringValue} (Confidence: {choice.Confidence:P1})"); break; case DecisionsRefusalAnswer refusal: Console.WriteLine($"[Refusal] Model declined question: {refusal.Name}"); break; } }
The Decisions API isn't a replacement for the Responses API or Function Calling—it’s the control plane that sits in front of them.
/decisions. Use a predicate question: "Is this action reversible, low-impact, and strictly within the user's stated request?" If probability is less than 0.80, pause the agent loop and trigger a human-in-the-loop approval.product.png show physical chassis damage, ignoring packaging?") alongside JSON metadata in ~170ms.Before you rip out your existing routers this afternoon, keep three sharp edges in mind:
gpt-6-luna is hosted, East US 2 is excluded from the initial rollout. Check your regional quota before hardcoding endpoints.gpt-6-luna is currently the only supported model on /openai/v1/decisions. Remember that Azure requires your deployment name in the model parameter, not the raw model string, unless they match./decisions is a non-streaming endpoint billed on input evaluation, treat network timeouts carefully—and never wire an unthresholded choice output directly to a destructive execution path without checking both confidence and DecisionsRefusalAnswer.Generative models are fantastic at writing, summarizing, and synthesizing. Stop forcing them to talk when all you need them to do is decide.
Sources: