Artéfacts

Les Artéfacts permettent de stocker et de récupérer des données binaires (images, PDF, fichiers audio, etc.) au sein de vos applications d'agent. Le système d'artéfacts gère le versionnement, la portée des espaces de noms et la persistance des données entre les sessions.

Aperçu

Le système d'artéfacts dans adk-rust se compose de :

  • Part : La représentation de données principale qui peut contenir du texte ou des données binaires avec des types MIME
  • ArtifactService : Le trait définissant les opérations de stockage d'artéfacts
  • InMemoryArtifactService : Une implémentation en mémoire pour le développement et les tests
  • ScopedArtifacts : Un wrapper qui simplifie les opérations d'artéfacts en gérant automatiquement le contexte de session

Les Artéfacts sont gérés par application, utilisateur et session, offrant isolation et organisation. Les fichiers peuvent être limités à la session (par défaut) ou à l'utilisateur (en utilisant le préfixe user:).

Représentation de Part

L'énumération Part représente les données qui peuvent être stockées comme des artéfacts :

pub enum Part {
    Text { text: String },
    InlineData { mime_type: String, data: Vec<u8> },
    FunctionCall { name: String, args: serde_json::Value },
    FunctionResponse { name: String, response: serde_json::Value },
}

Pour les artéfacts, vous utiliserez principalement :

  • Part::Text pour les données textuelles
  • Part::InlineData pour les données binaires avec des types MIME

Utilisation de base

La manière la plus simple de travailler avec les artéfacts est d'utiliser le trait Artifacts, qui est disponible dans les contextes d'agent :

use adk_rust::prelude::*;

// Dans un outil d'agent ou un rappel
async fn save_report(ctx: &ToolContext) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Enregistrer des données textuelles
    let version = artifacts.save(
        "report.txt",
        &Part::Text { text: "Report content".to_string() }
    ).await?;
    
    // Enregistrer des données binaires
    let image_data = vec![0xFF, 0xD8, 0xFF]; // Entête JPEG
    artifacts.save(
        "chart.jpg",
        &Part::InlineData {
            mime_type: "image/jpeg".to_string(),
            data: image_data,
        }
    ).await?;
    
    Ok(json!({ "saved": true, "version": version }))
}

Trait ArtifactService

Le trait ArtifactService définit les opérations essentielles pour la gestion des artefacts :

#[async_trait]
pub trait ArtifactService: Send + Sync {
    async fn save(&self, req: SaveRequest) -> Result<SaveResponse>;
    async fn load(&self, req: LoadRequest) -> Result<LoadResponse>;
    async fn delete(&self, req: DeleteRequest) -> Result<()>;
    async fn list(&self, req: ListRequest) -> Result<ListResponse>;
    async fn versions(&self, req: VersionsRequest) -> Result<VersionsResponse>;
}

Opération de Sauvegarde

Sauvegarder un artefact avec un versionnement automatique ou explicite :

use adk_artifact::{InMemoryArtifactService, SaveRequest};
use adk_core::Part;

let service = InMemoryArtifactService::new();

let response = service.save(SaveRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    part: Part::InlineData {
        mime_type: "application/pdf".to_string(),
        data: pdf_bytes,
    },
    version: None, // Auto-increment version
}).await?;

println!("Saved as version: {}", response.version);

Opération de Chargement

Charger la dernière version ou une version spécifique :

use adk_artifact::LoadRequest;

// Charger la dernière version
let response = service.load(LoadRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: None, // Charger la dernière
}).await?;

// Charger une version spécifique
let response = service.load(LoadRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: Some(2), // Charger la version 2
}).await?;

match response.part {
    Part::InlineData { mime_type, data } => {
        println!("Loaded {} bytes of {}", data.len(), mime_type);
    }
    _ => {}
}

Opération de Liste

Lister tous les artefacts d'une session :

use adk_artifact::ListRequest;

let response = service.list(ListRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
}).await?;

for file_name in response.file_names {
    println!("Found artifact: {}", file_name);
}

Opération de Suppression

Supprimer une version spécifique ou toutes les versions :

use adk_artifact::DeleteRequest;

// Supprimer une version spécifique
service.delete(DeleteRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: Some(1), // Supprimer la version 1
}).await?;

// Supprimer toutes les versions
service.delete(DeleteRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: None, // Supprimer toutes les versions
}).await?;

Opération de Versions

Lister toutes les versions d'un artefact :

use adk_artifact::VersionsRequest;

let response = service.versions(VersionsRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
}).await?;

println!("Available versions: {:?}", response.versions);
// Sortie : [3, 2, 1] (triées de la plus récente à la plus ancienne)

Gestion des versions

Les artefacts prennent en charge la gestion automatique des versions :

  • Lors de la sauvegarde sans spécifier de version, le système incrémente automatiquement à partir de la dernière version
  • La version 1 est attribuée à la première sauvegarde
  • Chaque sauvegarde ultérieure incrémente le numéro de version
  • Vous pouvez charger, supprimer ou interroger des versions spécifiques
