Agentes de Fluxo de Trabalho

Agentes de fluxo de trabalho orquestram múltiplos agentes em padrões previsíveis—pipelines sequenciais, execução paralela ou loops iterativos. Diferente do LlmAgent, que usa raciocínio de IA, agentes de fluxo de trabalho seguem caminhos de execução determinísticos.

Início Rápido

Crie um novo projeto:

cargo new workflow_demo
cd workflow_demo

Adicione dependências a Cargo.toml:

[dependencies]
adk-rust = "0.2.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"

Crie .env:

echo 'GOOGLE_API_KEY=your-api-key' > .env

SequentialAgent

SequentialAgent executa sub-agentes um após o outro. Cada agente vê o histórico de conversa acumulado dos agentes anteriores.

Quando Usar

  • Pipelines multi-etapas onde a saída alimenta o próximo passo
  • Fluxos de trabalho de Pesquisa → Análise → Resumo
  • Cadeias de transformação de dados

Exemplo Completo

Substitua 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(())
}

Execute:

cargo run

Exemplo de Interação

You: Tell me about Rust programming language

🔄 [researcher] Researching...
Here are key facts about Rust:
1. Systems programming language created at Mozilla in 2010
2. Memory safety without garbage collection via ownership system
3. Zero-cost abstractions and minimal runtime
4. Voted "most loved language" on Stack Overflow for 7 years
5. Used by Firefox, Discord, Dropbox, and Linux kernel

🔄 [analyzer] Analyzing...
Key insights:
1. Rust solves the memory safety vs performance tradeoff
2. Strong developer satisfaction drives rapid adoption
3. Trust from major tech companies validates production-readiness

🔄 [summarizer] Summarizing...
Rust is a systems language that achieves memory safety without garbage 
collection through its ownership model. Created at Mozilla in 2010, it's 
been rated the most loved language for 7 consecutive years. Major companies 
like Discord and Linux kernel adopt it for its zero-cost abstractions 
and performance guarantees.

Como Funciona

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  Researcher │ →  │   Analyzer  │ →  │  Summarizer │
│   (step 1)  │    │   (step 2)  │    │   (step 3)  │
└─────────────┘    └─────────────┘    └─────────────┘
       ↓                  ↓                  ↓
 "Key facts..."    "Insights..."    "Executive summary"
  1. A mensagem do usuário vai para o primeiro agente (Researcher)
  2. A resposta do Researcher é adicionada ao histórico
  3. O Analyzer vê: mensagem do usuário + resposta do researcher
  4. O Summarizer vê: mensagem do usuário + respostas do researcher + do analyzer
  5. O pipeline é concluído quando o último agente termina

ParallelAgent

ParallelAgent executa todos os sub-agents concorrentemente. Cada agent recebe a mesma entrada e trabalha de forma independente.

Quando Usar

  • Múltiplas perspectivas sobre o mesmo tópico
  • Processamento em fan-out (mesma entrada, análises diferentes)
  • Cenários de multitarefa críticos para a velocidade

Exemplo 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(())
}

💡 Dica: Torne as instruções do ParallelAgent altamente distintas com personas únicas, áreas de foco e prefixos de resposta. Isso garante que cada Agent produza uma saída única.

Exemplo de Interação

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

Como Funciona

                    ┌─────────────────┐
                    │  User Message   │
                    └────────┬────────┘
         ┌───────────────────┼───────────────────┐
         ↓                   ↓                   ↓
  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
  │  Technical  │    │  Business   │    │     UX      │
  │   Analyst   │    │   Analyst   │    │   Analyst   │
  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘
         ↓                   ↓                   ↓
    (response 1)       (response 2)       (response 3)

Todos os agents iniciam simultaneamente e os resultados são transmitidos conforme são concluídos.


LoopAgent

LoopAgent executa sub-agentes repetidamente até que uma condição de saída seja atendida ou o número máximo de iterações seja atingido.

Quando Usar

  • Refinamento iterativo (rascunho → crítica → melhoria → repetir)
  • Lógica de repetição com melhoria
  • Portões de qualidade que exigem múltiplas passagens

ExitLoopTool

Para sair de um loop prematuramente, atribua a um agente o ExitLoopTool. Quando acionada, ela sinaliza para o loop parar.

Exemplo 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!("🔄 Iterative Improvement Loop");
    println!("   critic → refiner → repeat (max 3x or until quality >= 8)");
    println!();

    Launcher::new(Arc::new(iterative_improver)).run().await?;
    Ok(())
}

Exemplo de Interação

Você: Escreva um slogan para uma cafeteria

🔄 Iteração 1
[critic] Pontuação: 5/10. "Bom café aqui" é muito genérico. Necessita de:
- Proposta de valor única
- Conexão emocional
- Fraseado memorável

[refiner] Melhorado: "Onde cada xícara conta uma história"

🔄 Iteração 2
[critic] Pontuação: 7/10. Melhor! Mas poderia ser mais forte:
- Mais orientada à ação
- Sugere a experiência

