LlmAgent

LlmAgent es el tipo de agente central en ADK-Rust que usa un modelo de lenguaje grande para el razonamiento y la toma de decisiones.

Inicio rápido

Crea un nuevo proyecto:

cargo new llm_agent
cd llm_agent

Añade dependencias a Cargo.toml:

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

Crea .env con tu clave de API:

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

Reemplaza src/main.rs:

use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
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 = GeminiModel::new(&api_key, "gemini-2.5-flash")?;

    let agent = LlmAgentBuilder::new("my_agent")
        .instruction("You are a helpful assistant.")
        .model(Arc::new(model))
        .build()?;

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

Ejecuta:

cargo run

Interactuar con tu agente

Verás un prompt interactivo:

🤖 Agent ready! Type your questions (or 'exit' to quit).

You: Hello! What can you help me with?
Assistant: Hello! I'm a helpful assistant. I can help you with:
- Answering questions on various topics
- Explaining concepts clearly
- Having a conversation

What would you like to know?

You: exit
👋 Goodbye!

Dar forma al comportamiento del agente con instrucciones

El método instruction() define la personalidad y el comportamiento de tu agente. Este es el prompt del sistema que guía cada respuesta:

// A formal business assistant
let formal_agent = LlmAgentBuilder::new("formal_assistant")
    .instruction("You are a professional business consultant. \
                 Use formal language. Be concise and data-driven.")
    .model(Arc::new(model))
    .build()?;

// A friendly coding tutor
let tutor_agent = LlmAgentBuilder::new("code_tutor")
    .instruction("You are a friendly coding tutor for beginners. \
                 Explain concepts simply. Use examples. \
                 Encourage questions. Never make the user feel bad for not knowing.")
    .model(Arc::new(model))
    .build()?;

// A creative storyteller
let story_agent = LlmAgentBuilder::new("storyteller")
    .instruction("You are a creative storyteller. \
                 Craft engaging narratives with vivid descriptions. \
                 Use plot twists and memorable characters.")
    .model(Arc::new(model))
    .build()?;

Ejemplos de salida

Prompt del usuario: "¿Qué es Rust?"

Asistente formal de negocios:

Rust is a systems programming language focused on performance, memory safety, 
and concurrency. Key metrics: 0 runtime overhead, 0 garbage collection. 
Adoption growing 30% YoY in enterprise. Recommended for performance-critical 
applications where reliability is paramount.

Tutor amable de programación:

Great question! 🎉 Rust is a programming language that helps you write fast 
and safe code. Think of it like a helpful friend who stops you from making 
mistakes before they happen!

Here's a simple example:
  let name = "Alice";  // This creates a variable

What makes Rust special is that it catches bugs at compile time, not when 
your program is running. Pretty cool, right? Want me to explain more?

Narrador creativo:

In the twilight of 2010, in a secret Mozilla laboratory, a lone developer 
named Graydon Hoare breathed life into something extraordinary—a language 
that would challenge the old gods of C and C++.

They called it Rust, named for a fungus that survives against all odds. 
Like a phoenix rising from the ashes of segmentation faults and memory 
leaks, Rust promised a new era: speed without sacrifice, power without peril.

And so, the revolution began...

Plantillas de instrucciones

Las instrucciones admiten inyección de variables usando la sintaxis {var}. Las variables se resuelven desde el estado de la sesión en tiempo de ejecución:

let agent = LlmAgentBuilder::new("personalized")
    .instruction("You are helping {user_name}. Their role is {user_role}. \
                 Tailor your responses to their expertise level.")
    .model(Arc::new(model))
    .build()?;

Guía paso a paso para usar plantillas:

  1. Crea el agente con variables de plantilla en la instrucción
  2. Configura Runner y SessionService para gestionar el estado
  3. Crea una sesión con variables de estado que coincidan con tu plantilla
  4. Ejecuta el agente - las plantillas se reemplazan automáticamente

Aquí tienes un ejemplo completo y funcional:

