Outils de fonction

Étendez les capacitĂ©s des agents avec des fonctions Rust personnalisĂ©es.


Que sont les outils de fonction ?

Les outils de fonction vous permettent de donner aux agents des capacitĂ©s au-delĂ  de la conversation : appeler APIs, effectuer des calculs, accĂ©der Ă  des bases de donnĂ©es ou exĂ©cuter toute logique personnalisĂ©e. Le LLM dĂ©cide quand utiliser un outil en fonction de la demande de l’utilisateur.

Points clés :

  • 🚀 macro #[tool] - enregistrement d’outil sans boilerplate (recommandĂ©)
  • 🔧 FunctionTool::new() - envelopper manuellement n’importe quelle fonction async
  • 📝 paramĂštres JSON - entrĂ©e/sortie flexibles
  • 🎯 schĂ©mas typĂ©s en toute sĂ©curitĂ© - JSON Schema automatique Ă  partir des types via schemars
  • 🔗 AccĂšs au contexte - Ă©tat de session, artefacts, mĂ©moire

Pipeline d’exĂ©cution des outils

Rendering architecture


La façon la plus rapide de créer des outils. La macro lit votre commentaire de documentation comme description et dérive le schéma JSON à partir du type de vos arguments :

use adk_tool::tool;
use adk_core::AdkError;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::{json, Value};

#[derive(Deserialize, JsonSchema)]
struct WeatherArgs {
    /// The city to look up
    city: String,
    /// Temperature unit (celsius or fahrenheit)
    unit: Option<String>,
}

/// Get the current weather for a city.
#[tool]
async fn get_weather(args: WeatherArgs) -> Result<Value, AdkError> {
    Ok(json!({ "temp": 22, "city": args.city }))
}

// Generated: pub struct GetWeather; — implements adk_core::Tool
// Use it: agent_builder.tool(Arc::new(GetWeather))

Si votre outil a besoin du contexte de session, ajoutez Arc<dyn ToolContext> comme premier paramĂštre :

use adk_core::ToolContext;
use std::sync::Arc;

/// Search the user's saved documents.
#[tool]
async fn search_docs(
    ctx: Arc<dyn ToolContext>,
    args: SearchArgs,
) -> Result<Value, AdkError> {
    let user_id = ctx.user_id();
    // ... use context for scoped access
}

Attributs de métadonnées des outils

Marquez les outils comme lecture seule, sûrs pour la concurrence ou de longue durée directement dans la macro :

/// Look up cached data — no side effects, safe for parallel dispatch.
#[tool(read_only, concurrency_safe)]
async fn cache_lookup(args: LookupArgs) -> Result<Value, AdkError> {
    Ok(json!({"result": "cached"}))
}

/// Start a long-running background report.
#[tool(long_running)]
async fn generate_report(args: ReportArgs) -> Result<Value, AdkError> {
    Ok(json!({"task_id": "abc123", "status": "processing"}))
}

Attributs disponibles (tous facultatifs, combinables librement) :

AttributEffet
read_onlyis_read_only() → true — l’un des deux signaux requis pour l’envoi concurrent de Auto
concurrency_safeis_concurrency_safe() → true — l’un des deux signaux requis pour l’envoi concurrent de Auto
long_runningis_long_running() → true — empĂȘche LLM de rappeler un outil en attente

Plain #[tool] sans attributs conserve les valeurs par dĂ©faut (tous false), donc le code existant n’est pas à€Șà„à€°à€­à€Ÿà€”à€żà€€Ă©.


Alternative : FunctionTool::new()

Pour les outils dynamiques ou lorsque vous préférez un enregistrement explicite :

Créez un outil avec FunctionTool::new() et ajoutez toujours un schéma afin que le LLM sache quels paramÚtres transmettre :

use adk_rust::prelude::*;
use adk_rust::Launcher;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::sync::Arc;

#[derive(JsonSchema, Serialize, Deserialize)]
struct WeatherParams {
    /// The city or location to get weather for
    location: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;

