const OVERFLOW_PHRASES: [&str; 2] = ["Context size has been exceeded", "prompt is too long:"];
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))
}
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)
}
#[cfg(test)]
mod tests {
use super::*;
#[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 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}");
}
}