Crea una app de restauración de fotos con Genkit Go y Nano Banana Pro

1. Introducción

En este codelab, compilaremos GlowUp, una herramienta de restauración de fotos. GlowUp usa la IA para restaurar fotos antiguas, dañadas o en blanco y negro, y crear imágenes en color de alta calidad en 4K. Puedes usar esta herramienta para darles una nueva vida a tus fotos familiares o incluso adaptarla para restaurar ilustraciones, dibujos, pinturas o cualquier otro tipo de imágenes dañadas.

Usarás Genkit Go para implementar la lógica de la aplicación y Gemini 3 Pro Image (también conocido como Nano Banana Pro) como el modelo para procesar las fotos.

Requisitos previos

  • Conocimientos básicos del lenguaje de programación Go
  • Conocimientos básicos de la consola de Google Cloud

Qué aprenderás

  • Cómo desarrollar aplicaciones de Genkit en Go
  • Conceptos básicos de Genkit, como flujos, complementos y mensajes
  • Cómo escribir instrucciones con plantillas de Handlebars
  • Cómo obtener datos de imágenes a partir de las respuestas del modelo

Requisitos

Este taller se puede realizar por completo en Google Cloud Shell, que viene con todas las dependencias necesarias (gcloud CLI, editor de código, Go y Gemini CLI) preinstaladas.

Como alternativa, si prefieres trabajar en tu propia máquina, necesitarás lo siguiente:

  • La cadena de herramientas de Go (versión 1.24 o posterior)
  • Node.js v20 o una versión posterior (para la CLI de genkit)
  • Una terminal con la CLI de gcloud instalada
  • Un IDE para editar tu código, como VS Code o uno similar
  • Recomendado: Un agente de programación como Gemini CLI o Antigravity

Tecnologías clave

Aquí puedes encontrar más información sobre las tecnologías que utilizaremos:

  • Gemini Nano Banana Pro (Gemini 3 Pro Image): El modelo que impulsa nuestro proceso de restauración
  • Genkit Go: Nuestro kit de herramientas para organizar llamadas a modelos

2. Configuración del entorno

Elige una de las siguientes opciones: Configuración del entorno a tu propio ritmo si deseas ejecutar este codelab en tu propia máquina o Iniciar Cloud Shell si deseas ejecutar este codelab por completo en la nube.

Configuración del entorno de autoaprendizaje

  1. Accede a Google Cloud Console y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una cadena de caracteres que no se utiliza en las APIs de Google. Puedes actualizarla cuando quieras.
  • El ID del proyecto es único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Cloud genera automáticamente una cadena única. Por lo general, no importa cuál sea. En la mayoría de los codelabs, deberás hacer referencia al ID de tu proyecto (suele identificarse como PROJECT_ID). Si no te gusta el ID que se generó, podrías generar otro aleatorio. También puedes probar uno propio y ver si está disponible. No se puede cambiar después de este paso y se usa el mismo durante todo el proyecto.
  • Recuerda que hay un tercer valor, un número de proyecto, que usan algunas APIs. Obtén más información sobre estos tres valores en la documentación.
  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar las APIs o los recursos de Cloud. Ejecutar este codelab no costará mucho, tal vez nada. Para cerrar recursos y evitar que se generen cobros más allá de este instructivo, puedes borrar los recursos que creaste o borrar el proyecto. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de $300.

Inicia Cloud Shell

Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usarás Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

En Google Cloud Console, haz clic en el ícono de Cloud Shell en la barra de herramientas en la parte superior derecha:

Activar Cloud Shell

El aprovisionamiento y la conexión al entorno deberían tomar solo unos minutos. Cuando termine el proceso, debería ver algo como lo siguiente:

Captura de pantalla de la terminal de Google Cloud Shell que muestra que el entorno se conectó

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Todo tu trabajo en este codelab se puede hacer en un navegador. No es necesario que instales nada.

3. Configuración del proyecto

Crea el proyecto

Primero, debemos crear un directorio nuevo para tu proyecto y, luego, inicializar el módulo de Go. En tu terminal, ejecuta los siguientes comandos:

mkdir -p glowup && cd glowup
go mod init glowup

Instala la CLI de Genkit

