pub fn retry_nudge(_raw_response: &str) -> String {
"Your previous response was not a valid tool call. \
You must respond with a tool call, not free text. \
Please try again with a valid tool call."
.to_string()
}
pub fn unknown_tool_nudge(called_tool: &str, available_tools: &[&str]) -> String {
let tools_list = available_tools.join(", ");
format!(
"Tool '{}' does not exist. Available tools: {}. Call one of them.",
called_tool, tools_list
)
}
pub fn step_nudge(terminal_tool: &str, pending_steps: &[&str], tier: i32) -> String {
let clamped = tier.clamp(1, 3);
let steps_str = pending_steps.join(", ");
match clamped {
1 => format!(
"You cannot call {} yet. \
You must first complete these required steps: {}. \
Call one of them now.",
terminal_tool, steps_str
),
2 => format!(
"You must call one of these tools now: {}. Pick one.",
steps_str
),
3 => format!(
"STOP. You MUST call one of: {}. Do NOT call {}. \
Your next response MUST be a tool call to one of: {}.",
steps_str, terminal_tool, steps_str
),
_ => unreachable!("tier clamped to [1,3]"),
}
}
pub fn prerequisite_nudge(tool_name: &str, missing_prereqs: &[&str]) -> String {
let prereqs_str = missing_prereqs.join(", ");
format!(
"You cannot call {} yet. \
You must first call: {}. \
Call the prerequisite tool now.",
tool_name, prereqs_str
)
}
pub fn unsafe_batch_nudge(allowed_next_tools: &[&str], blocked_tools: &[&str]) -> String {
let allowed = allowed_next_tools.join(", ");
let blocked = blocked_tools.join(", ");
format!(
"Do not combine terminal and non-terminal tools in the same response. \
Allowed next tool calls: {}. \
Blocked until later: {}. \
Retry with only an allowed tool call.",
allowed, blocked
)
}
pub fn classifier_nudge(label: &str) -> String {
match label {
"wrong_arguments_semantic" => {
"The tool choice is plausible, but the argument values do not match the user request or current workflow state. Re-read the requested transformation and regenerate only the tool call.".to_string()
}
"wrong_tool_semantic" => {
"The selected tool does not match the user request or current workflow state. Re-read the request and choose the correct tool call.".to_string()
}
"tool_not_needed" => {
"A tool call is not needed for this step. Answer directly or use the terminal response tool if the workflow is complete.".to_string()
}
"needs_clarification" => {
"The request is too ambiguous for a safe tool call. Ask for the missing clarification instead of guessing.".to_string()
}
"missing_tool_fact" => {
"The final response is missing facts that are present in the tool results. Re-read the tool results and regenerate the final response with all required facts.".to_string()
}
"contradicts_tool_result" => {
"The final response contradicts the tool results. Re-read the tool results and regenerate a grounded final response.".to_string()
}
"unsupported_claim" => {
"The final response contains a claim that is not supported by the tool results. Remove unsupported claims and regenerate the final response.".to_string()
}
"failed_to_acknowledge_data_gap" => {
"The final response fails to acknowledge missing data. Regenerate the response and explicitly identify unavailable facts.".to_string()
}
_ => {
"The proposed tool call does not match the user request or workflow state. Re-read the context and regenerate only the corrected tool call.".to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retry_nudge_returns_nonempty() {
let result = retry_nudge("some text");
assert!(!result.is_empty());
}
#[test]
fn retry_nudge_does_not_echo_input() {
let input = "This is some raw model output";
let result = retry_nudge(input);
assert!(!result.contains(input));
}
#[test]
fn unknown_tool_nudge_contains_tool_names() {
let result = unknown_tool_nudge("bad_tool", &["tool_a", "tool_b"]);
assert!(result.contains("bad_tool"));
assert!(result.contains("tool_a, tool_b"));
}
#[test]
fn step_nudge_tier1_polite() {
let result = step_nudge("respond", &["search"], 1);
assert!(result.contains("respond"));
assert!(result.contains("search"));
assert!(!result.contains("STOP"));
}
#[test]
fn step_nudge_tier2_direct() {
let result = step_nudge("respond", &["search", "analyze"], 2);
assert!(result.contains("search, analyze"));
assert!(!result.contains("STOP"));
}
#[test]
fn step_nudge_tier3_aggressive() {
let result = step_nudge("respond", &["search"], 3);
assert!(result.contains("STOP"));
assert!(result.contains("respond"));
assert!(result.contains("search"));
}
#[test]
fn step_nudge_tier_clamped_low() {
let a = step_nudge("respond", &["search"], 0);
let b = step_nudge("respond", &["search"], 1);
assert_eq!(a, b);
}
#[test]
fn step_nudge_tier_clamped_high() {
let a = step_nudge("respond", &["search"], 5);
let b = step_nudge("respond", &["search"], 3);
assert_eq!(a, b);
}
#[test]
fn prerequisite_nudge_lists_prereqs() {
let result = prerequisite_nudge("finalize", &["search", "analyze"]);
assert!(result.contains("finalize"));
assert!(result.contains("search, analyze"));
}
#[test]
fn unsafe_batch_nudge_lists_allowed_and_blocked() {
let result = unsafe_batch_nudge(&["search"], &["respond"]);
assert!(result.contains("search"));
assert!(result.contains("respond"));
assert!(result.contains("Do not combine"));
}
#[test]
fn classifier_nudge_for_wrong_arguments_is_specific() {
let result = classifier_nudge("wrong_arguments_semantic");
assert!(result.contains("tool choice is plausible"));
assert!(result.contains("argument values"));
}
}