1. Introduction
Objectifs de l'atelier
Dans cet atelier de programmation, vous allez vous mettre dans la peau d'un développeur qui crée Fashion App, une application d'achat Flutter pour une marque de vente au détail fictive. Votre mission : ajouter deux fonctionnalités optimisées par l'IA qui transforment l'expérience d'achat en ligne.
- Cabine d'essayage virtuelle : un utilisateur importe une photo de lui, sĂ©lectionne un vĂȘtement et voit une image gĂ©nĂ©rĂ©e par IA de lui portant ce vĂȘtement.
- Styliste IA : en fonction de l'emplacement, de l'occasion et des préférences de style de l'utilisateur, un agent IA propose des tenues complÚtes. L'utilisateur peut les affiner par le biais d'une conversation.
L'idĂ©e est simple : lorsque les clients essayent des vĂȘtements dans une cabine, ils sont beaucoup plus susceptibles de les acheter. Mais en ligne ? Vous ne faites que deviner. Ce projet comble cette lacune grĂące Ă l'IA.
Architecture en un coup d'Ćil
Flutter App ââââ HTTP/REST âââââ¶ ADK Go Backend
â
ââââââââââââŒâââââââââââ
Fitting Room Stylist Catalog
Agent Agent Agent
â
Gemini API + Cloud Storage
Technologie principale
Composant | Technologie | Objectif |
Framework de l'agent | ADK (Agent Development Kit) pour Go | Orchestration multi-agents, sessions, artefacts |
Raisonnement de l'agent (Pro) | Gemini 3.1 Pro (preview) | Alimente les agents de cabine d'essayage et de styliste |
Raisonnement de l'agent (Flash) | Preview Gemini 3 Flash | Alimente les agents racine et de catalogue (routage/recherche légers) |
Génération d'images | Gemini 2.5 Flash Image | Générer des images d'essayages et de tenues |
Frontend | Flutter (Dart) | Application multiplate-forme (Web, iOS, Android) |
Stockage | Google Cloud Storage | Stocke les images de produits et les artefacts générés |
Hébergement | Cloud Run | Déploiement de conteneurs sans serveur |
2. đŠÂ PrĂ©requis et configuration de Cloud Shell
1. Ouvrir l'éditeur Cloud Shell
đ Ouvrez l'Ă©diteur Cloud Shell dans votre navigateur.
Si le terminal n'apparaßt pas en bas de l'écran :
- Cliquez sur Afficher â Terminal.
2. Configurer le SDK Flutter
Flutter est prĂ©installĂ© dans Cloud Shell Ă l'emplacement /google/flutter. Ătant donnĂ© que ce rĂ©pertoire appartient Ă un autre utilisateur du systĂšme, vous rencontrerez une erreur fatal: detected dubious ownership la premiĂšre fois que vous exĂ©cuterez flutter. Ajoutez-le une fois Ă la liste des rĂ©pertoires sĂ©curisĂ©s de Git :
git config --global --add safe.directory /google/flutter
Vérifiez que Flutter est installé et fonctionne sur votre PATH :
flutter --version
La premiÚre exécution télécharge le SDK Dart et crée l'outil Flutter. Patientez une minute. Vous devriez voir quelque chose semblable à Flutter 3.x ⹠channel stable.
3. Cloner le dépÎt
cd ~
git clone https://github.com/gca-americas/fashion-app-demo
cd fashion_app_demo
4. Explorer la structure du projet
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. âïžÂ Configurer un projet Google Cloud
1. Créer un projet
gcloud projects create fashion-app-demo --name="Fashion App Demo"
gcloud config set project fashion-app-demo
2. Associer un compte de facturation
Répertoriez vos comptes de facturation :
gcloud billing accounts list
Regardez le
OPEN
colonne. Il doit indiquer True. Si le message False s'affiche (ce qui est courant avec un essai sans frais expiré), le compte est clÎturé et ne paiera rien. Passez directement au bloc de dépannage ci-dessous avant de continuer.
Copiez le ACCOUNT_ID d'un compte OPEN: True (qui ressemble à 0X0X0X-0X0X0X-0X0X0X) et associez-le à votre projet :
gcloud billing projects link fashion-app-demo \
--billing-account=YOUR_BILLING_ACCOUNT_ID
Vérifiez le lien :
gcloud billing projects describe fashion-app-demo
Vous devriez voir billingEnabled: true. Si vous voyez billingEnabled: false mĂȘme aprĂšs l'association, cela signifie que le compte est clĂŽturĂ© (OPEN: False). Consultez la section de dĂ©pannage ci-dessous.
3. Activer les API requises
gcloud services enable \
aiplatform.googleapis.com \
storage.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
API | Objectif |
| Vertex AI : les appels |
| Cloud Storage : stocke les images du catalogue de produits et les résultats d'essayage générés |
| Cloud Run : héberge le backend en tant que conteneur sans serveur. |
| Cloud Build : crée des images Docker à partir de la source. |
| Artifact Registry : stocke les images Docker créées |
4. Créer un bucket GCS
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. Importer des images de catalogue de produits
L'outil getProductImage du backend lit les données à partir de gs://$GCS_BUCKET/catalog-assets/images/. Importez les images du catalogue dans ce chemin d'accÚs exact :
cd ~/fashion_app_demo
gcloud storage cp flutter_frontend/assets/images/*.png \
gs://fashion-app-$PROJECT_ID/catalog-assets/images/
Vérifiez l'importation (une liste de fichiers .png devrait s'afficher) :
gcloud storage ls gs://fashion-app-$PROJECT_ID/catalog-assets/images/
6. Configurer le fichier .env
cd ~/fashion_app_demo/adk_backend
cat > .env << EOF
GOOGLE_CLOUD_PROJECT=$PROJECT_ID
GCS_BUCKET=fashion-app-$PROJECT_ID
EOF
7. S'authentifier avec les identifiants par défaut de l'application
Vous devez exĂ©cuter cette commande avant de dĂ©marrer le backend en local. Le backend Go utilise ADC pour authentifier chaque appel Ă Vertex AI (Gemini) et Cloud Storage. Sans ADC, le backend dĂ©marrera, mais chaque requĂȘte d'essayage Ă©chouera avec un code d'erreur 401 CREDENTIALS_MISSING.
Un seul identifiant couvre les deux services. Exécutez les deux commandes suivantes dans l'ordre :
# 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)
Vérifiez que l'ADC est opérationnel :
gcloud auth application-default print-access-token | head -c 20 && echo "..."
Vous devriez voir environ 20 caractÚres d'un jeton suivis de .... Si une erreur se produit, cela signifie que la connexion n'a pas fonctionné. Répétez l'étape 1.
4. đïžÂ PrĂ©sentation de l'architecture
Maintenant que l'environnement est prĂȘt, comprenons comment fonctionne le systĂšme avant d'examiner le code.
Le systĂšme Ă quatre agents
Le backend est conçu comme un systÚme multi-agent à l'aide d'ADK (Agent Development Kit) pour Go. Quatre agents travaillent ensemble, chacun ayant une responsabilité spécifique :
ââââââââââââââââ
â 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 | ModĂšle | RĂŽle |
Agent racine |
| Un agent de la circulation. Lit le message de l'utilisateur et le transmet à l'agent spécialiste approprié. Utilise un modÚle rapide et léger, car il n'a besoin que de prendre des décisions de routage. |
Agent de catalogue |
| Expert Produit Charge le catalogue de produits à partir d'un fichier YAML et répond aux questions sur les produits. Il est également léger, car il ne fait que rechercher des données. |
Agent pour les cabines d'essayage |
| Spécialiste de l'essai virtuel. Prend une photo de l'utilisateur et une image du produit, puis génÚre une image composite de la personne portant l'article. Utilise un modÚle plus performant, car il doit raisonner sur des images. |
Agent Stylist |
| Conseiller en mode. En fonction du lieu, de l'occasion et des préférences, il sélectionne trois combinaisons de tenues dans le catalogue. peut générer des images d'essayage pour chaque tenue. Utilise également le modÚle performant pour le raisonnement créatif. |
Point d'entrée : main.go
Tout commence dans main.go, qui relie les agents et démarre le serveur HTTP :
// 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()
}
Voici quelques points importants à retenir :
- Les agents sont créés de bas en haut : l'agent de catalogue est créé en premier, car les agents de cabine d'essayage et de styliste en dépendent (ils lui délÚguent les recherches de produits).
agent.NewMultiLoaderenregistre les quatre agents afin que l'API REST puisse les acheminer vers l'un d'eux par nom.adkrest.NewServerfournit automatiquement l'API REST. Vous n'avez pas besoin d'Ă©crire vous-mĂȘme les gestionnaires de points de terminaison. L'ADK vous offre une gestion des sessions, un stockage des artefacts et une exĂ©cution des agents prĂȘts Ă l'emploi.session.InMemoryService()stocke les sessions en mĂ©moire. Cela signifie que les sessions sont perdues si le serveur redĂ©marre, ce qui convient pour une dĂ©monstration. En production, vous utiliserez un magasin persistant.gcsartifact.NewServicestocke les artefacts (images gĂ©nĂ©rĂ©es) dans Google Cloud Storage. Ils sont donc conservĂ©s d'une requĂȘte Ă l'autre et peuvent ĂȘtre partagĂ©s via des URI GCS.
5. đ€Â PrĂ©sentation dĂ©taillĂ©e de l'ADK (Agent Development Kit)
Qu'est-ce que ADKÂ ?
L'Agent Development Kit (ADK) est un framework Open Source de Google permettant de créer des agents IA en Go (et en Python/Java). Il s'agit de la couche entre votre application et l'API Gemini.
Vous pourriez appeler directement l'API Gemini. Mais une fois que votre application doit :
- Rechercher des produits dans un catalogue
- Générer des images à partir de photos d'utilisateurs
- Se souvenir des tenues suggérées précédemment
- Coordonner plusieurs agents IA
Vous avez besoin de structure. L'ADK fournit cette structure.
Boucle de l'agent
Chaque agent ADK suit une boucle :
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
Cette boucle peut se rĂ©pĂ©ter plusieurs fois dans une mĂȘme requĂȘte. Par exemple, l'agent de coiffure peut :
- Recevoir "Trouve-moi une tenue pour des vacances Ă la plage"
- Appeler l'outil
catalog_agentpour obtenir la liste des produits - Sélectionnez trois combinaisons de tenues
- Appelez
fitting_toolpour chaque tenue afin de générer des images. - Renvoyer la réponse JSON structurée
Concepts fondamentaux (avec le code de ce dépÎt)
Agents LLM
Composant de base principal. Créé avec 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
})
Le champ Instruction correspond au persona de l'agent. Il indique au LLM qui il est et comment il doit se comporter. Dans ce dépÎt, les instructions sont écrites sous forme de fichiers Markdown et intégrées au temps de compilation à l'aide de la directive //go:embed de Go :
//go:embed instructions.md
var instructions string
Les requĂȘtes sont ainsi conservĂ©es sous forme de documents distincts et versionnables, plutĂŽt que de chaĂźnes intĂ©grĂ©es.
Outils
Les outils sont des fonctions Go que le LLM peut appeler. L'ADK gÚre la traduction entre le format d'appel d'outil du LLM et votre fonction Go typée :
// 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 génÚre automatiquement un schéma JSON à partir de vos structs Go et l'envoie au LLM. Lorsque le LLM décide d'appeler listProducts, ADK désérialise les arguments, appelle votre fonction et renvoie le résultat.
Le paramÚtre tool.Context permet aux outils d'accéder aux services d'exécution d'ADK, et plus particuliÚrement aux artefacts :
// Save an image as an artifact
ctx.Artifacts().Save(ctx, "my_image", imagePart)
// Load an artifact
resp, _ := ctx.Artifacts().Load(ctx, "my_image")
Délégation de sous-agent
Un agent peut utiliser un autre agent comme outil via agenttool.New()Â :
// 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
},
Lorsque l'agent de la cabine d'essayage a besoin d'informations produit, il peut appeler l'agent de catalogue comme s'il s'agissait d'un outil normal. Le LLM le voit dans la liste des outils et peut décider de l'appeler.
Sessions
Les sessions permettent de suivre l'historique des conversations. L'API REST de l'ADK les gÚre automatiquement :
POST /api/apps/{appName}/users/{userId}/sessions â Creates a new session
POST /api/run (with sessionId) â Runs agent within that session
Une dĂ©cision de conception essentielle dans cette application : la cabine d'essayage crĂ©e une session par requĂȘte (chaque essayage est indĂ©pendant), tandis que le styliste rĂ©utilise la mĂȘme session (il se souvient donc des suggestions prĂ©cĂ©dentes et peut les affiner en fonction des commentaires).
Ătat
L'état est un magasin clé-valeur associé à une session. Les agents lisent et écrivent l'état pour se coordonner :
// Write to state
ctx.State().Set("previously_used_products", "[\"id_bomber\",\"id_hat\"]")
// Read from state
val, err := ctx.State().Get("previously_used_products")
L'agent styliste utilise l'état pour se souvenir des produits qu'il a déjà suggérés et en choisir d'autres la prochaine fois.
Artefacts
Les artefacts sont des objets binaires nommés (généralement des images) stockés par session. Contrairement aux réponses textuelles, elles sont stockées séparément et récupérées par leur nom :
// 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}
Cela permet de garder les réponses légÚres : l'agent ne renvoie que le nom de l'artefact, et le frontend récupÚre les données binaires de l'image séparément.
Rappels
Les rappels sont des hooks qui s'exécutent à des moments précis de la boucle de l'agent. Ils peuvent inspecter, modifier ou court-circuiter l'exécution :
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},
}
Si un rappel renvoie une réponse non nulle, le comportement par défaut est ignoré. Par exemple, un BeforeModelCallback qui renvoie une réponse mise en cache ignorerait complÚtement l'appel LLM réel.
Application du schéma JSON
Les agents de cabine d'essayage et de styliste forcent le LLM à répondre au format JSON structuré :
GenerateContentConfig: &genai.GenerateContentConfig{
ResponseMIMEType: "application/json",
ResponseJsonSchema: fittingSchemaMap(), // Defines the expected structure
}
Cela garantit que le frontend Flutter reçoit toujours des données analysables, et non du texte libre.
Agent de catalogue : exemple le plus simple
L'agent de catalogue (catalog/agent.go) est l'agent le plus simple du systÚme. Il constitue un bon point de départ pour comprendre les schémas ADK.
Il comporte deux outils :
listProducts : renvoie le catalogue de produits complet à partir d'un fichier YAML.getProductImage : charge une image de produit à partir de GCS (ou d'une solution de secours locale) et l'enregistre en tant qu'artefact.
L'outil getProductImage affiche un modÚle important : le chargement multisource avec mise en cache des artefacts :
// 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
}
L'outil essaie d'abord les artefacts, puis GCS, puis les fichiers locaux. Une fois l'image chargée, elle est mise en cache en tant qu'artefact. Les appels suivants sont donc instantanés.
6. đ§ȘÂ Le pipeline d'IAÂ : les agents en action
Passons maintenant en revue les deux agents les plus sophistiqués, ceux qui génÚrent des images et sélectionnent des tenues.
6.1Â L'agent de la cabine d'essayage
Fichier :
adk_backend/fittingroom/agent.go
L'agent de cabine d'essayage est le moteur de la fonctionnalité "Essayer virtuellement". Lorsqu'un utilisateur importe sa photo et choisit un produit, cet agent génÚre une image composite de la personne portant cet article.
fitting_tool : étape par étape
La logique principale se trouve dans la fonction doFitting. Voici ce qui se passe lorsque l'agent l'appelle :
Ătape 1 : RĂ©solvez le problĂšme liĂ© Ă l'image de l'utilisateur
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
}
L'image de l'utilisateur peut provenir de deux sources :
- Nom de l'artefact (comme
upload_abc123_1) : il s'agit de l'importation initiale, enregistrée par le rappelSaveIncomingBlobs. - Un
gs://URI : il s'agit d'un résultat d'ajustement généré précédemment et stocké dans GCS pour une réutilisation entre les sessions.
Cette conception à double chemin est intentionnelle : lorsque l'agent styliste génÚre des essayages de tenues, il réutilise l'URL GCS du résultat initial de l'essayage afin que l'identité de l'utilisateur reste cohérente pour toutes les tenues.
Ătape 2 : CrĂ©ez le prompt multimodal
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
}
Le toolInstructions (intĂ©grĂ© Ă partir de tool_instructions.md) est essentiel : il indique Ă Gemini de prĂ©server l'identitĂ© de l'utilisateur (visage, morphologie, teint, cheveux) tout en appliquant uniquement le vĂȘtement. Sans cette ingĂ©nierie des prompts, le modĂšle pourrait modifier l'apparence de la personne.
Ătape 3 : Appeler Gemini pour gĂ©nĂ©rer des images
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
})
Les quatre agents et l'outil de gĂ©nĂ©ration d'images partagent un mĂȘme chemin d'authentification : Backend: genai.BackendVertexAI avec l'ID du projet, authentifiĂ© via les identifiants par dĂ©faut de l'application. Les modĂšles d'orchestration (gemini-3.1-pro-preview, gemini-3-flash-preview) et le modĂšle d'image (gemini-2.5-flash-image) se trouvent tous derriĂšre le mĂȘme point de terminaison Vertex AI, et les mĂȘmes identifiants ADC autorisent Ă©galement l'accĂšs Ă Cloud Storage.
Ătape 4 : Enregistrez le rĂ©sultat
// 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
La double sauvegarde (artefact + GCS) est essentielle pour le transfert de l'agent entre la cabine d'essayage et le styliste. L'artefact permet d'accĂ©der immĂ©diatement Ă l'image dans la session en cours, tandis que l'URI GCS permet au styliste (qui s'exĂ©cute dans une autre session) de faire rĂ©fĂ©rence Ă la mĂȘme image ultĂ©rieurement.
Rappel SaveIncomingBlobs
Avant mĂȘme que l'agent ne commence Ă raisonner, ce BeforeAgentCallback s'exĂ©cute pour enregistrer les images que l'utilisateur a importĂ©es :
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
}
En renvoyant (nil, nil), le rappel indique "J'ai terminé le prétraitement. Exécute maintenant l'agent normalement." Si elle renvoyait du contenu non nul, elle court-circuiterait complÚtement l'agent.
6.2Â L'Agent Stylist
Fichier :
adk_backend/stylist/agent.go
L'agent styliste est le plus sophistiqué du systÚme. Il propose des recommandations de tenues personnalisées et permet d'affiner les résultats de maniÚre itérative grùce à la conversation.
Trois rappels : la mémoire du styliste
Le styliste utilise trois rappels pour conserver le contexte dans les conversations multitours :
Rappel 1 :
InjectPreviousProducts (BeforeModel)
ProblĂšme : si l'utilisateur demande "montre-moi d'autres options", le LLM peut suggĂ©rer Ă nouveau les mĂȘmes produits, car il ne suit pas intrinsĂšquement ce qu'il a dĂ©jĂ recommandĂ©.
Solution : aprÚs chaque réponse, les ID de produit sont enregistrés dans l'état de la session. Avant le prochain appel LLM, ce rappel les lit et insÚre un indice :
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
}
Rappel 2 :
ExtractAndInjectUserImage (BeforeModel)
ProblÚme : lorsque l'utilisateur fournit des commentaires ("Rends-le plus décontracté"), le message de suivi n'inclut plus la photo de l'utilisateur. Mais l'outil d'ajustement en a besoin.
Solution : Lors de la premiĂšre requĂȘte, ce rappel extrait la rĂ©fĂ©rence de l'image de l'utilisateur et l'enregistre dans l'Ă©tat. Lors des requĂȘtes suivantes, il le rĂ©injecte :
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
}
Rappel 3 :
SaveSelectedProducts (AfterModel)
Une fois que le LLM a répondu avec des suggestions de tenues, ce rappel analyse le JSON pour extraire les ID de produit et les enregistre pour que le rappel InjectPreviousProducts les utilise la prochaine fois :
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
}
Ensemble, ces trois rappels créent une boucle de rétroaction :
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Â L'agent racine
Fichier :
adk_backend/rootagent/agent.go
L'agent le plus simple (seulement 31Â lignes)Â :
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},
})
}
Il utilise gemini-3-flash-preview (le modÚle le plus rapide) parce que les décisions de routage sont simples : le LLM n'a qu'à lire l'intention de l'utilisateur et à choisir le bon sous-agent. Aucun outil n'est nécessaire : SubAgents gÚre la délégation automatiquement.
7. đ±Â Architecture du frontend Flutter
Le frontend Flutter est une application d'achat entiÚrement fonctionnelle. Les fonctionnalités d'IA se trouvent dans flutter_frontend/lib/workshop_tasks/, séparément de l'expérience d'achat prédéfinie dans core_app/.
Le modĂšle MVVM
L'application suit l'architecture Model-View-ViewModel avec le package Provider :
ââââââââââââââââââââ ââââââââââââââââââââââ ââââââââââââââââââââ
â 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 â â â â â
ââââââââââââââââââââ ââââââââââââââââââââââ ââââââââââââââââââââ
Chaque couche a un rÎle clair :
- ModÚle : classes de données telles que
Product,Outfit,StyleRequestet énumérations telles queTryOnState - ViewModel (
ChangeNotifier) : contient l'état actuel et diffuse les modifications apportées à l'UI vianotifyListeners() - View (Widget) : s'abonne au ViewModel avec
context.watchet se reconstruit lorsque l'état change() - Service : effectue des appels HTTP au backend de l'ADK et renvoie des données typées.
La couche de service
Les services sont définis comme des interfaces abstraites, avec des implémentations spécifiques à l'ADK :
// 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 { ... }
Cette séparation signifie que vous pouvez remplacer le backend de l'ADK par Firebase AI, un service fictif ou toute autre implémentation sans modifier le reste de l'application.
Le modÚle d'API en trois étapes
AdkFittingRoomService et AdkStylingService suivent le mĂȘme modĂšle pour communiquer avec le backend ADKÂ :
Ătape 1 : CrĂ©ez une session
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
}
Ătape 2 : ExĂ©cutez l'agent
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...
}
Ătape 3 : RĂ©cupĂ©rez l'artefact
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
}
Une diffĂ©rence de conception essentielle : le service d'essayage crĂ©e une session pour chaque requĂȘte (_createSession() est appelĂ© Ă chaque fois), tandis que le service de stylisme rĂ©utilise la mĂȘme session (_sessionId ??= await _createSession()) pour permettre une conversation en plusieurs tours.
Gestion de l'état : TryItOnProvider
Fichier :
workshop_tasks/step_1_try_it_on/providers/try_it_on_provider.dart
Le TryItOnProvider gÚre l'ensemble du parcours d'essayage. Il utilise une énumération TryOnState comme machine à états :
enum TryOnState { initial, imagePicked, generating, success, error }
class TryItOnProvider with ChangeNotifier {
TryOnState _state = TryOnState.initial;
Uint8List? _userImageBytes;
Uint8List? _generatedImage;
String? _errorMessage;
Les transitions d'état privées garantissent la cohérence : vous ne mettez jamais à jour l'état sans effacer également les données obsolÚtes et notifier l'UI :
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();
}
La méthode de génération principale relie le tout :
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;
}
L'UI : écrans en tant que routeur d'état
Fichier :
workshop_tasks/step_1_try_it_on/ui/2_try_it_on_screen.dart
L'écran d'essayage utilise la correspondance de modÚles de Dart 3 avec AnimatedSwitcher pour effectuer le routage entre les sous-écrans en fonction de l'état du fournisseur :
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 s'abonne au fournisseur. Chaque fois que notifyListeners() est appelé, ce widget est reconstruit et AnimatedSwitcher effectue une transition fluide entre les écrans. Il n'y a pas de Navigator.push : le contenu de l'écran change sur place en fonction de l'énumération d'état.
Transfert agentique : cabine d'essayage â styliste
Le modÚle d'UX le plus intéressant est la façon dont l'application transmet le contexte de l'agent de cabine d'essayage à l'agent styliste.
Dans 5_fitting_room.dart, une fois l'image d'essayage générée, le bouton "Style Me" (Trouve mon style) ouvre un formulaire. Lorsque l'utilisateur envoie :
// 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 regroupe tout ce dont le styliste a besoin :
- Lieu et occasion : contexte textuel pour la mise en forme
- URL de l'image utilisateur GCS : le styliste peut ainsi rĂ©utiliser exactement la mĂȘme reprĂ©sentation de l'utilisateur.
- Produit sélectionné : le styliste l'inclut dans chaque tenue.
Il s'agit d'un transfert agentique, qui consiste à transférer de maniÚre fluide le contexte multimodal d'un agent d'IA à un autre, l'utilisateur ne voyant qu'un simple formulaire.
Processus de stylisation : StylingProvider
Fichier :
workshop_tasks/step_2_style_me/providers/styling_provider.dart
StylingProvider est plus simple que TryItOnProvider, car il délÚgue la plupart de la complexité au backend :
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;
}
}
La mĂ©thode refineWithFeedback envoie un message en texte brut Ă la mĂȘme session. Les rappels InjectPreviousProducts et ExtractAndInjectUserImage du backend gĂšrent automatiquement toute la gestion du contexte.
8. đ ExĂ©cuter l'application en local
Pour une expĂ©rience Cloud Shell fluide, le backend Go sert l'application Web Flutter compilĂ©e Ă partir du mĂȘme port (8080). Un processus, une URL de prĂ©visualisation, aucun problĂšme d'origine croisĂ©e, aucune modification des fichiers de configuration.
Avant de commencer : vérifiez l'intégrité de l'ADC
Le backend a besoin des identifiants par dĂ©faut de l'application pour appeler Vertex AI. Si vous avez terminĂ© l'Ă©tape 7 de la configuration du projet dans cette session Cloud Shell et avec ce compte Google, tout est en ordre. Si vous revenez aprĂšs une pause, si vous avez changĂ© de compte ou si vous n'ĂȘtes pas sĂ»r, prenez cinq secondes pour vĂ©rifier :
gcloud auth application-default print-access-token | head -c 20 && echo "..."
Si environ 20 caractÚres d'un jeton s'affichent, tout est en ordre. Si une erreur se produit, réexécutez l'étape 7 de la configuration du projet :
gcloud auth application-default login
gcloud auth application-default set-quota-project $(gcloud config get-value project)
Vous allez utiliser deux terminaux Cloud Shell :
- Terminal A : exécute le backend en continu (
./run.sh). Laissez-le ouvert. - Terminal B : exécute la compilation Web Flutter une seule fois (
flutter build web). Quitte une fois terminé.
L'ordre n'a pas d'importance. Vous pouvez commencer par l'un ou l'autre. Toutefois, pour une expérience de premiÚre exécution la plus propre possible, commencez par compiler Flutter afin que le backend dispose d'une UI à partir de laquelle servir le contenu dÚs son démarrage.
1. Terminal B : compiler le bundle Flutter Web (ponctuel)
Ouvrez un nouvel onglet Cloud Shell (+ en haut du panneau du terminal), puis :
cd ~/fashion_app_demo/flutter_frontend
flutter pub get
flutter build web
Cela génÚre flutter_frontend/build/web/, un répertoire de fichiers statiques (HTML, JS, éléments), et se ferme une fois terminé. Le backend les diffusera dÚs qu'il verra que le répertoire existe.
2. Terminal A : démarrer le backend (opération de longue durée)
Dans votre terminal Cloud Shell d'origine :
cd ~/fashion_app_demo/adk_backend
./run.sh
Vous devez obtenir un résultat semblable au suivant :
Serving Flutter web build from ../flutter_frontend/build/web
Laissez ce terminal en cours d'exĂ©cution : le backend reste actif tant que run.sh est actif. Pour l'arrĂȘter, appuyez sur Ctrl+C.
Le serveur expose tout sur le port 8080 :
/ : application Web Flutter (interface utilisateur d'achat)/api/ : points de terminaison REST ADK (appelés par l'application Flutter)- UI de développement ADK : également disponible sur
/en l'absence de build Flutter. Utile pour le débogage direct des agents.
3. Ouvrir l'aperçu sur le Web
- Dans Cloud Shell, cliquez sur l'icĂŽne Aperçu sur le Web (en haut Ă droite) â Aperçu sur le port 8080.
- L'application d'achat Flutter se charge dans un nouvel onglet.
- Parcourez le catalogue de produits et sélectionnez un article.
- Appuyez sur l'icĂŽne reprĂ©sentant une personne (đ€) pour lancer le flux d'essayage.
- Importez une photo et regardez l'IA générer une image avec l'article porté.
- Appuyez sur "Style Me" pour obtenir des recommandations de tenues.
- Saisissez des commentaires de suivi tels que "rends-le plus dĂ©contractĂ©" : affinement au cours de la mĂȘme session
9. âïžÂ DĂ©ployer sur Cloud Run
Regrouper la compilation Flutter dans le backend
Le conteneur Cloud Run fournit à la fois l'API et l'UI à partir d'une seule image. Copiez la compilation Web Flutter dans adk_backend/flutter_web/. Il s'agit du premier chemin d'accÚs que le serveur Go vérifie lorsqu'il choisit l'UI à diffuser :
cd ~/fashion_app_demo/flutter_frontend
flutter build web
rm -rf ../adk_backend/flutter_web
cp -r build/web ../adk_backend/flutter_web
(Si vous avez effectuĂ© des itĂ©rations en local, vous avez peut-ĂȘtre dĂ©jĂ build/web Ă l'Ă©tape "ExĂ©cuter en local". Vous pouvez toujours rĂ©exĂ©cuter flutter build web.)
Déployer le backend (qui sert l'API et l'UI)
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
Une fois le dĂ©ploiement terminĂ©, une URL de service s'affiche, par exemple https://fashion-app-backend-xyz-uc.a.run.app. Ouvrez-la dans un navigateur : l'application d'achat Flutter se charge Ă partir de /, et ses appels d'API sont envoyĂ©s Ă /api/ sur le mĂȘme hĂŽte. Aucune modification de la configuration du frontend n'est nĂ©cessaire, aucune clĂ© API n'est transmise.
Vérifier le déploiement
Ouvrez l'URL Cloud Run dans votre navigateur et parcourez l'intégralité du flux :
- Parcourir â SĂ©lectionnez un produit
- Essayage â Importez votre photo â DĂ©couvrez l'image gĂ©nĂ©rĂ©e par l'IA
- Style-moi â Indiquez le lieu/l'occasion â DĂ©couvrez des tenues sĂ©lectionnĂ©es
- Commentaires â Saisissez "Rends-le plus dĂ©contractĂ©" â DĂ©couvrez les tenues modifiĂ©es
- Ajouter au panier â Finaliser le parcours d'achat
10. đ Conclusion
Ce que vous avez créé
Vous avez exploré une expérience retail complÚte optimisée par l'IA avec :
- â  Un backend multi-agent avec quatre agents spĂ©cialisĂ©s qui travaillent ensemble
- â  Une cabine d'essayage virtuelle qui gĂ©nĂšre des images d'essayage personnalisĂ©es
- â  Un styliste IA qui sĂ©lectionne des tenues et les affine grĂące Ă la conversation
- â Â Une application Flutter multiplate-forme qui se connecte au backend de l'agent
- â  DĂ©ploiement Cloud Run pour un hĂ©bergement Ă©volutif et sans serveur
Concepts clés
Concept | OĂč l'avez-vous vu ? |
Orchestration multi-agents ADK | Routage de l'agent racine vers les agents de cabine d'essayage, de catalogue et de styliste |
Génération d'images multimodales Gemini |
|
Ătat de la session pour l'IA conversationnelle | Styliste rĂ©utilisant des sessions pour obtenir des commentaires itĂ©ratifs |
Stockage des artefacts pour les données binaires | Séparer le stockage des images des réponses textuelles |
Rappels pour la logique du middleware |
|
MVVM + Provider dans Flutter |
|
Transfert agentif |
|
Ătapes suivantes
- đšÂ Personnaliser les requĂȘtes de l'agent : modifiez
instructions.mdpour changer la personnalitĂ© du styliste. - đïžÂ Ajouter des produits : mettez Ă jour
catalog.yamlavec de nouveaux articles. - đ±Â CrĂ©ez une vidĂ©o adaptĂ©e aux mobiles : exĂ©cutez
flutter build iosouflutter build apk. - đ Ajouter des sessions persistantes : remplacez
InMemoryServicepar une implĂ©mentation basĂ©e sur une base de donnĂ©es. - đ Ajouter l'authentification : sĂ©curiser le point de terminaison Cloud Run avec IAM
Ressources
- Documentation ADKÂ : documentation officielle sur l'Agent Development Kit
- Code source Go de l'ADK : dépÎt GitHub
- Documentation de référence du package Go de l'ADK : documentation de référence de l'API
- Documentation de l'API Gemini : capacités et guides des modÚles
- Package Flutter Provider : documentation sur la gestion des états
- Documentation Cloud Run : guides de déploiement et de scaling