Types Fondamentaux

Types et traits fondamentaux de adk-core qui constituent la base de ADK-Rust.

Content et Part

Chaque message dans ADK transite par Content et Part. Comprendre ces types est essentiel pour travailler avec les agents, les outils et les callbacks.

Content

Content représente un unique message dans une conversation. Il possède un role (qui l'a envoyé) et une ou plusieurs parts (le contenu réel).

Rôles :

  • "user" - Messages de l'utilisateur
  • "model" - Réponses du modèle d'IA
  • "tool" - Résultats de l'exécution d'outils
use adk_core::Content;

// Simple message texte de l'utilisateur
let user_msg = Content::new("user")
    .with_text("What's the weather like?");

// Réponse du modèle
let model_msg = Content::new("model")
    .with_text("The weather is sunny and 72°F.");

// Plusieurs parties de texte dans un seul message
let detailed_msg = Content::new("user")
    .with_text("Here's my question:")
    .with_text("What is the capital of France?");

Contenu Multimodal :

Un Content peut inclure des images, de l'audio, des PDF et d'autres données binaires en plus du texte :

// Image à partir d'octets (ex: lue depuis un fichier)
let image_bytes = std::fs::read("photo.jpg")?;
let content = Content::new("user")
    .with_text("What's in this image?")
    .with_inline_data("image/jpeg", image_bytes);

// Image à partir d'une URL (le modèle la récupère)
let content = Content::new("user")
    .with_text("Describe this image")
    .with_file_uri("image/png", "https://example.com/chart.png");

// Document PDF
let pdf_bytes = std::fs::read("report.pdf")?;
let content = Content::new("user")
    .with_text("Summarize this document")
    .with_inline_data("application/pdf", pdf_bytes);

Part

Part est une énumération représentant différents types de contenu au sein d'un message :

pub enum Part {
    // Texte brut
    Text { text: String },
    
    // Données binaires intégrées dans le message
    InlineData { mime_type: String, data: Vec<u8> },
    
    // Référence à un fichier externe (URL ou stockage cloud)
    FileData { mime_type: String, file_uri: String },
    
    // Le modèle demande un appel d'outil
    FunctionCall { name: String, args: Value, id: Option<String> },
    
    // Résultat de l'exécution d'un outil
    FunctionResponse { function_response: FunctionResponseData, id: Option<String> },
}

Création de Parts Directement :

use adk_core::Part;

// Partie texte
let text = Part::text_part("Hello, world!");

// Image à partir d'octets
let image = Part::inline_data("image/png", png_bytes);

// Image à partir d'une URL
let remote_image = Part::file_data("image/jpeg", "https://example.com/photo.jpg");

Inspection des Parts :

// Obtenir le contenu texte (renvoie None pour les parts non textuelles)
if let Some(text) = part.text() {
    println!("Text: {}", text);
}

// Obtenir le type MIME (pour InlineData et FileData)
if let Some(mime) = part.mime_type() {
    println!("MIME type: {}", mime);
}

// Obtenir l'URI du fichier (pour FileData seulement)
if let Some(uri) = part.file_uri() {
    println!("File URI: {}", uri);
}

// Vérifier si la part contient des médias (image, audio, vidéo, etc.)
if part.is_media() {
    println!("This part contains binary media");
}

Itération sur les Parts :

for part in &content.parts {
    match part {
        Part::Text { text } => println!("Text: {}", text),
        Part::InlineData { mime_type, data } => {
            println!("Binary data: {} ({} bytes)", mime_type, data.len());
        }
        Part::FileData { mime_type, file_uri } => {
            println!("File: {} at {}", mime_type, file_uri);
        }
        Part::FunctionCall { name, args, .. } => {
            println!("Tool call: {}({})", name, args);
        }
        Part::FunctionResponse { function_response, .. } => {
            println!("Tool result: {} -> {}", 
                function_response.name, 
                function_response.response
            );
        }
    }
}

Trait Agent

Le trait Agent est l'abstraction principale pour tous les agents dans ADK. Chaque type d'agent — LlmAgent, agents de workflow, agents personnalisés — implémente ce trait.

#[async_trait]
pub trait Agent: Send + Sync {
    /// Identifiant unique pour cet agent
    fn name(&self) -> &str;
    
    /// Description lisible par un humain de ce que fait cet agent
    fn description(&self) -> &str;
    
    /// Agents enfants (pour les agents de workflow comme SequentialAgent, ParallelAgent)
    fn sub_agents(&self) -> &[Arc<dyn Agent>];
    
    /// Exécute l'agent et retourne un flux d'événements
    async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}

Points Clés :

  • name() : Utilisé pour la journalisation, les transferts et l'identification. Doit être unique au sein d'un système multi-agents.
  • description() : Affiché aux LlmAgent lorsque l'agent est utilisé comme FunctionTool ou pour les décisions de routage.
  • sub_agents() : Retourne les agents enfants. Vide pour les agents feuilles (LlmAgent), peuplé pour les conteneurs (SequentialAgent, ParallelAgent).
  • run() : La méthode d'exécution principale. Reçoit le contexte et retourne un flux d'événements.

Pourquoi EventStream ?

Les agents retournent un EventStream (un flux de Result<Event>) plutôt qu'une seule réponse car :

  1. Streaming : Les réponses peuvent être diffusées jeton par jeton pour une meilleure UX
  2. Appels d'outils : Plusieurs appels d'outils et réponses se produisent pendant l'exécution
  3. Changements d'état : Les mises à jour d'état sont émises sous forme d'événements
  4. Transferts : Les transferts d'agents sont signalés par des événements

Trait Tool

Les outils étendent les capacités des agents au-delà de la conversation. Ils permettent aux agents d'interagir avec des API, des bases de données, des systèmes de fichiers ou d'effectuer des calculs.

#[async_trait]
pub trait Tool: Send + Sync {
    /// Tool name (used in function calls from the model)
    fn name(&self) -> &str;
    
    /// Description shown to the LLM to help it decide when to use this tool
    fn description(&self) -> &str;
    
    /// JSON Schema defining the expected parameters
    fn parameters_schema(&self) -> Option<Value> { None }
    
    /// JSON Schema defining the response format
    fn response_schema(&self) -> Option<Value> { None }
    
    /// Whether this tool runs asynchronously (returns task ID immediately)
    fn is_long_running(&self) -> bool { false }
    
    /// Execute the tool with given arguments
    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
}

Points Clés :

  • name(): Le nom de la fonction que le modèle utilise pour appeler cet outil. Gardez-le court et descriptif (par exemple, get_weather, search_database).
  • description(): Critique pour que le modèle comprenne quand utiliser l'outil. Soyez précis sur ce qu'il fait et quand l'utiliser.
  • parameters_schema(): Schéma JSON qui indique au modèle quels arguments fournir. Sans cela, le modèle devine.
  • execute(): Reçoit les arguments parsés sous forme de serde_json::Value. Renvoie le résultat au format JSON.

Implémenter un outil personnalisé :

use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::{json, Value};
use std::sync::Arc;

struct WeatherTool {
    api_key: String,
}

#[async_trait]
impl Tool for WeatherTool {
    fn name(&self) -> &str { 
        "get_weather" 
    }
    
    fn description(&self) -> &str { 
        "Get current weather for a city. Use this when the user asks about weather conditions."
    }
    
    fn parameters_schema(&self) -> Option<Value> {
        Some(json!({
            "type": "object",
            "properties": {
                "city": { 
                    "type": "string",
                    "description": "City name (e.g., 'London', 'New York')"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature units"
                }
            },
            "required": ["city"]
        }))
    }
    
    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
        let city = args["city"].as_str().unwrap_or("Unknown");
        let units = args["units"].as_str().unwrap_or("celsius");
        
        // Call weather API here...
        
        Ok(json!({
            "city": city,
            "temperature": 22,
            "units": units,
            "condition": "sunny"
        }))
    }
}

