ModelBeatbeta

Routing transparency

Every response records which model actually served it. The routing_info object, field by field.

ModelBeat may serve your request with a different model than the one you named. That is the product. The corollary is an obligation: you can always establish which model actually answered, from the response itself, without asking us.

That record is extra_fields.routing_info, and it is present on every successful response.

"extra_fields": {
  "request_type": "chat_completion",
  "latency": 842,
  "chunk_index": 0,
  "routing_info": {
    "provider": "bedrock",
    "model": "claude-haiku-4-5",
    "is_fallback": true,
    "primary_provider": "bedrock",
    "primary_model": "claude-sonnet-4-5"
  }
}

Read that as: you asked for bedrock/claude-sonnet-4-5, that target failed, and bedrock/claude-haiku-4-5 served the request instead.

Fields

FieldMeaning
providerThe provider that served this attempt.
modelThe model name sent to that provider. This is what answered you.
keyName of the ModelBeat-side provider key used. Useful when diagnosing a provider-account issue with us.
is_fallbacktrue when the primary target failed and a fallback served the request.
primary_providerThe provider originally targeted. Present only when a fallback occurred.
primary_modelThe model originally targeted. Present only when a fallback occurred.
resolved_key_aliasPresent only when the model you named matched an alias on the serving key. Carries model_id (the wire identifier actually sent), and optionally model_name and model_family.

Log the served model

If you keep one thing from this page: record routing_info.model alongside every completion you store.

raw = client.chat.completions.with_raw_response.create(model="auto", messages=[...])
r = raw.parse()

# routing_info is a field the OpenAI client does not know about, so it arrives as a
# plain dict, so it is subscripted, not dotted. `r.extra_fields.routing_info` raises
# AttributeError.
routing = r.extra_fields["routing_info"]

log.info(
    "completion served",
    extra={
        "requested_model": "auto",
        "served_model": routing["model"],
        "served_provider": routing["provider"],
        "was_fallback": routing.get("is_fallback", False),
        "request_id": raw.headers.get("x-request-id"),
    },
)

Two reasons this matters beyond curiosity:

  1. Debugging. Output quality changed and you cannot explain it? Check whether a fallback silently moved you to a different model.
  2. Compliance. If you operate under the EU AI Act or an equivalent regime, you are likely required to know which model produced a given output. routing_info is how you evidence that, per request, without trusting us to reconstruct it later.

Detecting fallbacks

routing = r.extra_fields["routing_info"]

# .get(), not [], for both: is_fallback may be absent, and primary_model is present
# ONLY on a fallback, so indexing it on a normal response raises KeyError.
if routing.get("is_fallback"):
    log.warning(
        "primary target unavailable: wanted %s, served by %s",
        routing.get("primary_model"),
        routing["model"],
    )

A fallback is not an error, because the request succeeded. A rising fallback rate is still worth an alert: it usually means a provider is degraded.

When your pinned model was overridden

A fallback is one way you can end up on a model you did not name. The other is a silent override. You pinned a model the router dropped, because it was retired, unrecognised, or not eligible for your account, and it served the request with something else.

That case sets a response header:

x-modelbeat-routing-notice: requested_ineligible

It is present only when an override happened, and the value is a closed enum. requested_ineligible is the only member today. Absence means your pin was honoured.

if raw.headers.get("x-modelbeat-routing-notice") == "requested_ineligible":
    log.warning("pinned model was not eligible; served %s instead", routing["model"])

routing_info.model still tells you what actually answered. The header exists so you can distinguish "I asked for auto and got a choice" from "I pinned a model and did not get it". Those are the same response body and very different facts.

On this page