Inicio / Artículos / Diagnosticar fallos de builds de CI con reglas, RAG y un LLM local

Diagnosticar fallos de builds de CI con reglas, RAG y un LLM local

Un sistema en Java para analizar fallos de CI que procesa los logs de forma determinista, recupera notas de troubleshooting de PGVector con Ollama y después pide a un LLM que razone.

1539 palabras

Un servidor de CI es excelente en una cosa: anunciar que un build se ha roto. Lo que casi nunca explica es lo que importa a los ingenieros cuando un pipeline de producción se pone en rojo. ¿Por qué se rompió? ¿Dónde está el defecto real? ¿Qué hay que cambiar? ¿Y podría el conocimiento técnico acumulado afinar el diagnóstico?

De eso trata este artículo: un analizador de fallos construido en torno a esas preguntas. El plan no es pegar un log en bruto en un LLM y preguntarle qué falló. El diseño combina lógica de ingeniería determinista, búsqueda semántica, generación aumentada por recuperación (RAG) y el razonamiento del modelo. El desarrollo es incremental: primero el análisis de logs, luego agentes que entienden el repositorio y después correcciones automáticas.

Qué produce el sistema

Ante un build fallido, el analizador pasa el log por un pipeline, que aquí se resume a grandes rasgos.

CI/CD Build
     |
     v
 Build Log
     |
     v
Log Preprocessing
     |
     v
Deterministic Analysis
     |
     +------------------+
     |                  |
     v                  v
Structured Data       RAG
     |                  |
     |                  v
     |          Relevant Knowledge
     |                  |
     +--------+---------+
              |
              v
          LLM Reasoning
              |
              v
       Build Diagnosis

La salida es estructurada. Indica el tipo de fallo, el componente afectado, la ubicación en el código fuente, el stack trace, la causa raíz, una corrección recomendada y un valor de confianza.

Primero los hechos, después el modelo

La primera decisión de arquitectura tuvo que ver con el orden. La versión ingenua le pasa el log directamente al modelo.

Build Log
    |
    v
   LLM
    |
    v
Root Cause

El problema es grande. El modelo tiene que deducirlo todo a partir de un log que puede estar lleno de ruido. El analizador, en cambio, extrae primero todo lo que puede establecerse con certeza. Pensemos en un log con una marca de build fallido, una excepción de puntero nulo lanzada al validar un pago y un frame de la pila que apunta a una clase del servicio de pagos.

BUILD FAILURE
java.lang.NullPointerException:
Cannot invoke "PaymentValidator.validate()"
because "validator" is null
at com.example.payment.PaymentService.process(
    PaymentService.java:47)

Un parser determinista puede leer de ahí el estado, la categoría del error y el archivo y la línea exactos.

{
  "status": "FAILED",
  "errorType": "RUNTIME_ERROR",
  "sourceLocation": "PaymentService.java:47",
  "stackTrace": "..."
}

No hay nada adivinado: todo sale del log. Con esos hechos en la mano, el LLM puede centrarse en interpretarlos en lugar de hacer el parseo básico. De ahí sale una fórmula guía.

Deterministic Analysis
          +
     RAG Knowledge
          +
     LLM Reasoning
          =
Context-Aware Diagnosis

Añadir recuperación

La siguiente pregunta era si se podía traer conocimiento técnico relevante antes de pedirle al modelo que diagnosticara nada. Ahí entra RAG. Se escribió una pequeña base de conocimiento con notas de troubleshooting, un archivo Markdown por tema: excepciones de puntero nulo, excepciones de argumento ilegal, inyección de dependencias en Spring, etcétera.

knowledge/
├── null-pointer-exceptions.md
├── illegal-argument-exceptions.md
├── spring-dependency-injection.md
├── database-connection-failures.md
├── kafka-failures.md
└── maven-build-failures.md

Cada nota cubre patrones de fallo típicos, causas, pasos de diagnóstico y soluciones recomendadas. La ingesta convierte los archivos Markdown en vectores sobre los que se puede buscar.

Markdown Documents
        |
        v
Document Loader
        |
        v
Chunking
        |
        v
Embedding Model
        |
        v
PostgreSQL + PGVector

En el momento de la consulta, el fallo se reformula como una consulta semántica y se compara con esos vectores.

Build Failure
      |
      v
Create Semantic Query
      |
      v
Embedding Model
      |
      v
PGVector Similarity Search
      |
      v
Relevant Knowledge

Lo que devuelva puede servir de contexto para el modelo.

Un stack deliberadamente local

La primera versión debía ser local y modesta. Funciona con PostgreSQL y la extensión PGVector, Ollama y LangChain4j. PostgreSQL vive en un contenedor Docker. Los embeddings los genera un modelo que Ollama sirve en local.

nomic-embed-text

Cada embedding es un vector numérico largo.

768-dimensional embeddings

Como el conocimiento se convierte en vectores, se puede buscar por similitud semántica en lugar de por palabras clave.

Primera prueba de que la recuperación funciona

Una de las primeras pruebas usó una consulta en lenguaje corriente que describía el bug.

PaymentValidator is null when processing a payment

La consulta se convirtió en embedding y se envió a PGVector. Volvió la nota sobre punteros nulos, con una similitud de aproximadamente este valor.

0.8006

El texto devuelto explicaba que una NullPointerException aparece cuando el código usa una referencia a objeto que contiene null. También se recuperó el ejemplo de inyección en Spring, una clase de servicio con un campo que nunca llega a conectarse.