    // Weather tool with proper schema
    let weather_tool = FunctionTool::new(
        "get_weather",
        "Get current weather for a location",
        |_ctx, args| async move {
            let location = args.get("location")
                .and_then(|v| v.as_str())
                .unwrap_or("unknown");
            Ok(json!({
                "location": location,
                "temperature": "22°C",
                "conditions": "sunny"
            }))
        },
    )
    .with_parameters_schema::<WeatherParams>(); // Required for LLM to call correctly!

    let agent = LlmAgentBuilder::new("weather_agent")
        .instruction("You help users check the weather. Always use the get_weather tool.")
        .model(Arc::new(model))
        .tool(Arc::new(weather_tool))
        .build()?;

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

⚠ Important : utilisez toujours .with_parameters_schema<T>() - sans cela, le LLM ne saura pas quels paramĂštres transmettre et pourra ne pas appeler l’outil.

Fonctionnement :

  1. L’utilisateur demande : "What's the weather in Tokyo?"
  2. LLM dĂ©cide d’appeler get_weather avec {"location": "Tokyo"}
  3. L’outil renvoie {"location": "Tokyo", "temperature": "22°C", "conditions": "sunny"}
  4. LLM formate la réponse : "The weather in Tokyo is sunny at 22°C."

Étape 2 : gestion des paramùtres

Extrayez les paramùtres de l’JSON args :

let order_tool = FunctionTool::new(
    "process_order",
    "Process an order. Parameters: product_id (required), quantity (required), priority (optional)",
    |_ctx, args| async move {
        // Required parameters - return error if missing
        let product_id = args.get("product_id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| adk_core::AdkError::tool("product_id is required"))?;
        
        let quantity = args.get("quantity")
            .and_then(|v| v.as_i64())
            .ok_or_else(|| adk_core::AdkError::tool("quantity is required"))?;
        
        // Optional parameter with default
        let priority = args.get("priority")
            .and_then(|v| v.as_str())
            .unwrap_or("normal");
        
        Ok(json!({
            "order_id": "ORD-12345",
            "product_id": product_id,
            "quantity": quantity,
            "priority": priority,
            "status": "confirmed"
        }))
    },
);

Étape 3 : paramĂštres typĂ©s avec schĂ©ma

Pour les outils complexes, utilisez des structs typés avec le schéma JSON :

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(JsonSchema, Serialize, Deserialize)]
struct CalculatorParams {
    /// The arithmetic operation to perform
    operation: Operation,
    /// First operand
    a: f64,
    /// Second operand
    b: f64,
}

#[derive(JsonSchema, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Operation {
    Add,
    Subtract,
    Multiply,
    Divide,
}

let calculator = FunctionTool::new(
    "calculator",
    "Perform arithmetic operations",
    |_ctx, args| async move {
        let params: CalculatorParams = serde_json::from_value(args)?;
        let result = match params.operation {
            Operation::Add => params.a + params.b,
            Operation::Subtract => params.a - params.b,
            Operation::Multiply => params.a * params.b,
            Operation::Divide if params.b != 0.0 => params.a / params.b,
            Operation::Divide => return Err(adk_core::AdkError::tool("Cannot divide by zero")),
        };
        Ok(json!({ "result": result }))
    },
)
.with_parameters_schema::<CalculatorParams>();

Le schĂ©ma est gĂ©nĂ©rĂ© automatiquement Ă  partir des types Rust Ă  l’aide de schemars.


Étape 4 : agent multi-outils

Ajoutez plusieurs outils Ă  un seul agent :

let agent = LlmAgentBuilder::new("assistant")
    .instruction("Help with calculations, conversions, and weather.")
    .model(Arc::new(model))
    .tool(Arc::new(calc_tool))
    .tool(Arc::new(convert_tool))
    .tool(Arc::new(weather_tool))
    .build()?;

Le LLM choisit automatiquement le bon outil en fonction de la demande de l’utilisateur.


Gestion des erreurs

Renvoyez les erreurs avec le composant Tool pour les Ă©checs spĂ©cifiques Ă  l’outil :

use adk_core::{AdkError, ErrorComponent, ErrorCategory};

