use super::ReasoningParser;
const THINK: &str = "<think>";
const THINK_END: &str = "</think>";
const TOOL_CALL: &str = "<tool_call>";
const TOOL_CALL_END: &str = "</tool_call>";
pub(super) struct Glm47ReasoningParser;
#[derive(Clone, Copy, PartialEq, Eq)]
enum State {
Reasoning,
Answer,
Call,
}
impl ReasoningParser for Glm47ReasoningParser {
fn name(&self) -> &str {
"glm47"
}
fn start_tag(&self) -> &str {
THINK
}
fn end_tag(&self) -> &str {
THINK_END
}
fn tool_call_closes_reasoning(&self) -> bool {
true
}
fn shapes_stream_whitespace(&self) -> bool {
true
}
fn reasoning_wire(&self) -> super::ReasoningWire {
super::ReasoningWire::Vllm
}
fn extract_thinking(&self, text: &str, _enable_thinking: bool) -> (Option<String>, String) {
let mut state = State::Reasoning;
let mut reasoning = String::new();
let mut answer = String::new();
let mut rest = text;
while !rest.is_empty() {
let markers: &[&str] = match state {
State::Call => &[TOOL_CALL_END],
_ => &[THINK, THINK_END, TOOL_CALL],
};
let Some((at, marker)) = markers
.iter()
.filter_map(|m| rest.find(m).map(|at| (at, *m)))
.min_by_key(|&(at, _)| at)
else {
sink(state, &mut reasoning, &mut answer).push_str(rest);
break;
};
sink(state, &mut reasoning, &mut answer).push_str(&rest[..at]);
state = match (state, marker) {
(State::Call, _) => {
answer.push_str(marker);
State::Answer
}
(_, TOOL_CALL) => {
answer.push_str(marker);
State::Call
}
(_, THINK) => State::Reasoning,
(_, _) => State::Answer,
};
rest = &rest[at + marker.len()..];
}
let reasoning = reasoning.trim_end();
(
(!reasoning.is_empty()).then(|| reasoning.to_string()),
answer,
)
}
}
fn sink<'a>(state: State, reasoning: &'a mut String, answer: &'a mut String) -> &'a mut String {
match state {
State::Reasoning => reasoning,
State::Answer | State::Call => answer,
}
}