feat: recipes can retry with success criteria (#3474)

This commit is contained in:
Prem Pillai
2025-07-22 10:49:21 +10:00
committed by GitHub
parent 5f3c7d339c
commit 99cc0a9c81
17 changed files with 1078 additions and 82 deletions
+41 -15
View File
@@ -4,6 +4,7 @@ use std::collections::HashMap;
use std::fmt;
use crate::agents::extension::ExtensionConfig;
use crate::agents::types::RetryConfig;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -37,7 +38,7 @@ fn default_version() -> String {
/// * `author` - Information about the Recipe's creator and metadata
/// * `parameters` - Additional parameters for the Recipe
/// * `response` - Response configuration including JSON schema validation
///
/// * `retry` - Retry configuration for automated validation and recovery
/// # Example
///
///
@@ -66,6 +67,7 @@ fn default_version() -> String {
/// parameters: None,
/// response: None,
/// sub_recipes: None,
/// retry: None,
/// };
///
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
@@ -109,6 +111,9 @@ pub struct Recipe {
#[serde(skip_serializing_if = "Option::is_none")]
pub sub_recipes: Option<Vec<SubRecipe>>, // sub-recipes for the recipe
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryConfig>,
}
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
@@ -239,6 +244,7 @@ pub struct RecipeBuilder {
parameters: Option<Vec<RecipeParameter>>,
response: Option<Response>,
sub_recipes: Option<Vec<SubRecipe>>,
retry: Option<RetryConfig>,
}
impl Recipe {
@@ -271,26 +277,39 @@ impl Recipe {
parameters: None,
response: None,
sub_recipes: None,
retry: None,
}
}
pub fn from_content(content: &str) -> Result<Self> {
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(content) {
if let Some(nested_recipe) = json_value.get("recipe") {
Ok(serde_json::from_value(nested_recipe.clone())?)
let recipe: Recipe =
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(content) {
if let Some(nested_recipe) = json_value.get("recipe") {
serde_json::from_value(nested_recipe.clone())?
} else {
serde_json::from_str(content)?
}
} else if let Ok(yaml_value) = serde_yaml::from_str::<serde_yaml::Value>(content) {
if let Some(nested_recipe) = yaml_value.get("recipe") {
serde_yaml::from_value(nested_recipe.clone())?
} else {
serde_yaml::from_str(content)?
}
} else {
Ok(serde_json::from_str(content)?)
return Err(anyhow::anyhow!(
"Unsupported format. Expected JSON or YAML."
));
};
if let Some(ref retry_config) = recipe.retry {
if let Err(validation_error) = retry_config.validate() {
return Err(anyhow::anyhow!(
"Invalid retry configuration: {}",
validation_error
));
}
} else if let Ok(yaml_value) = serde_yaml::from_str::<serde_yaml::Value>(content) {
if let Some(nested_recipe) = yaml_value.get("recipe") {
Ok(serde_yaml::from_value(nested_recipe.clone())?)
} else {
Ok(serde_yaml::from_str(content)?)
}
} else {
Err(anyhow::anyhow!(
"Unsupported format. Expected JSON or YAML."
))
}
Ok(recipe)
}
}
@@ -369,6 +388,12 @@ impl RecipeBuilder {
self
}
/// Sets the retry configuration for the Recipe
pub fn retry(mut self, retry: RetryConfig) -> Self {
self.retry = Some(retry);
self
}
/// Builds the Recipe instance
///
/// Returns an error if any required fields are missing
@@ -394,6 +419,7 @@ impl RecipeBuilder {
parameters: self.parameters,
response: self.response,
sub_recipes: self.sub_recipes,
retry: self.retry,
})
}
}