multiagentetutorialiarustv0.1.8

Construindo Sistemas Multiagente com ADK-Rust

Aprenda quando e como usar diferentes padrões multiagente—desde coordenação simples até orquestração complexa baseada em grafos—com a segurança de tipos e o desempenho de Rust.

·15 min de leitura·ADK-Rust v0.1.8

1. Introdução

O Problema: IA Que Atinge um Limite

Você construiu seu primeiro agente de IA. É impressionante — ele pode responder perguntas, resumir documentos, talvez até escrever código. Mas então a realidade bate:

"Você pode verificar minha última fatura e também me ajudar a configurar o API?"

Seu agente tem dificuldades. Ele não foi treinado em sistemas de faturamento E documentação de desenvolvedor E fluxos de trabalho de solução de problemas. Seu prompt de instrução já tem 2000 tokens tentando cobrir tudo. A qualidade da resposta degrada.

Este é o limite do agente único. À medida que sua aplicação cresce, você enfrenta compensações dolorosas:

  • Prompts inchados: Cada nova capacidade significa instruções mais longas, maior latência e mais confusão para o modelo
  • Pau para toda obra: Um agente que lida com faturamento, suporte E vendas torna-se medíocre em todos os três
  • Manutenção impossível: Alterar a lógica de faturamento não deveria arriscar quebrar seus fluxos de suporte
  • Sem especialização: Seu agente de matemática não pode ter uma ferramenta de calculadora enquanto seu agente de pesquisa tem busca na web — eles compartilham tudo

A Solução: Agentes Especializados Trabalhando Juntos

Sistemas multiagentes resolvem isso decompondo tarefas complexas em papéis especializados. Em vez de um generalista sobrecarregado, você cria especialistas focados:

  • Atendimento ao cliente: Um coordinator direciona os usuários para especialistas em faturamento, suporte técnico ou vendas — cada um com treinamento e ferramentas focados
  • Criação de conteúdo: Um agente de pesquisa coleta fatos, um writer elabora a narrativa, um editor a aprimora — cada agente domina uma habilidade
  • Geração de código: Um planejador projeta a arquitetura, um coder implementa, um revisor encontra bugs — diferentes perspectivas melhoram a qualidade

O resultado? Cada agente permanece focado, os prompts permanecem gerenciáveis, e você pode atualizar a lógica de faturamento sem tocar no suporte. São microsserviços para IA.

O Que Você Vai Aprender

ADK-Rust oferece três padrões progressivamente poderosos para orquestração multiagente. Este tutorial ensinará a você:

  • Quando usar cada padrão com base em seus requisitos
  • Como implementá-los com código Rust pronto para produção
  • Por que as compensações arquitetônicas são importantes para o seu caso de uso específico

2. Escolhendo o Padrão Certo

Antes de mergulhar no código, vamos entender o que cada padrão oferece:

PadrãoMelhor ParaNível de ControleComplexidade
CoordenadorTransferências de conversaLLM decideBaixa
AgentToolProcessamento de respostaCoordenador processaMédia
Grafo SupervisorFluxos de trabalho complexosGerenciamento completo de estadoAlta

3. Padrão 1: O Coordenador (Subagentes)

🎯 Caso de Uso: Roteamento de Atendimento ao Cliente

Você está construindo um bot de atendimento ao cliente. Os usuários podem perguntar sobre faturamento, solicitar ajuda técnica ou indagar sobre novos recursos. Cada domínio requer conhecimento especializado, mas os usuários não deveriam precisar saber qual departamento contatar.

O padrão Coordenador utiliza transferência automática de agente. Ao adicionar subagentes via .sub_agent(), ADK-Rust injeta uma ferramenta transfer_to_agent. O LLM decide quando fazer a transferência com base na conversa.

UsuárioCoordenadorEncaminha requisiçõespara especialistasFaturamentoSubagenteSuporteSubagenteVendasSubagentetransfer_to_agenttransfer_to_agenttransfer_to_agent