let divide_tool = FunctionTool::new(
    "divide",
    "Divide two numbers",
    |_ctx, args| async move {
        let a = args.get("a").and_then(|v| v.as_f64())
            .ok_or_else(|| AdkError::new(
                ErrorComponent::Tool,
                ErrorCategory::InvalidInput,
                "tool.divide.missing_param",
                "Parameter 'a' is required",
            ))?;
        let b = args.get("b").and_then(|v| v.as_f64())
            .ok_or_else(|| AdkError::new(
                ErrorComponent::Tool,
                ErrorCategory::InvalidInput,
                "tool.divide.missing_param",
                "Parameter 'b' is required",
            ))?;
        
        if b == 0.0 {
            return Err(AdkError::new(
                ErrorComponent::Tool,
                ErrorCategory::InvalidInput,
                "tool.divide.division_by_zero",
                "Cannot divide by zero",
            ));
        }
        
        Ok(json!({ "result": a / b }))
    },
);

Pour une migration rapide, la forme abrégée rétrocompatible fonctionne aussi :

Err(AdkError::tool("Parameter 'a' is required"))

Les messages d’erreur sont transmis au LLM, qui peut rĂ©essayer ou demander une entrĂ©e diffĂ©rente.


Contexte de l’outil

Accédez aux informations de session via ToolContext :

#[derive(JsonSchema, Serialize, Deserialize)]
struct GreetParams {
    #[serde(default)]
    message: Option<String>,
}

let greet_tool = FunctionTool::new(
    "greet",
    "Greet the user with session info",
    |ctx, _args| async move {
        let user_id = ctx.user_id();
        let session_id = ctx.session_id();
        let agent_name = ctx.agent_name();
        Ok(json!({
            "greeting": format!("Hello, user {}!", user_id),
            "session": session_id,
            "served_by": agent_name
        }))
    },
)
.with_parameters_schema::<GreetParams>();

Contexte disponible :

  • ctx.user_id() - ID utilisateur actuel
  • ctx.session_id() - ID de session actuel
  • ctx.agent_name() - Nom de l’agent
  • ctx.artifacts() - AccĂšs au stockage des artefacts
  • ctx.search_memory(query) - Service de recherche mĂ©moire

Outils de longue durée

Pour les opérations qui prennent beaucoup de temps (traitement de données, APIs externe), utilisez le modÚle non bloquant :

  1. DĂ©marrer l’outil renvoie immĂ©diatement un task_id
  2. Le travail en arriĂšre-plan s’exĂ©cute de maniĂšre asynchrone
  3. L’outil d’état permet aux utilisateurs de vĂ©rifier la progression
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(JsonSchema, Serialize, Deserialize)]
struct ReportParams {
    topic: String,
}

#[derive(JsonSchema, Serialize, Deserialize)]
struct StatusParams {
    task_id: String,
}

// Shared task store
let tasks: Arc<RwLock<HashMap<String, TaskState>>> = Arc::new(RwLock::new(HashMap::new()));
let tasks1 = tasks.clone();
let tasks2 = tasks.clone();

// Tool 1: Start (returns immediately)
let start_tool = FunctionTool::new(
    "generate_report",
    "Start generating a report. Returns task_id immediately.",
    move |_ctx, args| {
        let tasks = tasks1.clone();
        async move {
            let topic = args.get("topic").and_then(|v| v.as_str()).unwrap_or("general").to_string();
            let task_id = format!("task_{}", rand::random::<u32>());
            
            // Store initial state
            tasks.write().await.insert(task_id.clone(), TaskState {
                status: "processing".to_string(),
                progress: 0,
                result: None,
            });

            // Spawn background work (non-blocking!)
            let tasks_bg = tasks.clone();
            let tid = task_id.clone();
            tokio::spawn(async move {
                // Simulate work...
                tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
                if let Some(t) = tasks_bg.write().await.get_mut(&tid) {
                    t.status = "completed".to_string();
                    t.result = Some("Report complete".to_string());
                }
            });

            // Return immediately with task_id
            Ok(json!({"task_id": task_id, "status": "processing"}))
        }
    },
)
.with_parameters_schema::<ReportParams>()
.with_long_running(true);  // Mark as long-running

