use std::sync::Arc;
pub type Workflow = crate::workflows::AgentFlow;
pub type Pipeline = crate::workflows::AgentFlow;
pub type Agents = crate::workflows::AgentTeam;
pub type AgentManager = crate::workflows::AgentTeam;
pub fn loop_step(
agent: Arc<crate::agent::Agent>,
items: Vec<String>,
) -> crate::workflows::FlowStep {
crate::workflows::FlowStep::Loop(crate::workflows::Loop { agent, items })
}
pub fn parallel(agents: Vec<Arc<crate::agent::Agent>>) -> crate::workflows::FlowStep {
crate::workflows::FlowStep::Parallel(crate::workflows::Parallel { agents })
}
pub fn repeat(agent: Arc<crate::agent::Agent>, times: usize) -> crate::workflows::FlowStep {
crate::workflows::FlowStep::Repeat(crate::workflows::Repeat { agent, times })
}
pub fn route<F>(
condition: F,
if_true: Arc<crate::agent::Agent>,
if_false: Option<Arc<crate::agent::Agent>>,
) -> crate::workflows::FlowStep
where
F: Fn(&str) -> bool + Send + Sync + 'static,
{
crate::workflows::FlowStep::Route(crate::workflows::Route {
condition: Box::new(condition),
if_true,
if_false,
})
}
pub fn when<F>(
condition: F,
if_true: Arc<crate::agent::Agent>,
if_false: Option<Arc<crate::agent::Agent>>,
) -> crate::workflows::FlowStep
where
F: Fn(&str) -> bool + Send + Sync + 'static,
{
route(condition, if_true, if_false)
}
#[derive(Debug, Clone, Default)]
pub struct HandoffConfig {
pub target: String,
pub message: Option<String>,
pub include_history: bool,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
}
impl HandoffConfig {
pub fn new(target: impl Into<String>) -> Self {
Self {
target: target.into(),
message: None,
include_history: true,
metadata: std::collections::HashMap::new(),
}
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn include_history(mut self, include: bool) -> Self {
self.include_history = include;
self
}
pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
}
pub fn handoff(target: impl Into<String>, message: Option<&str>) -> HandoffConfig {
let mut config = HandoffConfig::new(target);
if let Some(msg) = message {
config = config.message(msg);
}
config
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HandoffFilter {
AllowAll,
DenyAll,
AllowList,
DenyList,
}
#[derive(Debug, Clone, Default)]
pub struct HandoffFilters {
pub filter_type: Option<HandoffFilter>,
pub agents: Vec<String>,
}
impl HandoffFilters {
pub fn new() -> Self {
Self::default()
}
pub fn allow_all() -> Self {
Self {
filter_type: Some(HandoffFilter::AllowAll),
agents: Vec::new(),
}
}
pub fn deny_all() -> Self {
Self {
filter_type: Some(HandoffFilter::DenyAll),
agents: Vec::new(),
}
}
pub fn allow_only(agents: Vec<String>) -> Self {
Self {
filter_type: Some(HandoffFilter::AllowList),
agents,
}
}
pub fn deny(agents: Vec<String>) -> Self {
Self {
filter_type: Some(HandoffFilter::DenyList),
agents,
}
}
pub fn is_allowed(&self, target: &str) -> bool {
match self.filter_type {
Some(HandoffFilter::AllowAll) => true,
Some(HandoffFilter::DenyAll) => false,
Some(HandoffFilter::AllowList) => self.agents.iter().any(|a| a == target),
Some(HandoffFilter::DenyList) => !self.agents.iter().any(|a| a == target),
None => true, }
}
}
pub fn handoff_filters() -> HandoffFilters {
HandoffFilters::new()
}
pub fn prompt_with_handoff_instructions(base_prompt: &str, available_agents: &[&str]) -> String {
if available_agents.is_empty() {
return base_prompt.to_string();
}
let agents_list = available_agents.join(", ");
format!(
r#"{base_prompt}
## Handoff Instructions
You can hand off the conversation to specialized agents when appropriate.
Available agents: {agents_list}
To hand off, respond with:
HANDOFF: <agent_name>
REASON: <brief explanation>
CONTEXT: <relevant context for the receiving agent>
Only hand off when the query is better suited for another agent's expertise."#
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handoff_config() {
let config = HandoffConfig::new("target_agent")
.message("Please handle this")
.include_history(false)
.metadata("priority", serde_json::json!("high"));
assert_eq!(config.target, "target_agent");
assert_eq!(config.message, Some("Please handle this".to_string()));
assert!(!config.include_history);
assert!(config.metadata.contains_key("priority"));
}
#[test]
fn test_handoff_function() {
let config = handoff("agent1", Some("Test message"));
assert_eq!(config.target, "agent1");
assert_eq!(config.message, Some("Test message".to_string()));
}
#[test]
fn test_handoff_filters_allow_all() {
let filters = HandoffFilters::allow_all();
assert!(filters.is_allowed("any_agent"));
assert!(filters.is_allowed("another_agent"));
}
#[test]
fn test_handoff_filters_deny_all() {
let filters = HandoffFilters::deny_all();
assert!(!filters.is_allowed("any_agent"));
assert!(!filters.is_allowed("another_agent"));
}
#[test]
fn test_handoff_filters_allow_list() {
let filters = HandoffFilters::allow_only(vec!["agent1".to_string(), "agent2".to_string()]);
assert!(filters.is_allowed("agent1"));
assert!(filters.is_allowed("agent2"));
assert!(!filters.is_allowed("agent3"));
}
#[test]
fn test_handoff_filters_deny_list() {
let filters = HandoffFilters::deny(vec!["blocked".to_string()]);
assert!(filters.is_allowed("agent1"));
assert!(!filters.is_allowed("blocked"));
}
#[test]
fn test_prompt_with_handoff_instructions() {
let prompt = prompt_with_handoff_instructions(
"You are a helpful assistant.",
&["specialist", "researcher"],
);
assert!(prompt.contains("You are a helpful assistant."));
assert!(prompt.contains("specialist, researcher"));
assert!(prompt.contains("HANDOFF:"));
}
#[test]
fn test_prompt_with_handoff_no_agents() {
let prompt = prompt_with_handoff_instructions("Base prompt", &[]);
assert_eq!(prompt, "Base prompt");
}
}