Ahora debemos instalar la CLI de Genkit. Esto te da acceso a las herramientas para desarrolladores locales, incluida la IU para desarrolladores. En la ventana de la terminal, escribe lo siguiente:

curl -sL cli.genkit.dev | bash

Configure las variables de entorno

Asegúrate de tener configuradas las credenciales correctas de Google Cloud. Reemplaza your-project-id por el ID de tu proyecto. La ubicación debe ser global cuando se usan los modelos de vista previa de Gemini 3 Pro (Nano Banana Pro es uno de ellos).

export GOOGLE_CLOUD_PROJECT=$(gcloud config get project)
export GOOGLE_CLOUD_LOCATION=global

Ejecuta el siguiente comando en el modo de shell para habilitar la API de Vertex AI:

gcloud services enable aiplatform.googleapis.com

Si ejecutas el comando desde tu máquina local (no en Cloud Shell), deberás autenticarte con el comando gcloud:

gcloud auth application-default login

4. Crea tu primera aplicación de Genkit

Genkit es un framework de código abierto diseñado para ayudar a los desarrolladores a compilar, implementar y supervisar aplicaciones impulsadas por IA listas para la producción. En esta sección, crearemos una aplicación simple de "Hello World" para que te familiarices con el framework antes de profundizar en la lógica de restauración de fotos.

Terminología de Genkit

Antes de comenzar a trabajar con Genkit, es importante comprender algunos términos clave:

  • Complementos: Se usan para extender las capacidades de Genkit. Entre otras cosas, a través de los complementos, registras modelos de IA para potenciar tu aplicación.
  • Flujos: Son el componente arquitectónico principal de Genkit. Un flujo típico tomaría una entrada, la procesaría y devolvería un resultado. No es necesario usar un modelo, pero la mayoría de las veces usarás modelos dentro de tus flujos.
  • Instrucciones: Son plantillas de interacción almacenadas en formato dotprompt (guardadas como archivos *.prompt). Estos no solo contienen las instrucciones del modelo, sino también configuraciones como el nombre, los parámetros, las entradas y las salidas del modelo.

Conéctate con modelos de IA

Genkit usa complementos para conectar tu código a los proveedores de modelos. Existen complementos para todos los proveedores de modelos principales, incluidos Google, Anthropic y OpenAI.También puedes usar complementos para conectarte a modelos locales (p.ej., con Ollama) o para extender las capacidades de Genkit (p.ej., conectarte a servidores de MCP).

Para acceder a los modelos de Google, debes usar el complemento googlegenai. Admite dos backends:

  • Google AI: Es la mejor opción para crear prototipos. Usa una clave de API.
  • Vertex AI (Google Cloud): Se recomienda para producción. Usa el ID y la ubicación del proyecto.

En este codelab, usaremos la autenticación de Vertex AI haciendo referencia al proyecto que creaste al comienzo del lab.

Cómo crear un flujo de saludo

Antes de profundizar en el flujo de restauración de fotos Glow Up, creemos un flujo básico para familiarizarnos con los conceptos y asegurarnos de que nuestra configuración funcione correctamente.

Un flujo es una función especial de Genkit que encapsula tu lógica de IA para proporcionar lo siguiente:

  • Entradas y salidas con seguridad de tipos: Define esquemas con structs de Go para la validación estática y de tiempo de ejecución
  • Compatibilidad con la transmisión: Transmite respuestas parciales o datos personalizados
  • Integración de la IU para desarrolladores: Prueba y depura flujos con registros visuales
  • Implementación sencilla: Implementa como extremos HTTP en cualquier plataforma

Abre tu IDE y crea un archivo main.go en el directorio de tu proyecto. Si usas Cloud Shell, puedes usar el siguiente comando:

cloudshell edit main.go

Luego, agrega el siguiente código:

main.go

package main

import (
        "context"
        "log"
        "os"
        "os/signal"
        "syscall"

        "github.com/firebase/genkit/go/genkit"
        "github.com/firebase/genkit/go/ai"
        "github.com/firebase/genkit/go/plugins/googlegenai"
)