// Tool 2: Check status
let status_tool = FunctionTool::new(
    "check_report_status",
    "Check report generation status",
    move |_ctx, args| {
        let tasks = tasks2.clone();
        async move {
            let task_id = args.get("task_id").and_then(|v| v.as_str()).unwrap_or("");
            if let Some(t) = tasks.read().await.get(task_id) {
                Ok(json!({"status": t.status, "result": t.result}))
            } else {
                Ok(json!({"error": "Task not found"}))
            }
        }
    },
)
.with_parameters_schema::<StatusParams>();

Points clés :

  • .with_long_running(true) indique Ă  l’agent que cet outil renvoie un Ă©tat en attente
  • L’outil lance le travail avec tokio::spawn() et renvoie immĂ©diatement
  • Fournissez un outil de vĂ©rification d’état afin que les utilisateurs puissent interroger la progression

Cela ajoute une note pour empĂȘcher le LLM d’appeler l’outil de façon rĂ©pĂ©tĂ©e.


Diffusion de la progression d’un outil

Les outils de longue durĂ©e peuvent envoyer une sortie intermĂ©diaire Ă  l’interface utilisateur tout en s’exĂ©cutant, afin que l’utilisateur voie en direct la stdout d’une commande shell, les logs d’un build ou les octets d’un tĂ©lĂ©chargement, au lieu d’attendre le rĂ©sultat final. Appelez ToolContext::emit_progress Ă  mesure que la sortie arrive :

use adk_core::{Result, Tool, ToolContext};
use std::sync::Arc;

#[async_trait::async_trait]
impl Tool for BuildTool {
    // ... name(), description(), parameters_schema() ...

    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: serde_json::Value) -> Result<serde_json::Value> {
        // Emit chunks as they arrive — each becomes a partial Event on the
        // agent's EventStream, the SAME stream the model's reply travels on.
        ctx.emit_progress("stdout", "Compiling project...\n").await;
        ctx.emit_progress("stdout", "Build finished in 4.2s\n").await;
        ctx.emit_progress("stderr", "warning: unused variable `x`\n").await;

        // The final return value is still the complete result the model consumes.
        Ok(serde_json::json!({ "status": "ok", "warnings": 1 }))
    }
}

La signature :

async fn emit_progress(&self, stream: &str, chunk: &str)
  • stream — un libellĂ© pour le fragment : "stdout", "stderr", ou tout autre canal personnalisĂ©.
  • chunk — le texte Ă  Ă©mettre (Ă©mettre ligne par ligne pour une sortie de type terminal).

Comment cela atteint l’interface utilisateur. Le framework relaie chaque fragment sous forme de Event partiel sur le EventStream de l’agent. Un consommateur le dĂ©tecte avec event.tool_progress_stream() et l’affiche en direct. Il n’y a pas de second canal et pas de scraping des logs — la progression, le texte du modĂšle et le rĂ©sultat final de l’outil arrivent tous sur un seul flux ordonnĂ©.

Rétrocompatible. Le emit_progress par défaut est un no-op, donc les outils et exécuteurs existants qui ne diffusent pas de flux ne sont pas affectés. Seuls les outils qui activent cette fonctionnalité émettent de la progression, et seuls les consommateurs qui vérifient tool_progress_stream() la voient.

La progression est bornĂ©e et avec pertes. Un outil peut produire des donnĂ©es plus vite qu’un client ne les consomme — un journal de compilateur, une commande shell, une boucle incontrĂŽlĂ©e — donc le framework limite ce qu’il conserve et relaie au lieu de croĂźtre sans limite :

LimiteValeurEn le dépassant
Profondeur de file par lot d’outil256 Ă©vĂ©nementsL’outil attend jusqu’à 100 ms qu’il y ait de la place, puis le bloc est supprimĂ©
Octets par bloc8 KiBLe bloc est tronqué à une limite de caractÚre
Octets par appel d'outil1 MiBLa progression restante n'est pas transmise

Lorsque la sortie est supprimĂ©e pour l’une de ces raisons, exactement un Ă©vĂ©nement de progression portant le texte [adk: tool progress truncated] est Ă©mis pour cet appel, de sorte qu’un Ă©cart est toujours visible plutĂŽt que silencieux. Un consommateur lent ralentit donc briĂšvement l’outil, mais ne peut jamais le bloquer indĂ©finiment ni Ă©puiser la mĂ©moire.

