Genkit Go 및 Nano Banana Pro를 사용하여 사진 복원 앱 빌드

1. 소개

이 Codelab에서는 사진 복원 도구인 GlowUp을 빌드합니다. GlowUp은 AI를 사용하여 오래되거나 손상된 사진 또는 흑백 사진을 복원하여 고품질 4K 컬러 이미지를 만듭니다. 이 도구를 사용하면 가족 사진에 새로운 생명을 불어넣거나 손상된 일러스트레이션, 그림, 회화 또는 기타 형태의 이미지를 복원할 수도 있습니다.

Genkit Go를 사용하여 애플리케이션 로직을 구현하고 Gemini 3 Pro Image (Nano Banana Pro라고도 함)를 모델로 사용하여 사진을 처리합니다.

기본 요건

  • Go 프로그래밍 언어에 관한 기본 지식
  • Google Cloud 콘솔에 대한 기본 지식

학습할 내용

  • Go에서 Genkit 애플리케이션을 개발하는 방법
  • 플로우, 플러그인, 프롬프트와 같은 기본 Genkit 개념
  • 핸들바 템플릿으로 프롬프트를 작성하는 방법
  • 모델 응답에서 이미지 데이터를 가져오는 방법

필요한 항목

이 워크숍은 필요한 모든 종속 항목 (gcloud CLI, 코드 편집기, Go, Gemini CLI)이 사전 설치Google Cloud Shell 내에서 전부 실행할 수 있습니다.

또는 자체 머신에서 작업하려면 다음이 필요합니다.

  • Go 도구 모음 (버전 1.24 이상)
  • Node.js v20 이상 (genkit CLI용)
  • gcloud CLI가 설치된 터미널
  • VS Code와 같은 코드를 수정하는 IDE
  • 권장: Gemini CLI 또는 Antigravity와 같은 코딩 에이전트

주요 기술

여기에서 활용할 기술에 대한 자세한 내용을 확인할 수 있습니다.

  • Gemini Nano Banana Pro (Gemini 3 Pro Image): 복원 프로세스를 지원하는 모델
  • Genkit Go: 모델 호출을 오케스트레이션하기 위한 툴킷

2. 환경 설정

자체 머신에서 이 Codelab을 실행하려면 자체 속도 환경 설정을 선택하고, 클라우드에서 이 Codelab을 완전히 실행하려면 Cloud Shell 시작을 선택합니다.

자습형 환경 설정

  1. Google Cloud Console에 로그인하여 새 프로젝트를 만들거나 기존 프로젝트를 재사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • 프로젝트 이름은 이 프로젝트 참가자의 표시 이름입니다. 이는 Google API에서 사용하지 않는 문자열이며 언제든지 업데이트할 수 있습니다.
  • 프로젝트 ID는 모든 Google Cloud 프로젝트에서 고유하며, 변경할 수 없습니다(설정된 후에는 변경할 수 없음). Cloud 콘솔은 고유한 문자열을 자동으로 생성합니다. 일반적으로는 신경 쓰지 않아도 됩니다. 대부분의 Codelab에서는 프로젝트 ID (일반적으로 PROJECT_ID로 식별됨)를 참조해야 합니다. 생성된 ID가 마음에 들지 않으면 다른 임의 ID를 생성할 수 있습니다. 또는 직접 시도해 보고 사용 가능한지 확인할 수도 있습니다. 이 단계 이후에는 변경할 수 없으며 프로젝트 기간 동안 유지됩니다.
  • 참고로 세 번째 값은 일부 API에서 사용하는 프로젝트 번호입니다. 이 세 가지 값에 대한 자세한 내용은 문서를 참고하세요.
  1. 다음으로 Cloud 리소스/API를 사용하려면 Cloud 콘솔에서 결제를 사용 설정해야 합니다. 이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 튜토리얼이 끝난 후에 요금이 청구되지 않도록 리소스를 종료하려면 만든 리소스 또는 프로젝트를 삭제하면 됩니다. Google Cloud 신규 사용자는 300달러(USD) 상당의 무료 체험판 프로그램에 참여할 수 있습니다.

Cloud Shell 시작

Google Cloud를 노트북에서 원격으로 실행할 수 있지만, 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.

Google Cloud Console의 오른쪽 상단 툴바에 있는 Cloud Shell 아이콘을 클릭합니다.

Cloud Shell 활성화

환경을 프로비저닝하고 연결하는 데 몇 분 정도 소요됩니다. 완료되면 다음과 같이 표시됩니다.

