google-adk 1.6.1__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. google/adk/a2a/converters/event_converter.py +5 -85
  2. google/adk/a2a/executor/a2a_agent_executor.py +45 -16
  3. google/adk/agents/__init__.py +5 -0
  4. google/adk/agents/agent_config.py +46 -0
  5. google/adk/agents/base_agent.py +234 -41
  6. google/adk/agents/callback_context.py +41 -0
  7. google/adk/agents/common_configs.py +79 -0
  8. google/adk/agents/config_agent_utils.py +184 -0
  9. google/adk/agents/config_schemas/AgentConfig.json +544 -0
  10. google/adk/agents/invocation_context.py +5 -1
  11. google/adk/agents/llm_agent.py +190 -9
  12. google/adk/agents/loop_agent.py +29 -0
  13. google/adk/agents/parallel_agent.py +24 -3
  14. google/adk/agents/remote_a2a_agent.py +15 -3
  15. google/adk/agents/sequential_agent.py +22 -1
  16. google/adk/artifacts/gcs_artifact_service.py +24 -2
  17. google/adk/auth/auth_handler.py +3 -3
  18. google/adk/auth/credential_manager.py +23 -23
  19. google/adk/auth/credential_service/base_credential_service.py +6 -6
  20. google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
  21. google/adk/auth/credential_service/session_state_credential_service.py +8 -8
  22. google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
  23. google/adk/auth/oauth2_credential_util.py +2 -2
  24. google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
  25. google/adk/cli/agent_graph.py +3 -1
  26. google/adk/cli/browser/index.html +1 -1
  27. google/adk/cli/browser/main-SRBSE46V.js +3914 -0
  28. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  29. google/adk/cli/fast_api.py +42 -2
  30. google/adk/cli/utils/agent_loader.py +35 -1
  31. google/adk/code_executors/base_code_executor.py +14 -19
  32. google/adk/code_executors/built_in_code_executor.py +4 -1
  33. google/adk/evaluation/base_eval_service.py +46 -2
  34. google/adk/evaluation/evaluation_generator.py +1 -1
  35. google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
  36. google/adk/evaluation/local_eval_service.py +389 -0
  37. google/adk/evaluation/local_eval_sets_manager.py +23 -8
  38. google/adk/flows/llm_flows/auto_flow.py +6 -11
  39. google/adk/flows/llm_flows/base_llm_flow.py +41 -23
  40. google/adk/flows/llm_flows/contents.py +16 -10
  41. google/adk/flows/llm_flows/functions.py +76 -33
  42. google/adk/memory/in_memory_memory_service.py +20 -14
  43. google/adk/models/anthropic_llm.py +44 -5
  44. google/adk/models/google_llm.py +11 -6
  45. google/adk/models/lite_llm.py +21 -4
  46. google/adk/plugins/__init__.py +17 -0
  47. google/adk/plugins/base_plugin.py +317 -0
  48. google/adk/plugins/plugin_manager.py +265 -0
  49. google/adk/runners.py +122 -18
  50. google/adk/sessions/database_session_service.py +26 -28
  51. google/adk/sessions/vertex_ai_session_service.py +14 -7
  52. google/adk/tools/agent_tool.py +1 -0
  53. google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
  54. google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
  55. google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
  56. google/adk/tools/base_tool.py +9 -9
  57. google/adk/tools/base_toolset.py +7 -5
  58. google/adk/tools/bigquery/__init__.py +3 -3
  59. google/adk/tools/enterprise_search_tool.py +4 -2
  60. google/adk/tools/google_api_tool/google_api_tool.py +16 -1
  61. google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
  62. google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
  63. google/adk/tools/google_search_tool.py +4 -2
  64. google/adk/tools/langchain_tool.py +2 -3
  65. google/adk/tools/long_running_tool.py +21 -0
  66. google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
  67. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
  68. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
  69. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
  70. google/adk/tools/tool_context.py +0 -10
  71. google/adk/tools/url_context_tool.py +4 -2
  72. google/adk/tools/vertex_ai_search_tool.py +4 -2
  73. google/adk/utils/model_name_utils.py +90 -0
  74. google/adk/version.py +1 -1
  75. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/METADATA +2 -2
  76. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/RECORD +79 -69
  77. google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
  78. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
  79. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/WHEEL +0 -0
  80. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/entry_points.txt +0 -0
  81. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,317 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may in obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC
