Tipos Fundamentales
Tipos y traits fundamentales de adk-core que forman la base de adk-rust.
Content y Part
Cada mensaje en adk fluye a través de Content y Part. Comprender estos tipos es esencial para trabajar con agents, tools y callbacks.
Content
Content representa un único mensaje en una conversación. Tiene un role (quién lo envió) y una o más parts (el contenido real).
Roles:
"user"- Mensajes del usuario"model"- Respuestas del model de IA"tool"- Resultados de la ejecución de la tool
use adk_core::Content;
// Mensaje de texto simple del usuario
let user_msg = Content::new("user")
.with_text("What's the weather like?");
// Respuesta del model
let model_msg = Content::new("model")
.with_text("The weather is sunny and 72°F.");
// Múltiples partes de texto en un mensaje
let detailed_msg = Content::new("user")
.with_text("Here's my question:")
.with_text("What is the capital of France?");
Content Multimodal:
Content puede incluir imágenes, audio, PDFs y otros datos binarios junto con texto:
// Imagen desde bytes (ej., leída de un archivo)
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);
// Imagen desde URL (el model la obtiene)
let content = Content::new("user")
.with_text("Describe this image")
.with_file_uri("image/png", "https://example.com/chart.png");
// Documento 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 es un enum que representa diferentes tipos de contenido dentro de un mensaje:
pub enum Part {
// Texto plano
Text { text: String },
// Datos binarios incrustados en el mensaje
InlineData { mime_type: String, data: Vec<u8> },
// Referencia a archivo externo (URL o almacenamiento en la nube)
FileData { mime_type: String, file_uri: String },
// Model solicitando una llamada a tool
FunctionCall { name: String, args: Value, id: Option<String> },
// Resultado de la ejecución de una tool
FunctionResponse { function_response: FunctionResponseData, id: Option<String> },
}
Creando Parts Directamente:
use adk_core::Part;
// Parte de texto
let text = Part::text_part("Hello, world!");
// Imagen desde bytes
let image = Part::inline_data("image/png", png_bytes);
// Imagen desde URL
let remote_image = Part::file_data("image/jpeg", "https://example.com/photo.jpg");
Inspeccionando Parts:
// Obtiene el contenido de texto (devuelve None para partes que no son de texto)
if let Some(text) = part.text() {
println!("Texto: {}", text);
}
// Obtiene el tipo MIME (para InlineData y FileData)
if let Some(mime) = part.mime_type() {
println!("Tipo MIME: {}", mime);
}
// Obtiene el file URI (solo para FileData)
if let Some(uri) = part.file_uri() {
println!("File URI: {}", uri);
}
// Comprueba si la parte contiene medios (imagen, audio, video, etc.)
if part.is_media() {
println!("Esta parte contiene medios binarios");
}
Iterando Sobre Parts:
for part in &content.parts {
match part {
Part::Text { text } => println!("Texto: {}", text),
Part::InlineData { mime_type, data } => {
println!("Datos binarios: {} ({} bytes)", mime_type, data.len());
}
Part::FileData { mime_type, file_uri } => {
println!("Archivo: {} en {}", mime_type, file_uri);
}
Part::FunctionCall { name, args, .. } => {
println!("Llamada a tool: {}({})", name, args);
}
Part::FunctionResponse { function_response, .. } => {
println!("Resultado de la tool: {} -> {}",
function_response.name,
function_response.response
);
}
}
}
Rasgo Agent
El rasgo Agent es la abstracción central para todos los Agents en ADK. Cada tipo de Agent —LlmAgent, Agents de flujo de trabajo, Agents personalizados— implementa este rasgo.
#[async_trait]
pub trait Agent: Send + Sync {
/// Identificador único para este Agent
fn name(&self) -> &str;
/// Descripción legible por humanos de lo que hace este Agent
fn description(&self) -> &str;
/// Agents hijo (para Agents de flujo de trabajo como SequentialAgent, ParallelAgent)
fn sub_agents(&self) -> &[Arc<dyn Agent>];
/// Ejecuta el Agent y devuelve un stream de eventos
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}
Puntos Clave:
name(): Se usa para logging, transferencias e identificación. Debe ser único dentro de un sistema multi-Agent.description(): Se muestra a los LLMs cuando el Agent se usa como Tool o para decisiones de routing.sub_agents(): Devuelve Agents hijo. Vacío para Agents hoja (LlmAgent), populado para contenedores (SequentialAgent, ParallelAgent).run(): El método de ejecución principal. Recibe el contexto y devuelve un stream de eventos.
¿Por qué EventStream?
Los Agents devuelven EventStream (un stream de Result<Event>) en lugar de una única respuesta porque:
- Streaming: Las respuestas se pueden transmitir token a token para una mejor UX
- Tool calls: Múltiples tool calls y respuestas ocurren durante la ejecución
- Cambios de estado: Las actualizaciones de estado se emiten como eventos
- Transferencias: Las transferencias de Agent se señalan a través de eventos
Rasgo Tool
Las Tools extienden las capacidades del Agent más allá de la conversación. Permiten a los Agents interactuar con APIs, bases de datos, sistemas de archivos o realizar cálculos.
#[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>;
}
Puntos Clave:
name(): El nombre de la función que el modelo utiliza para llamar a esta Tool. Manténgalo corto y descriptivo (ej.,get_weather,search_database).description(): Fundamental para que el modelo entienda cuándo usar la Tool. Sea específico sobre lo que hace y cuándo usarla.parameters_schema(): JSON Schema que le dice al modelo qué argumentos debe proporcionar. Sin esto, el modelo adivina.execute(): Recibe los argumentos parseados comoserde_json::Value. Devuelve el resultado como JSON.
Implementando una 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 Larga Duración:
Para operaciones que requieren mucho tiempo (procesamiento de archivos, llamadas a API externas), marque la Tool como de larga duración:
fn is_long_running(&self) -> bool { true }
Las Tools de larga duración devuelven un ID de tarea inmediatamente. Se le indica al modelo que no vuelva a llamar a la Tool mientras esté pendiente.
Rasgo Toolset
Toolset proporciona tools dinámicamente según el contexto. Úsalo cuando:
- Las tools dependen de los permisos de usuario
- Las tools se cargan desde fuentes externas (servidores MCP)
- La disponibilidad de las tools cambia durante la ejecución
#[async_trait]
pub trait Toolset: Send + Sync {
/// Identificador del Toolset
fn name(&self) -> &str;
/// Devuelve las tools disponibles para el contexto actual
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
}
Ejemplo: Toolset Basado en Permisos:
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())
}
}
}
Rasgos de Contexto
Los contextos proporcionan información y servicios a los agents y tools durante la ejecución. Existe una jerarquía de rasgos de contexto, cada uno añadiendo más capacidades.
ReadonlyContext
Información básica disponible en todas partes:
pub trait ReadonlyContext: Send + Sync {
/// ID único para esta invocation
fn invocation_id(&self) -> &str;
/// Nombre del agent actualmente en ejecución
fn agent_name(&self) -> &str;
/// Identificador de usuario
fn user_id(&self) -> &str;
/// Nombre de la aplicación
fn app_name(&self) -> &str;
/// Identificador de Session
fn session_id(&self) -> &str;
/// La entrada del usuario que activó esta invocation
fn user_content(&self) -> &Content;
}
CallbackContext
Añade acceso a los artefactos (extiende ReadonlyContext):
pub trait CallbackContext: ReadonlyContext {
/// Acceso al almacenamiento de artefactos (si está configurado)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
ToolContext
Para la ejecución de tools (extiende CallbackContext):
pub trait ToolContext: CallbackContext {
/// ID de la llamada a función que activó esta tool
fn function_call_id(&self) -> &str;
/// Obtiene las acciones de evento actuales (transfer, escalate, etc.)
fn actions(&self) -> EventActions;
/// Establece las acciones de evento (p. ej., activar un transfer)
fn set_actions(&self, actions: EventActions);
/// Busca en la memoria a largo plazo
async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}
InvocationContext
Contexto completo para la ejecución del agent (extiende CallbackContext):
pub trait InvocationContext: CallbackContext {
/// El agent que se está ejecutando
fn agent(&self) -> Arc<dyn Agent>;
/// Servicio de Memory (si está configurado)
fn memory(&self) -> Option<Arc<dyn Memory>>;
/// Session actual con estado e historial
fn session(&self) -> &dyn Session;
/// Configuración de ejecución
fn run_config(&self) -> &RunConfig;
/// Indica que esta invocation debe terminar
fn end_invocation(&self);
/// Verifica si la invocation ha terminado
fn ended(&self) -> bool;
}
Sesión y Estado
Las sesiones rastrean conversaciones. El estado almacena datos dentro de las sesiones.
Sesión
pub trait Session: Send + Sync {
/// Identificador único de la sesión
fn id(&self) -> &str;
/// Aplicación a la que pertenece esta sesión
fn app_name(&self) -> &str;
/// Usuario propietario de esta sesión
fn user_id(&self) -> &str;
/// Almacenamiento de estado mutable
fn state(&self) -> &dyn State;
/// Mensajes anteriores en esta conversación
fn conversation_history(&self) -> Vec<Content>;
}
Estado
Almacenamiento clave-valor con prefijos de alcance:
pub trait State: Send + Sync {
/// Obtener un valor por clave
fn get(&self, key: &str) -> Option<Value>;
/// Establecer un valor
fn set(&mut self, key: String, value: Value);
/// Obtener todos los pares clave-valor
fn all(&self) -> HashMap<String, Value>;
}
Prefijos de estado:
Las claves de State usan prefijos para controlar el alcance y la persistencia:
| Prefijo | Alcance | Persistencia | Caso de uso |
|---|---|---|---|
user: | Nivel de usuario | En todas las sesiones | Preferencias de usuario, configuraciones |
app: | Nivel de aplicación | En toda la aplicación | Configuración compartida |
temp: | Nivel de turno | Se borra en cada turno | Datos de cómputo temporales |
| (none) | Nivel de sesión | Solo esta sesión | Contexto de conversación |
// En un callback o Tool
let state = ctx.session().state();
// Preferencia de usuario (persiste en todas las sesiones)
state.set("user:theme".into(), json!("dark"));
// Datos específicos de la sesión
state.set("current_topic".into(), json!("weather"));
// Datos temporales (borrados después de este turno)
state.set("temp:step_count".into(), json!(1));
// Leer valores
if let Some(theme) = state.get("user:theme") {
println!("Theme: {}", theme);
}
Manejo de errores
ADK utiliza un tipo de error unificado para todas las operaciones:
pub enum AdkError {
Agent(String), // Errores de ejecución de Agent
Tool(String), // Errores de ejecución de Tool
Model(String), // Errores de API de LLM
Session(String), // Errores de almacenamiento de Session
Artifact(String), // Errores de almacenamiento de Artifact
Config(String), // Errores de configuración
Io(std::io::Error), // Errores de E/S de archivo/red
Json(serde_json::Error), // Errores de análisis de JSON
}
pub type Result<T> = std::result::Result<T, AdkError>;
Manejo de errores en Tools:
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" }))
}
EventStream
Los Agents devuelven un flujo de eventos en lugar de una única respuesta:
pub type EventStream = Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;
Procesando eventos:
use futures::StreamExt;
let mut stream = agent.run(ctx).await?;
while let Some(result) = stream.next().await {
match result {
Ok(event) => {
// Verificar contenido de texto
if let Some(content) = event.content() {
for part in &content.parts {
if let Some(text) = part.text() {
print!("{}", text);
}
}
}
// Verificar cambios de estado
for (key, value) in event.state_delta() {
println!("State changed: {} = {}", key, value);
}
// Verificar si esta es la respuesta final
if event.is_final_response() {
println!("\n[Done]");
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
Rasgo Llm
El rasgo que implementan todos los proveedores de LLM:
#[async_trait]
pub trait Llm: Send + Sync {
/// Identificador del modelo (ej., "gemini-2.0-flash", "gpt-4o")
fn name(&self) -> &str;
/// Generar contenido (en streaming o no en streaming)
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream>;
}
LlmRequest:
pub struct LlmRequest {
pub contents: Vec<Content>, // Historial de conversación
pub tools: Vec<ToolDeclaration>, // Herramientas disponibles
pub system_instruction: Option<String>, // Prompt del sistema
pub config: GenerateContentConfig, // Temperatura, max_tokens, etc.
}
LlmResponse:
pub struct LlmResponse {
pub content: Option<Content>, // Contenido generado
pub finish_reason: Option<FinishReason>, // Razón por la que la generación se detuvo
pub usage: Option<UsageMetadata>, // Recuentos de tokens
pub partial: bool, // ¿Es este un fragmento de streaming?
pub turn_complete: bool, // ¿Ha terminado el turno?
}
Todos los proveedores (Gemini, OpenAI, Anthropic, Ollama, etc.) implementan este rasgo, haciéndolos intercambiables:
// Cambiar de proveedor cambiando una línea
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: ← Introducción | Siguiente: Runner →