use super::super::*;
use crate::reasoning_parser::ReasoningFormat;
fn tools() -> Vec<ToolDefinition> {
vec![ToolDefinition {
tool_type: "function".into(),
function: FunctionDefinition {
name: "get_weather".into(),
description: None,
parameters: Some(serde_json::json!({
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
})),
},
}]
}
const PREFIX: &str = "I'll check the weather in Paris for you.";
const CALLS: [&str; 3] = [
"<tool_call>get_weather<arg_key>city</arg_key><arg_value>Paris</arg_value></tool_call>",
"<tool_call>get_weather\n<arg_key>city</arg_key>\n<arg_value>Paris</arg_value>\n</tool_call>",
"\n<tool_call>get_weather\n<arg_key>city</arg_key><arg_value>Paris</arg_value>\n</tool_call>\n",
];
fn outputs() -> Vec<String> {
CALLS
.iter()
.flat_map(|call| {
[
format!("Need the weather tool.</think>{PREFIX}{call}"),
format!("{PREFIX}{call}"),
]
})
.collect()
}
fn expected() -> (String, serde_json::Value) {
("get_weather".into(), serde_json::json!({"city": "Paris"}))
}
#[test]
fn every_shape_is_one_accepted_call_on_the_blocking_path() {
for output in outputs() {
let (_, answer) = ReasoningFormat::Glm47
.into_parser()
.extract_thinking(&output, true);
let (_, verdicts) = parse_glm47_answer(&answer, &tools());
let calls: Vec<_> = verdicts
.into_iter()
.map(|v| match v {
Verdict::Accept { name, arguments } => (
name,
serde_json::from_str::<serde_json::Value>(&arguments).unwrap(),
),
other => panic!("refused: {other:#?}\n{output:?}"),
})
.collect();
assert_eq!(calls, vec![expected()], "{output:?}");
}
}
#[test]
fn every_shape_is_one_accepted_call_on_the_streaming_path() {
for output in outputs() {
let (_, answer) = ReasoningFormat::Glm47
.into_parser()
.extract_thinking(&output, true);
for chunk in [1, 5, 64] {
let mut d = StreamingToolDetector::new_with_tools(tools());
d.set_promote_bare_names(true);
d.set_fail_closed(true);
let mut outs = Vec::new();
let bytes: Vec<char> = answer.chars().collect();
for piece in bytes.chunks(chunk) {
outs.extend(d.process(&piece.iter().collect::<String>()));
}
outs.extend(d.flush());
let calls: Vec<_> = outs
.into_iter()
.filter_map(|o| match o {
DetectorOutput::CheckedToolCall { call, refused, .. } => {
assert!(!refused, "{output:?}");
Some((
call.function.name,
serde_json::from_str::<serde_json::Value>(&call.function.arguments)
.unwrap(),
))
}
_ => None,
})
.collect();
assert_eq!(calls, vec![expected()], "chunk {chunk}: {output:?}");
}
}
}