google-adk 1.2.1__py3-none-any.whl → 1.4.1__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.
- google/adk/a2a/__init__.py +13 -0
- google/adk/a2a/converters/__init__.py +13 -0
- google/adk/a2a/converters/part_converter.py +177 -0
- google/adk/agents/invocation_context.py +2 -0
- google/adk/agents/llm_agent.py +1 -6
- google/adk/agents/run_config.py +11 -0
- google/adk/auth/auth_credential.py +4 -0
- google/adk/auth/auth_handler.py +22 -96
- google/adk/auth/auth_preprocessor.py +3 -3
- google/adk/auth/auth_tool.py +46 -0
- google/adk/auth/credential_manager.py +261 -0
- google/adk/auth/credential_service/__init__.py +13 -0
- google/adk/auth/credential_service/base_credential_service.py +75 -0
- google/adk/auth/credential_service/in_memory_credential_service.py +64 -0
- google/adk/auth/exchanger/__init__.py +21 -0
- google/adk/auth/exchanger/base_credential_exchanger.py +57 -0
- google/adk/auth/exchanger/credential_exchanger_registry.py +58 -0
- google/adk/auth/exchanger/oauth2_credential_exchanger.py +104 -0
- google/adk/auth/oauth2_credential_util.py +107 -0
- google/adk/auth/refresher/__init__.py +21 -0
- google/adk/auth/refresher/base_credential_refresher.py +74 -0
- google/adk/auth/refresher/credential_refresher_registry.py +59 -0
- google/adk/auth/refresher/oauth2_credential_refresher.py +126 -0
- google/adk/cli/agent_graph.py +34 -32
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/main-JAAWEV7F.js +92 -0
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +10 -0
- google/adk/cli/cli_deploy.py +80 -21
- google/adk/cli/cli_tools_click.py +132 -61
- google/adk/cli/fast_api.py +46 -41
- google/adk/cli/utils/agent_loader.py +15 -2
- google/adk/code_executors/container_code_executor.py +10 -6
- google/adk/code_executors/vertex_ai_code_executor.py +8 -2
- google/adk/evaluation/_eval_set_results_manager_utils.py +44 -0
- google/adk/evaluation/_eval_sets_manager_utils.py +108 -0
- google/adk/evaluation/eval_metrics.py +0 -5
- google/adk/evaluation/eval_result.py +12 -7
- google/adk/evaluation/eval_set_results_manager.py +6 -1
- google/adk/evaluation/gcs_eval_set_results_manager.py +121 -0
- google/adk/evaluation/gcs_eval_sets_manager.py +196 -0
- google/adk/evaluation/local_eval_set_results_manager.py +6 -18
- google/adk/evaluation/local_eval_sets_manager.py +27 -78
- google/adk/flows/llm_flows/basic.py +9 -0
- google/adk/flows/llm_flows/functions.py +1 -2
- google/adk/models/anthropic_llm.py +1 -1
- google/adk/models/gemini_llm_connection.py +2 -0
- google/adk/models/google_llm.py +57 -16
- google/adk/models/lite_llm.py +2 -1
- google/adk/platform/__init__.py +13 -0
- google/adk/platform/internal/__init__.py +15 -0
- google/adk/platform/internal/thread.py +30 -0
- google/adk/platform/thread.py +31 -0
- google/adk/runners.py +8 -2
- google/adk/sessions/in_memory_session_service.py +12 -1
- google/adk/sessions/vertex_ai_session_service.py +71 -50
- google/adk/tools/__init__.py +2 -0
- google/adk/tools/_automatic_function_calling_util.py +1 -0
- google/adk/tools/_forwarding_artifact_service.py +96 -0
- google/adk/tools/_function_parameter_parse_util.py +1 -0
- google/adk/tools/agent_tool.py +5 -39
- google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -2
- google/adk/tools/authenticated_function_tool.py +107 -0
- google/adk/tools/base_authenticated_tool.py +107 -0
- google/adk/tools/bigquery/bigquery_credentials.py +6 -4
- google/adk/tools/bigquery/bigquery_tool.py +22 -9
- google/adk/tools/bigquery/bigquery_toolset.py +9 -3
- google/adk/tools/bigquery/client.py +7 -3
- google/adk/tools/bigquery/config.py +46 -0
- google/adk/tools/bigquery/metadata_tool.py +114 -91
- google/adk/tools/bigquery/query_tool.py +141 -23
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +7 -4
- google/adk/tools/google_search_tool.py +0 -1
- google/adk/tools/mcp_tool/__init__.py +6 -0
- google/adk/tools/mcp_tool/mcp_session_manager.py +271 -149
- google/adk/tools/mcp_tool/mcp_tool.py +73 -22
- google/adk/tools/mcp_tool/mcp_toolset.py +32 -29
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +3 -3
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +55 -33
- google/adk/tools/retrieval/files_retrieval.py +7 -1
- google/adk/tools/url_context_tool.py +61 -0
- google/adk/tools/vertex_ai_search_tool.py +13 -2
- google/adk/utils/feature_decorator.py +175 -0
- google/adk/version.py +1 -1
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/METADATA +10 -2
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/RECORD +89 -59
- google/adk/cli/browser/main-CS5OLUMF.js +0 -91
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/WHEEL +0 -0
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/entry_points.txt +0 -0
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -27,7 +27,11 @@ from google.genai import types as genai_types
|
|
27
27
|
from pydantic import ValidationError
|
28
28
|
from typing_extensions import override
|
29
29
|
|
30
|
-
from
|
30
|
+
from ._eval_sets_manager_utils import add_eval_case_to_eval_set
|
31
|
+
from ._eval_sets_manager_utils import delete_eval_case_from_eval_set
|
32
|
+
from ._eval_sets_manager_utils import get_eval_case_from_eval_set
|
33
|
+
from ._eval_sets_manager_utils import get_eval_set_from_app_and_id
|
34
|
+
from ._eval_sets_manager_utils import update_eval_case_in_eval_set
|
31
35
|
from .eval_case import EvalCase
|
32
36
|
from .eval_case import IntermediateData
|
33
37
|
from .eval_case import Invocation
|
@@ -218,7 +222,7 @@ class LocalEvalSetsManager(EvalSetsManager):
|
|
218
222
|
eval_cases=[],
|
219
223
|
creation_timestamp=time.time(),
|
220
224
|
)
|
221
|
-
self.
|
225
|
+
self._write_eval_set_to_path(new_eval_set_path, new_eval_set)
|
222
226
|
|
223
227
|
@override
|
224
228
|
def list_eval_sets(self, app_name: str) -> list[str]:
|
@@ -233,51 +237,27 @@ class LocalEvalSetsManager(EvalSetsManager):
|
|
233
237
|
|
234
238
|
return sorted(eval_sets)
|
235
239
|
|
236
|
-
@override
|
237
|
-
def add_eval_case(self, app_name: str, eval_set_id: str, eval_case: EvalCase):
|
238
|
-
"""Adds the given EvalCase to an existing EvalSet identified by app_name and eval_set_id.
|
239
|
-
|
240
|
-
Raises:
|
241
|
-
NotFoundError: If the eval set is not found.
|
242
|
-
"""
|
243
|
-
eval_case_id = eval_case.eval_id
|
244
|
-
self._validate_id(id_name="Eval Case Id", id_value=eval_case_id)
|
245
|
-
|
246
|
-
eval_set = self.get_eval_set(app_name, eval_set_id)
|
247
|
-
|
248
|
-
if not eval_set:
|
249
|
-
raise NotFoundError(f"Eval set `{eval_set_id}` not found.")
|
250
|
-
|
251
|
-
if [x for x in eval_set.eval_cases if x.eval_id == eval_case_id]:
|
252
|
-
raise ValueError(
|
253
|
-
f"Eval id `{eval_case_id}` already exists in `{eval_set_id}`"
|
254
|
-
" eval set.",
|
255
|
-
)
|
256
|
-
|
257
|
-
eval_set.eval_cases.append(eval_case)
|
258
|
-
|
259
|
-
eval_set_file_path = self._get_eval_set_file_path(app_name, eval_set_id)
|
260
|
-
self._write_eval_set(eval_set_file_path, eval_set)
|
261
|
-
|
262
240
|
@override
|
263
241
|
def get_eval_case(
|
264
242
|
self, app_name: str, eval_set_id: str, eval_case_id: str
|
265
243
|
) -> Optional[EvalCase]:
|
266
244
|
"""Returns an EvalCase if found, otherwise None."""
|
267
245
|
eval_set = self.get_eval_set(app_name, eval_set_id)
|
268
|
-
|
269
246
|
if not eval_set:
|
270
247
|
return None
|
248
|
+
return get_eval_case_from_eval_set(eval_set, eval_case_id)
|
271
249
|
|
272
|
-
|
250
|
+
@override
|
251
|
+
def add_eval_case(self, app_name: str, eval_set_id: str, eval_case: EvalCase):
|
252
|
+
"""Adds the given EvalCase to an existing EvalSet identified by app_name and eval_set_id.
|
273
253
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
254
|
+
Raises:
|
255
|
+
NotFoundError: If the eval set is not found.
|
256
|
+
"""
|
257
|
+
eval_set = get_eval_set_from_app_and_id(self, app_name, eval_set_id)
|
258
|
+
updated_eval_set = add_eval_case_to_eval_set(eval_set, eval_case)
|
279
259
|
|
280
|
-
|
260
|
+
self._save_eval_set(app_name, eval_set_id, updated_eval_set)
|
281
261
|
|
282
262
|
@override
|
283
263
|
def update_eval_case(
|
@@ -288,28 +268,9 @@ class LocalEvalSetsManager(EvalSetsManager):
|
|
288
268
|
Raises:
|
289
269
|
NotFoundError: If the eval set or the eval case is not found.
|
290
270
|
"""
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
eval_case_to_update = self.get_eval_case(
|
295
|
-
app_name, eval_set_id, eval_case_id
|
296
|
-
)
|
297
|
-
|
298
|
-
if eval_case_to_update:
|
299
|
-
# Remove the eval case from the existing eval set.
|
300
|
-
eval_set = self.get_eval_set(app_name, eval_set_id)
|
301
|
-
eval_set.eval_cases.remove(eval_case_to_update)
|
302
|
-
|
303
|
-
# Add the updated eval case to the existing eval set.
|
304
|
-
eval_set.eval_cases.append(updated_eval_case)
|
305
|
-
|
306
|
-
# Persit the eval set.
|
307
|
-
eval_set_file_path = self._get_eval_set_file_path(app_name, eval_set_id)
|
308
|
-
self._write_eval_set(eval_set_file_path, eval_set)
|
309
|
-
else:
|
310
|
-
raise NotFoundError(
|
311
|
-
f"Eval Set `{eval_set_id}` or Eval id `{eval_case_id}` not found.",
|
312
|
-
)
|
271
|
+
eval_set = get_eval_set_from_app_and_id(self, app_name, eval_set_id)
|
272
|
+
updated_eval_set = update_eval_case_in_eval_set(eval_set, updated_eval_case)
|
273
|
+
self._save_eval_set(app_name, eval_set_id, updated_eval_set)
|
313
274
|
|
314
275
|
@override
|
315
276
|
def delete_eval_case(
|
@@ -320,25 +281,9 @@ class LocalEvalSetsManager(EvalSetsManager):
|
|
320
281
|
Raises:
|
321
282
|
NotFoundError: If the eval set or the eval case to delete is not found.
|
322
283
|
"""
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
)
|
327
|
-
|
328
|
-
if eval_case_to_remove:
|
329
|
-
logger.info(
|
330
|
-
"EvalCase`%s` was found in the eval set. It will be removed"
|
331
|
-
" permanently.",
|
332
|
-
eval_case_id,
|
333
|
-
)
|
334
|
-
eval_set = self.get_eval_set(app_name, eval_set_id)
|
335
|
-
eval_set.eval_cases.remove(eval_case_to_remove)
|
336
|
-
eval_set_file_path = self._get_eval_set_file_path(app_name, eval_set_id)
|
337
|
-
self._write_eval_set(eval_set_file_path, eval_set)
|
338
|
-
else:
|
339
|
-
raise NotFoundError(
|
340
|
-
f"Eval Set `{eval_set_id}` or Eval id `{eval_case_id}` not found.",
|
341
|
-
)
|
284
|
+
eval_set = get_eval_set_from_app_and_id(self, app_name, eval_set_id)
|
285
|
+
updated_eval_set = delete_eval_case_from_eval_set(eval_set, eval_case_id)
|
286
|
+
self._save_eval_set(app_name, eval_set_id, updated_eval_set)
|
342
287
|
|
343
288
|
def _get_eval_set_file_path(self, app_name: str, eval_set_id: str) -> str:
|
344
289
|
return os.path.join(
|
@@ -354,6 +299,10 @@ class LocalEvalSetsManager(EvalSetsManager):
|
|
354
299
|
f"Invalid {id_name}. {id_name} should have the `{pattern}` format",
|
355
300
|
)
|
356
301
|
|
357
|
-
def
|
302
|
+
def _write_eval_set_to_path(self, eval_set_path: str, eval_set: EvalSet):
|
358
303
|
with open(eval_set_path, "w") as f:
|
359
304
|
f.write(eval_set.model_dump_json(indent=2))
|
305
|
+
|
306
|
+
def _save_eval_set(self, app_name: str, eval_set_id: str, eval_set: EvalSet):
|
307
|
+
eval_set_file_path = self._get_eval_set_file_path(app_name, eval_set_id)
|
308
|
+
self._write_eval_set_to_path(eval_set_file_path, eval_set)
|
@@ -65,6 +65,15 @@ class _BasicLlmRequestProcessor(BaseLlmRequestProcessor):
|
|
65
65
|
llm_request.live_connect_config.input_audio_transcription = (
|
66
66
|
invocation_context.run_config.input_audio_transcription
|
67
67
|
)
|
68
|
+
llm_request.live_connect_config.realtime_input_config = (
|
69
|
+
invocation_context.run_config.realtime_input_config
|
70
|
+
)
|
71
|
+
llm_request.live_connect_config.enable_affective_dialog = (
|
72
|
+
invocation_context.run_config.enable_affective_dialog
|
73
|
+
)
|
74
|
+
llm_request.live_connect_config.proactivity = (
|
75
|
+
invocation_context.run_config.proactivity
|
76
|
+
)
|
68
77
|
|
69
78
|
# TODO: handle tool append here, instead of in BaseTool.process_llm_request.
|
70
79
|
|
@@ -288,8 +288,7 @@ async def handle_function_calls_live(
|
|
288
288
|
trace_tool_call(
|
289
289
|
tool=tool,
|
290
290
|
args=function_args,
|
291
|
-
|
292
|
-
function_response=function_response,
|
291
|
+
function_response_event=function_response_event,
|
293
292
|
)
|
294
293
|
function_response_events.append(function_response_event)
|
295
294
|
|
@@ -201,7 +201,7 @@ def function_declaration_to_tool_param(
|
|
201
201
|
|
202
202
|
|
203
203
|
class Claude(BaseLlm):
|
204
|
-
"""
|
204
|
+
"""Integration with Claude models served from Vertex AI.
|
205
205
|
|
206
206
|
Attributes:
|
207
207
|
model: The name of the Claude model.
|
google/adk/models/google_llm.py
CHANGED
@@ -23,6 +23,7 @@ import sys
|
|
23
23
|
from typing import AsyncGenerator
|
24
24
|
from typing import cast
|
25
25
|
from typing import TYPE_CHECKING
|
26
|
+
from typing import Union
|
26
27
|
|
27
28
|
from google.genai import Client
|
28
29
|
from google.genai import types
|
@@ -94,6 +95,13 @@ class Gemini(BaseLlm):
|
|
94
95
|
)
|
95
96
|
logger.info(_build_request_log(llm_request))
|
96
97
|
|
98
|
+
# add tracking headers to custom headers given it will override the headers
|
99
|
+
# set in the api client constructor
|
100
|
+
if llm_request.config and llm_request.config.http_options:
|
101
|
+
if not llm_request.config.http_options.headers:
|
102
|
+
llm_request.config.http_options.headers = {}
|
103
|
+
llm_request.config.http_options.headers.update(self._tracking_headers)
|
104
|
+
|
97
105
|
if stream:
|
98
106
|
responses = await self.api_client.aio.models.generate_content_stream(
|
99
107
|
model=llm_request.model,
|
@@ -200,24 +208,21 @@ class Gemini(BaseLlm):
|
|
200
208
|
return tracking_headers
|
201
209
|
|
202
210
|
@cached_property
|
203
|
-
def
|
211
|
+
def _live_api_version(self) -> str:
|
204
212
|
if self._api_backend == GoogleLLMVariant.VERTEX_AI:
|
205
213
|
# use beta version for vertex api
|
206
|
-
|
207
|
-
# use default api version for vertex
|
208
|
-
return Client(
|
209
|
-
http_options=types.HttpOptions(
|
210
|
-
headers=self._tracking_headers, api_version=api_version
|
211
|
-
)
|
212
|
-
)
|
214
|
+
return 'v1beta1'
|
213
215
|
else:
|
214
216
|
# use v1alpha for using API KEY from Google AI Studio
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
217
|
+
return 'v1alpha'
|
218
|
+
|
219
|
+
@cached_property
|
220
|
+
def _live_api_client(self) -> Client:
|
221
|
+
return Client(
|
222
|
+
http_options=types.HttpOptions(
|
223
|
+
headers=self._tracking_headers, api_version=self._live_api_version
|
224
|
+
)
|
225
|
+
)
|
221
226
|
|
222
227
|
@contextlib.asynccontextmanager
|
223
228
|
async def connect(self, llm_request: LlmRequest) -> BaseLlmConnection:
|
@@ -229,6 +234,21 @@ class Gemini(BaseLlm):
|
|
229
234
|
Yields:
|
230
235
|
BaseLlmConnection, the connection to the Gemini model.
|
231
236
|
"""
|
237
|
+
# add tracking headers to custom headers and set api_version given
|
238
|
+
# the customized http options will override the one set in the api client
|
239
|
+
# constructor
|
240
|
+
if (
|
241
|
+
llm_request.live_connect_config
|
242
|
+
and llm_request.live_connect_config.http_options
|
243
|
+
):
|
244
|
+
if not llm_request.live_connect_config.http_options.headers:
|
245
|
+
llm_request.live_connect_config.http_options.headers = {}
|
246
|
+
llm_request.live_connect_config.http_options.headers.update(
|
247
|
+
self._tracking_headers
|
248
|
+
)
|
249
|
+
llm_request.live_connect_config.http_options.api_version = (
|
250
|
+
self._live_api_version
|
251
|
+
)
|
232
252
|
|
233
253
|
llm_request.live_connect_config.system_instruction = types.Content(
|
234
254
|
role='system',
|
@@ -244,9 +264,18 @@ class Gemini(BaseLlm):
|
|
244
264
|
|
245
265
|
def _preprocess_request(self, llm_request: LlmRequest) -> None:
|
246
266
|
|
247
|
-
if
|
267
|
+
if self._api_backend == GoogleLLMVariant.GEMINI_API:
|
248
268
|
# Using API key from Google AI Studio to call model doesn't support labels.
|
249
|
-
llm_request.config
|
269
|
+
if llm_request.config:
|
270
|
+
llm_request.config.labels = None
|
271
|
+
|
272
|
+
if llm_request.contents:
|
273
|
+
for content in llm_request.contents:
|
274
|
+
if not content.parts:
|
275
|
+
continue
|
276
|
+
for part in content.parts:
|
277
|
+
_remove_display_name_if_present(part.inline_data)
|
278
|
+
_remove_display_name_if_present(part.file_data)
|
250
279
|
|
251
280
|
|
252
281
|
def _build_function_declaration_log(
|
@@ -324,3 +353,15 @@ Raw response:
|
|
324
353
|
{resp.model_dump_json(exclude_none=True)}
|
325
354
|
-----------------------------------------------------------
|
326
355
|
"""
|
356
|
+
|
357
|
+
|
358
|
+
def _remove_display_name_if_present(
|
359
|
+
data_obj: Union[types.Blob, types.FileData, None],
|
360
|
+
):
|
361
|
+
"""Sets display_name to None for the Gemini API (non-Vertex) backend.
|
362
|
+
|
363
|
+
This backend does not support the display_name parameter for file uploads,
|
364
|
+
so it must be removed to prevent request failures.
|
365
|
+
"""
|
366
|
+
if data_obj and data_obj.display_name:
|
367
|
+
data_obj.display_name = None
|
google/adk/models/lite_llm.py
CHANGED
@@ -739,11 +739,12 @@ class LiteLlm(BaseLlm):
|
|
739
739
|
_message_to_generate_content_response(
|
740
740
|
ChatCompletionAssistantMessage(
|
741
741
|
role="assistant",
|
742
|
-
content=
|
742
|
+
content=text,
|
743
743
|
tool_calls=tool_calls,
|
744
744
|
)
|
745
745
|
)
|
746
746
|
)
|
747
|
+
text = ""
|
747
748
|
function_calls.clear()
|
748
749
|
elif finish_reason == "stop" and text:
|
749
750
|
aggregated_llm_response = _message_to_generate_content_response(
|
@@ -0,0 +1,13 @@
|
|
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.
|
@@ -0,0 +1,15 @@
|
|
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
|
+
# Code in this directory is internal to Google.
|
@@ -0,0 +1,30 @@
|
|
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 asyncio
|
18
|
+
from typing import Callable
|
19
|
+
|
20
|
+
from google3.learning.deepmind.python.threading import g3_executor
|
21
|
+
from google3.learning.deepmind.python.threading import g3_thread
|
22
|
+
|
23
|
+
# TODO(b/423882251): Switch to copybara replacements.
|
24
|
+
# Right now this doesn't work because the ADK runs unit and integration tests,
|
25
|
+
# outside google3, before copybara replacements.
|
26
|
+
|
27
|
+
|
28
|
+
def create_thread(target: Callable[..., None], *args, **kwargs):
|
29
|
+
"""Creates a thread."""
|
30
|
+
return g3_thread.G3Thread(target=target, args=args, kwargs=kwargs)
|
@@ -0,0 +1,31 @@
|
|
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 threading
|
18
|
+
from typing import Callable
|
19
|
+
|
20
|
+
internal_thread = None
|
21
|
+
try:
|
22
|
+
from .internal import thread as internal_thread
|
23
|
+
except ImportError:
|
24
|
+
internal_thread = None
|
25
|
+
|
26
|
+
|
27
|
+
def create_thread(target: Callable[..., None], *args, **kwargs):
|
28
|
+
"""Creates a thread."""
|
29
|
+
if internal_thread:
|
30
|
+
return internal_thread.create_thread(target, *args, **kwargs)
|
31
|
+
return threading.Thread(target=target, args=args, kwargs=kwargs)
|
google/adk/runners.py
CHANGED
@@ -17,7 +17,6 @@ from __future__ import annotations
|
|
17
17
|
import asyncio
|
18
18
|
import logging
|
19
19
|
import queue
|
20
|
-
import threading
|
21
20
|
from typing import AsyncGenerator
|
22
21
|
from typing import Generator
|
23
22
|
from typing import Optional
|
@@ -34,10 +33,12 @@ from .agents.llm_agent import LlmAgent
|
|
34
33
|
from .agents.run_config import RunConfig
|
35
34
|
from .artifacts.base_artifact_service import BaseArtifactService
|
36
35
|
from .artifacts.in_memory_artifact_service import InMemoryArtifactService
|
36
|
+
from .auth.credential_service.base_credential_service import BaseCredentialService
|
37
37
|
from .code_executors.built_in_code_executor import BuiltInCodeExecutor
|
38
38
|
from .events.event import Event
|
39
39
|
from .memory.base_memory_service import BaseMemoryService
|
40
40
|
from .memory.in_memory_memory_service import InMemoryMemoryService
|
41
|
+
from .platform.thread import create_thread
|
41
42
|
from .sessions.base_session_service import BaseSessionService
|
42
43
|
from .sessions.in_memory_session_service import InMemorySessionService
|
43
44
|
from .sessions.session import Session
|
@@ -72,6 +73,8 @@ class Runner:
|
|
72
73
|
"""The session service for the runner."""
|
73
74
|
memory_service: Optional[BaseMemoryService] = None
|
74
75
|
"""The memory service for the runner."""
|
76
|
+
credential_service: Optional[BaseCredentialService] = None
|
77
|
+
"""The credential service for the runner."""
|
75
78
|
|
76
79
|
def __init__(
|
77
80
|
self,
|
@@ -81,6 +84,7 @@ class Runner:
|
|
81
84
|
artifact_service: Optional[BaseArtifactService] = None,
|
82
85
|
session_service: BaseSessionService,
|
83
86
|
memory_service: Optional[BaseMemoryService] = None,
|
87
|
+
credential_service: Optional[BaseCredentialService] = None,
|
84
88
|
):
|
85
89
|
"""Initializes the Runner.
|
86
90
|
|
@@ -96,6 +100,7 @@ class Runner:
|
|
96
100
|
self.artifact_service = artifact_service
|
97
101
|
self.session_service = session_service
|
98
102
|
self.memory_service = memory_service
|
103
|
+
self.credential_service = credential_service
|
99
104
|
|
100
105
|
def run(
|
101
106
|
self,
|
@@ -139,7 +144,7 @@ class Runner:
|
|
139
144
|
finally:
|
140
145
|
event_queue.put(None)
|
141
146
|
|
142
|
-
thread =
|
147
|
+
thread = create_thread(target=_asyncio_thread_main)
|
143
148
|
thread.start()
|
144
149
|
|
145
150
|
# consumes and re-yield the events from background thread.
|
@@ -417,6 +422,7 @@ class Runner:
|
|
417
422
|
artifact_service=self.artifact_service,
|
418
423
|
session_service=self.session_service,
|
419
424
|
memory_service=self.memory_service,
|
425
|
+
credential_service=self.credential_service,
|
420
426
|
invocation_id=invocation_id,
|
421
427
|
agent=self.agent,
|
422
428
|
session=session,
|
@@ -11,6 +11,7 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
+
from __future__ import annotations
|
14
15
|
|
15
16
|
import copy
|
16
17
|
import logging
|
@@ -223,6 +224,7 @@ class InMemorySessionService(BaseSessionService):
|
|
223
224
|
sessions_without_events.append(copied_session)
|
224
225
|
return ListSessionsResponse(sessions=sessions_without_events)
|
225
226
|
|
227
|
+
@override
|
226
228
|
async def delete_session(
|
227
229
|
self, *, app_name: str, user_id: str, session_id: str
|
228
230
|
) -> None:
|
@@ -247,7 +249,7 @@ class InMemorySessionService(BaseSessionService):
|
|
247
249
|
)
|
248
250
|
is None
|
249
251
|
):
|
250
|
-
return
|
252
|
+
return
|
251
253
|
|
252
254
|
self.sessions[app_name][user_id].pop(session_id)
|
253
255
|
|
@@ -261,11 +263,20 @@ class InMemorySessionService(BaseSessionService):
|
|
261
263
|
app_name = session.app_name
|
262
264
|
user_id = session.user_id
|
263
265
|
session_id = session.id
|
266
|
+
|
267
|
+
def _warning(message: str) -> None:
|
268
|
+
logger.warning(
|
269
|
+
f'Failed to append event to session {session_id}: {message}'
|
270
|
+
)
|
271
|
+
|
264
272
|
if app_name not in self.sessions:
|
273
|
+
_warning(f'app_name {app_name} not in sessions')
|
265
274
|
return event
|
266
275
|
if user_id not in self.sessions[app_name]:
|
276
|
+
_warning(f'user_id {user_id} not in sessions[app_name]')
|
267
277
|
return event
|
268
278
|
if session_id not in self.sessions[app_name][user_id]:
|
279
|
+
_warning(f'session_id {session_id} not in sessions[app_name][user_id]')
|
269
280
|
return event
|
270
281
|
|
271
282
|
if event.actions and event.actions.state_delta:
|