use super::super::emit_step::emit_token;
use super::super::sched_ctx::SchedCtx;
use super::super::test_support::{EOS, test_seq};
use super::super::types::ActiveSeq;
use super::close_thinking_at_tool_call;
const THINK_END: u32 = 154842;
const TOOL_CALL: u32 = 154843;
const TOOL_CALL_END: u32 = 154844;
const CALL_BODY: [u32; 8] = [455, 68852, 154847, 8923, 154848, 154849, 59190, 154850];
fn glm_seq() -> ActiveSeq {
let (mut a, _rx) = test_seq((1000..1007).collect(), 5000, None, 10);
a.finished = false;
a.inside_thinking = true;
a.think_end_token = Some(THINK_END);
a.tool_call_start_token = Some(TOOL_CALL);
a.tool_call_end_token = Some(TOOL_CALL_END);
a
}
fn sched(closed_by: Option<u32>) -> SchedCtx {
let mut s = SchedCtx::for_test();
s.limits.thinking_closed_by = closed_by;
s
}
fn run_call_written_while_thinking(a: &mut ActiveSeq, s: &SchedCtx) {
for tok in [40, 3278, 1779, 279, 9101, 304, 12089, 369, 498, 13] {
emit_token(a, tok, None, s);
}
emit_token(a, TOOL_CALL, None, s);
for tok in CALL_BODY {
emit_token(a, tok, None, s);
}
emit_token(a, TOOL_CALL_END, None, s);
emit_token(a, EOS[0], None, s);
}
#[test]
fn a_call_written_while_thinking_ends_thinking_and_the_eos_after_it_ends_the_turn() {
let s = sched(Some(TOOL_CALL));
let mut a = glm_seq();
run_call_written_while_thinking(&mut a, &s);
assert!(!a.inside_thinking);
assert!(
a.tool_call_completed,
"</tool_call> marks the call complete"
);
assert!(a.finished, "the EOS after the call must end the turn");
let n = a.output_tokens.len();
assert_eq!(&a.output_tokens[n - 2..], &[TOOL_CALL_END, EOS[0]]);
assert_eq!(
a.output_tokens.iter().filter(|&&t| t == TOOL_CALL).count(),
1,
"the opener is kept in the output, once"
);
}
#[test]
fn without_the_format_flag_the_call_stays_in_thinking_and_the_eos_still_ends_the_turn() {
let s = sched(None);
let mut a = glm_seq();
run_call_written_while_thinking(&mut a, &s);
assert!(a.inside_thinking);
assert!(!a.tool_call_completed);
assert!(a.finished, "an EOS inside thinking must end the turn");
}
#[test]
fn only_the_named_opener_closes_and_only_while_thinking() {
let mut a = glm_seq();
assert!(!close_thinking_at_tool_call(&mut a, TOOL_CALL, None));
assert!(!close_thinking_at_tool_call(&mut a, 1000, Some(TOOL_CALL)));
assert!(a.inside_thinking);
a.force_end_thinking = true;
assert!(close_thinking_at_tool_call(
&mut a,
TOOL_CALL,
Some(TOOL_CALL)
));
assert!(!a.inside_thinking && a.think_ended && !a.force_end_thinking);
assert!(
!close_thinking_at_tool_call(&mut a, TOOL_CALL, Some(TOOL_CALL)),
"outside thinking an opener is plain content"
);
}
#[test]
fn the_during_thinking_mask_spares_an_opener_that_closes_thinking() {
use crate::scheduler::logit_processors::tool_during_think::ToolCallDuringThinkingMask;
use crate::scheduler::logit_processors::{LogitsContext, LogitsProcessor, SamplingLevers};
let scratch = crate::scheduler::sched_ctx::DecodeScratch::default();
let io = crate::scheduler::io::SchedIo::for_test();
let logit_after = |closed_by: Option<u32>| {
let ctx = LogitsContext {
scratch: &scratch,
tel: &*io.tel,
clock: &*io.clock,
watchdog: crate::scheduler::helpers::WatchdogParams::default(),
boundary_mask: None,
mid_word_mask: None,
sampling: SamplingLevers::default(),
think_end_token: Some(THINK_END),
think_start_token: None,
tool_call_start_token: Some(TOOL_CALL),
tool_call_end_token: Some(TOOL_CALL_END),
thinking_closed_by: closed_by,
verify_pos: 0,
};
let mut a = glm_seq();
let mut logits = vec![1.0f32; TOOL_CALL as usize + 1];
ToolCallDuringThinkingMask.apply(&mut logits, &mut a, &ctx);
logits[TOOL_CALL as usize]
};
assert_eq!(logit_after(None), f32::NEG_INFINITY);
assert_eq!(logit_after(Some(TOOL_CALL)), 1.0);
}