Skip to content

Commit 7d74a0a

Browse files
boyangsvlcopybara-github
authored andcommitted
docs: add unit guides for event.py, request_input.py and update adk-unit-guide skill
- Updated request_input.py unit guide to use function node example and removed field table. - Updated adk-unit-guide skill: - Discourage exhaustive API reference tables in guides. - Prefer simple Python functions over extending BaseNode for workflow node samples. - Removed field reference tables from the event guide. Co-authored-by: Bo Yang <ybo@google.com> PiperOrigin-RevId: 931347184
1 parent 59f7bdf commit 7d74a0a

4 files changed

Lines changed: 332 additions & 1 deletion

File tree

.agents/skills/adk-unit-guide/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Use the following structure and instructions to create the guide for the code un
5151
- Show enough of the containing classes to make it clear where the code could be used.
5252
- Use unit test code as a starting point for the code example, if available.
5353
- When writing a sample agent, do not set the `model` attribute.
54+
- For workflow node samples, prefer using a simple Python function rather than extending `BaseNode` to demonstrate the node's logic, unless class extension is explicitly required for the use case.
5455
5556
## How it works
5657
@@ -61,7 +62,8 @@ Use the following structure and instructions to create the guide for the code un
6162
6263
## Configuration options
6364
64-
- If the code unit has configuration options, document them in a table detailing parameters, types, default values, and descriptions.
65+
- If the code unit has configuration options (e.g., settings, configuration objects), document them in a table detailing parameters, types, default values, and descriptions.
66+
- **Do NOT** list references of all attributes or methods of the classes. Exhaustive API references belong in auto-generated reference documentation, not in guides. Guides should focus on how to use the code unit.
6567
6668
## Advanced applications
6769
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# LlmAgent Single-Turn Mode
2+
3+
This guide explains the behavior of `LlmAgent` in `single_turn` mode, both when
4+
executed as a workflow node and when defined as a sub-agent in a multi-agent
5+
hierarchy. It covers default stateless execution, delegation mechanics, and how
6+
to configure history visibility.
7+
8+
--------------------------------------------------------------------------------
9+
10+
## Introduction
11+
12+
In ADK, `mode="single_turn"` is designed for isolated, stateless tasks where the
13+
agent only needs to process the immediate input without accumulating or
14+
referencing prior conversation history.
15+
16+
Depending on how the agent is deployed—either as a step in a `Workflow` or as a
17+
`sub_agent` of another LLM agent—its behavior and interaction patterns differ.
18+
19+
--------------------------------------------------------------------------------
20+
21+
## 1. Single-Turn Mode as a Workflow Node
22+
23+
When building a `Workflow` graph, any `LlmAgent` added to the graph defaults to
24+
`mode="single_turn"` (unless explicitly configured otherwise).
25+
26+
### Behavior
27+
28+
- **Stateless by Default**: The node does not see previous conversation turns
29+
in the workflow session. Its history visibility (`include_contents`)
30+
automatically defaults to `'none'`.
31+
- **Isolated Execution**: Each execution of the node is independent.
32+
33+
### Example
34+
35+
```python
36+
from google.adk.agents import LlmAgent
37+
from google.adk.workflow import Workflow, build_node
38+
39+
# Defaults to mode="single_turn" when run as a node
40+
writer_agent = LlmAgent(
41+
name="writer",
42+
instruction="Write a short story about the input topic."
43+
)
44+
45+
writer_node = build_node(writer_agent)
46+
47+
wf = Workflow(
48+
name="story_generator",
49+
edges=[
50+
("START", writer_node),
51+
(writer_node, "END")
52+
]
53+
)
54+
```
55+
56+
--------------------------------------------------------------------------------
57+
58+
## 2. Single-Turn Mode as a Sub-Agent
59+
60+
You can define hierarchical agent structures by assigning agents to the
61+
`sub_agents` list of a parent `LlmAgent`.
62+
63+
### Behavior
64+
65+
- **Exposed as a Tool**: A `single_turn` sub-agent is **not** a transfer
66+
target. The parent agent cannot hand over control of the conversation to it.
67+
Instead, the framework automatically exposes the sub-agent to the parent as
68+
a **Tool** (function).
69+
- **Functional Delegation**: The parent agent calls the sub-agent like a
70+
function, passing arguments. The sub-agent executes, returns its output to
71+
the parent, and the parent continues the conversation.
72+
- **Isolated Sub-Branch**: When the parent calls the sub-agent tool, the
73+
framework executes the sub-agent in an isolated sub-branch (derived from the
74+
parent's branch, e.g., `parent_branch.sub_agent@run_id`).
75+
- **Stateless by Default**: Like the workflow node, a `single_turn` sub-agent
76+
defaults to `include_contents="none"` and only sees the inputs passed to it
77+
in the tool call.
78+
79+
### Example
80+
81+
```python
82+
from google.adk.agents import LlmAgent
83+
84+
# Define a specialized single-turn sub-agent
85+
translator_agent = LlmAgent(
86+
name="translator",
87+
instruction="Translate the input text to Spanish.",
88+
mode="single_turn" # Must be explicit if not auto-wrapped in workflow
89+
)
90+
91+
# Define the parent agent and assign the sub-agent
92+
bilingual_writer = LlmAgent(
93+
name="bilingual_writer",
94+
instruction="Write a poem about the topic, then use the translator tool to translate it.",
95+
sub_agents=[translator_agent] # Exposes 'translator' as a tool to bilingual_writer
96+
)
97+
```
98+
99+
--------------------------------------------------------------------------------
100+
101+
## How Context Isolation Works
102+
103+
ADK manages history visibility using **branches** and the `include_contents`
104+
configuration:
105+
106+
1. **Branch Hierarchy**: When a sub-agent runs, it executes in a sub-branch
107+
(e.g., `main.translator@1`).
108+
- A sub-branch is allowed to read events from its parent branch (one-way
109+
visibility).
110+
- The parent branch cannot read events from the sub-branch (protecting the
111+
parent from sub-agent internal reasoning chatter).
112+
2. **History Filtering**:
113+
- **`include_contents="none"`** (Default): The agent bypasses history
114+
loading entirely. It only sees the immediate input (the workflow node
115+
input or the tool call arguments).
116+
- **`include_contents="default"`**: The agent loads conversation history.
117+
Because of the branch hierarchy, a sub-agent with this setting can see
118+
the parent agent's conversation history leading up to the tool call.
119+
120+
--------------------------------------------------------------------------------
121+
122+
## Configuration Options
123+
124+
Parameter | Type | Default | Description
125+
:----------------- | :--------------------------------------- | :------------------------------------ | :----------
126+
`mode` | `Literal['single_turn', 'task', 'chat']` | `'single_turn'` (when run as node) | The execution mode. `single_turn` isolates execution; `task` supports delegation; `chat` preserves full history.
127+
`include_contents` | `Literal['default', 'none']` | `'none'` (for `single_turn` if unset) | Controls history visibility. For `single_turn` mode, it defaults to `'none'` (stateless), but can be explicitly set to `'default'` to make the agent context-aware.
128+
129+
--------------------------------------------------------------------------------
130+
131+
## Advanced Applications: Context-Aware Execution
132+
133+
If you want a single-turn agent (node or sub-agent) to have access to the
134+
conversation history, you must explicitly set `include_contents="default"`.
135+
136+
### Context-Aware Sub-Agent Example
137+
138+
In this setup, the `verifier` sub-agent needs to see the history of the
139+
conversation to verify the parent's draft against previous user constraints:
140+
141+
```python
142+
verifier_agent = LlmAgent(
143+
name="verifier",
144+
instruction="Verify that the draft meets all constraints discussed in the chat.",
145+
mode="single_turn",
146+
include_contents="default" # Allows the sub-agent to see the parent's conversation history
147+
)
148+
149+
editor_agent = LlmAgent(
150+
name="editor",
151+
instruction="Discuss the draft with the user and use verifier to check constraints.",
152+
sub_agents=[verifier_agent]
153+
)
154+
```
155+
156+
--------------------------------------------------------------------------------
157+
158+
## Limitations
159+
160+
- **Difference from Standalone Behavior**: A standalone `LlmAgent` defaults to
161+
`include_contents="default"`. When used in a workflow or as a sub-agent, it
162+
defaults to `include_contents="none"`.
163+
- **No Direct Transfer**: You cannot use `transfer_to_agent` to target a
164+
`single_turn` agent. They must be invoked via tool calls.

docs/guides/events/event/index.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Event and NodeInfo
2+
3+
The `event.py` file defines the `Event` and `NodeInfo` classes, which are the fundamental data structures used in the Agent Development Kit (ADK) to represent interactions, actions, and metadata within a workflow.
4+
5+
## Introduction
6+
7+
In ADK, conversations and workflow executions are modeled as a sequence of events. The `Event` class represents a single unit of this sequence, capturing:
8+
- **Content:** Messages exchanged between users and agents (text, function calls, function responses).
9+
- **Actions:** Side-effects or instructions, such as state updates, routing decisions, agent transfers, and UI rendering requests.
10+
- **Metadata:** Information about who generated the event, when, and from which part of the workflow.
11+
12+
`NodeInfo` specifically carries metadata about the workflow node that generated the event, enabling tracking of execution paths and run IDs.
13+
14+
Key classes depending on `Event` include `Session` (which stores the event history) and `Workflow` / `NodeRunner` (which use events for execution flow and state management).
15+
16+
## Get started
17+
18+
Here is how to create and use `Event` objects.
19+
20+
### Basic Message Event
21+
22+
You can create a simple event with a text message:
23+
24+
```python
25+
from google.adk.events.event import Event
26+
27+
# Create a user message event
28+
user_event = Event(author="user", message="Hello, agent!")
29+
30+
# The 'message' argument is a convenience alias for 'content'
31+
print(user_event.message.parts[0].text) # Output: Hello, agent!
32+
```
33+
34+
### Event with State Delta
35+
36+
Events can carry state updates that should be applied to the session state:
37+
38+
```python
39+
from google.adk.events.event import Event
40+
41+
# Create an agent event that updates the state
42+
state_event = Event(
43+
author="my_agent",
44+
message="I've updated the user preference.",
45+
state={"user_theme": "dark"}
46+
)
47+
48+
print(state_event.actions.state_delta) # Output: {'user_theme': 'dark'}
49+
```
50+
51+
### Event with Node Metadata
52+
53+
When events are generated within a workflow, they usually include node information.
54+
55+
> [!NOTE]
56+
> `NodeInfo` is automatically populated by the ADK framework. While you can access these fields, you should not manually construct or modify `node_info` in your application logic.
57+
58+
```python
59+
from google.adk.events.event import Event, NodeInfo
60+
61+
node_event = Event(
62+
author="agent_node",
63+
node_path="parent_workflow/child_node@run-123",
64+
output="some_result"
65+
)
66+
67+
print(node_event.node_info.path) # Output: parent_workflow/child_node@run-123
68+
print(node_event.node_info.name) # Output: child_node
69+
print(node_event.node_info.run_id) # Output: run-123
70+
```
71+
72+
## How it works
73+
74+
`Event` inherits from `LlmResponse`, which allows it to directly wrap responses from Gemini models, including content, grounding metadata, and token usage.
75+
76+
### Convenience Kwargs Routing
77+
78+
The `Event` constructor accepts several convenience arguments that are automatically routed to nested Pydantic models:
79+
- `message`: Automatically converted to `types.Content` and set to the `content` field.
80+
- `state`: Mapped to `actions.state_delta`.
81+
- `route`: Mapped to `actions.route`.
82+
- `node_path`: Mapped to `node_info.path`.
83+
84+
This routing is handled by the `@model_validator(mode='before')` method `_accept_convenience_kwargs`.
85+
86+
### Serialization
87+
88+
Both `Event` and `NodeInfo` are Pydantic models configured to use camelCase aliases for serialization. When sending events over the wire or saving them, use `model_dump(by_alias=True)` to ensure compatibility with ADK APIs.
89+
90+
### Lifecycle
91+
92+
Every event is assigned a unique UUID `id` and a `timestamp` upon initialization if they are not explicitly provided.
93+
94+
## Advanced applications
95+
96+
### Workflow Routing
97+
98+
Workflows use `Event` to communicate routing decisions. By setting `route` (which maps to `actions.route`), a node can signal to the workflow engine which edge to follow next.
99+
100+
```python
101+
routing_event = Event(author="router_node", route="success_path")
102+
```
103+
104+
### Context Isolation
105+
106+
The `isolation_scope` field is used by the Task API to isolate conversations of delegated agents. Events with a specific `isolation_scope` (e.g., `"task:fc-987"`) will only be visible to agents running within that same scope, preventing them from seeing the main conversation history.
107+
108+
## Limitations
109+
110+
- **NodeInfo Assignment:** The `node_info` field (and the `node_path` constructor argument) is managed and assigned by the ADK framework during workflow execution. Developers should not manually set or modify `node_info` in production code.
111+
- **Internal Fields:** The `isolation_scope` field is an internal implementation detail. External developers should not rely on it or modify it directly.
112+
- **Mutual Exclusion:** You cannot specify both `message` and `content` in the `Event` constructor; doing so will raise a `ValueError`.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# RequestInput
2+
3+
The `RequestInput` class represents a structured request for input from the user, typically used to trigger an interrupt in a workflow (Human-in-the-loop).
4+
5+
## Introduction
6+
7+
In ADK, workflows can be configured to pause and wait for user intervention. The `RequestInput` event is the data structure that represents this interrupt request. It is typically yielded by a workflow node and translated into an `Event` with a special function call (`adk_request_input`) that the client application handles.
8+
9+
Key classes depending on `RequestInput` include `Workflow` (which pauses execution when encountering this event) and various HITL helper utilities (like `create_request_input_event` and `create_request_input_response`) that wrap it. It solves the developer problem of pausing a workflow and gathering structured feedback from a user before resuming.
10+
11+
## Get started
12+
13+
To request input from a user within a workflow, you yield a `RequestInput` object from a node function.
14+
15+
Here is a basic example of a node that requests user details:
16+
17+
```python
18+
from typing import Any, AsyncGenerator
19+
from google.adk import Context
20+
from google.adk.events.request_input import RequestInput
21+
from pydantic import BaseModel
22+
23+
class UserDetails(BaseModel):
24+
name: str
25+
age: int
26+
27+
async def request_input_node(
28+
ctx: Context,
29+
node_input: Any,
30+
) -> AsyncGenerator[Any, None]:
31+
"""A simple node that requests input from the user."""
32+
# Yield RequestInput to pause and request user details.
33+
# The response must conform to UserDetails schema.
34+
yield RequestInput(
35+
interrupt_id="get-user-details-1",
36+
message="Please provide user details.",
37+
response_schema=UserDetails,
38+
)
39+
```
40+
41+
## How it works
42+
43+
When a node yields a `RequestInput` object, the following process occurs:
44+
45+
1. **Workflow Pause**: The workflow engine detects the `RequestInput` event and pauses the execution of the workflow.
46+
2. **Event Translation**: The `RequestInput` is wrapped into an `Event` containing a mock function call named `adk_request_input`. The fields `message`, `payload`, and `response_schema` are passed as arguments to this function call.
47+
3. **Client Interaction**: The client application receiving this event displays the message to the user (optionally validating the input against the provided `response_schema`).
48+
4. **Resuming execution**: To resume the workflow, the client sends back a `FunctionResponse` matching the `interrupt_id` (used as the function call `id`) and named `adk_request_input`. The response payload is placed inside the `response` dictionary.
49+
5. **Resume**: The workflow engine delivers this response back to the node, allowing it to continue execution.
50+
51+
## Limitations
52+
53+
- **Client-Side Validation**: When using `response_schema`, the client application is responsible for validating that the user's input conforms to the schema before sending it back to resume the workflow. ADK handles parsing on resume, but client-side validation is recommended for a better user experience.

0 commit comments

Comments
 (0)