Unify subrecipe and subagent execution through shared recipe pipeline (#5082)

This commit is contained in:
tlongwell-block
2025-11-06 19:02:03 -05:00
committed by GitHub
parent 3bf769edfe
commit 20ba2fdb7e
22 changed files with 261 additions and 616 deletions
+47 -21
View File
@@ -3,6 +3,7 @@ use crate::{
agents::{subagent_task_config::TaskConfig, AgentEvent, SessionConfig},
conversation::{message::Message, Conversation},
execution::manager::AgentManager,
recipe::Recipe,
session::SessionManager,
};
use anyhow::{anyhow, Result};
@@ -10,27 +11,30 @@ use futures::StreamExt;
use rmcp::model::{ErrorCode, ErrorData};
use std::future::Future;
use std::pin::Pin;
use tracing::debug;
use tracing::{debug, info};
type AgentMessagesFuture =
Pin<Box<dyn Future<Output = Result<(Conversation, Option<String>)>> + Send>>;
/// Standalone function to run a complete subagent task with output options
pub async fn run_complete_subagent_task(
text_instruction: String,
recipe: Recipe,
task_config: TaskConfig,
return_last_only: bool,
) -> Result<String, anyhow::Error> {
let messages = get_agent_messages(text_instruction, task_config)
.await
.map_err(|e| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to execute task: {}", e),
None,
)
})?;
let (messages, final_output) = get_agent_messages(recipe, task_config).await.map_err(|e| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to execute task: {}", e),
None,
)
})?;
if let Some(output) = final_output {
return Ok(output);
}
// Extract text content based on return_last_only flag
let response_text = if return_last_only {
// Get only the last message's text content
messages
.messages()
.last()
@@ -44,7 +48,6 @@ pub async fn run_complete_subagent_task(
})
.unwrap_or_else(|| String::from("No text content in last message"))
} else {
// Extract all text content from all messages (original behavior)
let all_text_content: Vec<String> = messages
.iter()
.flat_map(|message| {
@@ -88,15 +91,17 @@ pub async fn run_complete_subagent_task(
all_text_content.join("\n")
};
// Return the result
Ok(response_text)
}
fn get_agent_messages(
text_instruction: String,
task_config: TaskConfig,
) -> Pin<Box<dyn Future<Output = Result<Conversation>> + Send>> {
fn get_agent_messages(recipe: Recipe, task_config: TaskConfig) -> AgentMessagesFuture {
Box::pin(async move {
let text_instruction = recipe
.instructions
.clone()
.or(recipe.prompt.clone())
.ok_or_else(|| anyhow!("Recipe has no instructions or prompt"))?;
let agent_manager = AgentManager::instance()
.await
.map_err(|e| anyhow!("Failed to create AgentManager: {}", e))?;
@@ -130,14 +135,24 @@ fn get_agent_messages(
}
}
let has_response_schema = recipe.response.is_some();
agent
.apply_recipe_components(recipe.sub_recipes.clone(), recipe.response.clone(), true)
.await;
let user_message = Message::user().with_text(text_instruction);
let mut conversation = Conversation::new_unvalidated(vec![user_message.clone()]);
if let Some(activities) = recipe.activities {
for activity in activities {
info!("Recipe activity: {}", activity);
}
}
let session_config = SessionConfig {
id: session.id.clone(),
schedule_id: None,
max_turns: task_config.max_turns.map(|v| v as u32),
retry_config: None,
retry_config: recipe.retry,
};
let mut stream = crate::session_context::with_session_id(Some(session.id.clone()), async {
@@ -159,6 +174,17 @@ fn get_agent_messages(
}
}
Ok(conversation)
let final_output = if has_response_schema {
agent
.final_output_tool
.lock()
.await
.as_ref()
.and_then(|tool| tool.final_output.clone())
} else {
None
};
Ok((conversation, final_output))
})
}