Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions hacks/adk-intro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ This hack will help you explore the following tasks:
- Challenge 1: First Scan
- Challenge 2: Equipping the Scanner
- Challenge 3: Sticky Notes
- Challenge 4: Agent Symphony
- Challenge 5: MCP: Universal Tooling
- Challenge 6: A2A: Remote Agent Power
- Challenge 4: The Gatekeeper
- Challenge 5: Agent Symphony
- Challenge 6: MCP: Universal Tooling
- Challenge 7: A2A: Remote Agent Power

## Prerequisites

Expand Down Expand Up @@ -144,7 +145,41 @@ Modify the `resource_scanner_agent` to save the list of all virtual machines in

- You can use `adk web` UI to inspect the session state (and to verify that everything works as expected).

## Challenge 4: Agent Symphony
## Challenge 4: The Gatekeeper

### Introduction

In the previous challenge, we saved the scanned resources to the session state under the `resources` key.

While our LLM is intelligent enough to inspect the conversation history and recognize that we already scanned the resources, asking the agent about them still triggers a model invocation. This wastes Gemini tokens and adds unnecessary latency. To prevent this, we'll introduce a *callback*. Callbacks are specialized lifecycle hooks that intercept execution of agents, models and tools.

Callbacks can be used for many different purposes, such as to modify data flowing through the agent, or even to bypass certain steps entirely based on your logic. You can use them to log detailed information at every step, enforce guardrails, cache things and manage state. In this challenge we'll skip the agent execution altogether if the state indicates that a certain step has already run.

### Description

Create a new callback function named `gatekeeper` that returns `Already scanned!` *if and only if* the session state contains the `resources` key.

Make sure that this function is called every time *before* the agent runs.

### Success Criteria

- There's a new Python function called `gatekeeper` with the correct function signature.
- The `resource_scanner_agent` is configured to use the `gatekeeper` every time before the agent runs.
- Asking the agent to scan resources a second time returns `"Already scanned!"` instantly, bypassing model and tool calls.
- The changes have been pushed to the remote Git repository.

### Learning Resources

