google-adk 1.7.0__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/request_converter.py +1 -2
- 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/base_agent.py +6 -1
- google/adk/agents/config_schemas/AgentConfig.json +22 -0
- google/adk/agents/live_request_queue.py +15 -0
- google/adk/agents/llm_agent.py +11 -0
- google/adk/agents/loop_agent.py +6 -1
- google/adk/agents/remote_a2a_agent.py +2 -2
- google/adk/artifacts/gcs_artifact_service.py +86 -18
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/{main-SRBSE46V.js → main-W7QZBYAR.js} +139 -139
- google/adk/cli/cli_eval.py +87 -12
- google/adk/cli/cli_tools_click.py +143 -82
- google/adk/cli/fast_api.py +136 -95
- google/adk/evaluation/eval_metrics.py +4 -0
- google/adk/evaluation/eval_sets_manager.py +5 -1
- google/adk/evaluation/final_response_match_v2.py +2 -2
- google/adk/evaluation/gcs_eval_sets_manager.py +2 -1
- google/adk/evaluation/local_eval_service.py +2 -2
- google/adk/evaluation/local_eval_set_results_manager.py +2 -2
- google/adk/evaluation/local_eval_sets_manager.py +1 -1
- 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/base_llm_flow.py +25 -6
- google/adk/flows/llm_flows/functions.py +13 -19
- google/adk/memory/in_memory_memory_service.py +1 -1
- google/adk/memory/vertex_ai_memory_bank_service.py +12 -10
- google/adk/models/anthropic_llm.py +2 -1
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +17 -6
- google/adk/models/google_llm.py +35 -5
- google/adk/models/lite_llm.py +31 -18
- google/adk/sessions/database_session_service.py +25 -24
- google/adk/sessions/vertex_ai_session_service.py +13 -5
- google/adk/tools/__init__.py +2 -0
- google/adk/tools/_automatic_function_calling_util.py +20 -2
- google/adk/tools/agent_tool.py +14 -3
- google/adk/tools/base_toolset.py +22 -0
- 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/exit_loop_tool.py +1 -0
- google/adk/tools/langchain_tool.py +14 -3
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +5 -0
- google/adk/version.py +1 -1
- {google_adk-1.7.0.dist-info → google_adk-1.8.0.dist-info}/METADATA +2 -1
- {google_adk-1.7.0.dist-info → google_adk-1.8.0.dist-info}/RECORD +57 -50
- {google_adk-1.7.0.dist-info → google_adk-1.8.0.dist-info}/WHEEL +0 -0
- {google_adk-1.7.0.dist-info → google_adk-1.8.0.dist-info}/entry_points.txt +0 -0
- {google_adk-1.7.0.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
|
@@ -51,7 +53,6 @@ from starlette.types import Lifespan
|
|
51
53
|
from typing_extensions import override
|
52
54
|
from watchdog.events import FileSystemEventHandler
|
53
55
|
from watchdog.observers import Observer
|
54
|
-
import yaml
|
55
56
|
|
56
57
|
from ..agents import RunConfig
|
57
58
|
from ..agents.live_request_queue import LiveRequest
|
@@ -61,6 +62,9 @@ from ..artifacts.gcs_artifact_service import GcsArtifactService
|
|
61
62
|
from ..artifacts.in_memory_artifact_service import InMemoryArtifactService
|
62
63
|
from ..auth.credential_service.in_memory_credential_service import InMemoryCredentialService
|
63
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
|
64
68
|
from ..evaluation.eval_case import EvalCase
|
65
69
|
from ..evaluation.eval_case import SessionInput
|
66
70
|
from ..evaluation.eval_metrics import EvalMetric
|
@@ -72,9 +76,7 @@ from ..evaluation.local_eval_sets_manager import LocalEvalSetsManager
|
|
72
76
|
from ..events.event import Event
|
73
77
|
from ..memory.in_memory_memory_service import InMemoryMemoryService
|
74
78
|
from ..memory.vertex_ai_memory_bank_service import VertexAiMemoryBankService
|
75
|
-
from ..memory.vertex_ai_rag_memory_service import VertexAiRagMemoryService
|
76
79
|
from ..runners import Runner
|
77
|
-
from ..sessions.database_session_service import DatabaseSessionService
|
78
80
|
from ..sessions.in_memory_session_service import InMemorySessionService
|
79
81
|
from ..sessions.session import Session
|
80
82
|
from ..sessions.vertex_ai_session_service import VertexAiSessionService
|
@@ -198,6 +200,7 @@ class RunEvalResult(common.BaseModel):
|
|
198
200
|
final_eval_status: EvalStatus
|
199
201
|
eval_metric_results: list[tuple[EvalMetric, EvalMetricResult]] = Field(
|
200
202
|
deprecated=True,
|
203
|
+
default=[],
|
201
204
|
description=(
|
202
205
|
"This field is deprecated, use overall_eval_metric_results instead."
|
203
206
|
),
|
@@ -212,18 +215,11 @@ class GetEventGraphResult(common.BaseModel):
|
|
212
215
|
dot_src: str
|
213
216
|
|
214
217
|
|
215
|
-
class AgentBuildRequest(common.BaseModel):
|
216
|
-
agent_name: str
|
217
|
-
agent_type: str
|
218
|
-
model: str
|
219
|
-
description: str
|
220
|
-
instruction: str
|
221
|
-
|
222
|
-
|
223
218
|
def get_fast_api_app(
|
224
219
|
*,
|
225
220
|
agents_dir: str,
|
226
221
|
session_service_uri: Optional[str] = None,
|
222
|
+
session_db_kwargs: Optional[Mapping[str, Any]] = None,
|
227
223
|
artifact_service_uri: Optional[str] = None,
|
228
224
|
memory_service_uri: Optional[str] = None,
|
229
225
|
eval_storage_uri: Optional[str] = None,
|
@@ -248,6 +244,8 @@ def get_fast_api_app(
|
|
248
244
|
memory_exporter = InMemoryExporter(session_trace_dict)
|
249
245
|
provider.add_span_processor(export.SimpleSpanProcessor(memory_exporter))
|
250
246
|
if trace_to_cloud:
|
247
|
+
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
|
248
|
+
|
251
249
|
envs.load_dotenv_for_agent("", agents_dir)
|
252
250
|
if project_id := os.environ.get("GOOGLE_CLOUD_PROJECT", None):
|
253
251
|
processor = export.BatchSpanProcessor(
|
@@ -304,9 +302,36 @@ def get_fast_api_app(
|
|
304
302
|
eval_sets_manager = LocalEvalSetsManager(agents_dir=agents_dir)
|
305
303
|
eval_set_results_manager = LocalEvalSetResultsManager(agents_dir=agents_dir)
|
306
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
|
+
|
307
330
|
# Build the Memory service
|
308
331
|
if memory_service_uri:
|
309
332
|
if memory_service_uri.startswith("rag://"):
|
333
|
+
from ..memory.vertex_ai_rag_memory_service import VertexAiRagMemoryService
|
334
|
+
|
310
335
|
rag_corpus = memory_service_uri.split("://")[1]
|
311
336
|
if not rag_corpus:
|
312
337
|
raise click.ClickException("Rag corpus can not be empty.")
|
@@ -315,13 +340,13 @@ def get_fast_api_app(
|
|
315
340
|
rag_corpus=f'projects/{os.environ["GOOGLE_CLOUD_PROJECT"]}/locations/{os.environ["GOOGLE_CLOUD_LOCATION"]}/ragCorpora/{rag_corpus}'
|
316
341
|
)
|
317
342
|
elif memory_service_uri.startswith("agentengine://"):
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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
|
+
)
|
322
347
|
memory_service = VertexAiMemoryBankService(
|
323
|
-
project=
|
324
|
-
location=
|
348
|
+
project=project,
|
349
|
+
location=location,
|
325
350
|
agent_engine_id=agent_engine_id,
|
326
351
|
)
|
327
352
|
else:
|
@@ -334,18 +359,24 @@ def get_fast_api_app(
|
|
334
359
|
# Build the Session service
|
335
360
|
if session_service_uri:
|
336
361
|
if session_service_uri.startswith("agentengine://"):
|
337
|
-
|
338
|
-
agent_engine_id =
|
339
|
-
|
340
|
-
|
341
|
-
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
|
+
)
|
342
366
|
session_service = VertexAiSessionService(
|
343
|
-
project=
|
344
|
-
location=
|
367
|
+
project=project,
|
368
|
+
location=location,
|
345
369
|
agent_engine_id=agent_engine_id,
|
346
370
|
)
|
347
371
|
else:
|
348
|
-
|
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
|
+
)
|
349
380
|
else:
|
350
381
|
session_service = InMemorySessionService()
|
351
382
|
|
@@ -644,63 +675,66 @@ def get_fast_api_app(
|
|
644
675
|
app_name: str, eval_set_id: str, req: RunEvalRequest
|
645
676
|
) -> list[RunEvalResult]:
|
646
677
|
"""Runs an eval given the details in the eval request."""
|
647
|
-
from .cli_eval import run_evals
|
648
|
-
|
649
678
|
# Create a mapping from eval set file to all the evals that needed to be
|
650
679
|
# run.
|
651
|
-
|
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
|
652
684
|
|
653
|
-
|
654
|
-
raise HTTPException(
|
655
|
-
status_code=400, detail=f"Eval set `{eval_set_id}` not found."
|
656
|
-
)
|
685
|
+
eval_set = eval_sets_manager.get_eval_set(app_name, eval_set_id)
|
657
686
|
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
logger.info("Eval ids to run list is empty. We will run all eval cases.")
|
663
|
-
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
|
+
)
|
664
691
|
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
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,
|
674
700
|
session_service=session_service,
|
675
701
|
artifact_service=artifact_service,
|
676
|
-
)
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
app_name=app_name,
|
693
|
-
user_id=eval_case_result.user_id,
|
694
|
-
session_id=eval_case_result.session_id,
|
695
|
-
)
|
696
|
-
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
|
+
)
|
697
718
|
except ModuleNotFoundError as e:
|
698
719
|
logger.exception("%s", e)
|
699
|
-
raise HTTPException(
|
720
|
+
raise HTTPException(
|
721
|
+
status_code=400, detail=MISSING_EVAL_DEPENDENCIES_MESSAGE
|
722
|
+
) from e
|
700
723
|
|
701
|
-
|
702
|
-
|
703
|
-
|
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
|
+
)
|
704
738
|
|
705
739
|
return run_eval_results
|
706
740
|
|
@@ -820,26 +854,30 @@ def get_fast_api_app(
|
|
820
854
|
|
821
855
|
@working_in_progress("builder_save is not ready for use.")
|
822
856
|
@app.post("/builder/save", response_model_exclude_none=True)
|
823
|
-
async def builder_build(
|
857
|
+
async def builder_build(files: list[UploadFile]) -> bool:
|
824
858
|
base_path = Path.cwd() / agents_dir
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
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
|
843
881
|
|
844
882
|
@app.post("/run", response_model_exclude_none=True)
|
845
883
|
async def agent_run(req: AgentRunRequest) -> list[Event]:
|
@@ -857,7 +895,8 @@ def get_fast_api_app(
|
|
857
895
|
new_message=req.new_message,
|
858
896
|
)
|
859
897
|
]
|
860
|
-
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)
|
861
900
|
return events
|
862
901
|
|
863
902
|
@app.post("/run_sse")
|
@@ -883,7 +922,7 @@ def get_fast_api_app(
|
|
883
922
|
):
|
884
923
|
# Format as SSE data
|
885
924
|
sse_event = event.model_dump_json(exclude_none=True, by_alias=True)
|
886
|
-
logger.
|
925
|
+
logger.debug("Generated event in agent run streaming: %s", sse_event)
|
887
926
|
yield f"data: {sse_event}\n\n"
|
888
927
|
except Exception as e:
|
889
928
|
logger.exception("Error in event_generator: %s", e)
|
@@ -1136,7 +1175,9 @@ def get_fast_api_app(
|
|
1136
1175
|
|
1137
1176
|
app.mount(
|
1138
1177
|
"/dev-ui/",
|
1139
|
-
StaticFiles(
|
1178
|
+
StaticFiles(
|
1179
|
+
directory=ANGULAR_DIST_PATH, html=True, follow_symlink=True
|
1180
|
+
),
|
1140
1181
|
name="static",
|
1141
1182
|
)
|
1142
1183
|
return app
|
@@ -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(
|
@@ -21,7 +21,7 @@ from typing import Optional
|
|
21
21
|
from typing_extensions import override
|
22
22
|
|
23
23
|
from ..models.llm_response import LlmResponse
|
24
|
-
from ..utils.feature_decorator import
|
24
|
+
from ..utils.feature_decorator import experimental
|
25
25
|
from .eval_case import Invocation
|
26
26
|
from .eval_metrics import EvalMetric
|
27
27
|
from .evaluator import EvalStatus
|
@@ -125,7 +125,7 @@ def _parse_critique(response: str) -> Label:
|
|
125
125
|
return label
|
126
126
|
|
127
127
|
|
128
|
-
@
|
128
|
+
@experimental
|
129
129
|
class FinalResponseMatchV2Evaluator(LlmAsJudge):
|
130
130
|
"""V2 final response match evaluator which uses an LLM to judge responses.
|
131
131
|
|
@@ -23,6 +23,7 @@ from google.cloud import exceptions as cloud_exceptions
|
|
23
23
|
from google.cloud import storage
|
24
24
|
from typing_extensions import override
|
25
25
|
|
26
|
+
from ..errors.not_found_error import NotFoundError
|
26
27
|
from ._eval_sets_manager_utils import add_eval_case_to_eval_set
|
27
28
|
from ._eval_sets_manager_utils import delete_eval_case_from_eval_set
|
28
29
|
from ._eval_sets_manager_utils import get_eval_case_from_eval_set
|
@@ -130,7 +131,7 @@ class GcsEvalSetsManager(EvalSetsManager):
|
|
130
131
|
eval_sets.append(eval_set_id)
|
131
132
|
return sorted(eval_sets)
|
132
133
|
except cloud_exceptions.NotFound as e:
|
133
|
-
raise
|
134
|
+
raise NotFoundError(
|
134
135
|
f"App `{app_name}` not found in GCS bucket `{self.bucket_name}`."
|
135
136
|
) from e
|
136
137
|
|
@@ -30,7 +30,7 @@ from ..artifacts.in_memory_artifact_service import InMemoryArtifactService
|
|
30
30
|
from ..errors.not_found_error import NotFoundError
|
31
31
|
from ..sessions.base_session_service import BaseSessionService
|
32
32
|
from ..sessions.in_memory_session_service import InMemorySessionService
|
33
|
-
from ..utils.feature_decorator import
|
33
|
+
from ..utils.feature_decorator import experimental
|
34
34
|
from .base_eval_service import BaseEvalService
|
35
35
|
from .base_eval_service import EvaluateConfig
|
36
36
|
from .base_eval_service import EvaluateRequest
|
@@ -60,7 +60,7 @@ def _get_session_id() -> str:
|
|
60
60
|
return f'{EVAL_SESSION_ID_PREFIX}{str(uuid.uuid4())}'
|
61
61
|
|
62
62
|
|
63
|
-
@
|
63
|
+
@experimental
|
64
64
|
class LocalEvalService(BaseEvalService):
|
65
65
|
"""An implementation of BaseEvalService, that runs the evals locally."""
|
66
66
|
|
@@ -60,7 +60,7 @@ class LocalEvalSetResultsManager(EvalSetResultsManager):
|
|
60
60
|
eval_set_result.eval_set_result_name + _EVAL_SET_RESULT_FILE_EXTENSION,
|
61
61
|
)
|
62
62
|
logger.info("Writing eval result to file: %s", eval_set_result_file_path)
|
63
|
-
with open(eval_set_result_file_path, "w") as f:
|
63
|
+
with open(eval_set_result_file_path, "w", encoding="utf-8") as f:
|
64
64
|
f.write(json.dumps(eval_set_result_json, indent=2))
|
65
65
|
|
66
66
|
@override
|
@@ -78,7 +78,7 @@ class LocalEvalSetResultsManager(EvalSetResultsManager):
|
|
78
78
|
)
|
79
79
|
if not os.path.exists(maybe_eval_result_file_path):
|
80
80
|
raise NotFoundError(f"Eval set result `{eval_set_result_id}` not found.")
|
81
|
-
with open(maybe_eval_result_file_path, "r") as file:
|
81
|
+
with open(maybe_eval_result_file_path, "r", encoding="utf-8") as file:
|
82
82
|
eval_result_data = json.load(file)
|
83
83
|
return EvalSetResult.model_validate_json(eval_result_data)
|
84
84
|
|
@@ -315,7 +315,7 @@ class LocalEvalSetsManager(EvalSetsManager):
|
|
315
315
|
)
|
316
316
|
|
317
317
|
def _write_eval_set_to_path(self, eval_set_path: str, eval_set: EvalSet):
|
318
|
-
with open(eval_set_path, "w") as f:
|
318
|
+
with open(eval_set_path, "w", encoding="utf-8") as f:
|
319
319
|
f.write(eval_set.model_dump_json(indent=2))
|
320
320
|
|
321
321
|
def _save_eval_set(self, app_name: str, eval_set_id: str, eval_set: EvalSet):
|
@@ -21,7 +21,9 @@ from .eval_metrics import EvalMetric
|
|
21
21
|
from .eval_metrics import MetricName
|
22
22
|
from .eval_metrics import PrebuiltMetrics
|
23
23
|
from .evaluator import Evaluator
|
24
|
+
from .final_response_match_v2 import FinalResponseMatchV2Evaluator
|
24
25
|
from .response_evaluator import ResponseEvaluator
|
26
|
+
from .safety_evaluator import SafetyEvaluatorV1
|
25
27
|
from .trajectory_evaluator import TrajectoryEvaluator
|
26
28
|
|
27
29
|
logger = logging.getLogger("google_adk." + __name__)
|
@@ -71,16 +73,24 @@ def _get_default_metric_evaluator_registry() -> MetricEvaluatorRegistry:
|
|
71
73
|
metric_evaluator_registry = MetricEvaluatorRegistry()
|
72
74
|
|
73
75
|
metric_evaluator_registry.register_evaluator(
|
74
|
-
metric_name=PrebuiltMetrics.TOOL_TRAJECTORY_AVG_SCORE,
|
75
|
-
evaluator=
|
76
|
+
metric_name=PrebuiltMetrics.TOOL_TRAJECTORY_AVG_SCORE.value,
|
77
|
+
evaluator=TrajectoryEvaluator,
|
76
78
|
)
|
77
79
|
metric_evaluator_registry.register_evaluator(
|
78
|
-
metric_name=PrebuiltMetrics.RESPONSE_EVALUATION_SCORE,
|
79
|
-
evaluator=
|
80
|
+
metric_name=PrebuiltMetrics.RESPONSE_EVALUATION_SCORE.value,
|
81
|
+
evaluator=ResponseEvaluator,
|
80
82
|
)
|
81
83
|
metric_evaluator_registry.register_evaluator(
|
82
|
-
metric_name=PrebuiltMetrics.RESPONSE_MATCH_SCORE,
|
83
|
-
evaluator=
|
84
|
+
metric_name=PrebuiltMetrics.RESPONSE_MATCH_SCORE.value,
|
85
|
+
evaluator=ResponseEvaluator,
|
86
|
+
)
|
87
|
+
metric_evaluator_registry.register_evaluator(
|
88
|
+
metric_name=PrebuiltMetrics.SAFETY_V1.value,
|
89
|
+
evaluator=SafetyEvaluatorV1,
|
90
|
+
)
|
91
|
+
metric_evaluator_registry.register_evaluator(
|
92
|
+
metric_name=PrebuiltMetrics.FINAL_RESPONSE_MATCH_V2.value,
|
93
|
+
evaluator=FinalResponseMatchV2Evaluator,
|
84
94
|
)
|
85
95
|
|
86
96
|
return metric_evaluator_registry
|
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
|
+
import math
|
17
18
|
import os
|
18
19
|
from typing import Optional
|
19
20
|
|
@@ -112,7 +113,12 @@ class _VertexAiEvalFacade(Evaluator):
|
|
112
113
|
return ""
|
113
114
|
|
114
115
|
def _get_score(self, eval_result) -> Optional[float]:
|
115
|
-
if
|
116
|
+
if (
|
117
|
+
eval_result
|
118
|
+
and eval_result.summary_metrics
|
119
|
+
and isinstance(eval_result.summary_metrics[0].mean_score, float)
|
120
|
+
and not math.isnan(eval_result.summary_metrics[0].mean_score)
|
121
|
+
):
|
116
122
|
return eval_result.summary_metrics[0].mean_score
|
117
123
|
|
118
124
|
return None
|
google/adk/events/event.py
CHANGED
@@ -42,7 +42,6 @@ class Event(LlmResponse):
|
|
42
42
|
branch: The branch of the event.
|
43
43
|
id: The unique identifier of the event.
|
44
44
|
timestamp: The timestamp of the event.
|
45
|
-
is_final_response: Whether the event is the final response of the agent.
|
46
45
|
get_function_calls: Returns the function calls in the event.
|
47
46
|
"""
|
48
47
|
|
@@ -92,7 +91,13 @@ class Event(LlmResponse):
|
|
92
91
|
self.id = Event.new_id()
|
93
92
|
|
94
93
|
def is_final_response(self) -> bool:
|
95
|
-
"""Returns whether the event is the final response of
|
94
|
+
"""Returns whether the event is the final response of an agent.
|
95
|
+
|
96
|
+
NOTE: This method is ONLY for use by Agent Development Kit.
|
97
|
+
|
98
|
+
Note that when multiple agents participage in one invocation, there could be
|
99
|
+
one event has `is_final_response()` as True for each participating agent.
|
100
|
+
"""
|
96
101
|
if self.actions.skip_summarization or self.long_running_tool_ids:
|
97
102
|
return True
|
98
103
|
return (
|
@@ -42,6 +42,7 @@ from ...models.llm_response import LlmResponse
|
|
42
42
|
from ...telemetry import trace_call_llm
|
43
43
|
from ...telemetry import trace_send_data
|
44
44
|
from ...telemetry import tracer
|
45
|
+
from ...tools.base_toolset import BaseToolset
|
45
46
|
from ...tools.tool_context import ToolContext
|
46
47
|
|
47
48
|
if TYPE_CHECKING:
|
@@ -194,7 +195,12 @@ class BaseLlmFlow(ABC):
|
|
194
195
|
if live_request.close:
|
195
196
|
await llm_connection.close()
|
196
197
|
return
|
197
|
-
|
198
|
+
|
199
|
+
if live_request.activity_start:
|
200
|
+
await llm_connection.send_realtime(types.ActivityStart())
|
201
|
+
elif live_request.activity_end:
|
202
|
+
await llm_connection.send_realtime(types.ActivityEnd())
|
203
|
+
elif live_request.blob:
|
198
204
|
# Cache audio data here for transcription
|
199
205
|
if not invocation_context.transcription_cache:
|
200
206
|
invocation_context.transcription_cache = []
|
@@ -205,6 +211,7 @@ class BaseLlmFlow(ABC):
|
|
205
211
|
TranscriptionEntry(role='user', data=live_request.blob)
|
206
212
|
)
|
207
213
|
await llm_connection.send_realtime(live_request.blob)
|
214
|
+
|
208
215
|
if live_request.content:
|
209
216
|
await llm_connection.send_content(live_request.content)
|
210
217
|
|
@@ -335,13 +342,25 @@ class BaseLlmFlow(ABC):
|
|
335
342
|
yield event
|
336
343
|
|
337
344
|
# Run processors for tools.
|
338
|
-
for
|
339
|
-
ReadonlyContext(invocation_context)
|
340
|
-
):
|
345
|
+
for tool_union in agent.tools:
|
341
346
|
tool_context = ToolContext(invocation_context)
|
342
|
-
|
343
|
-
|
347
|
+
|
348
|
+
# If it's a toolset, process it first
|
349
|
+
if isinstance(tool_union, BaseToolset):
|
350
|
+
await tool_union.process_llm_request(
|
351
|
+
tool_context=tool_context, llm_request=llm_request
|
352
|
+
)
|
353
|
+
|
354
|
+
from ...agents.llm_agent import _convert_tool_union_to_tools
|
355
|
+
|
356
|
+
# Then process all tools from this tool union
|
357
|
+
tools = await _convert_tool_union_to_tools(
|
358
|
+
tool_union, ReadonlyContext(invocation_context)
|
344
359
|
)
|
360
|
+
for tool in tools:
|
361
|
+
await tool.process_llm_request(
|
362
|
+
tool_context=tool_context, llm_request=llm_request
|
363
|
+
)
|
345
364
|
|
346
365
|
async def _postprocess_async(
|
347
366
|
self,
|