use crate::message::ToolChoice;
pub const OUTPUT_TOOL_NAME: &str = "final_result";
pub const OUTPUT_TOOL_DESCRIPTION: &str = "Call this tool exactly once with your final answer when you are done. Its arguments are the structured result and must satisfy the output schema.";
pub const AUGMENTATION_SEPARATOR: &str = "\n\n";
pub fn output_tool_augmentation(name: &str) -> String {
format!(
"When you have gathered enough information to answer, call the `{name}` tool exactly once with your final answer. Its arguments are the structured result and must satisfy the required schema. Do not return the final answer as plain text."
)
}
pub fn prompted_augmentation(schema: &str) -> String {
format!(
"Respond with ONLY a single JSON object that conforms to this JSON Schema. Do not include any prose, explanation, or markdown code fences.\n{schema}"
)
}
pub fn reprompt_text_answer(name: &str) -> String {
format!(
"Provide your final answer by calling the `{name}` tool with the structured result as its arguments, not as plain text."
)
}
pub fn reprompt_missing_fields(name: &str, missing: &[String]) -> String {
format!(
"The `{name}` arguments were missing required field(s): {}. Call `{name}` again with every required field.",
missing.join(", ")
)
}
pub fn output_tool_name(is_taken: impl Fn(&str) -> bool) -> String {
let mut name = OUTPUT_TOOL_NAME.to_owned();
let mut suffix = 1u32;
while is_taken(&name) {
name = format!("{OUTPUT_TOOL_NAME}_{suffix}");
suffix += 1;
}
name
}
pub fn output_tool_callable(choice: Option<&ToolChoice>, name: &str) -> bool {
match choice {
None | Some(ToolChoice::Auto | ToolChoice::Required) => true,
Some(ToolChoice::None) => false,
Some(ToolChoice::Specific { function_names }) => {
function_names.iter().any(|named| named == name)
}
}
}
pub fn missing_required_fields(
schema: &serde_json::Value,
arguments: &serde_json::Value,
) -> Vec<String> {
let object = arguments.as_object();
schema
.get("required")
.and_then(serde_json::Value::as_array)
.into_iter()
.flatten()
.filter_map(serde_json::Value::as_str)
.filter(|name| object.is_none_or(|object| !object.contains_key(*name)))
.map(str::to_owned)
.collect()
}
pub fn text_satisfies_schema(schema: Option<&serde_json::Value>, text: &str) -> bool {
serde_json::from_str::<serde_json::Value>(text.trim())
.ok()
.is_some_and(|value| {
schema.is_none_or(|schema| missing_required_fields(schema, &value).is_empty())
})
}