#![allow(dead_code)]
use regex::Regex;
use crate::support::trace_llm::TraceExpects;
pub fn assert_response_contains(response: &str, needles: &[&str]) {
let lower = response.to_lowercase();
for needle in needles {
assert!(
lower.contains(&needle.to_lowercase()),
"response_contains: missing \"{needle}\" in response: {response}"
);
}
}
pub fn assert_response_matches(response: &str, pattern: &str) {
let re = Regex::new(pattern).expect("invalid regex pattern");
assert!(
re.is_match(response),
"response_matches: /{pattern}/ did not match response: {response}"
);
}
pub fn assert_tools_used(started: &[String], expected: &[&str]) {
for tool in expected {
assert!(
started.iter().any(|s| s == tool),
"tools_used: \"{tool}\" not called, got: {started:?}"
);
}
}
pub fn assert_tools_not_used(started: &[String], forbidden: &[&str]) {
for tool in forbidden {
assert!(
!started.iter().any(|s| s == tool),
"tools_not_used: \"{tool}\" was called, got: {started:?}"
);
}
}
pub fn assert_max_tool_calls(started: &[String], max: usize) {
assert!(
started.len() <= max,
"max_tool_calls: expected <= {max}, got {}. Tools: {started:?}",
started.len()
);
}
pub fn assert_all_tools_succeeded(completed: &[(String, bool)]) {
let failed: Vec<&str> = completed
.iter()
.filter(|(_, success)| !*success)
.map(|(name, _)| name.as_str())
.collect();
assert!(
failed.is_empty(),
"Expected all tools to succeed, but these failed: {failed:?}. All: {completed:?}"
);
}
pub fn assert_tool_succeeded(completed: &[(String, bool)], tool_name: &str) {
let found = completed
.iter()
.any(|(name, success)| name == tool_name && *success);
assert!(
found,
"Expected '{tool_name}' to complete successfully, got: {completed:?}"
);
}
pub fn assert_response_not_contains(response: &str, forbidden: &[&str]) {
let lower = response.to_lowercase();
for needle in forbidden {
assert!(
!lower.contains(&needle.to_lowercase()),
"response_not_contains: found \"{needle}\" in response: {response}"
);
}
}
pub fn assert_tool_order(started: &[String], expected: &[&str]) {
let mut search_from = 0;
for tool in expected {
let pos = started[search_from..]
.iter()
.position(|s| s == tool)
.map(|p| p + search_from);
match pos {
Some(idx) => search_from = idx + 1,
None => {
panic!(
"assert_tool_order: \"{tool}\" not found after position {search_from} \
in: {started:?}. Expected order: {expected:?}"
);
}
}
}
}
pub fn verify_expects(
expects: &TraceExpects,
responses: &[String],
started: &[String],
completed: &[(String, bool)],
results: &[(String, String)],
label: &str,
) {
if expects.is_empty() {
return;
}
if let Some(min) = expects.min_responses {
assert!(
responses.len() >= min,
"[{label}] min_responses: expected >= {min}, got {}",
responses.len()
);
}
let joined = responses.join("\n");
if !expects.response_contains.is_empty() {
let needles: Vec<&str> = expects
.response_contains
.iter()
.map(|s| s.as_str())
.collect();
assert_response_contains(&joined, &needles);
}
if !expects.response_not_contains.is_empty() {
let forbidden: Vec<&str> = expects
.response_not_contains
.iter()
.map(|s| s.as_str())
.collect();
assert_response_not_contains(&joined, &forbidden);
}
if let Some(ref pattern) = expects.response_matches {
assert_response_matches(&joined, pattern);
}
if !expects.tools_used.is_empty() {
let expected: Vec<&str> = expects.tools_used.iter().map(|s| s.as_str()).collect();
assert_tools_used(started, &expected);
}
if !expects.tools_not_used.is_empty() {
let forbidden: Vec<&str> = expects.tools_not_used.iter().map(|s| s.as_str()).collect();
assert_tools_not_used(started, &forbidden);
}
if expects.all_tools_succeeded == Some(true) {
let failed: Vec<&str> = completed
.iter()
.filter(|(_, success)| !*success)
.map(|(name, _)| name.as_str())
.collect();
assert!(
failed.is_empty(),
"[{label}] Expected all tools to succeed, failed={failed:?}, completed={completed:?}, results={results:?}"
);
}
if let Some(max) = expects.max_tool_calls {
assert_max_tool_calls(started, max);
}
if !expects.tools_order.is_empty() {
let expected: Vec<&str> = expects.tools_order.iter().map(|s| s.as_str()).collect();
assert_tool_order(started, &expected);
}
for (tool_name, substring) in &expects.tool_results_contain {
let found = results.iter().find(|(name, _)| name == tool_name);
assert!(
found.is_some(),
"[{label}] tool_results_contain: no result for tool \"{tool_name}\", got: {results:?}"
);
let (_, preview) = found.unwrap();
assert!(
preview.to_lowercase().contains(&substring.to_lowercase()),
"[{label}] tool_results_contain: tool \"{tool_name}\" result does not contain \"{substring}\", got: \"{preview}\""
);
}
}