1. EinfĂŒhrung
Umfang
In diesem Codelab schlĂŒpfen Sie in die Rolle eines Entwicklers, der die Fashion App erstellt, eine Flutter-Shopping-App fĂŒr eine fiktive Einzelhandelsmarke. Ihre Aufgabe: FĂŒgen Sie zwei KI-basierte Funktionen hinzu, die das Online-Shopping-Erlebnis verĂ€ndern.
- Virtueller Anproberaum: Ein Nutzer lĂ€dt ein Foto von sich selbst hoch, wĂ€hlt ein KleidungsstĂŒck aus und sieht ein KI-generiertes Bild von sich selbst mit diesem KleidungsstĂŒck.
- AI Stylist: Basierend auf dem Standort, dem Anlass und den Stilvorlieben des Nutzers stellt ein KI-Agent Empfehlungen fĂŒr komplette Outfits zusammen, die der Nutzer im GesprĂ€ch verfeinern kann.
Die Idee ist einfach: Wenn Nutzer Kleidung in einer Umkleidekabine anprobieren, ist die Wahrscheinlichkeit, dass sie sie kaufen, viel höher. Aber online? Du rĂ€tst doch nur. Dieses Projekt schlieĂt diese LĂŒcke mit KI.
Architektur auf einen Blick
Flutter App ââââ HTTP/REST âââââ¶ ADK Go Backend
â
ââââââââââââŒâââââââââââ
Fitting Room Stylist Catalog
Agent Agent Agent
â
Gemini API + Cloud Storage
Kerntechnologien
Komponente | Technologie | Zweck |
Agenten-Framework | ADK (Agent Development Kit) fĂŒr Go | Orchestrierung mehrerer Agenten, Sitzungen, Artefakte |
Agent Reasoning (Pro) | Gemini 3.1 Pro (Vorabversion) | UnterstĂŒtzt die Kundenservicemitarbeiter in der Umkleidekabine und die Stylisten |
Agent Reasoning (Flash) | Gemini 3 Flash (Vorabversion) | UnterstĂŒtzt die Root- und Katalog-Agents (einfaches Routing/Lookup) |
Bildgenerierung | Gemini 2.5 Flash Image | Generiert Bilder zum Anprobieren und Outfit-Bilder |
Frontend | Flutter (Dart) | PlattformĂŒbergreifende App (Web, iOS, Android) |
Speicher | Google Cloud Storage | Produktbilder und generierte Artefakte speichern |
Hosting | Cloud Run | Bereitstellung serverloser Container |
2. đŠÂ Voraussetzungen und Cloud Shell-Einrichtung
1. Cloud Shell-Editor öffnen
đ Ăffnen Sie den Cloud Shell-Editor in Ihrem Browser.
Wenn das Terminal nicht unten auf dem Bildschirm angezeigt wird:
- Klicken Sie auf Ansicht â Terminal.
2. Flutter SDK einrichten
In Cloud Shell ist Flutter unter /google/flutter vorinstalliert. Da das Verzeichnis einem anderen Systemnutzer gehört, wird beim ersten AusfĂŒhren von flutter ein fatal: detected dubious ownership-Fehler angezeigt. FĂŒgen Sie es einmalig der Liste der sicheren Verzeichnisse von Git hinzu:
git config --global --add safe.directory /google/flutter
PrĂŒfen Sie, ob Flutter auf Ihrem PATH installiert ist und funktioniert:
flutter --version
Beim ersten AusfĂŒhren wird das Dart SDK heruntergeladen und das Flutter-Tool erstellt. Das kann eine Weile dauern. Sie sollten etwa Flutter 3.x âą channel stable sehen.
3. Repository klonen
cd ~
git clone https://github.com/gca-americas/fashion-app-demo
cd fashion_app_demo
4. Projektstruktur ansehen
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-Projekt einrichten
1. Neues Projekt erstellen
gcloud projects create fashion-app-demo --name="Fashion App Demo"
gcloud config set project fashion-app-demo
2. Rechnungskonto verknĂŒpfen
Rechnungskonten auflisten:
gcloud billing accounts list
Schau dir die an.
OPEN
-Spalte Dort muss True stehen. Wenn dort False steht (hĂ€ufig bei einem abgelaufenen kostenlosen Testzeitraum), ist das Konto geschlossen und es werden keine Zahlungen ausgefĂŒhrt. Fahren Sie mit dem Block zur Fehlerbehebung unten fort, bevor Sie fortfahren.
Kopieren Sie die ACCOUNT_ID eines OPEN: True-Kontos (sieht so aus: 0X0X0X-0X0X0X-0X0X0X) und verknĂŒpfen Sie sie mit Ihrem Projekt:
gcloud billing projects link fashion-app-demo \
--billing-account=YOUR_BILLING_ACCOUNT_ID
Link prĂŒfen:
gcloud billing projects describe fashion-app-demo
Sie sollten billingEnabled: true sehen. Wenn billingEnabled: false auch nach der VerknĂŒpfung angezeigt wird, ist das Konto geschlossen (OPEN: False). Weitere Informationen finden Sie im Block zur Fehlerbehebung unten.
3. Erforderliche APIs aktivieren
gcloud services enable \
aiplatform.googleapis.com \
storage.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
API | Zweck |
| Vertex AI: Der |
| Cloud Storage: Hier werden Produktkatalogbilder und generierte Anprobeergebnisse gespeichert. |
| Cloud Run: Das Backend wird als serverloser Container gehostet. |
| Cloud Build: Erstellt Docker-Images aus dem Quellcode. |
| Artifact Registry: Hier werden erstellte Docker-Images gespeichert. |
4. GCS-Bucket erstellen
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. Produktkatalogbilder hochladen
Das getProductImage-Tool des Backends liest aus gs://$GCS_BUCKET/catalog-assets/images/. Laden Sie die Katalogbilder in diesen Pfad hoch:
cd ~/fashion_app_demo
gcloud storage cp flutter_frontend/assets/images/*.png \
gs://fashion-app-$PROJECT_ID/catalog-assets/images/
PrĂŒfen Sie den Upload. Sie sollten eine Liste mit .png-Dateien sehen:
gcloud storage ls gs://fashion-app-$PROJECT_ID/catalog-assets/images/
6. .env-Datei konfigurieren
cd ~/fashion_app_demo/adk_backend
cat > .env << EOF
GOOGLE_CLOUD_PROJECT=$PROJECT_ID
GCS_BUCKET=fashion-app-$PROJECT_ID
EOF
7. Mit Standardanmeldedaten fĂŒr Anwendungen authentifizieren
Sie mĂŒssen diesen Befehl ausfĂŒhren, bevor Sie das Back-End lokal starten. Das Go-Back-End verwendet ADC, um jeden Aufruf von Vertex AI (Gemini) und Cloud Storage zu authentifizieren. Ohne ADC wird das Backend gestartet, aber jeder Try-on-Anfrage schlĂ€gt mit dem Fehler 401 CREDENTIALS_MISSING fehl.
Ein Anmeldedatenpaar deckt beide Dienste ab. FĂŒhren Sie diese beiden Befehle in der angegebenen Reihenfolge aus:
# 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)
PrĂŒfen Sie, ob ADC fehlerfrei ist:
gcloud auth application-default print-access-token | head -c 20 && echo "..."
Sie sollten etwa 20 Zeichen eines Tokens gefolgt von ... sehen. Wenn ein Fehler auftritt, hat die Anmeldung nicht funktioniert. FĂŒhren Sie Schritt 1 noch einmal aus.
4. đïžîîArchitekturĂŒbersicht
Nachdem die Umgebung eingerichtet ist, sehen wir uns an, wie das System funktioniert, bevor wir uns den Code ansehen.
Das Four-Agent-System
Das Backend ist als Multi-Agenten-System mit dem ADK (Agent Development Kit) fĂŒr Go aufgebaut. Vier Agents arbeiten zusammen, jeder mit einer bestimmten Aufgabe:
ââââââââââââââââ
â 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 â
ââââââââââââââââââ
Agent | Modell | Rolle |
Root-Agent |
| Verkehrspolizist. Liest die Nachricht des Nutzers und leitet sie an den richtigen Kundenservicemitarbeiter weiter. Es wird ein schnelles, schlankes Modell verwendet, da es nur Routing-Entscheidungen treffen muss. |
Catalog Agent |
| Produktexperte LĂ€dt den Produktkatalog aus einer YAML-Datei und beantwortet Produktanfragen. AuĂerdem ist es ressourcenschonend, da nur Daten abgerufen werden. |
Fitting Room Agent |
| Spezialist fĂŒr virtuelle Anproben. Nimmt ein Nutzerfoto und ein Produktbild und generiert ein zusammengesetztes Bild der Person, die den Artikel trĂ€gt. Es wird ein leistungsfĂ€higeres Modell verwendet, da Bilder analysiert werden mĂŒssen. |
Stylist Agent |
| Modeberater Basierend auf Standort, Anlass und Vorlieben werden drei Outfit-Kombinationen aus dem Katalog zusammengestellt. Kann Anprobierbilder fĂŒr jedes Outfit generieren. Das leistungsstarke Modell wird auch fĂŒr kreative Schlussfolgerungen verwendet. |
Der Einstiegspunkt: main.go
Alles beginnt in main.go, wo die Agents miteinander verbunden und der HTTP-Server gestartet wird:
// 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()
}
Wichtige Hinweise:
- Agenten werden von unten nach oben erstellt: Der Katalog-Agent wird zuerst erstellt, da sowohl der Anproberaum- als auch der Stylist-Agent von ihm abhÀngen (sie delegieren Produktsuchen an ihn).
- Mit
agent.NewMultiLoaderwerden alle vier Agents registriert, sodass die REST API Anfragen anhand des Namens an jeden von ihnen weiterleiten kann. adkrest.NewServerstellt die REST API automatisch bereit. Sie mĂŒssen keine Endpunkthandler selbst schreiben. ADK bietet Ihnen standardmĂ€Ăig Sitzungsverwaltung, Artefaktspeicher und AgentenausfĂŒhrung.session.InMemoryService()speichert Sitzungen im Arbeitsspeicher. Das bedeutet, dass Sitzungen verloren gehen, wenn der Server neu gestartet wird. FĂŒr eine Demo ist das in Ordnung. In der Produktion wĂŒrden Sie einen persistenten Speicher verwenden.gcsartifact.NewServicespeichert Artefakte (generierte Bilder) in Google Cloud Storage, sodass sie ĂŒber Anfragen hinweg erhalten bleiben und ĂŒber GCS-URIs freigegeben werden können.
5. đ€Â ADK (Agent Development Kit) â Detaillierte Informationen
Was ist das ADK?
Das Agent Development Kit (ADK) ist ein Open-Source-Framework von Google zum Erstellen von KI-Agenten in Go (und Python/Java). Sie bildet die Schicht zwischen Ihrer Anwendung und der Gemini API.
Sie könnten die Gemini API direkt aufrufen. Wenn Ihre App jedoch
- Produkte aus einem Katalog suchen
- Bilder auf Grundlage von Nutzerfotos generieren
- Sich merken, welche Outfits zuvor vorgeschlagen wurden
- Mehrere KI-Agenten koordinieren
Sie brauchen Struktur. Das ADK bietet diese Struktur.
Die Agentenschleife
Jeder ADK-Agent folgt einem Loop:
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
Diese Schleife kann innerhalb einer einzelnen Anfrage mehrmals wiederholt werden. Der Stylist-Agent könnte beispielsweise:
- âStelle ein Outfit fĂŒr einen Strandurlaub fĂŒr mich zusammenâ
- Rufen Sie das Tool
catalog_agentauf, um die Produktliste abzurufen. - WĂ€hle drei Outfitkombinationen aus.
- Rufen Sie
fitting_toolfĂŒr jedes Outfit auf, um Bilder zu generieren. - Strukturierte JSON-Antwort zurĂŒckgeben
Wichtige Konzepte (mit Code aus diesem Repository)
LLM-Agents
Der primÀre Baustein. Erstellt mit llmagent.New():
// 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
})
Das Feld Instruction ist die Rolle des KI-Agenten. Es gibt dem LLM vor, wer er ist und wie er sich verhalten soll. In diesem Repository werden Anleitungen als Markdown-Dateien geschrieben und zur Kompilierzeit mit der //go:embed-Anweisung von Go eingebettet:
//go:embed instructions.md
var instructions string
So werden Prompts als separate, versionierbare Dokumente und nicht als Inline-Strings gespeichert.
Tools
Tools sind Go-Funktionen, die vom LLM aufgerufen werden können. Das ADK ĂŒbernimmt die Ăbersetzung zwischen dem Tool-Aufrufformat des LLM und Ihrer typisierten Go-Funktion:
// 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)
Das ADK generiert automatisch ein JSON-Schema aus Ihren Go-Structs und sendet es an das LLM. Wenn das LLM beschlieĂt, listProducts aufzurufen, deserialisiert das ADK die Argumente, ruft Ihre Funktion auf und sendet das Ergebnis zurĂŒck.
Ăber den Parameter tool.Context können Tools auf die Laufzeitdienste des ADK zugreifen, insbesondere auf Artefakte:
// Save an image as an artifact
ctx.Artifacts().Save(ctx, "my_image", imagePart)
// Load an artifact
resp, _ := ctx.Artifacts().Load(ctx, "my_image")
Delegierung von Sub-Agents
Ein Agent kann einen anderen Agenten als Tool ĂŒber agenttool.New() verwenden:
// 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
},
Wenn der Ankleide-Agent Produktinformationen benötigt, kann er den Katalog-Agenten wie ein regulÀres Tool aufrufen. Das LLM sieht es in der Tool-Liste und kann es aufrufen.
Sitzungen
In Sitzungen wird der Unterhaltungsverlauf aufgezeichnet. Die REST API des ADK verwaltet sie automatisch:
POST /api/apps/{appName}/users/{userId}/sessions â Creates a new session
POST /api/run (with sessionId) â Runs agent within that session
Eine wichtige Designentscheidung in dieser App: Der Anproberaum erstellt fĂŒr jede Anfrage eine neue Sitzung (jede Anprobe ist unabhĂ€ngig), wĂ€hrend der Stylist dieselbe Sitzung wiederverwendet (er merkt sich also frĂŒhere VorschlĂ€ge und kann sie anhand von Feedback verfeinern).
Bundesland
Der Status ist ein SchlĂŒssel/Wert-Speicher, der an eine Sitzung angehĂ€ngt ist. Agenten lesen und schreiben den Status, um sich abzustimmen:
// Write to state
ctx.State().Set("previously_used_products", "[\"id_bomber\",\"id_hat\"]")
// Read from state
val, err := ctx.State().Get("previously_used_products")
Der Stylist-KI-Agent verwendet den Status, um sich zu merken, welche Produkte er bereits vorgeschlagen hat, damit er beim nÀchsten Mal andere auswÀhlt.
Artefakte
Artefakte sind benannte binÀre Objekte (in der Regel Bilder), die pro Sitzung gespeichert werden. Im Gegensatz zu Textantworten werden sie separat gespeichert und nach Namen abgerufen:
// 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}
So bleiben die Antworten schlank â der Agent gibt nur den Artefaktnamen zurĂŒck und das Frontend ruft die binĂ€ren Bilddaten separat ab.
Callbacks
Callbacks sind Hooks, die an bestimmten Punkten im Agenten-Loop ausgefĂŒhrt werden. Sie können die AusfĂŒhrung prĂŒfen, Ă€ndern oder abkĂŒrzen:
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},
}
Wenn ein Callback eine Antwort zurĂŒckgibt, die nicht ânilâ ist, wird das Standardverhalten ĂŒbersprungen. Wenn beispielsweise ein BeforeModelCallback eine Antwort aus dem Cache zurĂŒckgibt, wird der eigentliche LLM-Aufruf vollstĂ€ndig ĂŒbersprungen.
JSON-Schema-Durchsetzung
Sowohl der Anprobekabinen- als auch der Stylisten-Agent zwingen das LLM, in strukturiertem JSON zu antworten:
GenerateContentConfig: &genai.GenerateContentConfig{
ResponseMIMEType: "application/json",
ResponseJsonSchema: fittingSchemaMap(), // Defines the expected structure
}
So erhÀlt das Flutter-Frontend immer analysierbare Daten und keinen Freitext.
Der Katalog-Agent: Einfachstes Beispiel
Der Katalog-Agent (catalog/agent.go) ist der einfachste Agent im System und ein guter Ausgangspunkt, um ADK-Muster zu verstehen.
Es enthÀlt zwei Tools:
listProducts: Gibt den vollstĂ€ndigen Produktkatalog aus einer YAML-Datei zurĂŒck.getProductImage: LĂ€dt ein Produktbild aus GCS (oder lokaler Fallback) und speichert es als Artefakt.
Das getProductImage-Tool zeigt ein wichtiges Muster: Laden aus mehreren Quellen mit Artefakt-Caching:
// 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
}
Das Tool versucht es zuerst mit Artefakten, dann mit GCS und dann mit lokalen Dateien. Nach dem Laden wird das Bild als Artefakt im Cache gespeichert, sodass nachfolgende Aufrufe sofort erfolgen.
6. đ§ȘÂ Die KI-Pipeline: Agents in Aktion
Sehen wir uns nun die beiden anspruchsvollsten Agents an, die Bilder generieren und Outfits zusammenstellen.
6.1Â Der Fitting Room-Agent
Datei:
adk_backend/fittingroom/agent.go
Der Anprobe-Agent ist die Engine hinter âVirtuelle Anprobeâ. Wenn ein Nutzer sein Foto hochlĂ€dt und ein Produkt auswĂ€hlt, generiert dieser Agent ein zusammengesetztes Bild der Person, die den Artikel trĂ€gt.
Die fitting_tool â Schritt fĂŒr Schritt
Die Kernlogik befindet sich in der Funktion doFitting. Wenn der Agent die Funktion aufruft, geschieht Folgendes:
Schritt 1: Nutzerbild auflösen
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
}
Das Nutzerbild kann aus zwei Quellen stammen:
- Ein Artefaktnamen (z. B.
upload_abc123_1): Dies ist der erste Upload, der vomSaveIncomingBlobs-Callback gespeichert wird. - Ein
gs://-URI: Dies ist ein zuvor generiertes Anpassungsergebnis, das in GCS zur sitzungsĂŒbergreifenden Wiederverwendung gespeichert ist.
Dieses Dual-Path-Design ist beabsichtigt: Wenn der Stylist-Agent spĂ€ter Outfit-Anproben generiert, wird die GCS-URL aus dem ursprĂŒnglichen Ergebnis des virtuellen Anproberaums wiederverwendet, damit die IdentitĂ€t des Nutzers bei allen Outfits gleich bleibt.
Schritt 2: Multimodalen Prompt erstellen
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
}
Der toolInstructions-Prompt (eingebettet aus tool_instructions.md) ist entscheidend, da er Gemini anweist, die IdentitĂ€t des Nutzers (Gesicht, Körpertyp, Hautton, Haare) beizubehalten und nur das KleidungsstĂŒck anzuwenden. Ohne diese Prompt-Technik könnte das Modell das Aussehen der Person Ă€ndern.
Schritt 3: Gemini zur Bildgenerierung aufrufen
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
})
Alle vier Agents und das Tool zur Bildgenerierung verwenden denselben Authentifizierungspfad: Backend: genai.BackendVertexAI mit der Projekt-ID, authentifiziert ĂŒber Standardanmeldedaten fĂŒr Anwendungen. Die Orchestrierungsmodelle (gemini-3.1-pro-preview, gemini-3-flash-preview) und das Bildmodell (gemini-2.5-flash-image) befinden sich alle hinter demselben Vertex AI-Endpunkt und mit denselben ADC-Anmeldedaten wird auch der Cloud Storage-Zugriff autorisiert â eine Anmeldedaten fĂŒr jeden Aufruf.
Schritt 4: Ergebnis speichern
// 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
Das doppelte Speichern (Artefakt + GCS) ist der SchlĂŒssel fĂŒr die Ăbergabe des Kundenservice-Agents zwischen Anproberaum und Stylist. Das Artefakt ermöglicht den sofortigen Zugriff in der aktuellen Sitzung, wĂ€hrend der GCS-URI es dem Stylist (der in einer anderen Sitzung ausgefĂŒhrt wird) ermöglicht, spĂ€ter auf dasselbe Bild zu verweisen.
Der SaveIncomingBlobs-Callback
Bevor der Kundenservicemitarbeiter mit der BegrĂŒndung beginnt, wird dieses BeforeAgentCallback ausgefĂŒhrt, um alle vom Nutzer hochgeladenen Bilder zu speichern:
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
}
Durch die RĂŒckgabe von (nil, nil) signalisiert der Callback: âIch bin mit der Vorverarbeitung fertig â fĂŒhre den Agenten jetzt wie gewohnt aus.â Wenn nicht leere Inhalte zurĂŒckgegeben wĂŒrden, wĂŒrde der Agent vollstĂ€ndig umgangen.
6.2Â Der Stylist-Agent
Datei:
adk_backend/stylist/agent.go
Der Stylist-Agent ist der anspruchsvollste im System. Es werden personalisierte Outfit-Empfehlungen zusammengestellt und durch Konversationen können diese iterativ verfeinert werden.
Drei Callbacks â Die Erinnerung des Stylisten
Der Stylist verwendet drei Callbacks, um den Kontext ĂŒber mehrere Konversationsrunden hinweg beizubehalten:
Callback 1:
InjectPreviousProducts (BeforeModel)
Das Problem: Wenn der Nutzer sagt: âZeig mir verschiedene Optionenâ, schlĂ€gt das LLM möglicherweise dieselben Produkte noch einmal vor, da es nicht automatisch nachverfolgt, was es bereits empfohlen hat.
Die Lösung: Nach jeder Antwort werden Produkt-IDs im Sitzungsstatus gespeichert. Vor dem nĂ€chsten LLM-Aufruf werden diese gelesen und ein Hinweis wird eingefĂŒgt:
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
}
RĂŒckruf 2:
ExtractAndInjectUserImage (BeforeModel)
Das Problem: Wenn der Nutzer Feedback gibt (âmake it more casualâ), wird das Foto des Nutzers in der Folgeantwort nicht noch einmal eingefĂŒgt. Das Anpassungstool benötigt sie jedoch.
Die Lösung: Bei der ersten Anfrage wird in diesem Callback die Nutzerbildreferenz extrahiert und im Status gespeichert. Bei nachfolgenden Anfragen wird sie wieder eingefĂŒgt:
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
}
RĂŒckruf 3:
SaveSelectedProducts (AfterModel)
Nachdem das LLM mit OutfitvorschlĂ€gen geantwortet hat, wird das JSON in diesem Callback geparst, um Produkt-IDs zu extrahieren und fĂŒr den nĂ€chsten Aufruf des InjectPreviousProducts-Callbacks zu speichern:
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
}
Zusammen bilden diese drei RĂŒckrufe eine Feedbackschleife:
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Â Der Root-Agent
Datei:
adk_backend/rootagent/agent.go
Der einfachste Agent â nur 31 Zeilen:
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},
})
}
Es wird gemini-3-flash-preview (das schnellste Modell) verwendet, da die Routing-Entscheidungen einfach sind â das LLM muss nur die Absicht des Nutzers lesen und den richtigen Sub-Agenten auswĂ€hlen. Es sind keine Tools erforderlich. SubAgents ĂŒbernimmt die Delegierung automatisch.
7. đ±Â Flutter-Frontend-Architektur
Das Flutter-Frontend ist eine voll funktionsfÀhige Einzelhandels-Shopping-App. Die KI-Funktionen befinden sich in flutter_frontend/lib/workshop_tasks/, getrennt vom vorgefertigten Einkaufserlebnis in core_app/.
Das MVVM-Muster
Die App folgt der Model-View-ViewModel-Architektur mit dem Provider-Paket:
ââââââââââââââââââââ ââââââââââââââââââââââ ââââââââââââââââââââ
â 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 â â â â â
ââââââââââââââââââââ ââââââââââââââââââââââ ââââââââââââââââââââ
Jede Ebene hat eine klare Rolle:
- Modell: Datenklassen wie
Product,Outfit,StyleRequestund Enums wieTryOnState - ViewModel (
ChangeNotifier): EnthĂ€lt den aktuellen Zustand und ĂŒbertrĂ€gt Ănderungen ĂŒbernotifyListeners()an die BenutzeroberflĂ€che. - View (Widget): Abonniert das ViewModel mit
context.watchund wird bei StatusĂ€nderungen neu erstellt.() - Dienst: FĂŒhrt HTTP-Aufrufe an das ADK-Backend aus und gibt typisierte Daten zurĂŒck.
Die Serviceebene
Dienste werden als abstrakte Schnittstellen mit ADK-spezifischen Implementierungen definiert:
// 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 { ... }
Durch diese Trennung können Sie das ADK-Backend durch Firebase AI, einen Mock-Dienst oder eine andere Implementierung ersetzen, ohne den Rest der App Ă€ndern zu mĂŒssen.
Das 3-Schritt-API-Muster
Sowohl AdkFittingRoomService als auch AdkStylingService folgen demselben Muster fĂŒr die Kommunikation mit dem ADK-Backend:
Schritt 1: Sitzung erstellen
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
}
Schritt 2: Agent ausfĂŒhren
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...
}
Schritt 3: Artefakt abrufen
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
}
Ein wichtiger Unterschied im Design: Der Anprobeservice erstellt fĂŒr jede Anfrage eine neue Sitzung (_createSession() wird jedes Mal aufgerufen), wĂ€hrend der Styling-Service dieselbe Sitzung wiederverwendet (_sessionId ??= await _createSession()), um eine Konversation mit mehreren ZĂŒgen zu ermöglichen.
Zustandsverwaltung: TryItOnProvider
Datei:
workshop_tasks/step_1_try_it_on/providers/try_it_on_provider.dart
Der TryItOnProvider verwaltet den gesamten Anprobefluss. Dabei wird ein TryOnState-Enum als Zustandsautomat verwendet:
enum TryOnState { initial, imagePicked, generating, success, error }
class TryItOnProvider with ChangeNotifier {
TryOnState _state = TryOnState.initial;
Uint8List? _userImageBytes;
Uint8List? _generatedImage;
String? _errorMessage;
Private StatusĂŒbergĂ€nge sorgen fĂŒr Konsistenz. Sie aktualisieren den Status nie, ohne auch veraltete Daten zu löschen und die BenutzeroberflĂ€che zu benachrichtigen:
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();
}
Die Hauptgenerierungsmethode fasst alles zusammen:
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;
}
Die BenutzeroberflÀche: Bildschirme als State Router
Datei:
workshop_tasks/step_1_try_it_on/ui/2_try_it_on_screen.dart
Auf dem Try-on-Bildschirm wird das Pattern Matching von Dart 3 mit AnimatedSwitcher verwendet, um basierend auf dem Status des Anbieters zwischen Unterbildschirmen zu wechseln:
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 abonniert den Anbieter. Immer wenn notifyListeners() aufgerufen wird, wird dieses Widget neu erstellt und AnimatedSwitcher wechselt nahtlos zwischen den Bildschirmen. Es gibt kein Navigator.push. Der Bildschirminhalt wird basierend auf dem Status-Enum direkt geÀndert.
Die agentische Ăbergabe: Umkleidekabine â Stylist
Das interessanteste UX-Muster ist, wie die App den Kontext vom Anproberaum-Agent an den Stylist-Agent weitergibt.
In 5_fitting_room.dart wird nach dem Generieren des Anprobebilds ĂŒber den Button âStyle Meâ (Style mich) ein Formular geöffnet. Wenn der Nutzer Folgendes einreicht:
// 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,
));
Das StyleRequest bietet alles, was der Stylist braucht:
- Ort und Anlass: Textkontext fĂŒr die Gestaltung
- GCS-Nutzerbild-URLÂ â damit der Stylist genau dieselbe Nutzerdarstellung wiederverwenden kann
- AusgewÀhltes Produkt: Der Stylist nimmt es in jedes Outfit auf.
Das ist die Agent-Ăbergabe â der multimodale Kontext wird nahtlos von einem KI-Agent an einen anderen ĂŒbertragen und der Nutzer sieht nur ein einfaches Formular.
Der Styling-Ablauf: StylingProvider
Datei:
workshop_tasks/step_2_style_me/providers/styling_provider.dart
Die StylingProvider ist einfacher als die TryItOnProvider, da die meiste KomplexitÀt an das Backend delegiert wird:
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;
}
}
Mit der Methode refineWithFeedback wird eine Nur-Text-Nachricht an dieselbe Sitzung gesendet. Die Callbacks InjectPreviousProducts und ExtractAndInjectUserImage des Back-Ends ĂŒbernehmen die gesamte Kontextverwaltung automatisch.
8. đ App lokal ausfĂŒhren
Damit Cloud Shell reibungslos funktioniert, stellt das Go-Backend die kompilierte Flutter-Webanwendung ĂŒber denselben Port (8080) bereit. Ein Prozess, eine Vorschau-URL, keine Probleme mit ursprungsĂŒbergreifenden Anfragen und keine Bearbeitung von Konfigurationsdateien.
Vorab: ADC-Test
FĂŒr den Aufruf von Vertex AI sind Standardanmeldedaten fĂŒr Anwendungen erforderlich. Wenn Sie Schritt 7 der Projekteinrichtung in dieser Cloud Shell-Sitzung und mit diesem Google-Konto abgeschlossen haben, können Sie fortfahren. Wenn Sie nach einer Pause zurĂŒckkehren, das Konto gewechselt haben oder sich nicht sicher sind, können Sie in 5 Sekunden Folgendes prĂŒfen:
gcloud auth application-default print-access-token | head -c 20 && echo "..."
Wenn etwa 20 Zeichen eines Tokens ausgegeben werden, ist alles in Ordnung. Wenn ein Fehler auftritt, fĂŒhren Sie Schritt 7 der Projekteinrichtung noch einmal aus:
gcloud auth application-default login
gcloud auth application-default set-quota-project $(gcloud config get-value project)
Sie verwenden zwei Cloud Shell-Terminals:
- Terminal A: Hier wird das Backend kontinuierlich ausgefĂŒhrt (
./run.sh). Lassen Sie es geöffnet. - Terminal B: FĂŒhrt den Flutter-Web-Build einmal aus (
flutter build web). Wird nach Abschluss beendet.
Die Reihenfolge spielt keine Rolle. FĂŒr eine möglichst reibungslose Erstnutzung sollten Sie Flutter jedoch zuerst erstellen, damit das Backend von Anfang an eine BenutzeroberflĂ€che hat.
1. Terminal B: Flutter-Web-Bundle erstellen (einmalig)
Ăffnen Sie einen neuen Cloud Shell-Tab (+ oben im Terminalbereich) und gehen Sie dann so vor:
cd ~/fashion_app_demo/flutter_frontend
flutter pub get
flutter build web
Dadurch wird flutter_frontend/build/web/ erstellt, ein Verzeichnis mit statischen Dateien (HTML, JS, Assets). Das Programm wird beendet, wenn der Vorgang abgeschlossen ist. Der Backend-Dienst stellt diese bereit, sobald er feststellt, dass das Verzeichnis vorhanden ist.
2. Terminal A â Backend starten (lang andauernd)
FĂŒhren Sie im ursprĂŒnglichen Cloud Shell-Terminal folgende Schritte aus:
cd ~/fashion_app_demo/adk_backend
./run.sh
Sie sollte etwa so aussehen:
Serving Flutter web build from ../flutter_frontend/build/web
Lassen Sie dieses Terminal geöffnet: Das Backend bleibt aktiv, solange run.sh ausgefĂŒhrt wird. DrĂŒcken Sie die Ctrl+C, um die Aufnahme zu beenden.
Der Server stellt alles auf Port 8080 bereit:
/ â Flutter-Web-App (die Shopping-BenutzeroberflĂ€che)/api/: ADK-REST-Endpunkte (werden von der Flutter-App aufgerufen)- ADK-EntwicklungsoberflĂ€che â auch unter
/verfĂŒgbar, wenn kein Flutter-Build vorhanden ist; nĂŒtzlich fĂŒr das direkte Debugging von Agenten
3. Webvorschau öffnen
- Klicken Sie in Cloud Shell rechts oben auf das Symbol fĂŒr die Webvorschau â Vorschau auf Port 8080.
- Die Flutter-Shopping-App wird in einem neuen Tab geladen.
- Im Produktkatalog nach einem Artikel suchen und ihn auswÀhlen
- Tippen Sie auf das Personensymbol (đ€), um den Try-On-Vorgang zu starten.
- Laden Sie ein Foto hoch und lassen Sie von der KI ein Bild zur Anprobe generieren.
- Auf âStyle Meâ tippen, um Outfit-Empfehlungen zu erhalten
- Geben Sie Folge-Feedback wie âFormuliere es lockererâ ein â Verfeinerung in derselben Sitzung
9. âïžÂ In Cloud Run bereitstellen
Flutter-Build in das Backend einbinden
Der Cloud Run-Container enthĂ€lt sowohl die API als auch die BenutzeroberflĂ€che in einem Image. Kopieren Sie den Flutter-Web-Build in adk_backend/flutter_web/. Das ist der erste Pfad, den der Go-Server prĂŒft, wenn er die auszuliefernde BenutzeroberflĂ€che auswĂ€hlt:
cd ~/fashion_app_demo/flutter_frontend
flutter build web
rm -rf ../adk_backend/flutter_web
cp -r build/web ../adk_backend/flutter_web
Wenn Sie lokal iteriert haben, haben Sie möglicherweise bereits build/web aus dem Schritt âLokal ausfĂŒhrenâ. flutter build web kann weiterhin ausgefĂŒhrt werden.)
Backend bereitstellen (stellt API und BenutzeroberflÀche bereit)
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
Wenn die Bereitstellung abgeschlossen ist, erhalten Sie eine Service-URL wie https://fashion-app-backend-xyz-uc.a.run.app. Ăffnen Sie sie in einem Browser. Die Flutter-Shopping-App wird von / geladen und ihre API-Aufrufe werden an /api/ auf demselben Host gesendet. Keine Ănderungen an der Frontend-Konfiguration erforderlich, kein API-SchlĂŒssel ĂŒbergeben.
Deployment prĂŒfen
Ăffnen Sie die Cloud Run-URL in Ihrem Browser und fĂŒhren Sie den gesamten Ablauf durch:
- Durchsuchen â Produkt auswĂ€hlen
- Anprobieren â Foto hochladen â KI-generiertes Bild ansehen
- Style Me â Standort/Anlass eingeben â AusgewĂ€hlte Outfits ansehen
- Feedback â âMache es lĂ€ssigerâ eingeben â aktualisierte Outfits ansehen
- In den Warenkorb â Kaufvorgang abschlieĂen
10. đ Fazit
Was Sie erstellt haben
Sie haben ein vollstĂ€ndiges KI-gestĂŒtztes Einzelhandelserlebnis kennengelernt, das Folgendes umfasst:
- â Â Ein Multi-Agenten-Backend mit vier spezialisierten Agenten, die zusammenarbeiten
- â Â Eine virtuelle Umkleidekabine, in der personalisierte Anprobefotos generiert werden
- â Â Ein KI-Stylist, der Outfits zusammenstellt und sie durch Unterhaltungen verfeinert
- â Â Eine plattformĂŒbergreifende Flutter-App, die eine Verbindung zum Agent-Backend herstellt
- â Â Cloud Run-Bereitstellung fĂŒr skalierbares, serverloses Hosting
SchlĂŒsselkonzepte
Konzept | Wo Sie es gesehen haben |
ADK-Multiagenten-Orchestrierung | Root-Agent-Routing zu Anproberaum-, Katalog- und Stylist-Agents |
Gemini Multimodal Bildgenerierung |
|
Sitzungsstatus fĂŒr konversationelle KI | Stylist verwendet Sitzungen fĂŒr iteratives Feedback wieder |
Artefaktspeicher fĂŒr BinĂ€rdaten | Bildspeicher von Textantworten trennen |
Callbacks fĂŒr Middleware-Logik |
|
MVVMÂ + Provider in Flutter |
|
Agentic Handoff |
|
NĂ€chste Schritte
- đšÂ Agent-Prompts anpassen: Bearbeiten Sie
instructions.md, um die Persönlichkeit des Stylisten zu Ă€ndern. - đïžÂ Weitere Produkte hinzufĂŒgen: Aktualisieren Sie
catalog.yamlmit neuen Artikeln. - đ±Â Anzeigen fĂŒr MobilgerĂ€te optimieren â fĂŒhren Sie
flutter build iosoderflutter build apkaus. - đ Persistente Sitzungen hinzufĂŒgen: Ersetzen Sie
InMemoryServicedurch eine datenbankbasierte Implementierung. - đ Authentifizierung hinzufĂŒgen: Cloud Run-Endpunkt mit IAM sichern
Ressourcen
- ADK-Dokumentation â Offizielle Dokumentation zum Agent Development Kit
- ADK Go-Quellcode â GitHub-Repository
- ADK Go Package Reference â API-Referenz
- Gemini API-Dokumentation â Modellfunktionen und Anleitungen
- Flutter Provider Package â Dokumentation zur Statusverwaltung
- Cloud Run-Dokumentation â Bereitstellungs- und Skalierungsanleitungen