5 minuto(s) de lectura

De Python a Java: Construyendo un CLI de AI Agents para Resúmenes Académicos

Con tantos años Java, siempre me resistí a mudarme a Python para entender un poco mejor el ecosistema de AI/LLM. Pero después de leer el artículo de Rod Johnson (creador de Spring) sobre Embabel, descubrí que Java no solo puede competir, sino que ofrece ventajas significativas para construir agentes de producción.

Y ahora?

En este post, vamos a construir StudyBuddy CLI: una herramienta que ayuda a estudiantes a generar resúmenes académicos de alta calidad usando AI Agents en Java.

(Por las dudas metanse en el repo, porque puede haber diferencias)

# Ejemplo de uso
$ studybuddy summarize "patrones de diseño en python" -o resumen.md --lang es
✓ Investigando el tema...
✓ Generando estructura...
✓ Creando resumen académico...
✓ Resumen guardado en: resumen.md

Por qué Java para AI Agents?

Ventajas sobre Python

1. Type Safety

// Java - Errores en compilacion
public record ResearchRequest(
    String topic,
    Language language,
    AcademicLevel level
) {}

# Python - Errores en runtime
def research(topic: str, language: str, level: str):
    # Que valores son validos para language y level?

2. Mejor Concurrencia (con Java 21)

// Virtual Threads (Java 21) - Miles de threads sin overhead
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    var futures = chapters.stream()
        .map(ch -> executor.submit(() -> processChapter(ch)))
        .toList();
    // Process all chapters in parallel
}

3. Ecosistema Empresarial

  • Spring Boot para configuración e Inyección de Dependencias
  • Micrometer para métricas
  • Testcontainers para testing
  • Imagenes nativas con GraalVM

Arquitectura del Proyecto

Estructura de Agentes

StudyBuddy CLI
├── ResearchAgent       # Investiga el tema en múltiples fuentes
├── OutlineAgent        # Crea estructura académica
├── SummarizerAgent     # Genera el resumen final
└── FormatterAgent      # Formatea en Markdown académico

Stack Tecnológico

  • Java 21 - Virtual Threads, Records, Pattern Matching
  • Spring Boot 3.3 - DI, Configuration, CLI
  • Embabel - Framework de AI Agents
  • Picocli - CLI framework
  • OpenAI/Anthropic - provedores LLM

Implementación Paso a Paso

Importante: tenes que tener una API key de Anthropic o OpenAI, en mi caso, preferi usar Anthropic porque el modelo Haiku me ha dado buenos resultados para este tipo de tareas.

Paso 1: Setup del Proyecto

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.embabel</groupId>
    <artifactId>embabel-agent</artifactId>
</dependency>
<dependency>
    <groupId>info.picocli</groupId>
    <artifactId>picocli-spring-boot-starter</artifactId>
</dependency>

Paso 2: Domain Models

public record AcademicSummaryRequest(
    String topic,
    AcademicLevel level,
    Language language,
    OutputFormat format
) {
    public enum AcademicLevel {
        HIGH_SCHOOL("secundaria"),
        UNDERGRADUATE("universitario"),
        GRADUATE("posgrado");
    }
}

Paso 3: Research Agent

@Agent(description = "Investiga temas académicos en profundidad")
public class ResearchAgent {
    
    @Action
    public ResearchReport investigate(
            AcademicSummaryRequest request,
            OperationContext context) {
        
        // Configurar LLM con temperatura baja para precisión
        return context.ai()
            .withLlm(LlmOptions.builder()
                .model("gpt-4")
                .temperature(0.3)  // Baja para contenido académico
                .build())
            .withToolGroup(WebTools.class)
            .createObject(
                buildResearchPrompt(request),
                ResearchReport.class
            );
    }
    
    private String buildResearchPrompt(AcademicSummaryRequest request) {
        return """
            Investiga el tema: %s
            Nivel académico: %s
            Busca:
            - Conceptos fundamentales
            - Autores y referencias principales
            - Aplicaciones prácticas
            - Últimos avances
            """.formatted(request.topic(), request.level());
    }
}

Paso 4: Summarizer Agent

@Agent(description = "Genera resúmenes académicos estructurados")
public class SummarizerAgent {
    
    @Action
    public AcademicSummary createSummary(
            ResearchReport research,
            AcademicSummaryRequest request,
            OperationContext context) {
        
        return context.ai()
            .withLlm(writerLlm())
            .withPromptElements(
                new AcademicRole(request.level()),
                research,
                request
            )
            .createObject(
                SUMMARY_PROMPT,
                AcademicSummary.class
            );
    }
}

Paso 5: CLI Implementation

@Component
@Command(
    name = "studybuddy",
    description = "AI-powered academic summary generator"
)
public class StudyBuddyCLI implements Callable<Integer> {
    
    @Autowired
    private StudyBuddyOrchestrator orchestrator;
    
    @Parameters(index = "0", description = "Topic to summarize")
    private String topic;
    
    @Option(names = {"-o", "--output"}, 
            description = "Output file (default: summary.md)")
    private String outputFile = "summary.md";
    