Características Principais

  • Transferência contínua: O usuário continua naturalmente com o especialista
  • Roteamento impulsionado por LLM: O coordinator decide com base no contexto da conversa
  • Continuidade da conversa: O histórico da sessão é mantido entre as transferências
  • Sem processamento de resposta: O especialista fala diretamente com o usuário após a transferência

Implementação

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

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Specialist: Billing Agent
    // Clear description helps the coordinator know when to transfer
    let billing_agent = LlmAgentBuilder::new("billing_agent")
        .description(
            "Handles all billing questions: invoices, payments, refunds,              subscription plans, and account charges. Transfer here for              any money-related questions."
        )
        .instruction(
            "You are a billing specialist. Answer questions about invoices,              payments, and subscription plans. Be concise and accurate.              If asked about technical issues, suggest transferring to support."
        )
        .model(model.clone())
        .build()?;

    // Specialist: Technical Support Agent
    let support_agent = LlmAgentBuilder::new("support_agent")
        .description(
            "Provides technical support: troubleshooting, bug reports,              feature questions, and integration help. Transfer here for              any technical problems."
        )
        .instruction(
            "You are a technical support specialist. Help users troubleshoot              issues step by step. Ask clarifying questions when needed.              Be patient and thorough."
        )
        .model(model.clone())
        .build()?;

    // Coordinator: Routes to specialists
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Customer service coordinator")
        .instruction(
            "You are a friendly customer service coordinator. Your job is to:\n             1. Greet users warmly\n             2. Understand their needs\n             3. Route to the right specialist:\n                - Billing questions → transfer to billing_agent\n                - Technical issues → transfer to support_agent\n             4. Handle general questions yourself\n\n             Always explain who you're connecting them with."
        )
        .model(model.clone())
        .sub_agent(Arc::new(billing_agent))
        .sub_agent(Arc::new(support_agent))
        .build()?;

    // Run with the built-in launcher
    Launcher::new(Arc::new(coordinator))
        .run()
        .await?;

    Ok(())
}

Exemplo de Conversa

Usuário: Olá, tenho uma pergunta sobre minha fatura

[coordinator]: Olá! Ficarei feliz em ajudar com sua pergunta sobre faturamento. Deixe-me conectá-lo com nosso especialista em faturamento que poderá ajudá-lo.

Sistema: 🔄 Transferir para: billing_agent

[billing_agent]: Olá! Sou o especialista em faturamento. Posso ajudar com faturas, pagamentos e perguntas sobre assinaturas. O que você gostaria de saber sobre sua fatura?

Usuário: Por que fui cobrado duas vezes este mês?

[billing_agent]: Vou verificar essa cobrança duplicada para você...

✅ Quando Usar o Coordenador

  • O usuário deve interagir diretamente com os especialistas
  • As decisões de roteamento são diretas
  • Você não precisa processar as respostas dos especialistas
  • O fluxo da conversa é linear (um especialista por vez)

4. Padrão 2: Agentes como Ferramentas (AgentTool)

🎯 Caso de Uso: Agregação de Conhecimento

Você está construindo um assistente inteligente que responde a perguntas que abrangem múltiplos domínios. Um usuário pergunta "Quanto é 15% de 250, e por que esse número é significativo na história?" Você precisa chamar um especialista em matemática, depois um especialista em curiosidades, e combinar suas respostas.

O padrão AgentTool encapsula agentes como ferramentas invocáveis. Diferente dos sub-agentes, o coordinator invoca especialistas programaticamente e recebe suas respostas para processar ou combinar antes de responder ao usuário.

UsuárioCoordenadorChama agentes como ferramentasProcessa respostasAgrega resultadosRetorna ao usuárioEspecialista em MatemáticaAgentTool+ calculadoraEspecialista em CuriosidadesAgentToolLLM conhecimentoPesquisadorAgentTool+ web_searchchamada →← resposta

