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 :

  1. Créez l’agent avec des variables de modèle dans l’instruction
  2. Configurez Runner et SessionService pour gérer l’état
  3. Créez une session avec des variables d’état qui correspondent à votre modèle
  4. 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 :

MotifExempleSource
{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

  1. L’agent reçoit le message de l’utilisateur → "Quel temps fait-il à Tokyo ?"
  2. LLM décide d’appeler l’outil → Sélectionne get_weather avec {"city": "Tokyo"}
  3. L’outil s’exécute → Renvoie {"temperature": "22°C", "condition": "sunny"}
  4. 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 :

FournisseurApplication native
GeminiSchéma complet, envoyé comme schéma de réponse
OpenAI et OpenAI-compatiblesSchéma complet, envoyé comme un format de réponse strict json_schema
OpenRouterSchéma complet
DeepSeekJSON 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éthodeDescription
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 LLM
  • Parallel — 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’appelant
  • Auto — 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.


Précédent : Démarrage rapide | Suivant : Agents de workflow →

LlmAgent - Documentation ADK-Rust | ADK-Rust