    @Option(names = {"-l", "--level"}, 
            description = "Academic level: ${COMPLETION-CANDIDATES}")
    private AcademicLevel level = AcademicLevel.UNDERGRADUATE;
    
    @Option(names = {"--lang"}, 
            description = "Language: es, en (default: es)")
    private String language = "es";
    
    @Override
    public Integer call() {
        try {
            // Mostrar spinner mientras procesa
            var spinner = new SpinnerThread("Investigando el tema...");
            spinner.start();
            
            // Ejecutar pipeline de agentes
            var summary = orchestrator.generateSummary(
                new AcademicSummaryRequest(
                    topic, level, Language.of(language), OutputFormat.MARKDOWN
                )
            );
            
            spinner.stop();
            
            // Guardar resultado
            Files.writeString(Path.of(outputFile), summary.toMarkdown());
            
            System.out.println("✅ Resumen guardado en: " + outputFile);
            return 0;
            
        } catch (Exception e) {
            System.err.println("❌ Error: " + e.getMessage());
            return 1;
        }
    }
}

Testing con Mocks

@SpringBootTest
class StudyBuddyTest {
    
    @MockBean
    private LlmService llmService;
    
    @Test
    void shouldGenerateSummaryForDesignPatterns() {
        // Given
        var request = new AcademicSummaryRequest(
            "Patrones de diseño en Python",
            AcademicLevel.UNDERGRADUATE,
            Language.ES,
            OutputFormat.MARKDOWN
        );
        
        when(llmService.complete(any()))
            .thenReturn(mockResearchResponse())
            .thenReturn(mockSummaryResponse());
        
        // When
        var summary = orchestrator.generateSummary(request);
        
        // Then
        assertThat(summary).isNotNull();
        assertThat(summary.sections()).hasSize(5);
        assertThat(summary.toMarkdown()).contains("# Patrones de Diseño");
    }
}

Como se usa?

Resumir un tema (modo básico)

  # En español (por defecto)
  ./studybuddy.sh summarize "inteligencia artificial"

  # Con nivel específico
  ./studybuddy.sh summarize "machine learning" --level BEGINNER

  # En inglés
  ./studybuddy.sh summarize "quantum computing" --lang EN --level ADVANCED

Investigación académica

  # Investigación básica sobre blockchain
  ./studybuddy.sh research "blockchain technology" --depth BASIC

  # Investigación detallada en español
  ./studybuddy.sh research "computacion cuantica" --depth DETAILED

  # Investigación con profundidad en inglés
  ./studybuddy.sh research "neural networks" --lang EN --depth DETAILED

Explicar conceptos complejos

  # Explicación en formato Markdown
  ./studybuddy.sh explain "algoritmos de busqueda" --format MARKDOWN

  # Explicación en texto plano
  ./studybuddy.sh explain "distributed systems" --lang EN --format PLAIN

  # Concepto técnico en español
  ./studybuddy.sh explain "programacion orientada a objetos" --format MARKDOWN

Casos de uso reales

  # Para preparar una clase
  ./studybuddy.sh summarize "estructuras de datos" --level INTERMEDIATE

  # Para investigar para un proyecto
  ./studybuddy.sh research "microservicios con spring boot" --depth DETAILED

  # Para entender un concepto rápido
  ./studybuddy.sh explain "dependency injection" --lang EN --format PLAIN

Producción: Consideraciones

Observabilidad

@Component
public class MetricsCollector {
    private final MeterRegistry registry;
    
    public void recordSummaryGeneration(String topic, long duration) {
        registry.timer("summary.generation", 
            "topic", topic,
            "duration", String.valueOf(duration)
        ).record(Duration.ofMillis(duration));
    }
}

Rate Limiting

@Component
public class RateLimiter {
    private final Bucket bucket = Bucket.builder()
        .addLimit(Bandwidth.simple(10, Duration.ofMinutes(1)))
        .build();
    
    public void checkLimit() {
        if (!bucket.tryConsume(1)) {
            throw new RateLimitExceededException();
        }
    }
}

Lecciones Aprendidas

Lo Bueno

  1. Type safety elimina bugs en producción
  2. Virtual Threads simplifican concurrencia
  3. Spring Boot acelera desarrollo
  4. Records reducen boilerplate

Los Retos

  1. Menos ejemplos de AI/LLM en Java
  2. Curva de aprendizaje de Embabel
  3. Ecosistema ML más limitado

Tips

  • Usar Java 21+ para features modernos
  • Configurar timeouts agresivos para LLMs
  • Implementar circuit breakers
  • Cachear responses cuando sea posible

Conclusiones

Java no solo es viable para AI Agents, sino que ofrece ventajas significativas:

  • Type safety que previene errores
  • Performance superior con Virtual Threads
  • Ecosistema maduro para producción
  • Mantenibilidad a largo plazo

El proyecto StudyBuddy demuestra que podemos construir herramientas AI útiles y robustas sin sacrificar las ventajas del ecosistema JVM.

Recursos

Código Fuente

Referencias

Deja un comentario