Ces limites s’appliquent uniquement Ă  la progression. Le rĂ©sultat final d’un outil n’est pas affectĂ© ; tronquez donc les grands rĂ©sultats Ă  l’intĂ©rieur de l’outil si cela compte pour vous.

Voir l’exemple streaming_bash pour une interface web complĂšte qui affiche une sortie bash en direct et des rĂ©sultats d’outil en une seule fois (read_file, grep, glob) Ă  partir d’un flux d’évĂ©nements unique. L’outil de streaming bash lui-mĂȘme se trouve dans adk-devtools.


Exemples d’exĂ©cution

cargo adk new tool_agent --template tools
cd tool_agent
cargo run

Meilleures pratiques

  1. Descriptions claires - Aidez le LLM à comprendre quand utiliser l’outil
  2. Valider les entrĂ©es - Retournez des messages d’erreur utiles pour les paramĂštres manquants
  3. Retourner des JSON structurés - Utilisez des noms de champs clairs
  4. Garder les outils ciblés - Chaque outil doit faire une seule chose, et bien la faire
  5. Utiliser des schémas - Pour les outils complexes, définissez des schémas de paramÚtres
  6. Marquer les outils de lecture seule sûrs - Définissez à la fois .with_read_only(true) et .with_concurrency_safe(true) afin que le dispatch Auto puisse les inclure dans son sous-ensemble concurrent

MĂ©tadonnĂ©es d’outil : lecture seule et concurrence

Marquez les outils comme lecture seule ou sĂ»rs pour la concurrence afin d’activer un dispatch plus intelligent :

// A lookup tool that performs no side effects
let lookup = FunctionTool::new("lookup", "Look up data", |_ctx, args| async move {
    Ok(json!({"result": "cached data"}))
})
.with_read_only(true)
.with_concurrency_safe(true); // Auto mode requires both signals

// A mutation tool (defaults: read_only=false, concurrency_safe=false)
let update = FunctionTool::new("update", "Update record", |_ctx, args| async move {
    Ok(json!({"updated": true}))
});

Lorsque ToolExecutionStrategy::Auto est actif, la boucle de dispatch exĂ©cute d’abord les appels en parallĂšle lorsque leurs outils sĂ©lectionnĂ©s renvoient true Ă  la fois depuis is_read_only() et is_concurrency_safe(). Elle exĂ©cute ensuite sĂ©quentiellement tous les appels restants. ToolExecutionStrategy::Parallel est une surcharge explicite qui contourne ces signaux, de sorte que son appelant assume la responsabilitĂ© de la sĂ©curitĂ© de la concurrence.


SimpleToolContext : Utiliser des outils en dehors de la boucle d’agent

Lorsque vous devez appeler un outil en dehors de la boucle d’agent (tests, mode serveur MCP, dĂ©lĂ©gation Ă  un sous-agent), utilisez SimpleToolContext au lieu d’implĂ©menter toute la hiĂ©rarchie de traits ToolContext :

use adk_tool::SimpleToolContext;
use adk_core::ToolContext;
use std::sync::Arc;

// Construct with just a caller name — all other fields get sensible defaults
let ctx = SimpleToolContext::new("my-test-harness");

// Optionally override the function call ID
let ctx = SimpleToolContext::new("my-mcp-server")
    .with_function_call_id("custom-call-id");

// Bind a session ID so session-aware tools (and MCP servers that key state by
// session) see a stable identifier instead of the empty default.
let ctx = SimpleToolContext::new("my-mcp-server")
    .with_session_id("session-42");

// Use it to execute any tool
let tool_ctx: Arc<dyn ToolContext> = Arc::new(ctx);
let result = my_tool.execute(tool_ctx, json!({"key": "value"})).await?;

Valeurs par dĂ©faut : user_id() → "anonymous", session_id() / branch() → "", artifacts() → None, search_memory() → vecteur vide. invocation_id et function_call_id sont tous deux gĂ©nĂ©rĂ©s automatiquement UUIDs. DĂ©finissez une vĂ©ritable session avec with_session_id(...) lorsqu’un outil route ou persiste l’état par session.


StatefulTool : État partagĂ© entre les invocations

Pour les outils qui doivent conserver un état entre les appels (compteurs, caches, pools de connexions), utilisez StatefulTool<S> :

