Tipos Essenciais

Tipos e traits fundamentais de adk-core que formam a base do adk-rust.

Content e Part

Cada mensagem no ADK flui através de Content e Part. Compreender esses tipos é essencial para trabalhar com agents, tools e callbacks.

Content

Content representa uma única mensagem em uma conversa. Possui um role (quem a enviou) e uma ou mais parts (o conteúdo real).

Funções (Roles):

  • "user" - Mensagens do user
  • "model" - Respostas do AI model
  • "tool" - Resultados da execução da tool
use adk_core::Content;

// Simple text message from user
let user_msg = Content::new("user")
    .with_text("What's the weather like?");

// Model response
let model_msg = Content::new("model")
    .with_text("The weather is sunny and 72°F.");

// Multiple text parts in one message
let detailed_msg = Content::new("user")
    .with_text("Here's my question:")
    .with_text("What is the capital of France?");

Conteúdo Multimodal:

Content pode incluir imagens, áudios, PDFs e outros dados binários junto com texto:

// Image from bytes (e.g., read from file)
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 from URL (model fetches it)
let content = Content::new("user")
    .with_text("Describe this image")
    .with_file_uri("image/png", "https://example.com/chart.png");

// PDF document
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 é um enum que representa diferentes tipos de conteúdo dentro de uma mensagem:

pub enum Part {
    // Plain text
    Text { text: String },
    
    // Binary data embedded in the message
    InlineData { mime_type: String, data: Vec<u8> },
    
    // Reference to external file (URL or cloud storage)
    FileData { mime_type: String, file_uri: String },
    
    // Model requesting a tool call
    FunctionCall { name: String, args: Value, id: Option<String> },
    
    // Result of a tool execution
    FunctionResponse { function_response: FunctionResponseData, id: Option<String> },
}

Criando Parts Diretamente:

use adk_core::Part;

// Text part
let text = Part::text_part("Hello, world!");

// Image from bytes
let image = Part::inline_data("image/png", png_bytes);

// Image from URL
let remote_image = Part::file_data("image/jpeg", "https://example.com/photo.jpg");

Inspecionando Parts:

// Get text content (returns None for non-text parts)
if let Some(text) = part.text() {
    println!("Text: {}", text);
}

// Get MIME type (for InlineData and FileData)
if let Some(mime) = part.mime_type() {
    println!("MIME type: {}", mime);
}

// Get file URI (for FileData only)
if let Some(uri) = part.file_uri() {
    println!("File URI: {}", uri);
}

// Check if part contains media (image, audio, video, etc.)
if part.is_media() {
    println!("This part contains binary media");
}

Iterando Sobre 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

O trait Agent é a abstração central para todos os agents no ADK. Cada tipo de agent—agents LLM, agents de fluxo de trabalho, agents personalizados—implementa este trait.

#[async_trait]
pub trait Agent: Send + Sync {
    /// Identificador único para este agent
    fn name(&self) -> &str;
    
    /// Descrição legível por humanos do que este agent faz
    fn description(&self) -> &str;
    
    /// Agents filhos (para agents de fluxo de trabalho como SequentialAgent, ParallelAgent)
    fn sub_agents(&self) -> &[Arc<dyn Agent>];
    
    /// Executa o agent e retorna um stream de eventos
    async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}

Pontos Chave:

  • name(): Usado para logging, transfers e identificação. Deve ser único dentro de um multi-agent system.
  • description(): Exibido aos LLMs quando o agent é usado como um Tool ou para decisões de roteamento.
  • sub_agents(): Retorna agents filhos. Vazio para leaf agents (LlmAgent), preenchido para containers (SequentialAgent, ParallelAgent).
  • run(): O método principal de execução. Recebe context e retorna um stream de eventos.

Por que EventStream?

Agents retornam EventStream (um stream de Result<Event>) em vez de uma única resposta porque:

  1. Streaming: As respostas podem ser streamadas token-by-token para uma melhor UX
  2. Tool calls: Múltiplos Tool calls e respostas ocorrem durante a execução
  3. State changes: Atualizações de state são emitidas como events
  4. Transfers: Agent transfers são sinalizados através de events

