Creando un agente Gemini en Go: Comparación entre SDK raw, Genkit y ADK
Un agente tasador de juegos retro desarrolló tres métodos en Go, utilizando el SDK de GenAI, Genkit y el Kit de Desarrollo de Agentes, además de indicaciones sobre cómo ejecutar cada uno en Cloud Run o en la plataforma de agentes de Google.
La creación de aplicaciones con inteligencia artificial y agentes autónomos en Go puede realizarse en tres niveles diferentes de abstracción. Esta explicación detallada analiza la mecánica de un agente, elige un caso de uso concreto: un evaluador de juegos retro, y luego lo desarrolla en tres ocasiones:
- Un bucle de agente de bajo nivel escrito directamente con el SDK GenAI de Go.
- Un pipeline estructurado y orientado al flujo, creado con Genkit.
- Un sistema modular de múltiples agentes con sesiones, desarrollado con el Agent Development Kit (ADK) de Google.
Finalmente, se presentan los entornos de ejecución disponibles para desplegar agentes en Go en la nube de manera fiable.
¿Qué hace que algo sea un agente?
El término se usa de forma casual, pero en la ingeniería de IA tiene un significado arquitectónico bastante preciso: un sistema autónomo compuesto por tres partes: un LLM, un conjunto de herramientas como funciones llamables o APIs, y un mecanismo que ejecuta todo dentro de un bucle de retroalimentación.
Un modelo sin herramientas es simplemente un generador de texto. Solo puede responder a partir de sus pesos de entrenamiento fijos o de lo que se incluye en el prompt. Para dotarlo de autonomía, es decir, la capacidad de inspeccionar el estado externo, probar hipótesis y modificar cosas en el mundo real, es necesario conectarlo con herramientas ejecutables.
Un detalle arquitectónico es fácil de pasar por alto. El modelo nunca ejecuta código ni llama a APIs externas por sí mismo. El mecanismo local, en este caso un programa en Go, se encuentra en el medio. Cuando el modelo decide que necesita información externa o realizar una acción, emite una solicitud estructurada para llamar a una herramienta. El mecanismo ejecuta la función correspondiente en Go, captura la salida y envía el resultado de vuelta al siguiente paso del modelo. Luego, el modelo analiza el nuevo contexto y ya sea llama a otra herramienta o escribe su respuesta final.
No todas las herramientas se ejecutan localmente. Gemini ofrece herramientas integradas como Google Search grounding, que se ejecutan en el servidor. Una vez declaradas en la configuración de la solicitud, la API resuelve las consultas de búsqueda y adjunta metadatos de grounding a la respuesta sin necesidad de realizar viajes locales. Sin embargo, para la lógica de dominio personalizada, el framework Go se encarga de todo: declara el esquema, envía los argumentos y devuelve los resultados serializados.
El caso de uso: un evaluador de videojuegos retro
Cualquiera que coleccione videojuegos retro conoce un problema común. En un mercado de este tipo es fácil comprar una copia de un juego querido para descubrir luego en casa que ya lo tiene en su colección. Además, este hobby se caracteriza por precios volátiles, varios niveles de condición (cartucho suelto, completo en caja o CIB, sellado en fábrica) y anuncios frecuentes de productos falsificados.
Comprobar el precio de venta en tiempo real con Gemini mientras se está en un puesto resulta ser sorprendentemente útil. Muchos compradores están dispuestos a pagar un precio justo por la experiencia presencial y por apoyar a los vendedores locales, pero nadie quiere ser estafado o pagar de más por un cartucho falso.
El agente evaluador cubre ambos aspectos: compara el inventario personal con datos en tiempo real de la web para determinar un valor de mercado justo.
Cómo lo utiliza un coleccionista
El coleccionista escribe preguntas comunes, como:
- Una consulta para saber si un título determinado, por ejemplo Chrono Trigger, ya forma parte de la colección.
- Un EarthBound en perfectas condiciones, con su caja original, para SNES, encontrado a 350 dólares, con la pregunta de si ya está en posesión de alguien y si ese precio es inferior a los valores actuales del mercado.
Contratos de herramientas
Para responder sin inventar inventarios ni precios, el agente se apoya en dos fuentes fiables:
search_cataloges una función local en Go que consulta la propia base de datos del coleccionista. Busca palabras clave en títulos y plataformas, y luego informa sobre la propiedad, el estado, la fecha de adquisición y el precio de compra.google_searches una herramienta de verificación del lado del servidor. Busca en mercados y plataformas de subastas los valores justos actuales, ventas verificadas recientes y puntos de referencia de estado.
Estrategia de razonamiento
Para una posible compra, el agente sigue una secuencia fija. Primero verifica el catálogo local para determinar si el juego ya está en posesión de alguien y en qué condiciones se encuentra. Luego busca en Internet los precios actuales en esa plataforma y nivel de condición. Finalmente, combina los resultados en una evaluación: el precio solicitado en comparación con el promedio del mercado, las oportunidades de mejora, como cambiar un cartucho suelto por una copia en caja, y una recomendación clara.
Versión uno: el Go GenAI SDK
Desarrollar directamente sobre el SDK (google.golang.org/genai) representa el nivel de abstracción más bajo, ya que se mapea uno a uno con el protocolo de comunicación de la API Gemini. Al no haber ningún framework entre tu código y la API, puedes controlar tú mismo el envío de herramientas, el historial de conversaciones y los criterios para detener el bucle.
Funciona para proyectos pequeños y scripts utilitarios, para quienes estudian cómo funcionan realmente las llamadas a funciones y su implementación, y para cualquier caso con requisitos de bucle altamente específicos que los frameworks estándar no cubren. El resultado se compila en un binario Go autónomo sin dependencias de frameworks, por lo que puede desplegarse en servidores web o de API comunes como Google Cloud Run, Kubernetes o una máquina virtual sencilla.
La implementación completa y ejecutable:
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,
})
}
}
}
Para ejecutarlo, configure un proyecto de Google Cloud e inicie sesión con Credenciales predeterminadas de la aplicación utilizando gcloud auth application-default login:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
Se abre un chat interactivo en la terminal. La conversación puede extenderse por varias interacciones y finaliza cuando escribes exit, presionas Ctrl+C o envías EOF con Ctrl+D:
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!
La versión del SDK hace que el flujo de control sea explícito y fácil de seguir. Gracias a los modelos potentes y a las ventanas de tokens amplias, escribir los componentes internos, los bucles de asignación de herramientas y la estructura de soporte por uno mismo resulta sorprendentemente sencillo.
No obstante, esa facilidad es una trampa. Cada línea de código del framework desarrollado en casa se convierte en un costo de mantenimiento permanente y en un posible foco para errores sutiles. Crear un framework para agentes desde cero, en el mejor de los casos, distrae de la implementación de funcionalidades, y en el peor, genera una acumulación de deuda técnica que desvía los esfuerzos del objetivo empresarial real. El mejor código es aquel que no existe, y el siguiente mejor es aquel que resuelve el problema con la menor cantidad posible de código personalizado.
¿Por qué pasar a un framework?
El bucle básico del SDK es difícil de superar para aprender mecánicas de bajo nivel o para crear bucles personalizados, pero las aplicaciones en producción requieren un nivel de abstracción más alto, por varias razones:
- Reflexión automática de esquemas. Escribir esquemas JSON a mano con
&genai.Schema{...}es tedioso y propenso a errores. Los frameworks de producción derivan los esquemas de las estructuras nativas de Go y de los comentarios de documentación. - Observabilidad y trazabilidad. El uso en entornos de producción requiere trazas de OpenTelemetry, desglose de la latencia por herramienta y métricas de consumo de tokens de forma predeterminada.
- Gestión de prompts. Los prompts codificados como cadenas de texto en Go dificultan la colaboración con los ingenieros de prompts y impiden versionar los templates por separado de los lanzamientos binarios.
- Persistencia de sesiones y estado. Mantener historiales de múltiples interacciones a través de solicitudes HTTP sin estado requiere un almacenamiento seguro para hilos y desacoplado.
El ecosistema de Go cuenta con dos frameworks de código abierto principales para estas necesidades: Genkit y el Agent Development Kit.
Versión dos: Genkit
Genkit se encuentra en un nivel intermedio de abstracción y aporta disciplina de ingeniería de software y observabilidad estructurada a las aplicaciones de IA. Todo está organizado en torno a flujos, que son pipelines con tipado estricto y observables, y herramientas, que son funciones de Go seguras desde el punto de vista tipológico con generación automática de esquemas.
Es adecuado para tareas de generación puntuales como herramientas de línea de comandos, procesamiento por lotes y puntos de extremo webhook, además de flujos de trabajo deterministas y agentes de un solo dominio como un pipeline de restauración de fotos multimodal. Soporta varios proveedores de modelos a través de plugins. Dado que una aplicación Genkit es simplemente un servidor HTTP estándar de Go, funciona en cualquier plataforma de backend, siendo Google Cloud Run el destino preferido por su facilidad para alojar contenedores y su escalado automático.
El evaluador escrito con Genkit Go:
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.")
}
Ejecutar el flujo
Inicie el servidor de Genkit con su proyecto de Google Cloud configurado:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
export PORT=8080
go run main.go
Desde otra terminal, envíe una solicitud de evaluación:
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 .
El flujo se ejecuta, llama a la herramienta de catálogo y devuelve una carga útil de evaluación estructurada:
{
"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 elimina el bucle de envío escrito a mano. Desestructura los argumentos en estructuras nativas de Go, ejecuta la herramienta, devuelve la carga útil al modelo y emite automáticamente spans de OpenTelemetry para cada paso. Es la solución ideal cuando la carga de trabajo sigue un flujo estructurado y determinista.
Versión tres: el Kit de Desarrollo de Agentes
Mientras Genkit se enfoca en pipelines de aplicaciones estructurados, el ADK de Google funciona a un alto nivel de abstracción. Fue diseñado para agentes conversacionales autónomos, orquestación de múltiples agentes y arquitecturas complejas que necesitan memoria a largo plazo y RAG empresarial.
El kit también estandariza los ciclos de vida de los agentes, la delegación de subagentes y el protocolo de comunicación agente-a-agente (A2A), y puede utilizar múltiples proveedores de modelos a través de adaptadores, al igual que Genkit.
Para el despliegue, la plataforma Gemini Enterprise Agent es la opción preferida cuando se desea que Google gestione el almacenamiento de sesiones, los conectores empresariales y la memoria, de modo que no sea necesario programar uno’s propios niveles de base de datos. Los equipos que utilizan sus propios almacenes de estado o microservicios contenerizados se benefician más con Google Cloud Run.
El evaluador de 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())
}
}
Ejecutar el agente ADK
ADK incluye un lanzador universal, full.NewLauncher(), por lo que no es necesario crear manualmente la redirección HTTP, el empaquetado JSON ni el código básico para sesiones. Los argumentos de la línea de comandos pasados al lanzador seleccionan el modo de interacción sin realizar cambios en el código de la aplicación.
Para iniciar un chat en terminal interactivo:
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
El lanzador se inicia en modo consola y se puede interrogar directamente al evaluador:
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 gestiona por sí mismo el estado de la conversación y la continuidad de la sesión, de modo que las preguntas posteriores mantienen todo el contexto de las interacciones anteriores.
Como alternativa, existe una interfaz web para desarrolladores integrada que se proporciona junto con una API REST:
go run main.go web webui api
Al abrir http://localhost:8080 se obtiene un chat interactivo con respuestas en tiempo real, historial de sesiones y visibilidad paso a paso de las ejecuciones de las herramientas, abarcando tanto la herramienta personalizada search_catalog como la implementación nativa GoogleSearch.
ADK separa de manera clara la definición del agente (llmagent), los enlaces a las herramientas (functiontool y funciones integradas como geminitool.GoogleSearch) y el entorno de ejecución (launcher y runner). Por eso es el marco preferido para jerarquías complejas de múltiples agentes y asistentes conversacionales.
Dónde se ejecutan los agentes de Go
Escribir el agente en Go es solo la mitad del camino. Después, se necesita un entorno de ejecución que pueda manejar respuestas de streaming de larga duración, llamadas a herramientas en segundo plano, credenciales seguras y picos de tráfico sin causar problemas operativos. Go se compila en binarios autónomos que no generan casi ningún sobrecargo en tiempo de ejecución, por lo que los agentes escritos en este lenguaje son rápidos y económicos de alojar. Dependiendo del framework y de los requisitos de estado, existen dos objetivos principales.
Cloud Run, el backend de uso general
Google Cloud Run es el objetivo principal para los agentes en contenedores escritos en Go, ya sea que estén desarrollados con el SDK original, flujos Genkit o ejecutores ADK. Las compilaciones Docker de múltiples etapas pueden generar una imagen mínima scratch o distroless sin ninguna dependencia en tiempo de ejecución. Al no haber necesidad de iniciar un intérprete pesado ni una máquina virtual, el servicio se escala rápidamente desde cero, evitando la sobrecarga del arranque lento que afecta a los entornos de ejecución más complejos.
Las principales ventajas de Cloud Run para los agentes en Go son:
- Escalado a cero con alta concurrencia. Solo se paga por los milisegundos de CPU utilizados mientras el agente está trabajando o procesando respuestas de herramientas, y las ligeras goroutines de Go permiten que una instancia de contenedor procese cientos de operaciones del agente simultáneamente con un bajo consumo de memoria.
- Tiempos de espera largos para las solicitudes. Las solicitudes tienen un límite por defecto de 5 minutos, que puede extenderse hasta 60 minutos (3,600 segundos). Esto permite que los ciclos de razonamiento de múltiples pasos, las tareas de investigación complejas y los enjambres de subagentes iterativos tengan tiempo suficiente para completarse.
- Transmisión bidireccional y WebSockets. Se admite de forma nativa la transferencia por bloques de HTTP/2, los Eventos enviados por el servidor y los WebSockets. Para agentes multimodales en tiempo real o de voz a voz en la API Gemini Live, los WebSockets permiten una transmisión continua bidireccional entre el cliente y el servidor.
- Afinidad de sesión. Un agente que mantiene cachés temporales en memoria o contexto local entre turnos puede utilizar
--session-affinity, por IP del cliente o cookie, para enviar solicitudes posteriores a la misma instancia de contenedor. - Resiliencia sin estado. En entornos de producción, el estado debe almacenarse en bases de datos gestionadas externamente como Firestore, Redis o Cloud SQL. Tras una interrupción de red durante una operación, el agente puede reanudarla mediante su ID de interacción sin perder el contexto.
- Identidad de trabajo sin secretos. No se requieren claves API ni credenciales codificadas, ya que el agente se autentica en la Gemini Developer API, Vertex AI y Cloud Storage a través de la cuenta de servicio de Cloud Run disponible en el entorno.
Plataforma de agentes Gemini Enterprise
Los agentes ADK conversacionales y las soluciones empresariales que necesitan mantener sesiones, almacenar memoria a largo plazo y basar las respuestas en el conocimiento de la empresa pueden utilizar la plataforma Gemini Enterprise Agent, presentada como sucesora del Vertex AI Agent Engine, la cual ofrece un entorno de ejecución sin servidores y completamente gestionado. En lugar de configurar bases de datos y escribir adaptadores personalizados para el almacenamiento de sesiones, los equipos cuentan con una infraestructura gestionada que incluye las siguientes características:
- Persistencia de sesiones desacoplada. El almacenamiento a largo plazo de las conversaciones (
SessionServiceen ADK) está separado del bucle de ejecución en tiempo real efímero (LiveSession), con mecanismos automáticos para reanudar las sesiones tras las reconexiones. - Anclaje empresarial y búsqueda vectorial. Conectores listos para usar que permiten acceder a bases de conocimiento empresariales como Google Drive, BigQuery y repositorios de intranet, además de índices de Vertex AI Vector Search, incluyendo niveles optimizados para el almacenamiento, para una recuperación semántica rápida.
- Ejecución de código en entorno aislado. Entornos aislados permiten a los agentes escribir y ejecutar código, como scripts de análisis de datos o en Python y Go, sin exponer la infraestructura del servidor.
- Protocolos entre agentes. Una comunicación estandarizada permite a los agentes empresariales independientes descubrir capacidades, negociar esquemas y delegar tareas más allá de las fronteras organizativas.
- Seguridad, identidad del agente y Model Armor. Los permisos IAM detallados para los agentes, los perímetros de Control de Servicios VPC y la protección en tiempo de ejecución de Model Armor inspeccionan las entradas y salidas en busca de inyección de comandos, extracción de datos y violaciones de políticas.
Elegir entre los tres
Para aprender cómo funcionan las llamadas a funciones y el anclaje, o para una pequeña utilidad, el bucle del SDK en bruto es suficiente. Para un pipeline estructurado y determinista o un agente de un solo dominio expuesto como un punto de extremo HTTP, Genkit es la opción adecuada. Para asistentes conversacionales, jerarquías de múltiples agentes, delegación y memoria a largo plazo, ADK constituye una base más sólida. Cloud Run es adecuado para los tres casos, mientras que la Gemini Enterprise Agent Platform es el entorno ideal para los agentes ADK que necesitan sesiones gestionadas, memoria y anclaje empresarial.
Lecturas relacionadas
- Controlar el comportamiento del agente Bedrock: etapas de prompts, analizadores e inferencia — Cómo sobrescribir los prompts de preprocesamiento, orquestación y posprocesamiento de Bedrock en Terraform, utilizando un analizador Lambda, un contrato JSON y configuraciones de inferencia por etapa.
- Navegador en la nube: una plataforma de navegador remoto segura para cualquier agente de IA — Un diseño de referencia para un servicio de navegador remoto neutro con respecto a los modelos: gateway, trabajadores, vista en vivo mediante WebRTC, aprobaciones, protecciones contra SSRF, reproducción y todas las configuraciones posibles.