mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 14:44:21 +01:00
feat: Structured output for recipes (#3188)
This commit is contained in:
@@ -687,6 +687,7 @@ pub async fn cli() -> Result<()> {
|
||||
interactive: true,
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
})
|
||||
.await;
|
||||
setup_logging(
|
||||
@@ -736,7 +737,7 @@ pub async fn cli() -> Result<()> {
|
||||
quiet,
|
||||
additional_sub_recipes,
|
||||
}) => {
|
||||
let (input_config, session_settings, sub_recipes) = match (
|
||||
let (input_config, session_settings, sub_recipes, final_output_response) = match (
|
||||
instructions,
|
||||
input_text,
|
||||
recipe,
|
||||
@@ -755,6 +756,7 @@ pub async fn cli() -> Result<()> {
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
(Some(file), _, _) => {
|
||||
@@ -773,6 +775,7 @@ pub async fn cli() -> Result<()> {
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
(_, Some(text), _) => (
|
||||
@@ -783,6 +786,7 @@ pub async fn cli() -> Result<()> {
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(_, _, Some(recipe_name)) => {
|
||||
if explain {
|
||||
@@ -822,6 +826,7 @@ pub async fn cli() -> Result<()> {
|
||||
interactive, // Use the interactive flag from the Run command
|
||||
quiet,
|
||||
sub_recipes,
|
||||
final_output_response,
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -941,6 +946,7 @@ pub async fn cli() -> Result<()> {
|
||||
interactive: true, // Default case is always interactive
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
})
|
||||
.await;
|
||||
setup_logging(
|
||||
|
||||
@@ -47,6 +47,7 @@ pub async fn agent_generator(
|
||||
scheduled_job_id: None,
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
})
|
||||
.await;
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use goose::recipe::SubRecipe;
|
||||
use goose::recipe::{Response, SubRecipe};
|
||||
|
||||
use crate::{cli::InputConfig, recipes::recipe::load_recipe_as_template, 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>>)> {
|
||||
) -> Result<(
|
||||
InputConfig,
|
||||
Option<SessionSettings>,
|
||||
Option<Vec<SubRecipe>>,
|
||||
Option<Response>,
|
||||
)> {
|
||||
let recipe = load_recipe_as_template(&recipe_name, params).unwrap_or_else(|err| {
|
||||
eprintln!("{}: {}", console::style("Error").red().bold(), err);
|
||||
std::process::exit(1);
|
||||
@@ -43,6 +49,7 @@ pub fn extract_recipe_info_from_cli(
|
||||
temperature: s.temperature,
|
||||
}),
|
||||
Some(all_sub_recipes),
|
||||
recipe.response,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -69,7 +76,7 @@ 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) =
|
||||
let (input_config, settings, sub_recipes, response) =
|
||||
extract_recipe_info_from_cli(recipe_name, params, Vec::new()).unwrap();
|
||||
|
||||
assert_eq!(input_config.contents, Some("test_prompt".to_string()));
|
||||
@@ -91,6 +98,17 @@ mod tests {
|
||||
assert_eq!(sub_recipes[0].path, "existing_sub_recipe.yaml".to_string());
|
||||
assert_eq!(sub_recipes[0].name, "existing_sub_recipe".to_string());
|
||||
assert!(sub_recipes[0].values.is_none());
|
||||
assert!(response.is_some());
|
||||
let response = response.unwrap();
|
||||
assert_eq!(
|
||||
response.json_schema,
|
||||
Some(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "string"}
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -103,7 +121,7 @@ mod tests {
|
||||
"another/sub_recipe2.yaml".to_string(),
|
||||
];
|
||||
|
||||
let (input_config, settings, sub_recipes) =
|
||||
let (input_config, settings, sub_recipes, response) =
|
||||
extract_recipe_info_from_cli(recipe_name, params, additional_sub_recipes).unwrap();
|
||||
|
||||
assert_eq!(input_config.contents, Some("test_prompt".to_string()));
|
||||
@@ -131,6 +149,17 @@ mod tests {
|
||||
assert_eq!(sub_recipes[2].path, "another/sub_recipe2.yaml".to_string());
|
||||
assert_eq!(sub_recipes[2].name, "sub_recipe2".to_string());
|
||||
assert!(sub_recipes[2].values.is_none());
|
||||
assert!(response.is_some());
|
||||
let response = response.unwrap();
|
||||
assert_eq!(
|
||||
response.json_schema,
|
||||
Some(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "string"}
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
fn create_recipe() -> (TempDir, PathBuf) {
|
||||
@@ -151,6 +180,12 @@ settings:
|
||||
sub_recipes:
|
||||
- path: existing_sub_recipe.yaml
|
||||
name: existing_sub_recipe
|
||||
response:
|
||||
json_schema:
|
||||
type: object
|
||||
properties:
|
||||
result:
|
||||
type: string
|
||||
"#;
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let recipe_path: std::path::PathBuf = temp_dir.path().join("test_recipe.yaml");
|
||||
|
||||
@@ -3,7 +3,7 @@ use goose::agents::extension::ExtensionError;
|
||||
use goose::agents::Agent;
|
||||
use goose::config::{Config, ExtensionConfig, ExtensionConfigManager};
|
||||
use goose::providers::create;
|
||||
use goose::recipe::SubRecipe;
|
||||
use goose::recipe::{Response, SubRecipe};
|
||||
use goose::session;
|
||||
use goose::session::Identifier;
|
||||
use mcp_client::transport::Error as McpClientError;
|
||||
@@ -49,6 +49,8 @@ pub struct SessionBuilderConfig {
|
||||
pub quiet: bool,
|
||||
/// Sub-recipes to add to the session
|
||||
pub sub_recipes: Option<Vec<SubRecipe>>,
|
||||
/// Final output expected response
|
||||
pub final_output_response: Option<Response>,
|
||||
}
|
||||
|
||||
/// Offers to help debug an extension failure by creating a minimal debugging session
|
||||
@@ -180,6 +182,11 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
|
||||
if let Some(sub_recipes) = session_config.sub_recipes {
|
||||
agent.add_sub_recipes(sub_recipes).await;
|
||||
}
|
||||
|
||||
if let Some(final_output_response) = session_config.final_output_response {
|
||||
agent.add_final_output_tool(final_output_response).await;
|
||||
}
|
||||
|
||||
let new_provider = match create(&provider_name, model_config) {
|
||||
Ok(provider) => provider,
|
||||
Err(e) => {
|
||||
@@ -520,6 +527,7 @@ mod tests {
|
||||
interactive: true,
|
||||
quiet: false,
|
||||
sub_recipes: None,
|
||||
final_output_response: None,
|
||||
};
|
||||
|
||||
assert_eq!(config.extensions.len(), 1);
|
||||
@@ -549,6 +557,7 @@ mod tests {
|
||||
assert!(config.scheduled_job_id.is_none());
|
||||
assert!(!config.interactive);
|
||||
assert!(!config.quiet);
|
||||
assert!(config.final_output_response.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user