Startseite / Artikel / Fehlgeschlagene CI-Builds diagnostizieren: Regeln, RAG und ein lokales LLM

Fehlgeschlagene CI-Builds diagnostizieren: Regeln, RAG und ein lokales LLM

Ein Java-System zur Analyse von CI-Fehlern, das Logs deterministisch parst, Troubleshooting-Notizen per Ollama aus PGVector holt und erst dann ein LLM nachdenken lässt.

1539 Wörter

Ein CI-Server kann eine Sache hervorragend: melden, dass ein Build kaputt ist. Was er selten erklärt, ist genau das, was Engineers interessiert, sobald eine Produktions-Pipeline rot wird. Warum ist es kaputtgegangen? Wo steckt der eigentliche Defekt? Was muss sich ändern? Und könnte früheres Engineering-Wissen die Diagnose schärfen?

Um einen Fehleranalysator, der um diese Fragen herum gebaut ist, geht es hier. Ein rohes Log in ein LLM zu kippen und zu fragen, was schiefgelaufen ist, ist nicht der Plan. Das Design kombiniert deterministische Engineering-Logik, semantische Suche, Retrieval-Augmented Generation (RAG) und das Schlussfolgern des Modells. Entwickelt wird schrittweise: erst Log-Analyse, dann Agents, die das Repository kennen, dann automatische Fixes.

Was das System liefert

Bei einem fehlgeschlagenen Build schickt der Analysator das Log durch eine Pipeline, hier grob skizziert.

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

Die Ausgabe ist strukturiert. Sie nennt den Fehlertyp, die betroffene Komponente, die Stelle im Quellcode, den Stacktrace, die Grundursache, einen empfohlenen Fix und einen Konfidenzwert.

Erst Fakten, dann das Modell

Die früheste Architekturentscheidung betraf die Reihenfolge. Die naive Variante reicht das Log direkt ans Modell weiter.

Build Log
    |
    v
   LLM
    |
    v
Root Cause

Der Haken daran ist groß. Das Modell muss sich alles aus einem Log erschließen, das voller Rauschen sein kann. Der Analysator zieht stattdessen zuerst alles heraus, was sich mit Sicherheit feststellen lässt. Nehmen wir ein Log mit einer Build-Failure-Markierung, einer NullPointerException beim Validieren einer Zahlung und einem Stackframe, der auf eine Klasse des Zahlungsdienstes zeigt.

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

Ein deterministischer Parser kann daraus den Status, die Fehlerkategorie sowie die genaue Datei und Zeile ablesen.

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

Geraten wird dabei nichts, alles stammt direkt aus dem Log. Bekommt das LLM diese Fakten mitgeliefert, kann es sich aufs Interpretieren konzentrieren, statt grundlegendes Parsing zu erledigen. Daraus ergibt sich eine Leitformel.

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

Retrieval kommt dazu

Die nächste Frage war, ob sich relevantes Engineering-Wissen abrufen lässt, bevor das Modell überhaupt etwas diagnostizieren soll. Hier kommt RAG ins Spiel. Es entstand eine kleine Wissensbasis mit Troubleshooting-Notizen, eine Markdown-Datei pro Thema: NullPointerExceptions, IllegalArgumentExceptions, Dependency Injection in Spring und so weiter.

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

Jede Notiz behandelt typische Fehlermuster, Ursachen, Diagnoseschritte und empfohlene Lösungen. Beim Ingest werden die Markdown-Dateien zu durchsuchbaren Vektoren.

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

Zur Abfragezeit wird der Fehler als semantische Suchanfrage formuliert und mit diesen Vektoren abgeglichen.

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

Was dabei zurückkommt, kann dem Modell als Kontext dienen.

Ein bewusst lokaler Stack

Die erste Version sollte lokal und bescheiden bleiben. Sie läuft auf PostgreSQL mit der Erweiterung PGVector, Ollama und LangChain4j. PostgreSQL lebt in einem Docker-Container. Die Embeddings stammen von einem Modell, das Ollama lokal bereitstellt.

nomic-embed-text

Jedes Embedding ist ein langer numerischer Vektor.

768-dimensional embeddings

Weil das Wissen zu Vektoren wird, lässt es sich nach semantischer Ähnlichkeit statt nach Schlüsselwörtern durchsuchen.

Erster Beweis, dass Retrieval funktioniert

Ein früher Test nutzte eine Anfrage in Alltagssprache, die den Bug beschrieb.

PaymentValidator is null when processing a payment

Die Anfrage wurde eingebettet und an PGVector geschickt. Zurück kam die Troubleshooting-Notiz zu NullPointerExceptions mit einer Ähnlichkeit von ungefähr diesem Wert.

0.8006