// First save - becomes version 1
let v1 = service.save(SaveRequest {
    file_name: "data.json".to_string(),
    part: Part::Text { text: "v1 data".to_string() },
    version: None,
    // ... other fields
}).await?;
assert_eq!(v1.version, 1);

// Second save - becomes version 2
let v2 = service.save(SaveRequest {
    file_name: "data.json".to_string(),
    part: Part::Text { text: "v2 data".to_string() },
    version: None,
    // ... other fields
}).await?;
assert_eq!(v2.version, 2);

// Load latest (version 2)
let latest = service.load(LoadRequest {
    file_name: "data.json".to_string(),
    version: None,
    // ... other fields
}).await?;

// Load specific version
let old = service.load(LoadRequest {
    file_name: "data.json".to_string(),
    version: Some(1),
    // ... other fields
}).await?;

Portée des espaces de noms

Les artefacts peuvent être délimités à deux niveaux :

Lié à la Session (Par Défaut)

Par défaut, les artefacts sont liés à une session spécifique. Chaque session possède son propre espace de noms d'artefacts isolé :

// Session 1
service.save(SaveRequest {
    session_id: "session_1".to_string(),
    file_name: "notes.txt".to_string(),
    // ... other fields
}).await?;

// Session 2 - different artifact with same name
service.save(SaveRequest {
    session_id: "session_2".to_string(),
    file_name: "notes.txt".to_string(),
    // ... other fields
}).await?;

// Ce sont deux artefacts distincts

Lié à l'Utilisateur

Les artefacts avec le préfixe user: sont partagés entre toutes les sessions pour un utilisateur :

// Save in session 1
service.save(SaveRequest {
    session_id: "session_1".to_string(),
    file_name: "user:profile.jpg".to_string(), // user: prefix
    // ... other fields
}).await?;

// Load in session 2 - same artifact
let profile = service.load(LoadRequest {
    session_id: "session_2".to_string(),
    file_name: "user:profile.jpg".to_string(),
    // ... other fields
}).await?;

Le préfixe user: permet :

  • Le partage de données entre plusieurs conversations
  • Des préférences utilisateur persistantes
  • La mise en cache au niveau de l'utilisateur

InMemoryArtifactService

Le InMemoryArtifactService fournit une implémentation en mémoire adaptée au développement et aux tests :

use adk_artifact::InMemoryArtifactService;
use std::sync::Arc;

let service = Arc::new(InMemoryArtifactService::new());

// Utiliser avec des agents
let agent = LlmAgentBuilder::new("my_agent")
    .model(model)
    .build()?;

// Le service peut être passé aux Runner ou utilisé directement

Remarque : Les données ne sont pas persistées sur le disque. Pour une utilisation en production, envisagez d'implémenter un ArtifactService personnalisé adossé à une base de données ou à un stockage cloud.

ScopedArtifacts

L'encapsuleur ScopedArtifacts simplifie les opérations sur les artefacts en injectant automatiquement le contexte de session :

use adk_artifact::{ScopedArtifacts, InMemoryArtifactService};
use std::sync::Arc;

let service = Arc::new(InMemoryArtifactService::new());

let artifacts = ScopedArtifacts::new(
    service,
    "my_app".to_string(),
    "user_123".to_string(),
    "session_456".to_string(),
);

// API simple - pas besoin de spécifier l'application/l'utilisateur/la session
let version = artifacts.save("file.txt", &Part::Text {
    text: "content".to_string()
}).await?;

let part = artifacts.load("file.txt").await?;
let files = artifacts.list().await?;

C'est la même interface disponible via ToolContext::artifacts() et CallbackContext::artifacts().

Modèles Courants

Analyse d'images avec des modèles multimodaux

Lorsque vous souhaitez qu'un LLM analyse une image stockée comme un artifact, vous devez utiliser un callback BeforeModel pour injecter l'image directement dans la requête LLM. Cela suit le modèle adk-go.

Pourquoi ne pas utiliser un outil ? Les réponses des outils dans les API LLM sont du texte JSON. Si un outil renvoie des données d'image (même encodées en base64), le modèle les voit comme du texte, et non comme une image réelle. Pour une véritable analyse multimodale, l'image doit être incluse comme un Part::InlineData dans le contenu de la conversation.

