> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Model auto-selection

> Let an evaluation model choose which of your models answers a conversation.

Model auto-selection lets you name a set of models, describe what each one is for, and have jev choose which should answer a given conversation. jev is a small evaluation model built for structured decisions, available in the catalogue as `typesafe-ai/jev`.

You use it when pinning a single model is wasteful. A support agent that pins a frontier model pays frontier prices for "what are your hours"; one that pins a cheap model handles that well and then fails the hard ticket. Auto-selection reads the conversation and picks per call.

Nothing about routing changes. [`vetta/auto`](/docs/concepts/model-router#vetta-auto), pinned ids, [completion windows](/docs/concepts/completion-window) and the [live catalogue](/docs/concepts/model-router#the-catalogue-is-live) behave as they did before, whether or not you use selection.

## How it works

You supply two to sixteen catalogue ids, each with a short description of what it is good for, plus the conversation so far. jev returns one of your ids.

There are two ways to use the result. You can call [`POST /v1/models/auto_select`](/docs/api/models#choose-a-model-for-a-conversation) to get the id back and do what you like with it. Or you can attach the same set of options to a proxy call or an agent as a `model_selection` policy, in which case Vetta runs the selection and the model call together.

Both paths ask jev the same question. The difference is whether you act on the answer or Vetta does.

## Select a model for a conversation

`POST /v1/models/auto_select` returns an id and stops. It does not call the model it selected.

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  const pick = await vetta.models.autoSelect({
    options: {
      "anthropic/claude-sonnet-4.5": "Routine coding, edits and tool use",
      "openai/gpt-5.6-sol": "Hard multi-step reasoning or architecture",
      "google/gemini-3-flash": "Short factual answers where speed matters",
    },
    messages: [{ role: "user", content: "What year did the Berlin Wall fall?" }],
  });

  console.log(pick.model); // "google/gemini-3-flash"
  ```

  ```bash cURL theme={"system"}
  curl https://api.vetta.sh/v1/models/auto_select \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "options": {
        "anthropic/claude-sonnet-4.5": "Routine coding, edits and tool use",
        "google/gemini-3-flash": "Short factual answers where speed matters"
      },
      "messages": [{ "role": "user", "content": "What year did the Berlin Wall fall?" }]
    }'
  ```

  ```json Response theme={"system"}
  {
    "object": "model_selection",
    "model": "google/gemini-3-flash",
    "confidence": 0.94,
    "probabilities": {
      "anthropic/claude-sonnet-4.5": 0.06,
      "google/gemini-3-flash": 0.94
    },
    "defaulted": false,
    "spent_micro_usd": 20,
    "usage": { "input_tokens": 366 }
  }
  ```
</CodeGroup>

Pass the returned `model` unchanged to the [proxy](/docs/api/proxy), an [agent](/docs/api/agents) or a [session](/docs/api/sessions). The request requires the `proxy:write` scope, or `sessions:write`.

## Select during a call

Attaching `model_selection` to a request runs the selection and the model call in one round trip. The reply's `model` field names the model that was picked.

On an agent, the policy applies to every turn. Each turn asks again over the transcript to that point, and no selection is stored on the agent.

<CodeGroup>
  ```typescript Per call theme={"system"}
  const reply = await vetta.proxy.messages({
    model: "anthropic/claude-sonnet-4.5",
    model_selection: {
      options: {
        "anthropic/claude-sonnet-4.5": "Routine coding, edits and tool use",
        "openai/gpt-5.6-sol": "Hard multi-step reasoning or architecture",
      },
      min_confidence: 0.5,
    },
    max_tokens: 1024,
    messages: [{ role: "user", content: "Refactor this parser and prove it terminates." }],
  });
  ```

  ```typescript Per agent turn theme={"system"}
  await vetta.agents.create({
    name: "support",
    model: "google/gemini-3-flash",
    model_selection: {
      options: {
        "google/gemini-3-flash": "Short factual answers where speed matters",
        "anthropic/claude-sonnet-4.5": "Anything needing tools or judgement",
      },
    },
  });
  ```
</CodeGroup>

## Options

Each key of `options` must be a concrete catalogue id. Aliases, unknown ids and `vetta/auto` are rejected with `validation_failed` on `options.<id>`, and every id must be priced in the [completion window](/docs/concepts/completion-window) you are using. An agent's policy is checked again at the start of every session, so a policy that was valid when you saved it is refused with `window_unavailable` if the catalogue has since moved.

`messages` accepts one to thirty-two user and assistant turns, up to 32,000 characters in total.

`min_confidence` and `default` work together. When you set `min_confidence`, jev's own confidence in its choice is compared against it, and `default` is returned instead when confidence falls below the threshold or is unavailable. The response sets `defaulted` to `true` when that happens, and `confidence` and `probabilities` still report what jev actually said. If you do not set `min_confidence`, `default` has no effect.

`default` only covers low confidence. It is not returned when selection fails, which is covered below.

## Pricing

Selection is billed as input tokens only, at the rate that appears on [`GET /v1/credits/ledger`](/docs/api/credits#ledger). A selection over two options, measured on production traffic:

| Input tokens | Debited                  | Added latency |
| ------------ | ------------------------ | ------------- |
| 330 to 370   | 20 micro-USD (\$0.00002) | 260 to 450 ms |

A one-shot selection gets its own ledger entry. A selection made during a proxy call or an agent turn adds one `input` line to the entry that call already produces. You are not billed for a selection that does not happen.

## Errors

What happens when jev cannot answer depends on where the selection was made.

| Where                         | Behaviour                                                                                                                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /v1/models/auto_select` | Returns an error. `feature_not_configured` (501) if the deployment has no evaluation model, `rate_limited` (429) or `internal_error` (500) if jev is unreachable. Nothing is debited. |
| Proxy call with a policy      | The call is refused before the model is dialled. Nothing is spent.                                                                                                                    |
| Agent turn with a policy      | The turn runs on the agent's own `model` and continues. Nothing is billed for the selection.                                                                                          |

The agent case differs deliberately. An agent turn has nobody waiting on it, and the turn loop retries, so failing the turn would retry an unavailable evaluator on every attempt and end the session. Adding a policy to an agent should not make that agent less available than it was without one.

If you want a specific fallback when selection is unavailable, catch the error and apply it yourself. Vetta does not substitute `default`.

<Warning>
  On a deployment with no evaluation model configured, `POST /v1/agents` rejects a `model_selection` policy with `feature_not_configured` (501) rather than storing one that no turn could act on.
</Warning>

## Limitations

There is no CLI command and no dashboard control for selection, and it is [withheld from MCP](/docs/api/mcp) for the same reason the proxy is: it is a billed, credentialed call to a model.

<Card title="Next: completion window" icon="gauge" href="/docs/concepts/completion-window">
  The three-price model, and why you choose it per request.
</Card>