use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::session::{CreateRequest, InMemorySessionService, SessionService};
use adk_rust::futures::StreamExt;
use serde_json::json;
use std::collections::HashMap;
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 = GeminiModel::new(&api_key, "gemini-2.5-flash")?;

    // 1. Agent with templated instruction
    let agent = LlmAgentBuilder::new("personalized")
        .instruction("You are helping {user_name}. Their role is {user_role}. \
                     Tailor your responses to their expertise level.")
        .model(Arc::new(model))
        .build()?;

    // 2. Create session service and runner
    let session_service = Arc::new(InMemorySessionService::new());
    let runner = Runner::new(RunnerConfig {
        app_name: "templating_demo".to_string(),
        agent: Arc::new(agent),
        session_service: session_service.clone(),
        artifact_service: None,
        memory_service: None,
        run_config: None,
    })?;

    // 3. Create session with state variables
    let mut state = HashMap::new();
    state.insert("user_name".to_string(), json!("Alice"));
    state.insert("user_role".to_string(), json!("Senior Developer"));

    let session = session_service.create(CreateRequest {
        app_name: "templating_demo".to_string(),
        user_id: "user123".to_string(),
        session_id: None,
        state,
    }).await?;

    // 4. Run the agent - instruction becomes:
    // "You are helping Alice. Their role is Senior Developer..."
    let mut response_stream = runner.run(
        UserId::new("user123")?,
        SessionId::new(session.id())?,
        Content::new("user").with_text("Explain async/await in Rust"),
    ).await?;

    // Print the response
    while let Some(event) = response_stream.next().await {
        let event = event?;
        if let Some(content) = event.content() {
            for part in &content.parts {
                if let Part::Text { text } = part {
                    print!("{}", text);
                }
            }
        }
    }

    Ok(())
}

Tipos de variables de plantilla:

PatrónEjemploFuente
{var}{user_name}Estado de sesión
{prefix:var}{user:name}, {app:config}Estado con prefijo
{var?}{user_name?}Opcional (vacío si falta)
{artifact.file}{artifact.resume.pdf}Contenido del artefacto

Salida de ejemplo:

Plantilla: "You are helping {user_name}. Their role is {user_role}."
Se convierte en: "You are helping Alice. Their role is Senior Developer."

¡El agente responderá entonces con contenido personalizado basado en el nombre y el nivel de experiencia del usuario!


Añadir herramientas

Las herramientas dan a tu agente capacidades más allá de la conversación: pueden obtener datos, realizar cálculos, buscar en la web o llamar a APIs externos. El LLM decide cuándo usar una herramienta según la solicitud del usuario.

Cómo funcionan las herramientas

  1. El agente recibe el mensaje del usuario → "¿Cuál es el clima en Tokio?"
  2. LLM decide llamar a la herramienta → Selecciona get_weather con {"city": "Tokyo"}
  3. La herramienta se ejecuta → Devuelve {"temperature": "22°C", "condition": "sunny"}
  4. LLM da formato a la respuesta → "El clima en Tokio es soleado con 22°C."

Crear una herramienta con FunctionTool

FunctionTool es la forma más sencilla de crear una herramienta: envuelve cualquier función async de Rust y el LLM puede llamarla. Proporcionas un nombre, una descripción y una función manejadora que recibe argumentos JSON y devuelve un resultado JSON.

let weather_tool = FunctionTool::new(
    "get_weather",                              // Tool name (used by LLM)
    "Get the current weather for a city",       // Description (helps LLM decide when to use it)
    |_ctx, args| async move {                   // Handler function
        let city = args.get("city")             // Extract arguments from JSON
            .and_then(|v| v.as_str())
            .unwrap_or("unknown");
        Ok(json!({ "city": city, "temperature": "22°C" }))  // Return JSON result
    },
);

Las herramientas nativas integradas del proveedor ahora pueden mezclarse con instancias de FunctionTool en el mismo agente. ADK reenvía las declaraciones de herramientas nativas al proveedor mientras sigue ejecutando las herramientas de función ordinarias localmente.

Crear un agente con múltiples herramientas

Crea un nuevo proyecto:

cargo new tool_agent
cd tool_agent

Añade dependencias a Cargo.toml:

[dependencies]
adk-rust = { version = "2.0.0", features = ["tools"] }
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"

Crea .env:

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

Reemplaza src/main.rs con un agente que tenga tres herramientas:

use adk_rust::prelude::*;
use adk_rust::Launcher;
use serde_json::json;
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 = GeminiModel::new(&api_key, "gemini-2.5-flash")?;

    // Tool 1: Weather lookup
    let weather_tool = FunctionTool::new(
        "get_weather",
        "Get the current weather for a city. Parameters: city (string)",
        |_ctx, args| async move {
            let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("unknown");
            Ok(json!({ "city": city, "temperature": "22°C", "condition": "sunny" }))
        },
    );

    // Tool 2: Calculator
    let calculator = FunctionTool::new(
        "calculate",
        "Perform arithmetic. Parameters: a (number), b (number), operation (add/subtract/multiply/divide)",
        |_ctx, args| async move {
            let a = args.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
            let b = args.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
            let op = args.get("operation").and_then(|v| v.as_str()).unwrap_or("add");
            let result = match op {
                "add" => a + b,
                "subtract" => a - b,
                "multiply" => a * b,
                "divide" => if b != 0.0 { a / b } else { 0.0 },
                _ => 0.0,
            };
            Ok(json!({ "result": result }))
        },
    );

    // Tool 3: Built-in Google Search (Note: Currently unsupported in ADK-Rust)
    // let search_tool = GoogleSearchTool::new();

    // Build agent with weather and calculator tools
    let agent = LlmAgentBuilder::new("multi_tool_agent")
        .instruction("You are a helpful assistant. Use tools when needed: \
                     - get_weather for weather questions \
                     - calculate for math")
        .model(Arc::new(model))
        .tool(Arc::new(weather_tool))
        .tool(Arc::new(calculator))
        // .tool(Arc::new(search_tool))  // Currently unsupported
        .build()?;

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

Ejecuta tu agente:

cargo run

Ejemplo de interacción

You: What's 15% of 250?
Assistant: [Using calculate tool with a=250, b=0.15, operation=multiply]
15% of 250 is 37.5.

You: What's the weather in Tokyo?
Assistant: [Using get_weather tool with city=Tokyo]
The weather in Tokyo is sunny with a temperature of 22°C.

You: Search for latest Rust features
Assistant: I don't have access to search functionality at the moment, but I can help with other questions about Rust or perform calculations!

Salida estructurada con esquema JSON

Para aplicaciones que necesitan datos estructurados, usa output_schema():

use adk_rust::prelude::*;
use serde_json::json;
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 = GeminiModel::new(&api_key, "gemini-2.5-flash")?;

    let extractor = LlmAgentBuilder::new("entity_extractor")
        .instruction("Extract entities from the given text.")
        .model(Arc::new(model))
        .output_schema(json!({
            "type": "object",
            "properties": {
                "people": {
                    "type": "array",
                    "items": { "type": "string" }
                },
                "locations": {
                    "type": "array",
                    "items": { "type": "string" }
                },
                "dates": {
                    "type": "array",
                    "items": { "type": "string" }
                }
            },
            "required": ["people", "locations", "dates"]
        }))
        .build()?;

    println!("Entity extractor ready!");
    Ok(())
}

Cómo hacen cumplir el esquema los proveedores

output_schema llega al proveedor como GenerateContentConfig::response_schema. Lo que el proveedor haga con ello difiere, y el agente valida el resultado de cualquier manera:

ProveedorAplicación nativa
GeminiEsquema completo, enviado como el esquema de respuesta
OpenAI y compatible con OpenAIEsquema completo, enviado como un formato de respuesta estricto json_schema
OpenRouterEsquema completo
DeepSeekJSON sintaxis solo — DeepSeek's JSON modo de salida no tiene variante json_schema, por lo que el esquema es aplicado por la validación del agente

Cuando un proveedor aplica solo sintaxis, o nada en absoluto, el agente aún inyecta el esquema como una instrucción y valida la respuesta, por lo que una respuesta no conforme cuesta un reintento en lugar de devolver datos incorrectos.

Nota: DeepSeek requiere que la palabra "json" aparezca en el prompt siempre que JSON Output esté activado; de lo contrario, API puede devolver contenido vacío. El adaptador añade esa mención por sí mismo cuando tu prompt no la contiene ya.

Ejemplo de salida de JSON

Entrada: "John met Sarah in Paris on December 25th"

Salida:

{
  "people": ["John", "Sarah"],
  "locations": ["Paris"],
  "dates": ["December 25th"]
}

Características avanzadas

Incluir contenido

Controla la visibilidad del historial de conversación:

// Full history (default)
.include_contents(IncludeContents::Default)

// Stateless - sees only injected instructions plus the current user turn
.include_contents(IncludeContents::None)

Clave de salida

Guarda las respuestas del agente en el estado de la sesión:

.output_key("summary")  // Response saved to state["summary"]

Instrucciones dinámicas

Calcula las instrucciones en tiempo de ejecución:

.instruction_provider(|ctx| {
    Box::pin(async move {
        let user_id = ctx.user_id();
        Ok(format!("You are assisting user {}.", user_id))
    })
})

Llamadas de retorno

Intercepta el comportamiento del agente:

.before_model_callback(|ctx, request| {
    Box::pin(async move {
        println!("About to call LLM with {} messages", request.contents.len());
        Ok(BeforeModelResult::Continue)
    })
})

Referencia del constructor

