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:
- Crea el agente con variables de plantilla en la instrucción
- Configura Runner y SessionService para gestionar el estado
- Crea una sesión con variables de estado que coincidan con tu plantilla
- 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ón | Ejemplo | Fuente |
|---|---|---|
{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
- El agente recibe el mensaje del usuario → "¿Cuál es el clima en Tokio?"
- LLM decide llamar a la herramienta → Selecciona
get_weathercon{"city": "Tokyo"} - La herramienta se ejecuta → Devuelve
{"temperature": "22°C", "condition": "sunny"} - 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:
| Proveedor | Aplicación nativa |
|---|---|
| Gemini | Esquema completo, enviado como el esquema de respuesta |
| OpenAI y compatible con OpenAI | Esquema completo, enviado como un formato de respuesta estricto json_schema |
| OpenRouter | Esquema completo |
| DeepSeek | JSON 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étodo | Descripció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 LLMParallel— 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 llamaAuto— 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.
Relacionado
- Agentes de flujo de trabajo - Agentes secuenciales, paralelos y en bucle
- Sistemas multiagente - Construcción de jerarquías de agentes
- Herramientas de función - Creación de herramientas personalizadas
- Callbacks - Interceptación del comportamiento del agente
Anterior: Inicio rápido | Siguiente: Agentes de flujo de trabajo →