OBLETHdocs

Reasoning effort

Give callers four effort levels that mean the same on every chat model, and let one model name pick a different model for each level.

6 min readUpdated for v2.4.0Edit on GitHub

Set itThe model's page › Reasoning
FamiliesModels › Model families
Levelsnone, low, medium, high
Applies toChat models, on chat, Responses and Messages

Callers ask for reasoning effort as none, low, medium or high, but model servers disagree about what those words mean: one has only low and high, another turns thinking off with a chat-template flag, a third rejects high outright. A reasoning map tells obleth what each level becomes for one model's server, and a model family sends each level to a different model.

Translate effort for a model

  1. 1

    Open the model's Reasoning section

    On a chat model's page, open Reasoning and set Effort on requests to Translate the four levels.

  2. 2

    Say what each level sends

    Under What each level sends, pick one per level: Server default (send nothing), Send reasoning_effort = a value, Turn thinking off or Turn thinking on (chat_template_kwargs.enable_thinking), Same as another level, or Custom fields (JSON). Then set When a caller sets no effort: Leave it to the model's server, or serve it as one of the levels.

  3. 3

    Check the preview and save

    What requests become previews each level. Choose Save reasoning. The section saves on its own, not with the save bar, and applies within a few seconds.

Or through the Management API. This model turns thinking off with a chat-template flag, takes low and high, and runs at its server's own default for medium:

curl -X PUT http://localhost:9180/api/v1/models/$MODEL_ID/reasoning \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "mapped",
    "default_level": "none",
    "levels": {
      "none":   {"send": {"chat_template_kwargs": {"enable_thinking": false}}},
      "low":    {"send": {"reasoning_effort": "low"}},
      "medium": {"send": {}},
      "high":   {"send": {"reasoning_effort": "high"}}
    }
  }'

The body replaces the whole map. {"mode": "passthrough"} turns translation off.

How a level is applied

modeIn the dashboardWhat the model's server gets
passthrough (default)Pass throughThe request as the caller sent it, effort included
mappedTranslate the four levelsThe caller's reasoning_effort and reasoning removed, and the level's fields merged in
unsupportedThis model has no effort controlThe caller's reasoning_effort and reasoning removed, so a server that would reject them never sees them

A mapped model needs an action for every level:

  • {"send": {…}} merges those fields into the request. Objects merge key by key and your value wins, so a caller's other chat_template_kwargs keys stay. {"send": {}} sends nothing, which leaves the server's own default.
  • {"use": "<level>"} serves the request as another level, for a model that lacks this one, such as "none": {"use": "low"} on a model with no off switch. It must name a level that sends.
  • default_level is the level served when the caller names none. Without it, such a request goes as it came.

Ask for a level

APIWhere effort is read
Chat completionsreasoning_effort, or reasoning.effort
Responsesreasoning.effort
Anthropic Messagesoutput_config.effort; else thinking: disabled is none, and an enabled budget under 4,096 tokens is low, under 16,384 medium, and 16,384 or more (or unset) high. adaptive names no level

minimal reads as low, xhigh and max as high, and off and disabled as none. Any other value is refused with 400 by a mapped or unsupported model and by a family; a pass-through model gets it as sent. The Responses and Messages translations to chat have no field for effort, so on those APIs effort reaches a model only through a map or a family.

curl -si http://localhost:8088/v1/chat/completions \
  -H "Authorization: Bearer $OBLETH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "acme",
    "reasoning_effort": "high",
    "messages": [{"role": "user", "content": "Is 2^61 - 1 prime?"}]
  }'

What callers see

Response headerValue
x-obleth-reasoning-effortThe level served, or unsupported when the caller's effort was removed. Absent for a pass-through model, and when no level was named and no default applied
x-obleth-reasoning-effort-requestedThe level the caller asked for, sent only when it was served as another

A mapped model's response cache entries are kept apart per level served.