환경이 연결되었음을 보여주는 Google Cloud Shell 터미널 스크린샷

가상 머신에는 필요한 개발 도구가 모두 들어있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 Codelab의 모든 작업은 브라우저 내에서 수행할 수 있습니다. 아무것도 설치할 필요가 없습니다.

3. 프로젝트 설정

프로젝트 만들기

먼저 프로젝트의 새 디렉터리를 만들고 Go 모듈을 초기화해야 합니다. 터미널에서 다음 명령어를 실행합니다.

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

Genkit CLI 설치

이제 Genkit CLI를 설치해야 합니다. 이렇게 하면 개발자 UI를 비롯한 로컬 개발자 도구에 액세스할 수 있습니다. 터미널 창에서 다음을 입력합니다.

curl -sL cli.genkit.dev | bash

환경 변수 구성

올바른 Google Cloud 사용자 인증 정보가 설정되어 있는지 확인합니다. your-project-id를 실제 프로젝트 ID로 바꿉니다. Gemini 3 Pro 프리뷰 모델 (Nano Banana Pro가 그중 하나임)을 사용하는 경우 위치는 global여야 합니다.

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

셸 모드에서 다음 명령어를 실행하여 Vertex AI API를 사용 설정합니다.

gcloud services enable aiplatform.googleapis.com

CloudShell이 아닌 로컬 머신에서 실행하는 경우 gcloud 명령어로 인증해야 합니다.

gcloud auth application-default login

4. 첫 번째 Genkit 애플리케이션 만들기

Genkit는 개발자가 프로덕션에 즉시 사용 가능한 AI 기반 애플리케이션을 빌드, 배포, 모니터링할 수 있도록 설계된 오픈소스 프레임워크입니다. 이 섹션에서는 사진 복원 로직을 살펴보기 전에 프레임워크에 익숙해지도록 간단한 'Hello World' 애플리케이션을 만듭니다.

Genkit 용어

Genkit 작업을 시작하기 전에 몇 가지 주요 용어를 이해하는 것이 중요합니다.

  • 플러그인: Genkit의 기능을 확장하는 데 사용됩니다. 무엇보다도 플러그인을 통해 애플리케이션을 지원하는 AI 모델을 등록합니다.
  • 흐름: Genkit의 기본 아키텍처 구성요소입니다. 일반적인 흐름은 입력을 받아 처리하고 출력을 반환합니다. 모델을 사용할 필요는 없지만 대부분의 경우 흐름 내에서 모델을 사용합니다.
  • 프롬프트: dotprompt 형식으로 저장된 상호작용 템플릿 (*.prompt 파일로 저장됨) 여기에는 모델 안내뿐만 아니라 모델 이름, 모델 매개변수, 입력, 출력과 같은 구성도 포함됩니다.

AI 모델과 연결

Genkit은 플러그인을 사용하여 코드를 모델 제공업체에 연결합니다. Google, Anthropic, OpenAI를 비롯한 모든 주요 모델 제공업체를 위한 플러그인이 있습니다. 플러그인을 사용하여 로컬 모델 (예: Ollama 사용)에 연결하거나 Genkit의 기능을 확장 (예: MCP 서버에 연결)할 수도 있습니다.

Google 모델에 액세스하려면 googlegenai 플러그인을 사용해야 합니다. 다음 두 백엔드를 지원합니다.

  • Google AI: 프로토타입 제작에 가장 적합합니다. API 키를 사용합니다.
  • Vertex AI (Google Cloud): 프로덕션 환경에 권장됩니다. 프로젝트 ID와 위치를 사용합니다.

이 Codelab에서는 실습을 시작할 때 만든 프로젝트를 참조하는 Vertex AI 인증을 사용합니다.

인사말 흐름 만들기

Glow Up 사진 복원 흐름을 자세히 살펴보기 전에 개념을 숙지하고 설정이 제대로 작동하는지 확인하기 위해 기본 흐름을 빌드해 보겠습니다.

흐름은 AI 로직을 래핑하여 다음을 제공하는 특수한 Genkit 함수입니다.

  • 유형 안전 입력 및 출력: 정적 및 런타임 검사를 위해 Go 구조체를 사용하여 스키마 정의
  • 스트리밍 지원: 부분 응답 또는 맞춤 데이터 스트리밍
  • 개발자 UI 통합: 시각적 추적으로 흐름 테스트 및 디버그
  • 간편한 배포: HTTP 엔드포인트로 모든 플랫폼에 배포

