google-adk 1.6.1__py3-none-any.whl → 1.8.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 (110) hide show
  1. google/adk/a2a/converters/event_converter.py +5 -85
  2. google/adk/a2a/converters/request_converter.py +1 -2
  3. google/adk/a2a/executor/a2a_agent_executor.py +45 -16
  4. google/adk/a2a/logs/log_utils.py +1 -2
  5. google/adk/a2a/utils/__init__.py +0 -0
  6. google/adk/a2a/utils/agent_card_builder.py +544 -0
  7. google/adk/a2a/utils/agent_to_a2a.py +118 -0
  8. google/adk/agents/__init__.py +5 -0
  9. google/adk/agents/agent_config.py +46 -0
  10. google/adk/agents/base_agent.py +239 -41
  11. google/adk/agents/callback_context.py +41 -0
  12. google/adk/agents/common_configs.py +79 -0
  13. google/adk/agents/config_agent_utils.py +184 -0
  14. google/adk/agents/config_schemas/AgentConfig.json +566 -0
  15. google/adk/agents/invocation_context.py +5 -1
  16. google/adk/agents/live_request_queue.py +15 -0
  17. google/adk/agents/llm_agent.py +201 -9
  18. google/adk/agents/loop_agent.py +35 -1
  19. google/adk/agents/parallel_agent.py +24 -3
  20. google/adk/agents/remote_a2a_agent.py +17 -5
  21. google/adk/agents/sequential_agent.py +22 -1
  22. google/adk/artifacts/gcs_artifact_service.py +110 -20
  23. google/adk/auth/auth_handler.py +3 -3
  24. google/adk/auth/credential_manager.py +23 -23
  25. google/adk/auth/credential_service/base_credential_service.py +6 -6
  26. google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
  27. google/adk/auth/credential_service/session_state_credential_service.py +8 -8
  28. google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
  29. google/adk/auth/oauth2_credential_util.py +2 -2
  30. google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
  31. google/adk/cli/agent_graph.py +3 -1
  32. google/adk/cli/browser/index.html +2 -2
  33. google/adk/cli/browser/main-W7QZBYAR.js +3914 -0
  34. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  35. google/adk/cli/cli_eval.py +87 -12
  36. google/adk/cli/cli_tools_click.py +143 -82
  37. google/adk/cli/fast_api.py +150 -69
  38. google/adk/cli/utils/agent_loader.py +35 -1
  39. google/adk/code_executors/base_code_executor.py +14 -19
  40. google/adk/code_executors/built_in_code_executor.py +4 -1
  41. google/adk/evaluation/base_eval_service.py +46 -2
  42. google/adk/evaluation/eval_metrics.py +4 -0
  43. google/adk/evaluation/eval_sets_manager.py +5 -1
  44. google/adk/evaluation/evaluation_generator.py +1 -1
  45. google/adk/evaluation/final_response_match_v2.py +2 -2
  46. google/adk/evaluation/gcs_eval_sets_manager.py +2 -1
  47. google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
  48. google/adk/evaluation/local_eval_service.py +389 -0
  49. google/adk/evaluation/local_eval_set_results_manager.py +2 -2
  50. google/adk/evaluation/local_eval_sets_manager.py +24 -9
  51. google/adk/evaluation/metric_evaluator_registry.py +16 -6
  52. google/adk/evaluation/vertex_ai_eval_facade.py +7 -1
  53. google/adk/events/event.py +7 -2
  54. google/adk/flows/llm_flows/auto_flow.py +6 -11
  55. google/adk/flows/llm_flows/base_llm_flow.py +66 -29
  56. google/adk/flows/llm_flows/contents.py +16 -10
  57. google/adk/flows/llm_flows/functions.py +89 -52
  58. google/adk/memory/in_memory_memory_service.py +21 -15
  59. google/adk/memory/vertex_ai_memory_bank_service.py +12 -10
  60. google/adk/models/anthropic_llm.py +46 -6
  61. google/adk/models/base_llm_connection.py +2 -0
  62. google/adk/models/gemini_llm_connection.py +17 -6
  63. google/adk/models/google_llm.py +46 -11
  64. google/adk/models/lite_llm.py +52 -22
  65. google/adk/plugins/__init__.py +17 -0
  66. google/adk/plugins/base_plugin.py +317 -0
  67. google/adk/plugins/plugin_manager.py +265 -0
  68. google/adk/runners.py +122 -18
  69. google/adk/sessions/database_session_service.py +51 -52
  70. google/adk/sessions/vertex_ai_session_service.py +27 -12
  71. google/adk/tools/__init__.py +2 -0
  72. google/adk/tools/_automatic_function_calling_util.py +20 -2
  73. google/adk/tools/agent_tool.py +15 -3
  74. google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
  75. google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
  76. google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
  77. google/adk/tools/base_tool.py +9 -9
  78. google/adk/tools/base_toolset.py +29 -5
  79. google/adk/tools/bigquery/__init__.py +3 -3
  80. google/adk/tools/bigquery/metadata_tool.py +2 -0
  81. google/adk/tools/bigquery/query_tool.py +15 -1
  82. google/adk/tools/computer_use/__init__.py +13 -0
  83. google/adk/tools/computer_use/base_computer.py +265 -0
  84. google/adk/tools/computer_use/computer_use_tool.py +166 -0
  85. google/adk/tools/computer_use/computer_use_toolset.py +220 -0
  86. google/adk/tools/enterprise_search_tool.py +4 -2
  87. google/adk/tools/exit_loop_tool.py +1 -0
  88. google/adk/tools/google_api_tool/google_api_tool.py +16 -1
  89. google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
  90. google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
  91. google/adk/tools/google_search_tool.py +4 -2
  92. google/adk/tools/langchain_tool.py +16 -6
  93. google/adk/tools/long_running_tool.py +21 -0
  94. google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
  95. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +5 -0
  96. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
  97. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
  98. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
  99. google/adk/tools/tool_context.py +0 -10
  100. google/adk/tools/url_context_tool.py +4 -2
  101. google/adk/tools/vertex_ai_search_tool.py +4 -2
  102. google/adk/utils/model_name_utils.py +90 -0
  103. google/adk/version.py +1 -1
  104. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/METADATA +3 -2
  105. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/RECORD +108 -91
  106. google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
  107. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
  108. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/WHEEL +0 -0
  109. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/entry_points.txt +0 -0
  110. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -20,18 +20,21 @@ import json
