Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ async def after_agent_callback(callback_context: CallbackContext):
name='root_agent',
description='a verification agent.',
instruction=(
'Log all users query with `log_query` tool. Must always remind user you'
' cannot answer second query because your setup.'
'Reply to the user. Must always remind user you cannot answer a second'
' query because your setup.'
),
model='gemini-3.5-flash',
before_agent_callback=before_agent_callback,
Expand Down
36 changes: 36 additions & 0 deletions contributing/samples/workflows/node_as_tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Node as Tool

## Overview

Demonstrates wrapping both a regular ADK `Node` (using the `@node` decorator) and a `Workflow` as tools that can be automatically called by a parent `Agent`.

In this sample:

1. The parent agent receives an inquiry about a customer's discount.
1. It invokes `customer_lookup_workflow` (a `Workflow` wrapped as a tool) to retrieve customer status.
1. It then invokes `calculate_discount` (a regular `Node` wrapped as a tool) using the retrieved status.

## Sample Inputs

- `What discount does customer c123 get?`

*The parent agent first invokes `customer_lookup_workflow` to verify status, then invokes `calculate_discount` to determine the discount percentage, and summarizes the results.*

## Agent Topology Graph

```mermaid
graph TD
customer_service_agent[customer_service_agent] -->|calls| customer_lookup_workflow(customer_lookup_workflow)
customer_service_agent -->|calls| calculate_discount(calculate_discount)
```

## How To

To expose an existing `Node` or `Workflow` as a tool callable by an `Agent`:

1. Define your `Node` (or `@node`) or `Workflow` and assign both an `input_schema` and a `description`.
1. Pass the node/workflow directly into your parent agent's `tools` list: `Agent(..., tools=[my_node, my_workflow])`.

## Related Guides

- [Workflows](../../../../docs/guides/workflows/workflows.md) - Explains building complex multi-step graphs.
73 changes: 73 additions & 0 deletions contributing/samples/workflows/node_as_tool/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from typing import Generator

from google.adk import Agent
from google.adk import Event
from google.adk import Workflow
from google.adk.workflow import node
from pydantic import BaseModel
from pydantic import Field


# 1. Define schemas
class CustomerLookupArgs(BaseModel):
user_id: str = Field(description="The customer's unique identifier.")


# 2. Define a regular Node using the @node decorator.
# This Node is wrapped as a NodeTool automatically by the Agent.
# As a NodeTool, it has the ability to yield intermediate Events during execution.
@node
def calculate_discount(tier: str, ctx) -> Generator[Event | str, None, None]:
"""Calculates the discount percentage based on customer tier.

Args:
tier: The customer's membership tier (e.g., VIP, Standard).
"""
yield Event(message=f"Checking discount rules for tier '{tier}'...")
discount = "20% off" if "VIP" in tier else "5% off"
yield discount


# 3. Define a Workflow.
# This Workflow is wrapped as a NodeTool automatically by the Agent.
def lookup_customer_data(node_input: CustomerLookupArgs, ctx) -> dict[str, str]:
return {"user_id": node_input.user_id, "tier": "Verified VIP Member"}


customer_lookup_workflow = Workflow(
name="customer_lookup_workflow",
description="Looks up customer status and tier by user_id.",
input_schema=CustomerLookupArgs,
edges=[
("START", lookup_customer_data),
],
)


# 4. Define the Agent that uses both Node and Workflow as tools.
root_agent = Agent(
name="customer_service_agent",
instruction="""
You are a customer service assistant.
1. First, call `customer_lookup_workflow` using the user_id to get their membership tier.
2. Then, call `calculate_discount` node with that tier to find out what discount they get.
Summarize these details for the customer.
""",
tools=[customer_lookup_workflow, calculate_discount],
)
182 changes: 182 additions & 0 deletions contributing/samples/workflows/node_as_tool/tests/go.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
{
"appName": "node_as_tool",
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "What discount does customer c123 get?"
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "customer_service_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"user_id": "c123"
},
"id": "fc-1",
"name": "customer_lookup_workflow"
}
}
],
"role": "model"
},
"id": "e-2",
"invocationId": "i-1",
"longRunningToolIds": [
"fc-1"
],
"nodeInfo": {
"path": "customer_service_agent@1"
}
},
{
"author": "customer_lookup_workflow",
"branch": "customer_lookup_workflow@fc-1",
"id": "e-3",
"invocationId": "i-1",
"nodeInfo": {
"outputFor": [
"customer_lookup_workflow@1/lookup_customer_data@1",
"customer_lookup_workflow@1"
],
"path": "customer_lookup_workflow@1/lookup_customer_data@1"
},
"output": {
"tier": "Verified VIP Member",
"user_id": "c123"
}
},
{
"author": "customer_service_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-1",
"name": "customer_lookup_workflow",
"response": {
"tier": "Verified VIP Member",
"user_id": "c123"
}
}
}
],
"role": "user"
},
"id": "e-4",
"invocationId": "i-1",
"nodeInfo": {
"path": "customer_service_agent@1"
}
},
{
"author": "customer_service_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"tier": "Verified VIP Member"
},
"id": "fc-2",
"name": "calculate_discount"
}
}
],
"role": "model"
},
"id": "e-5",
"invocationId": "i-1",
"longRunningToolIds": [
"fc-2"
],
"nodeInfo": {
"path": "customer_service_agent@1"
}
},

{
"author": "calculate_discount",
"branch": "calculate_discount@fc-2",
"content": {
"parts": [
{
"text": "Checking discount rules for tier 'Verified VIP Member'..."
}
],
"role": "user"
},
"id": "e-6",
"invocationId": "i-1",
"nodeInfo": {
"path": "calculate_discount@1"
}
},
{
"author": "calculate_discount",
"branch": "calculate_discount@fc-2",
"id": "e-7",
"invocationId": "i-1",
"nodeInfo": {
"outputFor": [
"calculate_discount@1"
],
"path": "calculate_discount@1"
},
"output": "20% off"
},
{
"author": "customer_service_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-2",
"name": "calculate_discount",
"response": {
"result": "20% off"
}
}
}
],
"role": "user"
},
"id": "e-8",
"invocationId": "i-1",
"nodeInfo": {
"path": "customer_service_agent@1"
}
},
{
"author": "customer_service_agent",
"content": {
"parts": [
{
"text": "Customer c123 is a Verified VIP Member and gets a 20% discount."
}
],
"role": "model"
},
"id": "e-9",
"invocationId": "i-1",
"nodeInfo": {
"path": "customer_service_agent@1"
}
}
],
"id": "12345678-1234-1234-1234-123456789abc",
"userId": "user"
}
Loading
Loading