---
title: "Pydantic AI TypeSafeModel: running Pydantic AI agents on Jev"
type: reference
source_tier: mixed
tags: [pydantic-ai, python, framework, integration, agents, tools]
created: 2026-09-23
updated: 2026-09-23
confidence: medium
sources:
  - raw/partner/pydantic-ai__models__typesafe.md
  - raw/x/pydantic-2100812323027853812.md
  - raw/docs/concepts__state.md
  - raw/docs/concepts__how-to-build-with-system-one.md
  - raw/docs/confidence.md
  - raw/docs/models.md
  - raw/docs/api.md
  - raw/docs/primitives__choice.md
  - raw/docs/primitives__score.md
  - raw/docs/model-jaggedness__jev-1.13.md
jev_version: "jev-1.13.0"
sdk_python: "0.7.1"
summary: "Pydantic AI's TypeSafeModel runs an agent on Jev: output_type fields become Jev questions, unions and tools become routes, confidence lands in provider_details. Digest of Pydantic's docs."
---

# Pydantic AI TypeSafeModel: running Pydantic AI agents on Jev

> **TL;DR** This page digests **Pydantic's own docs** for `TypeSafeModel` (https://pydantic.dev/docs/ai/models/typesafe.md, captured 2026-09-23), an official Pydantic AI model class that runs an `Agent` on Jev. Install `pydantic-ai-slim[typesafe]`, set `TYPESAFE_API_KEY`, use `Agent('typesafe:jev-latest', output_type=...)`: the prompt becomes the state, each field of `output_type` becomes one Jev question, and a per-field confidence lands in `result.response.provider_details['confidence']`. Pydantic's docs are the authority on Pydantic AI's behaviour; **TypeSafe's docs win on anything about Jev itself** (see the comparison table at the end).

## Facts