20
20
  import logging
21
21
  import os
22
22
  from pathlib import Path
23
+ import shutil
23
24
  import time
24
25
  import traceback
25
26
  import typing
26
27
  from typing import Any
27
28
  from typing import List
28
29
  from typing import Literal
30
+ from typing import Mapping
29
31
  from typing import Optional
30
32
 
31
33
  import click
32
34
  from fastapi import FastAPI
33
35
  from fastapi import HTTPException
34
36
  from fastapi import Query
37
+ from fastapi import UploadFile
35
38
  from fastapi.middleware.cors import CORSMiddleware
36
39
  from fastapi.responses import RedirectResponse
37
40
  from fastapi.responses import StreamingResponse
@@ -41,7 +44,6 @@ from fastapi.websockets import WebSocketDisconnect
41
44
  from google.genai import types
42
45
  import graphviz
43
46
  from opentelemetry import trace
44
- from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
45
47
  from opentelemetry.sdk.trace import export
46
48
  from opentelemetry.sdk.trace import ReadableSpan
47
49
  from opentelemetry.sdk.trace import TracerProvider
@@ -60,6 +62,9 @@ from ..artifacts.gcs_artifact_service import GcsArtifactService
60
62
  from ..artifacts.in_memory_artifact_service import InMemoryArtifactService
61
63
  from ..auth.credential_service.in_memory_credential_service import InMemoryCredentialService
62
64
  from ..errors.not_found_error import NotFoundError
65
+ from ..evaluation.base_eval_service import InferenceConfig
66
+ from ..evaluation.base_eval_service import InferenceRequest
67
+ from ..evaluation.constants import MISSING_EVAL_DEPENDENCIES_MESSAGE
63
68
  from ..evaluation.eval_case import EvalCase
64
69
  from ..evaluation.eval_case import SessionInput
65
70
  from ..evaluation.eval_metrics import EvalMetric
@@ -71,12 +76,11 @@ from ..evaluation.local_eval_sets_manager import LocalEvalSetsManager
71
76
  from ..events.event import Event
72
77
  from ..memory.in_memory_memory_service import InMemoryMemoryService
73
78
  from ..memory.vertex_ai_memory_bank_service import VertexAiMemoryBankService
74
- from ..memory.vertex_ai_rag_memory_service import VertexAiRagMemoryService
75
79
  from ..runners import Runner
76
- from ..sessions.database_session_service import DatabaseSessionService
77
80
  from ..sessions.in_memory_session_service import InMemorySessionService
78
81
  from ..sessions.session import Session
79
82
  from ..sessions.vertex_ai_session_service import VertexAiSessionService