IDE를 열고 프로젝트 디렉터리에 main.go 파일을 만듭니다. Cloud Shell을 사용하는 경우 다음 명령어를 사용할 수 있습니다.

cloudshell edit main.go

그런 다음 다음 코드를 추가합니다.

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

파일을 저장한 후 go mod tidy를 실행하여 종속 항목을 업데이트합니다.

go mod tidy

위 코드는 VertexAI 플러그인으로 Genkit을 초기화하고, 'greeter'라는 흐름을 정의한 다음 <-ctx.Done()에서 무한정 대기합니다. 흐름을 실제로 실행하라는 명령을 제공하지 않으므로 이 프로그램이 즉시 종료되지 않도록 실행을 보류하고 있습니다.

즉, 이 프로그램을 현재 상태로 실행하면 자체적으로 많은 작업을 수행하지 않습니다. 흐름을 호출해야 합니다. 실제 프로덕션 애플리케이션에서는 이 흐름을 웹 서버나 CLI 앱으로 래핑하겠지만, 개발 중에는 genkit CLI를 사용하여 흐름을 개발하고 최적화할 수 있습니다.

genkit CLI는 Genkit 스택의 모델, 프롬프트, 흐름, 기타 구성요소를 테스트하는 데 도움이 되도록 개발되었습니다. 또한 trace를 완전히 지원하므로 애플리케이션이 내부적으로 어떻게 작동하는지 파악하려는 경우 매우 편리합니다. genkit CLI를 사용하여 인사말 흐름을 실행하려면 다음 명령어를 실행합니다.

genkit start -- go run main.go

그러면 Telemetry API 및 Developer UI 엔드포인트가 실행됩니다. 다음과 같은 결과를 확인할 수 있습니다.

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

브라우저에서 localhost:4000를 열어 개발자 UI를 실행하는 경우 다음과 같은 화면이 나타납니다.

93d9bd9e1bd6627d.png

시간을 내어 개발자 UI를 살펴보세요. 작동 방식을 확인하기 위해 '인사말' 흐름을 한 번 트리거해 보세요.

프롬프트 템플릿 사용

이전 예와 같이 모델 호출에서 프롬프트를 하드코딩할 수도 있지만, 프롬프트 템플릿을 만들어 애플리케이션의 모든 프롬프트를 중앙 집중식으로 저장하는 것이 더 나은 접근 방식입니다. 이렇게 하면 코드 유지보수 시 프롬프트를 더 쉽게 찾을 수 있을 뿐만 아니라 흐름과 독립적으로 프롬프트를 실험할 수 있습니다.

프롬프트 템플릿을 정의하기 위해 Genkit은 오픈소스 dotprompt 형식을 사용하며, 이는 *.prompt 파일로 저장됩니다. .prompt 파일은 다음 두 부분으로 구성됩니다.

  1. 프런트매터: 모델, 모델 매개변수, 입력 및 출력 스키마를 모두 정의하는 YAML 블록
  2. 본문: 프롬프트 자체의 본문으로, 'handlebars' 구문을 사용하여 템플릿을 만들 수 있습니다. 예를 들어 variable-name라는 입력을 정의하면 본문에서 {{variable-name}}로 참조할 수 있습니다.

프로젝트의 디렉터리 구조는 다음과 같습니다.

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

실제 사례를 살펴보겠습니다. 먼저 프롬프트를 저장할 폴더를 만듭니다.

mkdir -p prompts

그런 다음 greeter.prompt 파일을 만듭니다.

cloudshell edit prompts/greeter.prompt

다음 콘텐츠를 삽입합니다.

greeter.prompt

ec9fc82a98604123.png

이 프롬프트는 템플릿 언어의 몇 가지 기능을 보여줍니다. 먼저 프런트매터에서 모델 vertexai/gemini-2.5-flash를 지정합니다. 모델을 지정하려면 각 플러그인 문서에 정의된 명명법을 사용해야 합니다.

구성 섹션을 사용하면 모델 파라미터를 구성할 수 있습니다. 모델이 더 창의적으로 답변할 수 있도록 온도 1.9를 사용하고 있습니다. 온도는 0 (더 일관된 출력)에서 2 (더 창의적인 출력)까지입니다. 이 매개변수와 기타 모델 매개변수는 일반적으로 모델의 모델 시트에 게시됩니다. 예를 들어 gemini-2.5-flash의 모델 시트는 다음과 같습니다.

