use crate::models::reasoning::ReasoningLevel;
pub fn reasoning_output_reserve(level: ReasoningLevel) -> usize {
match level {
ReasoningLevel::None | ReasoningLevel::Minimal => 0,
ReasoningLevel::Low => 1_024,
ReasoningLevel::Medium => 4_096,
ReasoningLevel::High | ReasoningLevel::XHigh | ReasoningLevel::Max => 8_192,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputCapMode {
Required,
NumPredict,
}
#[derive(Debug, Clone, Copy)]
pub struct OutputBudgetInputs {
pub requested_cap: usize,
pub window: Option<usize>,
pub prompt_estimate: usize,
pub provider_max_output: Option<usize>,
pub margin: usize,
pub floor: usize,
}
pub fn resolve_output_budget(inputs: &OutputBudgetInputs, mode: OutputCapMode) -> Option<usize> {
let room = inputs.window.map(|w| {
w.saturating_sub(inputs.prompt_estimate)
.saturating_sub(inputs.margin)
});
if inputs.requested_cap > 0 {
let mut capped = room.map_or(inputs.requested_cap, |r| inputs.requested_cap.min(r));
if let Some(ceiling) = inputs.provider_max_output {
capped = capped.min(ceiling);
}
return Some(match mode {
OutputCapMode::NumPredict => capped.max(inputs.floor),
OutputCapMode::Required => capped.max(1),
});
}
match mode {
OutputCapMode::NumPredict => match (room, inputs.provider_max_output) {
(Some(r), Some(ceiling)) => Some(r.min(ceiling).max(inputs.floor)),
(Some(r), None) => Some(r.max(inputs.floor)),
(None, Some(ceiling)) => Some(ceiling.max(inputs.floor)),
(None, None) => None,
},
OutputCapMode::Required => {
let ceiling = inputs
.provider_max_output
.unwrap_or(inputs.floor.max(8_192));
Some(room.map_or(ceiling, |r| ceiling.min(r)).max(1))
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn inputs(requested_cap: usize, window: Option<usize>, prompt: usize) -> OutputBudgetInputs {
OutputBudgetInputs {
requested_cap,
window,
prompt_estimate: prompt,
provider_max_output: None,
margin: 256,
floor: 512,
}
}
#[test]
fn auto_num_predict_uses_full_room_or_omits() {
assert_eq!(
resolve_output_budget(&inputs(0, Some(131_072), 1_000), OutputCapMode::NumPredict),
Some(131_072 - 1_000 - 256)
);
assert_eq!(
resolve_output_budget(&inputs(0, None, 1_000), OutputCapMode::NumPredict),
None
);
assert_eq!(
resolve_output_budget(&inputs(0, Some(1_100), 1_000), OutputCapMode::NumPredict),
Some(512)
);
}
#[test]
fn auto_required_uses_documented_ceiling_clamped_to_room() {
let mut i = inputs(0, Some(200_000), 1_000);
i.provider_max_output = Some(64_000);
assert_eq!(
resolve_output_budget(&i, OutputCapMode::Required),
Some(64_000)
);
i.prompt_estimate = 190_000;
assert_eq!(
resolve_output_budget(&i, OutputCapMode::Required),
Some(200_000 - 190_000 - 256)
);
}
#[test]
fn hard_cap_is_honored_and_room_bounded_without_reserve() {
assert_eq!(
resolve_output_budget(
&inputs(4_096, Some(131_072), 1_000),
OutputCapMode::Required
),
Some(4_096)
);
assert_eq!(
resolve_output_budget(
&inputs(4_096, Some(8_192), 7_000),
OutputCapMode::NumPredict
),
Some(8_192 - 7_000 - 256)
);
assert_eq!(
resolve_output_budget(
&inputs(4_096, Some(8_192), 8_100),
OutputCapMode::NumPredict
),
Some(512)
);
}
}