👗 Flutter, ADK Go ve Gemini ile sanal deneme odası ve yapay zeka stilisti oluşturma

1. Giriş

Ne Oluşturacaksınız?

Bu codelab'de, kurgusal bir perakende markası için Flutter alışveriş uygulaması olan Fashion App'i geliştiren bir geliştiricinin rolünü üstleneceksiniz. Göreviniz: Online alışveriş deneyimini dönüştüren iki yapay zeka destekli özellik ekleyin.

  1. Sanal Soyunma Odası: Kullanıcı, kendi fotoğrafını yüklüyor, bir giyim eşyası seçiyor ve yapay zekayla üretilmiş, seçtiği giyim eşyasını giydiği bir fotoğrafını görüyor.
  2. Yapay Zeka Stilisti: Kullanıcının konumuna, etkinliğe ve stil tercihlerine göre yapay zeka aracısı, eksiksiz kıyafet önerileri sunar. Kullanıcılar, sohbet yoluyla bu önerileri iyileştirebilir.

Fikir basit: İnsanlar bir deneme kabininde kıyafetleri denediğinde satın alma olasılıkları çok daha yüksek olur. Peki internette? Sadece tahmin ediyorsunuz. Bu proje, yapay zeka ile bu boşluğu dolduruyor.

Bir Bakışta Mimari

Flutter App  ──── HTTP/REST ────▶  ADK Go Backend
                                       
                            ┌──────────┼──────────┐
                       Fitting Room  Stylist    Catalog
                         Agent       Agent      Agent
                                       
                            Gemini API + Cloud Storage

Temel Teknolojiler

Bileşen

Teknoloji

Amaç

Agent Framework

Go için ADK (Agent Development Kit)

Çoklu temsilci düzenlemesi, oturumlar, yapılar

Temsilci Muhakemesi (Profesyonel)

Gemini 3.1 Pro Önizlemesi

Deneme odası ve stilist temsilcilerine güç sağlar

Temsilci Muhakemesi (Flash)

Gemini 3 Flash Önizlemesi

Kök ve katalog aracılarını destekler (basit yönlendirme/arama)

Görüntü Üretme

Gemini 2.5 Flash Image

Deneme ve kıyafet görselleri oluşturur.

Frontend

Flutter (Dart)

Platformlar arası uygulama (Web, iOS, Android)

Depolama

Google Cloud Storage

Ürün resimlerini ve oluşturulan yapıları depolar.

Barındırma

Cloud Run

Sunucusuz container dağıtımı

2. 📦 Ön koşullar ve Cloud Shell kurulumu

1. Cloud Shell Düzenleyici'yi açın

👉 Tarayıcınızda Cloud Shell Düzenleyici'yi açın.

Terminal ekranın alt kısmında görünmüyorsa:

  • GörünümTerminal'i tıklayın.

2. Flutter SDK'yı kurma

Cloud Shell, Flutter önceden yüklenmiş şekilde /google/flutter konumunda gelir. Bu dizin farklı bir sistem kullanıcısına ait olduğundan flutter komutunu ilk kez çalıştırdığınızda fatal: detected dubious ownership hatasıyla karşılaşırsınız. Bir kez git'in safe-directory listesine ekleyin:

git config --global --add safe.directory /google/flutter

Flutter'ın PATH cihazınızda bulunduğunu ve çalıştığını doğrulayın:

flutter --version

İlk çalıştırmada Dart SDK'sı indirilir ve Flutter aracı oluşturulur. Bu işlem bir dakika sürer. Flutter 3.x • channel stable gibi bir ifade görürsünüz.

3. Depoyu Klonlama

cd ~
git clone https://github.com/gca-americas/fashion-app-demo
cd fashion_app_demo

4. Proje Yapısını Keşfetme

fashion_app_demo/
├── adk_backend/                 # Go backend with ADK agents
   ├── main.go                  # Entry point — wires all agents + REST server
   ├── catalog/                 # Catalog Agent — product lookup
      ├── agent.go
      ├── catalog.yaml         # Product database (YAML)
      └── instructions.md      # Agent persona prompt
   ├── fittingroom/             # Fitting Room Agent — virtual try-on
      ├── agent.go
      ├── instructions.md      # Agent persona prompt
      └── tool_instructions.md # Image generation prompt
   ├── stylist/                 # Stylist Agent — outfit curation
      ├── agent.go
      └── instructions.md      # Agent persona + output format
   ├── rootagent/               # Root Agent — routes to the right agent
      └── agent.go
   └── tools/                   # Shared tools
       ├── imagetool.go         # getProductImage — loads product images
       ├── outfit_gen_tool.go   # generate_outfit_image — creates outfit images
       └── cors_helper.go      # CORS middleware + request logging

├── flutter_frontend/            # Flutter cross-platform app
   ├── lib/
      ├── main.dart            # App entry point + Provider setup
      ├── app_config.dart      # Backend URL configuration
      ├── core_app/            # Pre-built shopping app (browse, cart, etc.)
      └── workshop_tasks/      # AI feature code
          ├── step_1_try_it_on/  # Virtual Try-On flow
             ├── providers/     # TryItOnProvider (state management)
             ├── services/      # AdkFittingRoomService (HTTP calls)
             └── ui/            # Screens (product detail → try on → fitting room)
          └── step_2_style_me/   # Style Me flow
              ├── models/        # Outfit, StyleRequest data classes
              ├── providers/     # StylingProvider (state management)
              ├── services/      # AdkStylingService (HTTP calls)
              └── ui/            # Screens (form sheet → outfit carousel)
   └── assets/images/           # Product catalog images

3. ☁️ Google Cloud projesi kurulumu

1. Yeni proje oluşturma

gcloud projects create fashion-app-demo --name="Fashion App Demo"
gcloud config set project fashion-app-demo

Faturalandırma hesaplarınızı listeleyin:

gcloud billing accounts list

OPEN

sütunu. True yazmalıdır. False ifadesi gösteriliyorsa (süresi dolmuş ücretsiz denemelerde yaygın olarak görülür) hesap kapatılmıştır ve herhangi bir ödeme yapılmaz. Devam etmeden önce aşağıdaki sorun giderme bölümüne geçin.

OPEN: True hesabının ACCOUNT_ID kısmını (0X0X0X-0X0X0X-0X0X0X gibi görünür) kopyalayın ve projenize bağlayın:

gcloud billing projects link fashion-app-demo \
 --billing-account=YOUR_BILLING_ACCOUNT_ID

Bağlantıyı doğrulayın:

gcloud billing projects describe fashion-app-demo

billingEnabled: true ifadesini görürsünüz. Bağlama işleminden sonra bile billingEnabled: false simgesini görüyorsanız hesap kapatılmıştır (OPEN: False). Aşağıdaki sorun giderme bölümüne bakın.

3. Gerekli API'leri Etkinleştirme

gcloud services enable \
 aiplatform.googleapis.com \
 storage.googleapis.com \
 run.googleapis.com \
 cloudbuild.googleapis.com \
 artifactregistry.googleapis.com

API

Amaç

aiplatform.googleapis.com

