Agentes de flujo de trabajo
Los agentes de flujo de trabajo orquestan múltiples agentes en patrones predecibles—tuberías secuenciales, ejecución paralela o bucles iterativos. A diferencia de LlmAgent, que utiliza razonamiento de IA, los agentes de flujo de trabajo siguen rutas de ejecución deterministas.
Inicio Rápido
Crea un nuevo proyecto:
cargo new workflow_demo
cd workflow_demo
Agrega dependencias a Cargo.toml:
[dependencies]
adk-rust = "0.2.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
Crea .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
SequentialAgent
SequentialAgent ejecuta sub-agentes uno tras otro. Cada agente ve el historial de conversación acumulado de los agentes anteriores.
Cuándo Usar
- Flujos de trabajo de múltiples pasos donde la salida alimenta el siguiente paso
- Flujos de trabajo de Investigación → Análisis → Resumen
- Cadenas de transformación de datos
Ejemplo Completo
Reemplace src/main.rs:
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Step 1: Research agent gathers information
let researcher = LlmAgentBuilder::new("researcher")
.instruction("Research the given topic. List 3-5 key facts or points. \
Be factual and concise.")
.model(model.clone())
.output_key("research") // Saves output to state
.build()?;
// Step 2: Analyzer agent identifies patterns
let analyzer = LlmAgentBuilder::new("analyzer")
.instruction("Based on the research above, identify 2-3 key insights \
or patterns. What's the bigger picture?")
.model(model.clone())
.output_key("analysis")
.build()?;
// Step 3: Summarizer creates final output
let summarizer = LlmAgentBuilder::new("summarizer")
.instruction("Create a brief executive summary combining the research \
and analysis. Keep it under 100 words.")
.model(model.clone())
.build()?;
// Create the sequential pipeline
let pipeline = SequentialAgent::new(
"research_pipeline",
vec![Arc::new(researcher), Arc::new(analyzer), Arc::new(summarizer)],
).with_description("Research → Analyze → Summarize");
println!("📋 Sequential Pipeline: Research → Analyze → Summarize");
println!();
Launcher::new(Arc::new(pipeline)).run().await?;
Ok(())
}
Ejecútelo:
cargo run
Ejemplo de Interacción
You: Tell me about Rust programming language
🔄 [researcher] Investigando...
Aquí están los hechos clave sobre Rust:
1. Lenguaje de programación de sistemas creado en Mozilla en 2010
2. Seguridad de memoria sin recolección de basura a través del sistema de propiedad
3. Abstracciones de costo cero y tiempo de ejecución mínimo
4. Votado "lenguaje más querido" en Stack Overflow durante 7 años
5. Usado por Firefox, Discord, Dropbox y el kernel de Linux
🔄 [analyzer] Analizando...
Ideas clave:
1. Rust resuelve el equilibrio entre seguridad de memoria y rendimiento
2. La fuerte satisfacción del desarrollador impulsa una rápida adopción
3. La confianza de las principales empresas tecnológicas valida la preparación para la producción
🔄 [summarizer] Resumiendo...
Rust es un lenguaje de sistemas que logra la seguridad de memoria sin recolección
de basura a través de su modelo de propiedad. Creado en Mozilla en 2010, ha sido
calificado como el lenguaje más querido durante 7 años consecutivos. Grandes empresas
como Discord y el kernel de Linux lo adoptan por sus abstracciones de costo cero
y garantías de rendimiento.
Cómo Funciona
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Researcher │ → │ Analyzer │ → │ Summarizer │
│ (step 1) │ │ (step 2) │ │ (step 3) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"Key facts..." "Insights..." "Executive summary"
- El mensaje del usuario va al primer agent (Researcher)
- La respuesta del Researcher se añade al historial
- El Analyzer ve: mensaje del usuario + respuesta del Researcher
- El Summarizer ve: mensaje del usuario + respuestas del Researcher + Analyzer
- El pipeline se completa cuando el último agent termina
ParallelAgent
ParallelAgent ejecuta todos los sub-agents concurrentemente. Cada agent recibe la misma entrada y trabaja de forma independiente.
Cuándo Usar
- Múltiples perspectivas sobre el mismo tema
- Procesamiento de distribución (misma entrada, diferentes análisis)
- Escenarios multitarea críticos en velocidad
Ejemplo Completo
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Three analysts with DISTINCT personas (important for parallel execution)
let technical = LlmAgentBuilder::new("technical_analyst")
.instruction("You are a senior software architect. \
FOCUS ONLY ON: code quality, system architecture, scalability, \
security vulnerabilities, and tech stack choices. \
Start your response with '🔧 TECHNICAL:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let business = LlmAgentBuilder::new("business_analyst")
.instruction("You are a business strategist and MBA graduate. \
FOCUS ONLY ON: market opportunity, revenue model, competition, \
cost structure, and go-to-market strategy. \
Start your response with '💼 BUSINESS:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let user_exp = LlmAgentBuilder::new("ux_analyst")
.instruction("You are a UX researcher and designer. \
FOCUS ONLY ON: user journey, accessibility, pain points, \
visual design, and user satisfaction metrics. \
Start your response with '🎨 UX:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
// Create parallel agent
let multi_analyst = ParallelAgent::new(
"multi_perspective",
vec![Arc::new(technical), Arc::new(business), Arc::new(user_exp)],
).with_description("Technical + Business + UX analysis in parallel");
println!("⚡ Parallel Analysis: Technical | Business | UX");
println!(" (All three run simultaneously!)");
println!();
Launcher::new(Arc::new(multi_analyst)).run().await?;
Ok(())
}
💡 Consejo: Haga que las instrucciones de los parallel agent sean muy distintas con personas únicas, áreas de enfoque y prefijos de respuesta. Esto asegura que cada agent produzca una salida única.
Ejemplo de Interacción
You: Evaluate a mobile banking app
🔧 TECHNICAL:
• Requires robust API security: OAuth 2.0, certificate pinning, encrypted storage
• Offline mode with sync requires complex state management and conflict resolution
• Biometric auth integration varies significantly across iOS/Android platforms
💼 BUSINESS:
• Highly competitive market - need unique differentiator (neobanks, traditional banks)
• Revenue model: interchange fees, premium tiers, or lending products cross-sell
• Regulatory compliance costs significant: PCI-DSS, regional banking laws, KYC/AML
🎨 UX:
• Critical: fast task completion - check balance must be < 3 seconds
• Accessibility essential: screen reader support, high contrast mode, large touch targets
• Trust indicators important: security badges, familiar banking patterns
Cómo Funciona
┌─────────────────┐
│ Mensaje del Usuario │
└────────┬────────┘
┌───────────────────┼───────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Analista │ │ Analista │ │ Analista │
│ Técnico │ │ de Negocios │ │ UX │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
↓ ↓ ↓
(respuesta 1) (respuesta 2) (respuesta 3)
Todos los agents comienzan simultáneamente y los resultados se transmiten a medida que se completan.
LoopAgent
LoopAgent ejecuta sub-agentes repetidamente hasta que se cumple una condición de salida o se alcanza el número máximo de iteraciones.
Cuándo usar
- Refinamiento iterativo (borrador → crítica → mejora → repetir)
- Lógica de reintento con mejora
- Controles de calidad que requieren múltiples pasadas
ExitLoopTool
Para salir de un bucle antes de tiempo, asigne al agente la ExitLoopTool. Cuando se llama, indica al bucle que se detenga.
Ejemplo Completo
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Critic agent evaluates content
let critic = LlmAgentBuilder::new("critic")
.instruction("Review the content for quality. Score it 1-10 and list \
specific improvements needed. Be constructive but critical.")
.model(model.clone())
.build()?;
// Refiner agent improves based on critique
let refiner = LlmAgentBuilder::new("refiner")
.instruction("Apply the critique to improve the content. \
If the score is 8 or higher, call exit_loop to finish. \
Otherwise, provide an improved version.")
.model(model.clone())
.tool(Arc::new(ExitLoopTool::new())) // Can exit the loop
.build()?;
// Create inner sequential: critic → refiner
let critique_refine = SequentialAgent::new(
"critique_refine_step",
vec![Arc::new(critic), Arc::new(refiner)],
);
// Wrap in loop with max 3 iterations
let iterative_improver = LoopAgent::new(
"iterative_improver",
vec![Arc::new(critique_refine)],
).with_max_iterations(3)
.with_description("Critique-refine loop (max 3 passes)");
println!("🔄 Bucle de Mejora Iterativa");
println!(" critic → refiner → repetir (máx 3x o hasta que la calidad >= 8)");
println!();
Launcher::new(Arc::new(iterative_improver)).run().await?;
Ok(())
}
Ejemplo de Interacción
You: Write a tagline for a coffee shop
🔄 Iteración 1
[critic] Puntuación: 5/10. "Good coffee here" es demasiado genérico. Necesita:
- Propuesta de valor única
- Conexión emocional
- Fraseología memorable
[refiner] Mejorado: "Where every cup tells a story"
🔄 Iteración 2
[critic] Puntuación: 7/10. ¡Mejor! Pero podría ser más fuerte:
- Más orientado a la acción
- Insinuar la experiencia
[refiner] Mejorado: "Brew your perfect moment"
🔄 Iteración 3
[critic] Puntuación: 8/10. Fuerte, orientado a la acción, experiencial.
Menor: podría ser más distintivo.
[refiner] La puntuación es 8+, ¡umbral de calidad alcanzado!
[exit_loop called]
Final: "Brew your perfect moment"
Cómo funciona
┌──────────────────────────────────────────┐
│ LoopAgent │
│ ┌────────────────────────────────────┐ │
│ │ SequentialAgent │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
→ │ │ │ Critic │ → │ Refiner │ │ │ →
│ │ │ (revisar)│ │ (mejorar o │ │ │
│ │ └──────────┘ │ exit_loop) │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑_____________↓ │
│ repetir hasta la salida │
└──────────────────────────────────────────┘
ConditionalAgent (Basado en Reglas)
ConditionalAgent ramifica la ejecución basándose en una condición síncrona y basada en reglas. Úsalo para enrutamiento determinista como pruebas A/B o enrutamiento basado en el entorno.
ConditionalAgent::new("router", |ctx| ctx.session().state().get("premium")..., premium_agent)
.with_else(basic_agent)
Nota: Para enrutamiento inteligente basado en LLM, usa
LlmConditionalAgenten su lugar.
LlmConditionalAgent (Basado en LLM)
LlmConditionalAgent utiliza un LLM para clasificar la entrada del usuario y enrutar al sub-agente apropiado. Esto es ideal para el enrutamiento inteligente donde la decisión de enrutamiento requiere comprender el contenido.
Cuándo Usar
- Clasificación de intención - Enrutar en función de lo que el usuario pregunta
- Enrutamiento múltiple - Más de 2 destinos
- Enrutamiento consciente del contexto - Necesita comprensión, no solo palabras clave
Ejemplo Completo
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Create specialist agents
let tech_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("tech_expert")
.instruction("You are a senior software engineer. Be precise and technical.")
.model(model.clone())
.build()?
);
let general_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("general_helper")
.instruction("You are a friendly assistant. Explain simply, use analogies.")
.model(model.clone())
.build()?
);
let creative_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("creative_writer")
.instruction("You are a creative writer. Be imaginative and expressive.")
.model(model.clone())
.build()?
);
// LLM classifies the query and routes accordingly
let router = LlmConditionalAgent::new("smart_router", model.clone())
.instruction("Classify the user's question as exactly ONE of: \
'technical' (coding, debugging, architecture), \
'general' (facts, knowledge, how-to), \
'creative' (writing, stories, brainstorming). \
Respond with ONLY the category name.")
.route("technical", tech_agent)
.route("general", general_agent.clone())
.route("creative", creative_agent)
.default_route(general_agent)
.build()?;
println!("🧠 LLM-Powered Intelligent Router");
Launcher::new(Arc::new(router)).run().await?;
Ok(())
}
Ejemplo de Interacción
You: How do I fix a borrow error in Rust?
[Routing to: technical]
[Agent: tech_expert]
A borrow error occurs when Rust's ownership rules are violated...
You: What's the capital of France?
[Routing to: general]
[Agent: general_helper]
The capital of France is Paris! It's a beautiful city...
You: Write me a haiku about the moon
[Routing to: creative]
[Agent: creative_writer]
Silver orb above,
Shadows dance on silent waves—
Night whispers secrets.
Cómo Funciona
┌─────────────────┐
│ Mensaje del usuario │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM Clasifica│ "technical" / "general" / "creative"
│ (smart_router)│
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech │ │general│ │creative │
│experto│ │ayudante│ │ escritor │
└───────┘ └───────┘ └─────────┘
Combinación de Agentes de Flujo de Trabajo
Los agentes de flujo de trabajo pueden anidarse para patrones complejos.
Secuencial + Paralelo + Bucle
use adk_rust::prelude::*;
use std::sync::Arc;
// 1. Análisis paralelo desde múltiples perspectivas
let parallel_analysis = ParallelAgent::new(
"multi_analysis",
vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);
// 2. Sintetizar los resultados paralelos
let synthesizer = LlmAgentBuilder::new("synthesizer")
.instruction("Combine all analyses into a unified recommendation.")
.model(model.clone())
.build()?;
// 3. Bucle de calidad: crítica y refinamiento
let quality_loop = LoopAgent::new(
"quality_check",
vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);
// Pipeline final: paralelo → sintetizar → bucle de calidad
let full_pipeline = SequentialAgent::new(
"full_analysis_pipeline",
vec![
Arc::new(parallel_analysis),
Arc::new(synthesizer),
Arc::new(quality_loop),
],
);
Rastreo de la Ejecución del Flujo de Trabajo
Para ver lo que está sucediendo dentro de un flujo de trabajo, habilite el rastreo:
use adk_rust::prelude::*;
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;
// Crear pipeline como antes...
// Usar Runner en lugar de Launcher para un control detallado
let session_service = Arc::new(InMemorySessionService::new());
let runner = Runner::new(RunnerConfig {
app_name: "workflow_trace".to_string(),
agent: Arc::new(pipeline),
session_service: session_service.clone(),
artifact_service: None,
memory_service: None,
run_config: None,
})?;
let session = session_service.create(CreateRequest {
app_name: "workflow_trace".to_string(),
user_id: "user".to_string(),
session_id: None,
state: Default::default(),
}).await?;
let mut stream = runner.run(
"user".to_string(),
session.id().to_string(),
Content::new("user").with_text("Analyze Rust"),
).await?;
// Procesar cada evento para ver la ejecución del flujo de trabajo
while let Some(event) = stream.next().await {
let event = event?;
// Mostrar qué agente está respondiendo
println!("📍 Agent: {}", event.author);
// Mostrar el contenido de la respuesta
if let Some(content) = event.content() {
for part in &content.parts {
if let Part::Text { text } = part {
println!(" {}", text);
}
}
}
println!();
}
Referencia de la API
SequentialAgent
SequentialAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback) // Llamado antes de la ejecución
.after_callback(callback) // Llamado después de la ejecución
ParallelAgent
ParallelAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
LoopAgent
LoopAgent::new("name", vec![agent1, agent2])
.with_max_iterations(5) // Límite de seguridad (recomendado)
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
ConditionalAgent
ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
.with_else(else_agent) // Rama else opcional
.with_description("Optional description")
ExitLoopTool
// Añadir a un agente para que pueda salir de un LoopAgent
.tool(Arc::new(ExitLoopTool::new()))
Anterior: LlmAgent | Siguiente: Sistemas Multi-Agente →