func main() {
        ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
        defer cancel()

        // Initialize Genkit with the Vertex AI plugin
        g := genkit.Init(ctx,
                genkit.WithPlugins(&googlegenai.VertexAI{}),
        )

        // Define the greeter flow
        genkit.DefineFlow(g, "greeter", func(ctx context.Context, name string) (string, error) {
                text, err := genkit.GenerateText(ctx, g,
                        ai.WithModelName("vertexai/gemini-2.5-pro"),
                        ai.WithPrompt("Say a warm and creative hello to %s", name),
                )
                if err != nil {
                        return "", err
                }

                return text, nil
        })

        // Register the flow here in the next steps
        log.Println("GlowUp initialized. Ready for flows.")
        <-ctx.Done()
}

Guarda el archivo y, luego, ejecuta go mod tidy para actualizar tus dependencias:

go mod tidy

El código anterior inicializa Genkit con el complemento de Vertex AI, define un flujo llamado "greeter" y, luego, espera indefinidamente en <-ctx.Done(). Detenemos la ejecución para que este programa no finalice de inmediato, ya que no le damos ninguna instrucción para que ejecute el flujo.

Esto significa que, si ejecutas este programa tal como está ahora, no hará mucho por sí solo. Necesitamos invocar el flujo de alguna manera. Si bien en una aplicación de producción real envolveríamos este flujo en un servidor web o una app de CLI, durante el tiempo de desarrollo podemos usar la CLI de genkit para ayudarnos a desarrollar y optimizar el flujo.

La CLI de genkit se desarrolló para ayudarnos a probar modelos, instrucciones, flujos y otros componentes de la pila de Genkit. También admite seguimientos por completo, lo que resulta muy conveniente cuando quieres comprender cómo funciona la aplicación de forma interna. Para iniciar nuestro flujo de saludo con la CLI de genkit, ejecuta el siguiente comando:

genkit start -- go run main.go

Esto activará los extremos de la API de Telemetría y la IU para desarrolladores. Deberías ver algo como esto:

$ genkit start -- go run main.go
Telemetry API running on http://localhost:4033
Project root: /home/daniela/glowup
Genkit Developer UI: http://localhost:4000

Si abres localhost:4000 en tu navegador para iniciar la IU de desarrollo. Deberías ver una pantalla como la que se muestra a continuación:

93d9bd9e1bd6627d.png

Tómate tu tiempo para explorar la IU de desarrollo. Tal vez, activa el flujo de "saludo" una vez para ver cómo funciona.

Cómo usar plantillas de instrucciones

Si bien puedes codificar de forma rígida tus instrucciones en las llamadas al modelo, como lo hicimos en el ejemplo anterior, un mejor enfoque sería crear plantillas de instrucciones para almacenar todas las instrucciones de nuestra aplicación de forma centralizada. Esto no solo facilita la búsqueda de los mensajes cuando se realiza el mantenimiento del código, sino que también te permite experimentar con los mensajes de forma independiente de los flujos.

Para definir plantillas de instrucciones, Genkit usa el formato de dotprompt de código abierto, que se guarda como archivos *.prompt. Un archivo .prompt consta de dos partes:

  1. Frontmatter: Es un bloque YAML que define el modelo, los parámetros del modelo y los esquemas de entrada y salida.
  2. Cuerpo: Es el cuerpo de la instrucción en sí, que se puede crear como plantilla con la sintaxis de "Handlebars". Por ejemplo, si defines una entrada llamada variable-name, puedes hacer referencia a ella en el cuerpo como {{variable-name}}.

La estructura de directorios del proyecto se verá de la siguiente manera:

glowup/
 ├── main.go        
 └── prompts/
      └── greeter.prompt

Veamos esto en la práctica. Primero, crea la carpeta para almacenar tus instrucciones:

mkdir -p prompts

Luego, crea un archivo greeter.prompt:

cloudshell edit prompts/greeter.prompt

E inserta el siguiente contenido:

greeter.prompt

ec9fc82a98604123.png

Esta instrucción muestra algunas de las funciones del lenguaje de la plantilla. Primero, especificamos el modelo vertexai/gemini-2.5-flash en el encabezado. Para especificar un modelo, debes usar la nomenclatura definida en la documentación del complemento correspondiente.

