multiagentetutorialiarustv0.1.8

Construyendo Sistemas Multiagente con ADK-Rust

Aprende cuándo y cómo usar diferentes patrones multiagente —desde la coordinación simple hasta la orquestación compleja basada en grafos— con la seguridad de tipos y el rendimiento de Rust.

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

1. Introducción

El Problema: IA Que Choca Contra un Muro

Has construido tu primer agente de IA. Es impresionante: puede responder preguntas, resumir documentos, quizás incluso escribir código. Pero luego la realidad golpea:

¿Puedes revisar mi última factura y también ayudarme a configurar el API?

Tu agente tiene dificultades. No fue entrenado en sistemas de facturación Y documentación para desarrolladores Y flujos de trabajo de resolución de problemas. Su prompt de instrucción ya tiene 2000 tokens intentando cubrirlo todo. La calidad de la respuesta se degrada.

Este es el límite del agente único. A medida que tu aplicación crece, te enfrentas a dolorosas compensaciones:

  • Prompts inflados: Cada nueva capacidad significa instrucciones más largas, mayor latencia y más confusión para el modelo
  • Generalista: Un agente que maneja facturación, soporte Y ventas se vuelve mediocre en los tres
  • Mantenimiento imposible: Cambiar la lógica de facturación no debería arriesgarse a romper tus flujos de soporte
  • Sin especialización: Tu agente de matemáticas no puede tener una herramienta de calculadora mientras tu agente de investigación tiene búsqueda web; comparten todo

La Solución: Agentes Especializados Trabajando Juntos

Los sistemas multiagente resuelven esto descomponiendo tareas complejas en roles especializados. En lugar de un generalista abrumado, creas especialistas enfocados:

  • Servicio al cliente: Un coordinator dirige a los usuarios a especialistas de facturación, soporte técnico o ventas, cada uno con capacitación y herramientas enfocadas
  • Creación de contenido: Un agente de investigación recopila hechos, un writer elabora la narrativa, un editor pule; cada agente domina una habilidad
  • Generación de código: Un planificador diseña la arquitectura, un coder implementa, un revisor detecta errores: diferentes perspectivas mejoran la calidad

¿El resultado? Cada agente se mantiene enfocado, las indicaciones son manejables y puedes actualizar la lógica de facturación sin tocar el soporte. Son microservicios para IA.

Lo que aprenderás

ADK-Rust proporciona tres patrones progresivamente potentes para la orquestación multiagente. Este tutorial te enseñará:

  • Cuándo usar cada patrón según tus requisitos
  • Cómo implementarlos con código Rust listo para producción
  • Por qué las compensaciones arquitectónicas importan para tu caso de uso específico

2. Eligiendo el patrón correcto

Antes de sumergirnos en el código, entendamos qué ofrece cada patrón:

PatrónMejor paraNivel de controlComplejidad
CoordinadorTraspasos de conversaciónLLM decideBaja
AgentToolProcesamiento de respuestasEl coordinador procesaMedia
Grafo SupervisorFlujos de trabajo complejosGestión completa del estadoAlta

3. Patrón 1: El Coordinador (Subagentes)

🎯 Caso de Uso: Enrutamiento de Servicio al Cliente

Estás construyendo un bot de servicio al cliente. Los usuarios pueden preguntar sobre facturación, solicitar ayuda técnica o consultar sobre nuevas funciones. Cada dominio requiere conocimientos especializados, pero los usuarios no deberían necesitar saber con qué departamento contactar.

El patrón Coordinador utiliza la transferencia automática de agentes. Cuando añades subagentes a través de .sub_agent(), ADK-Rust inyecta una herramienta transfer_to_agent. El LLM decide cuándo transferir basándose en la conversación.

UsuarioCoordinadorEnruta solicitudesa especialistasFacturaciónSubagenteSoporteSubagenteVentasSubagentetransfer_to_agenttransfer_to_agenttransfer_to_agent

Características Clave

  • Transferencia fluida: El usuario continúa de forma natural con el especialista
  • Enrutamiento impulsado por LLM: El coordinator decide basándose en el contexto de la conversación
  • Continuidad de la conversación: El historial de la sesión se mantiene a través de las transferencias
  • Sin procesamiento de respuesta: El especialista habla directamente con el usuario después de la transferencia

Implementación

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

Ejemplo de Conversación

Usuario: Hola, tengo una pregunta sobre mi factura

[coordinator]: ¡Hola! Estaré encantado de ayudarte con tu pregunta de facturación. Permíteme conectarte con nuestro especialista en facturación que puede asistirte.

Sistema: 🔄 Transferir a: billing_agent

[billing_agent]: ¡Hola! Soy el especialista en facturación. Puedo ayudarte con facturas, pagos y preguntas sobre suscripciones. ¿Qué te gustaría saber sobre tu factura?

Usuario: ¿Por qué se me cobró dos veces este mes?

[billing_agent]: Investigaré ese cargo duplicado por ti...

✅ Cuándo Usar el Coordinador

  • El usuario debe interactuar directamente con los especialistas
  • Las decisiones de enrutamiento son sencillas
  • No es necesario procesar las respuestas de los especialistas
  • El flujo de conversación es lineal (un especialista a la vez)

