feat: recipes can retry with success criteria (#3474)
This commit is contained in:
+39
-36
@@ -685,6 +685,14 @@ pub struct InputConfig {
|
||||
pub additional_system_prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RecipeInfo {
|
||||
pub session_settings: Option<SessionSettings>,
|
||||
pub sub_recipes: Option<Vec<goose::recipe::SubRecipe>>,
|
||||
pub final_output_response: Option<goose::recipe::Response>,
|
||||
pub retry_config: Option<goose::agents::types::RetryConfig>,
|
||||
}
|
||||
|
||||
pub async fn cli() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
@@ -771,6 +779,7 @@ pub async fn cli() -> Result<()> {
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
retry_config: None,
|
||||
})
|
||||
.await;
|
||||
setup_logging(
|
||||
@@ -828,27 +837,19 @@ pub async fn cli() -> Result<()> {
|
||||
provider,
|
||||
model,
|
||||
}) => {
|
||||
let (input_config, session_settings, sub_recipes, final_output_response) = match (
|
||||
instructions,
|
||||
input_text,
|
||||
recipe,
|
||||
) {
|
||||
let (input_config, recipe_info) = match (instructions, input_text, recipe) {
|
||||
(Some(file), _, _) if file == "-" => {
|
||||
let mut input = String::new();
|
||||
std::io::stdin()
|
||||
.read_to_string(&mut input)
|
||||
.expect("Failed to read from stdin");
|
||||
|
||||
(
|
||||
InputConfig {
|
||||
contents: Some(input),
|
||||
extensions_override: None,
|
||||
additional_system_prompt: system,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
let input_config = InputConfig {
|
||||
contents: Some(input),
|
||||
extensions_override: None,
|
||||
additional_system_prompt: system,
|
||||
};
|
||||
(input_config, None)
|
||||
}
|
||||
(Some(file), _, _) => {
|
||||
let contents = std::fs::read_to_string(&file).unwrap_or_else(|err| {
|
||||
@@ -858,27 +859,21 @@ pub async fn cli() -> Result<()> {
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
(
|
||||
InputConfig {
|
||||
contents: Some(contents),
|
||||
extensions_override: None,
|
||||
additional_system_prompt: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
let input_config = InputConfig {
|
||||
contents: Some(contents),
|
||||
extensions_override: None,
|
||||
additional_system_prompt: None,
|
||||
};
|
||||
(input_config, None)
|
||||
}
|
||||
(_, Some(text), _) => (
|
||||
InputConfig {
|
||||
(_, Some(text), _) => {
|
||||
let input_config = InputConfig {
|
||||
contents: Some(text),
|
||||
extensions_override: None,
|
||||
additional_system_prompt: system,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
};
|
||||
(input_config, None)
|
||||
}
|
||||
(_, _, Some(recipe_name)) => {
|
||||
if explain {
|
||||
explain_recipe(&recipe_name, params)?;
|
||||
@@ -891,7 +886,9 @@ pub async fn cli() -> Result<()> {
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
extract_recipe_info_from_cli(recipe_name, params, additional_sub_recipes)?
|
||||
let (input_config, recipe_info) =
|
||||
extract_recipe_info_from_cli(recipe_name, params, additional_sub_recipes)?;
|
||||
(input_config, Some(recipe_info))
|
||||
}
|
||||
(None, None, None) => {
|
||||
eprintln!("Error: Must provide either --instructions (-i), --text (-t), or --recipe. Use -i - for stdin.");
|
||||
@@ -909,7 +906,9 @@ pub async fn cli() -> Result<()> {
|
||||
builtins,
|
||||
extensions_override: input_config.extensions_override,
|
||||
additional_system_prompt: input_config.additional_system_prompt,
|
||||
settings: session_settings,
|
||||
settings: recipe_info
|
||||
.as_ref()
|
||||
.and_then(|r| r.session_settings.clone()),
|
||||
provider,
|
||||
model,
|
||||
debug,
|
||||
@@ -918,8 +917,11 @@ pub async fn cli() -> Result<()> {
|
||||
scheduled_job_id,
|
||||
interactive, // Use the interactive flag from the Run command
|
||||
quiet,
|
||||
sub_recipes,
|
||||
final_output_response,
|
||||
sub_recipes: recipe_info.as_ref().and_then(|r| r.sub_recipes.clone()),
|
||||
final_output_response: recipe_info
|
||||
.as_ref()
|
||||
.and_then(|r| r.final_output_response.clone()),
|
||||
retry_config: recipe_info.as_ref().and_then(|r| r.retry_config.clone()),
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -1051,6 +1053,7 @@ pub async fn cli() -> Result<()> {
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
retry_config: None,
|
||||
})
|
||||
.await;
|
||||
setup_logging(
|
||||
|
||||
@@ -52,6 +52,7 @@ pub async fn agent_generator(
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
retry_config: None,
|
||||
})
|
||||
.await;
|
||||
|
||||
|
||||
@@ -491,6 +491,7 @@ async fn process_message_streaming(
|
||||
schedule_id: None,
|
||||
execution_mode: None,
|
||||
max_turns: None,
|
||||
retry_config: None,
|
||||
};
|
||||
|
||||
// Get response from agent
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use goose::recipe::{Response, SubRecipe};
|
||||
use goose::recipe::SubRecipe;
|
||||
|
||||
use crate::recipes::print_recipe::print_recipe_info;
|
||||
use crate::recipes::recipe::load_recipe;
|
||||
use crate::recipes::search_recipe::retrieve_recipe_file;
|
||||
use crate::{cli::InputConfig, session::SessionSettings};
|
||||
use crate::{
|
||||
cli::{InputConfig, RecipeInfo},
|
||||
session::SessionSettings,
|
||||
};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn extract_recipe_info_from_cli(
|
||||
recipe_name: String,
|
||||
params: Vec<(String, String)>,
|
||||
additional_sub_recipes: Vec<String>,
|
||||
) -> Result<(
|
||||
InputConfig,
|
||||
Option<SessionSettings>,
|
||||
Option<Vec<SubRecipe>>,
|
||||
Option<Response>,
|
||||
)> {
|
||||
) -> Result<(InputConfig, RecipeInfo)> {
|
||||
let recipe = load_recipe(&recipe_name, params.clone()).unwrap_or_else(|err| {
|
||||
eprintln!("{}: {}", console::style("Error").red().bold(), err);
|
||||
std::process::exit(1);
|
||||
@@ -49,20 +46,24 @@ pub fn extract_recipe_info_from_cli(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
InputConfig {
|
||||
contents: recipe.prompt.filter(|s| !s.trim().is_empty()),
|
||||
extensions_override: recipe.extensions,
|
||||
additional_system_prompt: recipe.instructions,
|
||||
},
|
||||
recipe.settings.map(|s| SessionSettings {
|
||||
let input_config = InputConfig {
|
||||
contents: recipe.prompt.filter(|s| !s.trim().is_empty()),
|
||||
extensions_override: recipe.extensions,
|
||||
additional_system_prompt: recipe.instructions,
|
||||
};
|
||||
|
||||
let recipe_info = RecipeInfo {
|
||||
session_settings: recipe.settings.map(|s| SessionSettings {
|
||||
goose_provider: s.goose_provider,
|
||||
goose_model: s.goose_model,
|
||||
temperature: s.temperature,
|
||||
}),
|
||||
Some(all_sub_recipes),
|
||||
recipe.response,
|
||||
))
|
||||
sub_recipes: Some(all_sub_recipes),
|
||||
final_output_response: recipe.response,
|
||||
retry_config: recipe.retry,
|
||||
};
|
||||
|
||||
Ok((input_config, recipe_info))
|
||||
}
|
||||
|
||||
fn extract_recipe_name(recipe_identifier: &str) -> String {
|
||||
@@ -93,8 +94,11 @@ mod tests {
|
||||
let params = vec![("name".to_string(), "my_value".to_string())];
|
||||
let recipe_name = recipe_path.to_str().unwrap().to_string();
|
||||
|
||||
let (input_config, settings, sub_recipes, response) =
|
||||
let (input_config, recipe_info) =
|
||||
extract_recipe_info_from_cli(recipe_name, params, Vec::new()).unwrap();
|
||||
let settings = recipe_info.session_settings;
|
||||
let sub_recipes = recipe_info.sub_recipes;
|
||||
let response = recipe_info.final_output_response;
|
||||
|
||||
assert_eq!(input_config.contents, Some("test_prompt".to_string()));
|
||||
assert_eq!(
|
||||
@@ -149,8 +153,11 @@ mod tests {
|
||||
sub_recipe2_path.to_string_lossy().to_string(),
|
||||
];
|
||||
|
||||
let (input_config, settings, sub_recipes, response) =
|
||||
let (input_config, recipe_info) =
|
||||
extract_recipe_info_from_cli(recipe_name, params, additional_sub_recipes).unwrap();
|
||||
let settings = recipe_info.session_settings;
|
||||
let sub_recipes = recipe_info.sub_recipes;
|
||||
let response = recipe_info.final_output_response;
|
||||
|
||||
assert_eq!(input_config.contents, Some("test_prompt".to_string()));
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use console::style;
|
||||
use goose::agents::extension::ExtensionError;
|
||||
use goose::agents::types::RetryConfig;
|
||||
use goose::agents::Agent;
|
||||
use goose::config::{Config, ExtensionConfig, ExtensionConfigManager};
|
||||
use goose::providers::create;
|
||||
@@ -60,6 +61,8 @@ pub struct SessionBuilderConfig {
|
||||
pub sub_recipes: Option<Vec<SubRecipe>>,
|
||||
/// Final output expected response
|
||||
pub final_output_response: Option<Response>,
|
||||
/// Retry configuration for automated validation and recovery
|
||||
pub retry_config: Option<RetryConfig>,
|
||||
}
|
||||
|
||||
/// Offers to help debug an extension failure by creating a minimal debugging session
|
||||
@@ -138,6 +141,7 @@ async fn offer_extension_debugging_help(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
// Process the debugging request
|
||||
@@ -407,6 +411,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
|
||||
session_config.scheduled_job_id.clone(),
|
||||
session_config.max_turns,
|
||||
edit_mode,
|
||||
session_config.retry_config.clone(),
|
||||
);
|
||||
|
||||
// Add extensions if provided
|
||||
@@ -602,6 +607,7 @@ mod tests {
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
retry_config: None,
|
||||
};
|
||||
|
||||
assert_eq!(config.extensions.len(), 1);
|
||||
|
||||
@@ -28,6 +28,7 @@ use anyhow::{Context, Result};
|
||||
use completion::GooseCompleter;
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use goose::agents::extension::{Envs, ExtensionConfig};
|
||||
use goose::agents::types::RetryConfig;
|
||||
use goose::agents::{Agent, SessionConfig};
|
||||
use goose::config::Config;
|
||||
use goose::message::{Message, MessageContent};
|
||||
@@ -64,6 +65,7 @@ pub struct Session {
|
||||
scheduled_job_id: Option<String>, // ID of the scheduled job that triggered this session
|
||||
max_turns: Option<u32>,
|
||||
edit_mode: Option<EditMode>,
|
||||
retry_config: Option<RetryConfig>,
|
||||
}
|
||||
|
||||
// Cache structure for completion data
|
||||
@@ -127,6 +129,7 @@ impl Session {
|
||||
scheduled_job_id: Option<String>,
|
||||
max_turns: Option<u32>,
|
||||
edit_mode: Option<EditMode>,
|
||||
retry_config: Option<RetryConfig>,
|
||||
) -> Self {
|
||||
let messages = if let Some(session_file) = &session_file {
|
||||
match session::read_messages(session_file) {
|
||||
@@ -151,6 +154,7 @@ impl Session {
|
||||
scheduled_job_id,
|
||||
max_turns,
|
||||
edit_mode,
|
||||
retry_config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,6 +883,7 @@ impl Session {
|
||||
schedule_id: self.scheduled_job_id.clone(),
|
||||
execution_mode: None,
|
||||
max_turns: self.max_turns,
|
||||
retry_config: self.retry_config.clone(),
|
||||
}
|
||||
});
|
||||
let mut stream = self
|
||||
|
||||
Reference in New Issue
Block a user