Skip to content

pydantic_ai.models.decision

Setup

For how an agent’s output type and tools become a decision model’s questions, and how to implement one, see Decision models.

NoulCriteria

Descriptions of the two outcomes of a yes/no question.

Attributes

true

Description of the yes outcome.

Type: JsonValue Default: None

false

Description of the no outcome.

Type: JsonValue Default: None

NoulQuestion

A yes/no question whose answer is the probability of yes.

Attributes

instructions

What to decide about the state.

Type: JsonValue Default: None

criteria

Descriptions of the yes and no outcomes.

Type: NoulCriteria | None Default: None

type

The Decisions protocol question type.

Type: Literal[‘noul’] Default: 'noul'

ChoiceQuestion

A question that selects one named option.

Attributes

criteria

Option labels mapped to their descriptions.

Type: dict[str, JsonValue]

instructions

What to decide about the state.

Type: JsonValue Default: None

type

The Decisions protocol question type.

Type: Literal[‘choice’] Default: 'choice'

ScoreQuestion

A question that scores the state against ordered levels.

Attributes

criteria

One description per level, starting at zero.

Type: list[JsonValue]

instructions

What to decide about the state.

Type: JsonValue Default: None

type

The Decisions protocol question type.

Type: Literal[‘score’] Default: 'score'

NoulAnswer

The probability of yes for a yes/no question.

Attributes

noul

The probability of yes, from 0 to 1.

Type: float

type

The Decisions protocol answer type.

Type: Literal[‘noul’] Default: 'noul'

ChoiceAnswer

The selected option and its probability distribution.

Attributes

choice

The selected option label.

Type: str

confidence

Confidence in the selected option.

Type: float

probabilities

Probability for each option label.

Type: dict[str, float]

type

The Decisions protocol answer type.

Type: Literal[‘choice’] Default: 'choice'

ScoreAnswer

A score and its probability distribution across levels.

Attributes

score

The expected score along the ordered levels.

Type: float

confidence

Confidence in the score.

Type: float

probabilities

Probability for each level.

Type: dict[int, float]

legend

The descriptions of the levels, as the backend echoes them back, if it does.

Not read to build the output, which comes from score alone; kept so the answer is recorded as it was received.

Type: dict[int, JsonValue] Default: field(default_factory=(dict[int, JsonValue]))

type

The Decisions protocol answer type.

Type: Literal[‘score’] Default: 'score'

DecisionRequest

A request to decide typed questions about a state.

Attributes

state

The text or JSON value to decide about.

Type: JsonValue

questions

Named questions to answer about the state.

Type: dict[str, DecisionQuestion]

DecisionResponse

The answers to a DecisionRequest, and what produced them.

Attributes

answers

Answers keyed by question name.

Type: dict[str, DecisionAnswer]

model_name

The model that produced the answers.

Type: str

usage

Usage for this request.

Type: RequestUsage Default: field(default_factory=RequestUsage)

provider_response_id

The backend’s identifier for this request, if it returned one.

Recorded as gen_ai.response.id on the request’s decide span. It is not copied to the ModelResponse, which can be built from two Decisions requests.

Type: str | None Default: None

DecisionModelSettings

Bases: ModelSettings

Settings used for a decision model request.

Attributes

decision_boolean_threshold

How likely a yes has to be before a bool field is True, from 0 to 1. Default: 0.5.

A decision model answers a yes/no with the probability of yes, and the default rounds it: what the framework cannot know is what True has to mean for you. Raise it where a false positive is the expensive mistake and a True should be earned, lower it where a false negative is. It applies to every bool field and to each option of a list of a Literal or Enum, which is one yes/no per option; a float bounded with ge=0 and le=1 returns the probability itself and is not thresholded.

Reported confidence is the distance from the threshold rather than from the probability, scaled to run from 0 at the threshold to 1 at certainty, so a yes at 0.8 under a threshold of 0.75 reports the narrow margin it is.

Type: float

decision_route_threshold

How likely the picked route has to be before it is taken, from 0 to 1. Default: unset, so the pick always is.

With tools attached, or a union of output types, one more question asks which route the text calls for: a tool, an output type, an output function or None. The likeliest route is taken. With this set, a pick whose own probability is below it raises UnsureRoute instead, before any request to fill it. That is a ModelAPIError, so a FallbackModel with a language model behind the decision model hands that model the step; without one, the run raises it.

A route taken without a pick is not held to it: the one route left when every other has returned this turn, or a single output type with nothing else on offer. A higher threshold hands off more steps and gets more of the rest right; tune it on labelled examples of your own.

This is not a guard for a tool with side effects, such as a refund or an account change: require approval for that tool instead.

Type: float

DecisionHandOff

Bases: ModelAPIError

A decision model handed the step off instead of answering it: the base of the hand-offs it raises.

A ModelAPIError, so a FallbackModel with a language model behind the decision model hands that model the whole step by default, tools and all, and only the steps the decision model hands off cost a language model call. Pass fallback_on=DecisionHandOff to hand off only these, and let an error from the decision model’s backend fail the run rather than go to the language model.