Trait Tool

Tools estendem as capacidades dos agentes além da conversa. Elas permitem que os agentes interajam com APIs, bancos de dados, sistemas de arquivos ou realizem computações.

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

Pontos Chave:

  • name(): O nome da função que o modelo usa para chamar esta Tool. Mantenha-o curto e descritivo (ex: get_weather, search_database).
  • description(): Crítico para o modelo entender quando usar esta Tool. Seja específico sobre o que ela faz e quando usá-la.
  • parameters_schema(): JSON Schema que informa ao modelo quais argumentos fornecer. Sem isso, o modelo tenta adivinhar.
  • execute(): Recebe argumentos parseados como serde_json::Value. Retorna o resultado como JSON.

Implementando uma Tool Personalizada:

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"
        }))
    }
}

Tools de Longa Duração:

Para operações que demoram muito (processamento de arquivos, chamadas de API externas), marque a Tool como de longa duração:

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

Tools de longa duração retornam um ID de tarefa imediatamente. O modelo é instruído a não chamar a Tool novamente enquanto ela estiver pendente.


Trait Toolset

Toolset fornece tools dinamicamente com base no contexto. Use-o quando:

  • As tools dependem das permissões do usuário
  • As tools são carregadas de fontes externas (servidores MCP)
  • A disponibilidade das tools muda durante a execução
#[async_trait]
pub trait Toolset: Send + Sync {
    /// Identificador do Toolset
    fn name(&self) -> &str;
    
    /// Retorna as tools disponíveis para o contexto atual
    async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
}

Exemplo: Toolset Baseado em Permissões:

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 Contexto

Contexts fornecem informações e serviços para agents e tools durante a execução. Há uma hierarquia de traits de contexto, cada um adicionando mais capacidades.

ReadonlyContext

Informações básicas disponíveis em todos os lugares:

pub trait ReadonlyContext: Send + Sync {
    /// ID único para esta invocation
    fn invocation_id(&self) -> &str;
    
    /// Nome do agent atualmente em execução
    fn agent_name(&self) -> &str;
    
    /// Identificador do usuário
    fn user_id(&self) -> &str;
    
    /// Nome da aplicação
    fn app_name(&self) -> &str;
    
    /// Identificador da session
    fn session_id(&self) -> &str;
    
    /// A entrada do usuário que acionou esta invocation
    fn user_content(&self) -> &Content;
}

CallbackContext

Adiciona acesso a artefatos (estende ReadonlyContext):

pub trait CallbackContext: ReadonlyContext {
    /// Acesso ao armazenamento de artefatos (se configurado)
    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}

ToolContext

Para a execução de tools (estende CallbackContext):

pub trait ToolContext: CallbackContext {
    /// ID da chamada de função que acionou esta tool
    fn function_call_id(&self) -> &str;
    
    /// Obtém as ações de evento atuais (transferir, escalar, etc.)
    fn actions(&self) -> EventActions;
    
    /// Define as ações de evento (ex: acionar uma transferência)
    fn set_actions(&self, actions: EventActions);
    
    /// Pesquisa na memória de longo prazo
    async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}

InvocationContext

Contexto completo para a execução do agent (estende CallbackContext):

pub trait InvocationContext: CallbackContext {
    /// O agent sendo executado
    fn agent(&self) -> Arc<dyn Agent>;
    
    /// Serviço de memory (se configurado)
    fn memory(&self) -> Option<Arc<dyn Memory>>;
    
    /// session atual com estado e histórico
    fn session(&self) -> &dyn Session;
    
    /// Configuração de execução
    fn run_config(&self) -> &RunConfig;
    
    /// Sinaliza que esta invocation deve terminar
    fn end_invocation(&self);
    
    /// Verifica se a invocation foi encerrada
    fn ended(&self) -> bool;
}

Sessão e Estado

Sessions rastreiam conversas. State armazena dados dentro das sessions.

