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 :
- Streaming : Les réponses peuvent être diffusées jeton par jeton pour une meilleure UX
- Appels d'outils : Plusieurs appels d'outils et réponses se produisent pendant l'exécution
- Changements d'état : Les mises à jour d'état sont émises sous forme d'événements
- 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 deserde_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éfixe | Portée | Persistance | Cas d'utilisation |
|---|---|---|---|
user: | Niveau utilisateur | À travers toutes les sessions | Préférences utilisateur, paramètres |
app: | Niveau application | À l'échelle de l'application | Configuration partagée |
temp: | Niveau tour | Effacé à chaque tour | Données de calcul temporaires |
| (none) | Niveau session | Cette session uniquement | Contexte 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 →