Principais Diferenças em relação ao Coordenador

Coordenador (Sub-Agentes)

  • • O especialista fala diretamente com o usuário
  • • Um especialista por vez
  • • Sem processamento de resposta

AgentTool

  • • O Coordenador recebe as respostas
  • • Pode chamar múltiplos especialistas
  • • Agrega e resume

Implementação

use adk_agent::LlmAgentBuilder;
use adk_tool::{AgentTool, FunctionTool};
use adk_core::ToolContext;
use serde_json::{json, Value};
use std::sync::Arc;

// Calculator tool for the math agent
async fn calculator(
    _ctx: Arc<dyn ToolContext>,
    args: Value
) -> Result<Value, adk_core::AdkError> {
    let operation = args["operation"].as_str().unwrap_or("add");
    let a = args["a"].as_f64().unwrap_or(0.0);
    let b = args["b"].as_f64().unwrap_or(0.0);

    let result = match operation {
        "add" => a + b,
        "multiply" => a * b,
        "percent" => a * (b / 100.0),
        _ => return Err(adk_core::AdkError::Tool(
            format!("Unknown operation: {}", operation)
        )),
    };

    Ok(json!({ "result": result }))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);

    // Create calculator tool
    let calc_tool = FunctionTool::new(
        "calculator",
        "Performs arithmetic: add, multiply, percent.          Args: operation (string), a (number), b (number)",
        calculator,
    );

    // Math Expert agent - has its own tools
    let math_agent = LlmAgentBuilder::new("math_expert")
        .description(
            "A math expert that performs calculations. Use for any              math-related questions, percentages, or numerical analysis."
        )
        .instruction(
            "You are a math expert. Use the calculator tool for              calculations. Show your work step by step. Be precise."
        )
        .model(model.clone())
        .tool(Arc::new(calc_tool))
        .build()?;

    // Trivia Expert agent - uses LLM knowledge
    let trivia_agent = LlmAgentBuilder::new("trivia_expert")
        .description(
            "A trivia and history expert. Use for questions about              historical facts, pop culture, science facts, and trivia."
        )
        .instruction(
            "You are a trivia expert with vast knowledge across domains.              Answer questions accurately and include interesting related facts."
        )
        .model(model.clone())
        .build()?;

    // Wrap agents as tools with configuration
    let math_tool = AgentTool::new(Arc::new(math_agent))
        .skip_summarization(false)   // Summarize lengthy responses
        .forward_artifacts(true);     // Pass through any generated files

    let trivia_tool = AgentTool::new(Arc::new(trivia_agent))
        .skip_summarization(false);

    // Coordinator uses agents as tools
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Smart assistant that combines expert knowledge")
        .instruction(
            "You are a helpful assistant with access to expert agents:\n             - math_expert: For calculations and math problems\n             - trivia_expert: For facts, history, and trivia\n\n             When questions span multiple domains, call multiple experts              and synthesize their responses into a cohesive answer."
        )
        .model(model)
        .tool(Arc::new(math_tool))
        .tool(Arc::new(trivia_tool))
        .build()?;

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

Exemplo: Pergunta Multi-Domínio

Usuário: Quanto é 15% de 250, e esse número é significativo na história?

Sistema: // O Coordenador chama a ferramenta math_expert

[math_expert responde]: 15% de 250 é 37.5

Sistema: // Coordenador chama a ferramenta trivia_expert

[trivia_expert responde]: 37 e 38 são menos notáveis historicamente, mas 37,5°C é a temperatura do corpo humano...

Sistema: // Coordenador sintetiza

[coordinator]: 15% de 250 é igual a 37,5. Curiosamente, 37,5°C (99,5°F) está próximo da temperatura média do corpo humano de 37°C, tornando-o um número medicamente significativo!

