NAME

Langertha::Reasoning::BudgetPolicy - Invented level<->token-budget interpolation, clamped to a Profile's enforced bounds

VERSION

version 0.503

SYNOPSIS

my $policy = Langertha::Reasoning::BudgetPolicy->new(
    profile => Langertha::Reasoning::Profile->for_model('gemini-2.5-pro'),
    form    => 'range',   # interpolate across the profile's [min,max]
    levels  => [qw( low medium high )],
);
my $budget = $policy->budget_for('medium');   # ~16448, always within 128..32768
my $level  = $policy->level_for(4096);        # nearest anchor level

# explicit anchors instead of a curve
my $curated = Langertha::Reasoning::BudgetPolicy->new(
    profile => Langertha::Reasoning::Profile->for_model('gemini-2.5-flash'),
    points  => { none => 0, low => 2048, medium => 8192, high => 24576 },
);

DESCRIPTION

Category (c) of karr k173's three-category reasoning taxonomy (ADR 0023): the invented level-to-token-budget interpolation and its inbound inverse. No provider publishes an official reasoning-level to token-budget mapping — OpenAI and Anthropic effort is adaptive and undocumented, and the only numeric level-to-token formulas in the wild are OpenRouter's, labeled OpenRouter's own convention. So every number this class emits is library convention, not wire-truth, and "source" should say so honestly.

Because the numbers are invented, they must never be able to emit a value the API rejects. Every numeric output of this class is clamped to the owning Langertha::Reasoning::Profile's category-(b) enforced bounds ("budget_min" in Langertha::Reasoning::Profile / "budget_max"). This is the firewall rule: convention (c) may read the profile's enforced bounds (b) but can never cross them. Putting those bounds inside this convention layer — as the original karr k173 ticket proposed — would let an override silently violate a hard API bound; keeping them in the Profile and clamping against them here is the single most important correction of the design.

This is not a capability and is not default-shipped for effort providers. It exists only where a downstream consumer needs budget<->level conversion and constructs it explicitly. Nothing in Langertha wires it in by default.

profile

The owning Langertha::Reasoning::Profile. Its category-(b) bounds ("budget_min" in Langertha::Reasoning::Profile / "budget_max") are the firewall every numeric output is clamped to. A range-form policy needs both bounds present (there is nothing to interpolate across otherwise).

form

range (interpolate a curve across the profile's [budget_min, budget_max]) or explicit (look the budget up in curated "points"). Defaults to explicit when "points" is given, otherwise range.

curve

For form => 'range': linear (even spacing) or log (geometric spacing, finer at the low end). Ignored for explicit. Default linear; per karr k173 q3 the curve is convention, so log is offered but not asserted as truth.

points

For form => 'explicit': a curated map of reasoning level to token budget (the invented anchors). Keys must be members of the normalized vocabulary (none|minimal|low|medium|high|xhigh|max). Each looked-up budget is still clamped to the profile's (b) bounds, so a curated anchor outside them can never reach the wire.

levels

The quantization anchor ladder (ascending) this convention spans — the consumer-side anchors that deliberately do not live in the Profile (whose levels is empty for a budget control, keeping it pure wire-truth). Defaults to the profile's levels when it has any, to the sorted keys of "points" for an explicit policy, else to low|medium|high.

default_bool_level

The normalized level a boolean-control provider's "thinking on" (think:true) corresponds to for this convention, the boolean-to-level half of the inbound bijection (its "off" counterpart is none). Default medium.

source

The honest curation receipt. Because no provider publishes a level-to-budget table, this defaults to a string that says the numbers are convention rather than wire-truth; a consumer curating empirical anchors should replace it with what it measured and when.

budget_for

my $tokens = $policy->budget_for('medium');

Convert a normalized reasoning level to an integer token budget. In explicit form the curated "points" anchor is used (the nearest anchor by ordinal when the exact level is absent); in range form the "curve" is interpolated across the profile's [budget_min, budget_max]. The result is always clamped to the profile's category-(b) bounds — the firewall — so an invented number can never reach the wire above the ceiling or below the floor.

level_for

my $level = $policy->level_for(4096);

The inbound inverse: convert an integer token budget to the nearest normalized level on the anchor ladder. The budget is clamped to the profile's (b) bounds first, so an out-of-range budget maps to the boundary level rather than off the ladder; a budget equal to the profile's off_value maps to none.

for_model

my $policy = Langertha::Reasoning::BudgetPolicy->for_model(
    'gemini-2.5-pro', form => 'range' );

Convenience constructor mirroring "for_model" in Langertha::Reasoning::Profile: resolves the id to its profile and passes the remaining convention options ("form", "curve", "points", "levels", "source") through to "new".

SEE ALSO

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.