Artifacts

Artifacts oferecem uma maneira de armazenar e recuperar dados binários (imagens, PDFs, arquivos de áudio, etc.) dentro das suas aplicações de Agent. O sistema de Artifacts lida com versionamento, escopo de namespace e persistência de dados através de Sessions.

Visão Geral

O sistema de Artifacts em ADK-Rust consiste em:

  • Part: A representação de dados central que pode conter texto ou dados binários com MIME types
  • ArtifactService: A trait que define as operações de armazenamento de Artifacts
  • InMemoryArtifactService: Uma implementação em memória para desenvolvimento e teste
  • ScopedArtifacts: Um wrapper que simplifica as operações de Artifacts ao lidar automaticamente com o contexto de Session

Artifacts são definidos por escopo de aplicação, user e Session, proporcionando isolamento e organização. Arquivos podem ter escopo de Session (padrão) ou escopo de user (usando o prefixo user:).

Representação de Part

O Part enum representa dados que podem ser armazenados como Artifacts:

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 },
}

Para Artifacts, você usará principalmente:

  • Part::Text para dados de texto
  • Part::InlineData para dados binários com MIME types

Uso Básico

A maneira mais simples de trabalhar com Artifacts é através da trait Artifacts, que está disponível nos agent contexts:

use adk_rust::prelude::*;

// In an agent tool or callback
async fn save_report(ctx: &ToolContext) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Save text data
    let version = artifacts.save(
        "report.txt",
        &Part::Text { text: "Report content".to_string() }
    ).await?;
    
    // Save binary data
    let image_data = vec![0xFF, 0xD8, 0xFF]; // JPEG header
    artifacts.save(
        "chart.jpg",
        &Part::InlineData {
            mime_type: "image/jpeg".to_string(),
            data: image_data,
        }
    ).await?;
    
    Ok(json!({ "saved": true, "version": version }))
}

Trait ArtifactService

O trait ArtifactService define as operações centrais para o gerenciamento de artefatos:

#[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>;
}

Operação Save

Salva um artefato com versionamento automático ou explícito:

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);

Operação Load

Carrega a versão mais recente ou uma versão específica:

use adk_artifact::LoadRequest;

// Carrega a versão mais recente
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, // Carrega a mais recente
}).await?;

// Carrega uma versão específica
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), // Carrega a versão 2
}).await?;

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

Operação List

Lista todos os artefatos em uma sessão:

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!("Artefato encontrado: {}", file_name);
}

Operação Delete

Deleta uma versão específica ou todas as versões:

use adk_artifact::DeleteRequest;

// Deleta uma versão específica
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), // Deleta a versão 1
}).await?;

// Deleta todas as versões
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, // Deleta todas as versões
}).await?;

Operação Versions

Lista todas as versões de um artefato:

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!("Versões disponíveis: {:?}", response.versions);
// Saída: [3, 2, 1] (ordenado do mais novo para o mais antigo)

Versionamento

Artefatos suportam versionamento automático:

  • Ao salvar sem especificar uma versão, o sistema auto-incrementa a partir da versão mais recente
  • A versão 1 é atribuída à primeira gravação
  • Cada gravação subsequente incrementa o número da versão
  • Você pode carregar, excluir ou consultar versões específicas
// 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?;

Escopo de Namespace

Artefatos podem ter escopo em dois níveis:

Escopo de Sessão (Padrão)

Por padrão, os artefatos têm escopo para uma sessão específica. Cada sessão tem seu próprio namespace de artefato isolado:

// 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?;

// These are two separate artifacts

Escopo de Usuário

Artefatos com o prefixo user: são compartilhados entre todas as sessões para um usuário:

// 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?;

O prefixo user: permite:

  • Compartilhar dados entre várias conversas
  • Preferências de usuário persistentes
  • Cache em nível de usuário

InMemoryArtifactService

O InMemoryArtifactService fornece uma implementação em memória adequada para desenvolvimento e testes:

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

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

// Use with agents
let agent = LlmAgentBuilder::new("my_agent")
    .model(model)
    .build()?;

// The service can be passed to runners or used directly

Nota: Os dados não são persistidos em disco. Para uso em produção, considere implementar um ArtifactService customizado com suporte de um banco de dados ou armazenamento em nuvem.

ScopedArtifacts

