Decisions API¶
OpenAI's Decisions API answers questions about an input
with probabilities instead of text: is this statement true, which option fits, where on a scale. It is fast and
cheap (it only charges input tokens), which makes it a good fit for checks around an agent: triage, routing, guard
rails. It is in public beta, with one model, gpt-6-luna.
It is not a model turn, so it does not go through an agent: call decide(...) with the questions, by name.
from pyox_agents import Choice, Predicate, Score, decide
decision = await decide(
"The deploy failed twice and customers are seeing 500s. Can someone look now?",
{
"urgent": Predicate("Does this need attention right now?"),
"team": Choice("Which team should handle it?", {"infra": "Deploys and outages.", "billing": "Payments."}),
"severity": Score("How severe is the impact?", ["Cosmetic", "Degraded", "Outage"]),
},
)
decision["urgent"].probability # 0.97
decision["team"].choice # "infra"
decision["team"].probabilities # {"infra": 0.96, "billing": 0.04}
decision["severity"].score # 1.8
All the questions go in one request, and each answer comes back under its question's name.
Questions and answers¶
| Question | Asks | Answer |
|---|---|---|
Predicate(instructions) |
Is it true? | PredicateAnswer: probability (0 to 1) |
Choice(instructions, choices) |
Which option fits best? | ChoiceAnswer: choice, confidence, probabilities per option |
Score(instructions, levels) |
Where on a scale? | ScoreAnswer: score, confidence, probabilities per level |
choicesis a list of options (strings or booleans), or a dict from each option to a description.levelsgo from the lowest to the highest: a list of labels, or a dict from each label to a description.scoreis the probability-weighted average of the level indices, from 0, so it can fall between two levels.- Any question can get a
Refusalinstead, when the model declines it. The other answers are still valid, so check the type before reading an answer:
answer = decision["urgent"]
if isinstance(answer, PredicateAnswer) and answer.probability > 0.8:
page_on_call()
decision.answers holds every answer by name, decision.usage the tokens (a Usage), and
decision.request_id OpenAI's id for the request, for support.
Input¶
The input is any of:
- Text:
decide("The package arrived broken.", ...). - A prompt with images, as for
agent.run:decide(["Is it broken?", image("photo.jpg")], ...). Files (such as PDFs) are not accepted by the API. - A history, such as
result.messages: the whole conversation with its tool calls, so you can ask about what happened in a run. The model's reasoning is left out (the API does not accept it).
result = await agent.run("Cancel my subscription")
decision = await decide(result.messages, {"done": Predicate("Did the assistant cancel the subscription?")})
Options¶
await decide(
input,
questions,
client=client, # the same Client as your agents; Client() by default
model="gpt-6-luna", # the default
safety_identifier="9f86d0", # a hashed id of your user, for OpenAI's abuse checks
prompt_cache_key="triage", # groups requests that share a prefix, for the prompt cache
)
Errors are the same as for agents: RateLimitError, AuthenticationError, ApiError... (see
Errors). Retries and timeouts come from the Client.
Recipes¶
Ask for approval when a tool call looks risky. A before_tool_call hook asks about each call, and
raises ApprovalRequired to make the risky ones wait for a person (see
Human in the loop). A refusal counts as risky:
async def risk_check(ctx: RunContext, call: ToolCall) -> None:
decision = await decide(
f"Tool call: {call['name']}({call['arguments']})",
{"risky": Predicate("Could this tool call cause harm or lose data?")},
)
answer = decision["risky"]
if not isinstance(answer, PredicateAnswer) or answer.probability >= 0.5:
raise ApprovalRequired({"tool": call["name"]})
agent = Agent(model="gpt-5-mini", tools=[...], hooks=[Hooks(before_tool_call=risk_check)])
Pick the agent for a request. Ask a Choice before the run:
agents = {"quick": Agent(model="gpt-5-mini"), "deep": Agent(model="gpt-5", instructions="Think it through.")}
decision = await decide(
prompt,
{
"agent": Choice(
"How hard is this request?",
{
"quick": "Small talk or a simple question.",
"deep": "Needs reasoning, several steps or care.",
},
)
},
)
answer = decision["agent"]
agent = agents[answer.choice] if isinstance(answer, ChoiceAnswer) else agents["deep"]
result = await agent.run(prompt)
Azure OpenAI¶
decide sends POST /decisions to the client's base URL, so Client.azure(...) works if your Azure resource
serves the Decisions API. Microsoft has not documented it yet; pass your deployment's name as model.