Sessão

pub trait Session: Send + Sync {
    /// Identificador único da sessão
    fn id(&self) -> &str;
    
    /// Aplicação à qual esta sessão pertence
    fn app_name(&self) -> &str;
    
    /// Usuário proprietário desta sessão
    fn user_id(&self) -> &str;
    
    /// Armazenamento de estado mutável
    fn state(&self) -> &dyn State;
    
    /// Mensagens anteriores nesta conversa
    fn conversation_history(&self) -> Vec<Content>;
}

Estado

Armazenamento chave-valor com prefixos de escopo:

pub trait State: Send + Sync {
    /// Obtém um valor pela chave
    fn get(&self, key: &str) -> Option<Value>;
    
    /// Define um valor
    fn set(&mut self, key: String, value: Value);
    
    /// Obtém todos os pares chave-valor
    fn all(&self) -> HashMap<String, Value>;
}

Prefixos de Estado:

As chaves de estado usam prefixos para controlar o escopo e a persistência:

PrefixEscopoPersistênciaCaso de Uso
user:Nível de UsuárioEm todas as sessionsPreferências do usuário, configurações
app:Nível de AplicaçãoEm toda a aplicaçãoConfiguração compartilhada
temp:Nível de TurnoLimpo a cada turnoDados de computação temporários
(none)Nível de SessãoSomente nesta sessionContexto da conversa
// Em um callback ou tool
let state = ctx.session().state();

// Preferência do usuário (persiste entre as sessions)
state.set("user:theme".into(), json!("dark"));

// Dados específicos da sessão
state.set("current_topic".into(), json!("weather"));

// Dados temporários (limpos após este turno)
state.set("temp:step_count".into(), json!(1));

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

Tratamento de Erros

O ADK usa um tipo de erro unificado para todas as operações:

pub enum AdkError {
    Agent(String),      // Erros de execução do Agent
    Tool(String),       // Erros de execução da Tool
    Model(String),      // Erros de API do LLM
    Session(String),    // Erros de armazenamento de Session
    Artifact(String),   // Erros de armazenamento de artefato
    Config(String),     // Erros de configuração
    Io(std::io::Error), // Erros de I/O de arquivo/rede
    Json(serde_json::Error), // Erros de análise JSON
}

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

Tratamento de Erros nas Tools:

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

Fluxo de Eventos

Agents retornam um fluxo de eventos em vez de uma única resposta:

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

Processando Eventos:

use futures::StreamExt;

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

while let Some(result) = stream.next().await {
    match result {
        Ok(event) => {
            // Verificar conteúdo de texto
            if let Some(content) = event.content() {
                for part in &content.parts {
                    if let Some(text) = part.text() {
                        print!("{}", text);
                    }
                }
            }
            
            // Verificar alterações de estado
            for (key, value) in event.state_delta() {
                println!("Estado alterado: {} = {}", key, value);
            }
            
            // Verificar se esta é a resposta final
            if event.is_final_response() {
                println!("\n[Concluído]");
            }
        }
        Err(e) => {
            eprintln!("Erro: {}", e);
            break;
        }
    }
}

Trait Llm

O trait que todos os provedores de LLM implementam:

#[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>,           // Conversation history
    pub tools: Vec<ToolDeclaration>,      // Available tools
    pub system_instruction: Option<String>, // System prompt
    pub config: GenerateContentConfig,    // Temperature, max_tokens, etc.
}

LlmResponse:

pub struct LlmResponse {
    pub content: Option<Content>,         // Generated content
    pub finish_reason: Option<FinishReason>, // Why generation stopped
    pub usage: Option<UsageMetadata>,     // Token counts
    pub partial: bool,                    // Is this a streaming chunk?
    pub turn_complete: bool,              // Is the turn finished?
}

Todos os provedores (Gemini, OpenAI, Anthropic, Ollama, etc.) implementam este trait, tornando-os intercambiáveis:

// Switch providers by changing one line
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()?;

Anterior: ← Introdução | Próximo: Runner →