O wrapper ScopedArtifacts simplifica as operações de artefato injetando automaticamente o contexto da sessão:

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(),
);

// Simple API - no need to specify app/user/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?;

Esta é a mesma interface disponível através de ToolContext::artifacts() e CallbackContext::artifacts().

Padrões Comuns

Análise de Imagens com Modelos Multimodais

Quando você quer que um LLM analise uma imagem armazenada como um artifact, você precisa usar um BeforeModel callback para injetar a imagem diretamente na requisição do LLM. Isso segue o padrão do adk-go.

Por que não usar uma tool? As respostas das tools nas APIs do LLM são texto JSON. Se uma tool retorna dados de imagem (mesmo codificados em base64), o model os vê como texto, e não como uma imagem real. Para uma verdadeira análise multimodal, a imagem deve ser incluída como um Part::InlineData no conteúdo da conversa.

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")?);

    // Cria o serviço de artifacts e salva uma imagem
    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?;

    // Clona para uso no 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)
        // Usa o BeforeModel callback para injetar a imagem na requisição
        .before_model_callback(Box::new(move |_ctx, mut request| {
            let service = callback_service.clone();
            Box::pin(async move {
                // Carrega o artifact da imagem
                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 {
                    // Injeta a imagem no último conteúdo do usuário
                    if let Some(last_content) = request.contents.last_mut() {
                        if last_content.role == "user" {
                            last_content.parts.push(response.part);
                        }
                    }
                }

                // Continua com a requisição modificada
                Ok(BeforeModelResult::Continue(request))
            })
        }))
        .build()?;

    // Agora, quando os usuários perguntarem "O que está na imagem?", o model verá a imagem real
    Ok(())
}

Pontos chave:

  • Use BeforeModelResult::Continue(request) para passar a requisição modificada para o model
  • Use BeforeModelResult::Skip(response) se você quiser retornar uma resposta em cache em vez disso
  • A imagem é injetada como Part::InlineData, que o Gemini interpreta como dados de imagem reais
  • Use o prefixo user: para artifacts que devem ser acessíveis entre sessões

Análise de Documentos PDF

Os models Gemini podem processar documentos PDF nativamente usando o mesmo padrão de BeforeModel callback. Os PDFs são injetados com o tipo MIME application/pdf:

// Salva o PDF como 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?;

// Usa o BeforeModel callback para injetar o PDF (mesmo padrão das imagens)
.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))
    })
}))

Capacidades do Gemini para PDF:

  • Extrai e analisa conteúdo de texto
  • Responde a perguntas sobre documentos
  • Resume seções ou documentos inteiros
  • Processa até ~1000 páginas
  • Suporte a OCR para documentos digitalizados

Veja examples/artifacts/chat_pdf.rs para um exemplo completo e funcional.

Armazenando Imagens Geradas

async fn generate_and_save_image(ctx: &ToolContext) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Gera imagem (pseudo-código)
    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
    }))
}

Carregando e Processando Documentos

async fn process_document(ctx: &ToolContext, filename: &str) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Carrega o documento
    let part = artifacts.load(filename).await?;
    
    match part {
        Part::InlineData { mime_type, data } => {
            // Processa com base no tipo MIME
            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())),
    }
}

Histórico de Versões

async fn show_history(ctx: &ToolContext, filename: &str) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Obtém todos os arquivos
    let files = artifacts.list().await?;
    
    if !files.contains(&filename.to_string()) {
        return Ok(json!({ "error": "File not found" }));
    }
    
    // Nota: versions() não está disponível no trait simples Artifacts
    // Você precisaria de acesso ao ArtifactService subjacente
    
    Ok(json!({
        "file": filename,
        "exists": true
    }))
}

Referência da API

Para a documentação completa da API, consulte:

  • adk_core::Artifacts - Trait simples para uso de agente
  • adk_artifact::ArtifactService - Trait de serviço completo
  • adk_artifact::InMemoryArtifactService - Implementação em memória
  • adk_artifact::ScopedArtifacts - Wrapper com escopo

Relacionados

  • Sessões - Gerenciamento e ciclo de vida de sessões
  • Callbacks - Acessando artefatos em Callbacks
  • Tools - Usando artefatos em Tools personalizadas

Anterior: ← Callbacks | Próximo: Eventos →