Outils à exécution longue :

Pour les opérations qui prennent beaucoup de temps (traitement de fichiers, appels d'API externes), marquez l'outil comme étant à exécution longue :

fn is_long_running(&self) -> bool { true }

Les outils à exécution longue renvoient immédiatement un ID de tâche. Le modèle reçoit l'instruction de ne pas appeler à nouveau l'outil tant qu'il est en attente.


Trait Toolset

Toolset fournit des outils dynamiquement en fonction du contexte. Utilisez ceci lorsque :

  • Les outils dépendent des permissions utilisateur
  • Les outils sont chargés de sources externes (serveurs MCP)
  • La disponibilité des outils change pendant l'exécution
#[async_trait]
pub trait Toolset: Send + Sync {
    /// Toolset identifier
    fn name(&self) -> &str;
    
    /// Return available tools for the current context
    async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
}

Exemple : Toolset Basé sur les Permissions :

struct AdminToolset {
    admin_tools: Vec<Arc<dyn Tool>>,
    user_tools: Vec<Arc<dyn Tool>>,
}

#[async_trait]
impl Toolset for AdminToolset {
    fn name(&self) -> &str { "admin_toolset" }
    
    async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
        if ctx.user_id().starts_with("admin_") {
            Ok(self.admin_tools.clone())
        } else {
            Ok(self.user_tools.clone())
        }
    }
}

