Accueil / Articles / Notes pratiques : Gemini pour les développeurs Go : Création d’agents en Go

Notes pratiques : Gemini pour les développeurs Go : Création d’agents en Go

Guide pratique pas à pas : Gemini pour les développeurs Go – Création d’agents en Go : contrats, vérifications et emplacements de code intégrables pour les équipes utilisant ce modèle.

4187 mots

Ce guide reconstitue le parcours allant des matières premières à un système fonctionnel pour : Gemini for Go Developers : Building Agents in Go. L’accent est mis sur des étapes opérationnelles, des vérifications explicites, ainsi que du code que vous pouvez intégrer directement dans un dépôt sans devoir deviner son intention. Pour l’étape d’aperçu, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.

L’anatomie d’un agent

Lorsque vous travaillez sur « L’anatomie d’une étape », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’améliorations apportées ultérieurement. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence pour chaque appel. Sans cette trace, les erreurs intermittentes du fournisseur ressemblent à des bugs de l’application.

Conception de l’agent : l’évaluateur de jeux retro

Lors de la conception de l’agent pour l’étape Retro, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité et non un processus embrouillé. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence à chaque appel. Sans cette trace, les erreurs intermittentes du fournisseur ressemblent à des bugs de l’application.

Capacités et interaction utilisateur

Lors de l’étape des capacités et de l’interaction utilisateur, écrivez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès, et refusez toute exécution partielle silencieuse. Enregistrez l’ID de la demande, l’ID du modèle et le temps de réponse pour chaque appel. Sans ce suivi, les erreurs intermittentes du fournisseur sont perçues comme des bugs de l’application. Lors de l’étape des capacités et de l’interaction utilisateur, écrivez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans avoir à lire l’ensemble du système.

Contrats des outils

La phase des contrats des outils fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les différences entre l’ordinateur portable et l’environnement CI sont la cause la plus fréquente de dysfonctionnements silencieux lors des démonstrations API.

Stratégie de raisonnement

La phase de stratégie de raisonnement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les variations entre l’ordinateur portable et les environnements CI constituent la cause la plus fréquente d’échecs silencieux lors des démonstrations API.

Mettre en œuvre l’agent avec le Go GenAI SDK

L’implémentation de l’agent en étapes fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définites des vérifications de succès et refusez toute mise en œuvre partielle silencieuse. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les écarts entre l’ordinateur portable et les environnements CI constituent la cause la plus fréquente de dysfonctionnement silencieux dans les démos API. L’implémentation de l’agent en étapes fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans avoir à lire l’ensemble du système.

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,
   })
  }
 }
}
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
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!

Chargements de travail pour le développement des agents

Pendant l’étape des chargements de travail pour le développement des agents, il convient de définir les entrées, le responsable de l’étape ainsi que les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations ultérieures. Séparez la construction du client de la boucle de messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine d’état de la conversation.

Mise en œuvre de l’agent avec Genkit

Pour mettre en œuvre l’agent par étapes, définissez les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Séparez la construction du client de la boucle de messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine d’états de la conversation.

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.")
}

Démarrer le flux Genkit

Pour l’étape « Exécution du flux Genkit », définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Séparez la construction du client du cycle de messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine d’état de la conversation. Pour l’étape « Exécution du flux Genkit », définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du schéma.

/p>
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
export PORT=8080
go run main.go
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 .
{
  "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."
}

Mettre en œuvre l’agent avec le Agent Development Kit (ADK)

Lors de la phase de mise en œuvre de l’agent, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence pour chaque appel. Sans ces traces, les erreurs intermittentes du fournisseur sont perçues comme des bugs de l’application.

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())
 }
}

Démarrer l’agent ADK

Lors de l’étape de mise en œuvre de l’agent ADK, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une responsabilité précise plutôt qu’un processus embrouillé. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence à chaque appel. Sans ces traces, les erreurs intermittentes du fournisseur sont perçues comme des bugs de l’application.

export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
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.
go run main.go web webui api

Temps d’exécution de l’agent

Lors du traitement de l’étape des temps d’exécution des agents, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence à chaque appel. Sans ces traces, les erreurs intermittentes du fournisseur ressemblent à des bugs de l’application. Lors du traitement de l’étape des temps d’exécution des agents, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.

Cloud Run : le point idéal pour les backends universels

Cloud Run, en tant qu’environnement universel, fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemple parfait de fonctionnement, un cas d’échec et des notes de rollback avant d’élargir le périmètre. Documentez à la fois le parcours normal et celui de récupération. Les tentatives répétées, les contrôles manuels et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant de commencer à utiliser les boucles. Les différences entre l’ordinateur portable et l’environnement CI sont la cause la plus fréquente de dysfonctionnements silencieux dans les démos API.

Gemini Enterprise Agent Platform : sessions gérées et RAG d’entreprise

La plateforme Gemini Enterprise Agent fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les variations entre l’ordinateur portable et les environnements CI sont la cause la plus fréquente d’échecs silencieux lors des démonstrations API.

Que faire ensuite ?

La phase « Quel est le prochain pas ? » fonctionne au mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et une note de réversion avant d’élargir le périmètre. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez toute mise en œuvre partielle silencieuse. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les écarts entre l’ordinateur portable et les outils CI sont la cause la plus fréquente de dysfonctionnements silencieux dans les démos API. La phase « Quel est le prochain pas ? » fonctionne au mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et une note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système.

Liste de contrôle opérationnelle

La phase de liste de contrôle opérationnel fonctionne le mieux lorsqu’elle est considérée comme une donnée mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre.

Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration à des environnements partagés.

Sélectionnez l’interpréteur et le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les différences entre l’ordinateur portable et les environnements CI sont la cause la plus fréquente de dysfonctionnement silencieux dans les démonstrations API.

Obtenez l’approbation humaine pour les opérations qui entraînent des dépenses ou modifient des données de production. Un câblage en temps de compilation ne garantit pas une complétude opérationnelle.

Rédigez un petit manuel d’utilisation : comment rotationner les clés, comment vider la file d’attente, comment effectuer une réversion de la dernière ingestion.

Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité et non un processus embrouillé.

Au préalable de promouvoir la pile technologique, figez les versions, conservez une transcription exemplaire pour le chemin critique, et vérifiez les étapes de réversion. Les environnements partagés nécessitent des limites de fréquence, des contrôles d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité banale à des démonstrations originales mais éphémères.

Note de batch pour 05c61bdde7aa : gardez les clés du fournisseur hors du répertoire, fixez un plafond pour les tokens par session, et stockez les transcriptions à côté des fichiers de configuration d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.

Pour l’étape 0 des notes de renforcement, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.

Détail de renforcement 0/951 : mesurez le temps d’exécution, la classe de l’erreur et la consommation de tokens pour cette note, puis décidez si vous conservez le changement en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations subjectives.

Lors de la réalisation de la première étape des notes de renforcement de sécurité, notez d’abord les éléments essentiels : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code.

Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.

Détail 1/951 du renforcement de sécurité : mesurez le temps d’exécution réel, la catégorie de l’erreur et la consommation de jetons pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de critères prédéfinis plutôt que sur des observations subjectives.