La sección de configuración nos permite configurar los parámetros del modelo. Usamos una temperatura de 1.9 para permitir que el modelo sea más creativo. La temperatura varía de 0 (resultados más coherentes) a 2 (resultados más creativos). Este y otros parámetros del modelo suelen publicarse en la hoja del modelo. Por ejemplo, aquí se muestra la hoja del modelo gemini-2.5-flash.

La sección de entrada nos permite especificar argumentos para la instrucción. En este caso, definimos el nombre como una cadena. Después del frontmatter, tenemos el cuerpo de la instrucción. El primer bloque define la instrucción del sistema y el segundo, la instrucción del usuario. Todos estos elementos juntos permiten algunas técnicas de instrucciones muy poderosas. Puedes consultar la documentación completa de dotprompt aquí.

Ahora, adaptemos el flujo de greeter para usar la instrucción que acabamos de crear:

main.go

package main

import (
        "context"
        "os"
        "os/signal"
        "syscall"

        "github.com/firebase/genkit/go/ai"
        "github.com/firebase/genkit/go/genkit"
        "github.com/firebase/genkit/go/plugins/googlegenai"
)

func main() {
        ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
        defer cancel()

        // Initialize Genkit with the Vertex AI plugin
        g := genkit.Init(ctx,
                genkit.WithPlugins(&googlegenai.VertexAI{}),
                genkit.WithDefaultModel("vertexai/gemini-2.5-flash"),
        )

        // Define the greeter flow
        genkit.DefineFlow(g, "greeter", func(ctx context.Context, name *string) (string, error) {
                prompt := genkit.LookupPrompt(g, "greeter")

                input := map[string]any{
                        "name": name,
                }

                resp, err := prompt.Execute(ctx, ai.WithInput(input))
                if err != nil {
                        return "", err
                }
                return resp.Text(), nil
        })

        <-ctx.Done()
}

Intenta ejecutar el flujo desde la línea de comandos, con y sin un nombre para ver la diferencia. Ejecución sin nombre:

genkit flow:run greeter

Resultado de ejemplo:

$ genkit flow:run greeter
Telemetry API running on http://localhost:4035
Running '/flow/greeter' (stream=false)...
Result:
"Hello there, absolutely delightful human!\n\nThe very moment your message arrived, the day instantly sparkled a little brighter. It's not just nice, it's genuinely **wonderful** to meet you!\n\nMay your entire day be filled with unexpected pockets of joy, effortless triumphs, and all the happiness you truly deserve! We're thrilled to have you here!"

Y con un nombre:

genkit flow:run greeter '{"name":"Daniela"}'

Resultado de ejemplo:

$ genkit flow:run greeter '{"name":"Daniela"}'
Telemetry API running on http://localhost:4035
Running '/flow/greeter' (stream=false)...
Result:
"Well hello there, Daniela! What a truly beautiful name, and what an absolute pleasure it is to meet you!\n\nRight from this moment, I just know your day is going to be brimming with positive energy and wonderful surprises. May it be filled with brilliant ideas, joyful moments, and the delightful realization that you're an amazing person doing incredible things. So glad you're here!"

Puedes ver que la plantilla funcionó según lo esperado. ¡Estamos listos para llevar este proyecto al siguiente nivel!

5. Cómo restaurar imágenes con Nano Banana Pro

La instrucción de restauración

Ahora que conoces los componentes básicos de Genkit, es momento de crear nuestra instrucción para restaurar fotos.

Crea un archivo llamado glowup.prompt en el directorio de mensajes y pega en él el siguiente contenido:

glowup.prompt

fd0f1551466c8138.png

La instrucción de restauración sigue el mismo patrón que nuestra instrucción de "saludo", con solo un par de diferencias notables:

  • Tamaño de la imagen: La propiedad imageSize de imageConfig es un parámetro específico del modelo de Nano Banana Pro. Nos permite especificar el tamaño de salida como 1 K, 2 K o 4 K.
  • Entrada de medios: Usamos la plantilla {{ media }} para insertar la foto en la instrucción del usuario. Esta técnica nos permite enviar instrucciones multimodales al modelo (texto e imagen).

Puedes probar esta instrucción en la IU para desarrolladores. Siéntete libre de modificarlo para ver cómo afecta el resultado.

