Find your Search Engine ID: go to Agent Platform > Agents > Search, find "Paint Search" in the app list, copy its ID (looks like paint-search_1756...).
Create the .env file — copy this into a text file, replace YOUR_ID, then paste into the terminal:
Wait until the Cymbal Paint data store shows Ready on the Documents tab (Agent Platform > Agents > Search > Data Stores > Cymbal Paint), then wait a couple extra minutes.
Re-run and test:
bash
adkrunpaint_agent
Ask it for prices of EcoGreens and Forever Paint. Click Check my progress.
Type exit when done.
Task 4 — Implement shared state
Open adk_challenge_lab/paint_agent/tools.py. Find set_session_value and implement it:
# Copyright 2025 Google LLC
# ... (Standard Header)
from google.adk.tools.agent_tool import AgentTool
from dotenv import load_dotenv
import google.auth
from google.adk.agents import Agent
from google.adk.models import Gemini
from google.genai import types
import google.cloud.logging
import os
from .callback_logging import log_query_to_model, log_model_response
from .sub_agents.room_planner.agent import room_planner_agent
from .sub_agents.search_agent.agent import search_agent
Open the agent.py for coverage_calculator_agent (under room_planner's sub-agents). Find the instruction string with ALL-CAPS placeholders like SELECTED_PAINT, COVERAGE_RATE, etc. Replace each with ADK's optional templating syntax:
Before: ...using the paint SELECTED_PAINT with coverage rate COVERAGE_RATE...
After: ...using the paint {SELECTED_PAINT?} with coverage rate {COVERAGE_RATE?}...
Do this for every ALL-CAPS placeholder in that instruction.
You should now be able to have the following conversation with your agent:
You
Agent Response
hello
[Offers to share information about Cymbal Shops' paints]
yes
[Shares information about paint products, i.e. Project Paint, EcoGreens, SureCoverage, Forever Paint.]
I'd like to use EcoGreens
[The State tab should show updated state values. Asks how many rooms and their names.]
Just one room, my office
[Asks you to select a color for your office.]
Deep Ocean
[Asks you for the room dimensions.]
3m by 4m. 3m high. 1 door, 2 windows.
[Confirms how many coats.]
Two coats.
[Calculates you will need coverage of 74 sq meters.]
Open the web preview, select paint_agent, and run through the sample conversation in the doc (hello → yes → "I'd like to use EcoGreens" → room name → color → dimensions → coats). Confirm the State tab updates and the coverage calculation (74 sq meters) comes back correctly.
Click Check my progress.
Back in the terminal, press Ctrl+C to stop the server.
While it deploys, go to IAM in the console and grant these roles to service-573697952121@gcp-sa-aiplatform-re.iam.gserviceaccount.com:
Agent Platform User
Discovery Engine User
When deployment finishes, copy the printed agent resource name — you'll need it next.
Click Check my progress.
Task 6. Query the deployed agent
In the file adk_challenge_lab/chainlit_ui/app.py, find and update the following line with your deployed agent's resource name to load your remote agent:
import chainlit as cl
import vertexai
from uuid import uuid4
from dotenv import load_dotenv
import os
from bs4 import BeautifulSoup
import asyncio
load_dotenv()
project_id = os.environ["GOOGLE_CLOUD_PROJECT"]
location = os.environ["GOOGLE_CLOUD_LOCATION"]
bucket_name = f"gs://{project_id}-bucket"
client = vertexai.Client(
project=project_id,
location=location,
)
# Updated resource name for Task 6
agent = client.agent_engines.get(name='projects/653349639632/locations/us-central1/reasoningEngines/9083023225859866624')
def convert_img_tags_to_chainlit_images(msg):
img_list = []
if not msg.content:
return msg
soup = BeautifulSoup(msg.content, "html.parser")
print("Converting img tags to chainlit images")
found_imgs = soup.find_all("img")
for img_tag in found_imgs:
if img_tag.has_attr("src"):
img = cl.Image(url=img_tag["src"], name="swatch", display="inline")
img_list.append(img)
# Remove the tag so it doesn't appear as a broken image in text
img_tag.decompose()
msg.elements = img_list
# Only update content if there's actual text left, otherwise keep original
# logic or a space to ensure msg.content isn't 'falsy' if elements exist
cleaned_text = soup.get_text().strip()
if cleaned_text:
msg.content = cleaned_text
elif img_list:
# If we have images but no text, provide a small spacer so Chainlit displays it
msg.content = " "
return msg
@cl.set_starters
async def set_starters():
return [
cl.Starter(
label="Painting Project Help",
message="Tell me about Cymbal Shops' interior paints.",
icon="/public/swatches.svg",
)
]
@cl.on_chat_start
async def on_chat_start():
print("A new chat session has started!")
user_id = "user"
try:
# Use make_async to avoid blocking the event loop during session creation
session_details = await cl.make_async(agent.create_session)(user_id=user_id)
cl.user_session.set("user_id", user_id)
cl.user_session.set("session_id", session_details["id"])
cl.user_session.set(
"message_history",
[{"role": "system", "content": "You are a helpful assistant."}],
)
except Exception as e:
print(f"Error starting chat: {e}")
await cl.Message(content="Failed to initialize agent session. Please refresh the page.").send()
@cl.on_message
async def main(message: cl.Message):
# Ensure session is ready, wait slightly if it's currently initializing
for _ in range(10):
user_id = cl.user_session.get("user_id")
session_id = cl.user_session.get("session_id")
if session_id:
break
await asyncio.sleep(0.5)
else:
await cl.Message(content="Session initialization is taking longer than expected. Please refresh.").send()
return
message_history = cl.user_session.get("message_history")
message_history.append({"role": "user", "content": message.content})
msg = cl.Message(content="")
try:
async_stream = agent.async_stream_query(
user_id=user_id,
session_id=session_id,
message=message.content,
)
# Use the native async generator provided by ADK
async for event in async_stream:
print(f"DEBUG: Received event: {event}")
# Check if the stream returned an explicit backend error
if isinstance(event, dict):
if "error" in event:
await cl.Message(content=f"Agent Engine Error: {event['error']}").send()
return
# Handle payload errors like "code: 498, Event loop is closed" silently
if "code" in event and "message" in event:
print(f"Agent Backend Notification (Code {event.get('code')}): {event.get('message')}")
continue
# Check for standard content and parts
if "content" in event and "parts" in event["content"]:
for part in event["content"]["parts"]:
if "text" in part:
await msg.stream_token(part["text"])
# Post-process message
convert_img_tags_to_chainlit_images(msg)
# Check if we have content OR elements (images)
actual_content = msg.content.strip() if msg.content else ""
if actual_content or msg.elements:
message_history.append({"role": "assistant", "content": msg.content})
await msg.update()
else:
# If the stream finished completely empty, optionally clean up the empty message
await msg.remove()
except Exception as e:
print(f"Error querying agent: {e}")
await cl.Message(content=f"An error occurred: {str(e)}").send()
Run the UI with the following:
cd ~/adk_challenge_lab/chainlit_ui
chainlit run app.py
Copied!
Expected output:
2025-08-25 12:30:00 - Your app is available at http://localhost:8000
Click the link for http://localhost:8000 to open it in a new browser tab.
Have the following conversation with your deployed agent:
You
Agent Response
hello
[Offers to share information about Cymbal Shops’ paints]
yes
[Shares information about paint products, i.e. Project Paint, EcoGreens, SureCoverage, Forever Paint ]
I'd like to use Forever Paint
[Asks how many rooms and their names]
Two rooms. The living room and a baby's room.
[Asks you to select a color for your living room and baby's room.]
"Sunlight through a canvas tent" for the baby's room and "Coffee Cream" for the living room.
[Asks you for the room dimensions]
The living room is 5m by 4m. 2.5m high. 1 door, 3 windows.
[Asks number of coats.]
Two coats.
[Requests dimensions for baby's room.]
The baby's room is 3m by 3m. 2.5m high. 1 door, 1 window.
[Provides a one-coat estimate and confirms the number of coats.]
Always two coats.
[Calculates you will need coverage of 77 sq meters for the living room and 53 sq meters for the baby's room.]
If you'd like to start a new conversation with your agent, you can click the icon in the upper left to Create a New Chat.
Click Check my progress to verify the objective.
Congratulations!
In this lab, you have:
Built an agent with Agent Development Kit (ADK) made up of a root agent and sub-agents
Enabled agents with an Agent Search tool and Python functions used as tools
Stored agent output in a session state dictionary and retrieved values from the session state dictionary for subsequent agent instructions
Post a Comment
Post a Comment