google-adk 0.3.0__py3-none-any.whl → 0.5.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 (71) hide show
  1. google/adk/agents/active_streaming_tool.py +1 -0
  2. google/adk/agents/base_agent.py +27 -29
  3. google/adk/agents/callback_context.py +4 -4
  4. google/adk/agents/invocation_context.py +1 -0
  5. google/adk/agents/langgraph_agent.py +1 -0
  6. google/adk/agents/live_request_queue.py +1 -0
  7. google/adk/agents/llm_agent.py +54 -14
  8. google/adk/agents/run_config.py +4 -0
  9. google/adk/agents/transcription_entry.py +1 -0
  10. google/adk/artifacts/base_artifact_service.py +5 -10
  11. google/adk/artifacts/gcs_artifact_service.py +8 -8
  12. google/adk/artifacts/in_memory_artifact_service.py +5 -5
  13. google/adk/auth/auth_credential.py +4 -5
  14. google/adk/cli/browser/index.html +1 -1
  15. google/adk/cli/browser/{main-HWIBUY2R.js → main-ULN5R5I5.js} +40 -39
  16. google/adk/cli/cli.py +54 -47
  17. google/adk/cli/cli_eval.py +13 -11
  18. google/adk/cli/cli_tools_click.py +58 -7
  19. google/adk/cli/fast_api.py +11 -11
  20. google/adk/cli/fast_api.py.orig +728 -0
  21. google/adk/evaluation/agent_evaluator.py +3 -3
  22. google/adk/evaluation/evaluation_constants.py +1 -0
  23. google/adk/evaluation/evaluation_generator.py +5 -5
  24. google/adk/evaluation/response_evaluator.py +1 -1
  25. google/adk/events/event.py +1 -0
  26. google/adk/events/event_actions.py +10 -4
  27. google/adk/examples/example.py +1 -0
  28. google/adk/flows/__init__.py +0 -1
  29. google/adk/flows/llm_flows/_code_execution.py +10 -10
  30. google/adk/flows/llm_flows/base_llm_flow.py +40 -15
  31. google/adk/flows/llm_flows/basic.py +3 -0
  32. google/adk/flows/llm_flows/contents.py +9 -5
  33. google/adk/flows/llm_flows/functions.py +38 -16
  34. google/adk/flows/llm_flows/instructions.py +17 -6
  35. google/adk/memory/base_memory_service.py +4 -2
  36. google/adk/memory/in_memory_memory_service.py +2 -2
  37. google/adk/memory/vertex_ai_rag_memory_service.py +2 -2
  38. google/adk/models/anthropic_llm.py +20 -2
  39. google/adk/models/base_llm.py +45 -4
  40. google/adk/models/gemini_llm_connection.py +14 -1
  41. google/adk/models/google_llm.py +0 -42
  42. google/adk/models/lite_llm.py +17 -17
  43. google/adk/models/llm_request.py +1 -1
  44. google/adk/models/llm_response.py +1 -1
  45. google/adk/runners.py +5 -5
  46. google/adk/sessions/_session_util.py +43 -0
  47. google/adk/sessions/base_session_service.py +3 -0
  48. google/adk/sessions/database_session_service.py +63 -46
  49. google/adk/sessions/in_memory_session_service.py +3 -3
  50. google/adk/sessions/session.py +1 -0
  51. google/adk/sessions/vertex_ai_session_service.py +7 -5
  52. google/adk/tools/agent_tool.py +7 -4
  53. google/adk/tools/application_integration_tool/__init__.py +2 -0
  54. google/adk/tools/application_integration_tool/application_integration_toolset.py +48 -26
  55. google/adk/tools/application_integration_tool/clients/connections_client.py +33 -77
  56. google/adk/tools/application_integration_tool/integration_connector_tool.py +159 -0
  57. google/adk/tools/function_tool.py +42 -0
  58. google/adk/tools/load_artifacts_tool.py +4 -4
  59. google/adk/tools/load_memory_tool.py +4 -2
  60. google/adk/tools/mcp_tool/conversion_utils.py +1 -1
  61. google/adk/tools/mcp_tool/mcp_session_manager.py +14 -0
  62. google/adk/tools/openapi_tool/common/common.py +2 -5
  63. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +13 -3
  64. google/adk/tools/preload_memory_tool.py +1 -1
  65. google/adk/tools/tool_context.py +4 -4
  66. google/adk/version.py +1 -1
  67. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/METADATA +3 -7
  68. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/RECORD +71 -68
  69. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/WHEEL +0 -0
  70. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/entry_points.txt +0 -0
  71. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/licenses/LICENSE +0 -0