use adk_rust::prelude::*;
use adk_rust::artifact::{ArtifactService, InMemoryArtifactService, SaveRequest, LoadRequest};
use std::sync::Arc;

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

    // Create artifact service and save an image
    let artifact_service = Arc::new(InMemoryArtifactService::new());
    let image_bytes = std::fs::read("photo.png")?;

    artifact_service.save(SaveRequest {
        app_name: "image_app".to_string(),
        user_id: "user".to_string(),
        session_id: "init".to_string(),
        file_name: "user:photo.png".to_string(),  // user-scoped for cross-session access
        part: Part::InlineData {
            data: image_bytes,
            mime_type: "image/png".to_string(),
        },
        version: None,
    }).await?;

    // Clone for use in callback
    let callback_service = artifact_service.clone();

    let agent = LlmAgentBuilder::new("image_analyst")
        .description("Analyzes images")
        .instruction("You are an image analyst. Describe what you see in the image.")
        .model(model)
        // Use BeforeModel callback to inject image into the request
        .before_model_callback(Box::new(move |_ctx, mut request| {
            let service = callback_service.clone();
            Box::pin(async move {
                // Load the image artifact
                if let Ok(response) = service.load(LoadRequest {
                    app_name: "image_app".to_string(),
                    user_id: "user".to_string(),
                    session_id: "init".to_string(),
                    file_name: "user:photo.png".to_string(),
                    version: None,
                }).await {
                    // Inject image into the last user content
                    if let Some(last_content) = request.contents.last_mut() {
                        if last_content.role == "user" {
                            last_content.parts.push(response.part);
                        }
                    }
                }

                // Continue with the modified request
                Ok(BeforeModelResult::Continue(request))
            })
        }))
        .build()?;

    // Now when users ask "What's in the image?", the model will see the actual image
    Ok(())
}

Points clés :

  • Utilisez BeforeModelResult::Continue(request) pour passer la requête modifiée au model
  • Utilisez BeforeModelResult::Skip(response) si vous souhaitez retourner une réponse mise en cache à la place
  • L'image est injectée en tant que Part::InlineData, que Gemini interprète comme des données d'image réelles
  • Utilisez le préfixe user: pour les artifacts qui doivent être accessibles à travers les sessions

Analyse de documents PDF

Les modèles Gemini peuvent traiter les documents PDF nativement en utilisant le même modèle de callback BeforeModel. Les PDF sont injectés avec le type MIME application/pdf :

// Save PDF as artifact
artifact_service.save(SaveRequest {
    app_name: "my_app".to_string(),
    user_id: "user".to_string(),
    session_id: "init".to_string(),
    file_name: "user:document.pdf".to_string(),
    part: Part::InlineData {
        data: pdf_bytes,
        mime_type: "application/pdf".to_string(),
    },
    version: None,
}).await?;

// Use BeforeModel callback to inject PDF (same pattern as images)
.before_model_callback(Box::new(move |_ctx, mut request| {
    let service = callback_service.clone();
    Box::pin(async move {
        if let Ok(response) = service.load(LoadRequest {
            file_name: "user:document.pdf".to_string(),
            // ... other fields
        }).await {
            if let Some(last_content) = request.contents.last_mut() {
                if last_content.role == "user" {
                    last_content.parts.push(response.part);
                }
            }
        }
        Ok(BeforeModelResult::Continue(request))
    })
}))

Capacités PDF de Gemini :

  • Extraire et analyser le contenu textuel
  • Répondre à des questions sur des documents
  • Résumer des sections ou des documents entiers
  • Traiter jusqu'à ~1000 pages
  • Support OCR pour les documents numérisés

Consultez examples/artifacts/chat_pdf.rs pour un exemple complet et fonctionnel.

Stockage des images générées

async fn generate_and_save_image(ctx: &ToolContext) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Generate image (pseudo-code)
    let image_bytes = generate_image().await?;
    
    let version = artifacts.save(
        "generated_image.png",
        &Part::InlineData {
            mime_type: "image/png".to_string(),
            data: image_bytes,
        }
    ).await?;
    
    Ok(json!({
        "message": "Image saved",
        "file": "generated_image.png",
        "version": version
    }))
}

Chargement et traitement des documents

async fn process_document(ctx: &ToolContext, filename: &str) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Load the document
    let part = artifacts.load(filename).await?;
    
    match part {
        Part::InlineData { mime_type, data } => {
            // Process based on MIME type
            let result = match mime_type.as_str() {
                "application/pdf" => process_pdf(&data)?,
                "image/jpeg" | "image/png" => process_image(&data)?,
                _ => return Err(AdkError::Artifact("Unsupported type".into())),
            };
            
            Ok(json!({ "result": result }))
        }
        _ => Err(AdkError::Artifact("Expected binary data".into())),
    }
}

Historique des versions

async fn show_history(ctx: &ToolContext, filename: &str) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Get all files
    let files = artifacts.list().await?;
    
    if !files.contains(&filename.to_string()) {
        return Ok(json!({ "error": "File not found" }));
    }
    
    // Note: versions() is not available on the simple Artifacts trait
    // You would need access to the underlying ArtifactService
    
    Ok(json!({
        "file": filename,
        "exists": true
    }))
}

Référence d'API

Pour une documentation API complète, voir :

  • adk_core::Artifacts - Trait simple pour l'utilisation par l'agent
  • adk_artifact::ArtifactService - Trait de service complet
  • adk_artifact::InMemoryArtifactService - Implémentation en mémoire
  • adk_artifact::ScopedArtifacts - Wrapper scopé

Articles Similaires

  • Sessions - Gestion et cycle de vie des sessions
  • Callbacks - Accès aux artifacts dans les callbacks
  • Tools - Utilisation des artifacts dans des outils personnalisés

Précédent: ← Callbacks | Suivant: Événements →