NAME
Langertha::Reasoning::Profile - Typed per-model reasoning wire-truth (accepted vocabulary + numeric bounds)
VERSION
version 0.503
SYNOPSIS
my $profile = Langertha::Reasoning::Profile->for_model('gpt-5.6-terra');
$profile->control; # 'effort'
$profile->effort_accepted_on('openai', 'max'); # 1
DESCRIPTION
Immutable value object holding what a reasoning model's wire literally accepts — the linchpin datatype behind Langertha::Reasoning. It carries the two non-overridable categories from karr k173's three-category taxonomy: the accepted vocabulary + native control type (which levels the wire takes, effort/budget/boolean) and the provider-enforced numeric bounds & magic values (Gemini 2.5's budget floor/ceiling, 0=off, -1=dynamic). The invented level-to-token interpolation (category c) is deliberately absent — it lives in Langertha::Reasoning::BudgetPolicy, clamped to this object's bounds.
"for_model" resolves an id to its profile most-specific-first (exact id → family regex → provider default), replacing the scattered per-model hashes and regexes that used to live inline in Langertha::Reasoning. Each profile carries a source receipt (doc URL + verification date) so curating a new family is a single declarative add.
model_match
The id or family pattern this profile matches — an exact Str id or a RegexpRef family pattern. Descriptive; "for_model" tests the registry's matchers in order.
control
The wire's native reasoning control type: effort (a level string), budget (an integer token budget, Gemini 2.5), boolean (Ollama's options.think, or a thinking on/off toggle — see "thinking_on") or none.
levels
The on-spectrum vocabulary the wire literally accepts, ascending. Empty for budget/boolean controls (quantization anchors are not wire-truth). For a Gemini 3 family it is the accepted thinkingLevel subset.
levels_by_wire
Per-wire refinement of "levels" for a family whose accepted set differs between the wires it speaks (the OpenAI openai vs responses axis, karr k176). The gpt-6 and gpt-5.6 generations drop max on the openai (Chat Completions) wire while keeping it on responses — max is Responses-only (live-confirmed 2026-09-16, karr k176). A family whose wires agree populates both keys with the same set; the unlisted-id default leaves it empty (no per-wire restriction).
ollama_levels
The graded level strings this model accepts on Ollama's options.think knob, in place of the model-agnostic boolean. Set only on the GPT-OSS family, which ignores think:true/think:false and instead takes low|medium|high|max (live-probed 2026-09-17 via ollama.com gpt-oss:20b, k175). "has_ollama_levels" gates "to_ollama" in Langertha::Reasoning between the level-string and boolean serializations; unset for every other model (they take the boolean).
can_disable
Whether reasoning can be turned off on this model. 0 marks the always-on "Fable-class" Anthropic models, where thinking:{type:disabled} 400s and no thinking field may be sent.
default_reasoning_off
Whether the model's server-side default effort (the one that applies when no reasoning_effort is sent) leaves reasoning OFF. 0 — the common case — means the model reasons by default: a bare request already triggers reasoning. 1 marks the models whose no-effort default is non-reasoning (the gpt-5.1 / gpt-5.2 / gpt-5.4 line: reasoning_tokens=0 with no effort, live-verified 2026-09-19), so a control-on-control gate can tell "reasoning is active on the default path" apart from "an effort was set". Consumed read-only by "_temperature_rejected_by_reasoning" in Langertha::Engine::OpenAI; distinct from "can_disable" (whether an explicit none effort turns reasoning off at all).
is_reasoning_model
Whether the model is a curated OpenAI reasoning model — one that can reject a non-default temperature while reasoning is active. 1 is set explicitly on the o-series, the gpt-5 line (non-chat), the single-digit gpt-5.N lines (non-chat) and gpt-6 (with its single-digit gpt-6.N point releases). A multi-digit id such as gpt-5.10 or gpt-6.10 matches no family and is an unknown id (karr k196, k201). The explicit non-reasoning entries (gpt-4o / gpt-4.1 and every gpt-5-chat / gpt-5.N-chat id) carry 0, and so does the unlisted-id default: an unknown model never classifies as reasoning, because wrongly dropping a caller's temperature is the worse error (karr k186). Only the OpenAI families are curated; a 0 on another family (Claude, Gemini, Qwen, GPT-OSS) means "not classified", not "known non-reasoning". Consumed read-only by "_temperature_rejected_by_reasoning" in Langertha::Engine::OpenAI.
disable_form
How "off" is expressed on the wire: absent (omit the field), explicit_none (the literal none level), think_false (Ollama), budget_zero (Gemini flash thinkingBudget=0) or thinking_disabled (thinking => { type => 'disabled' }, the thinking-toggle rows; read by "thinking_toggle_for").
thinking_on
Set only on a thinking-toggle model: one whose wire takes a thinking object with an on/off type and no effort level (MiniMax-M3 / M2.x, Kimi K2.x; karr k209, k215). Its value is the "on" type the model takes: adaptive (MiniMax) or enabled (Kimi). When set, Langertha::Reasoning serializes the effort onto that toggle on the openai and anthropic wires instead of an effort field (see "thinking_toggle_for"); the level ladder collapses to on/off. Unset everywhere else.
wire_format
The reasoning dialect this model's family primarily speaks. Descriptive: the serialization is still selected by the caller's reasoning_wire_format (an OpenAI-compatible engine may run a non-gpt model on the openai wire), so it is a curation hint, not the dispatch key.
is_gemini3
Selects the Gemini serialization branch: true for the Gemini 3 family (map onto thinkingLevel then clamp to "levels"), false for everything else (the universally-accepted binary low|high collapse a non-Gemini-3 model takes).
budget_min
budget_max
Provider-enforced integer thinkingBudget bounds for a budget-control family (Gemini 2.5). Category (b) wire-truth: a later BudgetPolicy may only emit values inside them. Carried but not enforced in Phase 1 (the value passes through verbatim, as today).
off_value
The magic thinkingBudget that disables thinking (Gemini flash / flash-lite: 0); undef where the family cannot disable (Gemini 2.5 pro).
dynamic_value
The magic thinkingBudget that hands budget selection to the model (Gemini: -1).
source
The curation receipt — provider doc URL plus verification date — for the accepted vocabulary and numeric bounds this profile encodes.
fable_class
True for the always-on Anthropic "Fable-class" models (the inverse of "can_disable"): they carry an effort but never a thinking block.
thinking_toggle_for
$profile->thinking_toggle_for('none') # { type => 'disabled' } on MiniMax-M3
$profile->thinking_toggle_for('high') # { type => 'adaptive' }
The thinking object a thinking-toggle model ("has_thinking_on") takes for a normalized effort: none gives { type => 'disabled' } when "disable_form" is thinking_disabled, and nothing (undef) on a model that cannot turn thinking off — the field is omitted and the server default applies, as on every always-on model; any other level gives { type => "thinking_on" }. undef on a model without a toggle.
effort_accepted_on
$profile->effort_accepted_on('openai', 'max')
Whether the given effort is accepted on the named OpenAI wire (openai or responses). A family without a per-wire restriction (every non-gpt family and the unlisted-id default) returns true for every effort — the full normalized enum passes through, which is the current OpenAI enum. A restricted gpt family checks membership in its "levels_by_wire" set for that wire.
anthropic_effort_ok
Whether the effort maps onto this model's output_config.effort vocabulary. Per-model, checking membership in the resolved profile's own "levels" rather than a uniform Anthropic set: Claude 4.6 (opus/sonnet) accepts low|medium|high|max but not xhigh, while Claude 4.7+/5 accept the full low|medium|high|xhigh|max (karr k177). The normalized none/minimal have no Anthropic equivalent and are absent from every Claude profile's levels.
gemini_level_for
$profile->gemini_level_for('max') # -> 'high'
Maps the normalized effort onto the Gemini thinkingLevel this model accepts. A non-Gemini-3 family ("is_gemini3" false) collapses to the universally accepted binary low|high at high. A Gemini 3 family maps onto the minimal|low|medium|high base vocabulary then clamps down to its "levels" subset (never up — an unsupported level 400s), a level below the family's floor rising to that floor.
ollama_level_for
$profile->ollama_level_for('xhigh') # -> 'max'
Maps the normalized effort onto the GPT-OSS options.think level vocabulary (low|medium|high|max): none/minimal/low become low, xhigh/max become max, medium and high pass through. GPT-OSS always reasons, so none collapses onto the floor low rather than an off state (there is no off — think:false is ignored). Mirrors "gemini_level_for"; only meaningful when "has_ollama_levels" is true.
for_model
my $profile = Langertha::Reasoning::Profile->for_model('gemini-3-pro-preview');
Resolve a model id to its profile, matched most-specific-first: an exact id, a family regex, then the provider default (which every unlisted id and the no-model case falls through to). Never dies.
SEE ALSO
Langertha::Reasoning - The value object that resolves and consumes profiles
Langertha::Reasoning::BudgetPolicy - The category-(c) convention clamped to this object's (b) bounds
Langertha::Role::ReasoningEffort - The composed role dispatching to Langertha::Reasoning
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.