Wie Grab AI Agents zur Steigerung der Produktivität nutzt: Mit Beispielen

Zu diesem Artikel gibt es ein kurzes Video:TikTok

1. Übersicht

Viele große Technologieunternehmen wie Grab wechseln von „Chatbots zur Beantwortung von Fragen” zu „AI Agents, die eigenständig handeln”, um die technische Produktivität zu steigern: selbstständige Code-Reviews, automatische Pull-Request-Zusammenfassungen, autonome Incident-Triage. Dieser Beitrag erklärt, worin sich ein AI Agent fundamental von einem einfachen LLM-Call unterscheidet, stellt die verbreitete Planner-Tool-Executor-Architektur vor und zeigt dir, wie du mit Java 21 + Spring AI deinen eigenen Agent aufbaust. Danach kannst du selbst einen einfachen Agent schreiben, der echte Tools (Datei-Zugriff, interne API-Aufrufe) nutzt, und weißt, wann Agents nicht in die Produktion gehören.

2. Wie unterscheidet sich ein AI Agent von einem einfachen LLM-Call

Ein einfacher LLM-Call nimmt einen Prompt entgegen und gibt Text zurück – ohne Zustand, ohne Aktion. Ein AI Agent hat dagegen drei Hauptmerkmale:

  • Autonome Planung (Planning): zerlegt eine große Anforderung in kleinere Schritte.
  • Tool-Aufrufe (Tool Calling / Function Calling): der Agent kann APIs aufrufen, Dateien lesen, Queries ausführen – nicht nur Text generieren.
  • Observe-Act-Loop: der Agent beobachtet das Tool-Ergebnis, entscheidet über den nächsten Schritt und wiederholt dies, bis die Aufgabe erledigt ist.

Praktisches Beispiel in großen Engineering-Teams: Der Agent liest den Diff eines Pull Requests, ruft das Tool „Coding-Konventionen des Teams abrufen” auf, ruft das Tool „statische Analyse durchführen” auf und erstellt dann einen Review-Kommentar – alles ohne dass jemand auf die einzelnen Schritte warten muss.

Hinweis: Falls deine Aufgabe nur aus „Text zusammenfassen” oder „Sprache übersetzen” besteht, reicht ein einfacher LLM-Call aus. Nicht jeden API-Aufruf zu OpenAI als „Agent” bezeichnen – unnötige Komplexität macht das System schwer zu debuggen.

3. Grundarchitektur eines Agents: Planner – Tool – Executor

Das häufigste Muster besteht aus drei Komponenten:

User request
   │
   ▼
[Planner]  -- erzeugt eine Liste der erforderlichen Schritte oder wählt das nächste Tool
   │
   ▼
[Tool Executor] -- ruft echte Tools auf: APIs, Datenbanken, Dateisystem
   │
   ▼
[Observation] -- Tool-Ergebnis wird zum Planner zurückgegeben
   │
   └── wiederholt bis der Planner "fertig" signalisiert

Der Planner ist typischerweise ein LLM, dem ein „Menü” von Tools bereitgestellt wird (Name, Beschreibung, Parameter-Schema). Das LLM führt keinen Code aus – es entscheidet nur, welches Tool mit welchen Parametern aufgerufen wird; dein Code ist der eigentliche Ausführer.

3.1. Erforderliche Dependencies

Spring AI bietet eine elegante Abstraktion für dieses Muster:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini

4. Praktisches Beispiel: Code-Review-Agent

Angenommen, du möchtest einen Agent, der einen Diff eines Order-Service erhält, Konventionen überprüft und Verbesserungen vorschlägt. Zuerst deklarierst du die Tools, die der Agent aufrufen darf – in diesem Fall das Tool zum Auslesen von Coding-Konventionen aus einer internen Datei.

@Component
public class ConventionTool {

    @Tool(description = "Ruft die aktuellen Coding-Konventionen des Teams für eine Sprache ab")
    public String getConvention(String language) {
        return switch (language.toLowerCase()) {
            case "java" -> "Record für DTOs verwenden; kein @Autowired auf Feldern; Logging mit SLF4J.";
            default -> "Keine speziellen Konventionen für diese Sprache definiert.";
        };
    }
}
  • @Tool ist eine Spring-AI-Annotation, die diese Methode als Funktion kennzeichnet, die das LLM aufrufen kann.
  • description ist das, was das LLM liest, um zu entscheiden, „ob dieses Tool aufgerufen werden soll” – klar schreiben führt zu besseren Entscheidungen des LLM.