El flujo de restablecimiento de imágenes

Con la instrucción de restauración lista, ahora compilaremos nuestra aplicación de CLI. Reemplaza el contenido de main.go con el siguiente código:

main.go

package main

import (
        "context"
        "encoding/base64"
        "errors"
        "flag"
        "fmt"
        "log"
        "mime"
        "os"
        "os/signal"
        "strings"
        "syscall"

        "github.com/firebase/genkit/go/ai"
        "github.com/firebase/genkit/go/genkit"
        "github.com/firebase/genkit/go/plugins/googlegenai"
)

func main() {
        url := flag.String("url", "", "url of the image to restore")
        contentType := flag.String("contentType", "image/jpeg", "content type of the image (default: image/jpeg)")
        flag.Parse()

        ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
        defer cancel()

        // Initialize Genkit with the Vertex AI plugin
        g := genkit.Init(ctx,
                genkit.WithPlugins(&googlegenai.VertexAI{}),
        )

        // Input schema for the glowUp flow
        type Input struct {
                URL         string `json:"url,omitempty"`
                ContentType string `json:"contentType,omitempty"`
        }

        glowup := genkit.DefineFlow(g, "glowUp", func(ctx context.Context, input Input) (string, error) {
                // 1. Retrieve prompt
                prompt := genkit.LookupPrompt(g, "glowup")
                if prompt == nil {
                        return "", errors.New("prompt 'glowup' not found")
                }

                resp, err := prompt.Execute(ctx, ai.WithInput(input))
                if err != nil {
                        return "", fmt.Errorf("generation failed: %w", err)
                }

                return resp.Media(), nil
        })

        // triggers the flow and returns the encoded response from the model
        out, err := glowup.Run(ctx, Input{URL: *url, ContentType: *contentType})
        if err != nil {
                log.Fatalln(err)
        }

        // decodes image data and returns the appropriate file extension
        data, ext, err := decode(out)
        if err != nil {
                log.Fatalln(err)
        }

        // writes restored file to disk
        filename := "restored" + ext
        if err := os.WriteFile(filename, data, 0644); err != nil {
                log.Fatalln(err)
        }
}

// decode returns the decoded data and the file extension appropriate for the mime type
func decode(text string) ([]byte, string, error) {
        if !strings.HasPrefix(text, "data:") {
                return nil, "", errors.New("unsupported enconding format")
        }
        text = strings.TrimPrefix(text, "data:")
        parts := strings.Split(text, ";base64,")

        mimeType := parts[0]
        decoded, err := base64.StdEncoding.DecodeString(parts[1])
        if err != nil {
                return nil, "", err
        }

        ext, err := mime.ExtensionsByType(mimeType)
        if err != nil {
                return nil, "", err
        }

        return decoded, ext[0], nil
}

El código tiene una estructura muy similar al flujo greeter, pero esta vez se adaptó para ejecutarse como una aplicación de CLI independiente. En lugar de bloquearse para siempre, ejecutará el flujo de glowUp, decodificará el resultado y guardará los datos binarios resultantes en el disco.

Tenemos que decodificar el resultado porque el modelo devuelve los datos de la imagen en el siguiente formato:

data:<mime type>;base64,<base64 encoded image>

Puedes ver en la función decode que usamos la manipulación de cadenas para dividir el tipo de MIME y las partes de la imagen codificada. Luego, usamos las funciones mime.ExtensionByType y base64.DecodeString para extraer la información que necesitamos para guardar el archivo.

Prueba el flujo de restablecimiento

Ahora es el momento de ejecutar este flujo con una imagen real. Si no tienes a mano fotos antiguas que necesiten restauración, puedes probar con esta imagen de dominio público recuperada del sitio web de la Biblioteca del Congreso:

f0fc83a81e88052a.png

Este es el vínculo directo a la fotografía para pasarla al flujo: https://tile.loc.gov/storage-services/service/pnp/fsa/8c01000/8c01700/8c01765v.jpg

export IMAGE_URL="https://tile.loc.gov/storage-services/service/pnp/fsa/8c01000/8c01700/8c01765v.jpg"
go run main.go --url $IMAGE_URL