- [ADK Callbacks](https://adk.dev/callbacks/)
- [Before agent callback](https://adk.dev/callbacks/types-of-callbacks/#before-agent-callback)

### Tips

- Returning `None` from the callback causes the agent to proceed with the normal execution flow.
- You cannot return a string from a callback function, it has to be wrapped into an appropriate object.
- Callback function parameter names must match the [documented names exactly](https://adk.dev/callbacks/types-of-callbacks/#agent-lifecycle-callbacks) because ADK passes callback arguments by keyword.

## Challenge 5: Agent Symphony

### Introduction

Expand Down Expand Up @@ -179,7 +214,7 @@ Then create a new *Workflow* `orchestrator_agent` that calls the `resource_scann
- You can use `adk web` UI to view the agents involved.
- If idle resource list is not generated correctly, make your instructions more specific.

## Challenge 5: MCP: Universal Tooling
## Challenge 6: MCP: Universal Tooling

### Introduction

Expand Down Expand Up @@ -215,7 +250,7 @@ Then add the `resource_labeler_agent` to the `orchestrator_agent` sequence.
- LLMs have no understanding of the current date and can struggle with date arithmetic, you might want to use additional tools.
- If anything goes wrong with the labels, you can use the provided `reset-labels.sh` script to reset the labels to their original state.

## Challenge 6: A2A: Remote Agent Power
## Challenge 7: A2A: Remote Agent Power

### Introduction

Expand Down
23 changes: 21 additions & 2 deletions hacks/adk-intro/artifacts/setup.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,24 @@ gcloud run deploy a2a-server \
echo "Setup done!"

echo "Starting CPU hogging..."
# Hog the cpu after 2 minutes (120 seconds = 120,000,000 microseconds)
stress -c 1 --backoff 120000000 &
cat << EOF > /etc/systemd/system/cpu-hog.service
[Unit]
Description=My CPU Hogging Service
After=network.target

[Service]
ExecStart=/usr/bin/stress -c 1
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

sleep 120

# Reload systemd, enable and start the service
systemctl daemon-reload
systemctl enable cpu-hog.service
systemctl start cpu-hog.service

echo "CPU hogging started!"
62 changes: 54 additions & 8 deletions hacks/adk-intro/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ Welcome to the coach's guide for Introduction to Agents with ADK gHack. Here you
- Challenge 1: First Scan
- Challenge 2: Equipping the Scanner
- Challenge 3: Sticky Notes
- Challenge 4: Agent Symphony
- Challenge 5: MCP: Universal Tooling
- Challenge 6: A2A: Remote Agent Power
- Challenge 4: The Gatekeeper
- Challenge 5: Agent Symphony
- Challenge 6: MCP: Universal Tooling
- Challenge 7: A2A: Remote Agent Power

## Challenge 1: First Scan

Expand Down Expand Up @@ -123,6 +124,13 @@ Format of the response is not relevant for this challenge as long as all of the

Make sure that the changes are pushed to the repository so the next driver can pick up the changes.

Note that Git requires users to set up their identity before anything can be committed. So users need do the following before they can commit their changes:

```shell
git config --global user.name "$USER" # or participant's real name
git config --global user.email "$USER_EMAIL" # or participant's real email address
```

## Challenge 3: Sticky Notes

### Notes & Guidance
Expand All @@ -147,7 +155,45 @@ resource_scanner_agent = Agent(

Make sure that the changes are pushed to the repository so the next driver can pick up the changes.

## Challenge 4: Agent Symphony
## Challenge 4: The Gatekeeper

### Notes & Guidance

Again the new driver should follow the same steps for the first challenge to clone the repository (or pull the latest changes if they have already cloned it) and set up their environment (if they haven't done that already).

Then edit the `janitor/agent.py` to define the callback and register it on the scanner agent.

```python
# Keep other imports
from google.adk.agents.callback_context import CallbackContext
from google.genai.types import Content, Part


def gatekeeper(callback_context: CallbackContext) -> Content | None:
if "resources" in callback_context.state:
return Content(parts=[Part.from_text(text="Already scanned!")])
return None


resource_scanner_agent = Agent(
name="resource_scanner_agent",
model=settings.GEMINI_MODEL,
instruction="""
You are a Cloud Resource Scanner.
Return *all* resources.
""",
tools=[tools.get_compute_instances_list],
output_key="resources",
output_schema=schemas.VMInstanceList,
before_agent_callback=gatekeeper
)

root_agent = resource_scanner_agent
```

Make sure that the changes are pushed to the repository so the next driver can pick up the changes.

## Challenge 5: Agent Symphony

### Notes & Guidance

Expand Down Expand Up @@ -184,11 +230,11 @@ root_agent = orchestrator_agent
```

> [!NOTE]
> Sometimes after running both agents, the `resources` state variable seems to be empty, but as long as `idle_resources` provides the correct set of instances, it should be fine.
> Sometimes after running both agents, the `resources` state variable seems to be empty, but as long as `idle_resources` provides the correct set of instances, it should be fine. If the callback causees issues, because it will skip the agent execution if the `resources` is set, it can be updated to check for the length of the list and override execution only if the list exists and it is not empty.

Make sure that the changes are pushed to the repository so the next driver can pick up the changes.

## Challenge 5: MCP: Universal Tooling
## Challenge 6: MCP: Universal Tooling

### Notes & Guidance

Expand Down Expand Up @@ -262,7 +308,7 @@ mcp_tool_set = McpToolset(
```

> [!NOTE]
> At the time of this writing using the `auth_scheme` and `auth_credentials` for bearer tokens doesn't work well with MCP servers, as those credentials are not utilized for listing the tools, tracked [here](https://github.com/google/adk-python/issues/2168).
> When we originally designed this challenge, the `auth_scheme` and `auth_credentials` for bearer tokens didn't work well with MCP servers, as those credentials were not utilized for listing the tools, tracked [here](https://github.com/google/adk-python/issues/2168). This issue has been resolved, so participants are free to use that approach too.

As our tool is simple, this approach works fine, but in real world, you might need to use OAuth flows, API keys etc. And there will be cases where the currently authenticated user's credentials need to be forwarded to remote agents/tools so that they can perform actions on behalf of the user (see the official [docs](https://adk.dev/safety/#identity-and-authorization) for more details).

Expand All @@ -276,7 +322,7 @@ gcloud compute instances list \

Make sure that the changes are pushed to the repository so the next driver can pick up the changes.

## Challenge 6: A2A: Remote Agent Power
## Challenge 7: A2A: Remote Agent Power

### Notes & Guidance

Expand Down
Loading