✅ Quando Usar AgentTool

  • Você precisa combinar respostas de múltiplos especialistas
  • O Coordenador deve resumir ou filtrar a saída do especialista
  • Especialistas têm suas próprias ferramentas (capacidades aninhadas)
  • Você quer controle programático sobre a invocação do agente

5. Padrão 3: O Grafo Supervisor

🎯 Caso de Uso: Pipeline de Criação de Conteúdo

Você está construindo um sistema de criação de conteúdo. Dado um tópico, você precisa: (1) pesquisá-lo, (2) escrever um artigo, (3) adicionar exemplos de código. O supervisor decide dinamicamente a ordem com base na tarefa, e os trabalhadores podem retornar para revisões.

O padrão Grafo Supervisor usa o sistema de fluxo de trabalho baseado em grafo do ADK-Rust. Um agente supervisor roteia dinamicamente para os trabalhadores, com gerenciamento de estado completo e suporte à execução cíclica.

STARTSupervisorDecide o próximoagente trabalhadorPesquisadorTrabalhadorEscritorTrabalhadorProgramadorTrabalhadorENDretornar ao ciclo"concluído" → finalizar → ENDEstado Compartilhado• research_output• written_content• code_output

Por Que Usar um Grafo?

  • Roteamento dinâmico: O Supervisor decide o próximo trabalhador com base no estado atual
  • Execução cíclica: Os trabalhadores podem retornar para iterações
  • Estado compartilhado: Todos os nós leem/escrevem em um objeto de estado comum
  • Arestas condicionais: Caminhos diferentes com base nas decisões do LLM
  • Limites de recursão: Previnem loops infinitos

Implementação