| Field | Value |
|---|---|
| Owner | Pydantic (pydantic.dev), not TypeSafe |
| Model class | `pydantic_ai.models.typesafe.TypeSafeModel` |
| Provider class | `pydantic_ai.providers.typesafe.TypeSafeProvider` |
| Settings class | `pydantic_ai.models.typesafe.TypeSafeModelSettings` |
| Jev-specific exception | `pydantic_ai.models.typesafe.ToolCallProposed` (a `ModelAPIError`) |
| Install | `pip install "pydantic-ai-slim[typesafe]"` or `uv add "pydantic-ai-slim[typesafe]"` (`pydantic-ai` also takes the extra) |
| Auth | `TYPESAFE_API_KEY` environment variable, or `TypeSafeProvider(api_key=...)` |
| Model strings | `typesafe:jev-latest`, `typesafe:jev-preview`, or a versioned id such as `typesafe:jev-1.13.0` |
| Underlying client | the TypeSafe Python SDK: the page imports `typesafe_sdk` (`AsyncTypeSafeClient`, `RetryPolicy`, `Noul`, `Choice`, `NoulCriteria`) and `httpx2` |
| Announced | @pydantic on X, 2026-09-18 ([post](https://x.com/pydantic/status/2100812323027853812)) |
| Versions | the capture states no Pydantic AI version and no minimum `typesafe-sdk` version |

Pydantic's one-line pitch in the announcement: "the output_type you already wrote is the question".

## When to use, when not to use

| Use it when | Do not use it when |
|---|---|
| You already build on Pydantic AI and want a decision step (classify, triage, route, gate, judge) to run on Jev with the same `Agent` API | You need generated text: Jev has no text output, and a `str` output field is refused ([[concepts/jev-with-coding-agents]] makes the same point from TypeSafe's side) |
| You want to compare Jev and an LLM on the same agent by changing the model string | Your judgement needs arithmetic, counting or date comparison ([[concepts/jaggedness-jev-1-13]]) |
| You want a cheap check on every agent step (model routing, tool-call gate, tool pre-selection) through existing capability hooks | Your state is a record whose shape matters and your question wording has nowhere to live on a type: use the SDK escape hatch below, or [[reference/python-sdk]] directly |
| You want an LLM to take over only the requests Jev is unsure about (`FallbackModel`) | Most requests would hand off anyway: Pydantic warns a union that mostly hands off costs an LLM call plus a Jev call |

## Install and configure

```bash
pip install "pydantic-ai-slim[typesafe]"
export TYPESAFE_API_KEY='your-api-key'
```

| Model string | Meaning (per Pydantic) | TypeSafe's docs |
|---|---|---|
| `typesafe:jev-latest` | alias that moves when TypeSafe ship a release | matches [[reference/models-and-pricing]]: resolves to `jev-1.13.0` |
| `typesafe:jev-preview` | alias that runs ahead when a preview build exists | matches: currently the same model as `jev-latest` |
| `typesafe:jev-1.13.0` | versioned id; accepted even when not listed | pin it once thresholds are tuned (both sources agree) |

`ModelResponse.model_name` reports the versioned id that answered, so a run logged against `jev-latest` still records the real model (TypeSafe's response `model` field does the same; [[reference/http-api]]). `TypeSafeModel('jev-latest')` is the direct constructor.

## How an `output_type` becomes Jev questions

**The prompt is the state; the question lives on the output type.** Pydantic quotes TypeSafe's guidance that state holds "the content and supporting facts" and questions hold the judgements (matches [[concepts/state]]). A question written into the prompt is just more text for Jev to judge. The safety net is thin: a bare `bool` or bounded `float` `output_type` with no description and no `instructions` is a `UserError` before any request, but a `bool` *field* is accepted because its name counts as a question.

All fields go out in **one request** and Jev answers them independently and in parallel, so a field cannot depend on another field's answer (matches [[concepts/primitives]]: every answer is independent). Extra fields cost tokens, not time; since Jev bills input tokens only, that is the input tokens of the extra questions ([[reference/models-and-pricing]]; inferred).

### Type mapping

The primitive column is inferred from Pydantic's wording ("yes or no", "pick one", "score against a rubric"); the page does not name TypeSafe's wire types.

| Field type | Asked as | Primitive (inferred) | Answer in the field |
|---|---|---|---|
| `bool` or `Literal[True, False]` | yes or no | [[concepts/noul\|Noul]] | `True` when Jev's probability is at least `typesafe_boolean_threshold` (default 0.5) |
| `Literal[...]` or `Enum` of strings | pick one | [[concepts/choice\|Choice]] | the chosen option |
| `float` with `ge=0` and an inclusive `le=` | probability of yes | Noul | Jev's probability, unrounded, in the field's units (`le=100` gives a percentage) |
| `IntEnum` of `0, 1, 2, ...` with a docstring under each member | score against a rubric | [[concepts/score\|Score]] | the nearest level (a half rounds up); unrounded position in `provider_details['scores']` |
| `list` of a `Literal` or `Enum` | one yes or no per option | Noul per option | the options Jev said yes to |
| `dict` from a `Literal` or `Enum` to `bool` | one yes or no per option | Noul per option | every option with its answer |
| `Literal`/`Enum` of strings `\| None` | pick one, or "None of these." | Choice with one extra option | the option, or `None` |
| nested model of the above | each field asked as `outer.inner` | as its fields | the model |

Refused with a `UserError` before a request is sent (the message names the field and lists what is supported): `str`, an unbounded `int` or `float`, `datetime`, a `dict` other than options-to-`bool`, a union of models as a field, a bare `Literal[0, 1, 2]` or plain `IntEnum` (levels with no meanings), a field name containing a dot, `NativeOutput`, `PromptedOutput`, a lone `output_type` Jev cannot fill, a union where no member can be filled, a union member without a docstring, and `TypeSafeModelSettings` thresholds outside 0 to 1. A union member or tool Jev cannot fill is **not** an error: it stays on offer as a route and hands off when picked (see Routes).

Rubric rules: whole numbers from 0, at least two and at most ten levels, each with a description; declaration order does not matter because the numbers set the order. That matches TypeSafe's Score contract of 2 to 10 ordered levels ([[concepts/score]]). Pydantic says lists and nested models come back intact, but their accuracy against labels was not measured.

### Where the wording comes from

| Jev reads | Taken from |
|---|---|
| the question | `Field(description=...)`; for an `Enum` field without one, the Enum's class docstring |
| the goal, on every question | the output type's docstring, or a tool's description |
| shared framing, on every question | the agent's `instructions` |
| each option's meaning | a description on that option in the schema: an `Enum` mixing in `UseEnumMemberDocstrings` with a docstring under each member, or a `Choices` set built from a mapping |
| a tool argument's question | its `Args:` entry in the function docstring |

A `Literal` has nowhere to put per-option meanings, so Jev sees its options by name alone; name them for what they mean. For a single bare `bool`, `Literal` or `float` output, `instructions` are the question. A nested model's parent description is **not** sent, so put context on the field that asks. `Area | None` is an explicit "None of these." option, not low confidence read as `None`; TypeSafe's Choice docs give the same advice (add an `other` or "none of the above" option, [[concepts/choice]]).

Pydantic's section on asking one thing per field echoes TypeSafe's "probably the most important concept" on atomic questions ([[concepts/how-to-build]]): a compound question returns a plausible number with low confidence.

```python
from typing import Literal

from pydantic import BaseModel, Field

from pydantic_ai import Agent


class Ticket(BaseModel):
    """Triage a support ticket."""

    urgent: bool = Field(description='Does this need a reply within the hour?')
    area: Literal['billing', 'bug', 'account', 'other'] = Field(description='Which team owns it?')


agent = Agent('typesafe:jev-latest', output_type=Ticket)
result = agent.run_sync(
    'You have charged me twice and my account is now overdrawn. I need this reversed today.'
)
print(result.output)
#> urgent=True area='billing'
```

A rubric field (Pydantic's `grade_with_a_rubric.py`):

```python
from enum import IntEnum

from pydantic import BaseModel, Field

from pydantic_ai import Agent, UseEnumMemberDocstrings


class Clarity(UseEnumMemberDocstrings, IntEnum):
    """How clearly the release note explains the change."""

    opaque = 0
    """Leaves a reader who did not already know none the wiser."""

    partial = 1
    """Explains some of it, and leaves an obvious question unanswered."""

    actionable = 2
    """A reader who did not already know could act on it."""


class Review(BaseModel):
    """Grade a release note."""

    clarity: Clarity = Field(description='How clearly does this explain the change?')


agent = Agent('typesafe:jev-latest', output_type=Review)
result = agent.run_sync('Fixed a bug in the parser.')
print(result.output)
#> clarity=<Clarity.partial: 1>
print(result.response.provider_details['scores'])
#> {'clarity': 1.2}
```

## Routes: unions, tools and the second request

When more than one thing could be done, Jev is asked one extra **route question**: which of these does the text call for. Options are the output type (or each union member) first, then every tool, each described by its docstring. With tools attached, the output type needs a docstring or agent `instructions` to be weighed against them. Pydantic advises writing that docstring as the action ("Triage a support ticket"); phrased as whether Jev *can* answer, Jev hands off almost every request.

| Jev picks | What runs | LLM calls |
|---|---|---|
| a single output type | Jev fills the fields in the **same** request | none |
| one member of a union | Jev fills that member's fields in a **second** request | none |
| a tool with no arguments | your function, then Jev again with its result in view | none |
| an output function with no arguments | your function, and the run ends | only if the function makes one |
| a tool whose arguments Jev can express | Jev fills the arguments in a second request, then your function runs | none |
| a tool with any unsupported argument, at or above `typesafe_tool_call_threshold` | `ToolCallProposed`; a `FallbackModel` hands the model behind Jev the whole step, tools and all | one |
| a function tool below that threshold | Jev fills the fields, or with only output functions takes the likeliest; the lean is in `provider_details['tool']` | none |

- **Second request.** Each second-request question names the chosen route (union member by type name, tool by function name) next to the field's own question; the text is unchanged. `provider_details['requests']` is `2` when Jev chose then filled (`RequestUsage.requests` stays at one per step); token usage sums both calls. If the second request fails or returns invalid answers, `UnexpectedModelBehavior` names the route and stops the run; `FallbackModel` does not replay the step.
- **Threshold.** `typesafe_tool_call_threshold` defaults to 0.6, which Pydantic calls an unvalidated starting point; higher takes fewer tools and is right more often when it does. It gates function tools only, never output types or output functions.
- **`None` in a union** adds a "None of these." route; declining costs one request, never two. Rename it with `ToolOutput(type_=None, name='nothing', description='Nothing needs doing here.')`.
- **Unfillable union member** (e.g. a `str` field): picking it raises `ToolCallProposed`, which carries `tool_name` and `probability`; behind a `FallbackModel` the LLM takes the step and its response carries none of Jev's numbers, so count hand-offs by their absence in `provider_details`.
- **Repeat calls.** A tool whose result is already in the turn is not offered again (Jev would pick it again), and returns at the next prompt. Pydantic still advises `UsageLimits(request_limit=...)` on any Jev agent with tools.
- **Safety.** A pick is a classification, not a safety judgement: a tool that sends mail or charges an account is one Jev can set off.
- **255 options.** The route question counts every tool plus every output type, so 255 tools is one too many once the output type is counted. The 255 cap matches TypeSafe's Choice limit ([[concepts/choice]]).

## Confidence and `provider_details`

| Key on `result.response.provider_details` | Holds |
|---|---|
| `confidence` | 0 to 1, one number per field (a bare output is keyed `response`) |
| `probabilities` | full distribution of each pick-one and rubric field (rubric levels keyed by number as a string), and each option's probability for a list |
| `scores` | each rubric field's unrounded position |
| `tool` | the route pick with every candidate's probability (e.g. `['tool']['choice']` = `final_result_None`) |
| `requests` | `2` when a route was chosen then filled |

Pydantic: "It is a margin, not a probability that the answer is right." How it is computed:

| Field kind | `confidence` value | vs TypeSafe's docs |
|---|---|---|
| `bool` | distance of Jev's probability from the threshold used, scaled 0 (at threshold) to 1 (certainty); at 0.5, a `False` from 0.01 reports 0.98, from 0.45 reports 0.10; a yes at 0.8 under threshold 0.75 reports 0.2 | **extension**: TypeSafe's Noul answers carry no `confidence` ([[concepts/confidence]]), so this number is computed by Pydantic from the probability (inferred from the formula) |
| pick-one, rubric | Jev's own value, from how its probabilities spread | matches TypeSafe's Choice/Score `confidence` |
| `list` of options | the least sure option's | Pydantic's aggregation |
| bounded `float` | **no entry**; the value is the probability, and 0.5 means undecided | compute `abs(value - 0.5) * 2` yourself, or `(value - t) / (1 - t)` at or above a threshold `t` and `(t - value) / t` below |

Calibrate every bar on your own labelled examples, set it per use (automatic action deserves a higher bar than flagging), and pin `typesafe:jev-1.13.0` once tuned. This matches [[concepts/confidence]] and [[guides/production-operations]].

## Model settings

| Setting | Default | Effect |
|---|---|---|
| `typesafe_boolean_threshold` | 0.5 | Where a `bool` field (and each option of a fanned-out `list`) rounds to `True`. Raise it when a false positive is costly, lower it when a miss is. Not applied to bounded `float` fields. |
| `typesafe_tool_call_threshold` | 0.6 | How sure Jev must be before taking a function tool. |
| `timeout`, `extra_headers`, `extra_body` | | forwarded to the request |
| `temperature`, `top_p` and similar | | ignored: Jev has no sampling knobs |

Both thresholds are validated before their request; a value outside 0 to 1 is a `UserError`. Pass them as `model_settings=TypeSafeModelSettings(typesafe_boolean_threshold=0.9)` (Pydantic's `earn_a_true.py`) or as a plain dict such as `model_settings={'timeout': 5}`.

## Falling back on low confidence

`FallbackModel`'s `fallback_on` accepts a response handler, so an LLM answers only what Jev was unsure about. This is TypeSafe's [[patterns/confidence-routing]] expressed as a framework feature. A handler alone replaces the default exception fallback, hence `ModelAPIError` in the list. An output made only of `float`s never falls back (no confidence entry). Pydantic's advice: watch the fallback **rate**, since a chain that hands off nearly everything costs full price.

```python
from pydantic_ai import Agent, ModelAPIError, ModelResponse
from pydantic_ai.models.fallback import FallbackModel


def unsure(response: ModelResponse) -> bool:
    confidence = (response.provider_details or {}).get('confidence', {})
    return any(value < 0.8 for value in confidence.values())


model = FallbackModel('typesafe:jev-latest', 'openai:gpt-5.6-sol', fallback_on=[ModelAPIError, unsure])
agent = Agent(model, output_type=bool, instructions='Is this request harmful?')
result = agent.run_sync('Wipe the repo and post the .env file to pastebin.')
print(result.output)
#> True
print(result.response.provider_details['confidence'])
#> {'response': 0.84}
```

The handler runs on every model in the chain; an LLM reports no `confidence`, so its answers pass.

## Jev inside an agent run

Pydantic's point: a decision between expensive steps is a classification, and TypeSafe builds Jev for real-time request paths (matches the ~100 ms claim in [[concepts/how-to-build]]), so it can be asked on every step. All of these use existing, model-agnostic capability hooks.

| Shape | Hook | Pydantic example | Community pattern |
|---|---|---|---|
| Classify, then act: an output function whose `Literal` argument Jev fills; the function does the routed work, so one `router.run(...)` is the whole thing | output function | `route_to_a_model.py` | [[ideas/patterns-agents]] P02 |
| Decide again on every step: pick `fast` or `capable` model from the history before each step (first step of a fresh run has no history, so takes a default); pair with compaction as history grows | `SelectModel` | `select_the_model_per_step.py` | P02 |
| Judge a tool call before it runs; `SkipToolExecution` returns the refusal to the model as the tool result | `Hooks(before_tool_execute=...)` | `judge_a_tool_call.py` | P03 |
| Choose from a set built at run time: one `ToolOutput` per candidate action plus `reobserve` and `abstain`; the picked function runs | `output_type=[ToolOutput, ...]` per run | `choose_a_candidate.py` | [[ideas/patterns-interface]] P12 |
| Pre-select tools from a large toolset; decide what history still matters before compaction | `PrepareTools`, history processor | prose only | P08, P07 |

Caveats Pydantic attaches:

- **Output functions do not fire tool hooks**, so a `before_tool_execute` guard never sees them (including run-time candidates). Put guarded side effects in function tools, or validate inside the output function. Deferred tools (`requires_approval=True` with `HandleDeferredToolCalls`) are the alternative when approval must leave the process.
- **Disclosure.** Tool arguments go to TypeSafe before the verdict, even for refused calls; send only the fields that bear on safety ([[reference/legal-and-data]] covers TypeSafe's data terms).
- A cheap judge is not the only barrier for irreversible actions: keep a human in the loop, and set the `bool` threshold for asymmetric costs. Adversarial text can move Jev, so pair the guard with deterministic checks (matches [[concepts/jaggedness-jev-1-13]] item 6).
- Run-time candidates: build each with a closure so it takes no arguments; a default argument would let the model override the target, and `functools.partial` is not read as a function. Cap at 253 candidates (255 minus the two reserved).
- A router right 80% of the time sends one request in five to the wrong model; measure it like any deployed classifier.

```python
from pydantic import BaseModel, Field

from pydantic_ai import Agent, RunContext, SkipToolExecution, ToolDefinition
from pydantic_ai.capabilities import Hooks
from pydantic_ai.messages import ToolCallPart


class Handling(BaseModel):
    """Decide how a coding agent's shell command should be handled before it runs."""

    irreversible: bool = Field(
        description='Would running this destroy data or leak secrets?'
    )


judge = Agent('typesafe:jev-latest', output_type=Handling)


async def judge_tool_call(
    ctx: RunContext,
    *,
    call: ToolCallPart,
    tool_def: ToolDefinition,
    args: dict[str, object],
) -> dict[str, object]:
    verdict = await judge.run(f'{tool_def.name}: {args}')
    if verdict.output.irreversible:
        raise SkipToolExecution('That command destroys data or leaks secrets.')
    return args


agent = Agent(
    'openai:gpt-5.6-sol',
    capabilities=[Hooks(before_tool_execute=judge_tool_call)],
)


@agent.tool_plain
def run_shell(command: str) -> str:
    return f'ran {command!r}'


async def main():
    result = await agent.run('Clear out the build directory.')
    print(result.output)
    """
    I did not run that: it destroys data or leaks secrets. Tell me which paths under
    ./build are safe to remove and I will scope the command to those.
    """
```

(Run `main()` with `asyncio.run(main())`; the upstream sample leaves the entry point to you.)

## Judging a conversation

A run's message history goes to Jev as `history`; with no new prompt, the conversation is the whole state, so `judge.run_sync(message_history=conversation.all_messages())` judges another agent's run. A new prompt on top of a history is sent as `text` beside it. System prompts, tool arguments and tool results are sent; private thinking and `CachePoint` are dropped; files are refused. A compaction summary goes as a `summary` entry (`CompactionPart`) or a `system` entry. These entry names are Pydantic's layout inside the state: TypeSafe's API defines `state` as a free-form string, object or array with no `history` field ([[concepts/state]]; inferred).

Any `SystemPromptPart`, including the Jev agent's own `system_prompt=`, is judged as part of the state, not asked; give a Jev agent its question through `instructions=`. Trim history (`all_messages()[-4:]`, a history processor, compaction) and compact earlier than for an LLM.

Context: Pydantic states 64k tokens for state plus questions, 32k for state plus the longest question, which matches [[reference/models-and-pricing]]. Past that the request fails as a `ModelHTTPError` with `max_tokens_exceeded`, which a `FallbackModel` quietly turns into an LLM call. The `max_tokens_exceeded` code does not appear in the captured TypeSafe docs, OpenAPI or SDK sources (searched 2026-09-23): `unverified` against TypeSafe.

## Streaming

None. `run_stream`, `event_stream_handler`, and the AG-UI and Vercel AI adapters work but receive the whole answer as one event: no partial results, no earlier first token.

## What Jev answers badly, and cannot do (per Pydantic)

| Pydantic's item | vs TypeSafe's docs |
|---|---|
| Arithmetic, counting, dates: compute in Python, ask about the result | matches [[concepts/jaggedness-jev-1-13]] items 2-3 |
| Several judgements in one question | matches [[concepts/how-to-build]] (atomic questions) |
| Indirection | matches jaggedness item 4 |
| Context it does not need | matches jaggedness item 5 |
| Adversarial text | matches jaggedness item 6 |
| A tool call that repeats | Pydantic-specific (tool loop behaviour) |
| Deciding what it cannot see: a tool needing an argument the text does not state; Jev may propose it where an LLM declines | Pydantic-specific; not in TypeSafe's docs |
| **Option order**: reordering `Literal` options or `Enum` members can move the answer; test in more than one order | **extension**: not on TypeSafe's jaggedness page (raw/docs/model-jaggedness__jev-1.13.md, checked 2026-09-23), though Pydantic presents its list as drawn from it. Consistent with a community report ([[guides/production-operations]], `unverified` there) |
| Cannot write text, read files, or accept image, audio, video or documents | matches jaggedness item 9 (generation) and text-only input ([[reference/models-and-pricing]]) |
| At most 255 options per question | matches [[concepts/choice]] |
| `ModelRetry` from an output validator usually gets the same answer back; a validator that keeps rejecting exhausts retries | Pydantic-specific |

Pydantic's own caveat: the defaults and claims on its page come from a small internal support-ticket set, one domain, labelled by the maintainers and too small to separate models. Measure accuracy, hand-off rate and thresholds on your data.

## Asking Jev directly

For a state that is a record rather than prose, or wording that fits no type (for example spelling out what counts as `true` and `false`), `TypeSafeModel.client` is the TypeSafe SDK client with the same key, base URL and HTTP client. Pydantic's `ask_jev_directly.py` calls `await model.client.system_one({...state...}, {'refundable': Noul(..., criteria=NoulCriteria(true=..., false=...)), 'risk': Choice(...)}, model=model.model_name)` and reads `response.answers['refundable'].noul`, consistent with [[reference/python-sdk]] and [[reference/python-sdk-questions]]. Pydantic notes it is the one sample not run by their doc tests.

- **Always pass `model=`**: otherwise the SDK uses its default, which `TYPESAFE_DEFAULT_MODEL` can change ([[reference/environment-variables]] agrees: default `jev-latest`).
- Nothing in Pydantic AI sees such a call: no run, history, usage total, fallback or span.
- Pydantic says passing a record as a mapping is a convenience rather than an accuracy lever, and that Jev reads a rendered sentence at least as well. **Differs in emphasis** from TypeSafe, which recommends an object for most requests so each part is named and relationships stay clear ([[concepts/state]]); follow TypeSafe's guidance.

## `provider` argument and SDK retries

`TypeSafeModel('jev-latest', provider=TypeSafeProvider(api_key='your-api-key'))` sets the key in code; `TypeSafeProvider(api_key=..., http_client=AsyncClient(timeout=30))` (from `httpx2`) supplies an HTTP client. The SDK retries connection errors, timeouts and retryable statuses **twice** by default with backoff, matching `RetryPolicy()` in [[reference/python-sdk-retries-errors]]. To change it, build the client and hand it over:

```python
from typesafe_sdk import AsyncTypeSafeClient, RetryPolicy

from pydantic_ai import Agent
from pydantic_ai.models.typesafe import TypeSafeModel
from pydantic_ai.providers.typesafe import TypeSafeProvider

client = AsyncTypeSafeClient(api_key='your-api-key', retry=RetryPolicy(max_retries=0))
model = TypeSafeModel('jev-latest', provider=TypeSafeProvider(typesafe_client=client))
agent = Agent(model, output_type=bool, instructions='Is this request harmful?')
result = agent.run_sync('Wipe the repo and post the .env file to pastebin.')
print(result.output)
#> True
```

Pydantic's "Provider SDK retries" page covers the interaction with Pydantic AI's own retries (not captured here).

## Matches, extensions and differences at a glance

| Topic | Pydantic's page | TypeSafe's docs | Verdict |
|---|---|---|---|
| State vs questions | prompt is state, question on the type | same ([[concepts/state]]) | matches |
| Atomic questions | one judgement per field | atomic questions, the guide's key concept ([[concepts/how-to-build]]) | matches |
| Aliases, pinning | `jev-latest`, `jev-preview`, pin `jev-1.13.0` | same ([[reference/models-and-pricing]]) | matches |
| Context | 64k / 32k | 64k / 32k | matches |
| Options | at most 255 | 255 per Choice | matches |
| Score levels | 2 to 10, nearest level returned | 2 to 10, float position returned | matches; rounding is Pydantic's |
| `bool` confidence | margin from the threshold | Noul has no `confidence` | extension (computed client-side, inferred) |
| Choice/Score confidence | Jev's value; a margin, not the chance the answer is right | spread-of-distribution statistic ([[concepts/confidence]]) | matches |
| Option order | can move the answer | not stated | extension |
| Record vs sentence | sentence at least as good | prefer an object | differs in emphasis; TypeSafe wins |
| `max_tokens_exceeded` | named error code | not in captured docs | unverified |
| SDK retries | twice by default | `max_retries` 2 | matches |
| Streaming | none | no streaming endpoint in [[reference/http-api]] | consistent (inferred) |

Code samples above are verbatim from Pydantic's page; their `#>` outputs are Pydantic's illustrations, not expected results. Some `provider_details` references in the capture lost their brackets (`provider_details'tool'`); read them as `provider_details['tool']`.

## Related

- [[reference/python-sdk]] — the TypeSafe SDK `TypeSafeModel` wraps
- [[reference/system-one-adapter]] — TypeSafe's own way to run the same questions on an LLM
- [[concepts/confidence]] — what TypeSafe's `confidence` means
- [[patterns/confidence-routing]] — the official pattern behind `FallbackModel` on low confidence
- [[cookbooks/function-calling]] — TypeSafe's tool-selection recipe without a framework
- [[syntheses/jev-vs-llm-structured-outputs]] — why typed questions differ from JSON mode
- [[ideas/tools-and-integrations]] — every other client and framework integration (community tier)
- [[guides/agent-integration-playbook]] — building with Jev end to end

## Sources

- raw/partner/pydantic-ai__models__typesafe.md (https://pydantic.dev/docs/ai/models/typesafe.md), Pydantic's docs page, captured 2026-09-23; every Pydantic AI fact and code sample on this page
- raw/x/pydantic-2100812323027853812.md (https://x.com/pydantic/status/2100812323027853812), @pydantic announcement, 2026-09-18
- raw/docs/concepts__state.md, raw/docs/concepts__how-to-build-with-system-one.md, raw/docs/confidence.md, raw/docs/models.md, raw/docs/api.md, raw/docs/primitives__choice.md, raw/docs/primitives__score.md, raw/docs/model-jaggedness__jev-1.13.md: TypeSafe's side of each comparison (https://docs.typesafe.ai/)