google/adk/cli/cli.py CHANGED
@@ -39,12 +39,12 @@ class InputFile(BaseModel):
39
39
 
40
40
  async def run_input_file(
41
41
  app_name: str,
42
+ user_id: str,
42
43
  root_agent: LlmAgent,
43
44
  artifact_service: BaseArtifactService,
44
- session: Session,
45
45
  session_service: BaseSessionService,
46
46
  input_path: str,
47
- ) -> None:
47
+ ) -> Session:
48
48
  runner = Runner(
49
49
  app_name=app_name,
50
50
  agent=root_agent,
@@ -55,9 +55,11 @@ async def run_input_file(
55
55
  input_file = InputFile.model_validate_json(f.read())
56
56
  input_file.state['_time'] = datetime.now()
57
57
 
58
- session.state = input_file.state
58
+ session = session_service.create_session(
59
+ app_name=app_name, user_id=user_id, state=input_file.state
60
+ )
59
61
  for query in input_file.queries:
60
- click.echo(f'user: {query}')
62
+ click.echo(f'[user]: {query}')
61
63
  content = types.Content(role='user', parts=[types.Part(text=query)])
62
64
  async for event in runner.run_async(
63
65
  user_id=session.user_id, session_id=session.id, new_message=content
@@ -65,23 +67,23 @@ async def run_input_file(
65
67
  if event.content and event.content.parts:
66
68
  if text := ''.join(part.text or '' for part in event.content.parts):
67
69
  click.echo(f'[{event.author}]: {text}')
70
+ return session
68
71
 
69
72
 
70
73
  async def run_interactively(
71
- app_name: str,
72
74
  root_agent: LlmAgent,
73
75
  artifact_service: BaseArtifactService,
74
76
  session: Session,
75
77
  session_service: BaseSessionService,
76
78
  ) -> None:
77
79
  runner = Runner(
78
- app_name=app_name,
80
+ app_name=session.app_name,
79
81
  agent=root_agent,
80
82
  artifact_service=artifact_service,
81
83
  session_service=session_service,
82
84
  )
83
85
  while True:
84
- query = input('user: ')
86
+ query = input('[user]: ')
85
87
  if not query or not query.strip():
86
88
  continue
87
89
  if query == 'exit':
@@ -100,7 +102,8 @@ async def run_cli(
100
102
  *,
101
103
  agent_parent_dir: str,
102
104
  agent_folder_name: str,
103
- json_file_path: Optional[str] = None,
105
+ input_file: Optional[str] = None,
106
+ saved_session_file: Optional[str] = None,
104
107
  save_session: bool,
105
108
  ) -> None:
106
109
  """Runs an interactive CLI for a certain agent.
@@ -109,8 +112,11 @@ async def run_cli(
109
112
  agent_parent_dir: str, the absolute path of the parent folder of the agent
110
113
  folder.
111
114
  agent_folder_name: str, the name of the agent folder.
112
- json_file_path: Optional[str], the absolute path to the json file, either
113
- *.input.json or *.session.json.
115
+ input_file: Optional[str], the absolute path to the json file that contains
116
+ the initial session state and user queries, exclusive with
117
+ saved_session_file.
118
+ saved_session_file: Optional[str], the absolute path to the json file that
119
+ contains a previously saved session, exclusive with input_file.
114
120
  save_session: bool, whether to save the session on exit.
115
121
  """
116
122
  if agent_parent_dir not in sys.path:
@@ -118,46 +124,50 @@ async def run_cli(
118
124
 
119
125
  artifact_service = InMemoryArtifactService()
120
126
  session_service = InMemorySessionService()
121
- session = session_service.create_session(
122
- app_name=agent_folder_name, user_id='test_user'
123
- )
124
127
 
125
128
  agent_module_path = os.path.join(agent_parent_dir, agent_folder_name)
126
129
  agent_module = importlib.import_module(agent_folder_name)
130
+ user_id = 'test_user'
131
+ session = session_service.create_session(
132
+ app_name=agent_folder_name, user_id=user_id
133
+ )
127
134
  root_agent = agent_module.agent.root_agent
128
135
  envs.load_dotenv_for_agent(agent_folder_name, agent_parent_dir)
129
- if json_file_path:
130
- if json_file_path.endswith('.input.json'):
131
- await run_input_file(
132
- app_name=agent_folder_name,
133
- root_agent=root_agent,
134
- artifact_service=artifact_service,
135
- session=session,
136
- session_service=session_service,
137
- input_path=json_file_path,
138
- )
139
- elif json_file_path.endswith('.session.json'):
140
- with open(json_file_path, 'r') as f:
141
- session = Session.model_validate_json(f.read())
142
- for content in session.get_contents():
143
- if content.role == 'user':
144
- print('user: ', content.parts[0].text)
136
+ if input_file:
137
+ session = await run_input_file(
138
+ app_name=agent_folder_name,
139
+ user_id=user_id,
140
+ root_agent=root_agent,
141
+ artifact_service=artifact_service,
142
+ session_service=session_service,
143
+ input_path=input_file,
144
+ )
145
+ elif saved_session_file:
146
+
147
+ loaded_session = None
148
+ with open(saved_session_file, 'r') as f:
149
+ loaded_session = Session.model_validate_json(f.read())
150
+
151
+ if loaded_session:
152
+ for event in loaded_session.events:
153
+ session_service.append_event(session, event)
154
+ content = event.content
155
+ if not content or not content.parts or not content.parts[0].text:
156
+ continue
157
+ if event.author == 'user':
158
+ click.echo(f'[user]: {content.parts[0].text}')
145
159
  else:
146
- print(content.parts[0].text)
147
- await run_interactively(
148
- agent_folder_name,
149
- root_agent,
150
- artifact_service,
151
- session,
152
- session_service,
153
- )
154
- else:
155
- print(f'Unsupported file type: {json_file_path}')
156
- exit(1)
160
+ click.echo(f'[{event.author}]: {content.parts[0].text}')
161
+
162
+ await run_interactively(
163
+ root_agent,
164
+ artifact_service,
165
+ session,
166
+ session_service,
167
+ )
157
168
  else:
158
- print(f'Running agent {root_agent.name}, type exit to exit.')
169
+ click.echo(f'Running agent {root_agent.name}, type exit to exit.')
159
170
  await run_interactively(
160
- agent_folder_name,
161
171
  root_agent,
162
172
  artifact_service,
163
173
  session,
@@ -165,11 +175,8 @@ async def run_cli(
165
175
  )
166
176
 
167
177
  if save_session:
168
- if json_file_path:
169
- session_path = json_file_path.replace('.input.json', '.session.json')
170
- else:
171
- session_id = input('Session ID to save: ')
172
- session_path = f'{agent_module_path}/{session_id}.session.json'
178
+ session_id = input('Session ID to save: ')
179
+ session_path = f'{agent_module_path}/{session_id}.session.json'
173
180
 
174
181
  # Fetch the session again to get all the details.
175
182
  session = session_service.get_session(
@@ -20,7 +20,7 @@ import os
20
20
  import sys
21
21
  import traceback
22
22
  from typing import Any
23
- from typing import Generator
23
+ from typing import AsyncGenerator
24
24
  from typing import Optional
25
25
  import uuid
26
26
 
@@ -146,7 +146,7 @@ def parse_and_get_evals_to_run(
146
146
  return eval_set_to_evals
147
147
 
148
148
 
149
- def run_evals(
149
+ async def run_evals(
150
150
  eval_set_to_evals: dict[str, list[str]],
151
151
  root_agent: Agent,
152
152
  reset_func: Optional[Any],
@@ -154,7 +154,7 @@ def run_evals(
154
154
  session_service=None,
155
155
  artifact_service=None,
156
156
  print_detailed_results=False,
157
- ) -> Generator[EvalResult, None, None]:
157
+ ) -> AsyncGenerator[EvalResult, None]:
158
158
  try:
159
159
  from ..evaluation.agent_evaluator import EvaluationGenerator
160
160
  from ..evaluation.response_evaluator import ResponseEvaluator
@@ -181,14 +181,16 @@ def run_evals(
181
181
  print(f"Running Eval: {eval_set_file}:{eval_name}")
182
182
  session_id = f"{EVAL_SESSION_ID_PREFIX}{str(uuid.uuid4())}"
183
183
 
184
- scrape_result = EvaluationGenerator._process_query_with_root_agent(
185
- data=eval_data,
186
- root_agent=root_agent,
187
- reset_func=reset_func,
188
- initial_session=initial_session,
189
- session_id=session_id,
190
- session_service=session_service,
191
- artifact_service=artifact_service,
184
+ scrape_result = (
185
+ await EvaluationGenerator._process_query_with_root_agent(
186
+ data=eval_data,
187
+ root_agent=root_agent,
188
+ reset_func=reset_func,
189
+ initial_session=initial_session,
190
+ session_id=session_id,
191
+ session_service=session_service,
192
+ artifact_service=artifact_service,
193
+ )
192
194
  )
193
195
 
194
196
  eval_metric_results = []
@@ -96,6 +96,23 @@ def cli_create_cmd(
96
96
  )
97
97
 
98
98
 
99
+ def validate_exclusive(ctx, param, value):
100
+ # Store the validated parameters in the context
101
+ if not hasattr(ctx, "exclusive_opts"):
102
+ ctx.exclusive_opts = {}
103
+
104
+ # If this option has a value and we've already seen another exclusive option
105
+ if value is not None and any(ctx.exclusive_opts.values()):
106
+ exclusive_opt = next(key for key, val in ctx.exclusive_opts.items() if val)
107
+ raise click.UsageError(
108
+ f"Options '{param.name}' and '{exclusive_opt}' cannot be set together."
109
+ )
110
+
111
+ # Record this option's value
112
+ ctx.exclusive_opts[param.name] = value is not None
113
+ return value
114
+
115
+
99
116
  @main.command("run")
100
117
  @click.option(
101
118
  "--save_session",
@@ -105,13 +122,43 @@ def cli_create_cmd(
105
122
  default=False,
106
123
  help="Optional. Whether to save the session to a json file on exit.",
107
124
  )
125
+ @click.option(
126
+ "--replay",
127
+ type=click.Path(
128
+ exists=True, dir_okay=False, file_okay=True, resolve_path=True
129
+ ),
130
+ help=(
131
+ "The json file that contains the initial state of the session and user"
132
+ " queries. A new session will be created using this state. And user"
133
+ " queries are run againt the newly created session. Users cannot"
134
+ " continue to interact with the agent."
135
+ ),
136
+ callback=validate_exclusive,
137
+ )
138
+ @click.option(
139
+ "--resume",
140
+ type=click.Path(
141
+ exists=True, dir_okay=False, file_okay=True, resolve_path=True
142
+ ),
143
+ help=(
144
+ "The json file that contains a previously saved session (by"
145
+ "--save_session option). The previous session will be re-displayed. And"
146
+ " user can continue to interact with the agent."
147
+ ),
148
+ callback=validate_exclusive,
149
+ )
108
150
  @click.argument(
109
151
  "agent",
110
152
  type=click.Path(
111
153
  exists=True, dir_okay=True, file_okay=False, resolve_path=True
112
154
  ),
113
155
  )
114
- def cli_run(agent: str, save_session: bool):
156
+ def cli_run(
157
+ agent: str,
158
+ save_session: bool,
159
+ replay: Optional[str],
160
+ resume: Optional[str],
161
+ ):
115
162
  """Runs an interactive CLI for a certain agent.
116
163
 
117
164
  AGENT: The path to the agent source code folder.
@@ -129,6 +176,8 @@ def cli_run(agent: str, save_session: bool):
129
176
  run_cli(
130
177
  agent_parent_dir=agent_parent_folder,
131
178
  agent_folder_name=agent_folder_name,
179
+ input_file=replay,
180
+ saved_session_file=resume,
132
181
  save_session=save_session,
133
182
  )
134
183
  )
@@ -209,12 +258,14 @@ def cli_eval(
209
258
 
210
259
  try:
211
260
  eval_results = list(
212
- run_evals(
213
- eval_set_to_evals,
214
- root_agent,
215
- reset_func,
216
- eval_metrics,
217
- print_detailed_results=print_detailed_results,
261
+ asyncio.run(
262
+ run_evals(
263
+ eval_set_to_evals,
264
+ root_agent,
265
+ reset_func,
266
+ eval_metrics,
267
+ print_detailed_results=print_detailed_results,
268
+ )
218
269
  )
219
270
  )
220
271
  except ModuleNotFoundError:
@@ -467,7 +467,7 @@ def get_fast_api_app(
467
467
  )
468
468
  root_agent = await _get_root_agent_async(app_name)
469
469
  eval_results = list(
470
- run_evals(
470
+ await run_evals(
471
471
  eval_set_to_evals,
472
472
  root_agent,
473
473
  getattr(root_agent, "reset_data", None),
@@ -503,7 +503,7 @@ def get_fast_api_app(
503
503
  "/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}",
504
504
  response_model_exclude_none=True,
505
505
  )
506
- def load_artifact(
506
+ async def load_artifact(
507
507
  app_name: str,
508
508
  user_id: str,
509
509
  session_id: str,
@@ -511,7 +511,7 @@ def get_fast_api_app(
511
511
  version: Optional[int] = Query(None),
512
512
  ) -> Optional[types.Part]:
513
513
  app_name = agent_engine_id if agent_engine_id else app_name
514
- artifact = artifact_service.load_artifact(
514
+ artifact = await artifact_service.load_artifact(
515
515
  app_name=app_name,
516
516
  user_id=user_id,
517
517
  session_id=session_id,
@@ -526,7 +526,7 @@ def get_fast_api_app(
526
526
  "/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}/versions/{version_id}",
527
527
  response_model_exclude_none=True,
528
528
  )
529
- def load_artifact_version(
529
+ async def load_artifact_version(
530
530
  app_name: str,
531
531
  user_id: str,
532
532
  session_id: str,
@@ -534,7 +534,7 @@ def get_fast_api_app(
534
534
  version_id: int,
535
535
  ) -> Optional[types.Part]:
536
536
  app_name = agent_engine_id if agent_engine_id else app_name
537
- artifact = artifact_service.load_artifact(
537
+ artifact = await artifact_service.load_artifact(
538
538
  app_name=app_name,
539
539
  user_id=user_id,
540
540
  session_id=session_id,
@@ -549,11 +549,11 @@ def get_fast_api_app(
549
549
  "/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts",
550
550
  response_model_exclude_none=True,
551
551
  )
552
- def list_artifact_names(
552
+ async def list_artifact_names(
553
553
  app_name: str, user_id: str, session_id: str
554
554
  ) -> list[str]:
555
555
  app_name = agent_engine_id if agent_engine_id else app_name
556
- return artifact_service.list_artifact_keys(
556
+ return await artifact_service.list_artifact_keys(
557
557
  app_name=app_name, user_id=user_id, session_id=session_id
558
558
  )
559
559
 
@@ -561,11 +561,11 @@ def get_fast_api_app(
561
561
  "/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}/versions",
562
562
  response_model_exclude_none=True,
563
563
  )
564
- def list_artifact_versions(
564
+ async def list_artifact_versions(
565
565
  app_name: str, user_id: str, session_id: str, artifact_name: str
566
566
  ) -> list[int]:
567
567
  app_name = agent_engine_id if agent_engine_id else app_name
568
- return artifact_service.list_versions(
568
+ return await artifact_service.list_versions(
569
569
  app_name=app_name,
570
570
  user_id=user_id,
571
571
  session_id=session_id,
@@ -575,11 +575,11 @@ def get_fast_api_app(
575
575
  @app.delete(
576
576
  "/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}",
577
577
  )
578
- def delete_artifact(
578
+ async def delete_artifact(
579
579
  app_name: str, user_id: str, session_id: str, artifact_name: str
580
580
  ):
581
581
  app_name = agent_engine_id if agent_engine_id else app_name
582
- artifact_service.delete_artifact(
582
+ await artifact_service.delete_artifact(
583
583
  app_name=app_name,
584
584
  user_id=user_id,
585
585
  session_id=session_id,