Danach kommt das Service-Orchester – der Planner + Executor:

@Service
public class CodeReviewAgent {

    private final ChatClient chatClient;

    public CodeReviewAgent(ChatClient.Builder builder, ConventionTool conventionTool) {
        this.chatClient = builder
                .defaultTools(conventionTool)
                .build();
    }

    public String review(String diff) {
        return chatClient.prompt()
                .system("Du bist ein Code-Reviewer. Nutze Tools, um Konventionen zu überprüfen, bevor du kommentierst.")
                .user("Überprüfe den folgenden Diff:\n" + diff)
                .call()
                .content();
    }
}
  • defaultTools(conventionTool) registriert das Tool für alle Anfragen des chatClient.
  • Wenn du review(diff) aufrufst, sendet Spring AI automatisch ein „Tool-Menü” mit dem Prompt an das Modell. Wenn das Modell beschließt, getConvention("java") aufzurufen, führt das Framework automatisch die entsprechende Java-Methode aus, holt das Ergebnis und sendet es zurück an das Modell, damit es die finale Antwort generiert – die gesamte Schleife läuft in einem einzigen call()-Befehl.

So wird es aufgerufen:

@RestController
@RequestMapping("/api/review")
public class ReviewController {

    private final CodeReviewAgent agent;

    public ReviewController(CodeReviewAgent agent) {
        this.agent = agent;
    }

    @PostMapping
    public String review(@RequestBody String diff) {
        return agent.review(diff);
    }
}

Typische Ausgabe:

Das Team verlangt Records für DTOs und SLF4J zum Logging.
In diesem Diff ist die OrderDto-Klasse noch eine normale Klasse mit Gettern/Settern –
sollte zu einem Record konvertiert werden. Zeile 42 verwendet System.out.println statt Logger,
muss gemäß Konventionen geändert werden.

Der Schlüsselpunkt: Du schreibst nicht die Logik „wenn normale Klasse, dann Record empfehlen” – das Modell folgert selbst anhand der Konventionen, die es von Tools bezieht. Das ist warum Agents „Produktivität steigern”: Reviews hängen nicht von manuellen Konventions-Erinnerungen ab.

5. Agent mit mehreren Tools: PR-Summary-Agent

In der Praxis rufen Agents in großen Teams oft mehrere Tools nacheinander auf – beispielsweise: Liste der geänderten Dateien abrufen, Test-Coverage erfassen, dann erst den PR für Reviewer zusammenfassen.

@Component
public class PullRequestTools {

    private final GitService gitService;
    private final CoverageService coverageService;

    public PullRequestTools(GitService gitService, CoverageService coverageService) {
        this.gitService = gitService;
        this.coverageService = coverageService;
    }

    @Tool(description = "Ruft die Liste der geänderten Dateien in einem PR nach ID ab")
    public List<String> listChangedFiles(long prId) {
        return gitService.changedFiles(prId);
    }

    @Tool(description = "Ruft den Test-Coverage-Prozentsatz einer Datei ab")
    public double getCoverage(String filePath) {
        return coverageService.coverageOf(filePath);
    }
}
  • Zwei unabhängige Tools, jedes hat eine kleine, gut testbare Aufgabe.
  • Das Modell entscheidet selbst über die Reihenfolge: ruft normalerweise listChangedFiles zuerst auf, dann getCoverage für jede zurückgegebene Datei – das ist die „Observe-Act”-Schleife aus Abschnitt 3.
@Service
public class PullRequestSummaryAgent {

    private final ChatClient chatClient;

    public PullRequestSummaryAgent(ChatClient.Builder builder, PullRequestTools tools) {
        this.chatClient = builder.defaultTools(tools).build();
    }

    public String summarize(long prId) {
        return chatClient.prompt()
                .system("Fasse den PR zusammen: Anzahl geänderter Dateien, welche Dateien haben Coverage unter 70% und brauchen Aufmerksamkeit.")
                .user("Fasse PR ID " + prId + " zusammen")
                .call()
                .content();
    }
}

Dieser Agent-Typ löst das Produktivitätsproblem richtig: der Reviewer muss nicht jede Datei öffnen, um die Coverage zu überprüfen – der Agent hat alles bereits in einer Zusammenfassung aggregiert.

6. Testen von Agents

Der schwierigste Teil beim Produktiveinsatz von Agents ist nicht, das LLM aufzurufen, sondern sicherzustellen, dass Tools richtig aufgerufen werden, wenn sich der Input ändert. Deshalb solltest du Unit-Tests für Tools (deterministisch) von Integrationstests für Agents (mit Mock ChatClient) trennen.

