Startseite / Artikel

Erstellung eines Gemini-Agenten in Go: Vergleich von Raw SDK, Genkit und ADK

Ein Experte für Retro-Spielbewertungen hat mit dem GenAI SDK, Genkit sowie dem Agent Development Kit drei Methoden in Go entwickelt, zusammen mit Anleitungen dazu, wie man jede dieser Methoden auf Cloud Run oder auf Googles Agentenplattform ausführt.

Verlag: REACTAPP.TOP19 Min. Lesezeit4462 Wörter

Quellen: Gemini for Go Developers: Building Agents in Go

Die Entwicklung von mit KI ausgestatteten Anwendungen und autonomen Agenten in Go kann auf drei verschiedenen Abstraktionsebenen erfolgen. In dieser Übersicht werden die Mechanismen eines Agenten erläutert, ein konkretes Anwendungsbeispiel – ein Bewertungsprogramm für Retro-Spiele – ausgewählt, und dieses anschließend dreimal implementiert:

  1. Ein Agenten-Loop auf niedriger Ebene, der direkt mit dem Go GenAI SDK geschrieben wird.
  2. Ein strukturierter, flussorientierter Pipeline, der mit Genkit erstellt wird.
  3. Ein modulares Mehr-Agenten-System mit Sitzungen, das auf Google’s Agent Development Kit (ADK) basiert.

Am Ende werden die Laufzeiten aufgezeigt, die es ermöglichen, Go-Agenten zuverlässig in die Cloud zu deployen.

Was macht etwas zu einem Agenten

Der Begriff wird oft locker verwendet, doch in der KI-Entwicklung hat er eine recht präzise architektonische Bedeutung: ein autonomes System mit drei Komponenten – einem LLM, einer Reihe von Werkzeugen wie aufrufbaren Funktionen oder APIs sowie einer Steuerungseinheit, die alles innerhalb eines Feedbackloops ausführt.

Ein Modell ohne Werkzeuge ist lediglich ein Textgenerator. Es kann nur auf der Grundlage seiner festen Trainingsgewichte oder dessen antworten, was im Prompt enthalten ist. Um ihm Handlungsfähigkeit zu verleihen – also die Fähigkeit, den externen Zustand zu überprüfen, Hypothesen zu testen und im realen Welt etwas zu ändern – muss es mit ausführbaren Werkzeugen verbunden werden.

Ein architektonisches Detail fällt leicht übersehen. Das Modell führt selbst niemals Code aus oder ruft externe APIs auf. Der lokale Handler, hier ein Go-Programm, befindet sich dazwischen. Wenn das Modell entscheidet, dass es externe Informationen oder eine Aktion benötigt, sendet es eine strukturierte Anfrage zur Tool-Aufrufung. Der Handler führt die entsprechende Go-Funktion aus, speichert die Ausgabe und leitet das Ergebnis zurück an das Modell für die nächste Verarbeitungsrunde. Anschließend betrachtet das Modell den neuen Kontext und ruft entweder ein weiteres Tool auf oder gibt seine endgültige Antwort ab.

Nicht jedes Tool läuft lokal. Gemini bietet eingebaute Tools wie Google Search Grounding, die auf Serverseite ausgeführt werden. Sobald sie in der Anfragekonfiguration deklariert sind, verarbeitet die API die Suchanfragen und fügt Metadaten zur Verifizierung ohne lokale Kommunikationsschleife zur Antwort hinzu. Für benutzerdefinierte Domänenlogik hingegen übernimmt das Go-Framework die gesamte Steuerung: Es deklariert das Schema, leitet Argumente weiter und gibt serialisierte Ergebnisse zurück.

Der Anwendungsfall: Ein Bewertungsprogramm für Retro-Spiele

Jeder, der Retro-Videospiele sammelt, kennt dieses bekannte Problem. Auf einem Retro-Markt ist es einfach, eine Kopie eines geliebten Spiels zu kaufen, nur um zu Hause festzustellen, dass es bereits im Regal steht. Zudem sind die Preise in diesem Hobbybereich stark schwankend, es gibt verschiedene Zustandsstufen (loose Cartridge, komplett in der Verpackung oder CIB, fabrikneu versiegelt) sowie häufig Fälschungen im Angebot.

Es stellt sich als überraschend nützlich heraus, beim Stehen an einem Stand in Echtzeit den Anfragedurchschnittspreis bei Gemini zu überprüfen. Viele Käufer sind bereit, einen angemessenen Aufpreis für das persönliche Erlebnis und zur Unterstützung lokaler Verkäufer zu zahlen, doch niemand möchte betrogen werden oder zu viel für eine gefälschte Spielkonsole ausgeben.

Der Bewertungsagent berücksichtigt beide Seiten: Er vergleicht den persönlichen Bestand mit aktuellen Webdaten, um einen fairen Marktwert zu ermitteln.

Wie ein Sammler es verwendet