/v1/models and GET /v1/models/{id} give a mapped or unsupported model a reasoning object, and /model/info has the same under model_info.reasoning. levels says which level each one is served at, so a client can see that none means low here. An unsupported model shows {"mode": "unsupported"}; a pass-through model has no reasoning.

"reasoning": {
  "mode": "mapped",
  "levels": {"none": "low", "low": "low", "medium": "medium", "high": "high"},
  "default_level": "medium"
}

Model families

A family is one name for several chat models, with the level picking which one answers: acme-instruct for none and acme-think for the rest, all called as acme. A request that names no level gets the family's default level.

On the Models page, open Model families › Add family. Enter the Name callers send, an optional Description, the model for each level under Which model answers each level, and When a caller sets no effort. The preview says where requests go. Or:

curl -X POST http://localhost:9180/api/v1/model-families \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme",
    "description": "Instruct for quick answers, thinking for the rest.",
    "default_level": "none",
    "members": {
      "none": "acme-instruct",
      "low": "acme-think",
      "medium": "acme-think",
      "high": "acme-think"
    }
  }'

GET /api/v1/model-families lists families, PUT /api/v1/model-families/{id} replaces one whole and DELETE removes it. Gateways pick up a change within 15 seconds.

The member serves the request as if the caller had named it: its pool, prices, lifecycle and allowlist entry, and a tenant allowed the family's name may call it too. The level is passed on, so the member's own map applies. Set a member that has no effort control to This model has no effort control, so the caller's reasoning_effort is removed instead of reaching a server that may refuse it.

  • Names. A family name shares one namespace with model names, aliases and variants. A model that answers for a family can't be deleted until the family changes.
  • Responses. x-obleth-model-variant carries the family's name, and x-obleth-reasoning-effort the level served.
  • Discovery. /v1/models lists a family while its default member is enabled and active or deprecated, with a family object (default_level, members) and the default member's context window, prices, capabilities and boons. A tenant with an allowlist sees it when the family, or every member, is allowed.
  • Verdicts. /v1/verdicts answers a family name with its none member.

Keep members out of the listing

A family stands for its members, but callers would still see acme, acme-instruct and acme-think in /v1/models. On each member's page, turn off List in /v1/models under General and save with the save bar. Or:

curl -X PUT http://localhost:9180/api/v1/models/$MODEL_ID/listing \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"listed": false}'

An unlisted model and its variants are left out of /v1/models and /model/info only. They still answer to every name and to GET /v1/models/{id}, stay auto candidates and can be family members. Staging would also keep the model from auto.

Request logs

Usage fieldHolds
reasoning_effortThe level served, unsupported when the caller's effort was removed, or empty when the request went through as sent
reasoning_effort_requestedWhat the caller asked for, folded to the four levels when obleth knows the value; recorded for pass-through models too
model_familyThe family the caller named. model is the member that answered, and model_variant stays empty

A request's panel in Request logs shows Called as ("acme, a model family. The reasoning level picked acme-think to answer.") and Reasoning effort in a sentence, such as "Asked for none, served as low: this model has no none level." The list adds "via acme" and "high effort" under the model. The columns are in ClickHouse usage.

Rules for a reasoning map
  • Only chat models take a map other than pass-through.
  • A mapped model needs all four levels. A use can't name its own level or a level that itself uses another.
  • A level may not set model, messages, prompt, input, stream, stream_options, tools, tool_choice or n, and may send at most 4,096 bytes of fields.
  • passthrough and unsupported keep any levels sent with them, so switching back restores them.
  • Maps and listed travel in the model manifest and in backups.
Rules for a family
  • The name is at most 128 characters with no spaces, and can't be auto, any model's name, alias or variant, or another family's name.
  • Every level needs a member. A member is given by name or alias, stored as its name, and must be a registered chat model.
  • default_level is required; description is optional, up to 500 characters.
  • Families are carried in backups.