use adk_agent::LlmAgentBuilder;
use adk_graph::{
    StateGraph,
    edge::{START, END},
    node::{AgentNode, ExecutionConfig, NodeOutput},
    state::State,
};
use adk_model::GeminiModel;
use serde_json::json;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Supervisor: Decides which worker should act next
    let supervisor = LlmAgentBuilder::new("supervisor")
        .description("Routes tasks to specialized workers")
        .instruction(
            "You are a task supervisor. Based on the task and work done so far,              decide who should work next.\n\n             Workers available:\n             - researcher: Gathers information and facts\n             - writer: Writes content based on research\n             - coder: Creates code examples\n\n             Respond with ONLY one word: 'researcher', 'writer', 'coder', or 'done'."
        )
        .model(model.clone())
        .build()?;

    // Workers with specialized roles
    let researcher = LlmAgentBuilder::new("researcher")
        .instruction("Research the topic. Provide key facts as bullet points.")
        .model(model.clone())
        .build()?;

    let writer = LlmAgentBuilder::new("writer")
        .instruction("Write engaging content based on the research provided.")
        .model(model.clone())
        .build()?;

    let coder = LlmAgentBuilder::new("coder")
        .instruction("Write clean, documented code examples for the topic.")
        .model(model.clone())
        .build()?;

    // Create AgentNodes with input/output mappers
    let supervisor_node = AgentNode::new(Arc::new(supervisor))
        .with_input_mapper(|state| {
            let task = state.get("task").and_then(|v| v.as_str()).unwrap_or("");
            let history = state.get("history")
                .and_then(|v| v.as_array())
                .map(|arr| arr.iter()
                    .filter_map(|h| h.get("agent").and_then(|a| a.as_str()))
                    .map(|s| format!("- {} completed", s))
                    .collect::<Vec<_>>()
                    .join("\n"))
                .unwrap_or_default();

            adk_core::Content::new("user").with_text(format!(
                "Task: {}\n\nWork completed:\n{}\n\nWho next?",
                task,
                if history.is_empty() { "None yet" } else { &history }
            ))
        })
        .with_output_mapper(|events| {
            let mut updates = std::collections::HashMap::new();
            for event in events {
                if let Some(content) = event.content() {
                    let text: String = content.parts.iter()
                        .filter_map(|p| p.text())
                        .collect();
                    let next = if text.to_lowercase().contains("researcher") {
                        "researcher"
                    } else if text.to_lowercase().contains("writer") {
                        "writer"
                    } else if text.to_lowercase().contains("coder") {
                        "coder"
                    } else {
                        "done"
                    };
                    updates.insert("next_agent".to_string(), json!(next));
                }
            }
            updates
        });

    // Build the graph
    let graph = StateGraph::with_channels(&[
        "task", "next_agent", "history",
        "research_output", "written_content", "code_output"
    ])
    .add_node(supervisor_node)
    .add_node(AgentNode::new(Arc::new(researcher)))
    .add_node(AgentNode::new(Arc::new(writer)))
    .add_node(AgentNode::new(Arc::new(coder)))
    // Finalize node compiles all outputs
    .add_node_fn("finalize", |ctx| async move {
        let research = ctx.get("research_output").and_then(|v| v.as_str());
        let content = ctx.get("written_content").and_then(|v| v.as_str());
        let code = ctx.get("code_output").and_then(|v| v.as_str());

        let result = format!(
            "=== FINAL OUTPUT ===\n\n{}\n\n{}\n\n{}",
            research.unwrap_or("No research"),
            content.unwrap_or("No content"),
            code.unwrap_or("No code")
        );
        Ok(NodeOutput::new().with_update("final_result", json!(result)))
    })
    // Graph structure
    .add_edge(START, "supervisor")
    .add_conditional_edges(
        "supervisor",
        |state| state.get("next_agent")
            .and_then(|v| v.as_str())
            .unwrap_or("done")
            .to_string(),
        [
            ("researcher", "researcher"),
            ("writer", "writer"),
            ("coder", "coder"),
            ("done", "finalize"),
        ],
    )
    // Workers cycle back to supervisor
    .add_edge("researcher", "supervisor")
    .add_edge("writer", "supervisor")
    .add_edge("coder", "supervisor")
    .add_edge("finalize", END)
    .compile()?
    .with_recursion_limit(15);  // Prevent infinite loops

    // Execute
    let mut input = State::new();
    input.insert("task".to_string(), json!("Create a guide about Rust error handling"));
    input.insert("history".to_string(), json!([]));

    let result = graph.invoke(input, ExecutionConfig::new("content-thread")).await?;
    println!("{}", result.get("final_result").and_then(|v| v.as_str()).unwrap_or(""));

    Ok(())
}

✅ Quando Usar o Grafo Supervisor

  • A ordem do fluxo de trabalho é dinâmica e LLM-determinada
  • Os trabalhadores podem precisar iterar ou retornar
  • O estado complexo precisa ser compartilhado entre os agentes
  • Você precisa de pontos de verificação ou fluxos de trabalho retomáveis
  • A decomposição de tarefas requer múltiplos passos sequenciais

6. Comparação de Padrões

FuncionalidadeCoordenadorAgentToolGrafo Supervisor
Usuário fala comEspecialista diretamenteApenas CoordenadorSaída final
Chamadas multiagente❌ Um por vez✅ Paralelo possível✅ Orquestrado
Processamento de resposta
Fluxos de trabalho cíclicos
Estado compartilhadoSomente sessãoSomente sessãoEstado completo do grafo
Complexidade de configuração🟢 Baixa🟡 Média🔴 Alta

7. Conclusão

Sistemas multiagente permitem construir aplicações de IA sofisticadas combinando agentes especializados. Escolha seu padrão com base nas suas necessidades:

  • Coordenador: Rápido de configurar, ótimo para roteamento de atendimento ao cliente
  • AgentTool: Quando você precisa processar ou combinar respostas
  • Grafo Supervisor: Fluxos de trabalho complexos, dinâmicos e com múltiplas etapas

🦀 Começar

Pronto para construir seu próprio sistema multiagente?