class ConventionToolTest {

    private final ConventionTool tool = new ConventionTool();

    @Test
    void shouldReturnJavaConvention() {
        String result = tool.getConvention("Java");

        assertThat(result)
                .contains("Record")
                .contains("SLF4J");
    }

    @Test
    void shouldReturnDefaultMessageForUnknownLanguage() {
        String result = tool.getConvention("cobol");

        assertThat(result).isEqualTo("Keine speziellen Konventionen für diese Sprache definiert.");
    }
}
  • Einen Tool-Test schreibst du wie einen gewöhnlichen Methoden-Test, ohne LLM-Mock – das ist die Teststrecke, die du 100% kontrollierst.
  • Für die Agent-Tests mit echtem LLM solltest du „Contract-Level” testen: assert, dass die Antwort erforderliche Schlüsselwörter enthält (z.B. „Record”, „Coverage”), vermeide exakte String-Assertions, da LLM-Ausgaben zwischen Aufrufen schwach variieren können.
@Test
void reviewShouldMentionConventionKeyword() {
    String diff = "class OrderDto { private String id; }";

    String result = codeReviewAgent.review(diff);

    assertThat(result.toLowerCase()).containsAnyOf("record", "konvention");
}

Hinweis: Tests, die echte LLM-Calls machen, sind teuer und langsam – trenne diese in eine separate Test-Suite (z.B. Tag @Tag("llm-integration")) auf und führe sie nur in einer separaten Pipeline aus, nicht bei jedem Commit.

7. Häufige Fehler beim Agent-Deployment

  • Vage Tool-Beschreibungen: Wenn die Tool-Beschreibung unklar ist, wählt das Modell das falsche Tool oder ruft es gar nicht auf, wenn es sollte. Schreibe Beschreibungen wie Javadoc für jemanden, der sie zum ersten Mal liest.
  • Keine Schleifenbegrenzung: Der Agent kann in einer Endlosschleife von Tool-Aufrufen steckenbleiben, wenn die Endbedingung unklar ist. Setze immer maxIterations oder Timeouts auf der Orchestrierungsebene.
  • Agent hat Schreibzugriff auf sensible Daten: Gib deinem Agent kein Tool wie „Deploy auf Produktion” oder „Daten löschen” ohne Bestätigung durch einen Menschen. Agents sollten breite Lese-Rechte und enge Schreib-Rechte haben.
  • Fehlende Logging von Tool-Aufrufen: Wenn der Agent falsch antwortet, musst du wissen, welche Tools mit welchen Parametern aufgerufen wurden, um zu debuggen. Ohne dieses Logging ist Error-Tracing praktisch unmöglich.
  • Agent für Probleme mit einfachen Regeln verwenden: Wenn die Logik „wenn X dann Y” festgelegt ist, ist normaler Code schneller, billiger und leichter zu testen als ein Agent.

8. Wann man Agents nicht verwenden sollte

Agents funktionieren gut für Aufgaben mit vielen variablen Schritten, die Informationen aus mehreren Quellen aggregieren müssen und kleine Fehler tolerieren (Review-Vorschläge, Zusammenfassungen). Agents sind nicht geeignet für:

  • Aufgaben mit absoluter Genauigkeit (Geldberechnungen, Finanztransaktionen).
  • Aufgaben mit festem, wenig änderndem Prozess – hier sind Rule Engines oder Workflow Engines (Camunda, Temporal) stabiler und leichter zu auditen.
  • Systeme, die niedrige Latenz benötigen, da jede Tool-Call-Schleife einen zusätzlichen Round-Trip zum LLM kostet.

9. Fazit

AI Agents bieten klare Produktivitätsgewinne, wenn sie richtig eingesetzt werden: bei wiederholten Engineering-Aufgaben mit vielen Schritten, die Informationen aus verschiedenen Quellen aggregieren – Code-Review, PR-Zusammenfassung, Error-Triage. Anstatt einen All-in-One-Agent zu bauen, fang mit wenigen kleinen, klaren Tools an, die eigene Tests haben, und lass den Planner (LLM) entscheiden, wie er sie orchestriert. Am wichtigsten ist die Trennung von Berechtigungen: Gib deinem Agent breite Lesezugriffe, begrenzte Schreibzugriffe, und behalte immer menschliche Reviewer für Aktionen mit großen Auswirkungen.

AI AgentSpring AIProduktivitätLLMAutomatisierung