83
+ from ..utils.feature_decorator import working_in_progress
80
84
  from .cli_eval import EVAL_SESSION_ID_PREFIX
81
85
  from .cli_eval import EvalStatus
82
86
  from .utils import cleanup
@@ -175,6 +179,7 @@ class AgentRunRequest(common.BaseModel):
175
179
  session_id: str
176
180
  new_message: types.Content
177
181
  streaming: bool = False
182
+ state_delta: Optional[dict[str, Any]] = None
178
183
 
179
184
 
180
185
  class AddSessionToEvalSetRequest(common.BaseModel):
@@ -195,6 +200,7 @@ class RunEvalResult(common.BaseModel):
195
200
  final_eval_status: EvalStatus
196
201
  eval_metric_results: list[tuple[EvalMetric, EvalMetricResult]] = Field(
197
202
  deprecated=True,
203
+ default=[],
198
204
  description=(
199
205
  "This field is deprecated, use overall_eval_metric_results instead."
200
206
  ),
@@ -213,6 +219,7 @@ def get_fast_api_app(
213
219
  *,
214
220
  agents_dir: str,
215
221
  session_service_uri: Optional[str] = None,
222
+ session_db_kwargs: Optional[Mapping[str, Any]] = None,
216
223
  artifact_service_uri: Optional[str] = None,
217
224
  memory_service_uri: Optional[str] = None,
218
225
  eval_storage_uri: Optional[str] = None,
@@ -237,6 +244,8 @@ def get_fast_api_app(
237
244
  memory_exporter = InMemoryExporter(session_trace_dict)
238
245
  provider.add_span_processor(export.SimpleSpanProcessor(memory_exporter))
239
246
  if trace_to_cloud:
247
+ from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
248
+
240
249
  envs.load_dotenv_for_agent("", agents_dir)
241
250
  if project_id := os.environ.get("GOOGLE_CLOUD_PROJECT", None):
242
251
  processor = export.BatchSpanProcessor(
@@ -293,9 +302,36 @@ def get_fast_api_app(
293
302
  eval_sets_manager = LocalEvalSetsManager(agents_dir=agents_dir)
294
303
  eval_set_results_manager = LocalEvalSetResultsManager(agents_dir=agents_dir)
295
304
 
305
+ def _parse_agent_engine_resource_name(agent_engine_id_or_resource_name):
306
+ if not agent_engine_id_or_resource_name:
307
+ raise click.ClickException(
308
+ "Agent engine resource name or resource id can not be empty."
309
+ )
310
+
311
+ # "projects/my-project/locations/us-central1/reasoningEngines/1234567890",
312
+ if "/" in agent_engine_id_or_resource_name:
313
+ # Validate resource name.
314
+ if len(agent_engine_id_or_resource_name.split("/")) != 6:
315
+ raise click.ClickException(
316
+ "Agent engine resource name is mal-formatted. It should be of"
317
+ " format :"
318
+ " projects/{project_id}/locations/{location}/reasoningEngines/{resource_id}"
319
+ )
320
+ project = agent_engine_id_or_resource_name.split("/")[1]
321
+ location = agent_engine_id_or_resource_name.split("/")[3]
322
+ agent_engine_id = agent_engine_id_or_resource_name.split("/")[-1]
323
+ else:
324
+ envs.load_dotenv_for_agent("", agents_dir)
325
+ project = os.environ["GOOGLE_CLOUD_PROJECT"]
326
+ location = os.environ["GOOGLE_CLOUD_LOCATION"]
327
+ agent_engine_id = agent_engine_id_or_resource_name
328
+ return project, location, agent_engine_id
329
+
296
330
  # Build the Memory service
297
331
  if memory_service_uri:
298
332
  if memory_service_uri.startswith("rag://"):
333
+ from ..memory.vertex_ai_rag_memory_service import VertexAiRagMemoryService
334
+
299
335
  rag_corpus = memory_service_uri.split("://")[1]
300
336
  if not rag_corpus:
301
337
  raise click.ClickException("Rag corpus can not be empty.")
@@ -304,13 +340,13 @@ def get_fast_api_app(
304
340
  rag_corpus=f'projects/{os.environ["GOOGLE_CLOUD_PROJECT"]}/locations/{os.environ["GOOGLE_CLOUD_LOCATION"]}/ragCorpora/{rag_corpus}'
305
341
  )
306
342
  elif memory_service_uri.startswith("agentengine://"):
307
- agent_engine_id = memory_service_uri.split("://")[1]
308
- if not agent_engine_id:
309
- raise click.ClickException("Agent engine id can not be empty.")
310
- envs.load_dotenv_for_agent("", agents_dir)
343
+ agent_engine_id_or_resource_name = memory_service_uri.split("://")[1]
344
+ project, location, agent_engine_id = _parse_agent_engine_resource_name(
345
+ agent_engine_id_or_resource_name
346
+ )
311
347
  memory_service = VertexAiMemoryBankService(
312
- project=os.environ["GOOGLE_CLOUD_PROJECT"],
313
- location=os.environ["GOOGLE_CLOUD_LOCATION"],
348
+ project=project,
349
+ location=location,
314
350
  agent_engine_id=agent_engine_id,
315
351
  )
316
352
  else:
@@ -323,18 +359,24 @@ def get_fast_api_app(
323
359
  # Build the Session service
324
360
  if session_service_uri:
325
361
  if session_service_uri.startswith("agentengine://"):
326
- # Create vertex session service
327
- agent_engine_id = session_service_uri.split("://")[1]
328
- if not agent_engine_id:
329
- raise click.ClickException("Agent engine id can not be empty.")
330
- envs.load_dotenv_for_agent("", agents_dir)
362
+ agent_engine_id_or_resource_name = session_service_uri.split("://")[1]
363
+ project, location, agent_engine_id = _parse_agent_engine_resource_name(
364
+ agent_engine_id_or_resource_name
365
+ )
331
366
  session_service = VertexAiSessionService(
332
- project=os.environ["GOOGLE_CLOUD_PROJECT"],
333
- location=os.environ["GOOGLE_CLOUD_LOCATION"],
367
+ project=project,
368
+ location=location,
334
369
  agent_engine_id=agent_engine_id,
335
370
  )
336
371
  else:
337
- session_service = DatabaseSessionService(db_url=session_service_uri)
372
+ from ..sessions.database_session_service import DatabaseSessionService
373
+
374
+ # Database session additional settings
375
+ if session_db_kwargs is None:
376
+ session_db_kwargs = {}
377
+ session_service = DatabaseSessionService(
378
+ db_url=session_service_uri, **session_db_kwargs
379
+ )
338
380
  else:
339
381
  session_service = InMemorySessionService()
340
382
 
@@ -513,7 +555,11 @@ def get_fast_api_app(
513
555
  )
514
556
  def list_eval_sets(app_name: str) -> list[str]:
515
557
  """Lists all eval sets for the given app."""
516
- return eval_sets_manager.list_eval_sets(app_name)
558
+ try:
559
+ return eval_sets_manager.list_eval_sets(app_name)
560
+ except NotFoundError as e:
561
+ logger.warning(e)
562
+ return []
517
563
 
518
564
  @app.post(
519
565
  "/apps/{app_name}/eval_sets/{eval_set_id}/add_session",
@@ -629,63 +675,66 @@ def get_fast_api_app(
629
675
  app_name: str, eval_set_id: str, req: RunEvalRequest
630
676
  ) -> list[RunEvalResult]:
631
677
  """Runs an eval given the details in the eval request."""
632
- from .cli_eval import run_evals
633
-
634
678
  # Create a mapping from eval set file to all the evals that needed to be
635
679
  # run.
636
- eval_set = eval_sets_manager.get_eval_set(app_name, eval_set_id)
680
+ try:
681
+ from ..evaluation.local_eval_service import LocalEvalService
682
+ from .cli_eval import _collect_eval_results
683
+ from .cli_eval import _collect_inferences
637
684
 
638
- if not eval_set:
639
- raise HTTPException(
640
- status_code=400, detail=f"Eval set `{eval_set_id}` not found."
641
- )
685
+ eval_set = eval_sets_manager.get_eval_set(app_name, eval_set_id)
642
686
 
643
- if req.eval_ids:
644
- eval_cases = [e for e in eval_set.eval_cases if e.eval_id in req.eval_ids]
645
- eval_set_to_evals = {eval_set_id: eval_cases}
646
- else:
647
- logger.info("Eval ids to run list is empty. We will run all eval cases.")
648
- eval_set_to_evals = {eval_set_id: eval_set.eval_cases}
687
+ if not eval_set:
688
+ raise HTTPException(
689
+ status_code=400, detail=f"Eval set `{eval_set_id}` not found."
690
+ )
649
691
 
650
- root_agent = agent_loader.load_agent(app_name)
651
- run_eval_results = []
652
- eval_case_results = []
653
- try:
654
- async for eval_case_result in run_evals(
655
- eval_set_to_evals,
656
- root_agent,
657
- getattr(root_agent, "reset_data", None),
658
- req.eval_metrics,
692
+ root_agent = agent_loader.load_agent(app_name)
693
+
694
+ eval_case_results = []
695
+
696
+ eval_service = LocalEvalService(
697
+ root_agent=root_agent,
698
+ eval_sets_manager=eval_sets_manager,
699
+ eval_set_results_manager=eval_set_results_manager,
659
700
  session_service=session_service,
660
701
  artifact_service=artifact_service,
661
- ):
662
- run_eval_results.append(
663
- RunEvalResult(
664
- app_name=app_name,
665
- eval_set_file=eval_case_result.eval_set_file,
666
- eval_set_id=eval_set_id,
667
- eval_id=eval_case_result.eval_id,
668
- final_eval_status=eval_case_result.final_eval_status,
669
- eval_metric_results=eval_case_result.eval_metric_results,
670
- overall_eval_metric_results=eval_case_result.overall_eval_metric_results,
671
- eval_metric_result_per_invocation=eval_case_result.eval_metric_result_per_invocation,
672
- user_id=eval_case_result.user_id,
673
- session_id=eval_case_result.session_id,
674
- )
675
- )
676
- eval_case_result.session_details = await session_service.get_session(
677
- app_name=app_name,
678
- user_id=eval_case_result.user_id,
679
- session_id=eval_case_result.session_id,
680
- )
681
- eval_case_results.append(eval_case_result)
702
+ )
703
+ inference_request = InferenceRequest(
704
+ app_name=app_name,
705
+ eval_set_id=eval_set.eval_set_id,
706
+ eval_case_ids=req.eval_ids,
707
+ inference_config=InferenceConfig(),
708
+ )
709
+ inference_results = await _collect_inferences(
710
+ inference_requests=[inference_request], eval_service=eval_service
711
+ )
712
+
713
+ eval_case_results = await _collect_eval_results(
714
+ inference_results=inference_results,
715
+ eval_service=eval_service,
716
+ eval_metrics=req.eval_metrics,
717
+ )
682
718
  except ModuleNotFoundError as e:
683
719
  logger.exception("%s", e)
684
- raise HTTPException(status_code=400, detail=str(e)) from e
720
+ raise HTTPException(
721
+ status_code=400, detail=MISSING_EVAL_DEPENDENCIES_MESSAGE
722
+ ) from e
685
723
 
686
- eval_set_results_manager.save_eval_set_result(
687
- app_name, eval_set_id, eval_case_results
688
- )
724
+ run_eval_results = []
725
+ for eval_case_result in eval_case_results:
726
+ run_eval_results.append(
727
+ RunEvalResult(
728
+ eval_set_file=eval_case_result.eval_set_file,
729
+ eval_set_id=eval_set_id,
730
+ eval_id=eval_case_result.eval_id,
731
+ final_eval_status=eval_case_result.final_eval_status,
732
+ overall_eval_metric_results=eval_case_result.overall_eval_metric_results,
733
+ eval_metric_result_per_invocation=eval_case_result.eval_metric_result_per_invocation,
734
+ user_id=eval_case_result.user_id,
735
+ session_id=eval_case_result.session_id,
736
+ )
737
+ )
689
738
 
690
739
  return run_eval_results
691
740
 
@@ -803,6 +852,33 @@ def get_fast_api_app(
803
852
  filename=artifact_name,
804
853
  )
805
854
 
855
+ @working_in_progress("builder_save is not ready for use.")
856
+ @app.post("/builder/save", response_model_exclude_none=True)
857
+ async def builder_build(files: list[UploadFile]) -> bool:
858
+ base_path = Path.cwd() / agents_dir
859
+
860
+ for file in files:
861
+ try:
862
+ # File name format: {app_name}/{agent_name}.yaml
863
+ if not file.filename:
864
+ logger.exception("Agent name is missing in the input files")
865
+ return False
866
+
867
+ agent_name, filename = file.filename.split("/")
868
+
869
+ agent_dir = os.path.join(base_path, agent_name)
870
+ os.makedirs(agent_dir, exist_ok=True)
871
+ file_path = os.path.join(agent_dir, filename)
872
+
873
+ with open(file_path, "wb") as buffer:
874
+ shutil.copyfileobj(file.file, buffer)
875
+
876
+ except Exception as e:
877
+ logger.exception("Error in builder_build: %s", e)
878
+ return False
879
+
880
+ return True
881
+
806
882
  @app.post("/run", response_model_exclude_none=True)
807
883
  async def agent_run(req: AgentRunRequest) -> list[Event]:
808
884
  session = await session_service.get_session(
@@ -819,7 +895,8 @@ def get_fast_api_app(
819
895
  new_message=req.new_message,
820
896
  )
821
897
  ]
822
- logger.info("Generated %s events in agent run: %s", len(events), events)
898
+ logger.info("Generated %s events in agent run", len(events))
899
+ logger.debug("Events generated: %s", events)
823
900
  return events
824
901
 
825
902
  @app.post("/run_sse")
@@ -840,11 +917,12 @@ def get_fast_api_app(
840
917
  user_id=req.user_id,
841
918
  session_id=req.session_id,
842
919
  new_message=req.new_message,
920
+ state_delta=req.state_delta,
843
921
  run_config=RunConfig(streaming_mode=stream_mode),
844
922
  ):
845
923
  # Format as SSE data
846
924
  sse_event = event.model_dump_json(exclude_none=True, by_alias=True)
847
- logger.info("Generated event in agent run streaming: %s", sse_event)
925
+ logger.debug("Generated event in agent run streaming: %s", sse_event)
848
926
  yield f"data: {sse_event}\n\n"
849
927
  except Exception as e:
850
928
  logger.exception("Error in event_generator: %s", e)
@@ -1003,6 +1081,7 @@ def get_fast_api_app(
1003
1081
  from a2a.server.request_handlers import DefaultRequestHandler
1004
1082
  from a2a.server.tasks import InMemoryTaskStore
1005
1083
  from a2a.types import AgentCard
1084
+ from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
1006
1085
 
1007
1086
  from ..a2a.executor.a2a_agent_executor import A2aAgentExecutor
1008
1087
 
@@ -1066,7 +1145,7 @@ def get_fast_api_app(
1066
1145
 
1067
1146
  routes = a2a_app.routes(
1068
1147
  rpc_url=f"/a2a/{app_name}",
1069
- agent_card_url=f"/a2a/{app_name}/.well-known/agent.json",
1148
+ agent_card_url=f"/a2a/{app_name}{AGENT_CARD_WELL_KNOWN_PATH}",
1070
1149
  )
1071
1150
 
1072
1151
  for new_route in routes:
@@ -1096,7 +1175,9 @@ def get_fast_api_app(
1096
1175
 
1097
1176
  app.mount(
1098
1177
  "/dev-ui/",
1099
- StaticFiles(directory=ANGULAR_DIST_PATH, html=True),
1178
+ StaticFiles(
1179
+ directory=ANGULAR_DIST_PATH, html=True, follow_symlink=True
1180
+ ),
1100
1181
  name="static",
1101
1182
  )
1102
1183
  return app
@@ -16,11 +16,16 @@ from __future__ import annotations
16
16
 
17
17
  import importlib
18
18
  import logging
19
+ import os
19
20
  import sys
20
21
  from typing import Optional
21
22
 
23
+ from pydantic import ValidationError
24
+
22
25
  from . import envs
26
+ from ...agents import config_agent_utils
23
27
  from ...agents.base_agent import BaseAgent
28
+ from ...utils.feature_decorator import working_in_progress
24
29
 
25
30
  logger = logging.getLogger("google_adk." + __name__)
26
31
 
@@ -34,6 +39,8 @@ class AgentLoader:
34
39
  agents_dir/{agent_name}.py (with root_agent defined in the module)
35
40
  c) {agent_name} as a package name
36
41
  agents_dir/{agent_name}/__init__.py (with root_agent in the package)
42
+ d) {agent_name} as a YAML config folder:
43
+ agents_dir/{agent_name}/root_agent.yaml defines the root agent
37
44
 
38
45
  """
39
46
 
@@ -128,6 +135,29 @@ class AgentLoader:
128
135
 
129
136
  return None
130
137
 
138
+ @working_in_progress("_load_from_yaml_config is not ready for use.")
139
+ def _load_from_yaml_config(self, agent_name: str) -> Optional[BaseAgent]:
140
+ # Load from the config file at agents_dir/{agent_name}/root_agent.yaml
141
+ config_path = os.path.join(self.agents_dir, agent_name, "root_agent.yaml")
142
+ try:
143
+ agent = config_agent_utils.from_config(config_path)
144
+ logger.info("Loaded root agent for %s from %s", agent_name, config_path)
145
+ return agent
146
+ except FileNotFoundError:
147
+ logger.debug("Config file %s not found.", config_path)
148
+ return None
149
+ except ValidationError as e:
150
+ logger.error("Config file %s is invalid YAML.", config_path)
151
+ raise e
152
+ except Exception as e:
153
+ if hasattr(e, "msg"):
154
+ e.msg = f"Fail to load '{config_path}' config. " + e.msg
155
+ raise e
156
+ e.args = (
157
+ f"Fail to load '{config_path}' config. {e.args[0] if e.args else ''}",
158
+ ) + e.args[1:]
159
+ raise e
160
+
131
161
  def _perform_load(self, agent_name: str) -> BaseAgent:
132
162
  """Internal logic to load an agent"""
133
163
  # Add self.agents_dir to sys.path
@@ -145,10 +175,14 @@ class AgentLoader:
145
175
  if root_agent := self._load_from_submodule(agent_name):
146
176
  return root_agent
147
177
 
178
+ if root_agent := self._load_from_yaml_config(agent_name):
179
+ return root_agent
180
+
148
181
  # If no root_agent was found by any pattern
149
182
  raise ValueError(
150
183
  f"No root_agent found for '{agent_name}'. Searched in"
151
- f" '{agent_name}.agent.root_agent', '{agent_name}.root_agent'."
184
+ f" '{agent_name}.agent.root_agent', '{agent_name}.root_agent' and"
185
+ f" '{agent_name}/root_agent.yaml'."
152
186
  f" Ensure '{self.agents_dir}/{agent_name}' is structured correctly,"
153
187
  " an .env file can be loaded if present, and a root_agent is"
154
188
  " exposed."
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import abc
16
18
  from typing import List
17
19
 
@@ -42,42 +44,35 @@ class BaseCodeExecutor(BaseModel):
42
44
  """
43
45
 
44
46
  optimize_data_file: bool = False
45
- """
46
- If true, extract and process data files from the model request
47
+ """If true, extract and process data files from the model request
47
48
  and attach them to the code executor.
48
- Supported data file MimeTypes are [text/csv].
49
49
 
50
+ Supported data file MimeTypes are [text/csv].
50
51
  Default to False.
51
52
  """
52
53
 
53
54
  stateful: bool = False
54
- """
55
- Whether the code executor is stateful. Default to False.
56
- """
55
+ """Whether the code executor is stateful. Default to False."""
57
56
 
58
57
  error_retry_attempts: int = 2
59
- """
60
- The number of attempts to retry on consecutive code execution errors. Default to 2.
61
- """
58
+ """The number of attempts to retry on consecutive code execution errors. Default to 2."""
62
59
 
63
60
  code_block_delimiters: List[tuple[str, str]] = [
64
61
  ('```tool_code\n', '\n```'),
65
62
  ('```python\n', '\n```'),
66
63
  ]
67
- """
68
- The list of the enclosing delimiters to identify the code blocks.
69
- For example, the delimiter ('```python\n', '\n```') can be
70
- used to identify code blocks with the following format:
64
+ """The list of the enclosing delimiters to identify the code blocks.
71
65
 
72
- ```python
73
- print("hello")
74
- ```
66
+ For example, the delimiter ('```python\\n', '\\n```') can be
67
+ used to identify code blocks with the following format::
68
+
69
+ ```python
70
+ print("hello")
71
+ ```
75
72
  """
76
73
 
77
74
  execution_result_delimiters: tuple[str, str] = ('```tool_output\n', '\n```')
78
- """
79
- The delimiters to format the code execution result.
80
- """
75
+ """The delimiters to format the code execution result."""
81
76
 
82
77
  @abc.abstractmethod
83
78
  def execute_code(
@@ -12,11 +12,14 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from google.genai import types
16
18
  from typing_extensions import override
17
19
 
18
20
  from ..agents.invocation_context import InvocationContext
19
21
  from ..models import LlmRequest
22
+ from ..utils.model_name_utils import is_gemini_2_model
20
23
  from .base_code_executor import BaseCodeExecutor
21
24
  from .code_execution_utils import CodeExecutionInput
22
25
  from .code_execution_utils import CodeExecutionResult
@@ -39,7 +42,7 @@ class BuiltInCodeExecutor(BaseCodeExecutor):
39
42
 
40
43
  def process_llm_request(self, llm_request: LlmRequest) -> None:
41
44
  """Pre-process the LLM request for Gemini 2.0+ models to use the code execution tool."""
42
- if llm_request.model and llm_request.model.startswith("gemini-2"):
45
+ if is_gemini_2_model(llm_request.model):
43
46
  llm_request.config = llm_request.config or types.GenerateContentConfig()
44
47
  llm_request.config.tools = llm_request.config.tools or []
45
48
  llm_request.config.tools.append(
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  from abc import ABC
18
18
  from abc import abstractmethod
19
+ from enum import Enum
19
20
  from typing import AsyncGenerator
20
21
  from typing import Optional
21
22
 
@@ -41,6 +42,17 @@ class EvaluateConfig(BaseModel):
41
42
  description="""The list of metrics to be used in Eval.""",
42
43
  )
43
44
 
45
+ parallelism: int = Field(
46
+ default=4,
47
+ description="""Number of parallel evaluations to run during an Eval. Few
48
+ factors to consider while changing this value:
49
+
50
+ 1) Your available quota with the model, especially for those metrics that use
51
+ a model as a judge. Models tend to enforce per-minute or per-second SLAs. Using
52
+ a larger value could result in the eval quickly consuming the quota.
53
+ """,
54
+ )
55
+
44
56
 
45
57
  class InferenceConfig(BaseModel):
46
58
  """Contains configurations need to run inferences."""
@@ -56,6 +68,19 @@ class InferenceConfig(BaseModel):
56
68
  charges.""",
57
69
  )
58
70
 
71
+ parallelism: int = Field(
72
+ default=4,
73
+ description="""Number of parallel inferences to run during an Eval. Few
74
+ factors to consider while changing this value:
75
+
76
+ 1) Your available quota with the model. Models tend to enforce per-minute or
77
+ per-second SLAs. Using a larger value could result in the eval quickly consuming
78
+ the quota.
79
+
80
+ 2) The tools used by the Agent could also have their SLA. Using a larger value
81
+ could also overwhelm those tools.""",
82
+ )
83
+
59
84
 
60
85
  class InferenceRequest(BaseModel):
61
86
  """Represent a request to perform inferences for the eval cases in an eval set."""
@@ -88,6 +113,14 @@ in an eval set are evaluated.
88
113
  )
89
114
 
90
115
 
116
+ class InferenceStatus(Enum):
117
+ """Status of the inference."""
118
+
119
+ UNKNOWN = 0
120
+ SUCCESS = 1
121
+ FAILURE = 2
122
+
123
+
91
124
  class InferenceResult(BaseModel):
92
125
  """Contains inference results for a single eval case."""
93
126
 
@@ -106,14 +139,25 @@ class InferenceResult(BaseModel):
106
139
  description="""Id of the eval case for which inferences were generated.""",
107
140
  )
108
141
 
109
- inferences: list[Invocation] = Field(
110
- description="""Inferences obtained from the Agent for the eval case."""
142
+ inferences: Optional[list[Invocation]] = Field(
143
+ default=None,
144
+ description="""Inferences obtained from the Agent for the eval case.""",
111
145
  )
112
146
 
113
147
  session_id: Optional[str] = Field(
114
148
  description="""Id of the inference session."""
115
149
  )
116
150
 
151
+ status: InferenceStatus = Field(
152
+ default=InferenceStatus.UNKNOWN,
153
+ description="""Status of the inference.""",
154
+ )
155
+
156
+ error_message: Optional[str] = Field(
157
+ default=None,
158
+ description="""Error message if the inference failed.""",
159
+ )
160
+
117
161
 
118
162
  class EvaluateRequest(BaseModel):
119
163
  model_config = ConfigDict(
@@ -36,6 +36,10 @@ class PrebuiltMetrics(Enum):
36
36
 
37
37
  RESPONSE_MATCH_SCORE = "response_match_score"
38
38
 
39
+ SAFETY_V1 = "safety_v1"
40
+
41
+ FINAL_RESPONSE_MATCH_V2 = "final_response_match_v2"
42
+
39
43
 
40
44
  MetricName: TypeAlias = Union[str, PrebuiltMetrics]
41
45
 
@@ -36,7 +36,11 @@ class EvalSetsManager(ABC):
36
36
 
37
37
  @abstractmethod
38
38
  def list_eval_sets(self, app_name: str) -> list[str]:
39
- """Returns a list of EvalSets that belong to the given app_name."""
39
+ """Returns a list of EvalSets that belong to the given app_name.
40
+
41
+ Raises:
42
+ NotFoundError: If the app_name doesn't exist.
43
+ """
40
44
 
41
45
  @abstractmethod
42
46
  def get_eval_case(
@@ -137,7 +137,7 @@ class EvaluationGenerator:
137
137
  async def _generate_inferences_from_root_agent(
138
138
  invocations: list[Invocation],
139
139
  root_agent: Agent,
140
- reset_func: Any,
140
+ reset_func: Optional[Any] = None,
141
141
  initial_session: Optional[SessionInput] = None,
142
142
  session_id: Optional[str] = None,
143
143
  session_service: Optional[BaseSessionService] = None,