입력 섹션을 사용하면 프롬프트의 인수를 지정할 수 있습니다. 이 경우 이름을 문자열로 정의합니다. 프런트매터 뒤에는 프롬프트 본문이 있습니다. 여기서 첫 번째 블록은 시스템 프롬프트를 정의하고 두 번째 블록은 사용자 프롬프트입니다. 이러한 모든 요소를 함께 사용하면 매우 강력한 프롬프트 기법을 사용할 수 있습니다. dotprompt의 전체 문서는 여기에서 확인할 수 있습니다.

이제 방금 만든 프롬프트를 사용하도록 greeter 흐름을 조정해 보겠습니다.

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

이름을 포함하거나 제외하여 명령줄에서 흐름을 실행해 차이점을 확인해 보세요. 이름 없이 실행:

genkit flow:run greeter

출력 예시:

$ 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!"

이름이 있는 경우:

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

출력 예시:

$ 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!"

템플릿이 예상대로 작동한 것을 확인할 수 있습니다. 이 프로젝트를 한 단계 업그레이드할 준비가 되었습니다.

5. Nano Banana Pro로 이미지 복원하기

복원 프롬프트

이제 Genkit의 빌딩 블록에 대해 잘 알게 되었으니 사진 복원 프롬프트를 만들어 보겠습니다.

프롬프트 디렉터리에 glowup.prompt이라는 파일을 만들고 다음 콘텐츠를 붙여넣습니다.

glowup.prompt

fd0f1551466c8138.png

복원 프롬프트는 '인사' 프롬프트와 동일한 패턴을 따르며, 몇 가지 눈에 띄는 차이점만 있습니다.

  • 이미지 크기: imageConfigimageSize 속성은 Nano Banana Pro의 모델별 매개변수입니다. 이를 통해 출력 크기를 1K, 2K 또는 4K로 지정할 수 있습니다.
  • 미디어 입력: {{ media }} 템플릿을 사용하여 사용자 프롬프트에 사진을 삽입합니다. 이 기법을 사용하면 모델에 멀티모달 프롬프트 (텍스트 + 이미지)를 전송할 수 있습니다.

개발자 UI에서 이 프롬프트를 테스트할 수 있습니다. 출력에 어떤 영향을 미치는지 확인하기 위해 자유롭게 조정해 보세요.

이미지 복원 흐름

복원 프롬프트가 준비되었으니 이제 CLI 애플리케이션을 빌드해 보겠습니다. main.go의 내용을 아래 코드로 바꿉니다.

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
}

코드는 greeter 흐름과 구조가 매우 유사하지만 이번에는 독립형 CLI 애플리케이션으로 실행되도록 조정되었습니다. 영원히 차단하는 대신 glowUp 흐름을 실행하고 출력을 디코딩하며 결과 바이너리 데이터를 디스크에 저장합니다.

모델은 다음 형식으로 이미지 데이터를 반환하므로 출력을 디코딩해야 합니다.

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

decode 함수에서 문자열 조작을 사용하여 MIME 유형과 인코딩된 이미지 부분을 분할하는 것을 확인할 수 있습니다. 그런 다음 mime.ExtensionByTypebase64.DecodeString 함수를 사용하여 파일을 저장하는 데 필요한 정보를 추출합니다.

복원 흐름 테스트

이제 실제 사진으로 이 흐름을 실행할 때입니다. 복원이 필요한 오래된 사진이 없는 경우 미국 의회 도서관 웹사이트에서 가져온 이 퍼블릭 도메인 사진을 사용해 보세요.

f0fc83a81e88052a.png

흐름에 전달할 사진의 직접 링크는 다음과 같습니다. 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

복원되고 컬러화된 출력은 다음과 같습니다.

5ed7bfcf6d26313c.png

완료되었습니다.

6. glowUp을 웹 서비스로 배포

명령줄 애플리케이션 대신 웹 서비스에서 플로우를 노출하려면 Genkit에서 genkit.Handler 어댑터를 사용하여 플로우를 엔드포인트로 변환하는 편리한 방법을 제공합니다. 아래 코드는 genkit.Handler을 사용하여 glowUp 흐름을 glowUp 엔드포인트로 등록합니다.

표준 라이브러리의 http 패키지를 사용하여 평소와 같이 일반 HTTP 서버로 노출할 수 있지만, Genkit에서는 종료 신호를 정상적으로 처리하는 등 일반적인 서버 상용구에 도움이 되는 server 플러그인도 제공합니다.