Traits de Contexte

Les Contexts fournissent des informations et des services aux agents et aux outils pendant l'exécution. Il existe une hiérarchie de traits de contexte, chacun ajoutant plus de capacités.

ReadonlyContext

Informations de base disponibles partout :

pub trait ReadonlyContext: Send + Sync {
    /// Unique ID for this invocation
    fn invocation_id(&self) -> &str;
    
    /// Name of the currently executing agent
    fn agent_name(&self) -> &str;
    
    /// User identifier
    fn user_id(&self) -> &str;
    
    /// Application name
    fn app_name(&self) -> &str;
    
    /// Session identifier
    fn session_id(&self) -> &str;
    
    /// The user's input that triggered this invocation
    fn user_content(&self) -> &Content;
}

CallbackContext

Ajoute l'accès aux artifacts (étend ReadonlyContext) :

pub trait CallbackContext: ReadonlyContext {
    /// Access to artifact storage (if configured)
    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}

ToolContext

Pour l'exécution de Tool (étend CallbackContext) :

pub trait ToolContext: CallbackContext {
    /// ID of the function call that triggered this tool
    fn function_call_id(&self) -> &str;
    
    /// Get current event actions (transfer, escalate, etc.)
    fn actions(&self) -> EventActions;
    
    /// Set event actions (e.g., trigger a transfer)
    fn set_actions(&self, actions: EventActions);
    
    /// Search long-term memory
    async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}

InvocationContext

Contexte complet pour l'exécution d'agent (étend CallbackContext) :

pub trait InvocationContext: CallbackContext {
    /// The agent being executed
    fn agent(&self) -> Arc<dyn Agent>;
    
    /// Memory service (if configured)
    fn memory(&self) -> Option<Arc<dyn Memory>>;
    
    /// Current session with state and history
    fn session(&self) -> &dyn Session;
    
    /// Execution configuration
    fn run_config(&self) -> &RunConfig;
    
    /// Signal that this invocation should end
    fn end_invocation(&self);
    
    /// Check if invocation has been ended
    fn ended(&self) -> bool;
}

Session et État

Les sessions suivent les conversations. L'état stocke les données au sein des sessions.

Session

pub trait Session: Send + Sync {
    /// Unique session identifier
    fn id(&self) -> &str;
    
    /// Application this session belongs to
    fn app_name(&self) -> &str;
    
    /// User who owns this session
    fn user_id(&self) -> &str;
    
    /// Mutable state storage
    fn state(&self) -> &dyn State;
    
    /// Previous messages in this conversation
    fn conversation_history(&self) -> Vec<Content>;
}

État

Stockage clé-valeur avec des préfixes de portée :

pub trait State: Send + Sync {
    /// Get a value by key
    fn get(&self, key: &str) -> Option<Value>;
    
    /// Set a value
    fn set(&mut self, key: String, value: Value);
    
    /// Get all key-value pairs
    fn all(&self) -> HashMap<String, Value>;
}

Préfixes d'État :

Les clés d'état utilisent des préfixes pour contrôler la portée et la persistance :

PréfixePortéePersistanceCas d'utilisation
user:Niveau utilisateurÀ travers toutes les sessionsPréférences utilisateur, paramètres
app:Niveau applicationÀ l'échelle de l'applicationConfiguration partagée
temp:Niveau tourEffacé à chaque tourDonnées de calcul temporaires
(none)Niveau sessionCette session uniquementContexte de conversation
// Dans un rappel ou un outil
let state = ctx.session().state();