Vertex AI: fitting_tool, Vertex AI aracılığıyla Gemini'ın görüntü oluşturma işlevini çağırır.

storage.googleapis.com

Cloud Storage: Ürün kataloğu resimlerini ve oluşturulan deneme sonuçlarını depolar.

run.googleapis.com

Cloud Run: Arka ucu sunucusuz bir container olarak barındırır.

cloudbuild.googleapis.com

Cloud Build: Kaynaktan Docker görüntüleri oluşturur.

artifactregistry.googleapis.com

Artifact Registry: Oluşturulan Docker görüntülerini depolar.

4. GCS paketi oluşturma

export PROJECT_ID=$(gcloud config get-value project)
gcloud storage buckets create gs://fashion-app-$PROJECT_ID \
 --location=us-central1 \
 --uniform-bucket-level-access

5. Ürün Kataloğu Görsellerini Yükleme

Arka uçtaki getProductImage aracı, gs://$GCS_BUCKET/catalog-assets/images/ kaynağından okuma yapar. Katalog resimlerini tam olarak şu yola yükleyin:

cd ~/fashion_app_demo
gcloud storage cp flutter_frontend/assets/images/*.png \
 gs://fashion-app-$PROJECT_ID/catalog-assets/images/

Yüklemeyi doğrulayın (.png dosyanın listesini görmelisiniz):

gcloud storage ls gs://fashion-app-$PROJECT_ID/catalog-assets/images/

6. .env dosyasını yapılandırma

cd ~/fashion_app_demo/adk_backend
cat > .env << EOF
GOOGLE_CLOUD_PROJECT=$PROJECT_ID
GCS_BUCKET=fashion-app-$PROJECT_ID
EOF

7. Uygulama Varsayılan Kimlik Bilgileri ile kimlik doğrulama

Arka ucu yerel olarak başlatmadan önce bu komutu çalıştırmanız gerekir. Go arka ucu, Vertex AI (Gemini) ve Cloud Storage'a yapılan her çağrının kimliğini doğrulamak için ADC'yi kullanır. ADC olmadan arka uç başlatılır ancak her deneme isteği 401 CREDENTIALS_MISSING ile başarısız olur.

Tek bir kimlik bilgisi her iki hizmet için de geçerlidir. Şu iki komutu sırayla çalıştırın:

# 1. Log in (opens a browser; in Cloud Shell, paste the verification code back)
gcloud auth application-default login

# 2. Attach your project as the quota / billing project for ADC
gcloud auth application-default set-quota-project $(gcloud config get-value project)

ADC'nin sağlıklı olduğunu doğrulayın:

gcloud auth application-default print-access-token | head -c 20 && echo "..."

Jetonun yaklaşık 20 karakterini ve ardından ... simgesini görmeniz gerekir. Hata verirse giriş başarısız olmuştur. 1. adımı tekrar çalıştırın.

4. 🏗️ Mimariye Genel Bakış

Ortam hazır olduğuna göre, koda bakmadan önce sistemin nasıl çalıştığını anlayalım.

Dört Temsilcili Sistem

Arka uç, Go için ADK (Agent Development Kit) kullanılarak çoklu temsilci sistemi olarak oluşturulur. Her biri belirli bir sorumluluğa sahip dört temsilci birlikte çalışır:

                   ┌──────────────┐
                    Root Agent      Routes requests to the right agent
                    (gemini-3-   
                     flash-      
                     preview)    
                   └──────┬───────┘
                          
           ┌──────────────┼──────────────┐
                                       
  ┌────────────────┐ ┌──────────┐ ┌────────────────┐
   Fitting Room     Catalog    Stylist        
   Agent            Agent      Agent          
   (gemini-3.1-     (gemini-   (gemini-3.1-   
    pro-preview)     3-flash-   pro-preview)  
                     preview)                 
   Tools:                      Tools:         
    fitting_tool   Tools:      fitting_tool 
    getProduct      list      getProduct   
     Image          Products     Image        
    catalog_agent   get       catalog_agent
     (delegation)   Product      (delegation) 
                     Image      generate_    
  └────────────────┘ └──────────┘    outfit_image 
                                  └────────────────┘

Temsilci

Model

Rol

Root Agent

gemini-3-flash-preview

Trafik polisi. Kullanıcının mesajını okur ve doğru uzman ajana yönlendirir. Yalnızca yönlendirme kararları vermesi gerektiğinden hızlı ve basit bir model kullanır.

Catalog Agent

gemini-3-flash-preview

Ürün uzmanı. Ürün kataloğunu bir YAML dosyasından yükler ve ürün sorgularını yanıtlar. Ayrıca hafiftir. Yalnızca verileri arar.

Fitting Room Agent

gemini-3.1-pro-preview

Sanal deneme uzmanı. Kullanıcı fotoğrafı ve ürün resmini alarak kişinin bu ürünü giydiği bir birleşik resim oluşturur. Görseller hakkında akıl yürütmesi gerektiği için daha yetenekli bir model kullanır.

Stylist Agent

gemini-3.1-pro-preview

Moda danışmanı. Konum, etkinlik ve tercihler göz önünde bulundurularak katalogdan 3 kıyafet kombinasyonu oluşturulur. Her kıyafet için deneme resimleri oluşturabilir. Ayrıca, reklam öğesiyle ilgili akıl yürütme için de yetenekli modeli kullanır.

Giriş noktası: main.go

Her şey, aracıları birbirine bağlayan ve HTTP sunucusunu başlatan main.go ile başlar:

// main.go  simplified for clarity


func main() {
   godotenv.Load()  // Load .env file


   // 1. Create the artifact storage (GCS-backed)
   artifacts, _ := gcsartifact.NewService(ctx, bucket)


   // 2. Build agents bottom-up (dependencies first)
   catagent, _    := catalog.NewCatalogAgent(apikey, "catalog/catalog.yaml")
   fitagent, _    := fittingroom.NewFittingRoomAgent(apikey, catagent)
   stylistAgent, _ := stylist.NewStylistAgent(apikey, catagent)
   ragent, _      := rootagent.NewRootAgent(apikey, fitagent, catagent, stylistAgent)


   // 3. Register all agents with a multi-loader
   loader, _ := agent.NewMultiLoader(ragent, fitagent, catagent, stylistAgent)


   // 4. Create the ADK REST server
   restHandler, _ := adkrest.NewServer(adkrest.ServerConfig{
       SessionService:  session.InMemoryService(),
       MemoryService:   memory.InMemoryService(),
       AgentLoader:     loader,
       ArtifactService: artifacts,
   })


   // 5. Mount behind /api/ with CORS support
   r := mux.NewRouter()
   r.Use(tools.LocalhostCORS)
   r.PathPrefix("/api/").Handler(
       http.StripPrefix("/api", tools.LogHandler(restHandler)))


   http.Server{Addr: ":8080", Handler: r}.ListenAndServe()
}

Dikkat edilmesi gereken birkaç önemli nokta:

  • Ajanlar aşağıdan yukarıya doğru oluşturulur: Hem deneme odası hem de stilist ajanları katalog ajanına bağlı olduğundan (ürün aramalarını katalog ajanına devrederler) önce katalog ajanı oluşturulur.
  • agent.NewMultiLoader, REST API'nin ada göre herhangi birine yönlendirebilmesi için dört aracının tümünü kaydeder.
  • adkrest.NewServer, REST API'yi otomatik olarak sağlar. Uç nokta işleyicilerini kendiniz yazmazsınız. ADK, kullanıma hazır oturum yönetimi, yapay nesne depolama ve aracı yürütme özellikleri sunar.
  • session.InMemoryService() oturumları bellekte depolar. Bu, sunucu yeniden başlatılırsa oturumların kaybolacağı anlamına gelir. Bu durum, demo için uygundur. Üretimde kalıcı bir mağaza kullanırsınız.
  • gcsartifact.NewService, yapay görüntüleri Google Cloud Storage'da saklar. Böylece, bu görüntüler istekler arasında kalıcı olur ve GCS URI'leri aracılığıyla paylaşılabilir.

5. 🤖 ADK (Agent Development Kit) Ayrıntılı İncelemesi

ADK nedir?

Agent Development Kit (ADK), Google'ın Go (ve Python/Java) dilinde yapay zeka ajanları oluşturmak için kullandığı açık kaynaklı bir çerçevedir. Uygulamanız ile Gemini API arasındaki katmandır.

Gemini API'yi doğrudan çağırabilirsiniz. Ancak uygulamanızın şunları yapması gerektiğinde:

  • Katalogdaki ürünleri arama
  • Kullanıcı fotoğraflarına göre resim oluşturma
  • Daha önce hangi kıyafetlerin önerildiğini hatırlama
  • Birden fazla yapay zeka aracısını koordine etme

Yapıya ihtiyacınız var. ADK bu yapıyı sağlar.

The Agent Loop

Her ADK aracısı bir döngü izler:

1. Receive a message (from user or another agent)
2. Think  the LLM reasons about what to do
3. Act  call a tool, delegate to a sub-agent, or respond
4. Return  send the result back

Bu döngü, tek bir istek içinde birden çok kez tekrarlanabilir. Örneğin, stilist asistan şunları yapabilir:

  1. "Beni plaj tatiline göre giydir" istemine yanıt alma
  2. Ürün listesini almak için catalog_agent aracını çağırın.
  3. 3 kıyafet kombinasyonu seçin
  4. Görüntü oluşturmak için her kıyafet için fitting_tool işlevini kullanın.
  5. Yapılandırılmış JSON yanıtını döndürme

Temel Kavramlar (Bu Depodaki Kodla)

LLM Temsilcileri

Birincil yapı taşı. llmagent.New() ile oluşturuldu:

// From catalog/agent.go
agent, err := llmagent.New(llmagent.Config{
   Name:        "catalog_agent",                    // Unique identifier
   Model:       m,                                  // Which LLM to use
   Description: "An agent that can search and list products from the catalog",
   Instruction: instructions,                       // Persona prompt (embedded from .md file)
   Tools:       []tool.Tool{listTool, imageTool},   // What the agent can do
})

Instruction alanı, ajanın karakteridir. LLM'ye kim olduğunu ve nasıl davranması gerektiğini söyler. Bu depoda talimatlar, Markdown dosyaları olarak yazılır ve Go'nun //go:embed yönergesi kullanılarak derleme zamanında yerleştirilir:

//go:embed instructions.md
var instructions string

Bu sayede istemler satır içi dizeler yerine ayrı, sürümlendirilebilir belgeler olarak tutulur.

Araçlar

Araçlar, LLM'nin çağırabileceği Go işlevleridir. ADK, LLM'nin araç çağırma biçimi ile yazdığınız Go işlevi arasındaki çeviriyi gerçekleştirir:

// From catalog/agent.go
type ListProductsArgs struct{}                    // Input (can be empty)
type ListProductsResult struct {
   Products []Product `json:"products"`          // Output
}


func ListProducts(ctx tool.Context, args ListProductsArgs) (ListProductsResult, error) {
   return ListProductsResult{Products: catalogProducts}, nil
}


// Register it:
listTool, _ := functiontool.New(functiontool.Config{
   Name:        "listProducts",
   Description: "list all products in the catalog",
}, ListProducts)

ADK, Go yapılarınızdan otomatik olarak bir JSON şeması oluşturur ve bunu LLM'ye gönderir. LLM, listProducts işlevini çağırmaya karar verdiğinde ADK, bağımsız değişkenlerin seri durumunu kaldırır, işlevinizi çağırır ve sonucu geri gönderir.

tool.Context parametresi, araçların ADK'nın çalışma zamanı hizmetlerine (en önemlisi yapılar) erişmesini sağlar:

// Save an image as an artifact
ctx.Artifacts().Save(ctx, "my_image", imagePart)


// Load an artifact
resp, _ := ctx.Artifacts().Load(ctx, "my_image")

Alt Temsilci Yetkisi

Bir temsilci, agenttool.New() aracılığıyla başka bir temsilciyi araç olarak kullanabilir:

// From fittingroom/agent.go
Tools: []tool.Tool{
   loadartifactstool.New(),              // List available artifacts
   imgtool,                              // Get product images
   agenttool.New(catalogAgent, nil),     // Delegate to catalog agent
   fittingTool,                          // Generate try-on image
},

Deneme odası temsilcisi, ürün bilgisine ihtiyaç duyduğunda katalog temsilcisini normal bir araç gibi çağırabilir. LLM, araç listesinde bu aracı görür ve çağırmaya karar verebilir.

Oturum sayısı

Oturumlar, görüşme geçmişini izler. ADK'nın REST API'si bunları otomatik olarak yönetir:

POST /api/apps/{appName}/users/{userId}/sessions    Creates a new session
POST /api/run  (with sessionId)                     Runs agent within that session

Bu uygulamadaki önemli bir tasarım kararı: Deneme odası, her istek için yeni bir oturum oluşturur (her deneme birbirinden bağımsızdır). Stilist ise aynı oturumu yeniden kullanır (böylece önceki önerileri hatırlar ve geri bildirimlere göre önerileri iyileştirebilir).

Eyalet

Durum, bir oturuma eklenmiş anahtar/değer deposudur. Aracılar, koordinasyon için durumu okur ve yazar:

// Write to state
ctx.State().Set("previously_used_products", "[\"id_bomber\",\"id_hat\"]")


// Read from state
val, err := ctx.State().Get("previously_used_products")

Stilist aracısı, hangi ürünleri önerdiğini hatırlamak için durumu kullanır. Böylece bir sonraki sefer farklı ürünler seçer.

Yapılar

Yapılar, oturum başına depolanan, adlandırılmış ikili nesnelerdir (genellikle resimler). Metin yanıtlarının aksine, bunlar ayrı olarak depolanır ve ada göre getirilir:

// Save a generated image as an artifact
artName := fmt.Sprintf("generated_fitting_%s_%s", ctx.InvocationID(), uuid.NewString()[:8])
ctx.Artifacts().Save(ctx, artName, imagePart)


// The frontend fetches it via:
// GET /api/apps/{app}/users/{user}/sessions/{session}/artifacts/{artName}

Bu sayede yanıtlar hafif tutulur. Aracı yalnızca yapay nesne adını döndürür ve ön uç, ikili görüntü verilerini ayrı olarak getirir.

Geri aramalar

Geri çağırmalar, aracı döngüsünde belirli noktalarda çalışan kancalardır. Şunları inceleyebilir, değiştirebilir veya yürütmeyi kısa devre edebilirler:

llmagent.Config{
   // Runs before the agent starts  used to save uploaded images
   BeforeAgentCallbacks: []agent.BeforeAgentCallback{SaveIncomingBlobs},


   // Runs before each LLM call  used to inject context
   BeforeModelCallbacks: []llmagent.BeforeModelCallback{
       ExtractAndInjectUserImage,
       InjectPreviousProducts,
   },


   // Runs after each LLM response  used to extract data
   AfterModelCallbacks: []llmagent.AfterModelCallback{SaveSelectedProducts},
}

Geri çağırma işlevi sıfır olmayan bir yanıt döndürürse varsayılan davranış atlanır. Örneğin, önbelleğe alınmış bir yanıt döndüren bir BeforeModelCallback, gerçek LLM çağrısını tamamen atlar.

JSON Şeması Uygulaması

Hem deneme odası hem de stilist aracı, LLM'yi yapılandırılmış JSON biçiminde yanıt vermeye zorlar:

GenerateContentConfig: &genai.GenerateContentConfig{
   ResponseMIMEType:   "application/json",
   ResponseJsonSchema: fittingSchemaMap(),  // Defines the expected structure
}

Bu sayede Flutter ön ucu her zaman ayrıştırılabilir veriler alır, serbest biçimli metinler almaz.

Katalog Aracısı: En Basit Örnek

Katalog aracısı (catalog/agent.go), sistemdeki en basit aracıdır. ADK kalıplarını anlamak için iyi bir başlangıç noktasıdır.

İki aracı vardır:

  1. listProducts: YAML dosyasından tam ürün kataloğunu döndürür.
  2. getProductImage: GCS'den (veya yerel yedeklemeden) bir ürün resmi yükler ve bunu yapay ürün olarak kaydeder.

getProductImage aracı, önemli bir kalıbı gösterir: yapı önbelleğe alma ile çok kaynaklı yükleme:

// From tools/imagetool.go  simplified
func GetProductImage(ctx tool.Context, args GetProductImageArgs) (GetProductImageResult, error) {
   // First: check if it's already an artifact
   _, err := ctx.Artifacts().Load(ctx, args.ImageName)
   if err == nil {
       return GetProductImageResult{Image: args.ImageName}, nil  // Cache hit!
   }


   // Second: try loading from GCS bucket
   rc, err := client.Bucket(bucket).Object("catalog-assets/images/" + args.ImageName).NewReader(ctx)
   if err != nil {
       // Third: fall back to local filesystem
       localData, _ := os.ReadFile("../flutter_frontend/assets/images/" + args.ImageName)
       ctx.Artifacts().Save(ctx, args.ImageName, &genai.Part{InlineData: ...})
       return GetProductImageResult{Image: args.ImageName}, nil
   }


   // Save to artifact cache and return
   ctx.Artifacts().Save(ctx, args.ImageName, &genai.Part{InlineData: ...})
   return GetProductImageResult{Image: args.ImageName}, nil
}

Araç önce yapay nesneleri, ardından GCS'yi, sonra da yerel dosyaları dener. Yüklendikten sonra resim, yapay nesne olarak önbelleğe alınır. Böylece sonraki aramalar anında gerçekleşir.

6. 🧪 Yapay Zeka İşlem Hattı: Temsilciler İş Başında

Şimdi de en gelişmiş iki aracı, yani yapay görüntü oluşturan ve kıyafetleri seçen araçları inceleyelim.

6.1 Deneme Kabini Aracısı

Dosya:

adk_backend/fittingroom/agent.go

Deneme kabini aracısı, "Sanal Deneme"nin arkasındaki motordur. Kullanıcı fotoğrafını yükleyip bir ürün seçtiğinde bu aracı, kişinin söz konusu ürünü giydiği birleşik bir resim oluşturur.

fitting_tool — Adım Adım

Temel mantık, doFitting işlevinde bulunur. Temsilci bu durumu bildirdiğinde şu işlemler yapılır:

1. adım: Kullanıcı resmini düzeltin

func doFitting(ctx tool.Context, args FittingToolArgs) (FittingToolResult, error) {
   if len(args.Accessories) > 2 {
       args.Accessories = args.Accessories[:2]  // Safety limit: max 2 items
   }


   var userPart *genai.Part
   if strings.HasPrefix(args.UserImage, "gs://") {
       // If we have a GCS URI from a previous fitting, use it directly
       userPart = &genai.Part{FileData: &genai.FileData{
           FileURI:  args.UserImage,
           MIMEType: gcsURIMimeType(args.UserImage),
       }}
   } else {
       // Otherwise, load the image from artifact storage
       userImgResp, err := ctx.Artifacts().Load(ctx, args.UserImage)
       userPart = userImgResp.Part
   }

Kullanıcı resmi iki kaynaktan gelebilir:

  • Bir yapı adı (ör. upload_abc123_1): Bu, SaveIncomingBlobs geri çağırma işlevi tarafından kaydedilen ilk yüklemedir.
  • Bir gs:// URI: Bu, daha önce oluşturulmuş bir uygunluk sonucudur ve oturumlar arası yeniden kullanım için GCS'de saklanır.

Bu çift tasarımın amacı şudur: Stilist aracısı daha sonra kıyafet denemeleri oluşturduğunda, ilk deneme odası sonucundaki GCS URL'sini yeniden kullanır. Böylece kullanıcının kimliği tüm kıyafetlerde tutarlı kalır.

2. adım: Çok formatlı istemi oluşturun

   parts := []*genai.Part{
       genai.NewPartFromText(toolInstructions),           // Identity preservation prompt
       genai.NewPartFromText("Reference Person Photo:"),
       userPart,                                          // The user's photo
   }


   for _, acc := range args.Accessories {
       accResp, _ := ctx.Artifacts().Load(ctx, acc)       // Load product image artifact
       parts = append(parts, genai.NewPartFromText("Product Image to Apply:"))
       parts = append(parts, accResp.Part)                // The product photo
   }

toolInstructions (tool_instructions.md öğesinden yerleştirilmiş) çok önemlidir. Bu öğe, Gemini'a yalnızca giyim öğesini uygularken kullanıcının kimliğini (yüz, vücut tipi, ten rengi, saç) korumasını söyler. Bu istem mühendisliği olmadan model, kişinin görünümünü değiştirebilir.

3. adım: Görüntü üretmek için Gemini'ı çağırın

   client, _ := genai.NewClient(ctx, &genai.ClientConfig{
       Backend:  genai.BackendVertexAI,                 // Vertex AI endpoint
       Project:  os.Getenv("GOOGLE_CLOUD_PROJECT"),     // From your .env
       Location: "global",                              // Multi-region endpoint
   })


   resp, _ := client.Models.GenerateContent(ctx, "gemini-2.5-flash-image",
       []*genai.Content{genai.NewContentFromParts(parts, "user")},
       &genai.GenerateContentConfig{
           ResponseModalities: []string{"TEXT", "IMAGE"},  // Request both text and image output
           Temperature:        genai.Ptr(float32(0.2)),    // Low temperature for consistency
       })

Dört aracının tamamı ve görüntü oluşturma aracı, tek bir kimlik doğrulama yolu kullanır: Backend: genai.BackendVertexAI ile proje kimliği, Uygulama Varsayılan Kimlik Bilgileri aracılığıyla kimliği doğrulanır. Orkestrasyon modelleri (gemini-3.1-pro-preview, gemini-3-flash-preview) ve görüntü modeli (gemini-2.5-flash-image) aynı Vertex AI uç noktasının arkasında yer alır. Aynı ADC, Cloud Storage erişimini de yetkilendirir. Yani her çağrı için tek bir kimlik bilgisi kullanılır.

4. adım: Sonucu kaydedin

   // Find the image in the response (may contain both text + image parts)
   var genPart *genai.Part
   for _, p := range resp.Candidates[0].Content.Parts {
       if p.InlineData != nil {
           genPart = p
           break
       }
   }


   // Save as an ADK artifact
   artName := fmt.Sprintf("generated_fitting_%s_%s", ctx.InvocationID(), uuid.NewString()[:8])
   ctx.Artifacts().Save(ctx, artName, genPart)


   // Also upload to GCS for cross-session reuse
   objectName := fmt.Sprintf("generated-fittings/%s.jpg", ctx.InvocationID())
   w := storageClient.Bucket(bucket).Object(objectName).NewWriter(ctx)
   w.Write(genPart.InlineData.Data)
   gcsURI := fmt.Sprintf("gs://%s/%s", bucket, objectName)


   return FittingToolResult{ArtifactName: artName, GCSUrl: gcsURI}, nil

Çift kaydetme (yapay nesne + GCS), deneme kabini ile stilist arasında aracı devretme işleminin anahtarıdır. Yapı, mevcut oturumda anında erişim sağlarken GCS URI'si, stilistin (farklı bir oturumda çalışır) aynı resme daha sonra başvurmasına olanak tanır.

SaveIncomingBlobs Geri Arama

Aracı akıl yürütmeye başlamadan önce, kullanıcının yüklediği tüm resimleri kaydetmek için şu BeforeAgentCallback çalıştırılır:

func SaveIncomingBlobs(ctx agent.CallbackContext) (*genai.Content, error) {
   for pindex, p := range ctx.UserContent().Parts {
       if p.InlineData != nil {
           aname := fmt.Sprintf("upload_%s_%d", ctx.InvocationID(), pindex)
           ctx.Artifacts().Save(ctx, aname, p)
       }
   }
   return nil, nil  // Return nil to proceed with normal agent execution
}

(nil, nil) döndürülerek geri çağırma, "Ön işlemeyi tamamladım. Şimdi aracıyı normal şekilde çalıştırın." sinyalini verir. Sıfır olmayan içerik döndürürse aracı tamamen kısa devre yapardı.

6.2 Stilist Temsilcisi

Dosya:

adk_backend/stylist/agent.go

Stilist aracısı, sistemdeki en gelişmiş aracıdır. Kişiselleştirilmiş kıyafet önerileri sunar ve sohbet yoluyla yinelemeli iyileştirmeyi destekler.

Üç Geri Arama: Stilistin Anıları

Stilist, çok turlu görüşmelerde bağlamı korumak için üç geri çağırma kullanır:

Callback 1:

InjectPreviousProducts (BeforeModel)

Sorun: Kullanıcı "Bana farklı seçenekler göster" derse LLM, daha önce önerdiği ürünleri doğal olarak izlemediği için aynı ürünleri tekrar önerebilir.

Çözüm: Her yanıttan sonra ürün kimlikleri oturum durumuna kaydedilir. Bu geri çağırma, bir sonraki LLM çağrısından önce bunları okur ve bir ipucu ekler:

func InjectPreviousProducts(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
   prev, err := ctx.State().Get(stateKeyPreviousProducts)  // Read from session state
   if err != nil {
       return nil, nil  // No previous state  first run
   }


   // Append hint to the user's message
   for i := len(req.Contents) - 1; i >= 0; i-- {
       if req.Contents[i].Role == "user" {
           req.Contents[i].Parts = append(req.Contents[i].Parts,
               genai.NewPartFromText(fmt.Sprintf(
                   "IMPORTANT: You previously suggested these products: %s. "+
                   "You MUST pick DIFFERENT complementary products this time.", prev)))
           break
       }
   }
   return nil, nil  // Continue to LLM call
}

Callback 2:

ExtractAndInjectUserImage (BeforeModel)

Sorun: Kullanıcı geri bildirimde bulunduğunda ("daha samimi bir üslup kullan") takip mesajında kullanıcının fotoğrafı tekrar yer almıyor. Ancak montaj aracı için bu bilgi gereklidir.

Çözüm: Bu geri çağırma, ilk istekte kullanıcı resmi referansını ayıklar ve durumu kaydeder. Sonraki isteklerde bu bilgiyi yeniden ekler:

func ExtractAndInjectUserImage(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
   var foundImgStr string


   // Search for user image in the latest message
   for i := len(req.Contents) - 1; i >= 0; i-- {
       if req.Contents[i].Role == "user" {
           for _, part := range req.Contents[i].Parts {
               if strings.Contains(part.Text, "User try-on base image") {
                   foundImgStr = part.Text  // Found the GCS URI reference
               }
           }
           break
       }
   }


   if foundImgStr != "" {
       ctx.State().Set(stateKeyUserImageStr, foundImgStr)  // Save for later
   } else {
       // Not in current message  retrieve from state and inject
       val, _ := ctx.State().Get(stateKeyUserImageStr)
       if savedImgStr, ok := val.(string); ok {
           // Inject into the latest user message
           req.Contents[last].Parts = append(req.Contents[last].Parts,
               genai.NewPartFromText("REMINDER: Use this image: " + savedImgStr))
       }
   }
   return nil, nil
}

Callback 3:

SaveSelectedProducts (AfterModel)

LLM, kıyafet önerileriyle yanıt verdikten sonra bu geri çağırma, ürün kimliklerini ayıklamak için JSON'u ayrıştırır ve bunları bir sonraki sefer kullanmak üzere InjectPreviousProducts geri çağırması için kaydeder:

func SaveSelectedProducts(ctx agent.CallbackContext, resp *model.LLMResponse, respErr error) (*model.LLMResponse, error) {
   for _, part := range resp.Content.Parts {
       ids := extractProductIDs(part.Text)  // Parse JSON  extract product IDs
       if len(ids) > 0 {
           data, _ := json.Marshal(ids)
           ctx.State().Set(stateKeyPreviousProducts, string(data))  // Save to state
       }
   }
   return nil, nil  // Don't modify the response
}

Bu üç geri arama birlikte bir geri bildirim döngüsü oluşturur:

Request 1:  User sends styling request + user image
            ExtractAndInjectUserImage SAVES image to state
            LLM generates 3 outfits
            SaveSelectedProducts SAVES product IDs to state


Request 2:  User says "make it more casual"
            ExtractAndInjectUserImage INJECTS saved image into prompt
            InjectPreviousProducts INJECTS "don't reuse these IDs"
            LLM generates 3 NEW outfits
            SaveSelectedProducts UPDATES product IDs in state

6.3 Kök Ajan

Dosya:

adk_backend/rootagent/agent.go

En basit aracı (yalnızca 31 satır):

func NewRootAgent(project string, fittingAgent, catalogAgent, stylistAgent agent.Agent) (agent.Agent, error) {
   m, _ := gemini.NewModel(ctx, "gemini-3-flash-preview", &genai.ClientConfig{
       Backend:  genai.BackendVertexAI,
       Project:  project,
       Location: "global",
   })


   return llmagent.New(llmagent.Config{
       Name:        "root_agent",
       Model:       m,
       Description: "A root agent that delegates to other agents",
       Instruction: "You are a helpful shopping assistant. If the user asks about fitting " +
           "items or generating images, delegate to the fitting room agent. If the user " +
           "asks about products, delegate to the catalog agent. If the user asks for " +
           "styling advice, delegate to the stylist agent.",
       SubAgents: []agent.Agent{fittingAgent, catalogAgent, stylistAgent},
   })
}

Yönlendirme kararları basit olduğundan (LLM'nin yalnızca kullanıcının amacını okuyup doğru alt aracı seçmesi gerekir) gemini-3-flash-preview (en hızlı model) kullanılır. Araç gerekmez. SubAgents, yetki devrini otomatik olarak gerçekleştirir.

7. 📱 Flutter Ön Uç Mimarisi

Flutter ön ucu, tam işlevli bir perakende alışveriş uygulamasıdır. Yapay zeka özellikleri, flutter_frontend/lib/workshop_tasks/ içinde canlı olarak çalışır ve core_app/'deki önceden oluşturulmuş alışveriş deneyiminden ayrıdır.

MVVM Deseni

Uygulama, Provider paketiyle Model-View-ViewModel mimarisini kullanır:

┌──────────────────┐    ┌────────────────────┐    ┌──────────────────┐
  View (Widget)   │◀───│ ViewModel (Provider)│◀───│  Service (HTTP)  
                                                                 
  Renders UI           Holds state             Makes API calls
  User gestures  │───▶│  Business logic    │───▶│  Parses response
  Listens for          notifyListeners()       Returns data   
   state changes                                                 
└──────────────────┘    └────────────────────┘    └──────────────────┘

Her katmanın net bir rolü vardır:

  • Model: Product, Outfit, StyleRequest gibi veri sınıfları ve TryOnState gibi numaralandırılmış türler
  • ViewModel (ChangeNotifier): Mevcut durumu tutar ve notifyListeners() aracılığıyla kullanıcı arayüzündeki değişiklikleri yayınlar.
  • Görünüm (Widget): context.watch() ile ViewModel'e abone olur ve durum değiştiğinde yeniden oluşturur.
  • Hizmet: ADK arka ucuna HTTP çağrıları yapar ve türü belirlenmiş veriler döndürür.

Hizmet Katmanı

Hizmetler, ADK'ya özgü uygulamalarla birlikte soyut arayüzler olarak tanımlanır:

// Abstract interface  defines WHAT the service does
abstract class TryItOnService {
 Future<(Uint8List?, String?)> generateTryOnImage(
   Uint8List userImageBytes,
   Uint8List productImageBytes,
 );
}


// Concrete implementation  defines HOW (via ADK REST API)
class AdkFittingRoomService implements TryItOnService { ... }

Bu ayrım, uygulamanın geri kalanını değiştirmeden ADK arka ucunu Firebase AI, bir sahte hizmet veya başka bir uygulama ile değiştirebileceğiniz anlamına gelir.

3 Adımlı API Kalıbı

Hem AdkFittingRoomService hem de AdkStylingService, ADK arka ucuyla iletişim kurmak için aynı kalıbı kullanır:

1. adım: Oturum oluşturun

Future<String> _createSession() async {
 final url = Uri.parse(
   '$_baseUrl/apps/${Uri.encodeComponent(_appName)}/users/$_userId/sessions');
 final response = await _client.post(url,
   headers: {'Content-Type': 'application/json'},
   body: jsonEncode({}));
 final body = jsonDecode(response.body) as Map<String, dynamic>;
 return body['id'] as String;  // Returns the session ID
}

2. adım: Aracıyı çalıştırın

Future<(String?, String?)> _runAgent({required String sessionId, ...}) async {
 final requestBody = jsonEncode({
   'appName': _appName,
   'userId': _userId,
   'sessionId': sessionId,
   'newMessage': {
     'role': 'user',
     'parts': [
       {'text': 'Generate a virtual try-on...'},
       {'inlineData': {'mimeType': 'image/jpeg', 'data': base64Encode(userImageBytes)}},
       {'inlineData': {'mimeType': 'image/png', 'data': base64Encode(productImageBytes)}},
     ],
   },
 });
 final response = await _client.post(Uri.parse('$_baseUrl/run'), body: requestBody);
 // Parse response events for artifact name and GCS URL...
}

3. adım: Yapıyı getirin

Future<Uint8List?> _loadArtifact({required String sessionId, required String artifactName}) async {
 final url = Uri.parse(
   '$_baseUrl/apps/$_appName/users/$_userId/sessions/$sessionId/artifacts/$artifactName');
 final response = await _client.get(url);
 final part = jsonDecode(response.body) as Map<String, dynamic>;
 final data = part['inlineData']['data'] as String;
 return base64Decode(data);  // Returns raw image bytes
}

Önemli bir tasarım farkı: Deneme odası hizmeti her istek için yeni bir oturum oluşturur (_createSession() her seferinde çağrılır). Stil oluşturma hizmeti ise çok turlu görüşmeye olanak tanımak için aynı oturumu yeniden kullanır (_sessionId ??= await _createSession()).

Durum Yönetimi: TryItOnProvider

Dosya:

workshop_tasks/step_1_try_it_on/providers/try_it_on_provider.dart

Tüm deneme akışını TryItOnProvider yönetir. Durum makinesi olarak TryOnState enum'ı kullanır:

enum TryOnState { initial, imagePicked, generating, success, error }


class TryItOnProvider with ChangeNotifier {
 TryOnState _state = TryOnState.initial;
 Uint8List? _userImageBytes;
 Uint8List? _generatedImage;
 String? _errorMessage;

Özel durum geçişleri tutarlılık sağlar. Durumu, eski verileri temizlemeden ve kullanıcı arayüzünü bilgilendirmeden asla güncellemezsiniz:

 void _setGenerating() {
   _state = TryOnState.generating;
   _errorMessage = null;            // Clear any previous error
   _wasLastGenerationCached = false;
   notifyListeners();               // Tell the UI to rebuild
 }


 void _setSuccess(Uint8List image, {bool isCached = false}) {
   _generatedImage = image;
   _errorMessage = null;
   _wasLastGenerationCached = isCached;
   _state = TryOnState.success;
   notifyListeners();
 }

Ana oluşturma yöntemi tüm bunları bir araya getirir:

 Future<String?> generateTryOnImage(String productImagePath) async {
   final userImageBytes = _userImageBytes;
   if (userImageBytes == null) {
     _setError('No image selected.');
     return _errorMessage;
   }


   // Check local cache first
   final cachedImage = ImageUtils.getCachedImage(_sessionCache, userImageBytes, productUint8List);
   if (cachedImage != null) {
     _setSuccess(cachedImage, isCached: true);
     return null;
   }


   _setGenerating();  // Triggers loading state in UI


   try {
     final (generatedBytes, gcsUrl) = await _aiService.generateTryOnImage(
       userImageBytes, productUint8List);
     if (generatedBytes != null) {
       _fittingGcsUrl = gcsUrl;     // Save for the stylist agent later
       _setSuccess(generatedBytes);
     }
   } catch (e) {
     _setError(e.toString());
   }
   return _state == TryOnState.success ? null : _errorMessage;
 }

Kullanıcı arayüzü: Durum yönlendirici olarak ekranlar

Dosya:

workshop_tasks/step_1_try_it_on/ui/2_try_it_on_screen.dart

Deneme ekranı, sağlayıcının durumuna göre alt ekranlar arasında yönlendirme yapmak için Dart 3'ün AnimatedSwitcher ile desen eşleştirme özelliğini kullanır:

class TryItOnScreen extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   final tryOnProvider = context.watch<TryItOnProvider>();


   return ScaffoldWithBackgroundNoise(
     appBar: const TryOnAppBar(),
     body: AnimatedSwitcher(
       duration: AppDurations.medium,
       child: switch (tryOnProvider.state) {
         TryOnState.initial || TryOnState.error => const ChooseImageScreen(),
         TryOnState.imagePicked || TryOnState.generating => LoadingScreen(
           userImage: tryOnProvider.userImageBytes),
         TryOnState.success => const FittingRoomScreen(),
       },
     ),
   );
 }
}

context.watch(), sağlayıcıya abone olur. notifyListeners() her çağrıldığında bu widget yeniden oluşturulur ve AnimatedSwitcher ekranlar arasında sorunsuz bir şekilde geçiş yapar. Navigator.push yok. Ekran içeriği, durum numaralandırmasına göre yerinde değişir.

Ajan tabanlı devretme: Soyunma kabini → Stilist

En ilginç kullanıcı deneyimi kalıbı, uygulamanın bağlamı deneme odası temsilcisinden stilist temsilcisine nasıl aktardığıdır.

5_fitting_room.dart'da, deneme resmi oluşturulduktan sonra "Stilimi Oluştur" düğmesi bir form açar. Kullanıcı aşağıdaki durumlarda gönderim yaptığında:

// From 1_style_me_form_sheet.dart
Navigator.pop(context, StyleRequest(
 location: _locationController.text.trim(),
 occasion: _occasionController.text.trim(),
 notes: _notesController.text.trim(),
 gcsUserImageUrl: provider.fittingGcsUrl,          // GCS URI from fitting result
 userImageData: provider.fittingGcsUrl == null
     ? provider.userImageBytes : null,              // Fallback to raw bytes
 selectedProductId: provider.selectedProduct?.id,   // Product they already tried on
 selectedProductTitle: provider.selectedProduct?.title,
));

StyleRequest, stilistin ihtiyaç duyduğu her şeyi içerir:

  • Konum ve durum: Stile yönelik metin bağlamı
  • GCS kullanıcı resmi URL'si: Stilist, tam olarak aynı kullanıcı temsilini yeniden kullanabilir.
  • Seçilen ürün: Stilist, bu ürünü her kıyafete dahil eder.

Bu, ajan tabanlı devretme işlemidir. Kullanıcı yalnızca basit bir form görürken çok formatlı bağlam, bir yapay zeka ajanından diğerine sorunsuz bir şekilde aktarılır.

Stil Akışı: StylingProvider

Dosya:

workshop_tasks/step_2_style_me/providers/styling_provider.dart

StylingProvider, karmaşıklığın büyük bir kısmını arka uca devrettiği için TryItOnProvider'den daha basittir:

class StylingProvider with ChangeNotifier {
 StylingState _state = StylingState.initial;
 List<Outfit> _outfits = [];


 Future<bool> getStyleSuggestions(StyleRequest request) async {
   if (_state == StylingState.loading) return false;  // Prevent spam
   _currentRequest = request;
   _setLoading();
   try {
     final suggestions = await _stylingService.getStyleSuggestions(request);
     _setSuccess(suggestions);
     return true;
   } catch (e) {
     _setError('Something went wrong.');
   }
   return false;
 }


 // Multi-turn refinement  uses the same session
 Future<bool> refineWithFeedback(String feedback) async {
   if (_state == StylingState.loading) return false;
   _setLoading();
   try {
     final suggestions = await _stylingService.refineWithFeedback(feedback);
     _setSuccess(suggestions);
     return true;
   } catch (e) {
     _setError('Something went wrong.');
   }
   return false;
 }
}

refineWithFeedback yöntemi, aynı oturuma düz metin mesajı gönderir. Arka uçtaki InjectPreviousProducts ve ExtractAndInjectUserImage geri çağırmaları, tüm bağlam yönetimini otomatik olarak gerçekleştirir.

8. 🚀 Uygulamayı Yerel Olarak Çalıştırma

Sorunsuz bir Cloud Shell deneyimi için Go arka ucu, derlenmiş Flutter web uygulamasını aynı bağlantı noktasından (8080) sunar. Tek işlem, tek önizleme URL'si, kaynaklar arası sorun yok, yapılandırma dosyalarını düzenleme yok.

Başlamadan önce: ADC'yi kontrol edin

Arka uç, Vertex AI'ı çağırmak için Uygulama Varsayılan Kimlik Bilgileri'ne ihtiyaç duyar. Proje kurulumunun 7. adımını bu Cloud Shell oturumunda ve bu Google Hesabı'nda tamamladıysanız sorun yok. Ara verdikten sonra geri dönüyorsanız, hesap değiştirdiyseniz veya emin değilseniz 5 saniye içinde doğrulama yapın:

gcloud auth application-default print-access-token | head -c 20 && echo "..."

Bu komutla jetonun yaklaşık 20 karakteri yazdırılıyorsa sorun yok demektir. Hata oluşursa proje kurulumunun 7. adımını yeniden çalıştırın:

gcloud auth application-default login
gcloud auth application-default set-quota-project $(gcloud config get-value project)

İki Cloud Shell terminali kullanacaksınız:

  • Terminal A: Arka ucu sürekli olarak çalıştırır (./run.sh). Açık bırakın.
  • Terminal B: Flutter web derlemesini bir kez çalıştırır (flutter build web). İşlem tamamlandığında çıkar.

Sıra önemli değildir. Önce istediğinizle başlayabilirsiniz. Ancak en sorunsuz ilk çalıştırma deneyimi için önce Flutter'ı oluşturun. Böylece arka uç, başlatıldığı andan itibaren hizmet verebileceği bir kullanıcı arayüzüne sahip olur.

1. Terminal B: Flutter Web Bundle'ı oluşturma (tek seferlik)

Yeni bir Cloud Shell sekmesi açın (terminal panelinin üst kısmındaki +), ardından:

cd ~/fashion_app_demo/flutter_frontend
flutter pub get
flutter build web

Bu işlem, statik dosyaların (HTML, JS, öğeler) bulunduğu bir dizin olan flutter_frontend/build/web/ oluşturur ve tamamlandığında çıkar. Arka uç, dizinin var olduğunu gördüğü anda bunları sunar.

2. Terminal A: Arka ucu başlatın (uzun süren işlem)

Orijinal Cloud Shell terminalinizde:

cd ~/fashion_app_demo/adk_backend
./run.sh

Aşağıdakine benzer bir ifade görürsünüz:

Serving Flutter web build from ../flutter_frontend/build/web

Bu terminali çalışır durumda bırakın: Arka uç, run.sh çalışmaya devam ettiği sürece açık kalır. Durdurmak için Ctrl+C tuşuna basın.

Sunucu, 8080 numaralı bağlantı noktasında her şeyi kullanıma sunar:

  • /: Flutter web uygulaması (alışveriş kullanıcı arayüzü)
  • /api/: ADK REST uç noktaları (Flutter uygulaması tarafından çağrılır)
  • ADK Dev UI: Flutter derlemesi olmadığında / adresinde de bulunur. Doğrudan aracı hata ayıklama için kullanışlıdır.

3. Web önizlemesini açma

  1. Cloud Shell'de Web Önizlemesi simgesini (sağ üst) → 8080 bağlantı noktasında önizle'yi tıklayın.
  2. Flutter alışveriş uygulaması yeni sekmede yüklenir.
  3. Ürün kataloğuna göz atın ve bir öğe seçin.
  4. Deneme akışını başlatmak için kişi simgesine (👤) dokunun.
  5. Fotoğraf yükleyin ve yapay zekanın deneme görüntüsü oluşturmasını izleyin
  6. Kıyafet önerileri almak için "Stil Öner"e dokunun.
  7. "Daha samimi bir üslup kullan" gibi takip geri bildirimi yazma (aynı oturumda iyileştirme)

9. ☁️ Cloud Run'a dağıtma

Flutter derlemesini arka uca paketleme

Cloud Run container'ı, tek bir görüntüden hem API'yi hem de kullanıcı arayüzünü gönderir. Flutter web derlemesini adk_backend/flutter_web/ içine kopyalayın. Bu, Go sunucusunun hangi kullanıcı arayüzünün sunulacağını seçerken kontrol ettiği ilk yoldur:

cd ~/fashion_app_demo/flutter_frontend
flutter build web
rm -rf ../adk_backend/flutter_web
cp -r build/web ../adk_backend/flutter_web

(Yerel olarak yineleme yaptıysanız Yerel Olarak Çalıştır adımından build/web öğesini almış olabilirsiniz. flutter build web'yı yeniden çalıştırmanızda bir sakınca yoktur.)

Arka ucu dağıtma (API + kullanıcı arayüzüne hizmet verir)

cd ~/fashion_app_demo/adk_backend

gcloud run deploy fashion-app-backend \
 --source . \
 --region us-central1 \
 --allow-unauthenticated \
 --set-env-vars "GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GCS_BUCKET=fashion-app-$PROJECT_ID" \
 --memory 1Gi \
 --cpu 2 \
 --timeout 300s \
 --min-instances 0 \
 --max-instances 3

Dağıtım tamamlandığında https://fashion-app-backend-xyz-uc.a.run.app gibi bir hizmet URL'si alırsınız. Uygulamayı bir tarayıcıda açın. Flutter alışveriş uygulaması / adresinden yüklenir ve API çağrıları aynı ana makinedeki /api/ adresine gider. Ön uç yapılandırmasında düzenleme yapılması gerekmiyor, API anahtarı iletilmedi.

Dağıtımı doğrulama

Tarayıcınızda Cloud Run URL'sini açın ve tüm akışı çalıştırın:

  1. Göz at → Bir ürün seçin
  2. Üzerinizde deneyin → Fotoğrafınızı yükleyin → Yapay zekayla üretilen görüntüyü görün
  3. Stil Oluştur → Konum/etkinlik bilgilerini girin → Seçilmiş kıyafetleri görün
  4. Geri bildirim → "Daha gündelik yap" yazın → Güncellenen kıyafetleri görün
  5. Sepete Ekle → Alışveriş sürecini tamamlayın.

10. 🎉 Sonuç

Oluşturduklarınız

Aşağıdaki özelliklerle yapay zeka destekli eksiksiz bir perakende deneyimini keşfettiniz:

  • ✅ Birlikte çalışan 4 uzmanlaşmış ajana sahip çoklu ajan arka ucu
  • ✅ Kişiselleştirilmiş deneme görselleri oluşturan bir sanal deneme odası
  • ✅ Kıyafetleri düzenleyen ve sohbet yoluyla iyileştiren bir yapay zeka stilisti
  • ✅ Temsilci arka ucuna bağlanan bir platformlar arası Flutter uygulaması
  • ✅ Ölçeklenebilir, sunucusuz barındırma için Cloud Run dağıtımı

Temel Kavramlar

Kavram

Nerede Gördüğünüz

ADK çoklu temsilci düzenleme

Uygunluk odası, katalog ve stilist temsilcilerine temel temsilci yönlendirmesi

Çoklu format destekli Gemini ile görüntü üretme

fitting_tool Kullanıcı fotoğraflarını ürün resimleriyle birleştirme

Etkileşimli yapay zeka için oturum durumu

Stilistlerin, yinelemeli geri bildirim için oturumları yeniden kullanması

İkili veriler için yapay ürün depolama alanı

Görüntü depolama alanını metin yanıtlarından ayırma

Ara katman yazılımı mantığı için geri aramalar

SaveIncomingBlobs, InjectPreviousProducts, SaveSelectedProducts

Flutter'da MVVM + Provider

ChangeNotifier ile TryItOnProvider ve StylingProvider

Temsilci tabanlı devretme

StyleRequest Ajanlar arasında çok formatlı bağlam aktarma

Sonraki Adımlar

  • 🎨 Aracı istemlerini özelleştirme: Stilistin kişiliğini değiştirmek için instructions.md simgesini düzenleyin.
  • 🛍️ Daha fazla ürün ekleyin: catalog.yaml öğesini yeni öğelerle güncelleyin.
  • 📱 Mobil cihazlara uygun reklam öğeleri hazırlama: flutter build ios veya flutter build apk reklamları yayınlayın.
  • 🔄 Kalıcı oturumlar ekleme: InMemoryService yerine veritabanı destekli bir uygulama kullanın.
  • 🔒 Kimlik doğrulama ekleme: Cloud Run uç noktasını IAM ile güvenli hale getirme

Kaynaklar