Der Sammler gibt einfache Fragen ein, wie zum Beispiel:

  • Eine Anfrage, ob ein bestimmtes Spiel, beispielsweise Chrono Trigger, bereits im Bestand vorhanden ist.
  • Ein in Originalverpackung befindliches EarthBound für SNES zum Preis von 350 Dollar – mit der Frage, ob es bereits im Besitz ist und ob der Preis unter den aktuellen Marktwerten liegt.
  • Eine Anfrage nach dem Kaufpreis für Castlevania: Symphony of the Night sowie ob sein Marktwert seitdem gestiegen ist.
  • Vertragswerkzeuge

    Um ohne Erfindung von Inventarlisten oder Preisen antworten zu können, stützt sich der Agent auf zwei zuverlässige Quellen:

    1. search_catalog ist eine lokale Go-Funktion, die die eigene Datenbank des Sammlers abruft. Sie sucht nach Schlüsselwörtern in Titeln und Plattformen und gibt anschließend Eigentumsverhältnisse, Zustand, Erwerbsdatum sowie Kaufpreis an.
    2. google_search ist ein auf der Serverseite angesiedeltes Werkzeug zur Überprüfung. Es durchsucht Marktplätze und Auktionstracker nach aktuellen fairen Werten, kürzlich bestätigten Verkäufen sowie Vergleichswerten für den Zustand.

    Strategie der Argumentation

    Für einen möglichen Kauf arbeitet der Agent nach einer festen Abfolge. Zunächst prüft er den lokalen Katalog, um herauszufinden, ob das Spiel bereits vorhanden ist und in welchem Zustand es sich befindet. Anschließend sucht er im Internet nach aktuellen Preisen für diese Plattform und diesen Zustandsgrad. Schließlich fasst er die Erkenntnisse zu einer Bewertung zusammen: Verkaufspreis im Vergleich zum Marktdurchschnitt, Möglichkeiten zur Verbesserung wie der Austausch einer lose vorhandenen Cartridge gegen eine in der Verpackung, sowie eine klare Empfehlung.

    Version eins: das Go GenAI SDK

    Durch die direkte Nutzung des SDKs (google.golang.org/genai) wird das niedrigste Abstraktionsniveau erreicht, wobei ein eins-zu-eins-Bezug zum Wire-Protocol der Gemini-API hergestellt wird. Da es kein Framework zwischen Ihrem Code und der API gibt, können Sie selbst die Steuerung der Tool-Auswahl, den Konversationsverlauf sowie die Kriterien für das Beenden des Schleifens festlegen.

    Es eignet sich für kleine Projekte und Hilfsskripte, für alle, die herausfinden möchten, wie Funktionsaufrufe und -implementierungen tatsächlich funktionieren, sowie für Fälle mit sehr spezifischen Schleifenanforderungen, die von Standardframeworks nicht abgedeckt werden. Das Ergebnis wird zu einem eigenständigen Go-Binärdatei kompiliert, das keine Framework-Abhängigkeiten hat, sodass es auf herkömmlichen Web- oder API-Hosting-Diensten wie Google Cloud Run, Kubernetes oder einer einfachen Virtual Machine bereitgestellt werden kann.

    Die vollständige, ausführbare Implementierung:

    package main
    
    import (
     "bufio"
     "context"
     "fmt"
     "log"
     "os"
     "os/signal"
     "strings"
     "syscall"
    
     "google.golang.org/genai"
    )
    
    // GameItem represents a collectible item in the user's personal inventory.
    type GameItem struct {
     Title     string  `json:"title"`
     Platform  string  `json:"platform"`
     Year      int     `json:"year"`
     Condition string  `json:"condition"` // e.g. "Loose Cartridge", "CIB (Complete in Box)", "Mint"
     PricePaid float64 `json:"price_paid"`
     Notes     string  `json:"notes"`
    }
    
    // localCatalog simulates an inventory database for retro games.
    var localCatalog = []GameItem{
     {
      Title:     "Chrono Trigger",
      Platform:  "Super Nintendo (SNES)",
      Year:      1995,
      Condition: "CIB (Complete in Box)",
      PricePaid: 210.00,
      Notes:     "Includes original map and registration card.",
     },
     {
      Title:     "EarthBound",
      Platform:  "Super Nintendo (SNES)",
      Year:      1994,
      Condition: "Loose Cartridge",
      PricePaid: 180.00,
      Notes:     "Authentic board verified; label in excellent shape.",
     },
     {
      Title:     "Castlevania: Symphony of the Night",
      Platform:  "Sony PlayStation",
      Year:      1997,
      Condition: "CIB (Black Label)",
      PricePaid: 135.00,
      Notes:     "Original soundtrack disc included.",
     },
    }
    
    // searchCatalogTool searches the local collection for matching games.
    func searchCatalogTool(args map[string]any) map[string]any {
     query, _ := args["query"].(string)
     queryLower := strings.ToLower(strings.TrimSpace(query))
    
     var matches []GameItem
     for _, item := range localCatalog {
      if strings.Contains(strings.ToLower(item.Title), queryLower) ||
       strings.Contains(strings.ToLower(item.Platform), queryLower) {
       matches = append(matches, item)
      }
     }
    
     if len(matches) == 0 {
      return map[string]any{
       "found":   false,
       "message": fmt.Sprintf("No items matching %q found in your collection.", query),
      }
     }
    
     return map[string]any{
      "found":   true,
      "count":   len(matches),
      "results": matches,
     }
    }
    
    func main() {
     ctx := context.Background()
    
     // Initialise GenAI client for Gemini Enterprise
     client, err := genai.NewClient(ctx, &genai.ClientConfig{
      Project:  os.Getenv("GOOGLE_CLOUD_PROJECT"),
      Location: "global",
      Backend:  genai.BackendEnterprise,
     })
     if err != nil {
      log.Fatalf("failed to create client: %v", err)
     }
    
     // 1. Declare custom function schema for collection lookup
     catalogToolDecl := &genai.FunctionDeclaration{
      Name:        "search_catalog",
      Description: "Search the collector's personal inventory for owned games by title or platform.",
      Parameters: &genai.Schema{
       Type: genai.TypeObject,
       Properties: map[string]*genai.Schema{
        "query": {
         Type:        genai.TypeString,
         Description: "Game title or platform to search (e.g. 'EarthBound', 'SNES').",
        },
       },
       Required: []string{"query"},
      },
     }
    
     // 2. Configure model tools: custom function declaration + Google Search grounding
     config := &genai.GenerateContentConfig{
      SystemInstruction: &genai.Content{
       Parts: []*genai.Part{
        {Text: "You are an expert Retro Game Appraiser. When evaluating purchases, check the user's " +
         "collection catalog first to see if they already own the item, then check current market " +
         "prices using Google Search to evaluate whether the deal is fair, overpriced, or a bargain."},
       },
      },
      Tools: []*genai.Tool{
       {
        FunctionDeclarations: []*genai.FunctionDeclaration{catalogToolDecl},
       },
       {
        GoogleSearch: &genai.GoogleSearch{},
       },
      },
     }
    
     // 3. Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM
     sigChan := make(chan os.Signal, 1)
     signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
     go func() {
      <-sigChan
      fmt.Println("\nGoodbye!")
      os.Exit(0)
     }()
    
     model := "gemini-3.8-flash"
     var contents []*genai.Content
    
     fmt.Println("Retro Game Appraiser (SDK Agent)")
     fmt.Println("Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.")
     fmt.Println("-----------------------------------------------------------------")
    
     scanner := bufio.NewScanner(os.Stdin)
     for {
      fmt.Print("\nUser: ")
      if !scanner.Scan() {
       fmt.Println("\nGoodbye!")
       break
      }
    
      input := strings.TrimSpace(scanner.Text())
      if input == "" {
       continue
      }
      if strings.EqualFold(input, "exit") {
       fmt.Println("Goodbye!")
       break
      }
    
      contents = append(contents, &genai.Content{
       Role:  "user",
       Parts: []*genai.Part{genai.NewPartFromText(input)},
      })
    
      // 4. The Agent Loop: model generation -> tool dispatch -> feedback -> until final answer
      for {
       resp, err := client.Models.GenerateContent(ctx, model, contents, config)
       if err != nil {
        log.Printf("error generating content: %v", err)
        break
       }
    
       if len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil {
        log.Println("received empty response candidate from model")
        break
       }
    
       // Append the model's response to the conversation history
       modelContent := resp.Candidates[0].Content
       contents = append(contents, modelContent)
    
       // Check if the model requested any client-side tool executions
       funcCalls := resp.FunctionCalls()
       if len(funcCalls) == 0 {
        fmt.Printf("\nAppraiser: %s\n", resp.Text())
        break
       }
    
       // Execute each requested tool and prepare response parts
       var responseParts []*genai.Part
       for _, call := range funcCalls {
        fmt.Printf("[Harness] Executing tool: %s(args=%v)\n", call.Name, call.Args)
    
        var result map[string]any
        switch call.Name {
        case "search_catalog":
         result = searchCatalogTool(call.Args)
        default:
         result = map[string]any{"error": fmt.Sprintf("unsupported tool: %s", call.Name)}
        }
    
        responseParts = append(responseParts, genai.NewPartFromFunctionResponse(call.Name, result))
       }
    
       // Return tool execution results as a user turn
       contents = append(contents, &genai.Content{
        Role:  "user",
        Parts: responseParts,
       })
      }
     }
    }
    

    Um es auszuführen, konfigurieren Sie ein Google Cloud-Projekt und melden Sie sich mit den Standardanmeldeinformationen der Anwendung unter Verwendung von gcloud auth application-default login an:

    export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
    go run main.go
    

    In der Terminal öffnet sich ein interaktiver Chat. Das Gespräch kann mehrere Wechsel zwischen den Parteien umfassen und endet, wenn Sie exit eingeben, Ctrl+C drücken oder mit Ctrl+D EOF senden:

    Retro Game Appraiser (SDK Agent)
    Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.
    -----------------------------------------------------------------
    
    User: I found a copy of EarthBound for SNES in mint Complete-in-Box (CIB) condition for $350. Do I already own it, and is $350 a good deal compared to current market prices?
    [Harness] Executing tool: search_catalog(args=map[query:EarthBound])
    
    Appraiser: Here is your collection check and appraisal for **EarthBound (SNES)**:
    
    1. **Current Collection Status**:
       - You currently own **EarthBound** on Super Nintendo as a **Loose Cartridge**, purchased for **$180.00**.
    
    2. **Market Price Appraisal**:
       - Verified market sales for an authentic, **Complete-in-Box (CIB)** copy of EarthBound typically range between **$1,200.00 and $1,500.00** depending on the condition of the box, tray, and original player's guide.
    
    3. **Recommendation**:
       - At **$350.00**, a genuine Mint CIB copy is an **exceptional deal** (more than 70% below prevailing market value).
       - **Caution**: Because EarthBound is one of the most heavily counterfeited SNES titles, inspect the box printing, registration card, and PCB board carefully before completing the transaction. If verified authentic, this is an outstanding opportunity to upgrade your loose copy to CIB.
    
    User: exit
    Goodbye!
    

    Die SDK-Version macht den Ablauf explizit und leicht verständlich. Dank leistungsstarker Modelle und großer Token-Fenster ist es erstaunlich einfach, die notwendigen Strukturen, Tool-Dispatch-Schleifen sowie unterstützende Komponenten selbst zu entwickeln.

    Diese Einfachheit ist jedoch eine Falle. Jede Zeile an selbst entwickeltem Framework-Code wird zu einer dauerhaften Wartungskostenquelle und kann außerdem Ort für subtile Fehler sein. Das Erstellen eines Agent-Frameworks von Grund auf lenkt im besten Fall von der Umsetzung neuer Funktionen ab, im schlimmsten Fall entsteht eine Menge technischer Schulden, die Ressourcen von den eigentlichen Geschäftszielen abziehen. Der beste Code ist kein Code – der nächstbeste ist alles, was das Problem mit möglichst wenig angepasstem Code löst.

    Warum auf ein Framework wechseln?

    Die reine SDK-Schleife ist für das Erlernen von Grundmechanismen oder für maßgeschneiderte Schleifen kaum zu übertreffen. Produktionsanwendungen benötigen jedoch aus mehreren Gründen ein höheres Abstraktionsniveau:

    • Automatische Schema-Reflexion. Das Manuell-Erstellen von JSON-Schemata mit &genai.Schema{...} ist zeitaufwendig und fehleranfällig. Produktionsframeworks erzeugen Schemata aus nativen Go-Strukturen sowie Dokumentationskommentaren.
    • Überwachbarkeit und Tracing. Für den Produktivbetrieb sind OpenTelemetry-Traces, detaillierte Latenzanalysen pro Tool sowie Metriken zur Tokenverbrauch sofort verfügbar.
    • Prompt-Verwaltung. Als Go-Strings fest codierte Prompts erschweren die Zusammenarbeit mit Prompt-Engineern und verhindern eine getrennte Versionierung von Templates von binären Releases.
    • Sitzungspersistenz und Zustand. Um mehrstufige Dialoghistorien über stateless HTTP-Anfragen hinweg zu speichern, ist thread-sichere, entkoppelte Speicherung erforderlich.
  • Modellportabilität. Das Go SDK ist auf Gemini spezialisiert, während Frameworks es ermöglichen, Anbieter zu wechseln oder lokale Modelle auszuprobieren, ohne die Geschäftslogik anzufassen.
  • Das Go-Ecosystem bietet zwei Haupt-Open-Source-Frameworks für diese Anforderungen: Genkit und das Agent Development Kit.

    Version zwei: Genkit

    Genkit verfügt über ein mittleres Abstraktionsniveau und bringt Disziplin aus der Softwareentwicklung sowie strukturierte Überwachungsfähigkeiten in KI-Anwendungen ein. Alles ist um Flüsse organisiert, die stark typisiert und überwachbare Pipelines sind, sowie um Tools, die typsichere Go-Funktionen mit automatischer Schema-Generierung sind.

    Es eignet sich für einmalige Generierungsaufgaben wie Kommandozeilenwerkzeuge, Batchverarbeitung und webhook-Endpunkte sowie für deterministische Workflows und Agenten für einzelne Domänen, wie beispielsweise Pipelines zur multimodalen Fotorestaurierung. Über Plugins werden mehrere Modellanbieter unterstützt. Da eine Genkit-App lediglich ein standardmäßiger Go-HTTP-Server ist, läuft sie auf jeder Backend-Plattform – wobei Google Cloud Run aufgrund der einfachen Container-Hosting-Möglichkeiten und der automatischen Skalierung der bevorzugte Zielort ist.

    Der mit Genkit Go geschriebene Appraiser:

    package main
    
    import (
     "context"
     "fmt"
     "log"
     "net/http"
     "os"
     "os/signal"
     "strings"
     "syscall"
     "time"
    
     "github.com/firebase/genkit/go/ai"
     "github.com/firebase/genkit/go/genkit"
     "github.com/firebase/genkit/go/plugins/googlegenai"
     "google.golang.org/genai"
    )
    
    // GameItem represents a collectible item in the user's personal inventory.
    type GameItem struct {
     Title     string  `json:"title"`
     Platform  string  `json:"platform"`
     Year      int     `json:"year"`
     Condition string  `json:"condition"`
     PricePaid float64 `json:"price_paid"`
     Notes     string  `json:"notes"`
    }
    
    // localCatalog simulates an inventory database for retro games.
    var localCatalog = []GameItem{
     {
      Title:     "Chrono Trigger",
      Platform:  "Super Nintendo (SNES)",
      Year:      1995,
      Condition: "CIB (Complete in Box)",
      PricePaid: 210.00,
      Notes:     "Includes original map and registration card.",
     },
     {
      Title:     "EarthBound",
      Platform:  "Super Nintendo (SNES)",
      Year:      1994,
      Condition: "Loose Cartridge",
      PricePaid: 180.00,
      Notes:     "Authentic board verified; label in excellent shape.",
     },
     {
      Title:     "Castlevania: Symphony of the Night",
      Platform:  "Sony PlayStation",
      Year:      1997,
      Condition: "CIB (Black Label)",
      PricePaid: 135.00,
      Notes:     "Original soundtrack disc included.",
     },
    }
    
    type CatalogRequest struct {
     Query string `json:"query" jsonschema:"description=The game title or platform to search in the inventory"`
    }
    
    type CatalogResponse struct {
     Found   bool       `json:"found"`
     Message string     `json:"message,omitempty"`
     Count   int        `json:"count,omitempty"`
     Results []GameItem `json:"results,omitempty"`
    }
    
    type AppraiserRequest struct {
     Prompt string `json:"prompt" jsonschema:"description=The collector's question or purchase offer to evaluate"`
    }
    
    type AppraiserResponse struct {
     Appraisal string `json:"appraisal"`
    }
    
    func main() {
     ctx := context.Background()
    
     // 1. Initialise Genkit with Vertex AI plugin
     g := genkit.Init(ctx,
      genkit.WithPlugins(&googlegenai.VertexAI{
       ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
       Location:  "global",
      }),
     )
    
     // 2. Define strongly-typed tool with automatic schema generation
     catalogTool := genkit.DefineTool(
      g,
      "search_catalog",
      "Search the collector's personal inventory for owned games by title or platform.",
      func(ctx *ai.ToolContext, req CatalogRequest) (CatalogResponse, error) {
       queryLower := strings.ToLower(strings.TrimSpace(req.Query))
       queryWords := strings.Fields(queryLower)
       var matches []GameItem
    
       for _, item := range localCatalog {
        itemText := strings.ToLower(item.Title + " " + item.Platform)
        allMatch := true
        for _, word := range queryWords {
         if !strings.Contains(itemText, word) {
          allMatch = false
          break
         }
        }
        if allMatch {
         matches = append(matches, item)
        }
       }
    
       if len(matches) == 0 {
        return CatalogResponse{
         Found:   false,
         Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query),
        }, nil
       }
    
       return CatalogResponse{
        Found:   true,
        Count:   len(matches),
        Results: matches,
       }, nil
      },
     )
    
     // 3. Define structured appraisal flow with typed request and response
     appraiserFlow := genkit.DefineFlow(
      g,
      "appraise_game",
      func(ctx context.Context, req AppraiserRequest) (AppraiserResponse, error) {
       resp, err := genkit.Generate(ctx, g,
        ai.WithModelName("vertexai/gemini-3.8-flash"),
        ai.WithSystem(
         "You are an expert Retro Game Appraiser. Assist collectors by evaluating prospective purchases, "+
          "cross-referencing their personal inventory, and assessing fair market valuations. "+
          "Always search the collection catalog using search_catalog before providing purchase recommendations.",
        ),
        ai.WithConfig(&genai.GenerateContentConfig{
         ThinkingConfig: &genai.ThinkingConfig{IncludeThoughts: true},
         Tools: []*genai.Tool{
          {
           GoogleSearch: &genai.GoogleSearch{},
          },
         },
        }),
        ai.WithPrompt(req.Prompt),
        ai.WithTools(catalogTool),
       )
       if err != nil {
        return AppraiserResponse{}, fmt.Errorf("appraisal generation failed: %w", err)
       }
       return AppraiserResponse{Appraisal: resp.Text()}, nil
      },
     )
    
     // 4. Mount flow directly using Genkit's built-in HTTP handler
     mux := http.NewServeMux()
     mux.Handle("POST /api/appraise", genkit.Handler(appraiserFlow))
    
     port := os.Getenv("PORT")
     if port == "" {
      port = "8080"
     }
    
     server := &http.Server{
      Addr:    ":" + port,
      Handler: mux,
     }
    
     // Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM
     serverCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
     defer stop()
    
     go func() {
      log.Printf("Retro Game Appraiser (Genkit) listening on :%s", port)
      if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
       log.Fatalf("server failed: %v", err)
      }
     }()
    
     <-serverCtx.Done()
     log.Println("\nShutting down server gracefully...")
    
     shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
     defer cancel()
    
     if err := server.Shutdown(shutdownCtx); err != nil {
      log.Fatalf("server forced shutdown: %v", err)
     }
     log.Println("Server exited cleanly.")
    }
    

    Ausführung des Workflows

    Starten Sie den Genkit-Server mit Ihrem konfigurierten Google Cloud-Projekt:

    export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
    export PORT=8080
    go run main.go
    

    Von einem anderen Terminal aus senden Sie eine Bewertungsanfrage:

    curl -s -X POST http://localhost:8080/api/appraise \
      -H "Content-Type: application/json" \
      -d '{"data": {"prompt": "I found a copy of EarthBound for SNES for $350. Do I own it, and is it a good deal?"}}' | jq .
    

    Der Ablauf wird ausgeführt, ruft das Katalogwerkzeug auf und gibt eine strukturierte Bewertungspayload zurück:

    {
      "appraisal": "### 1. Catalog Check\n**Yes, you already own it.**\n* **Title:** *EarthBound* (SNES, 1994)\n* **Status in Collection:** Loose Cartridge\n* **Condition/Notes:** Authentic board verified; label in excellent shape.\n* **Price Paid:** $180\n\n---\n\n### 2. Market Appraisal & Deal Analysis\n* **Loose Cartridge:** The current going market rate for an authentic loose copy ranges between **$320 and $380**. At **$350**, it is priced right at **fair market value**—neither an overpriced listing nor a significant bargain.\n* **Complete in Box (CIB) / Boxed with Guide:** If this listing happens to include the original big box and strategy guide with scratch-and-sniff cards, $350 would be an extraordinary steal (CIB copies regularly sell for **$1,500–$2,500+**).\n\n---\n\n### 3. Recommendation\n* **Pass (if Loose):** Since you already have an authentic copy in excellent condition, paying retail market price ($350) for a duplicate loose cart does not offer strong value or upside.\n* **Buy immediately (if Complete/Boxed):** Only pull the trigger if it includes the original packaging or represents a major condition upgrade/variant.\n* **Buyer Beware:** If you do ever consider another copy, always inspect the PCB (printed circuit board) screws and chips, as *EarthBound* is one of the most frequently counterfeited games on the SNES."
    }
    

    Genkit beseitigt den manuell geschriebenen Dispatch-Loop. Es wandelt Argumente in native Go-Strukturen um, führt das Tool aus, gibt die Payload an das Modell zurück und erzeugt automatisch OpenTelemetry-Spans für jeden Schritt. Es ist die ideale Lösung, wenn die Arbeitslast einem strukturierten, deterministischen Ablauf folgt.

    Version drei: das Agent Development Kit

    Während Genkit auf strukturierte Anwendungs-Pipelines abzielt, arbeitet Googles ADK auf einer höheren Abstraktionsstufe. Es wurde für autonome konversationelle Agenten, Multi-Agent-Orchestrierung sowie komplexe Architekturen entwickelt, die langfristiges Gedächtnis und enterprise RAG benötigen.

    Das Kit standardisiert außerdem die Agent-Lebenszyklen, die Delegation von Subagenten sowie das Agent-zu-Agenten-(A2A)-Kommunikationsprotokoll und kann wie Genkit über Adapter mehrere Modellanbieter nutzen.

    Zur Bereitstellung ist die Gemini Enterprise Agent Platform die bevorzugte Option, wenn Sie möchten, dass Google die Sitzungsspeicherung, Unternehmensanbindungen sowie den Speicher verwaltet, sodass Sie keine eigenen Datenbankschichten programmieren müssen. Teams, die eigene Zustandsspeicher oder konteinerisierte Microservices nutzen, kommen besser mit Google Cloud Run zurecht.

    Der Bewertungstool in ADK v2:

    package main
    
    import (
     "context"
     "fmt"
     "log"
     "os"
     "strings"
    
     "google.golang.org/genai"
    
     "google.golang.org/adk/v2/agent"
     "google.golang.org/adk/v2/agent/llmagent"
     "google.golang.org/adk/v2/cmd/launcher"
     "google.golang.org/adk/v2/cmd/launcher/full"
     "google.golang.org/adk/v2/model/gemini"
     "google.golang.org/adk/v2/tool"
     "google.golang.org/adk/v2/tool/functiontool"
     "google.golang.org/adk/v2/tool/geminitool"
    )
    
    // GameItem represents a collectible item in the user's personal inventory.
    type GameItem struct {
     Title     string  `json:"title"`
     Platform  string  `json:"platform"`
     Year      int     `json:"year"`
     Condition string  `json:"condition"`
     PricePaid float64 `json:"price_paid"`
     Notes     string  `json:"notes"`
    }
    
    // localCatalog simulates an inventory database for retro games.
    var localCatalog = []GameItem{
     {
      Title:     "Chrono Trigger",
      Platform:  "Super Nintendo (SNES)",
      Year:      1995,
      Condition: "CIB (Complete in Box)",
      PricePaid: 210.00,
      Notes:     "Includes original map and registration card.",
     },
     {
      Title:     "EarthBound",
      Platform:  "Super Nintendo (SNES)",
      Year:      1994,
      Condition: "Loose Cartridge",
      PricePaid: 180.00,
      Notes:     "Authentic board verified; label in excellent shape.",
     },
     {
      Title:     "Castlevania: Symphony of the Night",
      Platform:  "Sony PlayStation",
      Year:      1997,
      Condition: "CIB (Black Label)",
      PricePaid: 135.00,
      Notes:     "Original soundtrack disc included.",
     },
    }
    
    type CatalogRequest struct {
     Query string `json:"query" jsonschema:"The game title or platform to search in the inventory."`
    }
    
    type CatalogResponse struct {
     Found   bool       `json:"found"`
     Message string     `json:"message,omitempty"`
     Count   int        `json:"count,omitempty"`
     Results []GameItem `json:"results,omitempty"`
    }
    
    func main() {
     ctx := context.Background()
    
     // 1. Initialise Gemini Model adapter for Gemini Enterprise
     model, err := gemini.NewModel(ctx, "gemini-3.8-flash", &genai.ClientConfig{
      Project:  os.Getenv("GOOGLE_CLOUD_PROJECT"),
      Location: "global",
      Backend:  genai.BackendEnterprise,
     })
     if err != nil {
      log.Fatalf("failed to create Gemini model: %v", err)
     }
    
     // 2. Wrap collection lookup as an ADK Function Tool
     catalogTool, err := functiontool.New(functiontool.Config{
      Name:        "search_catalog",
      Description: "Search the collector's personal inventory for owned games by title or platform.",
     }, func(ctx agent.Context, req CatalogRequest) (CatalogResponse, error) {
      queryLower := strings.ToLower(strings.TrimSpace(req.Query))
      var matches []GameItem
    
      for _, item := range localCatalog {
       if strings.Contains(strings.ToLower(item.Title), queryLower) ||
        strings.Contains(strings.ToLower(item.Platform), queryLower) {
        matches = append(matches, item)
       }
      }
    
      if len(matches) == 0 {
       return CatalogResponse{
        Found:   false,
        Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query),
       }, nil
      }
    
      return CatalogResponse{
       Found:   true,
       Count:   len(matches),
       Results: matches,
      }, nil
     })
     if err != nil {
      log.Fatalf("failed to create catalog tool: %v", err)
     }
    
     // 3. Define autonomous LLM Agent
     appraiserAgent, err := llmagent.New(llmagent.Config{
      Name:        "retro_game_appraiser",
      Model:       model,
      Description: "Expert appraiser that analyzes retro video game purchases and collection inventory.",
      Instruction: "You are an expert Retro Game Appraiser. Assist collectors by verifying collection " +
       "status with search_catalog, assessing condition variants, and offering objective buying recommendations.",
      Tools: []tool.Tool{
       catalogTool,
       geminitool.GoogleSearch{},
      },
     })
     if err != nil {
      log.Fatalf("failed to create appraiser agent: %v", err)
     }
    
     // 4. Configure launcher and execute
     config := &launcher.Config{
      AgentLoader: agent.NewSingleLoader(appraiserAgent),
     }
    
     l := full.NewLauncher()
     if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
      log.Fatalf("run failed: %v\n\n%s", err, l.CommandLineSyntax())
     }
    }
    

    Ausführen des ADK-Agenten

    ADK liefert einen universellen Launcher, full.NewLauncher(), sodass es nicht notwendig ist, HTTP-Routing, JSON-Marshalling oder Standardcode für Sessions manuell zu erstellen. Die dem Launcher übergebenen Befehlszeilenargumente wählen den Interaktionsmodus aus, ohne dass der Anwendungscode geändert werden muss.

    Um ein interaktives Terminal-Chat zu starten:

    export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
    go run main.go
    

    Der Launcher startet im Konsole-Modus und der Bewertende kann direkt befragt werden:

    User: Do I have Chrono Trigger in my collection?
    Agent: Yes, you have Chrono Trigger in your collection! Here are the details from your inventory:
    
    * Title: Chrono Trigger
    * Platform: Super Nintendo (SNES)
    * Release Year: 1995
    * Condition: CIB (Complete in Box)
    * Price Paid: $210.00
    * Notes: Includes original map and registration card.
    
    User: What did I pay for it?
    Agent: You paid $210.00 for it.
    

    ADK verwaltet den Konversationszustand sowie die Sitzungskontinuität selbst, sodass nachfolgende Fragen den vollständigen Kontext früherer Äußerungen beibehalten.

    Als Alternative steht eine integrierte Entwickler-Web-Oberfläche zusammen mit einer REST-API zur Verfügung:

    go run main.go web webui api
    

    Durch das Öffnen von http://localhost:8080 wird ein interaktiver Chat mit Echtzeit-Streaming-Antworten, Sitzungshistorie sowie schrittweiser Übersicht über die Ausführung der Tools bereitgestellt – sowohl für das benutzerdefinierte Tool search_catalog als auch für die integrierte Funktion GoogleSearch.

    ADK trennt klar die Agentendefinition (llmagent), die Tool-Bindungen (functiontool sowie eingebaute Funktionen wie geminitool.GoogleSearch) sowie die Ausführungsumgebung (launcher und runner). Dadurch ist es das bevorzugte Framework für komplexe mehr-Agenten-Hierarchien sowie konversationelle Assistenten.

    Wo Go-Agenten ausgeführt werden

    Die Implementierung des Agents in Go ist erst der halbe Weg. Danach benötigt er eine Ausführungsumgebung, die mit langanhaltenden Streaming-Antworten, Hintergrundaufrufen von Tools, sicheren Anmeldeinformationen sowie Spitzenbelastungen ohne Betriebsprobleme umgehen kann. Go kompiliert zu einzelnen, selbstständigen Binären mit nahezu keinem Laufzeitaufwand, wodurch aus ihm erstellte Agents schnell und kostengünstig gehostet werden können. Je nach Framework und Anforderungen an den Zustand gibt es zwei Hauptziele.

    Cloud Run – der universelle Backend-Dienst

    Google Cloud Run ist das primäre Ziel für containerisierte Go-Agenten – unabhängig davon, ob sie mit dem Roh-SDK, Genkit-Flows oder ADK-Runnern erstellt werden. Mehrstufige Docker-Kompilierungen können den Agenten in ein minimales scratch- oder distroless-Image ohne Laufzeitabhängigkeiten kompilieren. Da kein aufwändiger Interpreter oder virtueller Rechner gestartet werden muss, skaliert der Dienst schnell auf Null und vermeidet so die Startverzögerungen, die bei schwereren Laufzeitsystemen auftreten.

    Die Hauptvorteile von Cloud Run für Go-Agenten:

    • Schnelle Skalierung auf Null mit hoher Konkurrenzfähigkeit. Sie zahlen nur für die genutzten CPU-Millisekunden, während der Agent tatsächlich arbeitet oder Tool-Antworten verarbeitet. Gos leichte Goroutinen ermöglichen es einer Containerinstanz, Hunderte von Agentenaktionen gleichzeitig mit geringem Speicherverbrauch zu verarbeiten.
  • Lange Anfragedauerlimits. Standardmäßig beträgt die Zeitbegrenzung für Anfragen 5 Minuten, die auf bis zu 60 Minuten (3.600 Sekunden) verlängert werden können. Dadurch haben mehrstufige Berechnungsschleifen, umfangreiche Suchaufgaben sowie iterative Subagenten-Gruppen genügend Zeit, abgeschlossen zu werden.
  • Zweirichtiger Stream und WebSockets. HTTP/2 chunked Transfer, Server-Sent Events sowie WebSockets werden nativ unterstützt. Für Sprach-zu-Sprach- oder Echtzeit-Multimodal-Agenten über die Gemini Live API ermöglichen WebSockets einen kontinuierlichen zweirichtigen Stream zwischen Client und Server.
  • Sitzungsaffinität. Ein Agent, der zwischen den Anfragen temporäre In-Memory-Caches oder lokalen Kontext speichert, kann mithilfe von --session-affinity – basierend auf der Client-IP oder Cookies – Nachfragen an dieselbe Container-Instanz senden.
  • Zustandslose Widerstandsfähigkeit. In der Produktion sollte der Zustand in externen, verwalteten Datenbanken wie Firestore, Redis oder Cloud SQL gespeichert werden. Nach einem Netzwerkausfall während einer Aktion kann der Agent über seine Interaktions-ID fortfahren, ohne den Kontext zu verlieren.
  • Identität der Arbeitslast ohne Geheimnisse. Es sind keine hardcodierten API-Schlüssel oder Anmeldeinformationen erforderlich, da der Agent sich über das vorhandene Cloud Run-Dienstkonto bei der Gemini Developer API, Vertex AI und Cloud Storage authentifiziert.
  • Gemini Enterprise Agent Platform

    Unterhaltungsorientierte ADK-Agenten sowie Unternehmenslösungen, die Sessions aufrechterhalten, Langzeitgedächtnis nutzen und Antworten auf Unternehmenswissen stützen müssen, können die Gemini Enterprise Agent Platform einsetzen. Sie gilt als Nachfolgerin des Vertex AI Agent Engine und bietet eine vollständig verwaltete, serverlose Ausführungsumgebung. Anstatt Datenbanken bereitzustellen und eigene Adapter für die Session-Speicherung zu entwickeln, erhalten Teams eine verwaltete Infrastruktur mit folgenden Funktionen:

    • Trennung der Session-Persistenz. Die Langzeitspeicherung von Konversationen (SessionService in ADK) ist vom vorübergehenden Streaming-Ausführungslauf (LiveSession, mit automatischen Mechanismen zur Wiederaufnahme von Sessions bei Neuverbindungen) getrennt.
  • Unternehmensweite Verknüpfung und Vektorabfrage. Fertige Anbindungen ermöglichen den Zugriff auf Unternehmens-Wissensdatenbanken wie Google Drive, BigQuery und Intranet-Repositorys sowie auf Vertex AI Vector Search-Indizes, einschließlich speziell für die Speicherung optimierter Ebenen, zur schnellen semantischen Suche.
  • Isolierte Codeausführung. Getrennte Umgebungen ermöglichen es Agenten, Code zu schreiben und auszuführen – beispielsweise für Datenanalyse oder Python- und Go-Skripte – ohne die Host-Infrastruktur zu gefährden.
  • Protokolle zwischen Agenten. Standardisierte Kommunikationsmöglichkeiten ermöglichen es unabhängigen Unternehmensagenten, Fähigkeiten zu entdecken, Schemata auszuarbeiten und Aufgaben über organisatorische Grenzen hinweg zu delegieren.
  • Sicherheit, Agentenidentität und Model Armor. Fein abgestimmte IAM-Berechtigungen für Agenten, VPC Service Controls sowie die laufende Schutzfunktion Model Armor überprüfen Eingaben und Ausgaben auf Prompt-Injection, Datenexfiltration und Verstöße gegen Richtlinien.
  • Wahl zwischen den drei Optionen

    Für das Verständnis von Funktionsaufrufen und -grundlagen oder für kleine Hilfsprogramme reicht der einfache SDK-Loop aus. Für strukturierte, deterministische Workflows oder Agenten in einem einzigen Domänenbereich, die über einen HTTP-Endpunkt zugänglich sind, eignet sich Genkit. Für konversationelle Assistenten, mehrere Agenten in Hierarchien, Delegation und Langzeitgedächtnis bietet ADK eine bessere Grundlage. Cloud Run passt zu allen drei Ansätzen, während die Gemini Enterprise Agent Platform der natürliche Ort für ADK-Agenten ist, die verwaltete Sitzungen, Speicher und unternehmensweite Grundlagen benötigen.