@Service
public class PaymentService {
    private PaymentValidator validator;    public void process(Payment payment) {
        validator.validate(payment);
    }
}

Junto a él llegó el consejo de pasar a la inyección por constructor. El hito era importante porque el sistema ya no se limitaba a guardar documentos. Encontraba conocimiento relevante según el significado de la consulta.

Chunks en lugar de documentos enteros

Al principio, cada archivo Markdown se convertía en un único embedding. Para una prueba de concepto vale; para producción, no. El flujo previsto divide los documentos grandes en chunks más pequeños.

Large Document
      |
      v
    Chunks
      |
      +----> Embedding
      |
      +----> Embedding
      |
      +----> Embedding
      |
      +----> Embedding
      |
      v
   PGVector

Con chunks, la recuperación devuelve los fragmentos de conocimiento más relevantes en lugar de un archivo entero. Se está probando un chunking recursivo con solapamiento, para que el contexto importante no se corte en los bordes de cada chunk.

El stack, pieza a pieza

  • Java 21 con Spring Boot forma un backend corriente que expone endpoints REST.
  • LangChain4j conecta la aplicación Java con el modelo de embeddings, el LLM y el almacén vectorial.
  • Llama 3.2 a través de Ollama se ejecuta en local, así que el proyecto no depende por completo de una API alojada.
  • Un modelo nomic-embed-text, también vía Ollama, convierte documentos y consultas en vectores.
  • PostgreSQL con PGVector guarda los embeddings y responde a las búsquedas por similitud.
  • Docker aloja PostgreSQL y PGVector durante el desarrollo local.

Lo que ya funciona

La detección cubre fallos de compilación, problemas de dependencias de Maven, fallos de tests automatizados y errores en tiempo de ejecución, con análisis específico para excepciones de puntero nulo y de argumento ilegal. La extracción se ocupa de componentes, ubicaciones en el código, stack traces y limpieza de logs con colores ANSI.

En la parte de razonamiento hay respuestas estructuradas del LLM, análisis de causa raíz, consejos de corrección y recomendaciones basadas en la confianza.

La capa de conocimiento incluye la base de conocimiento en Markdown, la ingesta, el chunking, la generación local de embeddings, el almacenamiento en PostgreSQL y PGVector, la búsqueda por similitud y la recuperación del material de troubleshooting pertinente.

Conectar la recuperación con el modelo

El siguiente hito une el conocimiento recuperado con el LLM. El flujo objetivo empieza con un fallo y termina en una recomendación.

Build Failure
                       |
                       v
                Rule-Based Analysis
                       |
                       v
                 Structured Data
                       |
                       v
                  RAG Retrieval
                       |
                       v
              Relevant Knowledge
                       |
                       v
                  Llama 3.2
                       |
                       v
              Context-Aware Diagnosis

En lugar de hacerle al modelo una pregunta abierta sobre el build,

"What is wrong with this build?"

el analizador le entregará en un mismo paquete el fallo, su tipo de error y el conocimiento recuperado.

Build failure
+
Error type
+
Component
+
Source location
+
Stack trace
+
Retrieved engineering knowledge

Después se le pide al modelo que razone sobre ese contexto.

Hacia un agente de ingeniería

RAG es una etapa de un diseño más largo. Hoy el camino es corto.

Build Log
    ↓
Diagnosis

La forma final es mucho más rica.

Build Failure
      |
      v
   Diagnosis
      |
      v
Repository Analysis
      |
      v
Find Relevant Code
      |
      v
Generate Fix
      |
      v
Create Git Branch
      |
      v
Create Pull Request
      |
      v
Run CI
      |
      +------ Failed ------+
      |                    |
      |                    v
      |               Investigate
      |                    |
      +--------------------+
      |
      +------ Passed ------> Successful Fix

Ahí aparece un agente de ingeniería con IA, que podría usar herramientas para inspeccionar un repositorio, ejecutar operaciones de Git, buscar en la base de conocimiento, hablar con Jenkins, lanzar builds y tests y abrir pull requests. La diferencia clave es que un agente así no se limitaría a dar una respuesta. Investigaría, actuaría, observaría el resultado y lo volvería a intentar.

Lecciones hasta ahora

La ingeniería con IA útil requiere algo más que llamar a un LLM. Un sistema que funciona tiene varias capas.

Software Engineering
        +
Data
        +
Retrieval
        +
Models
        +
Tools
        +
Validation

La capa determinista importa. También la recuperación y la calidad de la base de conocimiento. Lo más importante es poder comprobar una recomendación generada por IA. Por eso se construye paso a paso en lugar de saltar directamente a un agente autónomo.

Para un desarrollador backend en Java, un proyecto así saca a la superficie las preguntas de ingeniería: no cómo llamar a la API de un LLM, sino cómo encajar la IA en un flujo de trabajo de software real. Abarca integración de LLM, embeddings, bases de datos vectoriales, RAG, búsqueda semántica, ingeniería asistida por IA, flujos agénticos, corrección automática e integración con CI/CD. El desarrollo continúa, pero la progresión ya está clara.

Build Logs
    ↓
Deterministic Analysis
    ↓
LLM Reasoning
    ↓
Embeddings
    ↓
PGVector
    ↓
Semantic Retrieval
    ↓
RAG
    ↓
Repository Intelligence
    ↓
AI Agent
    ↓
Automated Remediation

Esa escalera que va de los logs al diagnóstico y, con el tiempo, a la acción marca la dirección de lo que viene.