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.
- google/adk/a2a/converters/event_converter.py +5 -85
- google/adk/a2a/converters/request_converter.py +1 -2
- google/adk/a2a/executor/a2a_agent_executor.py +45 -16
- google/adk/a2a/logs/log_utils.py +1 -2
- google/adk/a2a/utils/__init__.py +0 -0
- google/adk/a2a/utils/agent_card_builder.py +544 -0
- google/adk/a2a/utils/agent_to_a2a.py +118 -0
- google/adk/agents/__init__.py +5 -0
- google/adk/agents/agent_config.py +46 -0
- google/adk/agents/base_agent.py +239 -41
- google/adk/agents/callback_context.py +41 -0
- google/adk/agents/common_configs.py +79 -0
- google/adk/agents/config_agent_utils.py +184 -0
- google/adk/agents/config_schemas/AgentConfig.json +566 -0
- google/adk/agents/invocation_context.py +5 -1
- google/adk/agents/live_request_queue.py +15 -0
- google/adk/agents/llm_agent.py +201 -9
- google/adk/agents/loop_agent.py +35 -1
- google/adk/agents/parallel_agent.py +24 -3
- google/adk/agents/remote_a2a_agent.py +17 -5
- google/adk/agents/sequential_agent.py +22 -1
- google/adk/artifacts/gcs_artifact_service.py +110 -20
- google/adk/auth/auth_handler.py +3 -3
- google/adk/auth/credential_manager.py +23 -23
- google/adk/auth/credential_service/base_credential_service.py +6 -6
- google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
- google/adk/auth/credential_service/session_state_credential_service.py +8 -8
- google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
- google/adk/auth/oauth2_credential_util.py +2 -2
- google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
- google/adk/cli/agent_graph.py +3 -1
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/main-W7QZBYAR.js +3914 -0
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli_eval.py +87 -12
- google/adk/cli/cli_tools_click.py +143 -82
- google/adk/cli/fast_api.py +150 -69
- google/adk/cli/utils/agent_loader.py +35 -1
- google/adk/code_executors/base_code_executor.py +14 -19
- google/adk/code_executors/built_in_code_executor.py +4 -1
- google/adk/evaluation/base_eval_service.py +46 -2
- google/adk/evaluation/eval_metrics.py +4 -0
- google/adk/evaluation/eval_sets_manager.py +5 -1
- google/adk/evaluation/evaluation_generator.py +1 -1
- google/adk/evaluation/final_response_match_v2.py +2 -2
- google/adk/evaluation/gcs_eval_sets_manager.py +2 -1
- google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
- google/adk/evaluation/local_eval_service.py +389 -0
- google/adk/evaluation/local_eval_set_results_manager.py +2 -2
- google/adk/evaluation/local_eval_sets_manager.py +24 -9
- google/adk/evaluation/metric_evaluator_registry.py +16 -6
- google/adk/evaluation/vertex_ai_eval_facade.py +7 -1
- google/adk/events/event.py +7 -2
- google/adk/flows/llm_flows/auto_flow.py +6 -11
- google/adk/flows/llm_flows/base_llm_flow.py +66 -29
- google/adk/flows/llm_flows/contents.py +16 -10
- google/adk/flows/llm_flows/functions.py +89 -52
- google/adk/memory/in_memory_memory_service.py +21 -15
- google/adk/memory/vertex_ai_memory_bank_service.py +12 -10
- google/adk/models/anthropic_llm.py +46 -6
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +17 -6
- google/adk/models/google_llm.py +46 -11
- google/adk/models/lite_llm.py +52 -22
- google/adk/plugins/__init__.py +17 -0
- google/adk/plugins/base_plugin.py +317 -0
- google/adk/plugins/plugin_manager.py +265 -0
- google/adk/runners.py +122 -18
- google/adk/sessions/database_session_service.py +51 -52
- google/adk/sessions/vertex_ai_session_service.py +27 -12
- google/adk/tools/__init__.py +2 -0
- google/adk/tools/_automatic_function_calling_util.py +20 -2
- google/adk/tools/agent_tool.py +15 -3
- google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
- google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
- google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
- google/adk/tools/base_tool.py +9 -9
- google/adk/tools/base_toolset.py +29 -5
- google/adk/tools/bigquery/__init__.py +3 -3
- google/adk/tools/bigquery/metadata_tool.py +2 -0
- google/adk/tools/bigquery/query_tool.py +15 -1
- google/adk/tools/computer_use/__init__.py +13 -0
- google/adk/tools/computer_use/base_computer.py +265 -0
- google/adk/tools/computer_use/computer_use_tool.py +166 -0
- google/adk/tools/computer_use/computer_use_toolset.py +220 -0
- google/adk/tools/enterprise_search_tool.py +4 -2
- google/adk/tools/exit_loop_tool.py +1 -0
- google/adk/tools/google_api_tool/google_api_tool.py +16 -1
- google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
- google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
- google/adk/tools/google_search_tool.py +4 -2
- google/adk/tools/langchain_tool.py +16 -6
- google/adk/tools/long_running_tool.py +21 -0
- google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +5 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
- google/adk/tools/tool_context.py +0 -10
- google/adk/tools/url_context_tool.py +4 -2
- google/adk/tools/vertex_ai_search_tool.py +4 -2
- google/adk/utils/model_name_utils.py +90 -0
- google/adk/version.py +1 -1
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/METADATA +3 -2
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/RECORD +108 -91
- google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/WHEEL +0 -0
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/entry_points.txt +0 -0
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/licenses/LICENSE +0 -0
google/adk/cli/fast_api.py
CHANGED
@@ -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
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
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=
|
313
|
-
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
|
-
|
327
|
-
agent_engine_id =
|
328
|
-
|
329
|
-
|
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=
|
333
|
-
location=
|
367
|
+
project=project,
|
368
|
+
location=location,
|
334
369
|
agent_engine_id=agent_engine_id,
|
335
370
|
)
|
336
371
|
else:
|
337
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
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
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
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
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
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(
|
720
|
+
raise HTTPException(
|
721
|
+
status_code=400, detail=MISSING_EVAL_DEPENDENCIES_MESSAGE
|
722
|
+
) from e
|
685
723
|
|
686
|
-
|
687
|
-
|
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
|
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.
|
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}
|
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(
|
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
|
-
|
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
|
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
|
-
|
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,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,
|