LlmAgent
Le LlmAgent est le type d’agent principal dans ADK-Rust qui utilise un modèle de langage de grande taille pour le raisonnement et la prise de décision.
Démarrage rapide
Créez un nouveau projet :
cargo new llm_agent
cd llm_agent
Ajoutez des dépendances à Cargo.toml :
[dependencies]
adk-rust = "2.0.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
Créez .env avec votre clé API :
echo 'GOOGLE_API_KEY=your-api-key' > .env
Remplacez 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(())
}
Exécutez-le :
cargo run
Interagir avec votre agent
Vous verrez une invite interactive :
🤖 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!
Façonner le comportement de l’agent avec des instructions
La méthode instruction() définit la personnalité et le comportement de votre agent. C’est le prompt système qui guide chaque réponse :
// 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()?;
Exemples de sorties
Invite utilisateur : "What is Rust?"
Assistant professionnel formel :
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.
Tuteur de code convivial :
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?
Conteur créatif :
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...
Modélisation des instructions
Les instructions prennent en charge l’injection de variables à l’aide de la syntaxe {var}. Les variables sont résolues à partir de l’état de session au moment de l’exécution :
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()?;
Guide étape par étape pour utiliser la modélisation :
- Créez l’agent avec des variables de modèle dans l’instruction
- Configurez Runner et SessionService pour gérer l’état
- Créez une session avec des variables d’état qui correspondent à votre modèle
- Exécutez l’agent - les modèles sont remplacés automatiquement
Voici un exemple complet fonctionnel :
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(())
}
Types de variables de modèle :
| Motif | Exemple | Source |
|---|---|---|
{var} | {user_name} | État de session |
{prefix:var} | {user:name}, {app:config} | État préfixé |
{var?} | {user_name?} | Optionnel (vide si manquant) |
{artifact.file} | {artifact.resume.pdf} | Contenu de l'artefact |
Exemple de sortie :
Modèle : "You are helping {user_name}. Their role is {user_role}."
Devient : "You are helping Alice. Their role is Senior Developer."
L’agent répondra alors avec un contenu personnalisé basé sur le nom et le niveau d’expertise de l’utilisateur !
Ajout d’outils
Les outils donnent à votre agent des capacités au-delà de la conversation : ils peuvent récupérer des données, effectuer des calculs, rechercher sur le web ou appeler des APIs externes. Le LLM décide quand utiliser un outil en fonction de la demande de l’utilisateur.
Comment fonctionnent les outils
- L’agent reçoit le message de l’utilisateur → "Quel temps fait-il à Tokyo ?"
- LLM décide d’appeler l’outil → Sélectionne
get_weatheravec{"city": "Tokyo"} - L’outil s’exécute → Renvoie
{"temperature": "22°C", "condition": "sunny"} - LLM formate la réponse → "Le temps à Tokyo est ensoleillé avec 22°C."
Créer un outil avec FunctionTool
FunctionTool est la manière la plus simple de créer un outil — enveloppez n’importe quelle fonction Rust asynchrone et le LLM peut l’appeler. Vous fournissez un nom, une description et une fonction de gestion qui reçoit les arguments JSON et renvoie un résultat 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
},
);
Les outils natifs intégrés au fournisseur peuvent désormais être mélangés avec des instances FunctionTool dans le même agent. ADK transmet les déclarations d’outils natifs au fournisseur tout en exécutant localement les outils de fonction ordinaires.
Créer un agent multi-outils
Créez un nouveau projet :
cargo new tool_agent
cd tool_agent
Ajoutez des dépendances à Cargo.toml :
[dependencies]
adk-rust = { version = "2.0.0", features = ["tools"] }
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
Créez .env :
echo 'GOOGLE_API_KEY=your-api-key' > .env
Remplacez src/main.rs par un agent qui a trois outils :
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(())
}
Exécutez votre agent :
cargo run
Exemple d’interaction
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!
Sortie structurée avec le schéma JSON
Pour les applications qui ont besoin de données structurées, utilisez 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(())
}
Comment les fournisseurs imposent le schéma
output_schema atteint le fournisseur en tant que GenerateContentConfig::response_schema.
La manière dont le fournisseur l’utilise diffère, et l’agent valide le résultat dans tous les cas :
| Fournisseur | Application native |
|---|---|
| Gemini | Schéma complet, envoyé comme schéma de réponse |
| OpenAI et OpenAI-compatibles | Schéma complet, envoyé comme un format de réponse strict json_schema |
| OpenRouter | Schéma complet |
| DeepSeek | JSON syntaxe uniquement — le mode de sortie de DeepSeek's JSON n'a aucune variante json_schema, donc le schéma est imposé par la validation de l'agent |
Lorsqu’un fournisseur impose uniquement la syntaxe, ou rien du tout, l’agent injecte quand même le schéma comme instruction et valide la réponse, de sorte qu’une réponse non conforme entraîne une nouvelle tentative plutôt que le retour de données incorrectes.
Note : DeepSeek exige que le mot "json" apparaisse dans le prompt chaque fois que JSON l’Output est activé, sinon le API peut renvoyer un contenu vide. L’adaptateur ajoute lui-même cette mention lorsque votre prompt ne la contient pas déjà.
Exemple de sortie de JSON
Entrée : "John a rencontré Sarah à Paris le 25 décembre"
Sortie :
{
"people": ["John", "Sarah"],
"locations": ["Paris"],
"dates": ["December 25th"]
}
Fonctionnalités avancées
Inclure le contenu
Contrôler la visibilité de l’historique de conversation :
// Full history (default)
.include_contents(IncludeContents::Default)
// Stateless - sees only injected instructions plus the current user turn
.include_contents(IncludeContents::None)
Clé de sortie
Enregistrer les réponses de l’agent dans l’état de session :
.output_key("summary") // Response saved to state["summary"]
Instructions dynamiques
Calculer les instructions à l’exécution :
.instruction_provider(|ctx| {
Box::pin(async move {
let user_id = ctx.user_id();
Ok(format!("You are assisting user {}.", user_id))
})
})
Rappels
Intercepter le comportement de l’agent :
.before_model_callback(|ctx, request| {
Box::pin(async move {
println!("About to call LLM with {} messages", request.contents.len());
Ok(BeforeModelResult::Continue)
})
})
Référence du Builder
| Méthode | Description |
|---|---|
new(name) | Crée le générateur avec le nom de l'agent |
model(Arc<dyn Llm>) | Définit le LLM (requis) |
description(text) | Description de l’agent |
instruction(text) | Prompt système |
tool(Arc<dyn Tool>) | Ajoute un outil statique |
toolset(Arc<dyn Toolset>) | Ajoute un ensemble d’outils dynamiques résolu à chaque invocation |
output_schema(json) | Schéma JSON pour une sortie structurée |
output_key(key) | Enregistre la réponse dans l'état |
include_contents(mode) | Visibilité de l'historique |
max_iterations(n) | Nombre maximal d'allers-retours LLM (par défaut : 100) |
tool_execution_strategy(strategy) | Mode de dispatch des outils : Sequential, Parallel ou Auto |
default_retry_budget(RetryBudget) | Réessayer les outils en échec jusqu’à N fois avec délai |
tool_retry_budget(name, RetryBudget) | Remplacement de nouvelle tentative par outil |
circuit_breaker_threshold(u32) | Désactiver l’outil après N échecs consécutifs |
on_tool_error(callback) | Enregistrer un gestionnaire de repli pour les échecs d’outil |
after_tool_callback_full(callback) | Rappel enrichi après l’outil V2 avec l’outil, les arguments et la réponse |
build() | Crée l’agent |
Contrôle des itérations
La méthode max_iterations() limite le nombre d’allers-retours LLM qu’un agent peut effectuer avant de s’arrêter. C’est utile pour :
- Prévenir les boucles incontrôlées d’appels d’outils
- Contrôler les coûts en production
- Définir des limites raisonnables pour des tâches complexes
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()?;
La valeur par défaut est de 100 itérations, ce qui suffit pour la plupart des cas d’usage. Des valeurs plus faibles (5-20) sont recommandées pour les agents de questions-réponses simples, tandis que des valeurs plus élevées peuvent être nécessaires pour des tâches de raisonnement multi-étapes complexes.
Ensembles d’outils dynamiques
Pour les outils qui dépendent du contexte d’invocation (par exemple, des sessions de navigateur par utilisateur), utilisez .toolset() au lieu de .tool(). Les ensembles d’outils sont résolus au début de chaque appel 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()?;
Vous pouvez mélanger des .tool() statiques et des .toolset() dynamiques sur le même agent. Les noms d’outils dupliqués entre les outils statiques et les ensembles d’outils produisent une erreur déterministe.
RealtimeAgentBuilder prend également en charge .toolset() avec la même sémantique, donc les agents vocaux en temps réel bénéficient eux aussi d’une résolution dynamique des outils.
Composition d’ensembles d’outils
Utilisez FilteredToolset, MergedToolset et PrefixedToolset depuis adk-tool pour composer des configurations d’ensembles d’outils complexes :
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()?;
Tous les utilitaires de composition fonctionnent avec n’importe quelle implémentation de Toolset, y compris McpToolset et BrowserToolset.
Exécution parallèle des outils
Lorsqu’un LLM renvoie plusieurs appels d’outils dans une seule réponse, vous pouvez contrôler la manière dont ils sont dispatchés :
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()?;
Trois stratégies sont disponibles :
Sequential(par défaut) — les outils s’exécutent un par un dans l’ordre LLMParallel— tous les outils s’exécutent concurremment ; cette surcharge explicite contourne les métadonnées de sécurité, donc la responsabilité de la sécurité revient à l’appelantAuto— les appels dont les outils sont à la fois en lecture seule et sûrs pour la concurrence s’exécutent d’abord en parallèle ; tous les appels restants s’exécutent ensuite séquentiellement
Les résultats sont toujours renvoyés dans l’ordre LLM d’origine, quelle que soit la stratégie. Les outils en échec produisent une réponse d’erreur JSON sans interrompre le lot.
La stratégie est définie par agent via LlmAgentBuilder::tool_execution_strategy(). Si elle n’est pas définie, la valeur par défaut est Sequential.
Résilience des outils
Configurez les budgets de relance et les coupe-circuits pour les agents de production :
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()?;
Les callbacks après outil peuvent inspecter les métadonnées structurées ToolOutcome via 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)
})
}))
Exemple complet
Un agent prêt pour la production avec plusieurs outils (météo, calculatrice, recherche) et une sortie enregistrée dans l’état de session :
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(())
}
Essayez ces 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.
Liés
- Agents de workflow - Agents séquentiels, parallèles et en boucle
- Systèmes multi-agents - Construction de hiérarchies d’agents
- Outils de fonction - Création d’outils personnalisés
- Callbacks - Intercepter le comportement de l’agent
Précédent : Démarrage rapide | Suivant : Agents de workflow →