Y aquí está el resultado restaurado y coloreado:

5ed7bfcf6d26313c.png

¡Listo!

6. Implementa glowUp como un servicio web

Si, en lugar de una aplicación de línea de comandos, deseas exponer tus flujos en un servicio web, Genkit proporciona una forma conveniente de convertir flujos en extremos con el adaptador genkit.Handler. El siguiente código registra el flujo glowUp como un extremo glowUp con genkit.Handler.

Puedes exponerlos con un servidor HTTP normal como lo harías normalmente con el paquete http de la biblioteca estándar, pero Genkit también proporciona un complemento server que ayuda con parte del código estándar común del servidor, como el control de los indicadores de apagado de forma correcta.

Reemplaza el contenido de main.go por el siguiente código para crear tu servidor web.

package main

import (
        "context"
        "errors"
        "fmt"
        "log"
        "net/http"
        "os"

        "github.com/firebase/genkit/go/ai"
        "github.com/firebase/genkit/go/genkit"
        "github.com/firebase/genkit/go/plugins/googlegenai"
        "github.com/firebase/genkit/go/plugins/server"
)

func main() {
        ctx := context.Background()

        PORT := os.Getenv("PORT")
        if PORT == "" {
                PORT = "8080"
        }

        listenAddr := ":" + PORT

        // Initialize Genkit with the Vertex AI plugin
        g := genkit.Init(ctx,
                genkit.WithPlugins(&googlegenai.VertexAI{}),
        )

        // Input schema for the glowUp flow
        type Input struct {
                URL         string `json:"url,omitempty"`
                ContentType string `json:"contentType,omitempty"`
        }

        genkit.DefineFlow(g, "glowUp", func(ctx context.Context, input Input) (string, error) {
                prompt := genkit.LookupPrompt(g, "glowup")
                if prompt == nil {
                        return "", errors.New("prompt 'glowup' not found")
                }

                resp, err := prompt.Execute(ctx, ai.WithInput(input))
                if err != nil {
                        return "", fmt.Errorf("generation failed: %w", err)
                }

                return resp.Media(), nil
        })

        log.Printf("GlowUp Flow Server started. Listening on %s", listenAddr)
        mux := http.NewServeMux()
        for _, flow := range genkit.ListFlows(g) {
                mux.HandleFunc("POST /"+flow.Name(), genkit.Handler(flow))
        }

        if err := server.Start(ctx, listenAddr, mux); err != nil {
                // Check if the error is due to context cancellation
                if ctx.Err() != nil {
                        log.Println("GlowUp server shutting down gracefully...")
                        return
                }
                log.Fatal(err)
        }
}

Puedes ejecutar este servicio de forma local o en la nube. Para ejecutarlo de forma local, puedes ejecutar el archivo directamente, ya que el programa ahora está completo, por lo que no es necesario iniciarlo a través de la CLI de Genkit. Por ejemplo:

go run main.go

Dado que esta es una operación de bloqueo, debes iniciar una segunda terminal para probarla. La forma más rápida sería usar el comando curl:

curl -sS -X POST http://localhost:8080/glowUp \
     -H "Content-Type: application/json" \
     -d '{"data":{"url": $IMAGE_URL, "contentType":"image/jpeg"}}' \
     > result.json

Como se trata de una respuesta del servidor, debemos decodificar la imagen en base64:

cat result.json | jq -r '.result' | awk -F ',' '{print $2}' | base64 -d > restored.png

Implementa el servicio web en Cloud Run

Está bien que este servidor "funcione en mi máquina", pero, en el mundo ideal, lo implementaríamos en otro lugar accesible para todos nuestros usuarios. Una de las formas más convenientes de implementar servicios como este es usar la función de implementación desde la fuente de Cloud Run. Con esta función, ni siquiera necesitas compilar el contenedor por tu cuenta, ya que todo está automatizado.

Para implementar este servicio en Cloud Run con la implementación desde la fuente, ejecuta el siguiente comando (reemplaza el ID del proyecto por el tuyo):

gcloud run deploy glowup --source . --region us-central1 --no-allow-unauthenticated --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=True,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT

La implementación puede tardar unos minutos en completarse. Cuando termines, puedes probar el endpoint enviando otra solicitud a través de curl:

GLOWUP_URL=$(gcloud run services describe glowup --region us-central1 --format='value(status.url)')

curl -X POST "$GLOWUP_URL/glowUp" \
     -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
     -H "Content-Type: application/json" \
     -d "{\"data\":{\"url\":\"$IMAGE_URL\", \"contentType\":\"image/jpeg\"} }" \
     > result.json

Nuevamente, debemos decodificar la imagen en base64:

cat result.json | jq -r '.result' | awk -F ',' '{print $2}' | base64 -d > restored_cloudrun.png

Ahora tenemos un servidor web de restauración de fotos que funciona a la perfección.

Opcional: "Vibe coding" de una aplicación cliente

Escribir código manualmente es divertido, pero también puede ser un desafío si nunca antes hiciste un determinado tipo de proyecto. Por suerte, hoy en día tenemos agentes de programación que pueden ayudarnos a acelerar el proceso.

En este paso, en lugar de escribir el código nosotros mismos, le pediremos a Gemini CLI (o a tu agente de programación favorito) que haga el trabajo por nosotros. Usa la siguiente instrucción:

GlowUp is a photo restoration service that takes a restoration request as input and returns a restored picture as output. Your task is to create a client application that uses this server to process image urls and save a restored file locally.

TODO:
- Write a CLI application that receives three arguments: an url (required), content type (optional, defaults to image/jpeg) and addr (server address, optional, defaults to localhost:8080)
- The CLI should send a POST request to the /glowUp endpoint in the server with the body '{"data":{"url": <url>, "contentType": <contentType>} }'
- The server response should be parsed by stripping the "data:" prefix. The remainder will have the format <mimeType>;base64,<encoded imageData>
- Extract the mime type and convert to a file extension using the mime package
- Extract the image data and decode it using the base64 package
Save a file named "restored" + the detected file extension

Acceptance Criteria:
- The client builds successfully
- Use the client to restore the image https://tile.loc.gov/storage-services/service/pnp/fsa/8c01000/8c01700/8c01765v.jpg
- Check that restored.png exists after processing the image above

Es posible que el agente necesite reorganizar un poco los archivos, ya que no podemos tener dos funciones "main" en el mismo módulo. Observa cómo funciona y guíalo hacia la implementación correcta proporcionándole el código del servidor o fragmentos si es necesario.

Limpieza después del lab

7. Conclusión

¡Felicitaciones! Compilaste correctamente una app de restauración de fotos de alta fidelidad con Genkit y Nano Banana Pro.

En este codelab, aprendiste a hacer lo siguiente:

  • Configura tu entorno para desarrollar aplicaciones de Genkit en Go
  • Crea instrucciones multimodales con dotprompt
  • Crea flujos de Genkit con plantillas de instrucciones
  • Usa Nano Banana Pro para procesar imágenes
  • Empaqueta flujos de Genkit como aplicaciones de línea de comandos y servicios web
  • Implementa aplicaciones de Genkit en Cloud Run

Cuando termines de realizar las pruebas, recuerda limpiar el entorno.

Próximos pasos

Puedes continuar tu recorrido de aprendizaje explorando otros codelabs en esta plataforma o mejorando glowUp por tu cuenta.

Si necesitas ideas para realizar mejoras, puedes probar lo siguiente:

  • Habilita la CLI de glowUp para restablecer archivos locales
  • Crea un frontend para el servicio web de GlowUp
  • Detectar automáticamente el tipo de MIME a partir de los datos
  • Realizar otros tipos de procesamiento de imágenes (de foto a dibujo, de dibujo a foto, de boceto a arte final, etcétera)
  • Crear o mejorar el cliente (¿quizás compilar una app para smartphones?)

Las posibilidades son infinitas. Si la idea de hacerlo por tu cuenta te intimida un poco, siempre puedes recurrir a la ayuda de un agente de programación como Gemini CLI o Antigravity. Y si quieres hacer más con Genkit, los agentes de codificación vinculados al servidor de MCP de Genkit pueden facilitarte mucho la vida.

Por último, si quieres acceder al código completo de este repo, puedes encontrarlo aquí.

¡Suerte con la programación!