// Préférence utilisateur (persiste à travers les sessions)
state.set("user:theme".into(), json!("dark"));

// Données spécifiques à la session
state.set("current_topic".into(), json!("weather"));

// Données temporaires (effacées après ce tour)
state.set("temp:step_count".into(), json!(1));

// Lire les valeurs
if let Some(theme) = state.get("user:theme") {
    println!("Theme: {}", theme);
}

Gestion des Erreurs

ADK utilise un type d'erreur unifié pour toutes les opérations :

pub enum AdkError {
    Agent(String),      // Erreurs d'exécution d'agent
    Tool(String),       // Erreurs d'exécution d'outil
    Model(String),      // Erreurs d'API LLM
    Session(String),    // Erreurs de stockage de session
    Artifact(String),   // Erreurs de stockage d'artefacts
    Config(String),     // Erreurs de configuration
    Io(std::io::Error), // Erreurs d'E/S fichier/réseau
    Json(serde_json::Error), // Erreurs d'analyse JSON
}

pub type Result<T> = std::result::Result<T, AdkError>;

Gestion des Erreurs dans les Outils :

async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
    let city = args["city"]
        .as_str()
        .ok_or_else(|| AdkError::Tool("Missing 'city' parameter".into()))?;
    
    let response = reqwest::get(&format!("https://api.weather.com/{}", city))
        .await
        .map_err(|e| AdkError::Tool(format!("API error: {}", e)))?;
    
    Ok(json!({ "weather": "sunny" }))
}

Flux d'Événements

Les agents renvoient un flux d'événements plutôt qu'une seule réponse :

pub type EventStream = Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;

Traitement des Événements :

use futures::StreamExt;

let mut stream = agent.run(ctx).await?;

while let Some(result) = stream.next().await {
    match result {
        Ok(event) => {
            // Vérifier le contenu textuel
            if let Some(content) = event.content() {
                for part in &content.parts {
                    if let Some(text) = part.text() {
                        print!("{}", text);
                    }
                }
            }
            
            // Vérifier les changements d'état
            for (key, value) in event.state_delta() {
                println!("State changed: {} = {}", key, value);
            }
            
            // Vérifier si c'est la réponse finale
            if event.is_final_response() {
                println!("\n[Done]");
            }
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            break;
        }
    }
}

Trait Llm

Le trait que tous les fournisseurs de LLM implémentent :

#[async_trait]
pub trait Llm: Send + Sync {
    /// Model identifier (e.g., "gemini-2.0-flash", "gpt-4o")
    fn name(&self) -> &str;
    
    /// Generate content (streaming or non-streaming)
    async fn generate_content(
        &self,
        request: LlmRequest,
        stream: bool,
    ) -> Result<LlmResponseStream>;
}

LlmRequest :

pub struct LlmRequest {
    pub contents: Vec<Content>,           // Historique de la conversation
    pub tools: Vec<ToolDeclaration>,      // Outils disponibles
    pub system_instruction: Option<String>, // Invite système
    pub config: GenerateContentConfig,    // Temperature, max_tokens, etc.
}

LlmResponse :

pub struct LlmResponse {
    pub content: Option<Content>,         // Contenu généré
    pub finish_reason: Option<FinishReason>, // Raison de l'arrêt de la génération
    pub usage: Option<UsageMetadata>,     // Comptes de tokens
    pub partial: bool,                    // S'agit-il d'un morceau en streaming ?
    pub turn_complete: bool,              // Le tour est-il terminé ?
}

Tous les fournisseurs (Gemini, OpenAI, Anthropic, Ollama, etc.) implémentent ce trait, ce qui les rend interchangeables :

// Changer de fournisseur en modifiant une ligne
let model: Arc<dyn Llm> = Arc::new(GeminiModel::new(&key, "gemini-2.0-flash")?);
// let model: Arc<dyn Llm> = Arc::new(OpenAIClient::new(config)?);
// let model: Arc<dyn Llm> = Arc::new(AnthropicClient::new(config)?);

let agent = LlmAgentBuilder::new("assistant")
    .model(model)
    .build()?;

Précédent : ← Introduction | Suivant : Runner →