NAME
Langertha::Knarr::Reasoning - Map a face's native reasoning controls onto the normalized reasoning_effort level
VERSION
version 1.102
SYNOPSIS
my $reasoning = Langertha::Knarr::Reasoning->new;
# Anthropic face: thinking / output_config.effort
my $level = $reasoning->from_anthropic($body);
# Ollama face: think (boolean or level string)
my $level = $reasoning->from_ollama( $body->{think} );
# exactly overridable
my $mine = Langertha::Knarr::Reasoning->new(
default_level => 'high',
budget_points => { low => 1024, medium => 4096, high => 16384 },
);
DESCRIPTION
Only the OpenAI face carries the normalized reasoning_effort enum natively. The Anthropic face carries thinking (enabled with a budget_tokens integer, adaptive, disabled) and the Ollama face carries think (a boolean, or a level string on GPT-OSS). This class turns those into the one normalized level "reasoning_effort" in Langertha::Knarr::Request holds, so the routed path forwards a client's thinking request to whatever engine the model routes to, capability-gated like every other control.
The mapping is:
thinking.type = disabled,think: false-nonethinking.type = adaptive,thinking.type = enabledwithout a budget,think: true- "default_level" (medium)think: "low", ... - the level as sent, when it is one ofnone minimal low medium high xhigh max; any other string maps to nothingthinking.budget_tokens = N- the nearest level of a Langertha::Reasoning::BudgetPolicy for the request's model (see "budget_points")
A face's own explicit effort always wins over a derived one: a top-level reasoning_effort, and on the Anthropic face output_config.effort, are taken as sent. Every value that does not fit maps to nothing, never to an invented level, and nothing here croaks on a client body.
Budget mapping needs a core with Langertha::Reasoning::BudgetPolicy ("budget_available"). On an older core (Langertha 0.503) budget_tokens stays unmapped - the request carries no reasoning_effort from it - while the boolean and level-string forms, which need no budget convention, still map.
default_level
The level a client's "thinking on" without a level maps to (think: true, thinking.type = adaptive, thinking.type = enabled without a budget). Defaults to "default_bool_level" in Langertha::Reasoning::BudgetPolicy on a core that has it, else medium. Its "off" counterpart is always none.
budget_points
Optional curated level-to-token anchors, e.g. { low => 1024, medium => 4096, high => 16384 }. When set, every budget_tokens maps through an explicit Langertha::Reasoning::BudgetPolicy with these points. When unset, a model whose Langertha::Reasoning::Profile carries both budget bounds (Gemini 2.5) maps through the range policy across those bounds, and every other model through "fallback_budget_points". The policy always clamps to the profile's enforced bounds.
budget_available
True when the installed core has Langertha::Reasoning::BudgetPolicy, i.e. when budget_tokens maps at all.
fallback_budget_points
The anchors used for a model whose profile carries no budget bounds (every Claude model) when "budget_points" is unset: { low => 2048, medium => 8192, high => 24576 } - the curated example anchors of Langertha::Reasoning::BudgetPolicy. No provider publishes a level-to-budget table, so these are convention, not wire-truth. There is no none anchor: a client that enabled thinking with a budget never maps to none. Override in a subclass, or set "budget_points".
budget_policy
my $policy = $reasoning->budget_policy('claude-sonnet-4-6');
The Langertha::Reasoning::BudgetPolicy a budget for $model maps through (see "budget_points"), or nothing when "budget_available" is false or the policy cannot be built.
level_for_budget
my $level = $reasoning->level_for_budget( 4096, $model );
The normalized level a token budget maps to for $model, or nothing for a budget that is not a non-negative integer or when budgets do not map (see "budget_available").
level_for_bool
my $level = $reasoning->level_for_bool(1); # default_level
my $level = $reasoning->level_for_bool(0); # 'none'
from_anthropic
my $level = $reasoning->from_anthropic($decoded_body);
The level an Anthropic Messages body asks for: a top-level reasoning_effort or output_config.effort as sent, else what thinking maps to (see "DESCRIPTION"), else nothing.
from_ollama
my $level = $reasoning->from_ollama( $body->{think}, $body->{reasoning_effort} );
The level an Ollama think value asks for: a JSON boolean maps through "level_for_bool", a level string of the normalized vocabulary is kept, any other value maps to nothing. An explicit reasoning_effort wins.
SEE ALSO
Langertha::Knarr::Request - Holds the resulting
reasoning_effort, capability-gated inchat_f_argsLangertha::Reasoning::BudgetPolicy - The budget-to-level convention budgets map through
Langertha::Reasoning::Profile - The per-model bounds that convention is clamped to
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-knarr/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 <torsten@raudssus.de> https://raudssus.de/
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.