From 3e8e759067c8d0b5acd20b684b32d2dc775b50f9 Mon Sep 17 00:00:00 2001 From: George Weale Date: Tue, 28 Jul 2026 17:09:23 +0000 Subject: [PATCH 1/2] docs(plugins): fix broken examples, correct plugin callback docs --- docs/plugins/index.md | 101 ++++++++++++++++++++++++++---------------- docs/skills/index.md | 12 ++++- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 18573613cc..2dcbe10600 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -70,6 +70,8 @@ immediately: Saves files included in user messages as Artifacts. * [**Logging**](https://github.com/google/adk-python/blame/main/src/google/adk/plugins/logging_plugin.py): Log important information at each agent workflow callback point. +* [**Debug Logging**](https://github.com/google/adk-python/blob/main/src/google/adk/plugins/debug_logging_plugin.py): + Captures complete debug information for each invocation to a YAML file. ## Define and register Plugins @@ -92,28 +94,28 @@ methods, as shown in the following code example: from google.adk.plugins.base_plugin import BasePlugin class CountInvocationPlugin(BasePlugin): - """A custom plugin that counts agent and tool invocations.""" - - def __init__(self) -> None: - """Initialize the plugin with counters.""" - super().__init__(name="count_invocation") - self.agent_count: int = 0 - self.tool_count: int = 0 - self.llm_request_count: int = 0 - - async def before_agent_callback( - self, *, agent: BaseAgent, callback_context: CallbackContext - ) -> None: - """Count agent runs.""" - self.agent_count += 1 - print(f"[Plugin] Agent run count: {self.agent_count}") - - async def before_model_callback( - self, *, callback_context: CallbackContext, llm_request: LlmRequest - ) -> None: - """Count LLM requests.""" - self.llm_request_count += 1 - print(f"[Plugin] LLM request count: {self.llm_request_count}") + """A custom plugin that counts agent and tool invocations.""" + + def __init__(self) -> None: + """Initialize the plugin with counters.""" + super().__init__(name="count_invocation") + self.agent_count: int = 0 + self.tool_count: int = 0 + self.llm_request_count: int = 0 + + async def before_agent_callback( + self, *, agent: BaseAgent, callback_context: CallbackContext + ) -> None: + """Count agent runs.""" + self.agent_count += 1 + print(f"[Plugin] Agent run count: {self.agent_count}") + + async def before_model_callback( + self, *, callback_context: CallbackContext, llm_request: LlmRequest + ) -> None: + """Count LLM requests.""" + self.llm_request_count += 1 + print(f"[Plugin] LLM request count: {self.llm_request_count}") ``` === "Typescript" @@ -266,6 +268,14 @@ multiple Plugins with this parameter. The following code example shows how to register the `CountInvocationPlugin` plugin defined in the previous section with a simple ADK agent. +!!! note "Python: prefer `App(plugins=...)` over `Runner(plugins=...)`" + + In Python, the `plugins` parameter of `Runner` and `InMemoryRunner` is + deprecated and raises a `DeprecationWarning`. Set `plugins` on an + [`App`](/apps/) instead and pass that `App` to the runner as + `InMemoryRunner(app=app)`. Passing both `plugins` and `app` raises a + `ValueError`. + === "Python" ```py @@ -281,14 +291,14 @@ a simple ADK agent. async def hello_world(tool_context: ToolContext, query: str): print(f'Hello world: query is [{query}]') - root_agent = Agent( - model='gemini-flash-latest', - name='hello_world', - description='Prints hello world with user query.', - instruction="""Use hello_world tool to print hello world and user query. - """, - tools=[hello_world], - ) + root_agent = Agent( + model='gemini-flash-latest', + name='hello_world', + description='Prints hello world with user query.', + instruction="""Use hello_world tool to print hello world and user query. + """, + tools=[hello_world], + ) async def main(): """Main entry point for the agent.""" @@ -847,7 +857,10 @@ The following code example shows the basic syntax of this callback: before the agent's main work begins. The main work encompasses the agent's entire process for handling the request, which could involve calling models or tools. After the agent has finished all its steps and prepared a result, the -`after_agent_callback` runs. +`after_agent_callback` runs. In Python, if the agent's run raises an exception, +`on_agent_error_callback(agent, callback_context, error)` runs instead of +`after_agent_callback`. That callback only observes the failure: its return +value is ignored and the original exception is still raised. **Caution:** Plugins that implement these callbacks are executed *before* the Agent-level callbacks are executed. Furthermore, if a Plugin-level agent @@ -876,8 +889,7 @@ Furthermore, if a Plugin-level model callback returns anything other than a #### Model on error callback details -The on error callback for Model objects is only supported by the Plugins -feature works as follows: +The on error callback for Model objects works as follows: - **When It Runs:** When an exception is raised during the model call. - **Common Use Cases:** Graceful error handling, logging the specific @@ -959,8 +971,7 @@ is *not executed* (skipped). #### Tool on error callback details -The on error callback for Tool objects is only supported by the Plugins feature -works as follows: +The on error callback for Tool objects works as follows: - **When It Runs:** When an exception is raised during the execution of a tool's `run` method. @@ -1031,8 +1042,10 @@ before it's streamed to the client. to the user. An agent's run may produce multiple events. - **Purpose:** Useful for modifying or enriching events (e.g., adding metadata) or for triggering side effects based on specific events. -- **Flow Control:** Return an `Event` object to **replace** the original - event. +- **Flow Control:** Return an `Event` object to **override** the original + event. In Python, ADK merges your event onto the original: only the fields + you set are applied, and `id`, `invocation_id`, and `timestamp` always come + from the original event. The following code example shows the basic syntax of this callback: @@ -1095,7 +1108,7 @@ The following code example shows the basic syntax of this callback: ```py async def after_run_callback( self, *, invocation_context: InvocationContext - ) -> Optional[None]: + ) -> None: ``` === "Typescript" @@ -1124,6 +1137,18 @@ The following code example shows the basic syntax of this callback: } ``` +In Python, ADK notifies your Plugin of two more end-of-life events: + +- **`on_run_error_callback(invocation_context, error)`**: Runs instead of + `after_run_callback` when the run fails with an unhandled exception. This + callback only observes the failure: its return value is ignored and the + original exception is still raised. +- **`close()`**: Runs once per Plugin when you close the `Runner` with + `await runner.close()`, not once per run. Use it to release resources the + Plugin owns, such as an HTTP client or a metrics exporter. Each `close()` + call is bounded by the runner's `plugin_close_timeout`, five seconds by + default. + ## Next steps Check out these resources for developing and applying Plugins to your ADK diff --git a/docs/skills/index.md b/docs/skills/index.md index 9143a7dad4..b666c78265 100644 --- a/docs/skills/index.md +++ b/docs/skills/index.md @@ -35,9 +35,16 @@ You can define [skills in code](#inline-skills) or load from google.adk.tools import skill_toolset weather_skill = load_skill_from_dir( - pathlib.Path(__file__).parent / "skills" / "weather_skill" + pathlib.Path(__file__).parent / "skills" / "weather-skill" ) + def get_weather_tool(city: str) -> dict: + """Retrieves the current weather report for a specified city.""" + return { + "status": "success", + "report": f"The weather in {city} is sunny with a temperature of 25°C.", + } + my_skill_toolset = skill_toolset.SkillToolset( skills=[weather_skill], additional_tools=[get_weather_tool], @@ -155,6 +162,9 @@ meets the following requirements: * Must not be empty. * Must be 1024 characters or less. +When loading a Skill from the filesystem, the directory name must match the +**name** in the frontmatter, or loading fails. + ### Skills directory structure The following directory structure shows the recommended way to include Skills in From b00885ade387b0f6f2758bb0b79c82b937d72593 Mon Sep 17 00:00:00 2001 From: George Weale Date: Thu, 30 Jul 2026 00:22:19 +0000 Subject: [PATCH 2/2] docs(plugins): scope before_run halt to non-LlmAgent roots, migrate to App(plugins=) --- docs/plugins/index.md | 44 ++++++++++++++++++++++++++----------------- docs/skills/index.md | 4 ++-- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 2dcbe10600..526cb1e5b3 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -263,10 +263,10 @@ of the agent. ### Register Plugin class Integrate your Plugin class by registering it during your agent initialization -as part of your `Runner` class, using the `plugins` parameter. You can specify -multiple Plugins with this parameter. The following code example shows how to -register the `CountInvocationPlugin` plugin defined in the previous section with -a simple ADK agent. +as part of your `Runner` class (in Python, your `App` object), using the +`plugins` parameter. You can specify multiple Plugins with this parameter. The +following code example shows how to register the `CountInvocationPlugin` plugin +defined in the previous section with a simple ADK agent. !!! note "Python: prefer `App(plugins=...)` over `Runner(plugins=...)`" @@ -281,6 +281,7 @@ a simple ADK agent. ```py from google.adk.runners import InMemoryRunner from google.adk import Agent + from google.adk.apps import App from google.adk.tools.tool_context import ToolContext from google.genai import types import asyncio @@ -300,16 +301,18 @@ a simple ADK agent. tools=[hello_world], ) + app = App( + name='test_app_with_plugin', + root_agent=root_agent, + + # Add your plugin here. You can add multiple plugins. + plugins=[CountInvocationPlugin()], + ) + async def main(): """Main entry point for the agent.""" prompt = 'hello world' - runner = InMemoryRunner( - agent=root_agent, - app_name='test_app_with_plugin', - - # Add your plugin here. You can add multiple plugins. - plugins=[CountInvocationPlugin()], - ) + runner = InMemoryRunner(app=app) # The rest is the same as starting a regular ADK runner. session = await runner.session_service.create_session( @@ -721,9 +724,10 @@ state of a single agent. You define when a Plugin is called with the callback functions to define in your Plugin class. Callbacks are available when a user message is received, before and after an `Runner`, `Agent`, `Model`, or `Tool` is called, for -`Events`, and when a `Model`, or `Tool` error occurs. These callbacks include, -and take precedence over, the any callbacks defined within your Agent, Model, -and Tool classes. +`Events`, and when a `Model`, or `Tool` error occurs. In Python, error +callbacks also run when an `Agent` raises an exception and when the run itself +fails. These callbacks include, and take precedence over, the any callbacks +defined within your Agent, Model, and Tool classes. The following diagram illustrates callback points where you can attach and run Plugin functionality during your agents workflow: @@ -740,6 +744,7 @@ more detail. - [Agent execution callbacks](#agent-execution-callbacks) - [Model callbacks](#model-callbacks) - [Tool callbacks](#tool-callbacks) +- [Event callbacks](#event-callbacks) - [Runner end callbacks](#runner-end-callbacks) ### User Message callbacks @@ -811,7 +816,12 @@ logic begins. - **Purpose:** Global setup or initialization before the invocation runs. - **Flow Control:** Return a `types.Content` object to **halt execution**: the `Runner` exits early and ends the run with that content as the result. - Return `None` to proceed normally. + Return `None` to proceed normally. In Python, `run_async()` honors this + return value only when the root agent is a `BaseAgent` that is not an + `LlmAgent`. When the root agent is an `LlmAgent` or a `Workflow`, the + returned content is ignored and the run proceeds as if you had returned + `None`; use `before_agent_callback` or `before_model_callback` to + short-circuit those runs instead. The following code example shows the basic syntax of this callback: @@ -858,7 +868,7 @@ before the agent's main work begins. The main work encompasses the agent's entire process for handling the request, which could involve calling models or tools. After the agent has finished all its steps and prepared a result, the `after_agent_callback` runs. In Python, if the agent's run raises an exception, -`on_agent_error_callback(agent, callback_context, error)` runs instead of +`on_agent_error_callback(*, agent, callback_context, error)` runs instead of `after_agent_callback`. That callback only observes the failure: its return value is ignored and the original exception is still raised. @@ -1139,7 +1149,7 @@ The following code example shows the basic syntax of this callback: In Python, ADK notifies your Plugin of two more end-of-life events: -- **`on_run_error_callback(invocation_context, error)`**: Runs instead of +- **`on_run_error_callback(*, invocation_context, error)`**: Runs instead of `after_run_callback` when the run fails with an unhandled exception. This callback only observes the failure: its return value is ignored and the original exception is still raised. diff --git a/docs/skills/index.md b/docs/skills/index.md index b666c78265..22f2bb175c 100644 --- a/docs/skills/index.md +++ b/docs/skills/index.md @@ -108,9 +108,9 @@ You can define [skills in code](#inline-skills) or load For a complete example, see the code sample in [skills](https://github.com/google/adk-go/tree/main/examples/skills). -!!! note "Check your working directory" +!!! note "Check where `skills/` is resolved from" - Ensure that 'skills/' directory exist in your current working directory and contains the sub-directories for the Skills you want to use in your agent. + The Python and TypeScript examples above resolve `skills/` relative to the directory holding the agent source file, so place `skills/` next to that file. The Go example uses `os.DirFS("./skills")`, which resolves relative to the current working directory instead. Either way, the `skills/` directory must contain the sub-directories for the Skills you want to use in your agent. ## Skill structure