Skip to content
Open
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
73 changes: 49 additions & 24 deletions docs/callbacks/types-of-callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,38 @@ These callbacks are available on *any* agent that inherits from `BaseAgent` (inc
!!! Note
The specific method names or return types may vary slightly by SDK language (e.g., return `None` in Python, return `Optional.empty()` or `Maybe.empty()` in Java). Refer to the language-specific API documentation for details.

??? warning "Python: Use the documented callback parameter names"
??? note "Python: Canonical callback parameter names"

In Python, callback function parameter names must match the documented
names exactly because ADK passes callback arguments by keyword. For example,
use `callback_context` for agent and model callbacks, and `tool_context` for
tool callbacks. Renaming these parameters to aliases such as `ctx` will cause
runtime `TypeError` failures.
For agent-defined callbacks in Python, ADK first tries to bind arguments by
keyword. If keyword binding fails, it tries positional binding in the
canonical order shown in the table below.

Canonical parameter names are recommended and are required for keyword-only
parameters. Callbacks that accept positional arguments may use different
parameter names, provided they accept the arguments in canonical order.

```python
# Correct
# Recommended: canonical parameter name; keyword binding succeeds.
def before_agent_callback(callback_context):
...
return None

# Supported: positional fallback supplies the callback context to ctx.
def before_agent_alias(ctx):
return None

# Incorrect
def before_agent_callback(ctx):
...
# Supported: a keyword-only parameter with the canonical name.
def before_agent_keyword_only(*, callback_context):
return None

# Unsupported: neither keyword nor positional binding can supply ctx.
def before_agent_keyword_only_alias(*, ctx):
return None
```

| Callback | Required parameter names |
The last signature raises `TypeError` when ADK invokes it. Positional
fallback does not make renamed keyword-only parameters valid.

| Callback | Canonical parameter names |
|---|---|
| `before_agent_callback` | `callback_context` |
| `after_agent_callback` | `callback_context` |
Expand All @@ -49,18 +62,29 @@ These callbacks are available on *any* agent that inherits from `BaseAgent` (inc
??? note "Python: `async` callbacks and lists of callbacks"

In Python, a callback may be a plain `def` or an `async def`. ADK awaits
the result either way.
the result if it is awaitable.

Every callback field also accepts a list of functions instead of a single
function. ADK invokes them in the order listed and stops at the first one
that returns a result: that value becomes the callback result, and the
remaining callbacks are skipped. What counts as a result differs by family.
The six `before_`/`after_` agent, model and tool hooks stop only on a
*truthy* value, so a callback returning `None`, or another falsy value such
as an empty `dict`, lets the next one run. `on_model_error_callback` and
`on_tool_error_callback` stop on any value that is not `None`, so an empty
`dict` from an `on_tool_error_callback` ends the chain, suppresses the
exception, and becomes the tool result.

| Agent-defined callbacks | Response type (or `None`) | Stop the list when |
|---|---|---|
| `before_agent_callback`, `after_agent_callback` | `types.Content` | The response is truthy |
| `before_model_callback`, `after_model_callback` | `LlmResponse` | The response is truthy |
| `before_tool_callback`, `after_tool_callback` | `dict` | The response is not `None` |
| `on_model_error_callback` | `LlmResponse` | The response is not `None` |
| `on_tool_error_callback` | `dict` | The response is not `None` |

Return `None` to let the next callback run. An empty `dict` (`{}`) is a valid
tool response: it stops a before-tool chain and skips the tool call, stops
an after-tool chain and replaces the tool result, or stops a tool-error
chain and handles the exception. Model callbacks, including
`on_model_error_callback`, return `LlmResponse`, not a `dict`.

These stopping rules describe agent-defined callback lists. Plugin callback
ordering and overrides follow the [plugin callback rules](../plugins/index.md).

Assign the list to the callback field on the agent:

Expand Down Expand Up @@ -112,7 +136,7 @@ These callbacks are available on *any* agent that inherits from `BaseAgent` (inc
* **What it Shows:** This example demonstrates the `before_agent_callback`. This callback runs *right before* the agent's main processing logic starts for a given request.
* **How it Works:** The callback function (`check_if_agent_should_run`) looks at a flag (`skip_llm_agent`) in the session's state.
* If the flag is `True`, the callback returns a `types.Content` object. This tells the ADK framework to **skip** the agent's main execution entirely and use the callback's returned content as the final response.
* If the flag is `False` (or not set), the callback returns `None` or an empty object. This tells the ADK framework to **proceed** with the agent's normal execution (calling the LLM in this case).
* If the flag is `False` (or not set), the callback returns `None` in Python (or an empty optional in Java). This tells the ADK framework to **proceed** with the agent's normal execution (calling the LLM in this case).
* **Expected Outcome:** You'll see two scenarios:
1. In the session *with* the `skip_llm_agent: True` state, the agent's LLM call is bypassed, and the output comes directly from the callback ("Agent... skipped...").
2. In the session *without* that state flag, the callback allows the agent to run, and you see the actual response from the LLM (e.g., "Hello!").
Expand Down Expand Up @@ -162,7 +186,7 @@ These callbacks are available on *any* agent that inherits from `BaseAgent` (inc
* **What it Shows:** This example demonstrates the `after_agent_callback`. This callback runs *right after* the agent's main processing logic has finished and produced its result, but *before* that result is finalized and returned.
* **How it Works:** The callback function (`modify_output_after_agent`) checks a flag (`add_concluding_note`) in the session's state.
* If the flag is `True`, the callback returns a *new* `types.Content` object. This tells the ADK framework to **append** the agent's original output with the content returned by the callback.
* If the flag is `False` (or not set), the callback returns `None` or an empty object. This tells the ADK framework to **use** the original output generated by the agent.
* If the flag is `False` (or not set), the callback returns `None` in Python (or an empty optional in Java). This tells the ADK framework to **use** the original output generated by the agent.
* **Expected Outcome:** You'll see two scenarios:
1. In the session *without* the `add_concluding_note: True` state, the callback allows the agent's original output ("Processing complete!") to be used.
2. In the session *with* that state flag, the callback intercepts the agent's original output and appends it with its own message ("Concluding note added...").
Expand Down Expand Up @@ -267,9 +291,8 @@ These callbacks are also specific to `LlmAgent` and trigger around the execution
ADK compares the returned value against `None`, so an empty `dict` counts
as an override: the tool is skipped and `{}` becomes the tool result.
Return `None`, not `{}`, when you want the tool to execute. With a list of
callbacks this applies to the last value produced, because an empty `dict`
does not stop the chain and is discarded if a later callback returns
something else.
callbacks, the first non-`None` response, including an empty `dict`, stops
the chain. Later callbacks do not run and cannot replace that response.

??? "Code"
=== "Python"
Expand Down Expand Up @@ -318,6 +341,8 @@ These callbacks are also specific to `LlmAgent` and trigger around the execution

ADK also compares the returned value against `None`, so returning `{}`
replaces the tool response with `{}`. Return `None` to keep the original.
With a list of callbacks, an empty `dict` stops the chain; later callbacks
do not run.

??? "Code"
=== "Python"
Expand Down