feat: show recipe explanation (#2530)
Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::commands::project::{handle_project_default, handle_projects_interacti
|
|||||||
use crate::commands::recipe::{handle_deeplink, handle_validate};
|
use crate::commands::recipe::{handle_deeplink, handle_validate};
|
||||||
use crate::commands::session::{handle_session_list, handle_session_remove};
|
use crate::commands::session::{handle_session_list, handle_session_remove};
|
||||||
use crate::logging::setup_logging;
|
use crate::logging::setup_logging;
|
||||||
use crate::recipes::recipe::load_recipe;
|
use crate::recipes::recipe::{explain_recipe_with_parameters, load_recipe_as_template};
|
||||||
use crate::session;
|
use crate::session;
|
||||||
use crate::session::{build_session, SessionBuilderConfig};
|
use crate::session::{build_session, SessionBuilderConfig};
|
||||||
use goose_bench::bench_config::BenchRunConfig;
|
use goose_bench::bench_config::BenchRunConfig;
|
||||||
@@ -333,6 +333,13 @@ enum Command {
|
|||||||
)]
|
)]
|
||||||
no_session: bool,
|
no_session: bool,
|
||||||
|
|
||||||
|
/// Show the recipe title, description, and parameters
|
||||||
|
#[arg(
|
||||||
|
long = "explain",
|
||||||
|
help = "Show the recipe title, description, and parameters"
|
||||||
|
)]
|
||||||
|
explain: bool,
|
||||||
|
|
||||||
/// Maximum number of consecutive identical tool calls allowed
|
/// Maximum number of consecutive identical tool calls allowed
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "max-tool-repetitions",
|
long = "max-tool-repetitions",
|
||||||
@@ -536,9 +543,10 @@ pub async fn cli() -> Result<()> {
|
|||||||
remote_extensions,
|
remote_extensions,
|
||||||
builtins,
|
builtins,
|
||||||
params,
|
params,
|
||||||
|
explain,
|
||||||
}) => {
|
}) => {
|
||||||
let input_config = match (instructions, input_text, recipe) {
|
let input_config = match (instructions, input_text, recipe, explain) {
|
||||||
(Some(file), _, _) if file == "-" => {
|
(Some(file), _, _, _) if file == "-" => {
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
std::io::stdin()
|
std::io::stdin()
|
||||||
.read_to_string(&mut input)
|
.read_to_string(&mut input)
|
||||||
@@ -550,7 +558,7 @@ pub async fn cli() -> Result<()> {
|
|||||||
additional_system_prompt: None,
|
additional_system_prompt: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(file), _, _) => {
|
(Some(file), _, _, _) => {
|
||||||
let contents = std::fs::read_to_string(&file).unwrap_or_else(|err| {
|
let contents = std::fs::read_to_string(&file).unwrap_or_else(|err| {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Instruction file not found — did you mean to use goose run --text?\n{}",
|
"Instruction file not found — did you mean to use goose run --text?\n{}",
|
||||||
@@ -564,14 +572,18 @@ pub async fn cli() -> Result<()> {
|
|||||||
additional_system_prompt: None,
|
additional_system_prompt: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, Some(text), _) => InputConfig {
|
(_, Some(text), _, _) => InputConfig {
|
||||||
contents: Some(text),
|
contents: Some(text),
|
||||||
extensions_override: None,
|
extensions_override: None,
|
||||||
additional_system_prompt: None,
|
additional_system_prompt: None,
|
||||||
},
|
},
|
||||||
(_, _, Some(recipe_name)) => {
|
(_, _, Some(recipe_name), explain) => {
|
||||||
|
if explain {
|
||||||
|
explain_recipe_with_parameters(&recipe_name, params)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let recipe =
|
let recipe =
|
||||||
load_recipe(&recipe_name, true, Some(params)).unwrap_or_else(|err| {
|
load_recipe_as_template(&recipe_name, params).unwrap_or_else(|err| {
|
||||||
eprintln!("{}: {}", console::style("Error").red().bold(), err);
|
eprintln!("{}: {}", console::style("Error").red().bold(), err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
@@ -581,7 +593,7 @@ pub async fn cli() -> Result<()> {
|
|||||||
additional_system_prompt: recipe.instructions,
|
additional_system_prompt: recipe.instructions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, None, None) => {
|
(None, None, None, _) => {
|
||||||
eprintln!("Error: Must provide either --instructions (-i), --text (-t), or --recipe. Use -i - for stdin.");
|
eprintln!("Error: Must provide either --instructions (-i), --text (-t), or --recipe. Use -i - for stdin.");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::recipes::recipe::load_recipe;
|
|||||||
/// Result indicating success or failure
|
/// Result indicating success or failure
|
||||||
pub fn handle_validate(recipe_name: &str) -> Result<()> {
|
pub fn handle_validate(recipe_name: &str) -> Result<()> {
|
||||||
// Load and validate the recipe file
|
// Load and validate the recipe file
|
||||||
match load_recipe(recipe_name, false, None) {
|
match load_recipe(recipe_name) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("{} recipe file is valid", style("✓").green().bold());
|
println!("{} recipe file is valid", style("✓").green().bold());
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -38,7 +38,7 @@ pub fn handle_validate(recipe_name: &str) -> Result<()> {
|
|||||||
/// Result indicating success or failure
|
/// Result indicating success or failure
|
||||||
pub fn handle_deeplink(recipe_name: &str) -> Result<()> {
|
pub fn handle_deeplink(recipe_name: &str) -> Result<()> {
|
||||||
// Load the recipe file first to validate it
|
// Load the recipe file first to validate it
|
||||||
match load_recipe(recipe_name, false, None) {
|
match load_recipe(recipe_name) {
|
||||||
Ok(recipe) => {
|
Ok(recipe) => {
|
||||||
if let Ok(recipe_json) = serde_json::to_string(&recipe) {
|
if let Ok(recipe_json) = serde_json::to_string(&recipe) {
|
||||||
let deeplink = base64::engine::general_purpose::STANDARD.encode(recipe_json);
|
let deeplink = base64::engine::general_purpose::STANDARD.encode(recipe_json);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod github_recipe;
|
pub mod github_recipe;
|
||||||
|
pub mod print_recipe;
|
||||||
pub mod recipe;
|
pub mod recipe;
|
||||||
pub mod search_recipe;
|
pub mod search_recipe;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use console::style;
|
||||||
|
use goose::recipe::Recipe;
|
||||||
|
|
||||||
|
pub fn print_recipe_explanation(recipe: &Recipe) {
|
||||||
|
println!(
|
||||||
|
"{} {}",
|
||||||
|
style("🔍 Loading recipe:").bold().green(),
|
||||||
|
style(&recipe.title).green()
|
||||||
|
);
|
||||||
|
println!("{}", style("📄 Description:").bold());
|
||||||
|
println!(" {}", recipe.description);
|
||||||
|
if let Some(params) = &recipe.parameters {
|
||||||
|
if !params.is_empty() {
|
||||||
|
println!("{}", style("⚙️ Recipe Parameters:").bold());
|
||||||
|
for param in params {
|
||||||
|
let default_display = match ¶m.default {
|
||||||
|
Some(val) => format!(" (default: {})", val),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
" - {} ({}, {}){}: {}",
|
||||||
|
style(¶m.key).cyan(),
|
||||||
|
param.input_type,
|
||||||
|
param.requirement,
|
||||||
|
default_display,
|
||||||
|
param.description
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_required_parameters_for_template(
|
||||||
|
params_for_template: HashMap<String, String>,
|
||||||
|
missing_params: Vec<String>,
|
||||||
|
) {
|
||||||
|
if !params_for_template.is_empty() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
style("📥 Parameters used to load this recipe:").bold()
|
||||||
|
);
|
||||||
|
for (key, value) in params_for_template {
|
||||||
|
println!(" {}: {}", key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !missing_params.is_empty() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
style("🔴 Missing parameters in the command line if you want to run the recipe:")
|
||||||
|
.bold()
|
||||||
|
);
|
||||||
|
for param in missing_params.iter() {
|
||||||
|
println!(" - {}", param);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"📩 {}:",
|
||||||
|
style("Please provide the following parameters in the command line if you want to run the recipe:").bold()
|
||||||
|
);
|
||||||
|
println!(" {}", missing_parameters_command_line(missing_params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn missing_parameters_command_line(missing_params: Vec<String>) -> String {
|
||||||
|
missing_params
|
||||||
|
.iter()
|
||||||
|
.map(|key| format!("--params {}=your_value", key))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use console::style;
|
use console::style;
|
||||||
|
|
||||||
|
use crate::recipes::print_recipe::{
|
||||||
|
missing_parameters_command_line, print_recipe_explanation,
|
||||||
|
print_required_parameters_for_template,
|
||||||
|
};
|
||||||
use crate::recipes::search_recipe::retrieve_recipe_file;
|
use crate::recipes::search_recipe::retrieve_recipe_file;
|
||||||
use goose::recipe::{Recipe, RecipeParameter, RecipeParameterRequirement};
|
use goose::recipe::{Recipe, RecipeParameter, RecipeParameterRequirement};
|
||||||
use minijinja::{Environment, Error, Template, UndefinedBehavior};
|
use minijinja::{Environment, Error, Template, UndefinedBehavior};
|
||||||
@@ -8,12 +12,63 @@ use serde_json::Value as JsonValue;
|
|||||||
use serde_yaml::Value as YamlValue;
|
use serde_yaml::Value as YamlValue;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// Loads, validates a recipe from a YAML or JSON file, and renders it with the given parameters
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - Path to the recipe file (YAML or JSON)
|
||||||
|
/// * `params` - parameters to render the recipe with
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The rendered recipe if successful
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if:
|
||||||
|
/// - Recipe is not valid
|
||||||
|
/// - The required fields are missing
|
||||||
|
pub fn load_recipe_as_template(recipe_name: &str, params: Vec<(String, String)>) -> Result<Recipe> {
|
||||||
|
let recipe_file_content = retrieve_recipe_file(recipe_name)?;
|
||||||
|
|
||||||
|
let recipe = validate_recipe_file_parameters(&recipe_file_content)?;
|
||||||
|
|
||||||
|
let (params_for_template, missing_params) =
|
||||||
|
apply_values_to_parameters(¶ms, recipe.parameters, true)?;
|
||||||
|
if !missing_params.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Please provide the following parameters in the command line: {}",
|
||||||
|
missing_parameters_command_line(missing_params)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let rendered_content = render_content_with_params(&recipe_file_content, ¶ms_for_template)?;
|
||||||
|
|
||||||
|
let recipe = parse_recipe_content(&rendered_content)?;
|
||||||
|
|
||||||
|
// Display information about the loaded recipe
|
||||||
|
println!(
|
||||||
|
"{} {}",
|
||||||
|
style("Loading recipe:").green().bold(),
|
||||||
|
style(&recipe.title).green()
|
||||||
|
);
|
||||||
|
println!("{} {}", style("Description:").bold(), &recipe.description);
|
||||||
|
|
||||||
|
if !params_for_template.is_empty() {
|
||||||
|
println!("{}", style("Parameters used to load this recipe:").bold());
|
||||||
|
for (key, value) in params_for_template {
|
||||||
|
println!("{}: {}", key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
Ok(recipe)
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads and validates a recipe from a YAML or JSON file
|
/// Loads and validates a recipe from a YAML or JSON file
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `path` - Path to the recipe file (YAML or JSON)
|
/// * `path` - Path to the recipe file (YAML or JSON)
|
||||||
/// * `log` - whether to log information about the recipe or not
|
|
||||||
/// * `params` - optional parameters to render the recipe with
|
/// * `params` - optional parameters to render the recipe with
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
@@ -26,65 +81,43 @@ use std::collections::{HashMap, HashSet};
|
|||||||
/// - The file doesn't exist
|
/// - The file doesn't exist
|
||||||
/// - The file can't be read
|
/// - The file can't be read
|
||||||
/// - The YAML/JSON is invalid
|
/// - The YAML/JSON is invalid
|
||||||
/// - The required fields are missing
|
/// - The parameter definition does not match the template variables in the recipe file
|
||||||
pub fn load_recipe(
|
pub fn load_recipe(recipe_name: &str) -> Result<Recipe> {
|
||||||
recipe_name: &str,
|
|
||||||
log: bool,
|
|
||||||
params: Option<Vec<(String, String)>>,
|
|
||||||
) -> Result<Recipe> {
|
|
||||||
let recipe_file_content = retrieve_recipe_file(recipe_name)?;
|
let recipe_file_content = retrieve_recipe_file(recipe_name)?;
|
||||||
|
|
||||||
let recipe_parameters = validate_recipe_file_parameters(&recipe_file_content)?;
|
validate_recipe_file_parameters(&recipe_file_content)
|
||||||
|
|
||||||
let (rendered_content, params_for_template) = if let Some(user_params) = params {
|
|
||||||
let params_for_template = apply_values_to_parameters(&user_params, recipe_parameters)?;
|
|
||||||
(
|
|
||||||
render_content_with_params(&recipe_file_content, ¶ms_for_template)?,
|
|
||||||
Some(params_for_template),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(recipe_file_content, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let recipe = parse_recipe_content(&rendered_content)?;
|
|
||||||
if log {
|
|
||||||
// Display information about the loaded recipe
|
|
||||||
println!(
|
|
||||||
"{} {}",
|
|
||||||
style("Loading recipe:").green().bold(),
|
|
||||||
style(&recipe.title).green()
|
|
||||||
);
|
|
||||||
println!("{} {}", style("Description:").dim(), &recipe.description);
|
|
||||||
|
|
||||||
if let Some(params) = params_for_template {
|
|
||||||
if !params.is_empty() {
|
|
||||||
println!("{}", style("Parameters:").dim());
|
|
||||||
for (key, value) in params {
|
|
||||||
println!("{}: {}", key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(); // Add a blank line for spacing
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(recipe)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_recipe_file_parameters(recipe_file_content: &str) -> Result<Vec<RecipeParameter>> {
|
pub fn explain_recipe_with_parameters(
|
||||||
|
recipe_name: &str,
|
||||||
|
params: Vec<(String, String)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let recipe_file_content = retrieve_recipe_file(recipe_name)?;
|
||||||
|
|
||||||
|
let raw_recipe = validate_recipe_file_parameters(&recipe_file_content)?;
|
||||||
|
print_recipe_explanation(&raw_recipe);
|
||||||
|
let recipe_parameters = raw_recipe.parameters;
|
||||||
|
let (params_for_template, missing_params) =
|
||||||
|
apply_values_to_parameters(¶ms, recipe_parameters, false)?;
|
||||||
|
print_required_parameters_for_template(params_for_template, missing_params);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_recipe_file_parameters(recipe_file_content: &str) -> Result<Recipe> {
|
||||||
let recipe_from_recipe_file: Recipe = parse_recipe_content(recipe_file_content)?;
|
let recipe_from_recipe_file: Recipe = parse_recipe_content(recipe_file_content)?;
|
||||||
validate_optional_parameters(&recipe_from_recipe_file)?;
|
validate_optional_parameters(&recipe_from_recipe_file)?;
|
||||||
validate_parameters_in_template(recipe_from_recipe_file, recipe_file_content)
|
validate_parameters_in_template(&recipe_from_recipe_file.parameters, recipe_file_content)?;
|
||||||
|
Ok(recipe_from_recipe_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_parameters_in_template(
|
fn validate_parameters_in_template(
|
||||||
recipe: Recipe,
|
recipe_parameters: &Option<Vec<RecipeParameter>>,
|
||||||
recipe_file_content: &str,
|
recipe_file_content: &str,
|
||||||
) -> Result<Vec<RecipeParameter>> {
|
) -> Result<()> {
|
||||||
let template_variables = extract_template_variables(recipe_file_content)?;
|
let template_variables = extract_template_variables(recipe_file_content)?;
|
||||||
|
|
||||||
let param_keys: HashSet<String> = recipe
|
let param_keys: HashSet<String> = recipe_parameters
|
||||||
.parameters
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&vec![])
|
.unwrap_or(&vec![])
|
||||||
.iter()
|
.iter()
|
||||||
@@ -100,7 +133,7 @@ fn validate_parameters_in_template(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if missing_keys.is_empty() && extra_keys.is_empty() {
|
if missing_keys.is_empty() && extra_keys.is_empty() {
|
||||||
return Ok(recipe.parameters.unwrap_or_default());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut message = String::new();
|
let mut message = String::new();
|
||||||
@@ -173,15 +206,16 @@ fn extract_template_variables(template_str: &str) -> Result<HashSet<String>> {
|
|||||||
|
|
||||||
fn apply_values_to_parameters(
|
fn apply_values_to_parameters(
|
||||||
user_params: &[(String, String)],
|
user_params: &[(String, String)],
|
||||||
recipe_parameters: Vec<RecipeParameter>,
|
recipe_parameters: Option<Vec<RecipeParameter>>,
|
||||||
) -> Result<HashMap<String, String>> {
|
enable_user_prompt: bool,
|
||||||
|
) -> Result<(HashMap<String, String>, Vec<String>)> {
|
||||||
let mut param_map: HashMap<String, String> = user_params.iter().cloned().collect();
|
let mut param_map: HashMap<String, String> = user_params.iter().cloned().collect();
|
||||||
let mut missing_params: Vec<String> = Vec::new();
|
let mut missing_params: Vec<String> = Vec::new();
|
||||||
for param in recipe_parameters {
|
for param in recipe_parameters.unwrap_or_default() {
|
||||||
if !param_map.contains_key(¶m.key) {
|
if !param_map.contains_key(¶m.key) {
|
||||||
match (¶m.default, ¶m.requirement) {
|
match (¶m.default, ¶m.requirement) {
|
||||||
(Some(default), _) => param_map.insert(param.key.clone(), default.clone()),
|
(Some(default), _) => param_map.insert(param.key.clone(), default.clone()),
|
||||||
(None, RecipeParameterRequirement::UserPrompt) => {
|
(None, RecipeParameterRequirement::UserPrompt) if enable_user_prompt => {
|
||||||
let input_value = cliclack::input(format!(
|
let input_value = cliclack::input(format!(
|
||||||
"Please enter {} ({})",
|
"Please enter {} ({})",
|
||||||
param.key, param.description
|
param.key, param.description
|
||||||
@@ -196,29 +230,16 @@ fn apply_values_to_parameters(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match missing_params.is_empty() {
|
Ok((param_map, missing_params))
|
||||||
true => Ok(param_map),
|
|
||||||
false => {
|
|
||||||
let formatted = missing_params
|
|
||||||
.iter()
|
|
||||||
.map(|key| format!("--params {}=your_value", key))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
Err(anyhow::anyhow!(
|
|
||||||
"Please provide the following parameters in the command line: {}",
|
|
||||||
formatted
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_content_with_params(content: &str, params: &HashMap<String, String>) -> Result<String> {
|
fn render_content_with_params(content: &str, params: &HashMap<String, String>) -> Result<String> {
|
||||||
// Create a minijinja environment and context
|
// Create a minijinja environment and context
|
||||||
let mut env = minijinja::Environment::new();
|
let mut env = minijinja::Environment::new();
|
||||||
env.set_undefined_behavior(UndefinedBehavior::Strict);
|
env.set_undefined_behavior(UndefinedBehavior::Strict);
|
||||||
let template: Template<'_, '_> = env.template_from_str(content)
|
let template: Template<'_, '_> = env
|
||||||
.map_err(|e: Error| anyhow::anyhow!("Failed to render recipe {}, please check if the recipe has proper syntax for variables: eg: {{ variable_name }}", e.to_string()))?;
|
.template_from_str(content)
|
||||||
|
.map_err(|e: Error| anyhow::anyhow!("Invalid template syntax: {}", e.to_string()))?;
|
||||||
|
|
||||||
// Render the template with the parameters
|
// Render the template with the parameters
|
||||||
template.render(params).map_err(|e: Error| {
|
template.render(params).map_err(|e: Error| {
|
||||||
@@ -284,13 +305,11 @@ mod tests {
|
|||||||
let content = "Hello {{ unclosed";
|
let content = "Hello {{ unclosed";
|
||||||
let params = HashMap::new();
|
let params = HashMap::new();
|
||||||
let err = render_content_with_params(content, ¶ms).unwrap_err();
|
let err = render_content_with_params(content, ¶ms).unwrap_err();
|
||||||
assert!(err
|
assert!(err.to_string().contains("Invalid template syntax"));
|
||||||
.to_string()
|
|
||||||
.contains("please check if the recipe has proper syntax"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_success() {
|
fn test_load_recipe_as_template_success() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions with {{ my_name }}",
|
"instructions": "Test instructions with {{ my_name }}",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -305,7 +324,7 @@ mod tests {
|
|||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
|
|
||||||
let params = vec![("my_name".to_string(), "value".to_string())];
|
let params = vec![("my_name".to_string(), "value".to_string())];
|
||||||
let recipe = load_recipe(recipe_path.to_str().unwrap(), false, Some(params)).unwrap();
|
let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), params).unwrap();
|
||||||
|
|
||||||
assert_eq!(recipe.title, "Test Recipe");
|
assert_eq!(recipe.title, "Test Recipe");
|
||||||
assert_eq!(recipe.description, "A test recipe");
|
assert_eq!(recipe.description, "A test recipe");
|
||||||
@@ -323,7 +342,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_success_variable_in_prompt() {
|
fn test_load_recipe_as_template_success_variable_in_prompt() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions",
|
"instructions": "Test instructions",
|
||||||
"prompt": "My prompt {{ my_name }}",
|
"prompt": "My prompt {{ my_name }}",
|
||||||
@@ -339,7 +358,7 @@ mod tests {
|
|||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
|
|
||||||
let params = vec![("my_name".to_string(), "value".to_string())];
|
let params = vec![("my_name".to_string(), "value".to_string())];
|
||||||
let recipe = load_recipe(recipe_path.to_str().unwrap(), false, Some(params)).unwrap();
|
let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), params).unwrap();
|
||||||
|
|
||||||
assert_eq!(recipe.title, "Test Recipe");
|
assert_eq!(recipe.title, "Test Recipe");
|
||||||
assert_eq!(recipe.description, "A test recipe");
|
assert_eq!(recipe.description, "A test recipe");
|
||||||
@@ -356,7 +375,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_wrong_parameters_in_recipe_file() {
|
fn test_load_recipe_as_template_wrong_parameters_in_recipe_file() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions with {{ expected_param1 }} {{ expected_param2 }}",
|
"instructions": "Test instructions with {{ expected_param1 }} {{ expected_param2 }}",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -369,7 +388,7 @@ mod tests {
|
|||||||
]"#;
|
]"#;
|
||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
|
|
||||||
let load_recipe_result = load_recipe(recipe_path.to_str().unwrap(), false, None);
|
let load_recipe_result = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new());
|
||||||
assert!(load_recipe_result.is_err());
|
assert!(load_recipe_result.is_err());
|
||||||
let err = load_recipe_result.unwrap_err();
|
let err = load_recipe_result.unwrap_err();
|
||||||
println!("{}", err.to_string());
|
println!("{}", err.to_string());
|
||||||
@@ -384,7 +403,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_with_default_values_in_recipe_file() {
|
fn test_load_recipe_as_template_with_default_values_in_recipe_file() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions with {{ param_with_default }} {{ param_without_default }}",
|
"instructions": "Test instructions with {{ param_with_default }} {{ param_without_default }}",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -405,7 +424,7 @@ mod tests {
|
|||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
let params = vec![("param_without_default".to_string(), "value1".to_string())];
|
let params = vec![("param_without_default".to_string(), "value1".to_string())];
|
||||||
|
|
||||||
let recipe = load_recipe(recipe_path.to_str().unwrap(), false, Some(params)).unwrap();
|
let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), params).unwrap();
|
||||||
|
|
||||||
assert_eq!(recipe.title, "Test Recipe");
|
assert_eq!(recipe.title, "Test Recipe");
|
||||||
assert_eq!(recipe.description, "A test recipe");
|
assert_eq!(recipe.description, "A test recipe");
|
||||||
@@ -416,7 +435,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_optional_parameters_without_default_values_in_recipe_file() {
|
fn test_load_recipe_as_template_optional_parameters_without_default_values_in_recipe_file() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions with {{ optional_param }}",
|
"instructions": "Test instructions with {{ optional_param }}",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -429,7 +448,7 @@ mod tests {
|
|||||||
]"#;
|
]"#;
|
||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
|
|
||||||
let load_recipe_result = load_recipe(recipe_path.to_str().unwrap(), false, None);
|
let load_recipe_result = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new());
|
||||||
assert!(load_recipe_result.is_err());
|
assert!(load_recipe_result.is_err());
|
||||||
let err = load_recipe_result.unwrap_err();
|
let err = load_recipe_result.unwrap_err();
|
||||||
println!("{}", err.to_string());
|
println!("{}", err.to_string());
|
||||||
@@ -439,7 +458,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_wrong_input_type_in_recipe_file() {
|
fn test_load_recipe_as_template_wrong_input_type_in_recipe_file() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions with {{ param }}",
|
"instructions": "Test instructions with {{ param }}",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -453,22 +472,22 @@ mod tests {
|
|||||||
let params = vec![("param".to_string(), "value".to_string())];
|
let params = vec![("param".to_string(), "value".to_string())];
|
||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
|
|
||||||
let load_recipe_result = load_recipe(recipe_path.to_str().unwrap(), false, Some(params));
|
let load_recipe_result = load_recipe_as_template(recipe_path.to_str().unwrap(), params);
|
||||||
assert!(load_recipe_result.is_err());
|
assert!(load_recipe_result.is_err());
|
||||||
let err = load_recipe_result.unwrap_err();
|
let err = load_recipe_result.unwrap_err();
|
||||||
assert!(err.to_string().contains("unknown variant `some_invalid_type`, expected one of `string`, `number`, `date`, `file`"));
|
assert!(err
|
||||||
|
.to_string()
|
||||||
|
.contains("unknown variant `some_invalid_type`"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_recipe_success_without_parameters() {
|
fn test_load_recipe_as_template_success_without_parameters() {
|
||||||
let instructions_and_parameters = r#"
|
let instructions_and_parameters = r#"
|
||||||
"instructions": "Test instructions"
|
"instructions": "Test instructions"
|
||||||
"#;
|
"#;
|
||||||
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters);
|
||||||
|
|
||||||
let load_recipe_result = load_recipe(recipe_path.to_str().unwrap(), false, None);
|
let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new()).unwrap();
|
||||||
assert!(load_recipe_result.is_ok());
|
|
||||||
let recipe = load_recipe_result.unwrap();
|
|
||||||
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
|
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
|
||||||
assert!(recipe.parameters.is_none());
|
assert!(recipe.parameters.is_none());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
use crate::agents::extension::ExtensionConfig;
|
use crate::agents::extension::ExtensionConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -102,15 +104,36 @@ pub enum RecipeParameterRequirement {
|
|||||||
UserPrompt,
|
UserPrompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RecipeParameterRequirement {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string(self).unwrap().trim_matches('"')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RecipeParameterInputType {
|
pub enum RecipeParameterInputType {
|
||||||
String,
|
String,
|
||||||
Number,
|
Number,
|
||||||
|
Boolean,
|
||||||
Date,
|
Date,
|
||||||
File,
|
File,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RecipeParameterInputType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string(self).unwrap().trim_matches('"')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct RecipeParameter {
|
pub struct RecipeParameter {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user