use adk_tool::StatefulTool;
use adk_core::ToolContext;
use std::sync::Arc;
use tokio::sync::RwLock;

struct AppCache {
    entries: RwLock<HashMap<String, String>>,
}

let cache = Arc::new(AppCache {
    entries: RwLock::new(HashMap::new()),
});

let cache_tool = StatefulTool::new(
    "cache_lookup",
    "Look up a value in the application cache",
    cache.clone(),
    |state, _ctx, args| async move {
        let key = args["key"].as_str().unwrap_or("");
        let entries = state.entries.read().await;
        let value = entries.get(key).cloned().unwrap_or_default();
        Ok(json!({"key": key, "value": value}))
    },
)
.with_read_only(true)
.with_concurrency_safe(true);

StatefulTool clone le Arc<S> Ă  chaque invocation (augmentation peu coĂ»teuse du compteur de rĂ©fĂ©rences), de sorte que toutes les exĂ©cutions partagent le mĂȘme Ă©tat sous-jacent. Il prend en charge les mĂȘmes mĂ©thodes de builder que FunctionTool : with_long_running, with_parameters_schema, with_response_schema, with_scopes, with_read_only, et with_concurrency_safe.



Réponses de fonction multimodales

Les modĂšles Gemini 3 prennent en charge la rĂ©ception d’images, d’audio, de PDFs et de rĂ©fĂ©rences de fichiers dans les rĂ©ponses de fonction — pas seulement de JSON. Les outils peuvent renvoyer des donnĂ©es multimodales en incluant des tableaux inline_data et/ou file_data dans leur valeur de retour JSON :

/// Tool that returns a chart image alongside JSON metadata.
async fn generate_chart(
    _ctx: Arc<dyn ToolContext>,
    args: serde_json::Value,
) -> Result<serde_json::Value> {
    let png_bytes: Vec<u8> = render_chart(&args);

    // Include inline_data in the return value — the framework extracts it automatically
    Ok(json!({
        "response": {
            "title": "Q4 Sales",
            "chart_type": "bar"
        },
        "inline_data": [{
            "mime_type": "image/png",
            "data": png_bytes
        }]
    }))
}

Le framework détecte automatiquement :

  1. les inline_data/file_data via FunctionResponseData::from_tool_result()
  2. les données binaires inline encodées en Base64
  3. les parties imbriquĂ©es Ă  l’intĂ©rieur de l’objet filaire functionResponse (correspondant au format Gemini 3 API)

Références de fichiers

Pour les grands fichiers stockĂ©s Ă  l’extĂ©rieur, utilisez file_data avec un URI au lieu d’intĂ©grer les octets :

Ok(json!({
    "response": { "document_id": "report-2024", "pages": 12 },
    "file_data": [{
        "mime_type": "application/pdf",
        "file_uri": "gs://my-bucket/reports/report-2024.pdf"
    }]
}))

Construction directe

Pour le code au niveau du framework (agents personnalisés, couches de conversion), construisez directement FunctionResponseData :

use adk_core::{FunctionResponseData, InlineDataPart, FileDataPart};

// JSON + inline image
let frd = FunctionResponseData::with_inline_data(
    "chart_tool",
    json!({"title": "Q4 Chart"}),
    vec![InlineDataPart { mime_type: "image/png".into(), data: png_bytes }],
);

// JSON + file reference
let frd = FunctionResponseData::with_file_data(
    "doc_tool",
    json!({"status": "ok"}),
    vec![FileDataPart { mime_type: "application/pdf".into(), file_uri: "gs://bucket/file.pdf".into() }],
);

// JSON + both
let frd = FunctionResponseData::with_multimodal("tool", json, inline_parts, file_parts);

Remarque : les réponses de fonction multimodales nécessitent des modÚles de la série Gemini 3 (gemini-3-flash-preview, gemini-3-pro-preview). Les modÚles antérieurs renvoient une erreur 400.

Voir examples/multimodal_function_response/ pour un exemple complet et fonctionnel.


PrĂ©cĂ©dent : ← mistral.rs | Suivant : Outils intĂ©grĂ©s →

Outils de fonction - Documentation ADK-Rust | ADK-Rust