18
+ from typing import Any
19
+ from typing import Optional
20
+ from typing import TYPE_CHECKING
21
+ from typing import TypeVar
22
+
23
+ from google.genai import types
24
+
25
+ from ..agents.base_agent import BaseAgent
26
+ from ..agents.callback_context import CallbackContext
27
+ from ..events.event import Event
28
+ from ..models.llm_request import LlmRequest
29
+ from ..models.llm_response import LlmResponse
30
+ from ..tools.base_tool import BaseTool
31
+ from ..utils.feature_decorator import working_in_progress
32
+
33
+ if TYPE_CHECKING:
34
+ from ..agents.invocation_context import InvocationContext
35
+ from ..tools.tool_context import ToolContext
36
+
37
+
38
+ # Type alias: The value may or may not be awaitable, and value is optional.
39
+ T = TypeVar("T")
40
+
41
+
42
+ class BasePlugin(ABC):
43
+ """Base class for creating plugins.
44
+
45
+ Plugins provide a structured way to intercept and modify agent, tool, and
46
+ LLM behaviors at critical execution points in a callback manner. While agent
47
+ callbacks apply to a particular agent, plugins applies globally to all
48
+ agents added in the runner. Plugins are best used for adding custom behaviors
49
+ like logging, monitoring, caching, or modifying requests and responses at key
50
+ stages.
51
+
52
+ A plugin can implement one or more methods of callbacks, but should not
53
+ implement the same method of callback for multiple times.
54
+
55
+ Relation with [Agent callbacks](https://google.github.io/adk-docs/callbacks/):
56
+
57
+ **Execution Order**
58
+ Similar to Agent callbacks, Plugins are executed in the order they are
59
+ registered. However, Plugin and Agent Callbacks are executed sequentially,
60
+ with Plugins takes precedence over agent callbacks. When the callback in a
61
+ plugin returns a value, it will short circuit all remaining plugins and
62
+ agent callbacks, causing all remaining plugins and agent callbacks
63
+ to be skipped.
64
+
65
+ **Change Propagation**
66
+ Plugins and agent callbacks can both modify the value of the input parameters,
67
+ including agent input, tool input, and LLM request/response, etc. They work in
68
+ the exactly same way. The modifications will be visible and passed to the next
69
+ callback in the chain. For example, if a plugin modifies the tool input with
70
+ before_tool_callback, the modified tool input will be passed to the
71
+ before_tool_callback of the next plugin, and further passed to the agent
72
+ callbacks if not short circuited.
73
+
74
+ To use a plugin, implement the desired callback methods and pass an instance
75
+ of your custom plugin class to the ADK Runner.
76
+
77
+ Examples:
78
+ A simple plugin that logs every tool call.
79
+
80
+ >>> class ToolLoggerPlugin(BasePlugin):
81
+ .. def __init__(self):
82
+ .. super().__init__(name="tool_logger")
83
+ ..
84
+ .. async def before_tool_callback(
85
+ .. self, *, tool: BaseTool, tool_args: dict[str, Any],
86
+ tool_context:
87
+ ToolContext
88
+ .. ):
89
+ .. print(f"[{self.name}] Calling tool '{tool.name}' with args:
90
+ {tool_args}")
91
+ ..
92
+ .. async def after_tool_callback(
93
+ .. self, *, tool: BaseTool, tool_args: dict, tool_context:
94
+ ToolContext, result: dict
95
+ .. ):
96
+ .. print(f"[{self.name}] Tool '{tool.name}' finished with result:
97
+ {result}")
98
+ ..
99
+ >>> # Add the plugin to ADK Runner
100
+ >>> # runner = Runner(
101
+ >>> # ...
102
+ >>> # plugins=[ToolLoggerPlugin(), AgentPolicyPlugin()],
103
+ >>> # )
104
+ """
105
+
106
+ def __init__(self, name: str):
107
+ """Initializes the plugin.
108
+
109
+ Args:
110
+ name: A unique identifier for this plugin instance.
111
+ """
112
+ super().__init__()
113
+ self.name = name
114
+
115
+ async def on_user_message_callback(
116
+ self,
117
+ *,
118
+ invocation_context: InvocationContext,
119
+ user_message: types.Content,
120
+ ) -> Optional[types.Content]:
121
+ """Callback executed when a user message is received before an invocation starts.
122
+
123
+ This callback helps logging and modifying the user message before the
124
+ runner starts the invocation.
125
+
126
+ Args:
127
+ invocation_context: The context for the entire invocation.
128
+ user_message: The message content input by user.
129
+
130
+ Returns:
131
+ An optional `types.Content` to be returned to the ADK. Returning a
132
+ value to replace the user message. Returning `None` to proceed
133
+ normally.
134
+ """
135
+ pass
136
+
137
+ async def before_run_callback(
138
+ self, *, invocation_context: InvocationContext
139
+ ) -> Optional[types.Content]:
140
+ """Callback executed before the ADK runner runs.
141
+
142
+ This is the first callback to be called in the lifecycle, ideal for global
143
+ setup or initialization tasks.
144
+
145
+ Args:
146
+ invocation_context: The context for the entire invocation, containing
147
+ session information, the root agent, etc.
148
+
149
+ Returns:
150
+ An optional `Event` to be returned to the ADK. Returning a value to
151
+ halt execution of the runner and ends the runner with that event. Return
152
+ `None` to proceed normally.
153
+ """
154
+ pass
155
+
156
+ async def on_event_callback(
157
+ self, *, invocation_context: InvocationContext, event: Event
158
+ ) -> Optional[Event]:
159
+ """Callback executed after an event is yielded from runner.
160
+
161
+ This is the ideal place to make modification to the event before the event
162
+ is handled by the underlying agent app.
163
+
164
+ Args:
165
+ invocation_context: The context for the entire invocation.
166
+ event: The event raised by the runner.
167
+
168
+ Returns:
169
+ An optional value. A non-`None` return may be used by the framework to
170
+ modify or replace the response. Returning `None` allows the original
171
+ response to be used.
172
+ """
173
+ pass
174
+
175
+ async def after_run_callback(
176
+ self, *, invocation_context: InvocationContext
177
+ ) -> Optional[None]:
178
+ """Callback executed after an ADK runner run has completed.
179
+
180
+ This is the final callback in the ADK lifecycle, suitable for cleanup, final
181
+ logging, or reporting tasks.
182
+
183
+ Args:
184
+ invocation_context: The context for the entire invocation.
185
+
186
+ Returns:
187
+ None
188
+ """
189
+ pass
190
+
191
+ async def before_agent_callback(
192
+ self, *, agent: BaseAgent, callback_context: CallbackContext
193
+ ) -> Optional[types.Content]:
194
+ """Callback executed before an agent's primary logic is invoked.
195
+
196
+ This callback can be used for logging, setup, or to short-circuit the
197
+ agent's execution by returning a value.
198
+
199
+ Args:
200
+ agent: The agent that is about to run.
201
+ callback_context: The context for the agent invocation.
202
+
203
+ Returns:
204
+ An optional `types.Content` object. If a value is returned, it will bypass
205
+ the agent's callbacks and its execution, and return this value directly.
206
+ Returning `None` allows the agent to proceed normally.
207
+ """
208
+ pass
209
+
210
+ async def after_agent_callback(
211
+ self, *, agent: BaseAgent, callback_context: CallbackContext
212
+ ) -> Optional[types.Content]:
213
+ """Callback executed after an agent's primary logic has completed.
214
+
215
+ This callback can be used to inspect, log, or modify the agent's final
216
+ result before it is returned.
217
+
218
+ Args:
219
+ agent: The agent that has just run.
220
+ callback_context: The context for the agent invocation.
221
+
222
+ Returns:
223
+ An optional `types.Content` object. If a value is returned, it will
224
+ replace the agent's original result. Returning `None` uses the original,
225
+ unmodified result.
226
+ """
227
+ pass
228
+
229
+ async def before_model_callback(
230
+ self, *, callback_context: CallbackContext, llm_request: LlmRequest
231
+ ) -> Optional[LlmResponse]:
232
+ """Callback executed before a request is sent to the model.
233
+
234
+ This provides an opportunity to inspect, log, or modify the `LlmRequest`
235
+ object. It can also be used to implement caching by returning a cached
236
+ `LlmResponse`, which would skip the actual model call.
237
+
238
+ Args:
239
+ callback_context: The context for the current agent call.
240
+ llm_request: The prepared request object to be sent to the model.
241
+
242
+ Returns:
243
+ An optional value. The interpretation of a non-`None` trigger an early
244
+ exit and returns the response immediately. Returning `None` allows the LLM
245
+ request to proceed normally.
246
+ """
247
+ pass
248
+
249
+ async def after_model_callback(
250
+ self, *, callback_context: CallbackContext, llm_response: LlmResponse
251
+ ) -> Optional[LlmResponse]:
252
+ """Callback executed after a response is received from the model.
253
+
254
+ This is the ideal place to log model responses, collect metrics on token
255
+ usage, or perform post-processing on the raw `LlmResponse`.
256
+
257
+ Args:
258
+ callback_context: The context for the current agent call.
259
+ llm_response: The response object received from the model.
260
+
261
+ Returns:
262
+ An optional value. A non-`None` return may be used by the framework to
263
+ modify or replace the response. Returning `None` allows the original
264
+ response to be used.
265
+ """
266
+ pass
267
+
268
+ async def before_tool_callback(
269
+ self,
270
+ *,
271
+ tool: BaseTool,
272
+ tool_args: dict[str, Any],
273
+ tool_context: ToolContext,
274
+ ) -> Optional[dict]:
275
+ """Callback executed before a tool is called.
276
+
277
+ This callback is useful for logging tool usage, input validation, or
278
+ modifying the arguments before they are passed to the tool.
279
+
280
+ Args:
281
+ tool: The tool instance that is about to be executed.
282
+ tool_args: The dictionary of arguments to be used for invoking the tool.
283
+ tool_context: The context specific to the tool execution.
284
+
285
+ Returns:
286
+ An optional dictionary. If a dictionary is returned, it will stop the tool
287
+ execution and return this response immediately. Returning `None` uses the
288
+ original, unmodified arguments.
289
+ """
290
+ pass
291
+
292
+ async def after_tool_callback(
293
+ self,
294
+ *,
295
+ tool: BaseTool,
296
+ tool_args: dict[str, Any],
297
+ tool_context: ToolContext,
298
+ result: dict,
299
+ ) -> Optional[dict]:
300
+ """Callback executed after a tool has been called.
301
+
302
+ This callback allows for inspecting, logging, or modifying the result
303
+ returned by a tool.
304
+
305
+ Args:
306
+ tool: The tool instance that has just been executed.
307
+ tool_args: The original arguments that were passed to the tool.
308
+ tool_context: The context specific to the tool execution.
309
+ result: The dictionary returned by the tool invocation.
310
+
311
+ Returns:
312
+ An optional dictionary. If a dictionary is returned, it will **replace**
313
+ the original result from the tool. This allows for post-processing or
314
+ altering tool outputs. Returning `None` uses the original, unmodified
315
+ result.
316
+ """
317
+ pass
@@ -0,0 +1,265 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import Any
19
+ from typing import List
20
+ from typing import Literal
21
+ from typing import Optional
22
+ from typing import TYPE_CHECKING
23
+
24
+ from google.genai import types
25
+
26
+ from .base_plugin import BasePlugin
27
+
28
+ if TYPE_CHECKING:
29
+ from ..agents.base_agent import BaseAgent
30
+ from ..agents.callback_context import CallbackContext
31
+ from ..agents.invocation_context import InvocationContext
32
+ from ..events.event import Event
33
+ from ..models.llm_request import LlmRequest
34
+ from ..models.llm_response import LlmResponse
35
+ from ..tools.base_tool import BaseTool
36
+ from ..tools.tool_context import ToolContext
37
+
38
+ # A type alias for the names of the available plugin callbacks.
39
+ # This helps with static analysis and prevents typos when calling run_callbacks.
40
+ PluginCallbackName = Literal[
41
+ "on_user_message_callback",
42
+ "before_run_callback",
43
+ "after_run_callback",
44
+ "on_event_callback",
45
+ "before_agent_callback",
46
+ "after_agent_callback",
47
+ "before_tool_callback",
48
+ "after_tool_callback",
49
+ "before_model_callback",
50
+ "after_model_callback",
51
+ ]
52
+
53
+ logger = logging.getLogger("google_adk." + __name__)
54
+
55
+
56
+ class PluginManager:
57
+ """Manages the registration and execution of plugins.
58
+
59
+ The PluginManager is an internal class that orchestrates the invocation of
60
+ plugin callbacks at key points in the SDK's execution lifecycle. It maintains
61
+ a list of registered plugins and ensures they are called in the order they
62
+ were registered.
63
+
64
+ The core execution logic implements an "early exit" strategy: if any plugin
65
+ callback returns a non-`None` value, the execution of subsequent plugins for
66
+ that specific event is halted, and the returned value is propagated up the
67
+ call stack. This allows plugins to short-circuit operations like agent runs,
68
+ tool calls, or model requests.
69
+ """
70
+
71
+ def __init__(self, plugins: Optional[List[BasePlugin]] = None):
72
+ """Initializes the plugin service.
73
+
74
+ Args:
75
+ plugins: An optional list of plugins to register upon initialization.
76
+ """
77
+ self.plugins: List[BasePlugin] = []
78
+ if plugins:
79
+ for plugin in plugins:
80
+ self.register_plugin(plugin)
81
+
82
+ def register_plugin(self, plugin: BasePlugin) -> None:
83
+ """Registers a new plugin.
84
+
85
+ Args:
86
+ plugin: The plugin instance to register.
87
+
88
+ Raises:
89
+ ValueError: If a plugin with the same name is already registered.
90
+ """
91
+ if any(p.name == plugin.name for p in self.plugins):
92
+ raise ValueError(f"Plugin with name '{plugin.name}' already registered.")
93
+ self.plugins.append(plugin)
94
+ logger.info("Plugin '%s' registered.", plugin.name)
95
+
96
+ def get_plugin(self, plugin_name: str) -> Optional[BasePlugin]:
97
+ """Retrieves a registered plugin by its name.
98
+
99
+ Args:
100
+ plugin_name: The name of the plugin to retrieve.
101
+
102
+ Returns:
103
+ The plugin instance if found, otherwise `None`.
104
+ """
105
+ return next((p for p in self.plugins if p.name == plugin_name), None)
106
+
107
+ async def run_on_user_message_callback(
108
+ self,
109
+ *,
110
+ user_message: types.Content,
111
+ invocation_context: InvocationContext,
112
+ ) -> Optional[types.Content]:
113
+ """Runs the `on_user_message_callback` for all plugins."""
114
+ return await self._run_callbacks(
115
+ "on_user_message_callback",
116
+ user_message=user_message,
117
+ invocation_context=invocation_context,
118
+ )
119
+
120
+ async def run_before_run_callback(
121
+ self, *, invocation_context: InvocationContext
122
+ ) -> Optional[types.Content]:
123
+ """Runs the `before_run_callback` for all plugins."""
124
+ return await self._run_callbacks(
125
+ "before_run_callback", invocation_context=invocation_context
126
+ )
127
+
128
+ async def run_after_run_callback(
129
+ self, *, invocation_context: InvocationContext
130
+ ) -> Optional[None]:
131
+ """Runs the `after_run_callback` for all plugins."""
132
+ return await self._run_callbacks(
133
+ "after_run_callback", invocation_context=invocation_context
134
+ )
135
+
136
+ async def run_on_event_callback(
137
+ self, *, invocation_context: InvocationContext, event: Event
138
+ ) -> Optional[Event]:
139
+ """Runs the `on_event_callback` for all plugins."""
140
+ return await self._run_callbacks(
141
+ "on_event_callback",
142
+ invocation_context=invocation_context,
143
+ event=event,
144
+ )
145
+
146
+ async def run_before_agent_callback(
147
+ self, *, agent: BaseAgent, callback_context: CallbackContext
148
+ ) -> Optional[types.Content]:
149
+ """Runs the `before_agent_callback` for all plugins."""
150
+ return await self._run_callbacks(
151
+ "before_agent_callback",
152
+ agent=agent,
153
+ callback_context=callback_context,
154
+ )
155
+
156
+ async def run_after_agent_callback(
157
+ self, *, agent: BaseAgent, callback_context: CallbackContext
158
+ ) -> Optional[types.Content]:
159
+ """Runs the `after_agent_callback` for all plugins."""
160
+ return await self._run_callbacks(
161
+ "after_agent_callback",
162
+ agent=agent,
163
+ callback_context=callback_context,
164
+ )
165
+
166
+ async def run_before_tool_callback(
167
+ self,
168
+ *,
169
+ tool: BaseTool,
170
+ tool_args: dict[str, Any],
171
+ tool_context: ToolContext,
172
+ ) -> Optional[dict]:
173
+ """Runs the `before_tool_callback` for all plugins."""
174
+ return await self._run_callbacks(
175
+ "before_tool_callback",
176
+ tool=tool,
177
+ tool_args=tool_args,
178
+ tool_context=tool_context,
179
+ )
180
+
181
+ async def run_after_tool_callback(
182
+ self,
183
+ *,
184
+ tool: BaseTool,
185
+ tool_args: dict[str, Any],
186
+ tool_context: ToolContext,
187
+ result: dict,
188
+ ) -> Optional[dict]:
189
+ """Runs the `after_tool_callback` for all plugins."""
190
+ return await self._run_callbacks(
191
+ "after_tool_callback",
192
+ tool=tool,
193
+ tool_args=tool_args,
194
+ tool_context=tool_context,
195
+ result=result,
196
+ )
197
+
198
+ async def run_before_model_callback(
199
+ self, *, callback_context: CallbackContext, llm_request: LlmRequest
200
+ ) -> Optional[LlmResponse]:
201
+ """Runs the `before_model_callback` for all plugins."""
202
+ return await self._run_callbacks(
203
+ "before_model_callback",
204
+ callback_context=callback_context,
205
+ llm_request=llm_request,
206
+ )
207
+
208
+ async def run_after_model_callback(
209
+ self, *, callback_context: CallbackContext, llm_response: LlmResponse
210
+ ) -> Optional[LlmResponse]:
211
+ """Runs the `after_model_callback` for all plugins."""
212
+ return await self._run_callbacks(
213
+ "after_model_callback",
214
+ callback_context=callback_context,
215
+ llm_response=llm_response,
216
+ )
217
+
218
+ async def _run_callbacks(
219
+ self, callback_name: PluginCallbackName, **kwargs: Any
220
+ ) -> Optional[Any]:
221
+ """Executes a specific callback for all registered plugins.
222
+
223
+ This private method iterates through the plugins and calls the specified
224
+ callback method on each one, passing the provided keyword arguments.
225
+
226
+ The execution stops as soon as a plugin's callback returns a non-`None`
227
+ value. This "early exit" value is then returned by this method. If all
228
+ plugins are executed and all return `None`, this method also returns `None`.
229
+
230
+ Args:
231
+ callback_name: The name of the callback method to execute.
232
+ **kwargs: Keyword arguments to be passed to the callback method.
233
+
234
+ Returns:
235
+ The first non-`None` value returned by a plugin callback, or `None` if
236
+ all callbacks return `None`.
237
+
238
+ Raises:
239
+ RuntimeError: If a plugin encounters an unhandled exception during
240
+ execution. The original exception is chained.
241
+ """
242
+ for plugin in self.plugins:
243
+ # Each plugin might not implement all callbacks. The base class provides
244
+ # default `pass` implementations, so `getattr` will always succeed.
245
+ callback_method = getattr(plugin, callback_name)
246
+ try:
247
+ result = await callback_method(**kwargs)
248
+ if result is not None:
249
+ # Early exit: A plugin has returned a value. We stop
250
+ # processing further plugins and return this value immediately.
251
+ logger.debug(
252
+ "Plugin '%s' returned a value for callback '%s', exiting early.",
253
+ plugin.name,
254
+ callback_name,
255
+ )
256
+ return result
257
+ except Exception as e:
258
+ error_message = (
259
+ f"Error in plugin '{plugin.name}' during '{callback_name}'"
260
+ f" callback: {e}"
261
+ )
262
+ logger.error(error_message, exc_info=True)
263
+ raise RuntimeError(error_message) from e
264
+
265
+ return None