LlmAgent
LlmAgent es el tipo de agente principal de ADK-Rust que utiliza un modelo de lenguaje de gran tamaño para razonar y tomar decisiones.
Inicio rápido
Crea un proyecto nuevo:
cargo new llm_agent
cd llm_agent
Añade las dependencias a Cargo.toml:
[dependencies]
adk-rust = "2.1.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-3.7-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(())
}
Ejecútalo:
cargo run
Interactuar con tu agente
Verás un indicador 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!
Definir el 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 resultados
Indicación del usuario: "¿Qué es Rust?"
Asistente empresarial formal:
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 de programación amigable:
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 la inyección de variables mediante la sintaxis {var}. Las variables se resuelven a partir del estado de la sesión durante la 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 utilizar plantillas:
- Crea el agente con variables de plantilla en la instrucción
- Configura Runner y SessionService para administrar 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-3.7-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 la 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 |
Ejemplo de salida:
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!
Adición de herramientas
Las herramientas proporcionan a tu agente capacidades que van más allá de la conversación: pueden obtener datos, realizar cálculos, buscar en la web o llamar a APIs externos. LLM decide cuándo utilizar una herramienta según la solicitud del usuario.
Cómo funcionan las herramientas
- El agente recibe el mensaje del usuario → "¿Qué tiempo hace en Tokio?"
- LLM decide llamar a una herramienta → Selecciona
get_weathercon{"city": "Tokyo"} - La herramienta se ejecuta → Devuelve
{"temperature": "22°C", "condition": "sunny"} - LLM da formato a la respuesta → "El tiempo en Tokio es soleado y hay 22 °C."
Creación de una herramienta con FunctionTool
FunctionTool es la forma más sencilla de crear una herramienta: permite envolver cualquier función asíncrona de Rust, y LLM puede llamarla. Proporcionas un nombre, una descripción y una función controladora 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
},
);
Ahora las herramientas integradas nativas del proveedor pueden combinarse con instancias de FunctionTool en el mismo agente. ADK reenvía las declaraciones de herramientas nativas al proveedor, mientras sigue ejecutando localmente las herramientas de función ordinarias.
Creación de un agente con varias herramientas
Crea un proyecto nuevo:
cargo new tool_agent
cd tool_agent
Añade dependencias a Cargo.toml:
[dependencies]
adk-rust = { version = "2.1.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 por 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-3.7-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 el esquema JSON
Para aplicaciones que necesitan datos estructurados, utiliza 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-3.7-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 aplican los proveedores el esquema
output_schema llega al proveedor como GenerateContentConfig::response_schema.
Lo que hace el proveedor con él varía, y el agente valida el resultado de
cualquier modo:
| Proveedor | Cumplimiento nativo |
|---|---|
| Gemini | Esquema completo, enviado como esquema de respuesta |
| OpenAI y compatible con OpenAI | Esquema completo, enviado como formato de respuesta json_schema estricto |
| OpenRouter | Esquema completo |
| DeepSeek | Solo la sintaxis de JSON — el modo de salida JSON de DeepSeek no tiene una variante json_schema, por lo que el esquema se aplica mediante la validación del agente |
Cuando un proveedor solo exige la sintaxis, o no exige nada en absoluto, el agente aún inyecta el esquema como una instrucción y valida la respuesta, por lo que una respuesta que no cumpla los requisitos 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 el prompt aún no la contiene.
Ejemplo de salida de JSON
Entrada: "John conoció a Sarah en París el 25 de diciembre"
Salida:
{
"people": ["John", "Sarah"],
"locations": ["Paris"],
"dates": ["December 25th"]
}
Funciones avanzadas
Incluir contenido
Controla la visibilidad del historial de la 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 durante la ejecución:
.instruction_provider(|ctx| {
Box::pin(async move {
let user_id = ctx.user_id();
Ok(format!("You are assisting user {}.", user_id))
})
})
Devoluciones de llamada
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 un 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 que se resuelve en cada invocación |
output_schema(json) | Esquema de JSON para salida estructurada |
output_key(key) | Guarda la respuesta en el estado |
include_contents(mode) | Visibilidad del historial |
max_iterations(n) | Máximo de viajes de ida y vuelta de LLM (predeterminado: 100) |
tool_execution_strategy(strategy) | Modo de despacho de herramientas: Sequential, Parallel o Auto |
default_retry_budget(RetryBudget) | Reintentar las herramientas fallidas hasta N veces con un retraso |
tool_retry_budget(name, RetryBudget) | Sobrescritura de reintentos por herramienta |
circuit_breaker_threshold(u32) | Deshabilitar la herramienta después de N fallos consecutivos |
on_tool_error(callback) | Registra un controlador de respaldo para los fallos de las herramientas |
after_tool_callback_full(callback) | Devolución de llamada enriquecida V2 posterior a la herramienta con la herramienta, los argumentos y la respuesta |
build() | Crea el agente |
Control de iteraciones
El método max_iterations() limita cuántos viajes de ida y vuelta de LLM puede realizar un agente antes de detenerse. Esto resulta ú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 de 100 iteraciones, suficiente para la mayoría de los casos de uso. Se recomiendan valores más bajos (5-20) para agentes sencillos de preguntas y respuestas, mientras que las tareas complejas de razonamiento en varios pasos pueden requerir valores más altos.
Conjuntos de herramientas dinámicos
Para las herramientas que dependen del contexto de invocación (por ejemplo, sesiones de navegador por usuario), usa .toolset() en lugar de .tool(). Los conjuntos de herramientas se resuelven al inicio de cada llamada de 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()?;
Puedes combinar .tool() estáticas y .toolset() dinámicas en el mismo agente. Los nombres de herramientas duplicados entre las herramientas estáticas y los conjuntos de herramientas generan 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 una resolución dinámica de herramientas.
Composición de conjuntos de herramientas
Usa 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 de herramientas en una sola respuesta, puedes 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 una en el orden de LLMParallel: todas las herramientas se ejecutan simultáneamente; esta sustitución explícita omite los metadatos de seguridad, por lo que la responsabilidad de la seguridad recae en quien realiza la llamadaAuto: las llamadas cuyas herramientas son de solo lectura y seguras para la concurrencia se ejecutan primero de forma simultánea; después, todas las llamadas restantes se ejecutan secuencialmente
Los resultados siempre se devuelven en el orden original de LLM, independientemente de la estrategia. Las herramientas que fallan producen una respuesta de error JSON sin cancelar el lote.
La estrategia se establece por agente mediante LlmAgentBuilder::tool_execution_strategy(). Si no se establece, el valor predeterminado es Sequential.
Resiliencia de las herramientas
Configure presupuestos de reintentos y disyuntores para agentes en 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 las herramientas pueden inspeccionar los 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 y búsqueda) y cuya salida se guarda 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-3.7-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 estas indicaciones:
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 de bucle
- Sistemas multiagente - Creación de jerarquías de agentes
- Herramientas de función - Creación de herramientas personalizadas
- Devoluciones de llamada - Interceptación del comportamiento de los agentes
Anterior: Inicio rápido | Siguiente: Agentes de flujo de trabajo →