Attributes

route

The route the model picked, by the label the route question offered it under.

Type: str Default: route

probability

How likely the model found the picked route, from 0 to 1.

Type: float Default: probability

UnfillableRoute

Bases: DecisionHandOff

A decision model picked a route whose fields or arguments it cannot fill.

A tool with an argument the model cannot express, such as a free-form str, or an output type with such a field. See DecisionHandOff for how a FallbackModel takes the step.

Attributes

tool_name

Deprecated alias for route.

For a tool, the tool’s name. For an output type, the name the route question offered it under, as Reply, rather than the name of the output tool Pydantic AI made for it.

Type: str

UnsureRoute

Bases: DecisionHandOff

A decision model picked a route less likely than decision_route_threshold.

Raised before any request to fill the route. See DecisionHandOff for how a FallbackModel takes the step.

Attributes

probabilities

The probability the model gave every route, by label.

Type: dict[str, float] Default: probabilities

threshold

The decision_route_threshold the pick fell below.

Type: float Default: threshold

DecisionModel

Bases: Model[InterfaceClient]

Base class for decision models: models that answer typed questions about a text rather than write text.

A decision model is sent a state, the text or JSON value to judge, and a set of named questions of three kinds: a yes/no (NoulQuestion), a pick-one (ChoiceQuestion), and a score against an ordered rubric (ScoreQuestion). It answers each one with a probability or a distribution. That exchange is the Decisions protocol, and this class maps an agent run onto it, so that an agent whose job is to decide something runs on a decision model like on any other model:

  • Each field of the output_type is one question, and its type picks the kind: a bool is a yes/no, a Literal or Enum of strings is a pick-one, and whole numbers from 0 with a description per level are a rubric. A list or dict of options is one yes/no per option, and a nested model is its fields. A field of any other type is a UserError before a request is sent, unless there is another route to take, as below.
  • The field’s description is the question, the output type’s docstring its goal, and the agent’s instructions framing shared by every question; a nested field’s question also carries what it sits in. The latest user prompt is the text to judge, the message history before it goes along beside it, and once a tool has returned, what was done since goes along apart from both.
  • With tools attached, or a union of output types, one more pick-one asks which route the text calls for, and the likeliest is taken. The fields of every route the model can fill are asked beside it, each on the premise of its route, and only the taken route’s answers are read; past a size cutoff, a picked route with fields is filled in a second request instead. A route whose fields the model cannot express, a single output type’s included, is raised as UnfillableRoute, for a FallbackModel to hand to a language model. A pick below decision_route_threshold, when set, is raised as UnsureRoute the same way.
  • Each field’s confidence, the full distribution of each pick-one and rubric, and the route pick are reported in ModelResponse.provider_details.

The answers arrive in one piece, so a streamed run gets the whole answer as one event.

To support a backend, subclass this, implement decide along with model_name, system and base_url, and set max_choice_options and max_score_levels to the backend’s limits. See Decision models for the full rules and an example.

Attributes

max_choice_options

The most options the backend accepts in one pick-one question, or None for no limit.

A pick-one field with more options, or more routes than this on the route question, is a UserError before a request is sent.

Type: int | None Default: None

max_score_levels

The most levels the backend accepts in one rubric, or None for no limit.

Whole numbers from 0 with more levels than this are not a rubric, so a field of them is asked as a pick-one instead, and counts against max_choice_options.

Type: int | None Default: None

profile

The model profile, with text output off whatever the provider or profile= says.

A decision model answers questions and has no way to write text, so this is a fact about the class rather than a default to override: with text output left on, an output_type like [Ticket, str] would pass the shared request preparation and have its str branch silently never taken.

Type: ModelProfile

Methods

decide

@abstractmethod

@async

def decide(
    request: DecisionRequest,
    model_settings: DecisionModelSettings,
) -> DecisionResponse

Send one request to the backend and return its answers.

This is called once per request the model makes: once per step, or twice when a route is picked in one request and, past the size cutoff for asking every route’s fields up front, filled in a second. Every question in request.questions needs an answer of the matching kind under the same name.

Raise ModelHTTPError or ModelAPIError when the backend fails, so a FallbackModel can take over; UnexpectedModelBehavior when it returns something that cannot be read; and UserError when the request cannot be sent as given. Forward timeout, extra_headers and extra_body from model_settings where the backend supports them.

Returns

DecisionResponse

DecisionStreamedResponse

Bases: StreamedResponse

A decision model’s whole answer as one event, so that a streamed run works on a model that cannot stream.

Methods

close_stream

@async

def close_stream() -> None

No live stream to close: the whole answer was in hand before the first event.

Returns

None

DecisionQuestion

A question supported by the Decisions protocol.

Type: TypeAlias Default: NoulQuestion | ChoiceQuestion | ScoreQuestion

DecisionAnswer

An answer returned by the Decisions protocol.

Type: TypeAlias Default: NoulAnswer | ChoiceAnswer | ScoreAnswer