Inicio / Artículos / Notas prácticas: Gemini para desarrolladores de Go: Creación de agentes en Go

Notas prácticas: Gemini para desarrolladores de Go: Creación de agentes en Go

Guía paso a paso práctica: Gemini para desarrolladores de Go: Creación de agentes en Go: contratos, verificaciones y espacios para código reutilizable para equipos que implementan este patrón.

4187 palabras

Esta guía reconstruye el proceso desde las materias primas hasta un sistema funcional para: Gemini for Go Developers: Building Agents in Go. Se centra en pasos operativos claros, verificaciones explícitas y código que se puede incorporar directamente a un repositorio sin tener que adivinar su propósito. En la fase de descripción general, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin necesidad de adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos confidenciales y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

La anatomía de un agente

Al trabajar en “La anatomía de una etapa”, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Registre el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen bugs de la aplicación.

Diseño del agente: el Evaluador de Juegos Retro

Al trabajar en la etapa Retro del diseño del agente, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Registre el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen bugs de la aplicación.

Capacidades e interacción con el usuario

Al trabajar en la etapa de capacidades e interacción con el usuario, primero escribe el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Considera esta etapa como un contrato entre las entradas y las salidas validadas. Nombra los artefactos, define las comprobaciones de éxito y rechaza las completaciones parciales silenciosas. Registra el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen bugs de la aplicación. Al trabajar en la etapa de capacidades e interacción con el usuario, primero escribe el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Mantén la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el código.

Contratos de herramientas

La etapa de contratos de herramientas funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el de recuperación. Los intentos repetidos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Fije el intérprete y el archivo de bloqueo de dependencias antes de explicar el bucle. La diferencia entre la computadora portátil y los entornos de integración continua es la causa más común de fallos silenciosos en las demostraciones de API.

Estrategia de razonamiento

La etapa de estrategia de razonamiento funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Fije el intérprete y el archivo de bloqueo de dependencias antes de enseñar el bucle. La diferencia entre la computadora portátil y los entornos de integración continua es la causa más común de fallos silenciosos en las demostraciones de API.

Implementación del agente con el Go GenAI SDK

La implementación del agente con etapas funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Fije el intérprete y el archivo de bloqueo de dependencias antes de enseñar el bucle. La diferencia entre la computadora portátil y el entorno de integración continua es la causa más común de fallos silenciosos en las demostraciones de API. La implementación del agente con etapas funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

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!

Marcos de desarrollo de agentes

En la fase de los marcos de desarrollo de agentes, se deben definir las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Se debe documentar tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Se debe separar la construcción del cliente del bucle de mensajes para que sea posible cambiar los proveedores sin tener que reescribir la máquina de estados de la conversación.

Implementación del agente con Genkit

Para implementar el agente con etapas, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Separe la construcción del cliente del bucle de mensajes para que sea posible cambiar los proveedores sin tener que reescribir la máquina de estados de la conversación.

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

Ejecutando el flujo de Genkit

En la etapa de ejecución del flujo Genkit, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Separe la construcción del cliente del bucle de mensajes para que sea posible cambiar los proveedores sin tener que reescribir la máquina de estados de la conversación. En la etapa de ejecución del flujo Genkit, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el grafo.

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

Implementación del agente con el Agent Development Kit (ADK)

Al trabajar en la fase de implementación del agente, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestos los cambios posteriores en el código. Documente tanto la ruta normal como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Registre el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen bugs de la aplicación.

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

Ejecución del agente ADK

Al trabajar en la fase de ejecución del agente ADK, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Registre el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen bugs de la aplicación.

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

Tiempos de ejecución del agente

Al trabajar en la etapa de tiempos de ejecución del agente, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y evite completaciones parciales silenciosas. Registre el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen bugs de la aplicación. Al trabajar en la etapa de tiempos de ejecución del agente, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el código.

Cloud Run: el punto óptimo universal para backends

Cloud Run funciona mejor como una plataforma medible. Capture un registro ideal, un caso de fallo y las notas para el rollback antes de ampliar el alcance. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Fije el intérprete y el archivo de bloqueo de dependencias antes de implementar los ciclos. La diferencia entre el entorno del portátil y el CI es la causa más común de fallos silenciosos en las demostraciones de API.

Gemini Enterprise Agent Platform: sesiones gestionadas y RAG empresarial

La plataforma Gemini Enterprise Agent funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad en lugar de a un proceso complicado. Fije el intérprete y el archivo de bloqueo de dependencias antes de explicar el bucle. La diferencia entre usar una computadora portátil y un entorno CI es la causa más común de fallos silenciosos en las demostraciones de API.

¿Qué sigue?

La etapa “¿Qué sigue?” funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Fije el intérprete y el archivo de bloqueo de dependencias antes de enseñar el bucle. La diferencia entre usar una computadora portátil y un entorno CI es la causa más común de fallos silenciosos en las demostraciones de API. La etapa “¿Qué sigue?” funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

Lista de verificación operativa

La etapa de lista de verificación operativa funciona mejor cuando se trata como un indicador medible. Consiga una transcripción de referencia, un caso de fallo y la nota de reversión antes de ampliar el alcance.

Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas al pasar de la versión de demostración a entornos compartidos.

Fije el intérprete y el archivo de bloqueo de dependencias antes de explicar los bucles. Las diferencias entre la computadora portátil y el entorno de integración continua son la causa más común de fallos silenciosos en las demostraciones de API.

Deje que un humano apruebe las operaciones que generan gastos o modifican datos de producción. La configuración en tiempo de compilación no equivale a una solución completa para el negocio.

Escriba un breve manual de operaciones: cómo rotar claves, cómo vaciar la cola y cómo revertir la última inserción.

Preferir unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado lleno de dependencias.

Antes de promocionar la tecnología, congele las versiones, guarde una transcripción de referencia para el camino crítico y confirme los pasos para revertir cambios. Los entornos compartidos requieren límites de velocidad, verificaciones de asignación y un responsable claro para la rotación de credenciales secretas. Es mejor optar por una fiabilidad sencilla que por demostraciones ingeniosas pero únicas.

Nota para el lote 05c61bdde7aa: mantenga las claves del proveedor fuera del repositorio, establezca un límite para los tokens por sesión y almacene las transcripciones junto a los archivos de prueba para que los cambios en los modelos posteriores sigan siendo comparables.

Para la fase 0 de las notas de fortalecimiento, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado.

Detalle de fortalecimiento 0/951: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en observaciones anecdóticas.

Al trabajar en la primera etapa de las notas de reforzamiento, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos.

Detalle de reforzamiento 1/951: mida el tiempo real empleado, la clase del error y el gasto en tokens para esta nota, y luego decida si mantener la modificación basándose en un conjunto fijo de criterios en lugar de en observaciones anecdóticas.

Lecturas relacionadas