Commit ยท
a61c2dd
1
Parent(s): 92a9c38
update
Browse files- agent_setup.py +13 -8
- app.py +23 -13
- bigquery_search.py +17 -0
- miscellaneous/instructions.md +648 -0
- tools.py +25 -0
agent_setup.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
from google.adk import Agent, Runner
|
| 2 |
from google.adk.sessions import InMemorySessionService
|
| 3 |
-
from tools import create_plant_diagnosis_tool,
|
| 4 |
|
| 5 |
def initialize_adk(vision_model, processor, retriever):
|
| 6 |
"""
|
|
@@ -9,23 +9,28 @@ def initialize_adk(vision_model, processor, retriever):
|
|
| 9 |
Args:
|
| 10 |
vision_model: The loaded vision model.
|
| 11 |
processor: The model's processor.
|
| 12 |
-
retriever: The RAG retriever.
|
| 13 |
|
| 14 |
Returns:
|
| 15 |
A dictionary containing the ADK components, or None on error.
|
| 16 |
"""
|
| 17 |
print("Initializing ADK Tools and Agent...")
|
| 18 |
try:
|
| 19 |
-
if vision_model and processor
|
| 20 |
diagnosis_tool = create_plant_diagnosis_tool(model=vision_model, processor=processor)
|
| 21 |
-
|
|
|
|
| 22 |
|
| 23 |
agent = Agent(
|
| 24 |
name="AuraMindGlowAgent",
|
| 25 |
model="gemini-2.0-flash",
|
| 26 |
-
description="A farming assistant that can diagnose plant health and suggest remedies.",
|
| 27 |
-
instruction="You are a friendly farming assistant. Your goal is to help users identify plant health issues and find solutions.
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
)
|
| 30 |
|
| 31 |
session_service = InMemorySessionService()
|
|
@@ -36,7 +41,7 @@ def initialize_adk(vision_model, processor, retriever):
|
|
| 36 |
"agent": agent,
|
| 37 |
"runner": runner,
|
| 38 |
"diagnosis_tool": diagnosis_tool,
|
| 39 |
-
"remedy_tool":
|
| 40 |
"session_service": session_service
|
| 41 |
}
|
| 42 |
else:
|
|
|
|
| 1 |
from google.adk import Agent, Runner
|
| 2 |
from google.adk.sessions import InMemorySessionService
|
| 3 |
+
from tools import create_plant_diagnosis_tool, create_chroma_db_search_tool, create_bigquery_search_tool
|
| 4 |
|
| 5 |
def initialize_adk(vision_model, processor, retriever):
|
| 6 |
"""
|
|
|
|
| 9 |
Args:
|
| 10 |
vision_model: The loaded vision model.
|
| 11 |
processor: The model's processor.
|
| 12 |
+
retriever: The RAG retriever (no longer used but kept for compatibility).
|
| 13 |
|
| 14 |
Returns:
|
| 15 |
A dictionary containing the ADK components, or None on error.
|
| 16 |
"""
|
| 17 |
print("Initializing ADK Tools and Agent...")
|
| 18 |
try:
|
| 19 |
+
if vision_model and processor:
|
| 20 |
diagnosis_tool = create_plant_diagnosis_tool(model=vision_model, processor=processor)
|
| 21 |
+
chroma_db_tool = create_chroma_db_search_tool()
|
| 22 |
+
bigquery_tool = create_bigquery_search_tool()
|
| 23 |
|
| 24 |
agent = Agent(
|
| 25 |
name="AuraMindGlowAgent",
|
| 26 |
model="gemini-2.0-flash",
|
| 27 |
+
description="A farming assistant that can diagnose plant health and suggest remedies from local or cloud knowledge bases.",
|
| 28 |
+
instruction="You are a friendly and intelligent farming assistant. Your primary goal is to help users identify plant health issues and find solutions. You have several tools at your disposal:\n" \
|
| 29 |
+
"- Use the 'diagnose_plant' tool when the user provides an image.\n" \
|
| 30 |
+
"- After diagnosing, use the 'search_chroma_db' tool to find a remedy in the local knowledge base.\n" \
|
| 31 |
+
"- If the user asks for cloud-based information or structured data, use the 'search_bigquery' tool.\n" \
|
| 32 |
+
"Always prioritize the local knowledge base (ChromaDB) for remedies unless the user specifies otherwise.",
|
| 33 |
+
tools=[diagnosis_tool, chroma_db_tool, bigquery_tool]
|
| 34 |
)
|
| 35 |
|
| 36 |
session_service = InMemorySessionService()
|
|
|
|
| 41 |
"agent": agent,
|
| 42 |
"runner": runner,
|
| 43 |
"diagnosis_tool": diagnosis_tool,
|
| 44 |
+
"remedy_tool": chroma_db_tool, # For compatibility with app.py
|
| 45 |
"session_service": session_service
|
| 46 |
}
|
| 47 |
else:
|
app.py
CHANGED
|
@@ -17,7 +17,7 @@ import json
|
|
| 17 |
import re
|
| 18 |
import requests # Added for authentication
|
| 19 |
|
| 20 |
-
from
|
| 21 |
|
| 22 |
# Suppress potential warnings for a cleaner console
|
| 23 |
warnings.filterwarnings("ignore")
|
|
@@ -232,9 +232,8 @@ def create_field_mode_ui(user_state):
|
|
| 232 |
# Note: For a full implementation, you would pass the user_state to other UIs
|
| 233 |
# and use the farmer_id there as well.
|
| 234 |
|
| 235 |
-
def create_connected_mode_ui():
|
| 236 |
"""Creates the Gradio UI for the online Connected Mode."""
|
| 237 |
-
# ... (This function remains unchanged) ...
|
| 238 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
|
| 239 |
gr.Markdown("# ๐ฝ Aura Mind Glow: Connected Mode ๐ค")
|
| 240 |
gr.Markdown("I am an AI farming assistant. Upload an image and ask for a diagnosis and remedy.")
|
|
@@ -242,10 +241,16 @@ def create_connected_mode_ui():
|
|
| 242 |
chatbot = gr.Chatbot(height=600)
|
| 243 |
msg = gr.MultimodalTextbox(file_types=["image"], label="Ask a question and/or upload an image...")
|
| 244 |
|
| 245 |
-
async def respond(chat_input, history):
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
if not SESSION_SERVICE or not ADK_RUNNER:
|
| 248 |
-
history.append((chat_input
|
| 249 |
yield history, gr.MultimodalTextbox(value=None)
|
| 250 |
return
|
| 251 |
|
|
@@ -254,8 +259,13 @@ def create_connected_mode_ui():
|
|
| 254 |
files = chat_input.get("files", [])
|
| 255 |
text = chat_input.get("text", "")
|
| 256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
if not files:
|
| 258 |
-
history.append(
|
| 259 |
yield history, gr.MultimodalTextbox(value=None)
|
| 260 |
return
|
| 261 |
|
|
@@ -273,7 +283,7 @@ def create_connected_mode_ui():
|
|
| 273 |
|
| 274 |
# Stream the response from the agent
|
| 275 |
bot_message = ""
|
| 276 |
-
history.append([(files[0],), bot_message])
|
| 277 |
|
| 278 |
try:
|
| 279 |
async for event in ADK_RUNNER.run_async(
|
|
@@ -281,20 +291,20 @@ def create_connected_mode_ui():
|
|
| 281 |
):
|
| 282 |
if event.is_llm_response_chunk() and event.content.parts:
|
| 283 |
bot_message += event.content.parts[0].text
|
| 284 |
-
history[-1] = ((files[0],), bot_message)
|
| 285 |
yield history, gr.MultimodalTextbox(value=None)
|
| 286 |
elif event.is_final_response() and event.content.parts:
|
| 287 |
bot_message = event.content.parts[0].text
|
| 288 |
-
history[-1] = ((files[0],), bot_message)
|
| 289 |
yield history, gr.MultimodalTextbox(value=None)
|
| 290 |
|
| 291 |
except Exception as e:
|
| 292 |
print(f"Error during agent execution: {e}")
|
| 293 |
-
history[-1] = ((files[0],), f"An error occurred: {e}")
|
| 294 |
yield history, gr.MultimodalTextbox(value=None)
|
| 295 |
|
| 296 |
|
| 297 |
-
msg.submit(respond, [msg, chatbot], [chatbot, msg])
|
| 298 |
|
| 299 |
return demo
|
| 300 |
|
|
@@ -518,7 +528,7 @@ if __name__ == "__main__":
|
|
| 518 |
tab_titles.append("Field Mode")
|
| 519 |
|
| 520 |
if check_internet_connection():
|
| 521 |
-
if ADK_RUNNER: interface_list.append(create_connected_mode_ui()); tab_titles.append("Connected Mode")
|
| 522 |
if STORY_LLM: interface_list.append(create_story_mode_ui()); tab_titles.append("Farmer's Story")
|
| 523 |
interface_list.append(create_document_analysis_ui()); tab_titles.append("Doc Analysis")
|
| 524 |
interface_list.append(create_settings_ui()); tab_titles.append("Settings")
|
|
|
|
| 17 |
import re
|
| 18 |
import requests # Added for authentication
|
| 19 |
|
| 20 |
+
from bigquery_search import search_bigquery_for_remedy
|
| 21 |
|
| 22 |
# Suppress potential warnings for a cleaner console
|
| 23 |
warnings.filterwarnings("ignore")
|
|
|
|
| 232 |
# Note: For a full implementation, you would pass the user_state to other UIs
|
| 233 |
# and use the farmer_id there as well.
|
| 234 |
|
| 235 |
+
def create_connected_mode_ui(user_state):
|
| 236 |
"""Creates the Gradio UI for the online Connected Mode."""
|
|
|
|
| 237 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
|
| 238 |
gr.Markdown("# ๐ฝ Aura Mind Glow: Connected Mode ๐ค")
|
| 239 |
gr.Markdown("I am an AI farming assistant. Upload an image and ask for a diagnosis and remedy.")
|
|
|
|
| 241 |
chatbot = gr.Chatbot(height=600)
|
| 242 |
msg = gr.MultimodalTextbox(file_types=["image"], label="Ask a question and/or upload an image...")
|
| 243 |
|
| 244 |
+
async def respond(chat_input, history, user_state):
|
| 245 |
+
if not user_state or not user_state.get("uid"):
|
| 246 |
+
history.append((chat_input.get("text", ""), "Authentication error. Please log out and log in again."))
|
| 247 |
+
yield history, gr.MultimodalTextbox(value=None)
|
| 248 |
+
return
|
| 249 |
+
|
| 250 |
+
user_id = user_state["uid"]
|
| 251 |
+
|
| 252 |
if not SESSION_SERVICE or not ADK_RUNNER:
|
| 253 |
+
history.append((chat_input.get("text", ""), "Connected mode is not available. Check logs."))
|
| 254 |
yield history, gr.MultimodalTextbox(value=None)
|
| 255 |
return
|
| 256 |
|
|
|
|
| 259 |
files = chat_input.get("files", [])
|
| 260 |
text = chat_input.get("text", "")
|
| 261 |
|
| 262 |
+
if not files and not text:
|
| 263 |
+
# If there is no input, do nothing
|
| 264 |
+
yield history, gr.MultimodalTextbox(value=None)
|
| 265 |
+
return
|
| 266 |
+
|
| 267 |
if not files:
|
| 268 |
+
history.append((text, "Please upload an image for diagnosis."))
|
| 269 |
yield history, gr.MultimodalTextbox(value=None)
|
| 270 |
return
|
| 271 |
|
|
|
|
| 283 |
|
| 284 |
# Stream the response from the agent
|
| 285 |
bot_message = ""
|
| 286 |
+
history.append([(files[0], text), bot_message])
|
| 287 |
|
| 288 |
try:
|
| 289 |
async for event in ADK_RUNNER.run_async(
|
|
|
|
| 291 |
):
|
| 292 |
if event.is_llm_response_chunk() and event.content.parts:
|
| 293 |
bot_message += event.content.parts[0].text
|
| 294 |
+
history[-1] = (((files[0], text), bot_message))
|
| 295 |
yield history, gr.MultimodalTextbox(value=None)
|
| 296 |
elif event.is_final_response() and event.content.parts:
|
| 297 |
bot_message = event.content.parts[0].text
|
| 298 |
+
history[-1] = (((files[0], text), bot_message))
|
| 299 |
yield history, gr.MultimodalTextbox(value=None)
|
| 300 |
|
| 301 |
except Exception as e:
|
| 302 |
print(f"Error during agent execution: {e}")
|
| 303 |
+
history[-1] = (((files[0], text), f"An error occurred: {e}"))
|
| 304 |
yield history, gr.MultimodalTextbox(value=None)
|
| 305 |
|
| 306 |
|
| 307 |
+
msg.submit(respond, [msg, chatbot, user_state], [chatbot, msg])
|
| 308 |
|
| 309 |
return demo
|
| 310 |
|
|
|
|
| 528 |
tab_titles.append("Field Mode")
|
| 529 |
|
| 530 |
if check_internet_connection():
|
| 531 |
+
if ADK_RUNNER: interface_list.append(create_connected_mode_ui(user_state)); tab_titles.append("Connected Mode")
|
| 532 |
if STORY_LLM: interface_list.append(create_story_mode_ui()); tab_titles.append("Farmer's Story")
|
| 533 |
interface_list.append(create_document_analysis_ui()); tab_titles.append("Doc Analysis")
|
| 534 |
interface_list.append(create_settings_ui()); tab_titles.append("Settings")
|
bigquery_search.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from google.cloud import bigquery
|
| 2 |
+
|
| 3 |
+
def search_bigquery_for_remedy(search_query: str) -> str:
|
| 4 |
+
try:
|
| 5 |
+
client = bigquery.Client(project="gem-creation")
|
| 6 |
+
query = """
|
| 7 |
+
SELECT remedy_description FROM `gem-creation.maize_remedies.remedies`
|
| 8 |
+
WHERE SEARCH(remedy_description, @query)
|
| 9 |
+
"""
|
| 10 |
+
job_config = bigquery.QueryJobConfig(
|
| 11 |
+
query_parameters=[bigquery.ScalarQueryParameter("query", "STRING", search_query)]
|
| 12 |
+
)
|
| 13 |
+
query_job = client.query(query, job_config=job_config)
|
| 14 |
+
results = list(query_job)
|
| 15 |
+
return results[0].remedy_description if results else "No remedy found."
|
| 16 |
+
except Exception as e:
|
| 17 |
+
return f"Error querying BigQuery: {e}"
|
miscellaneous/instructions.md
CHANGED
|
@@ -3130,3 +3130,651 @@ async def call_agent_async(query):
|
|
| 3130 |
# Note: In Colab, you can directly use 'await' at the top level.
|
| 3131 |
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
|
| 3132 |
await call_agent_async("what's the latest news on AI Agents?")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3130 |
# Note: In Colab, you can directly use 'await' at the top level.
|
| 3131 |
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
|
| 3132 |
await call_agent_async("what's the latest news on AI Agents?")
|
| 3133 |
+
|
| 3134 |
+
|
| 3135 |
+
ADK_Learning_tools.ipynb
|
| 3136 |
+
ADK_Learning_tools.ipynb_
|
| 3137 |
+
๐ Welcome to Your ADK Adventure - Tools & Memory! ๐
|
| 3138 |
+
Welcome, Agent Architect! This notebook is your guide to giving your AI agents two essential superpowers: custom tools and conversational memory.
|
| 3139 |
+
|
| 3140 |
+
By the end of this adventure, you will be able to:
|
| 3141 |
+
|
| 3142 |
+
Build a Foundational Agent: Create a simple but effective AI agent from scratch using the Google Agent Development Kit (ADK).
|
| 3143 |
+
|
| 3144 |
+
Grant New Skills with Custom Tools: Teach an agent to perform new tasks by connecting it to external APIs, like a real-time weather service.
|
| 3145 |
+
|
| 3146 |
+
Create a Team of Agents: Assemble a multi-agent system where a primary agent can delegate specialized tasks to other agents.
|
| 3147 |
+
|
| 3148 |
+
Master Conversational Memory: Understand the critical role of Sessions in enabling agents to remember previous interactions, handle feedback, and carry on a coherent conversation.
|
| 3149 |
+
|
| 3150 |
+
Let's get this adventure started!
|
| 3151 |
+
|
| 3152 |
+
Author
|
| 3153 |
+
HI, I'm Qingyue (Annie) Wang, a developer advocate and AI engineer at Google, passionate about helping developers build with AI and cloud technologies :)
|
| 3154 |
+
|
| 3155 |
+
If you have questions with this notebook, contact me on LinkedIn , X or email anniewangtech0510@Gmail.com
|
| 3156 |
+
|
| 3157 |
+
(\__/)
|
| 3158 |
+
(โขใ
โข)
|
| 3159 |
+
/ใฅ ๐ Enjoy learning AI Agents :)
|
| 3160 |
+
๐ ๐ Important Prerequisite: Setup Your Environment! ๐ ๐
|
| 3161 |
+
๐ Get Your API Key HERE: https://codelabs.developers.google.com/onramp/instructions#1
|
| 3162 |
+
|
| 3163 |
+
โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ โฌ๏ธ
|
| 3164 |
+
/\_/\ /\_/\ /\_/\ /\_/\ /\_/\
|
| 3165 |
+
( ^_^ ) ( -.- ) ( >_< ) ( =^.^= ) ( o_o )
|
| 3166 |
+
Part 0: Setup & Authentication ๐
|
| 3167 |
+
First things first, let's get all our tools ready. This step installs the necessary libraries and securely configures your Google API key so your agents can access the power of Gemini.
|
| 3168 |
+
|
| 3169 |
+
|
| 3170 |
+
[ ]
|
| 3171 |
+
!pip install google-adk google-generativeai -q
|
| 3172 |
+
|
| 3173 |
+
# --- Import all necessary libraries for our entire adventure ---
|
| 3174 |
+
import os
|
| 3175 |
+
import re
|
| 3176 |
+
import asyncio
|
| 3177 |
+
from IPython.display import display, Markdown
|
| 3178 |
+
import google.generativeai as genai
|
| 3179 |
+
from google.adk.agents import Agent
|
| 3180 |
+
from google.adk.tools import google_search
|
| 3181 |
+
from google.adk.runners import Runner
|
| 3182 |
+
from google.adk.sessions import InMemorySessionService, Session
|
| 3183 |
+
from google.genai.types import Content, Part
|
| 3184 |
+
from getpass import getpass
|
| 3185 |
+
|
| 3186 |
+
print("โ
All libraries are ready to go!")
|
| 3187 |
+
|
| 3188 |
+
[ ]
|
| 3189 |
+
# --- Securely Configure Your API Key ---
|
| 3190 |
+
|
| 3191 |
+
# Prompt the user for their API key securely
|
| 3192 |
+
api_key = getpass('Enter your Google API Key: ')
|
| 3193 |
+
|
| 3194 |
+
# Get Your API Key HERE ๐ https://codelabs.developers.google.com/onramp/instructions#0
|
| 3195 |
+
# Configure the generative AI library with the provided key
|
| 3196 |
+
genai.configure(api_key=api_key)
|
| 3197 |
+
|
| 3198 |
+
# Set the API key as an environment variable for ADK to use
|
| 3199 |
+
os.environ['GOOGLE_API_KEY'] = api_key
|
| 3200 |
+
|
| 3201 |
+
print("โ
API Key configured successfully! Let the fun begin.")
|
| 3202 |
+
Part 1: Your First Agent - The Day Trip Genie ๐ง
|
| 3203 |
+
Meet your first creation! The day_trip_agent is a simple but powerful assistant. We're making it a little smarter by teaching it to understand budget constraints.
|
| 3204 |
+
|
| 3205 |
+
Agent: The brain of the operation, defined by its instructions, tools, and the AI model it uses.
|
| 3206 |
+
Session: The conversation history. For this simple agent, it's just a container for a single request-response.
|
| 3207 |
+
Runner: The engine that connects the Agent and the Session to process your request and get a response.
|
| 3208 |
+
+--------------------------------------------------+
|
| 3209 |
+
| Spontaneous Day Trip Agent ๐ค |
|
| 3210 |
+
|--------------------------------------------------|
|
| 3211 |
+
| Model: gemini-2.5-flash |
|
| 3212 |
+
| Description: |
|
| 3213 |
+
| Generates full-day trip itineraries based on |
|
| 3214 |
+
| mood, interests, and budget |
|
| 3215 |
+
|--------------------------------------------------|
|
| 3216 |
+
| ๐ง Tools: |
|
| 3217 |
+
| - Google Search |
|
| 3218 |
+
|--------------------------------------------------|
|
| 3219 |
+
| ๐ง Capabilities: |
|
| 3220 |
+
| - Budget Awareness (cheap / splurge) |
|
| 3221 |
+
| - Mood Matching (adventurous, relaxing, etc.) |
|
| 3222 |
+
| - Real-Time Info (hours, events) |
|
| 3223 |
+
| - Morning / Afternoon / Evening plan |
|
| 3224 |
+
+--------------------------------------------------+
|
| 3225 |
+
|
| 3226 |
+
โฒ
|
| 3227 |
+
|
|
| 3228 |
+
+------------------+
|
| 3229 |
+
| User Input |
|
| 3230 |
+
|------------------|
|
| 3231 |
+
| Mood |
|
| 3232 |
+
| Interests |
|
| 3233 |
+
| Budget |
|
| 3234 |
+
+------------------+
|
| 3235 |
+
|
| 3236 |
+
|
|
| 3237 |
+
โผ
|
| 3238 |
+
|
| 3239 |
+
+--------------------------------------------------+
|
| 3240 |
+
| Output: Markdown Itinerary |
|
| 3241 |
+
|--------------------------------------------------|
|
| 3242 |
+
| - Time blocks (Morning / Afternoon / Evening) |
|
| 3243 |
+
| - Venue names with links and hours |
|
| 3244 |
+
| - Budget-matching activities |
|
| 3245 |
+
+--------------------------------------------------+
|
| 3246 |
+
|
| 3247 |
+
[ ]
|
| 3248 |
+
# --- Agent Definition ---
|
| 3249 |
+
|
| 3250 |
+
def create_day_trip_agent():
|
| 3251 |
+
"""Create the Spontaneous Day Trip Generator agent"""
|
| 3252 |
+
return Agent(
|
| 3253 |
+
name="day_trip_agent",
|
| 3254 |
+
model="gemini-2.5-flash",
|
| 3255 |
+
description="Agent specialized in generating spontaneous full-day itineraries based on mood, interests, and budget.",
|
| 3256 |
+
instruction="""
|
| 3257 |
+
You are the "Spontaneous Day Trip" Generator ๐ - a specialized AI assistant that creates engaging full-day itineraries.
|
| 3258 |
+
|
| 3259 |
+
Your Mission:
|
| 3260 |
+
Transform a simple mood or interest into a complete day-trip adventure with real-time details, while respecting a budget.
|
| 3261 |
+
|
| 3262 |
+
Guidelines:
|
| 3263 |
+
1. **Budget-Aware**: Pay close attention to budget hints like 'cheap', 'affordable', or 'splurge'. Use Google Search to find activities (free museums, parks, paid attractions) that match the user's budget.
|
| 3264 |
+
2. **Full-Day Structure**: Create morning, afternoon, and evening activities.
|
| 3265 |
+
3. **Real-Time Focus**: Search for current operating hours and special events.
|
| 3266 |
+
4. **Mood Matching**: Align suggestions with the requested mood (adventurous, relaxing, artsy, etc.).
|
| 3267 |
+
|
| 3268 |
+
RETURN itinerary in MARKDOWN FORMAT with clear time blocks and specific venue names.
|
| 3269 |
+
""",
|
| 3270 |
+
tools=[google_search]
|
| 3271 |
+
)
|
| 3272 |
+
|
| 3273 |
+
day_trip_agent = create_day_trip_agent()
|
| 3274 |
+
print(f"๐ง Agent '{day_trip_agent.name}' is created and ready for adventure!")
|
| 3275 |
+
|
| 3276 |
+
[ ]
|
| 3277 |
+
# --- A Helper Function to Run Our Agents ---
|
| 3278 |
+
# We'll use this function throughout the notebook to make running queries easy.
|
| 3279 |
+
|
| 3280 |
+
async def run_agent_query(agent: Agent, query: str, session: Session, user_id: str, is_router: bool = False):
|
| 3281 |
+
"""Initializes a runner and executes a query for a given agent and session."""
|
| 3282 |
+
print(f"\n๐ Running query for agent: '{agent.name}' in session: '{session.id}'...")
|
| 3283 |
+
|
| 3284 |
+
runner = Runner(
|
| 3285 |
+
agent=agent,
|
| 3286 |
+
session_service=session_service,
|
| 3287 |
+
app_name=agent.name
|
| 3288 |
+
)
|
| 3289 |
+
|
| 3290 |
+
final_response = ""
|
| 3291 |
+
try:
|
| 3292 |
+
async for event in runner.run_async(
|
| 3293 |
+
user_id=user_id,
|
| 3294 |
+
session_id=session.id,
|
| 3295 |
+
new_message=Content(parts=[Part(text=query)], role="user")
|
| 3296 |
+
):
|
| 3297 |
+
if not is_router:
|
| 3298 |
+
# Let's see what the agent is thinking!
|
| 3299 |
+
print(f"EVENT: {event}")
|
| 3300 |
+
if event.is_final_response():
|
| 3301 |
+
final_response = event.content.parts[0].text
|
| 3302 |
+
except Exception as e:
|
| 3303 |
+
final_response = f"An error occurred: {e}"
|
| 3304 |
+
|
| 3305 |
+
if not is_router:
|
| 3306 |
+
print("\n" + "-"*50)
|
| 3307 |
+
print("โ
Final Response:")
|
| 3308 |
+
display(Markdown(final_response))
|
| 3309 |
+
print("-"*50 + "\n")
|
| 3310 |
+
|
| 3311 |
+
return final_response
|
| 3312 |
+
|
| 3313 |
+
# --- Initialize our Session Service ---
|
| 3314 |
+
# This one service will manage all the different sessions in our notebook.
|
| 3315 |
+
session_service = InMemorySessionService()
|
| 3316 |
+
my_user_id = "adk_adventurer_001"
|
| 3317 |
+
|
| 3318 |
+
[ ]
|
| 3319 |
+
# --- Let's test the Day Trip Genie! ---
|
| 3320 |
+
|
| 3321 |
+
async def run_day_trip_genie():
|
| 3322 |
+
# Create a new, single-use session for this query
|
| 3323 |
+
day_trip_session = await session_service.create_session(
|
| 3324 |
+
app_name=day_trip_agent.name,
|
| 3325 |
+
user_id=my_user_id
|
| 3326 |
+
)
|
| 3327 |
+
|
| 3328 |
+
# Note the new budget constraint in the query!
|
| 3329 |
+
query = "Plan a relaxing and artsy day trip near Sunnyvale, CA. Keep it affordable!"
|
| 3330 |
+
print(f"๐ฃ๏ธ User Query: '{query}'")
|
| 3331 |
+
|
| 3332 |
+
await run_agent_query(day_trip_agent, query, day_trip_session, my_user_id)
|
| 3333 |
+
|
| 3334 |
+
await run_day_trip_genie()
|
| 3335 |
+
Part 2: Supercharging Agents with Custom Tools ๐ ๏ธ
|
| 3336 |
+
So far, we've used the powerful built-in GoogleSearch tool. But the true power of agents comes from connecting them to your own logic and data sources.
|
| 3337 |
+
|
| 3338 |
+
This is where custom tools come in. Let's explore three patterns for giving your agent new skills, using real-world, practical examples.
|
| 3339 |
+
|
| 3340 |
+
2.1 The Simple FunctionTool: Calling a Real-Time Weather API
|
| 3341 |
+
The most direct way to create a tool is by writing a Python function. This is perfect for synchronous tasks like fetching data from an API.
|
| 3342 |
+
|
| 3343 |
+
Key Concept: The function's docstring is critical. The ADK uses it as the tool's official description, which the LLM reads to understand its purpose, parameters, and when to use it.
|
| 3344 |
+
|
| 3345 |
+
In this example, we'll create a tool that calls the free, public U.S. National Weather Service API to get a real-time forecast. No API key needed!
|
| 3346 |
+
|
| 3347 |
+
|
| 3348 |
+
[ ]
|
| 3349 |
+
# --- Tool Definition: A function that calls a live public API ---
|
| 3350 |
+
import requests
|
| 3351 |
+
import json
|
| 3352 |
+
|
| 3353 |
+
# A simple lookup to avoid needing a separate geocoding API for this example
|
| 3354 |
+
LOCATION_COORDINATES = {
|
| 3355 |
+
"sunnyvale": "37.3688,-122.0363",
|
| 3356 |
+
"san francisco": "37.7749,-122.4194",
|
| 3357 |
+
"lake tahoe": "39.0968,-120.0324"
|
| 3358 |
+
}
|
| 3359 |
+
|
| 3360 |
+
def get_live_weather_forecast(location: str) -> dict:
|
| 3361 |
+
"""Gets the current, real-time weather forecast for a specified location in the US.
|
| 3362 |
+
|
| 3363 |
+
Args:
|
| 3364 |
+
location: The city name, e.g., "San Francisco".
|
| 3365 |
+
|
| 3366 |
+
Returns:
|
| 3367 |
+
A dictionary containing the temperature and a detailed forecast.
|
| 3368 |
+
"""
|
| 3369 |
+
print(f"๐ ๏ธ TOOL CALLED: get_live_weather_forecast(location='{location}')")
|
| 3370 |
+
|
| 3371 |
+
# Find coordinates for the location
|
| 3372 |
+
normalized_location = location.lower()
|
| 3373 |
+
coords_str = None
|
| 3374 |
+
for key, val in LOCATION_COORDINATES.items():
|
| 3375 |
+
if key in normalized_location:
|
| 3376 |
+
coords_str = val
|
| 3377 |
+
break
|
| 3378 |
+
if not coords_str:
|
| 3379 |
+
return {"status": "error", "message": f"I don't have coordinates for {location}."}
|
| 3380 |
+
|
| 3381 |
+
try:
|
| 3382 |
+
# NWS API requires 2 steps: 1. Get the forecast URL from the coordinates.
|
| 3383 |
+
points_url = f"https://api.weather.gov/points/{coords_str}"
|
| 3384 |
+
headers = {"User-Agent": "ADK Example Notebook"}
|
| 3385 |
+
points_response = requests.get(points_url, headers=headers)
|
| 3386 |
+
points_response.raise_for_status() # Raise an exception for bad status codes
|
| 3387 |
+
forecast_url = points_response.json()['properties']['forecast']
|
| 3388 |
+
|
| 3389 |
+
# 2. Get the actual forecast from the URL.
|
| 3390 |
+
forecast_response = requests.get(forecast_url, headers=headers)
|
| 3391 |
+
forecast_response.raise_for_status()
|
| 3392 |
+
|
| 3393 |
+
# Extract the relevant forecast details
|
| 3394 |
+
current_period = forecast_response.json()['properties']['periods'][0]
|
| 3395 |
+
return {
|
| 3396 |
+
"status": "success",
|
| 3397 |
+
"temperature": f"{current_period['temperature']}ยฐ{current_period['temperatureUnit']}",
|
| 3398 |
+
"forecast": current_period['detailedForecast']
|
| 3399 |
+
}
|
| 3400 |
+
except requests.exceptions.RequestException as e:
|
| 3401 |
+
return {"status": "error", "message": f"API request failed: {e}"}
|
| 3402 |
+
|
| 3403 |
+
# --- Agent Definition: An agent that USES the new tool ---
|
| 3404 |
+
|
| 3405 |
+
weather_agent = Agent(
|
| 3406 |
+
name="weather_aware_planner",
|
| 3407 |
+
model="gemini-2.5-flash",
|
| 3408 |
+
description="A trip planner that checks the real-time weather before making suggestions.",
|
| 3409 |
+
instruction="You are a cautious trip planner. Before suggesting any outdoor activities, you MUST use the `get_live_weather_forecast` tool to check conditions. Incorporate the live weather details into your recommendation.",
|
| 3410 |
+
tools=[get_live_weather_forecast]
|
| 3411 |
+
)
|
| 3412 |
+
|
| 3413 |
+
print(f"๐ฆ๏ธ Agent '{weather_agent.name}' is created and can now call a live weather API!")
|
| 3414 |
+
|
| 3415 |
+
[ ]
|
| 3416 |
+
# --- Let's test the Weather-Aware Planner ---
|
| 3417 |
+
|
| 3418 |
+
async def run_weather_planner_test():
|
| 3419 |
+
weather_session = await session_service.create_session(app_name=weather_agent.name, user_id=my_user_id)
|
| 3420 |
+
query = "I want to go hiking near Lake Tahoe, what's the weather like?"
|
| 3421 |
+
print(f"๐ฃ๏ธ User Query: '{query}'")
|
| 3422 |
+
await run_agent_query(weather_agent, query, weather_session, my_user_id)
|
| 3423 |
+
|
| 3424 |
+
await run_weather_planner_test()
|
| 3425 |
+
2.2 The Agent-as-a-Tool: Consulting a Specialist ๐งโ๐ณ
|
| 3426 |
+
Why build one agent that does everything when you can build a team of specialist agents? The Agent-as-a-Tool pattern allows one agent to delegate a task to another agent.
|
| 3427 |
+
|
| 3428 |
+
Key Concept: This is different from a sub-agent. When Agent A calls Agent B as a tool, Agent B's response is passed back to Agent A. Agent A then uses that information to form its own final response to the user. It's a powerful way to compose complex behaviors from simpler, focused, and reusable agents.
|
| 3429 |
+
|
| 3430 |
+
How It Works
|
| 3431 |
+
Our top-level agent, the trip_data_concierge_agent, acts as the Orchestrator. It has two tools at its disposal:
|
| 3432 |
+
|
| 3433 |
+
call_db_agent: A function that internally calls our db_agent to fetch raw data.
|
| 3434 |
+
call_concierge_agent: A function that calls the concierge_agent.
|
| 3435 |
+
The concierge_agent itself has a tool: the food_critic_agent.
|
| 3436 |
+
|
| 3437 |
+
The flow for a complex query is:
|
| 3438 |
+
|
| 3439 |
+
User asks the trip_data_concierge_agent for a hotel and a nearby restaurant.
|
| 3440 |
+
The Orchestrator first calls call_db_agent to get hotel data.
|
| 3441 |
+
The data is saved in tool_context.state.
|
| 3442 |
+
The Orchestrator then calls call_concierge_agent, which retrieves the hotel data from the context.
|
| 3443 |
+
The concierge_agent receives the request and decides it needs to use its own tool, the food_critic_agent.
|
| 3444 |
+
The food_critic_agent provides a witty recommendation.
|
| 3445 |
+
The concierge_agent gets the critic's response and politely formats it.
|
| 3446 |
+
This final, polished response is returned to the Orchestrator, which presents it to the user.
|
| 3447 |
+
|
| 3448 |
+
[ ]
|
| 3449 |
+
|
| 3450 |
+
Start coding or generate with AI.
|
| 3451 |
+
+-----------------------------------------------------------+
|
| 3452 |
+
| ๐งญ Trip Data Concierge Agent |
|
| 3453 |
+
|-----------------------------------------------------------|
|
| 3454 |
+
| Model: gemini-2.5-flash |
|
| 3455 |
+
| Description: |
|
| 3456 |
+
| Orchestrates database query and travel recommendation |
|
| 3457 |
+
|-----------------------------------------------------------|
|
| 3458 |
+
| ๐ง Tools: |
|
| 3459 |
+
| 1. call_db_agent |
|
| 3460 |
+
| 2. call_concierge_agent |
|
| 3461 |
+
+-----------------------------------------------------------+
|
| 3462 |
+
/ \
|
| 3463 |
+
/ \
|
| 3464 |
+
โผ โผ
|
| 3465 |
+
+-------------------------------------------+ +---------------------------------------------+
|
| 3466 |
+
| ๐ง Tool: call_db_agent | | ๐ง Tool: call_concierge_agent |
|
| 3467 |
+
|-------------------------------------------| |---------------------------------------------|
|
| 3468 |
+
| Calls: db_agent | | Calls: concierge_agent |
|
| 3469 |
+
| | | Uses data from db_agent for recommendations |
|
| 3470 |
+
+-------------------------------------------+ +---------------------------------------------+
|
| 3471 |
+
| |
|
| 3472 |
+
โผ โผ
|
| 3473 |
+
+--------------------------------------------+ +------------------------------------------------+
|
| 3474 |
+
| ๐ฆ db_agent | | ๐คต concierge_agent |
|
| 3475 |
+
|--------------------------------------------| |------------------------------------------------|
|
| 3476 |
+
| Model: gemini-2.5-flash | | Model: gemini-2.5-flash |
|
| 3477 |
+
| Role: Return mock JSON hotel data | | Role: Hotel staff that handles user Q&A |
|
| 3478 |
+
+--------------------------------------------+ | Tools: |
|
| 3479 |
+
| - food_critic_agent |
|
| 3480 |
+
+------------------------------------------------+
|
| 3481 |
+
|
|
| 3482 |
+
โผ
|
| 3483 |
+
+------------------------------------------------+
|
| 3484 |
+
| ๐ฝ๏ธ food_critic_agent |
|
| 3485 |
+
|------------------------------------------------|
|
| 3486 |
+
| Model: gemini-2.5-flash |
|
| 3487 |
+
| Role: Gives a witty restaurant recommendation |
|
| 3488 |
+
+------------------------------------------------+
|
| 3489 |
+
|
| 3490 |
+
[ ]
|
| 3491 |
+
import asyncio
|
| 3492 |
+
from google.adk.tools import ToolContext
|
| 3493 |
+
from google.adk.tools.agent_tool import AgentTool
|
| 3494 |
+
|
| 3495 |
+
# Assume 'db_agent' is a pre-defined NL2SQL Agent
|
| 3496 |
+
# For this example, we'll create placeholder agents.
|
| 3497 |
+
|
| 3498 |
+
db_agent = Agent(
|
| 3499 |
+
name="db_agent",
|
| 3500 |
+
model="gemini-2.5-flash",
|
| 3501 |
+
instruction="You are a database agent. When asked for data, return this mock JSON object: {'status': 'success', 'data': [{'name': 'The Grand Hotel', 'rating': 5, 'reviews': 450}, {'name': 'Seaside Inn', 'rating': 4, 'reviews': 620}]}")
|
| 3502 |
+
|
| 3503 |
+
# --- 1. Define the Specialist Agents ---
|
| 3504 |
+
|
| 3505 |
+
# The Food Critic remains the deepest specialist
|
| 3506 |
+
food_critic_agent = Agent(
|
| 3507 |
+
name="food_critic_agent",
|
| 3508 |
+
model="gemini-2.5-flash",
|
| 3509 |
+
instruction="You are a snobby but brilliant food critic. You ONLY respond with a single, witty restaurant suggestion near the provided location.",
|
| 3510 |
+
)
|
| 3511 |
+
|
| 3512 |
+
# The Concierge knows how to use the Food Critic
|
| 3513 |
+
concierge_agent = Agent(
|
| 3514 |
+
name="concierge_agent",
|
| 3515 |
+
model="gemini-2.5-flash",
|
| 3516 |
+
instruction="You are a five-star hotel concierge. If the user asks for a restaurant recommendation, you MUST use the `food_critic_agent` tool. Present the opinion to the user politely.",
|
| 3517 |
+
tools=[AgentTool(agent=food_critic_agent)]
|
| 3518 |
+
)
|
| 3519 |
+
|
| 3520 |
+
|
| 3521 |
+
# --- 2. Define the Tools for the Orchestrator ---
|
| 3522 |
+
|
| 3523 |
+
async def call_db_agent(
|
| 3524 |
+
question: str,
|
| 3525 |
+
tool_context: ToolContext,
|
| 3526 |
+
):
|
| 3527 |
+
"""
|
| 3528 |
+
Use this tool FIRST to connect to the database and retrieve a list of places, like hotels or landmarks.
|
| 3529 |
+
"""
|
| 3530 |
+
print("--- TOOL CALL: call_db_agent ---")
|
| 3531 |
+
agent_tool = AgentTool(agent=db_agent)
|
| 3532 |
+
db_agent_output = await agent_tool.run_async(
|
| 3533 |
+
args={"request": question}, tool_context=tool_context
|
| 3534 |
+
)
|
| 3535 |
+
# Store the retrieved data in the context's state
|
| 3536 |
+
tool_context.state["retrieved_data"] = db_agent_output
|
| 3537 |
+
return db_agent_output
|
| 3538 |
+
|
| 3539 |
+
|
| 3540 |
+
async def call_concierge_agent(
|
| 3541 |
+
question: str,
|
| 3542 |
+
tool_context: ToolContext,
|
| 3543 |
+
):
|
| 3544 |
+
"""
|
| 3545 |
+
After getting data with call_db_agent, use this tool to get travel advice, opinions, or recommendations.
|
| 3546 |
+
"""
|
| 3547 |
+
print("--- TOOL CALL: call_concierge_agent ---")
|
| 3548 |
+
# Retrieve the data fetched by the previous tool
|
| 3549 |
+
input_data = tool_context.state.get("retrieved_data", "No data found.")
|
| 3550 |
+
|
| 3551 |
+
# Formulate a new prompt for the concierge, giving it the data context
|
| 3552 |
+
question_with_data = f"""
|
| 3553 |
+
Context: The database returned the following data: {input_data}
|
| 3554 |
+
|
| 3555 |
+
User's Request: {question}
|
| 3556 |
+
"""
|
| 3557 |
+
|
| 3558 |
+
agent_tool = AgentTool(agent=concierge_agent)
|
| 3559 |
+
concierge_output = await agent_tool.run_async(
|
| 3560 |
+
args={"request": question_with_data}, tool_context=tool_context
|
| 3561 |
+
)
|
| 3562 |
+
return concierge_output
|
| 3563 |
+
|
| 3564 |
+
|
| 3565 |
+
# --- 3. Define the Top-Level Orchestrator Agent ---
|
| 3566 |
+
|
| 3567 |
+
trip_data_concierge_agent = Agent(
|
| 3568 |
+
name="trip_data_concierge",
|
| 3569 |
+
model="gemini-2.5-flash",
|
| 3570 |
+
description="Top-level agent that queries a database for travel data, then calls a concierge agent for recommendations.",
|
| 3571 |
+
tools=[call_db_agent, call_concierge_agent],
|
| 3572 |
+
instruction="""
|
| 3573 |
+
You are a master travel planner who uses data to make recommendations.
|
| 3574 |
+
|
| 3575 |
+
1. **ALWAYS start with the `call_db_agent` tool** to fetch a list of places (like hotels) that match the user's criteria.
|
| 3576 |
+
|
| 3577 |
+
2. After you have the data, **use the `call_concierge_agent` tool** to answer any follow-up questions for recommendations, opinions, or advice related to the data you just found.
|
| 3578 |
+
""",
|
| 3579 |
+
)
|
| 3580 |
+
|
| 3581 |
+
print(f"โ
Orchestrator Agent '{trip_data_concierge_agent.name}' is defined and ready.")
|
| 3582 |
+
|
| 3583 |
+
[ ]
|
| 3584 |
+
# --- Let's test the Trip Data Concierge Agent ---
|
| 3585 |
+
|
| 3586 |
+
async def run_trip_data_concierge():
|
| 3587 |
+
"""
|
| 3588 |
+
Sets up a session and runs a query against the top-level
|
| 3589 |
+
trip_data_concierge_agent.
|
| 3590 |
+
"""
|
| 3591 |
+
# Create a new, single-use session for this query
|
| 3592 |
+
concierge_session = await session_service.create_session(
|
| 3593 |
+
app_name=trip_data_concierge_agent.name,
|
| 3594 |
+
user_id=my_user_id
|
| 3595 |
+
)
|
| 3596 |
+
|
| 3597 |
+
# This query is specifically designed to trigger the full two-step process:
|
| 3598 |
+
# 1. Get data from the db_agent.
|
| 3599 |
+
# 2. Get a recommendation from the concierge_agent based on that data.
|
| 3600 |
+
query = "Find the top-rated hotels in San Francisco from the database, then suggest a dinner spot near the one with the most reviews."
|
| 3601 |
+
print(f"๐ฃ๏ธ User Query: '{query}'")
|
| 3602 |
+
|
| 3603 |
+
# We call our existing helper function with the top-level orchestrator agent
|
| 3604 |
+
await run_agent_query(trip_data_concierge_agent, query, concierge_session, my_user_id)
|
| 3605 |
+
|
| 3606 |
+
# Run the test
|
| 3607 |
+
await run_trip_data_concierge()
|
| 3608 |
+
Part 3: Agent with a Memory - The Adaptive Planner ๐บ๏ธ
|
| 3609 |
+
Now, let's see an agent that not only remembers but also adapts. We'll challenge the multi_day_trip_agent to re-plan part of its itinerary based on our feedback. This is a much more realistic test of conversational AI.
|
| 3610 |
+
|
| 3611 |
+
+-----------------------------------------------------+
|
| 3612 |
+
| Adaptive Multi-Day Trip Agent ๐บ๏ธ |
|
| 3613 |
+
|-----------------------------------------------------|
|
| 3614 |
+
| Model: gemini-2.5-flash |
|
| 3615 |
+
| Description: |
|
| 3616 |
+
| Builds multi-day travel itineraries step-by-step, |
|
| 3617 |
+
| remembers previous days, adapts to feedback |
|
| 3618 |
+
|-----------------------------------------------------|
|
| 3619 |
+
| ๐ง Tools: |
|
| 3620 |
+
| - Google Search |
|
| 3621 |
+
|-----------------------------------------------------|
|
| 3622 |
+
| ๐ง Capabilities: |
|
| 3623 |
+
| - Memory of past conversation & preferences |
|
| 3624 |
+
| - Progressive planning (1 day at a time) |
|
| 3625 |
+
| - Adapts to user feedback |
|
| 3626 |
+
| - Ensures activity variety across days |
|
| 3627 |
+
+-----------------------------------------------------+
|
| 3628 |
+
|
| 3629 |
+
โฒ
|
| 3630 |
+
|
|
| 3631 |
+
+---------------------------+
|
| 3632 |
+
| User Interaction |
|
| 3633 |
+
|---------------------------|
|
| 3634 |
+
| - Destination |
|
| 3635 |
+
| - Trip duration |
|
| 3636 |
+
| - Interests & feedback |
|
| 3637 |
+
+---------------------------+
|
| 3638 |
+
|
| 3639 |
+
|
|
| 3640 |
+
โผ
|
| 3641 |
+
|
| 3642 |
+
+-----------------------------------------------------+
|
| 3643 |
+
| Day-by-Day Itinerary Generation |
|
| 3644 |
+
|-----------------------------------------------------|
|
| 3645 |
+
| ๐๏ธ Day N Output (Markdown format): |
|
| 3646 |
+
| - Morning / Afternoon / Evening activities |
|
| 3647 |
+
| - Personalized & context-aware |
|
| 3648 |
+
| - Changes accepted, feedback acknowledged |
|
| 3649 |
+
+-----------------------------------------------------+
|
| 3650 |
+
|
| 3651 |
+
|
|
| 3652 |
+
โผ
|
| 3653 |
+
|
| 3654 |
+
+-----------------------------------------------------+
|
| 3655 |
+
| Next Day Planning Triggered ๐ |
|
| 3656 |
+
|-----------------------------------------------------|
|
| 3657 |
+
| - Builds on prior days |
|
| 3658 |
+
| - Avoids repetition |
|
| 3659 |
+
| - Asks user for confirmation before proceeding |
|
| 3660 |
+
+-----------------------------------------------------+
|
| 3661 |
+
|
| 3662 |
+
[ ]
|
| 3663 |
+
# --- Agent Definition: The Adaptive Planner ---
|
| 3664 |
+
|
| 3665 |
+
def create_multi_day_trip_agent():
|
| 3666 |
+
"""Create the Progressive Multi-Day Trip Planner agent"""
|
| 3667 |
+
return Agent(
|
| 3668 |
+
name="multi_day_trip_agent",
|
| 3669 |
+
model="gemini-2.5-flash",
|
| 3670 |
+
description="Agent that progressively plans a multi-day trip, remembering previous days and adapting to user feedback.",
|
| 3671 |
+
instruction="""
|
| 3672 |
+
You are the "Adaptive Trip Planner" ๐บ๏ธ - an AI assistant that builds multi-day travel itineraries step-by-step.
|
| 3673 |
+
|
| 3674 |
+
Your Defining Feature:
|
| 3675 |
+
You have short-term memory. You MUST refer back to our conversation to understand the trip's context, what has already been planned, and the user's preferences. If the user asks for a change, you must adapt the plan while keeping the unchanged parts consistent.
|
| 3676 |
+
|
| 3677 |
+
Your Mission:
|
| 3678 |
+
1. **Initiate**: Start by asking for the destination, trip duration, and interests.
|
| 3679 |
+
2. **Plan Progressively**: Plan ONLY ONE DAY at a time. After presenting a plan, ask for confirmation.
|
| 3680 |
+
3. **Handle Feedback**: If a user dislikes a suggestion (e.g., "I don't like museums"), acknowledge their feedback, and provide a *new, alternative* suggestion for that time slot that still fits the overall theme.
|
| 3681 |
+
4. **Maintain Context**: For each new day, ensure the activities are unique and build logically on the previous days. Do not suggest the same things repeatedly.
|
| 3682 |
+
5. **Final Output**: Return each day's itinerary in MARKDOWN format.
|
| 3683 |
+
""",
|
| 3684 |
+
tools=[google_search]
|
| 3685 |
+
)
|
| 3686 |
+
|
| 3687 |
+
multi_day_agent = create_multi_day_trip_agent()
|
| 3688 |
+
print(f"๐บ๏ธ Agent '{multi_day_agent.name}' is created and ready to plan and adapt!")
|
| 3689 |
+
Scenario 3a: Agent WITH Memory (Using a SINGLE Session) โ
|
| 3690 |
+
First, let's see the correct way to do it. We will use the exact same trip_session object for the entire conversation. Watch how the agent remembers the context from Turn 1 to correctly handle the requests in Turn 2 and 3.
|
| 3691 |
+
|
| 3692 |
+
|
| 3693 |
+
[ ]
|
| 3694 |
+
# --- Scenario 2: Testing Adaptation and Memory ---
|
| 3695 |
+
|
| 3696 |
+
async def run_adaptive_memory_demonstration():
|
| 3697 |
+
print("### ๐ง DEMO 2: AGENT THAT ADAPTS (SAME SESSION) ###")
|
| 3698 |
+
|
| 3699 |
+
# Create ONE session that we will reuse for the whole conversation
|
| 3700 |
+
trip_session = await session_service.create_session(
|
| 3701 |
+
app_name=multi_day_agent.name,
|
| 3702 |
+
user_id=my_user_id
|
| 3703 |
+
)
|
| 3704 |
+
print(f"Created a single session for our trip: {trip_session.id}")
|
| 3705 |
+
|
| 3706 |
+
# --- Turn 1: The user initiates the trip ---
|
| 3707 |
+
query1 = "Hi! I want to plan a 2-day trip to Lisbon, Portugal. I'm interested in historic sites and great local food."
|
| 3708 |
+
print(f"\n๐ฃ๏ธ User (Turn 1): '{query1}'")
|
| 3709 |
+
await run_agent_query(multi_day_agent, query1, trip_session, my_user_id)
|
| 3710 |
+
|
| 3711 |
+
# --- Turn 2: The user gives FEEDBACK and asks for a CHANGE ---
|
| 3712 |
+
# We use the EXACT SAME `trip_session` object!
|
| 3713 |
+
query2 = "That sounds pretty good, but I'm not a huge fan of castles. Can you replace the morning activity for Day 1 with something else historical?"
|
| 3714 |
+
print(f"\n๐ฃ๏ธ User (Turn 2 - Feedback): '{query2}'")
|
| 3715 |
+
await run_agent_query(multi_day_agent, query2, trip_session, my_user_id)
|
| 3716 |
+
|
| 3717 |
+
# --- Turn 3: The user confirms and asks to continue ---
|
| 3718 |
+
query3 = "Yes, the new plan for Day 1 is perfect! Please plan Day 2 now, keeping the food theme in mind."
|
| 3719 |
+
print(f"\n๐ฃ๏ธ User (Turn 3 - Confirmation): '{query3}'")
|
| 3720 |
+
await run_agent_query(multi_day_agent, query3, trip_session, my_user_id)
|
| 3721 |
+
|
| 3722 |
+
await run_adaptive_memory_demonstration()
|
| 3723 |
+
Scenario 3b: Agent WITHOUT Memory (Using SEPARATE Sessions) โ
|
| 3724 |
+
Now, let's see what happens if we mess up our session management. Here, we'll give the agent a case of amnesia by creating a brand new, separate session for each turn.
|
| 3725 |
+
|
| 3726 |
+
Pay close attention to the agent's response to the second query. Because it's in a new session, it has no memory of the trip to Lisbon we just discussed!
|
| 3727 |
+
|
| 3728 |
+
|
| 3729 |
+
[ ]
|
| 3730 |
+
# --- Scenario 2b: Demonstrating Memory FAILURE ---
|
| 3731 |
+
|
| 3732 |
+
async def run_memory_failure_demonstration():
|
| 3733 |
+
print("\n" + "#"*60)
|
| 3734 |
+
print("### ๐ง DEMO 2b: AGENT WITH AMNESIA (SEPARATE SESSIONS) ###")
|
| 3735 |
+
print("#"*60)
|
| 3736 |
+
|
| 3737 |
+
# --- Turn 1: The user initiates the trip in the FIRST session ---
|
| 3738 |
+
query1 = "Hi! I want to plan a 2-day trip to Lisbon, Portugal. I'm interested in historic sites and great local food."
|
| 3739 |
+
session_one = await session_service.create_session(
|
| 3740 |
+
app_name=multi_day_agent.name,
|
| 3741 |
+
user_id=my_user_id
|
| 3742 |
+
)
|
| 3743 |
+
print(f"\nCreated a session for Turn 1: {session_one.id}")
|
| 3744 |
+
print(f"๐ฃ๏ธ User (Turn 1): '{query1}'")
|
| 3745 |
+
await run_agent_query(multi_day_agent, query1, session_one, my_user_id)
|
| 3746 |
+
|
| 3747 |
+
# --- Turn 2: The user asks to continue... but in a completely NEW session ---
|
| 3748 |
+
query2 = "Yes, that looks perfect! Please plan Day 2."
|
| 3749 |
+
session_two = await session_service.create_session(
|
| 3750 |
+
app_name=multi_day_agent.name,
|
| 3751 |
+
user_id=my_user_id
|
| 3752 |
+
)
|
| 3753 |
+
print(f"\nCreated a BRAND NEW session for Turn 2: {session_two.id}")
|
| 3754 |
+
print(f"๐ฃ๏ธ User (Turn 2): '{query2}'")
|
| 3755 |
+
await run_agent_query(multi_day_agent, query2, session_two, my_user_id)
|
| 3756 |
+
|
| 3757 |
+
await run_memory_failure_demonstration()
|
| 3758 |
+
See? The agent was confused! It likely asked what destination or what trip we were talking about. Because the second query was in a fresh, isolated session, the agent had no memory of planning Day 1 in Lisbon.
|
| 3759 |
+
|
| 3760 |
+
This perfectly illustrates why managing sessions is the key to building truly conversational agents!
|
| 3761 |
+
|
| 3762 |
+
๐ Congratulations! ๐
|
| 3763 |
+
Congratulations on completing your ADK adventure into Tools and Memory! You've taken a massive leap from building single-shot agents to creating dynamic, stateful AI systems.
|
| 3764 |
+
|
| 3765 |
+
Let's recap the powerful concepts you've mastered:
|
| 3766 |
+
|
| 3767 |
+
Fundamental Agent & Tools: You started by building a "Day Trip Genie" and equipped it with its first tool, GoogleSearch.
|
| 3768 |
+
|
| 3769 |
+
Custom Function Tools: You gave your agent a new sense by creating a custom tool to fetch live data from the U.S. National Weather Service API.
|
| 3770 |
+
|
| 3771 |
+
Agent-as-a-Tool: You orchestrated a sophisticated hierarchy where agents delegate tasks to other, more specialized agents, creating a collaborative team.
|
| 3772 |
+
|
| 3773 |
+
The Power of Memory: Most importantly, you saw firsthand how managing a single, persistent Session allows an agent to remember context, adapt to user feedback, and conduct a meaningful, multi-turn conversation.
|
| 3774 |
+
|
| 3775 |
+
__ /\_/\ /\_/\ /\_/\ __ (\__/)
|
| 3776 |
+
o-''|\_____/). ( o.o ) ( -.- ) ( ^_^ ) o-''|\_____/). ( ^_^ )
|
| 3777 |
+
\_/|_) ) > ^ < > * < >๐< \_/|_) ) / >๐ธ< \
|
| 3778 |
+
\ __ / \ __ / / \
|
| 3779 |
+
(_/ (_/ (_/ (_/ (___|___)
|
| 3780 |
+
Colab paid products - Cancel contracts here
|
tools.py
CHANGED
|
@@ -5,6 +5,8 @@ from PIL import Image
|
|
| 5 |
from transformers import AutoProcessor
|
| 6 |
from unsloth import FastVisionModel
|
| 7 |
from langchain_community.vectorstores import FAISS
|
|
|
|
|
|
|
| 8 |
|
| 9 |
def create_plant_diagnosis_tool(model: FastVisionModel, processor: AutoProcessor):
|
| 10 |
"""Factory function to create the plant diagnosis tool."""
|
|
@@ -63,3 +65,26 @@ def create_remedy_retrieval_tool(retriever: FAISS):
|
|
| 63 |
return "No specific remedy found in the knowledge base for this condition."
|
| 64 |
|
| 65 |
return retrieve_remedy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from transformers import AutoProcessor
|
| 6 |
from unsloth import FastVisionModel
|
| 7 |
from langchain_community.vectorstores import FAISS
|
| 8 |
+
from vector_store import search_documents
|
| 9 |
+
from bigquery_search import search_bigquery_for_remedy
|
| 10 |
|
| 11 |
def create_plant_diagnosis_tool(model: FastVisionModel, processor: AutoProcessor):
|
| 12 |
"""Factory function to create the plant diagnosis tool."""
|
|
|
|
| 65 |
return "No specific remedy found in the knowledge base for this condition."
|
| 66 |
|
| 67 |
return retrieve_remedy
|
| 68 |
+
|
| 69 |
+
def create_chroma_db_search_tool():
|
| 70 |
+
"""Factory function to create the ChromaDB search tool."""
|
| 71 |
+
|
| 72 |
+
def search_chroma_db(query: str) -> str:
|
| 73 |
+
"""
|
| 74 |
+
Searches the local ChromaDB vector store for a remedy based on a diagnosis query.
|
| 75 |
+
"""
|
| 76 |
+
results = search_documents(query)
|
| 77 |
+
return results[0] if results else "No remedy found in local knowledge base."
|
| 78 |
+
|
| 79 |
+
return search_chroma_db
|
| 80 |
+
|
| 81 |
+
def create_bigquery_search_tool():
|
| 82 |
+
"""Factory function to create the BigQuery search tool."""
|
| 83 |
+
|
| 84 |
+
def search_bigquery(query: str) -> str:
|
| 85 |
+
"""
|
| 86 |
+
Searches BigQuery for a remedy based on a diagnosis query.
|
| 87 |
+
"""
|
| 88 |
+
return search_bigquery_for_remedy(query)
|
| 89 |
+
|
| 90 |
+
return search_bigquery
|