1. ã¯ããã«
äœæããæ©èœ
ãã® Codelab ã§ã¯ãæ¶ç©ºã®å°å£²ãã©ã³ãåãã® Flutter ã·ã§ããã³ã° ã¢ããªã§ãã Fashion App ãæ§ç¯ããéçºè ã®åœ¹å²ãæ ããŸããããã·ã§ã³: ãªã³ã©ã€ã³ ã·ã§ããã³ã° ãšã¯ã¹ããªãšã³ã¹ãå€é©ãã AI æèŒã®æ©èœã 2 ã€è¿œå ããŸãã
- ããŒãã£ã«è©Šç宀 - ãŠãŒã¶ãŒãèªåã®åçãã¢ããããŒãããè¡£æåãéžæãããšããã®è¡£æåãççšããŠããèªåã® AI çæç»åã衚瀺ãããŸãã
- AI ã¹ã¿ã€ãªã¹ã - ãŠãŒã¶ãŒã®å Žæãæ©äŒãã¹ã¿ã€ã«ã®å¥œã¿ã«åºã¥ããŠãAI ãšãŒãžã§ã³ããããããã®ã³ãŒãã£ããŒããææ¡ããŸãããŠãŒã¶ãŒã¯äŒè©±ãéããŠææ¡ãçµã蟌ãããšãã§ããŸãã
詊çå®€ã§æã詊çãããšãè³Œå ¥ããå¯èœæ§ãå€§å¹ ã«é«ãŸããšããã·ã³ãã«ãªèãæ¹ã§ãããªã³ã©ã€ã³ã§ã¯ã©ãã§ããããïŒåœãŠãã£ãœãã§èšã£ãŠããã ãã§ãããã®ãããžã§ã¯ãã¯ãAI ãæŽ»çšããŠãã®ã®ã£ãããåããŸãã
ã¢ãŒããã¯ãã£ã®æŠèŠ
Flutter App ââââ HTTP/REST âââââ¶ ADK Go Backend
â
ââââââââââââŒâââââââââââ
Fitting Room Stylist Catalog
Agent Agent Agent
â
Gemini API + Cloud Storage
æ žãšãªããã¯ãããžãŒ
ã³ã³ããŒãã³ã | ãã¯ãããžãŒ | ç®ç |
ãšãŒãžã§ã³ã ãã¬ãŒã ã¯ãŒã¯ | Go çš ADKïŒAgent Development KitïŒ | ãã«ããšãŒãžã§ã³ãã®ãªãŒã±ã¹ãã¬ãŒã·ã§ã³ãã»ãã·ã§ã³ãã¢ãŒãã£ãã¡ã¯ã |
ãšãŒãžã§ã³ãã®æšè«ïŒProïŒ | Gemini 3.1 Pro ãã¬ãã¥ãŒç | 詊ç宀ãšã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã匷å |
ãšãŒãžã§ã³ãã®æšè«ïŒFlashïŒ | Gemini 3 Flash ãã¬ãã¥ãŒ | ã«ãŒã ãšãŒãžã§ã³ããšã«ã¿ãã° ãšãŒãžã§ã³ãã匷åïŒè»œéã«ãŒãã£ã³ã°/ã«ãã¯ã¢ããïŒ |
ç»åçæ | Gemini 2.5 Flash Image | 詊çç»åãšã³ãŒãã£ããŒãç»åãçæãã |
ããã³ããšã³ã | FlutterïŒDartïŒ | ã¯ãã¹ ãã©ãããã©ãŒã ã¢ããªïŒãŠã§ããiOSãAndroidïŒ |
ã¹ãã¬ãŒãž | Google Cloud Storage | ååç»åãšçæãããã¢ãŒãã£ãã¡ã¯ããä¿åãã |
ãã¹ãæ¹æ³ | Cloud Run | ãµãŒããŒã¬ã¹ ã³ã³ããã®ããã〠|
2. ðŠ åææ¡ä»¶ãš Cloud Shell ã®èšå®
1. Cloud Shell ãšãã£ã¿ãéã
ð ãã©ãŠã¶ã§ Cloud Shell ãšãã£ã¿ãéããŸãã
ã¿ãŒããã«ãç»é¢ã®äžéšã«è¡šç€ºãããªãå Žå:
- [衚瀺] â [ã¿ãŒããã«] ãã¯ãªãã¯ããŸãã
2. Flutter SDK ãèšå®ãã
Cloud Shell ã«ã¯ã/google/flutter ã« Flutter ãããªã€ã³ã¹ããŒã«ãããŠããŸãããã®ãã£ã¬ã¯ããªã¯å¥ã®ã·ã¹ãã ãŠãŒã¶ãŒãææããŠãããããflutter ãæåã«å®è¡ãããšãã« fatal: detected dubious ownership ãšã©ãŒãçºçããŸããgit ã® safe-directory ãªã¹ãã« 1 å远å ããŸãã
git config --global --add safe.directory /google/flutter
PATH ã§ Flutter ãåäœããŠããããšã確èªããŸãã
flutter --version
ååå®è¡ã§ã¯ãDart SDK ãããŠã³ããŒããããFlutter ããŒã«ããã«ããããŸãã1 åã»ã©ããããŸããFlutter 3.x ⢠channel stable ãªã©ã®ã³ãŒãã衚瀺ãããŸãã
3. ãªããžããªã®ã¯ããŒã³ãäœæãã
cd ~
git clone https://github.com/gca-americas/fashion-app-demo
cd fashion_app_demo
4. ãããžã§ã¯ãã®æ§é ã確èªãã
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 ãããžã§ã¯ãã®èšå®
1. æ°ãããããžã§ã¯ãã®äœæ
gcloud projects create fashion-app-demo --name="Fashion App Demo"
gcloud config set project fashion-app-demo
2. è«æ±å ã¢ã«ãŠã³ãããªã³ã¯ãã
è«æ±å ã¢ã«ãŠã³ããäžèŠ§è¡šç€ºããŸãã
gcloud billing accounts list
æ¬¡ã®æé ã«æ²¿ã£ãŠ
OPEN
åãTrue ãšè¡šç€ºãããŠããå¿
èŠããããŸããFalse ãšè¡šç€ºãããŠããå ŽåïŒç¡æãã©ã€ã¢ã«ã®æå¹æéãåããŠããå Žåã«äžè¬çïŒãã¢ã«ãŠã³ãã¯ééãããŠãããå®éã«ã¯äœãæ¯æãããŸãããç¶è¡ããåã«ã以äžã®ãã©ãã«ã·ã¥ãŒãã£ã³ã° ãããã¯ã«é²ãã§ãã ããã
OPEN: True ã¢ã«ãŠã³ãã® ACCOUNT_IDïŒ0X0X0X-0X0X0X-0X0X0X ã®ãããªåœ¢åŒïŒãã³ããŒããŠããããžã§ã¯ãã«ãªã³ã¯ããŸãã
gcloud billing projects link fashion-app-demo \
--billing-account=YOUR_BILLING_ACCOUNT_ID
ãªã³ã¯ã確èªããŸãã
gcloud billing projects describe fashion-app-demo
billingEnabled: true ã衚瀺ãããŸãããªã³ã¯åŸã billingEnabled: false ã衚瀺ãããå Žåã¯ãã¢ã«ãŠã³ããééãããŠããŸãïŒOPEN: FalseïŒã以äžã®ãã©ãã«ã·ã¥ãŒãã£ã³ã° ãããã¯ãåç
§ããŠãã ããã
3. å¿ èŠãª API ã®æå¹å
gcloud services enable \
aiplatform.googleapis.com \
storage.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
API | ç®ç |
| Vertex AI - |
| Cloud Storage - ååã«ã¿ãã°ã®ç»åãšçæããã詊ççµæãä¿åããŸãã |
| Cloud Run - ããã¯ãšã³ãããµãŒããŒã¬ã¹ ã³ã³ãããšããŠãã¹ãããŸãã |
| Cloud Build - ãœãŒã¹ãã Docker ã€ã¡ãŒãžããã«ãããŸãã |
| Artifact Registry - ãã«ãããã Docker ã€ã¡ãŒãžãä¿åããŸãã |
4. 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. ååã«ã¿ãã°ã®ç»åãã¢ããããŒããã
ããã¯ãšã³ãã® getProductImage ããŒã«ã¯ gs://$GCS_BUCKET/catalog-assets/images/ ããèªã¿åããŸããã«ã¿ãã°ç»åãæ¬¡ã®ãã¹ã«ã¢ããããŒãããŸãã
cd ~/fashion_app_demo
gcloud storage cp flutter_frontend/assets/images/*.png \
gs://fashion-app-$PROJECT_ID/catalog-assets/images/
ã¢ããããŒãã確èªããŸãïŒ.png ãã¡ã€ã«ã®ãªã¹ãã衚瀺ãããŸãïŒã
gcloud storage ls gs://fashion-app-$PROJECT_ID/catalog-assets/images/
6. .env ãã¡ã€ã«ãæ§æãã
cd ~/fashion_app_demo/adk_backend
cat > .env << EOF
GOOGLE_CLOUD_PROJECT=$PROJECT_ID
GCS_BUCKET=fashion-app-$PROJECT_ID
EOF
7. ã¢ããªã±ãŒã·ã§ã³ã®ããã©ã«ãèªèšŒæ å ±ã§èªèšŒãã
ããã¯ãããã¯ãšã³ããããŒã«ã«ã§èµ·åããåã«å®è¡ããå¿
èŠããããŸããGo ããã¯ãšã³ãã¯ãADC ã䜿çšã㊠Vertex AIïŒGeminiïŒãš Cloud Storage ãžã®ãã¹ãŠã®åŒã³åºããèªèšŒããŸããADC ããªããšãããã¯ãšã³ãã¯èµ·åããŸããããã¹ãŠã®è©Šçãªã¯ãšã¹ãã 401 CREDENTIALS_MISSING ã§å€±æããŸãã
1 ã€ã®èªèšŒæ å ±ã§äž¡æ¹ã®ãµãŒãã¹ãã«ããŒããŸããæ¬¡ã® 2 ã€ã®ã³ãã³ããé çªã«å®è¡ããŸãã
# 1. Log in (opens a browser; in Cloud Shell, paste the verification code back)
gcloud auth application-default login
# 2. Attach your project as the quota / billing project for ADC
gcloud auth application-default set-quota-project $(gcloud config get-value project)
ADC ãæ£åžžã§ããããšã確èªããŸãã
gcloud auth application-default print-access-token | head -c 20 && echo "..."
ããŒã¯ã³ã®çŽ 20 æåã®åŸã« ... ã衚瀺ãããŸãããšã©ãŒãçºçããå Žåã¯ããã°ã€ã³ãå®äºããŠããŸãããæé 1 ãããäžåºŠå®è¡ããŠãã ããã
4. ðïž ã¢ãŒããã¯ãã£ã®æŠèŠ
ç°å¢ãæŽã£ãã®ã§ãã³ãŒããèŠãåã«ã·ã¹ãã ã®ä»çµã¿ãçè§£ããŸãããã
4 ãšãŒãžã§ã³ã ã·ã¹ãã
ããã¯ãšã³ãã¯ãGo çšã® ADKïŒAgent Development KitïŒã䜿çšããŠãã«ããšãŒãžã§ã³ã ã·ã¹ãã ãšããŠæ§ç¯ãããŠããŸãã4 ã€ã®ãšãŒãžã§ã³ãã飿ºããŠåäœãããããããç¹å®ã®åœ¹å²ãæ ã£ãŠããŸãã
ââââââââââââââââ
â 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 â
ââââââââââââââââââ
ãšãŒãžã§ã³ã | ã¢ãã« | ããŒã« |
ã«ãŒã ãšãŒãžã§ã³ã |
| 亀éèŠå¯å®ããŠãŒã¶ãŒã®ã¡ãã»ãŒãžãèªã¿åããé©åãªã¹ãã·ã£ãªã¹ã ãšãŒãžã§ã³ãã«å§ä»»ããŸããã«ãŒãã£ã³ã°ã®æ±ºå®ã®ã¿ãè¡ããããé«éã§è»œéãªã¢ãã«ã䜿çšããŸãã |
ã«ã¿ãã° ãšãŒãžã§ã³ã |
| ãããã¯ã ãšãã¹ããŒããYAML ãã¡ã€ã«ããååã«ã¿ãã°ãèªã¿èŸŒã¿ãååã«é¢ããã¯ãšãªã«åçããŸãã軜éã§ããããŸããããŒã¿ã®æ€çŽ¢ã®ã¿ãè¡ãããã§ãã |
Fitting Room Agent |
| ããŒãã£ã«ã§ã詊ãã¹ãã·ã£ãªã¹ãããŠãŒã¶ãŒã®åçãšååç»åãååŸãããã®ååãççšããŠãã人ç©ã®åæç»åãçæããŸããç»åã«ã€ããŠæšè«ããå¿ èŠãããããããã髿§èœãªã¢ãã«ã䜿çšããŸãã |
ã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ã |
| ãã¡ãã·ã§ã³ ã¢ããã€ã¶ãŒãå Žæãæ©äŒã奜ã¿ã«åºã¥ããŠãã«ã¿ãã°ãã 3 ã€ã®ã³ãŒãã£ããŒããææ¡ããŸããåã³ãŒãã£ããŒãã®è©Šçç»åãçæã§ããŸããã¯ãªãšã€ãã£ããªæšè«ã«ã髿§èœã¢ãã«ã䜿çšããŸãã |
ãšã³ã㪠ãã€ã³ã: main.go
ãã¹ãŠã¯ main.go ããå§ãŸãããšãŒãžã§ã³ããæ¥ç¶ã㊠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()
}
泚æãã¹ãéèŠãªç¹ãããã€ããããŸãã
- ãšãŒãžã§ã³ãã¯ããã ã¢ããã§æ§ç¯ããã: ãã£ããã£ã³ã° ã«ãŒã ãšãŒãžã§ã³ããšã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã®äž¡æ¹ãã«ã¿ãã° ãšãŒãžã§ã³ãã«äŸåããŠããããïŒååæ€çŽ¢ãã«ã¿ãã° ãšãŒãžã§ã³ãã«å§ä»»ããŠããããïŒãã«ã¿ãã° ãšãŒãžã§ã³ããæåã«äœæãããŸãã
agent.NewMultiLoader㯠4 ã€ã®ãšãŒãžã§ã³ããã¹ãŠãç»é²ãããããREST API ã¯ååã§ããããã®ãšãŒãžã§ã³ãã«ã«ãŒãã£ã³ã°ã§ããŸããadkrest.NewServer㯠REST API ãèªåçã«æäŸããŸãããšã³ããã€ã³ã ãã³ãã©ãèªåã§äœæããå¿ èŠã¯ãããŸãããADK ã«ã¯ãã»ãã·ã§ã³ç®¡çãã¢ãŒãã£ãã¡ã¯ã ã¹ãã¬ãŒãžããšãŒãžã§ã³ãå®è¡ãããã«äœ¿çšã§ããç¶æ ã§çšæãããŠããŸããsession.InMemoryService()ã¯ã»ãã·ã§ã³ãã¡ã¢ãªã«ä¿åããŸããã€ãŸãããµãŒããŒãåèµ·åãããšã»ãã·ã§ã³ã倱ãããŸããããã¢ã§ã¯åé¡ãããŸãããæ¬çªç°å¢ã§ã¯ãæ°žç¶ã¹ãã¢ã䜿çšããŸããgcsartifact.NewServiceã¯ãã¢ãŒãã£ãã¡ã¯ãïŒçæãããç»åïŒã Google Cloud Storage ã«ä¿åããããããªã¯ãšã¹ãéã§æ°žç¶åãããGCS URI ãä»ããŠå ±æã§ããŸãã
5. ð€ ADKïŒAgent Development KitïŒã®è©³çް
ADK ãšã¯
Agent Development KitïŒADKïŒã¯ãGoïŒããã³ Python/JavaïŒã§ AI ãšãŒãžã§ã³ããæ§ç¯ããããã® Google ã®ãªãŒãã³ãœãŒã¹ ãã¬ãŒã ã¯ãŒã¯ã§ããããã¯ãã¢ããªã±ãŒã·ã§ã³ãš Gemini API ã®éã®ã¬ã€ã€ã§ãã
Gemini API ãçŽæ¥åŒã³åºãããšãã§ããŸãããã ããã¢ããªã§æ¬¡ã®åŠçãå¿ èŠã«ãªã£ãå Žåã¯ã
- ã«ã¿ãã°ããååãæ€çŽ¢ãã
- ãŠãŒã¶ãŒã®åçã«åºã¥ããŠç»åãçæãã
- 以åã«ææ¡ãããæè£ ãèšæ¶ãã
- è€æ°ã® AI ãšãŒãžã§ã³ãã調æŽãã
æ§é ãå¿ èŠã§ããADK ã¯ãã®æ§é ãæäŸããŸãã
ãšãŒãžã§ã³ã ã«ãŒã
ãã¹ãŠã® ADK ãšãŒãžã§ã³ãã¯æ¬¡ã®ã«ãŒãã«åŸããŸãã
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
ãã®ã«ãŒãã¯ã1 ã€ã®ãªã¯ãšã¹ãå ã§è€æ°åç¹°ãè¿ãããšãã§ããŸããããšãã°ãã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã¯æ¬¡ã®ããšãã§ããŸãã
- ãããŒãã§ã®äŒæã«ãŽã£ããã®ã¹ã¿ã€ã«ãæããŠããšå ¥åãã
catalog_agentããŒã«ãåŒã³åºããŠååãªã¹ããååŸãã- 3 ã€ã®æè£ ã®çµã¿åãããéžæãã
- åã³ãŒãã£ããŒãã«å¯ŸããŠ
fitting_toolãåŒã³åºããŠç»åãçæããŸãã - æ§é åããã JSON ã¬ã¹ãã³ã¹ãè¿ã
åºæ¬ã³ã³ã»ããïŒãã®ãªããžããªã®ã³ãŒãã䜿çšïŒ
LLM ãšãŒãžã§ã³ã
äž»ãªæ§æèŠçŽ ã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
})
Instruction ãã£ãŒã«ãã¯ãšãŒãžã§ã³ãã®ãã«ãœãã§ããLLM ã«ãšãŒãžã§ã³ãã®åœ¹å²ãšè¡åæ¹æ³ãäŒããŸãããã®ãªããžããªã§ã¯ãæé 㯠Markdown ãã¡ã€ã«ãšããŠèšè¿°ãããGo ã® //go:embed ãã£ã¬ã¯ãã£ãã䜿çšããŠã³ã³ãã€ã«æã«åã蟌ãŸããŸãã
//go:embed instructions.md
var instructions string
ããã«ãããããã³ããã¯ã€ã³ã©ã€ã³æååã§ã¯ãªããåå¥ã®ããŒãžã§ã³ç®¡çå¯èœãªããã¥ã¡ã³ããšããŠä¿æãããŸãã
ããŒã«
ããŒã«ã¯ãLLM ãåŒã³åºãããšãã§ãã Go 颿°ã§ããADK ã¯ãLLM ã®ããŒã«åŒã³åºã圢åŒãšåä»ã Go 颿°éã®å€æãåŠçããŸãã
// From catalog/agent.go
type ListProductsArgs struct{} // Input (can be empty)
type ListProductsResult struct {
Products []Product `json:"products"` // Output
}
func ListProducts(ctx tool.Context, args ListProductsArgs) (ListProductsResult, error) {
return ListProductsResult{Products: catalogProducts}, nil
}
// Register it:
listTool, _ := functiontool.New(functiontool.Config{
Name: "listProducts",
Description: "list all products in the catalog",
}, ListProducts)
ADK ã¯ãGo æ§é äœãã JSON ã¹ããŒããèªåçã«çæããLLM ã«éä¿¡ããŸããLLM ã listProducts ã®åŒã³åºããæ±ºå®ãããšãADK ã¯åŒæ°ãéã·ãªã¢ã«åãã颿°ãåŒã³åºããŠçµæãè¿ããŸãã
tool.Context ãã©ã¡ãŒã¿ã䜿çšãããšãããŒã«ã¯ ADK ã®ã©ã³ã¿ã€ã ãµãŒãã¹ïŒæãéèŠãªã®ã¯ã¢ãŒãã£ãã¡ã¯ãïŒã«ã¢ã¯ã»ã¹ã§ããŸãã
// Save an image as an artifact
ctx.Artifacts().Save(ctx, "my_image", imagePart)
// Load an artifact
resp, _ := ctx.Artifacts().Load(ctx, "my_image")
ãµããšãŒãžã§ã³ãã®å§ä»»
ãšãŒãžã§ã³ãã¯ã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
},
ãã£ããã£ã³ã° ã«ãŒã ãšãŒãžã§ã³ãã¯ãååæ å ±ãå¿ èŠãªãšãã«ãéåžžããŒã«ãšåãããã«ã«ã¿ãã° ãšãŒãžã§ã³ããåŒã³åºãããšãã§ããŸããLLM ã¯ããŒã«ãªã¹ãã§ãã®ããŒã«ã確èªããåŒã³åºããã©ããã倿ã§ããŸãã
ã»ãã·ã§ã³
ã»ãã·ã§ã³ã¯äŒè©±å±¥æŽã远跡ããŸããADK ã® REST API ã¯ãããããèªåçã«ç®¡çããŸãã
POST /api/apps/{appName}/users/{userId}/sessions â Creates a new session
POST /api/run (with sessionId) â Runs agent within that session
ãã®ã¢ããªã®éèŠãªèšèšäžã®æ±ºå®äºé ã¯ã詊ç宀ã¯ãªã¯ãšã¹ãããšã«æ°ããã»ãã·ã§ã³ãäœæããïŒè©Šçã¯ããããç¬ç«ããŠããïŒã®ã«å¯Ÿããã¹ã¿ã€ãªã¹ãã¯åãã»ãã·ã§ã³ãåå©çšããïŒä»¥åã®ææ¡ãèšæ¶ãããã£ãŒãããã¯ã«åºã¥ããŠæ¹åã§ããïŒããšã§ãã
å·
State ã¯ãã»ãã·ã§ã³ã«é¢é£ä»ãããã Key-Value ã¹ãã¢ã§ãããšãŒãžã§ã³ãã¯ã調æŽã®ããã«ç¶æ ã®èªã¿åããšæžã蟌ã¿ãè¡ããŸãã
// Write to state
ctx.State().Set("previously_used_products", "[\"id_bomber\",\"id_hat\"]")
// Read from state
val, err := ctx.State().Get("previously_used_products")
ã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã¯ãç¶æ ã䜿çšããŠããã§ã«ææ¡ããååãèšæ¶ããæ¬¡åã¯å¥ã®ååãéžæããŸãã
ã¢ãŒãã£ãã¡ã¯ã
ã¢ãŒãã£ãã¡ã¯ãã¯ãã»ãã·ã§ã³ããšã«ä¿åãããååä»ããã€ã㪠ãªããžã§ã¯ãïŒéåžžã¯ç»åïŒã§ããããã¹ã ã¬ã¹ãã³ã¹ãšã¯ç°ãªãããããã¯åå¥ã«ä¿åãããååã§ååŸãããŸãã
// 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}
ããã«ãããã¬ã¹ãã³ã¹ã軜éã«ãªããŸãããšãŒãžã§ã³ãã¯ã¢ãŒãã£ãã¡ã¯ãåã®ã¿ãè¿ããããã³ããšã³ãã¯ãã€ããªç»åããŒã¿ãåå¥ã«ååŸããŸãã
ã³ãŒã«ããã¯
ã³ãŒã«ããã¯ã¯ããšãŒãžã§ã³ã ã«ãŒãã®ç¹å®ã®æç¹ã§å®è¡ãããããã¯ã§ããå®è¡ã®æ€æ»ã倿Žãç絡ãå¯èœã§ãã
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},
}
ã³ãŒã«ããã¯ã nil 以å€ã®ã¬ã¹ãã³ã¹ãè¿ããå Žåãããã©ã«ãã®åäœã¯ã¹ããããããŸããããšãã°ããã£ãã·ã¥ã«ä¿åãããã¬ã¹ãã³ã¹ãè¿ã BeforeModelCallback ã¯ãå®éã® LLM åŒã³åºããå®å
šã«ã¹ãããããŸãã
JSON ã¹ããŒãã®é©çš
ãã£ããã£ã³ã° ã«ãŒã ãšãŒãžã§ã³ããšã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã®äž¡æ¹ã§ãLLM ã«æ§é åããã JSON ã§ã®å¿çã匷å¶ããŸãã
GenerateContentConfig: &genai.GenerateContentConfig{
ResponseMIMEType: "application/json",
ResponseJsonSchema: fittingSchemaMap(), // Defines the expected structure
}
ããã«ãããFlutter ããã³ããšã³ãã¯åžžã«è§£æå¯èœãªããŒã¿ãåãåããèªç±åœ¢åŒã®ããã¹ããåãåãããšã¯ãããŸããã
ã«ã¿ãã° ãšãŒãžã§ã³ã: æãç°¡åãªäŸ
ã«ã¿ãã° ãšãŒãžã§ã³ãïŒcatalog/agent.goïŒã¯ã·ã¹ãã å
ã§æãã·ã³ãã«ãªãšãŒãžã§ã³ãã§ãããADK ãã¿ãŒã³ãçè§£ããããã®åºçºç¹ãšããŠæé©ã§ãã
次㮠2 ã€ã®ããŒã«ããããŸãã
listProducts- YAML ãã¡ã€ã«ããå®å šãªååã«ã¿ãã°ãè¿ããŸãgetProductImage- GCSïŒãŸãã¯ããŒã«ã« ãã©ãŒã«ããã¯ïŒããååç»åãèªã¿èŸŒã¿ãã¢ãŒãã£ãã¡ã¯ããšããŠä¿åããŸãã
getProductImage ããŒã«ã¯ãéèŠãªãã¿ãŒã³ã§ããã¢ãŒãã£ãã¡ã¯ã ãã£ãã·ã¥ã䜿çšãããã«ããœãŒã¹èªã¿èŸŒã¿ã瀺ããŠããŸãã
// 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
}
ãã®ããŒã«ã¯ããŸãã¢ãŒãã£ãã¡ã¯ãã詊ããæ¬¡ã« GCSãæåŸã«ããŒã«ã« ãã¡ã€ã«ã詊ããŸããèªã¿èŸŒãŸããã€ã¡ãŒãžã¯ã¢ãŒãã£ãã¡ã¯ããšããŠãã£ãã·ã¥ã«ä¿åãããããã以éã®åŒã³åºãã¯ç¬æã«è¡ãããŸãã
6. 𧪠AI ãã€ãã©ã€ã³: ãšãŒãžã§ã³ãã®æŽ»çš
次ã«ãå®éã«ç»åãçæããŠã³ãŒãã£ããŒããææ¡ãããæãé«åºŠãª 2 ã€ã®ãšãŒãžã§ã³ãã«ã€ããŠèª¬æããŸãã
6.1 詊ç宀ãšãŒãžã§ã³ã
ãã¡ã€ã«:
adk_backend/fittingroom/agent.go
詊ç宀ãšãŒãžã§ã³ãã¯ããããŒãã£ã«ã§ã詊ããã®èåŸã«ãããšã³ãžã³ã§ãããŠãŒã¶ãŒãåçãã¢ããããŒãããŠååãéžæãããšããã®ãšãŒãžã§ã³ãã¯ããã®ååãççšããŠãã人ç©ã®åæç»åãçæããŸãã
fitting_tool - æé
ã³ã¢ããžãã¯ã¯ doFitting 颿°ã«ãããŸãããšãŒãžã§ã³ãããã®é¢æ°ãåŒã³åºããšã次ã®åŠçãè¡ãããŸãã
ã¹ããã 1: ãŠãŒã¶ãŒç»åã解決ãã
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
}
ãŠãŒã¶ãŒç»åã¯æ¬¡ã® 2 ã€ã®ãœãŒã¹ããååŸã§ããŸãã
- ã¢ãŒãã£ãã¡ã¯ãåïŒ
upload_abc123_1ãªã©ïŒ - ããã¯æåã®ã¢ããããŒãã§ãSaveIncomingBlobsã³ãŒã«ããã¯ã«ãã£ãŠä¿åãããŸãã gs://URI - ããã¯ã以åã«çæããããã£ããã£ã³ã°çµæã§ãã»ãã·ã§ã³éã®åå©çšã®ããã« GCS ã«ä¿åãããŠããŸãã
ãã®ãã¥ã¢ã«ãã¹èšèšã¯æå³çãªãã®ã§ããã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ããåŸã§è¡£è£ ã®è©Šçãçæããéã«ãæåã®è©Šç宀ã®çµæãã GCS URL ãåå©çšããããããŠãŒã¶ãŒã® ID ã¯ãã¹ãŠã®è¡£è£ ã§äžè²«æ§ãä¿ã¡ãŸãã
ã¹ããã 2: ãã«ãã¢ãŒãã« ããã³ãããæ§ç¯ãã
parts := []*genai.Part{
genai.NewPartFromText(toolInstructions), // Identity preservation prompt
genai.NewPartFromText("Reference Person Photo:"),
userPart, // The user's photo
}
for _, acc := range args.Accessories {
accResp, _ := ctx.Artifacts().Load(ctx, acc) // Load product image artifact
parts = append(parts, genai.NewPartFromText("Product Image to Apply:"))
parts = append(parts, accResp.Part) // The product photo
}
toolInstructionsïŒtool_instructions.md ããåã蟌ã¿ïŒãéèŠã§ããããã¯ãGemini ã«ããŠãŒã¶ãŒã®ã¢ã€ãã³ãã£ãã£ïŒé¡ãäœåãèã®è²ã髪ïŒãä¿æããªãããè¡£æåã®ã¿ãé©çšããããã«æç€ºããŸãããã®ããã³ãã ãšã³ãžãã¢ãªã³ã°ããªããšãã¢ãã«ã人ç©ã®å€èŠã倿Žããå¯èœæ§ããããŸãã
ã¹ããã 3: ç»åçæã®ããã« Gemini ãåŒã³åºã
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
})
4 ã€ã®ãšãŒãžã§ã³ããšç»åçæããŒã«ã¯ãåäžã®èªèšŒãã¹ïŒBackend: genai.BackendVertexAI ãšãããžã§ã¯ã IDïŒãå
±æããã¢ããªã±ãŒã·ã§ã³ã®ããã©ã«ãèªèšŒæ
å ±ã䜿çšããŠèªèšŒãããŸãããªãŒã±ã¹ãã¬ãŒã·ã§ã³ ã¢ãã«ïŒgemini-3.1-pro-previewãgemini-3-flash-previewïŒãšç»åã¢ãã«ïŒgemini-2.5-flash-imageïŒã¯ãã¹ãŠåã Vertex AI ãšã³ããã€ã³ãã®èåŸã«ãããåã ADC ã Cloud Storage ãžã®ã¢ã¯ã»ã¹ãæ¿èªããŸããã€ãŸãã1 ã€ã®èªèšŒæ
å ±ã§ããã¹ãŠã®åŒã³åºããå¯èœã§ãã
ã¹ããã 4: çµæãä¿åãã
// 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
ãã¥ã¢ã«ä¿åïŒã¢ãŒãã£ãã¡ã¯ã + GCSïŒã¯ããã£ããã£ã³ã° ã«ãŒã ãšã¹ã¿ã€ãªã¹ãéã®ãšãŒãžã§ã³ã ãã³ããªãã®éµãšãªããŸããã¢ãŒãã£ãã¡ã¯ãã¯çŸåšã®ã»ãã·ã§ã³å ã§å³åº§ã«ã¢ã¯ã»ã¹ã§ããŸãããGCS URI ã䜿çšãããšãã¹ã¿ã€ãªã¹ãïŒå¥ã®ã»ãã·ã§ã³ã§å®è¡ïŒãåŸã§åãç»åãåç §ã§ããŸãã
SaveIncomingBlobs ã³ãŒã«ããã¯
ãšãŒãžã§ã³ããæšè«ãéå§ããåã«ããã® BeforeAgentCallback ãå®è¡ããããŠãŒã¶ãŒãã¢ããããŒãããç»åãä¿åãããŸãã
func SaveIncomingBlobs(ctx agent.CallbackContext) (*genai.Content, error) {
for pindex, p := range ctx.UserContent().Parts {
if p.InlineData != nil {
aname := fmt.Sprintf("upload_%s_%d", ctx.InvocationID(), pindex)
ctx.Artifacts().Save(ctx, aname, p)
}
}
return nil, nil // Return nil to proceed with normal agent execution
}
(nil, nil) ãè¿ãããšã§ãã³ãŒã«ããã¯ã¯ãååŠçãå®äºããŸããããšãŒãžã§ã³ããéåžžã©ããå®è¡ããŠãã ããããšããã·ã°ãã«ãéããŸããnil 以å€ã®ã³ã³ãã³ããè¿ãããå ŽåããšãŒãžã§ã³ãã¯å®å
šã«ã·ã§ãŒããµãŒããããããŸãã
6.2 ã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ã
ãã¡ã€ã«:
adk_backend/stylist/agent.go
ã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã¯ãã·ã¹ãã å ã§æãé«åºŠãªãšãŒãžã§ã³ãã§ããããŒãœãã©ã€ãºãããããããã®æè£ ãææ¡ããäŒè©±ãéããŠç¹°ãè¿ãçµã蟌ãããšãã§ããŸãã
3 ã€ã®ã³ãŒã«ãã㯠- ã¹ã¿ã€ãªã¹ãã®èšæ¶
ã¹ã¿ã€ãªã¹ãã¯ããã«ãã¿ãŒã³ã®äŒè©±ã§ã³ã³ããã¹ããç¶æããããã«ã次㮠3 ã€ã®ã³ãŒã«ããã¯ã䜿çšããŸãã
ã³ãŒã«ãã㯠1:
InjectPreviousProductsïŒBeforeModelïŒ
åé¡: ãŠãŒã¶ãŒããå¥ã®ãªãã·ã§ã³ã衚瀺ããŠããšèšããšãLLM ã¯ãã§ã«æšå¥šããååãååºŠææ¡ããå¯èœæ§ããããŸããããã¯ãLLM ãæšå¥šããååãæ¬è³ªçã«è¿œè·¡ããŠããªãããã§ãã
解決ç: åã¬ã¹ãã³ã¹ã®åŸã«ãåå ID ãã»ãã·ã§ã³ç¶æ ã«ä¿åãããŸããæ¬¡ã® LLM åŒã³åºãã®åã«ããã®ã³ãŒã«ããã¯ã¯ããããèªã¿åãããã³ããæ¿å ¥ããŸãã
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
}
ã³ãŒã«ãã㯠2:
ExtractAndInjectUserImageïŒBeforeModelïŒ
åé¡: ãŠãŒã¶ãŒããã£ãŒãããã¯ïŒããã£ãšã«ãžã¥ã¢ã«ã«ããŠãïŒãæäŸãããšããã©ããŒã¢ãã ã¡ãã»ãŒãžã«ãŠãŒã¶ãŒã®åçãåã³å«ãŸããŸããããã ãããã£ããã£ã³ã° ããŒã«ã«ã¯å¿ èŠã§ãã
ãœãªã¥ãŒã·ã§ã³: æåã®èŠæ±ã§ããã®ã³ãŒã«ããã¯ã¯ãŠãŒã¶ãŒç»ååç §ãæœåºããç¶æ ã«ä¿åããŸããåŸç¶ã®ãªã¯ãšã¹ãã§ã¯ãåæ¿å ¥ãããŸãã
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
}
ã³ãŒã«ãã㯠3:
SaveSelectedProductsïŒAfterModelïŒ
LLM ãè¡£è£
ã®ææ¡ãè¿ããšããã®ã³ãŒã«ããã¯ã¯ JSON ãè§£æããŠåå ID ãæœåºããæ¬¡å InjectPreviousProducts ã³ãŒã«ããã¯ã§äœ¿çšã§ããããã«ä¿åããŸãã
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
}
ããã 3 ã€ã®ã³ãŒã«ããã¯ãçµã¿åããããšããã£ãŒããã㯠ã«ãŒããäœæãããŸãã
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 ã«ãŒã ãšãŒãžã§ã³ã
ãã¡ã€ã«:
adk_backend/rootagent/agent.go
æãã·ã³ãã«ãªãšãŒãžã§ã³ãïŒããã 31 è¡ïŒ:
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},
})
}
ã«ãŒãã£ã³ã°ã®æ±ºå®ã¯åçŽã§ãLLM ã¯ãŠãŒã¶ãŒã®ã€ã³ãã³ããèªã¿åã£ãŠé©åãªãµããšãŒãžã§ã³ããéžæããã ãã§æžããããgemini-3-flash-previewïŒæéã®ã¢ãã«ïŒã䜿çšããŸããããŒã«ã¯äžèŠã§ããSubAgents ãå§ä»»ãèªåçã«åŠçããŸãã
7. ð± Flutter ããã³ããšã³ã ã¢ãŒããã¯ãã£
Flutter ããã³ããšã³ãã¯ãå®å
šã«æ©èœããå°å£²ã·ã§ããã³ã° ã¢ããªã§ããAI æ©èœã¯ core_app/ ã®äºåæ§ç¯ãããã·ã§ããã³ã° ãšã¯ã¹ããªãšã³ã¹ãšã¯å¥ã«ãflutter_frontend/lib/workshop_tasks/ ã«ååšããŸãã
MVVM ãã¿ãŒã³
ã¢ããªã¯ãProvider ããã±ãŒãžã§ Model-View-ViewModel ã¢ãŒããã¯ãã£ã«æ²¿ã£ãŠå®è¡ãããŸãã
ââââââââââââââââââââ ââââââââââââââââââââââ ââââââââââââââââââââ
â 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 â â â â â
ââââââââââââââââââââ ââââââââââââââââââââââ ââââââââââââââââââââ
åã¬ã€ã€ã«ã¯æç¢ºãªåœ¹å²ããããŸãã
- ã¢ãã«:
ProductãOutfitãStyleRequestãªã©ã®ããŒã¿ã¯ã©ã¹ãTryOnStateãªã©ã®åæå - ViewModelïŒ
ChangeNotifierïŒ: çŸåšã®ç¶æ ãä¿æããnotifyListeners()ãä»ã㊠UI ã«å€æŽããããŒããã£ã¹ãããŸãã - ViewïŒãŠã£ãžã§ããïŒ:
context.watchã§ ViewModel ããµãã¹ã¯ã©ã€ãããç¶æ ãå€åãããšåæ§ç¯ããŸãã() - ãµãŒãã¹: ADK ããã¯ãšã³ãã« HTTP åŒã³åºããè¡ããåä»ãããŒã¿ãè¿ããŸãã
ãµãŒãã¹ã¬ã€ã€
ãµãŒãã¹ã¯ã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 { ... }
ãã®åé¢ã«ãããã¢ããªã®æ®ãã®éšåã倿ŽããããšãªããADK ããã¯ãšã³ãã Firebase AIãã¢ãã¯ãµãŒãã¹ããã®ä»ã®å®è£ ã«çœ®ãæããããšãã§ããŸãã
3 ã¹ãããã® API ãã¿ãŒã³
AdkFittingRoomService ãš AdkStylingService ã¯ã©ã¡ããåããã¿ãŒã³ã«åŸã£ãŠ ADK ããã¯ãšã³ããšéä¿¡ããŸãã
ã¹ããã 1: ã»ãã·ã§ã³ãäœæãã
Future<String> _createSession() async {
final url = Uri.parse(
'$_baseUrl/apps/${Uri.encodeComponent(_appName)}/users/$_userId/sessions');
final response = await _client.post(url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({}));
final body = jsonDecode(response.body) as Map<String, dynamic>;
return body['id'] as String; // Returns the session ID
}
ã¹ããã 2: ãšãŒãžã§ã³ããå®è¡ãã
Future<(String?, String?)> _runAgent({required String sessionId, ...}) async {
final requestBody = jsonEncode({
'appName': _appName,
'userId': _userId,
'sessionId': sessionId,
'newMessage': {
'role': 'user',
'parts': [
{'text': 'Generate a virtual try-on...'},
{'inlineData': {'mimeType': 'image/jpeg', 'data': base64Encode(userImageBytes)}},
{'inlineData': {'mimeType': 'image/png', 'data': base64Encode(productImageBytes)}},
],
},
});
final response = await _client.post(Uri.parse('$_baseUrl/run'), body: requestBody);
// Parse response events for artifact name and GCS URL...
}
ã¹ããã 3: ã¢ãŒãã£ãã¡ã¯ããååŸãã
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
}
éèŠãªèšèšäžã®éã: ãã£ããã£ã³ã° ã«ãŒã ãµãŒãã¹ã¯ãªã¯ãšã¹ãããšã«æ°ããã»ãã·ã§ã³ãäœæããŸããïŒ_createSession() ãåŒã³åºããããã³ïŒãã¹ã¿ã€ãªã³ã° ãµãŒãã¹ã¯åãã»ãã·ã§ã³ãåå©çšããŸãïŒ_sessionId ??= await _createSession()ïŒãããã«ãããè€æ°ã¿ãŒã³ã®äŒè©±ãå¯èœã«ãªããŸãã
ç¶æ 管ç: TryItOnProvider
ãã¡ã€ã«:
workshop_tasks/step_1_try_it_on/providers/try_it_on_provider.dart
TryItOnProvider ã¯è©ŠçãããŒå
šäœã管çããŸããTryOnState åæåãã¹ããŒããã·ã³ãšããŠäœ¿çšããŸãã
enum TryOnState { initial, imagePicked, generating, success, error }
class TryItOnProvider with ChangeNotifier {
TryOnState _state = TryOnState.initial;
Uint8List? _userImageBytes;
Uint8List? _generatedImage;
String? _errorMessage;
ãã©ã€ããŒãç¶æ ã®é·ç§»ã«ããæŽåæ§ã確ä¿ãããŸããå€ãããŒã¿ãã¯ãªã¢ã㊠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();
}
äž»ãªçæã¡ãœããã¯æ¬¡ã®ãšããã§ãã
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;
}
UI: ç¶æ ã«ãŒã¿ãŒãšããŠã®ç»é¢
ãã¡ã€ã«:
workshop_tasks/step_1_try_it_on/ui/2_try_it_on_screen.dart
詊çç»é¢ã§ã¯ãDart 3 ã®ãã¿ãŒã³ ãããã³ã°ãš AnimatedSwitcher ã䜿çšããŠããããã€ãã®ç¶æ
ã«åºã¥ããŠãµãç»é¢éãã«ãŒãã£ã³ã°ããŸãã
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 ã¯ãããã€ããå®æè³Œå
¥ããŸããnotifyListeners() ãåŒã³åºããããã³ã«ããã®ãŠã£ãžã§ãããåæ§ç¯ãããAnimatedSwitcher ãç»é¢éãã¹ã ãŒãºã«é·ç§»ããŸããNavigator.push ã¯ãããŸãããç»é¢ã®ã³ã³ãã³ãã¯ãç¶æ
åæåã«åºã¥ããŠãã®å Žã§å€æŽãããŸãã
ãšãŒãžã§ã³ã ãã³ããªã: 詊ç宀 â ã¹ã¿ã€ãªã¹ã
æãè峿·±ã UX ãã¿ãŒã³ã¯ãã¢ããªããã£ããã£ã³ã° ã«ãŒã ãšãŒãžã§ã³ãããã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ãã«ã³ã³ããã¹ããæž¡ãæ¹æ³ã§ãã
5_fitting_room.dart ã§ã¯ã詊çç»åãçæãããåŸã[ã¹ã¿ã€ã«ãææ¡] ãã¿ã³ã§ãã©ãŒã ãéããŸãããŠãŒã¶ãŒãéä¿¡ãããšã:
// 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 ã«ã¯ãã¹ã¿ã€ãªã¹ãã«å¿
èŠãªãã®ããã¹ãŠãã³ãã«ãããŠããŸãã
- å Žæãšæ©äŒ - ã¹ã¿ã€ãªã³ã°ã®ããã¹ã ã³ã³ããã¹ã
- GCS ãŠãŒã¶ãŒç»å URL - ã¹ã¿ã€ãªã¹ãããŸã£ããåããŠãŒã¶ãŒè¡šçŸãåå©çšã§ããããã«ããŸãã
- éžæããåå - ã¹ã¿ã€ãªã¹ãããã¹ãŠã®ã³ãŒãã£ããŒãã«å«ãã
ããããšãŒãžã§ã³ã ãã³ããªãã§ãããã«ãã¢ãŒãã« ã³ã³ããã¹ãã 1 ã€ã® AI ãšãŒãžã§ã³ãããå¥ã® AI ãšãŒãžã§ã³ãã«ã·ãŒã ã¬ã¹ã«è»¢éãããŠãŒã¶ãŒã«ã¯ã·ã³ãã«ãªãã©ãŒã ã®ã¿ã衚瀺ãããŸãã
ã¹ã¿ã€ãªã³ã° ãããŒ: StylingProvider
ãã¡ã€ã«:
workshop_tasks/step_2_style_me/providers/styling_provider.dart
StylingProvider ã¯ãè€éãã®ã»ãšãã©ãããã¯ãšã³ãã«å§ä»»ãããããTryItOnProvider ãããã·ã³ãã«ã§ãã
class StylingProvider with ChangeNotifier {
StylingState _state = StylingState.initial;
List<Outfit> _outfits = [];
Future<bool> getStyleSuggestions(StyleRequest request) async {
if (_state == StylingState.loading) return false; // Prevent spam
_currentRequest = request;
_setLoading();
try {
final suggestions = await _stylingService.getStyleSuggestions(request);
_setSuccess(suggestions);
return true;
} catch (e) {
_setError('Something went wrong.');
}
return false;
}
// Multi-turn refinement â uses the same session
Future<bool> refineWithFeedback(String feedback) async {
if (_state == StylingState.loading) return false;
_setLoading();
try {
final suggestions = await _stylingService.refineWithFeedback(feedback);
_setSuccess(suggestions);
return true;
} catch (e) {
_setError('Something went wrong.');
}
return false;
}
}
refineWithFeedback ã¡ãœããã¯ãåãã»ãã·ã§ã³ã«ãã¬ãŒã³ ããã¹ã ã¡ãã»ãŒãžãéä¿¡ããŸããããã¯ãšã³ãã® InjectPreviousProducts ã³ãŒã«ããã¯ãš ExtractAndInjectUserImage ã³ãŒã«ããã¯ã¯ããã¹ãŠã®ã³ã³ããã¹ã管çãèªåçã«åŠçããŸãã
8. ð ã¢ããªãããŒã«ã«ã§å®è¡ãã
Cloud Shell ãã¹ã ãŒãºã«å©çšã§ããããã«ãGo ããã¯ãšã³ãã¯ã³ã³ãã€ã«ããã Flutter ãŠã§ãã¢ããªãåãããŒãïŒ8080ïŒããæäŸããŸãã1 ã€ã®ããã»ã¹ã1 ã€ã®ãã¬ãã¥ãŒ URLãã¯ãã¹ãªãªãžã³ã®åé¡ãªããæ§æãã¡ã€ã«ã®ç·šéãªãã
å§ããåã« - ADC ã®å¥å šæ§ãã§ãã¯
ããã¯ãšã³ãã Vertex AI ãåŒã³åºãã«ã¯ãã¢ããªã±ãŒã·ã§ã³ã®ããã©ã«ãèªèšŒæ å ±ãå¿ èŠã§ãããã® Cloud Shell ã»ãã·ã§ã³ãšãã® Google ã¢ã«ãŠã³ãã§ãããžã§ã¯ãèšå®ã®æé 7 ãå®äºããŠããå Žåã¯ããã®ãŸãŸé²ãã§ãã ããããã°ããããŠããæ»ã£ãŠããå Žåãã¢ã«ãŠã³ããåãæ¿ããå ŽåããŸãã¯ããããããªãå Žåã¯ã5 ç§ã»ã©ã§ç¢ºèªã§ããŸãã
gcloud auth application-default print-access-token | head -c 20 && echo "..."
ããŒã¯ã³ã®æåã 20 æåçšåºŠåºåãããã°ãèšå®ã¯å®äºã§ãããšã©ãŒãçºçããå Žåã¯ããããžã§ã¯ãèšå®ã®æé 7 ãåå®è¡ããŸãã
gcloud auth application-default login
gcloud auth application-default set-quota-project $(gcloud config get-value project)
2 ã€ã® Cloud Shell ã¿ãŒããã«ã䜿çšããŸãã
- ã¿ãŒããã« A - ããã¯ãšã³ããç¶ç¶çã«å®è¡ããŸãïŒ
./run.shïŒãéãããŸãŸã«ããŸãã - ã¿ãŒããã« B - Flutter ãŠã§ããã«ãã 1 åå®è¡ããŸãïŒ
flutter build webïŒãå®äºãããšçµäºããŸãã
é åºã¯é¢ä¿ãããŸãããã©ã¡ãããã§ãéå§ã§ããŸãããã ããååå®è¡æã®ãšã¯ã¹ããªãšã³ã¹ãæãã¯ãªãŒã³ã«ããã«ã¯ããŸã Flutter ããã«ãããŠãããã¯ãšã³ããèµ·åããç¬éããæäŸãã UI ãçšæããŸãã
1. ã¿ãŒããã« B - Flutter Web ãã³ãã«ããã«ãããïŒ1 åéãïŒ
æ°ãã Cloud Shell ã¿ãïŒã¿ãŒããã« ããã«ã®äžéšã«ãã +ïŒãéããæ¬¡ã®æäœãè¡ããŸãã
cd ~/fashion_app_demo/flutter_frontend
flutter pub get
flutter build web
ããã«ãããéçãã¡ã€ã«ïŒHTMLãJSãã¢ã»ããïŒã®ãã£ã¬ã¯ããªã§ãã flutter_frontend/build/web/ ãçæãããå®äºãããšçµäºããŸããããã¯ãšã³ãã¯ããã£ã¬ã¯ããªã®ååšã確èªãããšããã«ããããé
ä¿¡ããŸãã
2. ã¿ãŒããã« A - ããã¯ãšã³ããéå§ããïŒé·æéå®è¡ïŒ
å ã® Cloud Shell ã¿ãŒããã«ã§ãæ¬¡ã®æäœãè¡ããŸãã
cd ~/fashion_app_demo/adk_backend
./run.sh
次ã®ãããªåºåã衚瀺ãããŸãã
Serving Flutter web build from ../flutter_frontend/build/web
ãã®ã¿ãŒããã«ãå®è¡ãããŸãŸã«ããŸã - run.sh ãåç¶ããŠããéããããã¯ãšã³ãã¯çšŒåãç¶ããŸãã忢ããã«ã¯ãCtrl+C ãã¿ããããŸãã
ãµãŒããŒã¯ããŒã 8080 ã§å ¬éããŸãã
/- Flutter ãŠã§ãã¢ããªïŒã·ã§ããã³ã° UIïŒ/api/- ADK REST ãšã³ããã€ã³ãïŒFlutter ã¢ããªã«ãã£ãŠåŒã³åºãããïŒ- ADK éçº UI - Flutter ãã«ãããªãå Žåã¯
/ã«ããããŸãããšãŒãžã§ã³ãã®çŽæ¥ãããã°ã«äŸ¿å©ã§ãã
3. ãŠã§ã ãã¬ãã¥ãŒãéã
- Cloud Shell ã§ã[ãŠã§ãã§ãã¬ãã¥ãŒ] ã¢ã€ã³ã³ïŒå³äžïŒâ [ããŒã 8080 ã§ãã¬ãã¥ãŒ] ãã¯ãªãã¯ããŸãã
- Flutter ã·ã§ããã³ã° ã¢ããªãæ°ããã¿ãã§èªã¿èŸŒãŸãã
- ååã«ã¿ãã°ãé²èЧããŠã¢ã€ãã ãéžæãã
- 人åã¢ã€ã³ã³ïŒð€ïŒãã¿ããããŠãããŒãã£ã«è©ŠçãããŒãéå§ããŸãã
- åçãã¢ããããŒããããšãAI ã詊çç»åãçæããŸã
- [ããããã®ã¹ã¿ã€ã«ã衚瀺] ãã¿ããããŠãããããã®æè£ ã衚瀺ãã
- ããã£ãšã«ãžã¥ã¢ã«ãªæäœã«ããªã©ã®ãã©ããŒã¢ãã ãã£ãŒãããã¯ãå ¥åãã - åãã»ãã·ã§ã³ã§ã®èª¿æŽ
9. âïž Cloud Run ã«ãããã€ãã
Flutter ãã«ããããã¯ãšã³ãã«ãã³ãã«ãã
Cloud Run ã³ã³ããã¯ã1 ã€ã®ã€ã¡ãŒãžãã API ãš UI ã®äž¡æ¹ãé
ä¿¡ããŸããFlutter ãŠã§ããã«ãã adk_backend/flutter_web/ ã«ã³ããŒããŸããããã¯ãGo ãµãŒããŒãæäŸãã UI ãéžæããéã«æåã«ãã§ãã¯ãããã¹ã§ãã
cd ~/fashion_app_demo/flutter_frontend
flutter build web
rm -rf ../adk_backend/flutter_web
cp -r build/web ../adk_backend/flutter_web
ïŒããŒã«ã«ã§å埩åŠçãè¡ã£ãŠããå Žåã¯ãããŒã«ã«ã§å®è¡ããæé ã§ãã§ã« build/web ãããå¯èœæ§ããããŸããflutter build web ãåå®è¡ããŠãåé¡ãããŸããïŒã
ããã¯ãšã³ãããããã€ããïŒAPI ãš 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
ãããã€ãå®äºãããšãhttps://fashion-app-backend-xyz-uc.a.run.app ã®ãããªãµãŒãã¹ URL ã衚瀺ãããŸãããã©ãŠã¶ã§éããŸããFlutter ã·ã§ããã³ã° ã¢ããªã / ããèªã¿èŸŒãŸããAPI åŒã³åºãã¯åããã¹ãã® /api/ ã«éä¿¡ãããŸããããã³ããšã³ãæ§æã®ç·šéã¯äžèŠã§ãAPI ããŒã¯æž¡ãããŸããã
ãããã€ã確èªãã
ãã©ãŠã¶ã§ Cloud Run ã® URL ãéãããããŒå šäœãå®è¡ããŸãã
- [Browse]ïŒé²èЧïŒâ ãããã¯ããéžæ
- [詊ç] â åçãã¢ããããŒã â AI ã§çæãããç»åã確èª
- Style Me â å Žæ/å Žé¢ãå ¥å â ããããã®ã³ãŒãã£ããŒãã衚瀺
- ãã£ãŒããã㯠â ããã£ãšã«ãžã¥ã¢ã«ã«ããŠããšå ¥å â æŽæ°ãããæè£ ã確èªãã
- [Add to Bag]ïŒããã°ã«è¿œå ïŒâ ã·ã§ããã³ã° ãããŒãå®äºãã
10. ð ãŸãšã
æ§ç¯ããå 容
AI ãæŽ»çšããå°å£²æ¥ã®å®å šãªãšã¯ã¹ããªãšã³ã¹ã«ã€ããŠã以äžã®å 容ãåŠç¿ããŸããã
- â 4 ã€ã®ç¹ååãšãŒãžã§ã³ãã飿ºããŠåäœãããã«ããšãŒãžã§ã³ã ããã¯ãšã³ã
- â ããŒãœãã©ã€ãºããã詊çç»åãçæããããŒãã£ã«è©Šç宀
- â äŒè©±ãéããŠæè£ ãå³éžããæŽç·Žããã AI ã¹ã¿ã€ãªã¹ã
- â ãšãŒãžã§ã³ã ããã¯ãšã³ãã«æ¥ç¶ããã¯ãã¹ ãã©ãããã©ãŒã ã® Flutter ã¢ããª
- â ã¹ã±ãŒã©ãã«ãªãµãŒããŒã¬ã¹ ãã¹ãã£ã³ã°ã®ããã® Cloud Run ãããã€
äž»ãªæŠå¿µ
ã³ã³ã»ãã | ã©ãã§èŠãã |
ADK ãã«ããšãŒãžã§ã³ã ãªãŒã±ã¹ãã¬ãŒã·ã§ã³ | ãã£ããã£ã³ã° ã«ãŒã ãã«ã¿ãã°ãã¹ã¿ã€ãªã¹ã ãšãŒãžã§ã³ããžã®ã«ãŒã ãšãŒãžã§ã³ãã®ã«ãŒãã£ã³ã° |
Gemini ãã«ãã¢ãŒãã«ç»åçæ |
|
äŒè©±å AI ã®ã»ãã·ã§ã³ç¶æ | ã¹ã¿ã€ãªã¹ããã»ãã·ã§ã³ãåå©çšããŠãã£ãŒãããã¯ãç¹°ãè¿ã |
ãã€ã㪠ããŒã¿ã®ã¢ãŒãã£ãã¡ã¯ã ã¹ãã¬ãŒãž | ç»åã¹ãã¬ãŒãžãšããã¹ã ã¬ã¹ãã³ã¹ãåé¢ãã |
ããã«ãŠã§ã¢ ããžãã¯ã®ã³ãŒã«ãã㯠|
|
Flutter ã® MVVM + Provider |
|
ãšãŒãžã§ã³ãã«ããåŒãç¶ã |
|
次ã®ã¹ããã
- ðš ãšãŒãžã§ã³ãã®ããã³ãããã«ã¹ã¿ãã€ãº -
instructions.mdãç·šéããŠã¹ã¿ã€ãªã¹ãã®åæ§ãå€æŽ - ðïž ååã远å ãã - æ°ããã¢ã€ãã ã§
catalog.yamlãæŽæ°ããŸã - ð± ã¢ãã€ã«å¯Ÿå¿ -
flutter build iosãŸãã¯flutter build apkãå®è¡ããŸãã - ð æ°žç¶çãªã»ãã·ã§ã³ã远å -
InMemoryServiceãããŒã¿ããŒã¹ ããã¯ã¢ããå®è£ ã«çœ®ãæã - ð èªèšŒã远å ãã - IAM ã§ Cloud Run ãšã³ããã€ã³ããä¿è·ãã
ãªãœãŒã¹
- ADK ããã¥ã¡ã³ã - Agent Development Kit ã®å ¬åŒããã¥ã¡ã³ã
- ADK Go ãœãŒã¹ã³ãŒã - GitHub ãªããžããª
- ADK Go ããã±ãŒãž ãªãã¡ã¬ã³ã¹ - API ãªãã¡ã¬ã³ã¹
- Gemini API ã®ããã¥ã¡ã³ã - ã¢ãã«ã®æ©èœãšã¬ã€ã
- Flutter Provider ããã±ãŒãž - ç¶æ 管çã«é¢ããããã¥ã¡ã³ã
- Cloud Run ã®ããã¥ã¡ã³ã - ãããã€ãšã¹ã±ãŒãªã³ã°ã®ã¬ã€ã