[refiner] Melhorado: "Crie seu momento perfeito"

🔄 Iteração 3
[critic] Pontuação: 8/10. Forte, orientada à ação, experiencial.
Menor: poderia ser mais distintiva.

[refiner] A pontuação é 8+, limite de qualidade atingido!
[exit_loop chamado]

Final: "Crie seu momento perfeito"

Como Funciona

     ┌──────────────────────────────────────────┐
     │              LoopAgent                    │
     │  ┌────────────────────────────────────┐  │
     │  │        SequentialAgent              │  │
     │  │  ┌──────────┐    ┌──────────────┐  │  │
  →  │  │  │  Crítico │ →  │  Refinador   │  │  │  →
     │  │  │ (revisar)│    │ (melhorar ou │  │  │
     │  │  └──────────┘    │  exit_loop)  │  │  │
     │  │                  └──────────────┘  │  │
     │  └────────────────────────────────────┘  │
     │         ↑_____________↓                  │
     │         repetir até a saída              │
     └──────────────────────────────────────────┘

ConditionalAgent (Baseado em Regras)

ConditionalAgent ramifica a execução com base em uma condição síncrona, baseada em regras. Use isso para roteamento determinístico, como testes A/B ou roteamento baseado em ambiente.

ConditionalAgent::new("router", |ctx| ctx.session().state().get("premium")..., premium_agent)
    .with_else(basic_agent)

Nota: Para roteamento inteligente baseado em LLM, use LlmConditionalAgent em vez disso.


LlmConditionalAgent (Baseado em LLM)

LlmConditionalAgent usa um LLM para classificar a entrada do usuário e rotear para o sub-agente apropriado. Isso é ideal para roteamento inteligente onde a decisão de roteamento requer a compreensão do conteúdo.

Quando Usar

  • Classificação de intenção - Roteia com base no que o usuário está perguntando
  • Roteamento multi-direcional - Mais de 2 destinos
  • Roteamento sensível ao contexto - Requer compreensão, não apenas palavras-chave

Exemplo 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(())
}

Exemplo de Interação

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.

Como Funciona

┌─────────────────┐
│  User Message   │
└────────┬────────┘
         ↓
┌─────────────────┐
│   LLM Classifies│  "technical" / "general" / "creative"
│   (smart_router)│
└────────┬────────┘
         ↓
    ┌────┴────┬──────────┐
    ↓         ↓          ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech  │ │general│ │creative │
│expert │ │helper │ │ writer  │
└───────┘ └───────┘ └─────────┘

Combinando Agentes de Fluxo de Trabalho

Agentes de fluxo de trabalho podem ser aninhados para padrões complexos.

Sequencial + Paralelo + Loop

use adk_rust::prelude::*;
use std::sync::Arc;

// 1. Análise paralela de múltiplas perspectivas
let parallel_analysis = ParallelAgent::new(
    "multi_analysis",
    vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);

// 2. Sintetizar os resultados paralelos
let synthesizer = LlmAgentBuilder::new("synthesizer")
    .instruction("Combine all analyses into a unified recommendation.")
    .model(model.clone())
    .build()?;

// 3. Loop de qualidade: criticar e refinar
let quality_loop = LoopAgent::new(
    "quality_check",
    vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);

// Pipeline final: paralelo → sintetizar → loop de qualidade
let full_pipeline = SequentialAgent::new(
    "full_analysis_pipeline",
    vec![
        Arc::new(parallel_analysis),
        Arc::new(synthesizer),
        Arc::new(quality_loop),
    ],
);

Rastreando a Execução do Fluxo de Trabalho

Para ver o que está acontecendo dentro de um fluxo de trabalho, habilite o rastreamento:

use adk_rust::prelude::*;
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;

// Crie o pipeline como antes...

// Use Runner em vez de Launcher para controle detalhado
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?;

// Processe cada evento para ver a execução do fluxo de trabalho
while let Some(event) = stream.next().await {
    let event = event?;
    
    // Mostra qual agente está respondendo
    println!("📍 Agent: {}", event.author);
    
    // Mostra o conteúdo da resposta
    if let Some(content) = event.content() {
        for part in &content.parts {
            if let Part::Text { text } = part {
                println!("   {}", text);
            }
        }
    }
    println!();
}

Referência da API

SequentialAgent

SequentialAgent::new("name", vec![agent1, agent2, agent3])
    .with_description("Optional description")
    .before_callback(callback)  // Chamado antes da execução
    .after_callback(callback)   // Chamado após a execução

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)     // Limite de segurança (recomendado)
    .with_description("Optional description")
    .before_callback(callback)
    .after_callback(callback)

ConditionalAgent

ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
    .with_else(else_agent)      // Ramificação 'else' opcional
    .with_description("Optional description")

ExitLoopTool

// Adicione a um agente para permitir que ele saia de um LoopAgent
.tool(Arc::new(ExitLoopTool::new()))

Anterior: LlmAgent | Próximo: Sistemas Multiagentes →