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:
- Streaming: As respostas podem ser streamadas token-by-token para uma melhor UX
- Tool calls: Múltiplos Tool calls e respostas ocorrem durante a execução
- State changes: Atualizações de state são emitidas como events
- 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 comoserde_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:
| Prefix | Escopo | Persistência | Caso de Uso |
|---|---|---|---|
user: | Nível de Usuário | Em todas as sessions | Preferências do usuário, configurações |
app: | Nível de Aplicação | Em toda a aplicação | Configuração compartilhada |
temp: | Nível de Turno | Limpo a cada turno | Dados de computação temporários |
| (none) | Nível de Sessão | Somente nesta session | Contexto 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 →