main.go의 콘텐츠를 아래 코드로 바꿔 웹 서버를 만듭니다.

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

이 서비스는 로컬 또는 클라우드에서 실행할 수 있습니다. 이제 프로그램이 완료되었으므로 genkit CLI를 통해 실행할 필요 없이 파일을 직접 실행하여 로컬로 실행할 수 있습니다. 예를 들면 다음과 같습니다.

go run main.go

이는 차단 작업이므로 테스트하려면 두 번째 터미널을 실행해야 합니다. 가장 빠른 방법은 curl 명령어를 사용하는 것입니다.

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

이는 서버 응답이므로 base64 이미지를 디코딩해야 합니다.

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

Cloud Run에 웹 서비스 배포

이 서버가 '내 컴퓨터에서 작동'하는 것은 괜찮지만, 이상적인 상황에서는 모든 사용자가 액세스할 수 있는 다른 곳에 배포해야 합니다. 이러한 서비스를 배포하는 가장 편리한 방법 중 하나는 Cloud Run의 소스에서 배포 기능을 사용하는 것입니다. 이 기능을 사용하면 컨테이너를 직접 빌드할 필요가 없으며 모든 것이 자동화됩니다.

소스에서 배포를 사용하여 이 서비스를 Cloud Run에 배포하려면 다음 명령어를 실행하세요 (프로젝트 ID를 사용자 ID로 대체).

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

배포를 완료하는 데 몇 분 정도 걸릴 수 있습니다. 완료되면 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

다시 한번 base64 이미지를 디코딩해야 합니다.

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

이제 완전히 작동하는 사진 복원 웹 서버가 있습니다.

선택사항: 클라이언트 애플리케이션 '바이브 코딩'

수동으로 코드를 작성하는 것은 재미있지만 특정 유형의 프로젝트를 이전에 해본 적이 없다면 어려울 수 있습니다. 다행히도 오늘날에는 코딩 프로세스를 가속화하는 데 도움이 되는 코딩 에이전트가 있습니다.

이 단계에서는 코드를 직접 작성하는 대신 Gemini CLI (또는 즐겨 사용하는 코딩 에이전트)에 작업을 요청합니다. 다음 프롬프트를 사용하세요.

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

동일한 모듈에 'main'이라는 함수가 두 개 있을 수 없으므로 에이전트가 파일을 약간 재구성해야 할 수도 있습니다. 작동 방식을 지켜보고 필요한 경우 서버 코드나 스니펫을 제공하여 올바른 구현으로 안내하세요.

실습 후 정리

7. 결론

축하합니다. Genkit 및 Nano Banana Pro를 사용하여 충실도 높은 사진 복원 앱을 성공적으로 빌드했습니다.

이 Codelab을 통해 학습한 내용은 다음과 같습니다.

  • Genkit Go 애플리케이션을 개발하도록 환경 구성
  • dotprompt로 멀티모달 프롬프트 만들기
  • 프롬프트 템플릿을 사용하여 Genkit 흐름 만들기
  • Nano Banana Pro를 사용하여 이미지 처리
  • Genkit 흐름을 명령줄 애플리케이션 및 웹 서비스로 패키징
  • Cloud Run에 Genkit 애플리케이션 배포

테스트가 완료되면 환경을 정리해야 합니다.

다음 단계

이 플랫폼에서 다른 Codelab을 살펴보거나 직접 glowUp을 개선하여 학습 여정을 이어갈 수 있습니다.

개선 아이디어가 필요하다면 다음을 시도해 보세요.

  • glowUp CLI를 사용하여 로컬 파일 복원 사용 설정
  • GlowUp 웹 서비스의 프런트엔드 만들기
  • 데이터에서 MIME 유형 자동 감지
  • 다른 유형의 이미지 처리 (사진에서 그림으로, 그림에서 사진으로, 스케치에서 최종 아트 등으로)를 실행합니다.
  • 클라이언트 만들기 또는 개선 (스마트폰 앱 빌드 등)

가능성은 무한합니다. 혼자서 하는 것이 부담스럽다면 언제든지 Gemini CLIAntigravity와 같은 코딩 에이전트의 도움을 받을 수 있습니다. Genkit을 더 다양하게 활용하고 싶다면 Genkit MCP 서버와 페어링된 에이전트를 코딩하면 훨씬 간편하게 작업할 수 있습니다.

마지막으로 이 저장소의 전체 코드에 액세스하려면 여기에서 확인할 수 있습니다.

즐겁게 코딩해 보세요!