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 - none

  • thinking.type = adaptive, thinking.type = enabled without a budget, think: true - "default_level" (medium)

  • think: "low", ... - the level as sent, when it is one of none minimal low medium high xhigh max; any other string maps to nothing

  • thinking.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

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.