MétodoDescripción
new(name)Crea el constructor con el nombre del agente
model(Arc<dyn Llm>)Establece el LLM (obligatorio)
description(text)Descripción del agente
instruction(text)Prompt del sistema
tool(Arc<dyn Tool>)Añade una herramienta estática
toolset(Arc<dyn Toolset>)Añade un conjunto de herramientas dinámico resuelto por invocación
output_schema(json)JSON esquema para salida estructurada
output_key(key)Guarda la respuesta en el estado
include_contents(mode)Visibilidad del historial
max_iterations(n)Máximo de LLM rondas de ida y vuelta (valor predeterminado: 100)
tool_execution_strategy(strategy)Modo de despacho de herramientas: Sequential, Parallel o Auto
default_retry_budget(RetryBudget)Reintentar herramientas fallidas hasta N veces con retraso
tool_retry_budget(name, RetryBudget)Anulación de reintento por herramienta
circuit_breaker_threshold(u32)Desactivar la herramienta después de N fallos consecutivos
on_tool_error(callback)Registrar un controlador de reserva para fallos de herramienta
after_tool_callback_full(callback)Callback enriquecido V2 después de la herramienta con herramienta, args y respuesta
build()Crea el agente

Control de iteraciones

El método max_iterations() limita cuántos idas y vueltas de LLM puede realizar un agente antes de detenerse. Esto es útil para:

  • Evitar bucles descontrolados de invocación de herramientas
  • Controlar los costos en producción
  • Establecer límites razonables para tareas complejas
let agent = LlmAgentBuilder::new("bounded_agent")
    .model(Arc::new(model))
    .instruction("You are a helpful assistant.")
    .tool(Arc::new(my_tool))
    .max_iterations(10)  // Stop after 10 LLM calls
    .build()?;

El valor predeterminado es 100 iteraciones, lo cual es suficiente para la mayoría de los casos de uso. Se recomiendan valores más bajos (5-20) para agentes simples de preguntas y respuestas, mientras que pueden ser necesarios valores más altos para tareas complejas de razonamiento de varios pasos.


Conjuntos de herramientas dinámicos

Para herramientas que dependen del contexto de invocación (p. ej., sesiones de navegador por usuario), use .toolset() en lugar de .tool(). Los conjuntos de herramientas se resuelven al inicio de cada llamada run():

use adk_browser::{BrowserSessionPool, BrowserToolset, BrowserConfig};
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;

let pool = Arc::new(BrowserSessionPool::new(BrowserConfig::new(), 10));
let toolset = Arc::new(BrowserToolset::with_pool(pool));

let agent = LlmAgentBuilder::new("web_agent")
    .model(model)
    .instruction("You are a web automation assistant.")
    .toolset(toolset)  // Resolved per-user at runtime
    .build()?;

Puede combinar .tool() estáticas y .toolset() dinámicas en el mismo agente. Los nombres de herramientas duplicados entre herramientas estáticas y conjuntos de herramientas producen un error determinista.

RealtimeAgentBuilder también admite .toolset() con la misma semántica, por lo que los agentes de voz en tiempo real también obtienen resolución dinámica de herramientas.

Composición de conjuntos de herramientas

Use FilteredToolset, MergedToolset y PrefixedToolset de adk-tool para componer configuraciones complejas de conjuntos de herramientas:

use adk_tool::{BasicToolset, FilteredToolset, MergedToolset, PrefixedToolset, string_predicate};

// Prefix weather tools to avoid name collisions
let weather = Arc::new(PrefixedToolset::new(weather_toolset, "wx"));

// Filter utility tools to only expose search and calculate
let utils = Arc::new(FilteredToolset::new(
    utility_toolset,
    string_predicate(vec!["search".into(), "calculate".into()]),
));

// Merge into a single toolset
let composed = MergedToolset::new("all", vec![weather, utils]);

let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .toolset(Arc::new(composed))
    .build()?;

Todas las utilidades de composición funcionan con cualquier implementación de Toolset, incluidas McpToolset y BrowserToolset.

Ejecución paralela de herramientas

Cuando un LLM devuelve varias llamadas a herramientas en una sola respuesta, puede controlar cómo se distribuyen:

use adk_core::ToolExecutionStrategy;

let agent = LlmAgentBuilder::new("fast_agent")
    .model(Arc::new(model))
    .instruction("You are a research assistant. Use multiple tools in parallel.")
    // Auto requires both safety signals for concurrent inclusion
    .tool_execution_strategy(ToolExecutionStrategy::Auto)
    .tool(Arc::new(
        search_tool
            .with_read_only(true)
            .with_concurrency_safe(true),
    ))
    .tool(Arc::new(
        lookup_tool
            .with_read_only(true)
            .with_concurrency_safe(true),
    ))
    .tool(Arc::new(save_tool)) // runs after the concurrent safe subset
    .build()?;

