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.
- 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.
- 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üm → Terminal'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
2. Faturalandırma hesabı bağlama
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ç |
| Vertex AI: |
| Cloud Storage: Ürün kataloğu resimlerini ve oluşturulan deneme sonuçlarını depolar. |
| Cloud Run: Arka ucu sunucusuz bir container olarak barındırır. |
| Cloud Build: Kaynaktan Docker görüntüleri oluşturur. |
| 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 |
| 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 |
| Ü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 |
| 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 |
| 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:
- "Beni plaj tatiline göre giydir" istemine yanıt alma
- Ürün listesini almak için
catalog_agentaracını çağırın. - 3 kıyafet kombinasyonu seçin
- Görüntü oluşturmak için her kıyafet için
fitting_toolişlevini kullanın. - 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:
listProducts: YAML dosyasından tam ürün kataloğunu döndürür.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,SaveIncomingBlobsgeri ç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,StyleRequestgibi veri sınıfları veTryOnStategibi numaralandırılmış türler - ViewModel (
ChangeNotifier): Mevcut durumu tutar venotifyListeners()aracılığıyla kullanıcı arayüzündeki değişiklikleri yayınlar. - Görünüm (Widget):
context.watchile 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
- Cloud Shell'de Web Önizlemesi simgesini (sağ üst) → 8080 bağlantı noktasında önizle'yi tıklayın.
- Flutter alışveriş uygulaması yeni sekmede yüklenir.
- Ürün kataloğuna göz atın ve bir öğe seçin.
- Deneme akışını başlatmak için kişi simgesine (👤) dokunun.
- Fotoğraf yükleyin ve yapay zekanın deneme görüntüsü oluşturmasını izleyin
- Kıyafet önerileri almak için "Stil Öner"e dokunun.
- "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:
- Göz at → Bir ürün seçin
- Üzerinizde deneyin → Fotoğrafınızı yükleyin → Yapay zekayla üretilen görüntüyü görün
- Stil Oluştur → Konum/etkinlik bilgilerini girin → Seçilmiş kıyafetleri görün
- Geri bildirim → "Daha gündelik yap" yazın → Güncellenen kıyafetleri görün
- 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 |
|
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 |
|
Flutter'da MVVM + Provider |
|
Temsilci tabanlı devretme |
|
Sonraki Adımlar
- 🎨 Aracı istemlerini özelleştirme: Stilistin kişiliğini değiştirmek için
instructions.mdsimgesini düzenleyin. - 🛍️ Daha fazla ürün ekleyin:
catalog.yamlöğesini yeni öğelerle güncelleyin. - 📱 Mobil cihazlara uygun reklam öğeleri hazırlama:
flutter build iosveyaflutter build apkreklamları yayınlayın. - 🔄 Kalıcı oturumlar ekleme:
InMemoryServiceyerine veritabanı destekli bir uygulama kullanın. - 🔒 Kimlik doğrulama ekleme: Cloud Run uç noktasını IAM ile güvenli hale getirme
Kaynaklar
- ADK Belgeleri: Resmi Agent Development Kit belgeleri
- ADK Go Kaynak Kodu: GitHub deposu
- ADK Go Package Reference: API referansı
- Gemini API Belgeleri: Model özellikleri ve kılavuzlar
- Flutter Provider Paketi — Durum yönetimi belgeleri
- Cloud Run Belgeleri: Dağıtım ve ölçeklendirme kılavuzları