use super::*;
pub fn detect_thinking_token_loop(tokens: &[u32], wp: WatchdogParams) -> bool {
detect_thinking_token_loop_with(tokens, None, wp)
}
pub fn detect_thinking_token_loop_with(
tokens: &[u32],
override_: Option<crate::api::inference_types::RepetitionDetectionParams>,
wp: WatchdogParams,
) -> bool {
let (period_min, period_max, min_repeats) = match override_ {
Some(p) => (
p.min_pattern_size as usize,
p.max_pattern_size as usize,
p.min_count as usize,
),
None => (
THINK_LOOP_PERIOD_MIN,
THINK_LOOP_PERIOD_MAX,
wp.think_loop_min_repeats,
),
};
let scan_window = match override_ {
Some(_) => 0, None => wp.think_loop_scan_window,
};
detect_token_loop(
tokens,
THINK_LOOP_MIN_TOKENS as usize,
period_min,
period_max,
min_repeats,
scan_window,
)
}
pub fn detect_content_token_loop(tokens: &[u32]) -> bool {
detect_content_token_loop_with(tokens, None)
}
pub fn detect_content_token_loop_with(
tokens: &[u32],
override_: Option<crate::api::inference_types::RepetitionDetectionParams>,
) -> bool {
let (period_min, period_max, min_repeats) = match override_ {
Some(p) => (
p.min_pattern_size as usize,
p.max_pattern_size as usize,
p.min_count as usize,
),
None => (
CONTENT_LOOP_PERIOD_MIN,
CONTENT_LOOP_PERIOD_MAX,
CONTENT_LOOP_MIN_REPEATS,
),
};
detect_token_loop(
tokens,
CONTENT_LOOP_MIN_TOKENS as usize,
period_min,
period_max,
min_repeats,
CONTENT_LOOP_SCAN_WINDOW,
)
}
pub fn detect_content_token_loop_normalized(tokens: &[u32], mask: &[bool]) -> bool {
detect_content_token_loop_normalized_with(tokens, mask, None)
}
pub fn detect_content_token_loop_normalized_with(
tokens: &[u32],
mask: &[bool],
override_: Option<crate::api::inference_types::RepetitionDetectionParams>,
) -> bool {
let n = tokens.len();
if n < CONTENT_LOOP_MIN_TOKENS as usize {
return false;
}
let tail_start = n.saturating_sub(CONTENT_LOOP_SCAN_WINDOW);
let is_numeric = |t: u32| (t as usize) < mask.len() && mask[t as usize];
let mut norm: Vec<u32> = Vec::with_capacity(CONTENT_LOOP_SCAN_WINDOW);
for &t in &tokens[tail_start..] {
if is_numeric(t) {
if norm.last() != Some(&NUMERIC_SENTINEL) {
norm.push(NUMERIC_SENTINEL);
}
} else {
norm.push(t);
}
}
let has_sentinel = norm.contains(&NUMERIC_SENTINEL);
let has_struct = norm.iter().any(|&t| t != NUMERIC_SENTINEL);
if !has_sentinel || !has_struct {
return false;
}
let (period_min, period_max, min_repeats) = match override_ {
Some(p) => (
p.min_pattern_size as usize,
p.max_pattern_size as usize,
p.min_count as usize,
),
None => (
CONTENT_LOOP_PERIOD_MIN,
CONTENT_LOOP_PERIOD_MAX,
CONTENT_LOOP_NORM_MIN_REPEATS,
),
};
detect_token_loop_with_period(
&norm,
period_min,
period_max,
min_repeats,
CONTENT_LOOP_SCAN_WINDOW,
)
}
pub fn detect_token_loop(
tokens: &[u32],
min_tokens: usize,
period_min: usize,
period_max: usize,
min_repeats: usize,
_scan_window: usize,
) -> bool {
let n = tokens.len();
if n < min_tokens {
return false;
}
if min_repeats < 2 {
return false;
}
let period_min = period_min.max(1);
for pattern_len in period_min..=period_max {
if pattern_len * min_repeats > n {
return false;
}
if has_repeating_pattern_anchored(tokens, pattern_len, min_repeats) {
return true;
}
}
false
}
#[inline]
fn has_repeating_pattern_anchored(tokens: &[u32], pattern_len: usize, min_repeats: usize) -> bool {
let n = tokens.len();
for offset_in_window in 1..=pattern_len {
let target = tokens[n - offset_in_window];
for m in 1..min_repeats {
let idx = n - (pattern_len * m + offset_in_window);
if tokens[idx] != target {
return false;
}
}
}
true
}
fn detect_token_loop_with_period(
tokens: &[u32],
period_min: usize,
period_max: usize,
min_repeats: usize,
_scan_window: usize,
) -> bool {
let n = tokens.len();
if min_repeats < 2 {
return false;
}
let period_min = period_min.max(1);
for pattern_len in period_min..=period_max {
if pattern_len * min_repeats > n {
return false;
}
let window = &tokens[n - pattern_len..];
let has_numeric = window.contains(&NUMERIC_SENTINEL);
let has_structural = window.iter().any(|&t| t != NUMERIC_SENTINEL);
if !(has_numeric && has_structural) {
continue;
}
if has_repeating_pattern_anchored(tokens, pattern_len, min_repeats) {
return true;
}
}
false
}