Hay tres estrategias disponibles:

  • Sequential (predeterminada) — las herramientas se ejecutan una a la vez en orden LLM
  • Parallel — todas las herramientas se ejecutan de forma concurrente; esta anulación explícita omite los metadatos de seguridad, por lo que la seguridad queda a cargo de quien llama
  • Auto — las llamadas cuyas herramientas son de solo lectura y seguras para la concurrencia se ejecutan concurrentemente primero; luego, todas las llamadas restantes se ejecutan de forma secuencial

Los resultados siempre se devuelven en el orden original de LLM, independientemente de la estrategia. Las herramientas fallidas producen una respuesta de error JSON sin abortar el lote.

La estrategia se establece por agente mediante LlmAgentBuilder::tool_execution_strategy(). Si no se establece, el valor predeterminado es Sequential.

Resiliencia de herramientas

Configure presupuestos de reintentos y circuit breakers para agentes de producción:

use adk_core::RetryBudget;
use std::time::Duration;

let agent = LlmAgentBuilder::new("resilient_agent")
    .model(model)
    .tool(Arc::new(my_tool))
    // Retry all tools up to 2 times with 500ms delay
    .default_retry_budget(RetryBudget::new(2, Duration::from_millis(500)))
    // Override for a specific tool
    .tool_retry_budget("flaky_api", RetryBudget::new(4, Duration::from_secs(1)))
    // Disable a tool after 3 consecutive failures in one invocation
    .circuit_breaker_threshold(3)
    // Provide a fallback when a tool fails
    .on_tool_error(Box::new(|_ctx, tool, _args, error| {
        Box::pin(async move {
            tracing::warn!(tool = tool.name(), %error, "tool failed");
            Ok(None) // None = propagate error; Some(value) = use as fallback
        })
    }))
    .build()?;

Las devoluciones de llamada posteriores a la herramienta pueden inspeccionar metadatos estructurados de ToolOutcome mediante CallbackContext::tool_outcome():

.after_tool_callback(Box::new(|ctx| {
    Box::pin(async move {
        if let Some(outcome) = ctx.tool_outcome() {
            println!(
                "Tool '{}' {} in {:?} (attempt {})",
                outcome.tool_name,
                if outcome.success { "succeeded" } else { "failed" },
                outcome.duration,
                outcome.attempt,
            );
        }
        Ok(None)
    })
}))

Ejemplo completo

Un agente listo para producción con varias herramientas (clima, calculadora, búsqueda) y salida guardada en el estado de la sesión:

use adk_rust::prelude::*;
use adk_rust::Launcher;
use serde_json::json;
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 = GeminiModel::new(&api_key, "gemini-2.5-flash")?;

    // Weather tool
    let weather = FunctionTool::new(
        "get_weather",
        "Get weather for a city. Parameters: city (string)",
        |_ctx, args| async move {
            let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("unknown");
            Ok(json!({
                "city": city,
                "temperature": "22°C",
                "humidity": "65%",
                "condition": "partly cloudy"
            }))
        },
    );

    // Calculator tool
    let calc = FunctionTool::new(
        "calculate",
        "Math operations. Parameters: expression (string like '2 + 2')",
        |_ctx, args| async move {
            let expr = args.get("expression").and_then(|v| v.as_str()).unwrap_or("0");
            Ok(json!({ "expression": expr, "result": "computed" }))
        },
    );

    // Build the full agent
    let agent = LlmAgentBuilder::new("assistant")
        .description("A helpful assistant with weather and calculation abilities")
        .instruction("You are a helpful assistant. \
                     Use the weather tool for weather questions. \
                     Use the calculator for math. \
                     Be concise and friendly.")
        .model(Arc::new(model))
        .tool(Arc::new(weather))
        .tool(Arc::new(calc))
        // .tool(Arc::new(GoogleSearchTool::new()))  // Provider-native tools can be mixed with FunctionTool
        .output_key("last_response")
        .build()?;

    println!("✅ Agent '{}' ready!", agent.name());
    Launcher::new(Arc::new(agent)).run().await?;
    Ok(())
}

Pruebe estos prompts:

You: What's 25 times 4?
Assistant: It's 100.

You: How's the weather in New York?
Assistant: The weather in New York is partly cloudy with a temperature of 22°C and 65% humidity.

You: Calculate 15% tip on $85
Assistant: A 15% tip on $85 is $12.75, making the total $97.75.


Anterior: Inicio rápido | Siguiente: Agentes de flujo de trabajo →