Der zurückgegebene Text erklärte, dass eine NullPointerException auftritt, wenn Code eine Objektreferenz verwendet, die null enthält. Ebenfalls gefunden wurde das Spring-Injection-Beispiel, eine Service-Klasse mit einem Feld, das nie verdrahtet wird.

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

Dazu kam der Rat, auf Constructor Injection umzusteigen. Der Meilenstein war wichtig, weil das System nicht mehr nur Dokumente speicherte. Es fand relevantes Wissen anhand der Bedeutung einer Anfrage.

Chunks statt ganzer Dokumente

Anfangs wurde jede Markdown-Datei zu einem einzigen Embedding. Für einen Proof of Concept reicht das, für Produktion nicht. Der geplante Ablauf zerlegt große Dokumente in kleinere Chunks.

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

Mit Chunks liefert das Retrieval die relevantesten Wissensstücke statt einer ganzen Datei. Ausprobiert wird rekursives Chunking mit Überlappung, damit wichtiger Kontext nicht an Chunk-Grenzen abgeschnitten wird.

Der Stack im Einzelnen

  • Java 21 mit Spring Boot bildet ein gewöhnliches Backend mit REST-Endpunkten.
  • LangChain4j verbindet die Java-Anwendung mit dem Embedding-Modell, dem LLM und dem Vector Store.
  • Llama 3.2 über Ollama läuft lokal, sodass das Projekt nicht vollständig von einer gehosteten API abhängt.
  • Ein nomic-embed-text-Modell, ebenfalls über Ollama, wandelt Dokumente und Anfragen in Vektoren um.
  • PostgreSQL mit PGVector speichert Embeddings und beantwortet Ähnlichkeitssuchen.
  • Docker hostet PostgreSQL und PGVector während der lokalen Entwicklung.

Was schon funktioniert

Die Erkennung deckt Kompilierfehler, Maven-Abhängigkeitsprobleme, fehlgeschlagene automatisierte Tests und Laufzeitfehler ab, mit eigener Analyse für NullPointerExceptions und IllegalArgumentExceptions. Die Extraktion kümmert sich um Komponenten, Quellcodestellen, Stacktraces und das Bereinigen ANSI-farbiger Logs.

Auf der Reasoning-Seite gibt es strukturierte LLM-Antworten, Ursachenanalyse, Behebungsvorschläge und Empfehlungen mit Konfidenzwert.

Die Wissensschicht umfasst die Markdown-Wissensbasis, Ingest, Chunking, lokale Embedding-Erzeugung, Speicherung in PostgreSQL und PGVector, Ähnlichkeitssuche und das Abrufen passender Troubleshooting-Inhalte.

Retrieval ans Modell anschließen

Der nächste Meilenstein verbindet das abgerufene Wissen mit dem LLM. Der Zielablauf beginnt mit einem Fehler und endet mit einer Empfehlung.

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

Statt dem Modell eine offene Frage zum Build zu stellen,

"What is wrong with this build?"

übergibt der Analysator den Fehler, seinen Fehlertyp und das abgerufene Wissen als Paket.

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

Dann soll das Modell über diesen Kontext nachdenken.

Auf dem Weg zum Engineering-Agent

RAG ist eine Etappe eines längeren Entwurfs. Heute ist der Weg kurz.

Build Log
    ↓
Diagnosis

Die angestrebte Form ist deutlich reichhaltiger.

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

Damit kommt ein KI-Engineering-Agent ins Spiel, der Tools nutzen könnte, um ein Repository zu untersuchen, Git-Operationen auszuführen, die Wissensbasis zu durchsuchen, mit Jenkins zu sprechen, Builds und Tests zu starten und Pull Requests zu öffnen. Der entscheidende Unterschied: Ein solcher Agent würde nicht einfach eine Antwort ausgeben. Er würde untersuchen, handeln, das Ergebnis beobachten und es erneut versuchen.

Bisherige Erkenntnisse

Nützliches KI-Engineering ist mehr als ein LLM-Aufruf. Ein funktionierendes System hat mehrere Schichten.

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

Die deterministische Schicht zählt. Das Retrieval ebenso und auch die Qualität der Wissensbasis. Am wichtigsten ist aber, eine KI-generierte Empfehlung überprüfen zu können. Deshalb wird Schritt für Schritt gebaut, statt direkt zu einem autonomen Agent zu springen.

Für Java-Backend-Entwickler holt so ein Projekt die Engineering-Fragen an die Oberfläche: nicht, wie man eine LLM-API aufruft, sondern wie KI in einen echten Softwareprozess passt. Es umfasst LLM-Integration, Embeddings, Vektordatenbanken, RAG, semantische Suche, KI-gestütztes Engineering, agentische Workflows, automatische Behebung und CI/CD-Integration. Die Entwicklung geht weiter, aber die Richtung ist bereits klar.

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

Diese Leiter von Logs zur Diagnose und schließlich zur Aktion gibt die Richtung für das Kommende vor.