Guías de Desarrollo
Este documento proporciona guías completas para los desarrolladores que contribuyen a ADK-Rust. Seguir estos estándares asegura la calidad del código, la consistencia y la mantenibilidad en todo el proyecto.
Tabla de Contenidos
- Primeros Pasos
- Estructura del Proyecto
- Estilo de Código
- Manejo de Errores
- Patrones Async
- Pruebas
- Documentación
- Proceso de Pull Request
- Tareas Comunes
Primeros Pasos
Prerrequisitos
- Rust: 1.75 o superior (verificar con
rustc --version) - Cargo: Última versión estable
- Git: Para control de versiones
Configuración de tu Entorno
# Clone the repository
git clone https://github.com/zavora-ai/adk-rust.git
cd adk-rust
# Build the project
cargo build
# Run all tests
cargo test --all
# Check for lints
cargo clippy --all-targets --all-features
# Format code
cargo fmt --all
Variables de Entorno
Para ejecutar ejemplos y pruebas que requieren claves API:
# Gemini (default provider)
export GOOGLE_API_KEY="your-api-key"
# OpenAI (optional)
export OPENAI_API_KEY="your-api-key"
# Anthropic (optional)
export ANTHROPIC_API_KEY="your-api-key"
Estructura del Proyecto
ADK-Rust está organizado como un Cargo workspace con múltiples crates:
adk-rust/
├── adk-core/ # Foundational traits and types (Agent, Tool, Llm, Event)
├── adk-telemetry/ # OpenTelemetry integration
├── adk-model/ # LLM providers (Gemini, OpenAI, Anthropic)
├── adk-tool/ # Tool system (FunctionTool, MCP, AgentTool)
├── adk-session/ # Session management (in-memory, SQLite)
├── adk-artifact/ # Binary artifact storage
├── adk-memory/ # Long-term memory with search
├── adk-agent/ # Agent implementations (LlmAgent, workflow agents)
├── adk-runner/ # Execution runtime
├── adk-server/ # REST API and A2A protocol
├── adk-cli/ # Command-line launcher
├── adk-realtime/ # Voice/audio streaming agents
├── adk-graph/ # LangGraph-style workflows
├── adk-browser/ # Browser automation tools
├── adk-eval/ # Agent evaluation framework
├── adk-rust/ # Umbrella crate (re-exports all)
└── examples/ # Working examples
Dependencias de Crate
Los crates deben publicarse en orden de dependencia:
adk-core(no internal deps)adk-telemetryadk-modeladk-tooladk-sessionadk-artifactadk-memoryadk-agentadk-runneradk-serveradk-cliadk-realtimeadk-graphadk-browseradk-evaladk-rust(umbrella)
Estilo de Código
Principios Generales
- Claridad sobre la inteligencia: Escribe código que sea fácil de leer y entender
- Explícito sobre implícito: Prefiere tipos y manejo de errores explícitos
- Funciones pequeñas: Mantén las funciones enfocadas y, si es posible, por debajo de las 50 líneas
- Nombres significativos: Usa nombres de variables y funciones descriptivos
Formato
Usa rustfmt con la configuración predeterminada:
cargo fmt --all
La pipeline de CI exige el formato. Siempre ejecuta cargo fmt antes de hacer commit.
Convenciones de Nomenclatura
| Tipo | Convención | Ejemplo |
|---|---|---|
| Crates | adk-* (kebab-case) | adk-core, adk-agent |
| Módulos | snake_case | llm_agent, function_tool |
| Tipos/Traits | PascalCase | LlmAgent, ToolContext |
| Funciones | snake_case | execute_tool, run_agent |
| Constantes | SCREAMING_SNAKE_CASE | KEY_PREFIX_APP |
| Parámetros de tipo | Letra mayúscula única o PascalCase | T, State |
Imports
Organiza los imports en este orden:
// 1. Standard library
use std::collections::HashMap;
use std::sync::Arc;
// 2. External crates
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
// 3. Internal crates (adk-*)
use adk_core::{Agent, Event, Result};
// 4. Local modules
use crate::config::Config;
use super::utils;
Clippy
Todo el código debe pasar clippy sin advertencias:
cargo clippy --all-targets --all-features
Aborda las advertencias de clippy en lugar de suprimirlas. Si la supresión es necesaria, documenta por qué:
#[allow(clippy::too_many_arguments)]
// Builder pattern requires many parameters; refactoring would hurt usability
fn complex_builder(...) { }
Manejo de Errores
Usar adk_core::AdkError
Todos los errores deben usar el tipo de error centralizado:
use adk_core::{AdkError, Result};
// Return Result<T> (aliased to Result<T, AdkError>)
pub async fn my_function() -> Result<String> {
// Use ? for propagation
let data = fetch_data().await?;
// Create errors with appropriate variants
if data.is_empty() {
return Err(AdkError::Tool("No data found".into()));
}
Ok(data)
}
Variantes de Error
Usa la variante de error apropiada:
| Variante | Caso de Uso |
|---|---|
AdkError::Agent(String) | Errores de ejecución de Agent |
AdkError::Model(String) | Errores del proveedor de LLM |
AdkError::Tool(String) | Errores de ejecución de Tool |
AdkError::Session(String) | Errores de gestión de Session |
AdkError::Artifact(String) | Errores de almacenamiento de artefactos |
AdkError::Config(String) | Errores de configuración |
AdkError::Network(String) | Errores de red/HTTP |
Mensajes de Error
Escribe mensajes de error claros y accionables:
// Good: Specific and actionable
Err(AdkError::Config("API key not found. Set GOOGLE_API_KEY environment variable.".into()))
// Bad: Vague
Err(AdkError::Config("Invalid config".into()))
Patrones Async
Uso de Tokio
Todo el código async utiliza el runtime de Tokio:
use tokio::sync::{Mutex, RwLock};
// Preferir RwLock para datos con muchas lecturas
let state: Arc<RwLock<State>> = Arc::new(RwLock::new(State::default()));
// Usar Mutex para casos con muchas escrituras o simples
let counter: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
Traits Async
Utilice async_trait para métodos de trait async:
use async_trait::async_trait;
#[async_trait]
pub trait MyTrait: Send + Sync {
async fn do_work(&self) -> Result<()>;
}
Streaming
Utilice EventStream para respuestas de streaming:
use adk_core::EventStream;
use async_stream::stream;
use futures::Stream;
fn create_stream() -> EventStream {
let s = stream! {
yield Ok(Event::new("inv-1"));
yield Ok(Event::new("inv-2"));
};
Box::pin(s)
}
Seguridad de Hilos
Todos los tipos públicos deben ser Send + Sync:
// Bueno: Seguro para hilos
pub struct MyAgent {
name: String,
tools: Vec<Arc<dyn Tool>>, // Arc para propiedad compartida
}
// Verificar con comprobaciones en tiempo de compilación
fn assert_send_sync<T: Send + Sync>() {}
fn _check() {
assert_send_sync::<MyAgent>();
}
Pruebas
Organización de las Pruebas
crate/
├── src/
│ ├── lib.rs # Pruebas unitarias al final del archivo
│ └── module.rs # Pruebas específicas del módulo
└── tests/
└── integration.rs # Pruebas de integración
Pruebas Unitarias
Coloque las pruebas unitarias en el mismo archivo que el código:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
}
Pruebas de Integración
Coloque en el directorio tests/:
// tests/integration_test.rs
use adk_core::*;
#[tokio::test]
async fn test_full_workflow() {
// Configuración
let service = InMemorySessionService::new();
// Ejecución
let session = service.create(request).await.unwrap();
// Afirmación
assert_eq!(session.id(), "test-session");
}
Pruebas con Mock
Utilice MockLlm para realizar pruebas sin llamadas a la API:
use adk_model::MockLlm;
#[tokio::test]
async fn test_agent_with_mock() {
let mock = MockLlm::new(vec![
"First response".to_string(),
"Second response".to_string(),
]);
let agent = LlmAgentBuilder::new("test")
.model(Arc::new(mock))
.build()
.unwrap();
// Probar el comportamiento del agent
}
Comandos de Prueba
# Ejecutar todas las pruebas
cargo test --all
# Ejecutar pruebas de crate específico
cargo test --package adk-core
# Ejecutar con salida
cargo test --all -- --nocapture
# Ejecutar pruebas ignoradas (requieren claves API)
cargo test --all -- --ignored
Documentación
Comentarios de Documentación
Utilice /// para elementos públicos:
/// Crea un nuevo LLM agent con la configuración especificada.
///
/// # Argumentos
///
/// * `name` - Un identificador único para este agent
/// * `model` - El proveedor LLM a utilizar para el razonamiento
///
/// # Ejemplos
///
/// ```rust
/// use adk_agent::LlmAgentBuilder;
///
/// let agent = LlmAgentBuilder::new("assistant")
/// .model(Arc::new(model))
/// .build()?;
/// ```
///
/// # Errores
///
/// Devuelve `AdkError::Agent` si el modelo no está configurado.
pub fn new(name: impl Into<String>) -> Self {
// ...
}
Documentación del Módulo
Añada documentación a nivel de módulo en la parte superior de lib.rs:
//! # adk-core
//!
//! Tipos y traits principales para adk-rust.
//!
//! ## Resumen
//!
//! Este crate proporciona los tipos fundamentales...
Archivos README
Cada crate debe tener un README.md con:
- Breve descripción
- Instrucciones de instalación
- Ejemplo rápido
- Enlace a la documentación completa
Pruebas de Documentación
Asegúrese de que los ejemplos de la documentación compilan:
cargo test --doc --all
Proceso de Pull Request
Antes de Enviar
-
Ejecutar el conjunto completo de pruebas:
cargo test --all -
Ejecutar clippy:
cargo clippy --all-targets --all-features -
Formatear código:
cargo fmt --all -
Actualizar la documentación si se añade o cambia la API pública
-
Añadir pruebas para la nueva funcionalidad
Pautas para PRs
- Título: Descripción clara y concisa del cambio
- Descripción: Explicar qué y por qué (no cómo)
- Tamaño: Mantener los PRs enfocados; dividir los cambios grandes
- Pruebas: Incluir pruebas para la nueva funcionalidad
- Cambios importantes: Documentar claramente en la descripción
Mensajes de Commit
Siga los conventional commits:
feat: add OpenAI streaming support
fix: correct tool parameter validation
docs: update quickstart guide
refactor: simplify session state management
test: add integration tests for A2A protocol
Tareas Comunes
Añadir una nueva Tool
- Crear la tool:
use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::Value;
pub struct MyTool {
// fields
}
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str {
"my_tool"
}
fn description(&self) -> &str {
"Does something useful"
}
fn parameters_schema(&self) -> Option<Value> {
Some(serde_json::json!({
"type": "object",
"properties": {
"input": { "type": "string" }
},
"required": ["input"]
}))
}
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let input = args["input"].as_str().unwrap_or_default();
Ok(serde_json::json!({ "result": input }))
}
}
- Añadir al agent:
let agent = LlmAgentBuilder::new("agent")
.model(model)
.tool(Arc::new(MyTool::new()))
.build()?;
Añadir un nuevo Proveedor de Modelo
- Crear módulo en
adk-model/src/:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
- Implementar el Llm trait:
use adk_core::{Llm, LlmRequest, LlmResponse, LlmResponseStream, Result};
pub struct MyModelClient {
api_key: String,
}
#[async_trait]
impl Llm for MyModelClient {
fn name(&self) -> &str {
"my-model"
}
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream> {
// Implementation
}
}
- Añadir feature flag en
adk-model/Cargo.toml:
[features]
mymodel = ["dep:mymodel-sdk"]
- Exportar condicionalmente:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;
Añadir un nuevo Tipo de Agent
- Crear módulo en
adk-agent/src/:
// adk-agent/src/my_agent.rs
use adk_core::{Agent, EventStream, InvocationContext, Result};
use async_trait::async_trait;
pub struct MyAgent {
name: String,
}
#[async_trait]
impl Agent for MyAgent {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"My custom agent"
}
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
// Implementation
}
}
- Exportar en
adk-agent/src/lib.rs:
mod my_agent;
pub use my_agent::MyAgent;
Consejos de Depuración
-
Habilitar tracing:
adk_telemetry::init_telemetry(); -
Inspeccionar eventos:
while let Some(event) = stream.next().await { eprintln!("Event: {:?}", event); } -
Usar RUST_LOG:
RUST_LOG=debug cargo run --example myexample
Anterior: ← Control de Acceso
¿Preguntas? Abra un issue en GitHub.