4. Patrón 2: Agentes como Herramientas (AgentTool)

🎯 Caso de Uso: Agregación de Conocimiento

Estás construyendo un asistente inteligente que responde preguntas que abarcan múltiples dominios. Un usuario pregunta "¿Qué es el 15% de 250, y por qué es significativo ese número en la historia?" Necesitas llamar a un experto en matemáticas, luego a un experto en trivia, y combinar sus respuestas.

El patrón AgentTool envuelve a los agentes como herramientas invocables. A diferencia de los sub-agentes, el coordinator invoca a los especialistas programáticamente y recibe sus respuestas para procesarlas o combinarlas antes de responder al usuario.

UsuarioCoordinadorLlama a los agentes como herramientasProcesa las respuestasAgrega los resultadosDevuelve al usuarioExperto en matemáticasAgentTool+ calculadoraExperto en triviaAgentToolLLM conocimientoInvestigadorAgentTool+ web_searchllamada →← respuesta

Diferencias Clave con el Coordinador

Coordinador (Sub-Agentes)

  • • El especialista habla directamente con el usuario
  • • Un especialista a la vez
  • • Sin procesamiento de respuestas

AgentTool

  • • El coordinador recibe las respuestas
  • • Puede llamar a múltiples especialistas
  • • Agrega y resume

Implementación

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

Ejemplo: Pregunta Multidominio

Usuario: ¿Qué es el 15% de 250, y es ese número significativo en la historia?

Sistema: // El coordinador llama a la herramienta math_expert

[math_expert responde]: El 15% de 250 es 37.5

Sistema: // El Coordinador llama a la herramienta trivia_expert

[trivia_expert responde]: 37 y 38 son menos notables históricamente, pero 37.5°C es la temperatura del cuerpo humano...

Sistema: // El Coordinador sintetiza

[coordinator]: El 15% de 250 es igual a 37.5. Curiosamente, 37.5°C (99.5°F) está cerca de la temperatura corporal humana promedio de 37°C, ¡lo que lo convierte en un número médicamente significativo!

✅ Cuándo usar AgentTool

  • Necesitas combinar respuestas de múltiples expertos
  • El Coordinador debe resumir o filtrar la salida del especialista
  • Los especialistas tienen sus propias herramientas (capacidades anidadas)
  • Quieres control programático sobre la invocación del agente

5. Patrón 3: El Grafo Supervisor

🎯 Caso de Uso: Pipeline de Creación de Contenido

Estás construyendo un sistema de creación de contenido. Dado un tema, necesitas: (1) investigarlo, (2) escribir un artículo, (3) añadir ejemplos de código. El supervisor decide dinámicamente el orden basándose en la tarea, y los trabajadores pueden volver para revisiones.

El patrón de Grafo Supervisor utiliza el sistema de flujo de trabajo basado en grafos de ADK-Rust. Un agente supervisor enruta dinámicamente a los trabajadores, con gestión completa del estado y soporte para ejecución cíclica.

STARTSupervisorDecide el siguienteagente trabajadorInvestigadorTrabajadorEscritorTrabajadorProgramadorTrabajadorENDvolver a ciclar"hecho" → finalizar → ENDEstado Compartido• research_output• written_content• code_output

¿Por qué usar un Grafo?

  • Enrutamiento dinámico: El Supervisor decide el siguiente trabajador basándose en el estado actual
  • Ejecución cíclica: Los trabajadores pueden volver a iterar
  • Estado compartido: Todos los nodos leen/escriben en un objeto de estado común
  • Aristas condicionales: Diferentes rutas basadas en las decisiones de LLM
  • Límites de recursión: Evitar bucles infinitos

Implementación

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

✅ Cuándo usar el Grafo Supervisor

  • El orden del flujo de trabajo es dinámico y está determinado por LLM
  • Los trabajadores pueden necesitar iterar o volver atrás
  • El estado complejo necesita ser compartido entre agentes
  • Necesitas puntos de control o flujos de trabajo reanudables
  • La descomposición de tareas requiere múltiples pasos secuenciales

6. Comparación de Patrones

CaracterísticaCoordinadorAgentToolGrafo Supervisor
El usuario habla conEspecialista directamenteSolo el coordinadorSalida final
Llamadas multi-agente❌ Uno a la vez✅ Paralelo posible✅ Orquestado
Procesamiento de respuestas
Flujos de trabajo cíclicos
Estado compartidoSolo sesiónSolo sesiónEstado completo del grafo
Complejidad de configuración🟢 Baja🟡 Media🔴 Alta

7. Conclusión

Los sistemas multiagente le permiten construir aplicaciones de IA sofisticadas combinando agentes especializados. Elija su patrón según sus necesidades:

  • Coordinador: Rápido de configurar, ideal para el enrutamiento de servicio al cliente
  • AgentTool: Cuando necesite procesar o combinar respuestas
  • Grafo Supervisor: Flujos de trabajo complejos, dinámicos y de múltiples pasos

🦀 Empezar

¿Listo para construir su propio sistema multiagente?