const OVERFLOW_PHRASES: [&str; 3] = [
"Context size has been exceeded",
"prompt is too long:",
"This model's maximum context length is",
];
const MIN_DERIVED_CAP: u32 = 1024;
const SHRINK_PCT: u64 = 80;
pub fn is_context_overflow(msg: &str) -> bool {
OVERFLOW_PHRASES.iter().any(|p| msg.contains(p))
}
#[must_use]
pub fn parse_context_window_error(msg: &str) -> Option<(u64, u64)> {
if let Some((_, after)) = msg.split_once("prompt is too long:") {
let prompt = first_number(after)?;
let (_, after_gt) = after.split_once('>')?;
let max = first_number(after_gt)?;
return Some((prompt, max));
}
let (_, after) = msg.split_once("This model's maximum context length is")?;
let max = first_number(after)?;
let prompt = after
.split_once("your prompt contains")
.and_then(|(_, prompt)| first_number(prompt))
.or_else(|| {
after
.split_once("your request has")
.and_then(|(_, prompt)| first_number(prompt))
})?;
Some((prompt, max))
}
fn first_number(s: &str) -> Option<u64> {
s.split(|c: char| !c.is_ascii_digit())
.find(|part| !part.is_empty())
.and_then(|part| part.parse().ok())
}
#[must_use]
pub fn recover_context_window_400(err: &anyhow::Error, _model: &str, _today: &str) -> Option<u32> {
let (_, full_window) = parse_context_window_error(&err.to_string())?;
Some(u32::try_from(full_window).unwrap_or(u32::MAX))
}
pub fn core_recover_overflow(
msg: &str,
send_budget: Option<usize>,
num_ctx_ceiling: Option<usize>,
) -> Option<u32> {
if !is_context_overflow(msg) {
return None;
}
let base = match (send_budget, num_ctx_ceiling) {
(Some(a), Some(b)) => a.min(b),
(Some(a), None) | (None, Some(a)) => a,
(None, None) => return None,
};
let derived = ((base as u64).saturating_mul(SHRINK_PCT) / 100).max(MIN_DERIVED_CAP as u64);
if derived >= base as u64 {
return None;
}
Some(derived.min(u32::MAX as u64) as u32)
}
pub(super) fn recovery_target(
proposed: usize,
refused: usize,
input_ceiling: Option<usize>,
) -> Option<usize> {
let ceiling = input_ceiling
.map_or(proposed, |ceiling| proposed.min(ceiling))
.min(refused);
ceiling.checked_sub(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recovery_target_is_below_refused_request_and_reserved_window() {
for (proposed, refused, ceiling) in [
(83_885, 80_039, Some(104_857)),
(100_000, 80_039, Some(65_536)),
(100_000, 80_039, None),
(1_024, 100, Some(1_024)),
(1, 1, Some(1)),
(usize::MAX, usize::MAX, Some(usize::MAX)),
] {
let target = recovery_target(proposed, refused, ceiling).unwrap();
assert!(target < refused, "{target} >= refused {refused}");
assert!(ceiling.is_none_or(|ceiling| target < ceiling));
assert!(target <= proposed);
}
assert_eq!(recovery_target(0, 80_039, Some(0)), None);
assert_eq!(recovery_target(83_885, 0, None), None);
}
#[test]
fn detects_llamacpp_numberless_overflow() {
let msg = r#"inference endpoint 500 Internal Server Error: {"error":{"code":500,"message":"Context size has been exceeded.","type":"server_error"}}"#;
assert!(is_context_overflow(msg));
}
#[test]
fn detects_litellm_numbered_overflow() {
let msg = "inference endpoint 400: litellm.ContextWindowExceededError: prompt is too long: 5960028 tokens > 1000000 maximum";
assert!(is_context_overflow(msg));
}
#[test]
fn detects_vllm_numbered_overflow() {
let msg = "inference endpoint 400 Bad Request: This model's maximum context length is 32768 tokens. However, you requested 16000 output tokens and your prompt contains 20000 input tokens, for a total of 36000 tokens (20000 + 16000 = 36000 > 32768). Please reduce the length of the input prompt or the number of requested output tokens.";
assert!(is_context_overflow(msg));
}
#[test]
fn parses_litellm_and_both_vllm_numbered_forms() {
assert_eq!(
parse_context_window_error(
"litellm.ContextWindowExceededError: prompt is too long: 5960028 tokens > 1000000 maximum"
),
Some((5_960_028, 1_000_000))
);
assert_eq!(
parse_context_window_error(
"This model's maximum context length is 32768 tokens. However, you requested 16000 output tokens and your prompt contains 20000 input tokens"
),
Some((20_000, 32_768))
);
assert_eq!(
parse_context_window_error(
"This model's maximum context length is 32768 tokens. However, your request has 33000 input tokens."
),
Some((33_000, 32_768))
);
assert_eq!(parse_context_window_error("invalid api key"), None);
}
#[test]
fn ignores_unrelated_errors() {
assert!(!is_context_overflow(
"inference endpoint 400: invalid api key"
));
assert!(!is_context_overflow(
"inference endpoint 503 Service Unavailable"
));
assert!(!is_context_overflow("request failed: connection reset"));
}
#[test]
fn recovery_none_when_not_overflow() {
assert_eq!(
core_recover_overflow("invalid api key", Some(24_000), Some(24_000)),
None
);
}
#[test]
fn recovery_shrinks_to_80pct_of_send_budget() {
let msg = r#"{"message":"Context size has been exceeded."}"#;
assert_eq!(core_recover_overflow(msg, Some(24_000), None), Some(19_200));
}
#[test]
fn recovery_uses_the_smaller_of_budget_and_ceiling() {
let msg = "Context size has been exceeded";
assert_eq!(
core_recover_overflow(msg, Some(24_000), Some(20_000)),
Some(16_000)
);
assert_eq!(
core_recover_overflow(msg, Some(18_000), Some(30_000)),
Some(14_400)
);
}
#[test]
fn recovery_pins_to_floor_just_above_it_then_gives_up() {
let msg = "Context size has been exceeded";
assert_eq!(core_recover_overflow(msg, Some(1_279), None), Some(1_024));
assert_eq!(core_recover_overflow(msg, Some(1_280), None), Some(1_024));
assert_eq!(core_recover_overflow(msg, Some(1_025), None), Some(1_024));
assert_eq!(core_recover_overflow(msg, Some(1_024), None), None);
assert_eq!(core_recover_overflow(msg, Some(100), None), None);
assert_eq!(core_recover_overflow(msg, Some(0), None), None);
}
#[test]
fn recovery_never_raises_the_budget() {
let msg = "Context size has been exceeded";
for base in [
1_025usize,
1_280,
2_000,
8_000,
24_000,
100_000,
u32::MAX as usize,
] {
if let Some(cap) = core_recover_overflow(msg, Some(base), None) {
assert!((cap as usize) < base, "cap {cap} !< base {base}");
assert!(cap >= 1_024, "cap {cap} below floor for base {base}");
}
}
}
#[test]
fn recovery_none_when_no_budget_known() {
let msg = "Context size has been exceeded";
assert_eq!(core_recover_overflow(msg, None, None), None);
}
#[test]
fn recovery_monotonically_tightens_on_repeat() {
let msg = "Context size has been exceeded";
let first = core_recover_overflow(msg, Some(24_000), None).unwrap();
let second = core_recover_overflow(msg, Some(first as usize), None).unwrap();
assert!(second < first, "{second} !< {first}");
}
}