google-adk 0.4.0__py3-none-any.whl → 1.0.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 (129) hide show
  1. google/adk/agents/active_streaming_tool.py +1 -0
  2. google/adk/agents/base_agent.py +91 -47
  3. google/adk/agents/base_agent.py.orig +330 -0
  4. google/adk/agents/callback_context.py +4 -9
  5. google/adk/agents/invocation_context.py +1 -0
  6. google/adk/agents/langgraph_agent.py +1 -0
  7. google/adk/agents/live_request_queue.py +1 -0
  8. google/adk/agents/llm_agent.py +172 -35
  9. google/adk/agents/loop_agent.py +1 -1
  10. google/adk/agents/parallel_agent.py +7 -0
  11. google/adk/agents/readonly_context.py +7 -1
  12. google/adk/agents/run_config.py +5 -1
  13. google/adk/agents/sequential_agent.py +31 -0
  14. google/adk/agents/transcription_entry.py +5 -2
  15. google/adk/artifacts/base_artifact_service.py +5 -10
  16. google/adk/artifacts/gcs_artifact_service.py +9 -9
  17. google/adk/artifacts/in_memory_artifact_service.py +6 -6
  18. google/adk/auth/auth_credential.py +9 -5
  19. google/adk/auth/auth_preprocessor.py +7 -1
  20. google/adk/auth/auth_tool.py +3 -4
  21. google/adk/cli/agent_graph.py +5 -5
  22. google/adk/cli/browser/index.html +2 -2
  23. google/adk/cli/browser/{main-HWIBUY2R.js → main-QOEMUXM4.js} +58 -58
  24. google/adk/cli/cli.py +7 -7
  25. google/adk/cli/cli_deploy.py +7 -2
  26. google/adk/cli/cli_eval.py +181 -106
  27. google/adk/cli/cli_tools_click.py +147 -62
  28. google/adk/cli/fast_api.py +340 -158
  29. google/adk/cli/fast_api.py.orig +822 -0
  30. google/adk/cli/utils/common.py +23 -0
  31. google/adk/cli/utils/evals.py +83 -1
  32. google/adk/cli/utils/logs.py +13 -5
  33. google/adk/code_executors/__init__.py +3 -1
  34. google/adk/code_executors/built_in_code_executor.py +52 -0
  35. google/adk/evaluation/__init__.py +1 -1
  36. google/adk/evaluation/agent_evaluator.py +168 -128
  37. google/adk/evaluation/eval_case.py +102 -0
  38. google/adk/evaluation/eval_set.py +37 -0
  39. google/adk/evaluation/eval_sets_manager.py +42 -0
  40. google/adk/evaluation/evaluation_constants.py +1 -0
  41. google/adk/evaluation/evaluation_generator.py +89 -114
  42. google/adk/evaluation/evaluator.py +56 -0
  43. google/adk/evaluation/local_eval_sets_manager.py +264 -0
  44. google/adk/evaluation/response_evaluator.py +107 -3
  45. google/adk/evaluation/trajectory_evaluator.py +83 -2
  46. google/adk/events/event.py +7 -1
  47. google/adk/events/event_actions.py +7 -1
  48. google/adk/examples/example.py +1 -0
  49. google/adk/examples/example_util.py +3 -2
  50. google/adk/flows/__init__.py +0 -1
  51. google/adk/flows/llm_flows/_code_execution.py +19 -11
  52. google/adk/flows/llm_flows/audio_transcriber.py +4 -3
  53. google/adk/flows/llm_flows/base_llm_flow.py +86 -22
  54. google/adk/flows/llm_flows/basic.py +3 -0
  55. google/adk/flows/llm_flows/functions.py +10 -9
  56. google/adk/flows/llm_flows/instructions.py +28 -9
  57. google/adk/flows/llm_flows/single_flow.py +1 -1
  58. google/adk/memory/__init__.py +1 -1
  59. google/adk/memory/_utils.py +23 -0
  60. google/adk/memory/base_memory_service.py +25 -21
  61. google/adk/memory/base_memory_service.py.orig +76 -0
  62. google/adk/memory/in_memory_memory_service.py +59 -27
  63. google/adk/memory/memory_entry.py +37 -0
  64. google/adk/memory/vertex_ai_rag_memory_service.py +40 -17
  65. google/adk/models/anthropic_llm.py +36 -11
  66. google/adk/models/base_llm.py +45 -4
  67. google/adk/models/gemini_llm_connection.py +15 -2
  68. google/adk/models/google_llm.py +9 -44
  69. google/adk/models/google_llm.py.orig +305 -0
  70. google/adk/models/lite_llm.py +94 -38
  71. google/adk/models/llm_request.py +1 -1
  72. google/adk/models/llm_response.py +15 -3
  73. google/adk/models/registry.py +1 -1
  74. google/adk/runners.py +68 -44
  75. google/adk/sessions/__init__.py +1 -1
  76. google/adk/sessions/_session_util.py +14 -0
  77. google/adk/sessions/base_session_service.py +8 -32
  78. google/adk/sessions/database_session_service.py +58 -61
  79. google/adk/sessions/in_memory_session_service.py +108 -26
  80. google/adk/sessions/session.py +4 -0
  81. google/adk/sessions/vertex_ai_session_service.py +23 -45
  82. google/adk/telemetry.py +3 -0
  83. google/adk/tools/__init__.py +4 -7
  84. google/adk/tools/{built_in_code_execution_tool.py → _built_in_code_execution_tool.py} +11 -0
  85. google/adk/tools/_memory_entry_utils.py +30 -0
  86. google/adk/tools/agent_tool.py +16 -13
  87. google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
  88. google/adk/tools/application_integration_tool/application_integration_toolset.py +107 -85
  89. google/adk/tools/application_integration_tool/clients/connections_client.py +29 -25
  90. google/adk/tools/application_integration_tool/clients/integration_client.py +6 -6
  91. google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
  92. google/adk/tools/base_toolset.py +58 -0
  93. google/adk/tools/enterprise_search_tool.py +65 -0
  94. google/adk/tools/function_parameter_parse_util.py +2 -2
  95. google/adk/tools/google_api_tool/__init__.py +18 -70
  96. google/adk/tools/google_api_tool/google_api_tool.py +11 -5
  97. google/adk/tools/google_api_tool/google_api_toolset.py +126 -0
  98. google/adk/tools/google_api_tool/google_api_toolsets.py +102 -0
  99. google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
  100. google/adk/tools/langchain_tool.py +96 -49
  101. google/adk/tools/load_artifacts_tool.py +4 -4
  102. google/adk/tools/load_memory_tool.py +16 -5
  103. google/adk/tools/mcp_tool/__init__.py +3 -2
  104. google/adk/tools/mcp_tool/conversion_utils.py +1 -1
  105. google/adk/tools/mcp_tool/mcp_session_manager.py +167 -16
  106. google/adk/tools/mcp_tool/mcp_session_manager.py.orig +322 -0
  107. google/adk/tools/mcp_tool/mcp_tool.py +12 -12
  108. google/adk/tools/mcp_tool/mcp_toolset.py +155 -195
  109. google/adk/tools/openapi_tool/common/common.py +2 -5
  110. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +32 -7
  111. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +43 -33
  112. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
  113. google/adk/tools/preload_memory_tool.py +27 -18
  114. google/adk/tools/retrieval/__init__.py +1 -1
  115. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
  116. google/adk/tools/tool_context.py +4 -4
  117. google/adk/tools/toolbox_toolset.py +79 -0
  118. google/adk/tools/transfer_to_agent_tool.py +0 -1
  119. google/adk/version.py +1 -1
  120. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/METADATA +7 -5
  121. google_adk-1.0.0.dist-info/RECORD +195 -0
  122. google/adk/agents/remote_agent.py +0 -50
  123. google/adk/tools/google_api_tool/google_api_tool_set.py +0 -110
  124. google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
  125. google/adk/tools/toolbox_tool.py +0 -46
  126. google_adk-0.4.0.dist-info/RECORD +0 -179
  127. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/WHEEL +0 -0
  128. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/entry_points.txt +0 -0
  129. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -51,7 +51,7 @@ from .base_llm import BaseLlm
51
51
  from .llm_request import LlmRequest
52
52
  from .llm_response import LlmResponse
53
53
 
54
- logger = logging.getLogger(__name__)
54
+ logger = logging.getLogger("google_adk." + __name__)
55
55
 
56
56
  _NEW_LINE = "\n"
57
57
  _EXCLUDED_PART_FIELD = {"inline_data": {"data"}}
@@ -67,6 +67,12 @@ class TextChunk(BaseModel):
67
67
  text: str
68
68
 
69
69
 
70
+ class UsageMetadataChunk(BaseModel):
71
+ prompt_tokens: int
72
+ completion_tokens: int
73
+ total_tokens: int
74
+
75
+
70
76
  class LiteLLMClient:
71
77
  """Provides acompletion method (for better testability)."""
72
78
 
@@ -172,19 +178,19 @@ def _content_to_message_param(
172
178
  tool_calls = []
173
179
  content_present = False
174
180
  for part in content.parts:
175
- if part.function_call:
176
- tool_calls.append(
177
- ChatCompletionMessageToolCall(
178
- type="function",
179
- id=part.function_call.id,
180
- function=Function(
181
- name=part.function_call.name,
182
- arguments=part.function_call.args,
183
- ),
184
- )
181
+ if part.function_call:
182
+ tool_calls.append(
183
+ ChatCompletionMessageToolCall(
184
+ type="function",
185
+ id=part.function_call.id,
186
+ function=Function(
187
+ name=part.function_call.name,
188
+ arguments=part.function_call.args,
189
+ ),
185
190
  )
186
- elif part.text or part.inline_data:
187
- content_present = True
191
+ )
192
+ elif part.text or part.inline_data:
193
+ content_present = True
188
194
 
189
195
  final_content = message_content if content_present else None
190
196
 
@@ -344,15 +350,20 @@ def _function_declaration_to_tool_param(
344
350
  def _model_response_to_chunk(
345
351
  response: ModelResponse,
346
352
  ) -> Generator[
347
- Tuple[Optional[Union[TextChunk, FunctionChunk]], Optional[str]], None, None
353
+ Tuple[
354
+ Optional[Union[TextChunk, FunctionChunk, UsageMetadataChunk]],
355
+ Optional[str],
356
+ ],
357
+ None,
358
+ None,
348
359
  ]:
349
- """Converts a litellm message to text or function chunk.
360
+ """Converts a litellm message to text, function or usage metadata chunk.
350
361
 
351
362
  Args:
352
363
  response: The response from the model.
353
364
 
354
365
  Yields:
355
- A tuple of text or function chunk and finish reason.
366
+ A tuple of text or function or usage metadata chunk and finish reason.
356
367
  """
357
368
 
358
369
  message = None
@@ -384,11 +395,21 @@ def _model_response_to_chunk(
384
395
  if not message:
385
396
  yield None, None
386
397
 
398
+ # Ideally usage would be expected with the last ModelResponseStream with a
399
+ # finish_reason set. But this is not the case we are observing from litellm.
400
+ # So we are sending it as a separate chunk to be set on the llm_response.
401
+ if response.get("usage", None):
402
+ yield UsageMetadataChunk(
403
+ prompt_tokens=response["usage"].get("prompt_tokens", 0),
404
+ completion_tokens=response["usage"].get("completion_tokens", 0),
405
+ total_tokens=response["usage"].get("total_tokens", 0),
406
+ ), None
407
+
387
408
 
388
409
  def _model_response_to_generate_content_response(
389
410
  response: ModelResponse,
390
411
  ) -> LlmResponse:
391
- """Converts a litellm response to LlmResponse.
412
+ """Converts a litellm response to LlmResponse. Also adds usage metadata.
392
413
 
393
414
  Args:
394
415
  response: The model response.
@@ -403,7 +424,15 @@ def _model_response_to_generate_content_response(
403
424
 
404
425
  if not message:
405
426
  raise ValueError("No message in response")
406
- return _message_to_generate_content_response(message)
427
+
428
+ llm_response = _message_to_generate_content_response(message)
429
+ if response.get("usage", None):
430
+ llm_response.usage_metadata = types.GenerateContentResponseUsageMetadata(
431
+ prompt_token_count=response["usage"].get("prompt_tokens", 0),
432
+ candidates_token_count=response["usage"].get("completion_tokens", 0),
433
+ total_token_count=response["usage"].get("total_tokens", 0),
434
+ )
435
+ return llm_response
407
436
 
408
437
 
409
438
  def _message_to_generate_content_response(
@@ -453,9 +482,9 @@ def _get_completion_inputs(
453
482
  for content in llm_request.contents or []:
454
483
  message_param_or_list = _content_to_message_param(content)
455
484
  if isinstance(message_param_or_list, list):
456
- messages.extend(message_param_or_list)
457
- elif message_param_or_list: # Ensure it's not None before appending
458
- messages.append(message_param_or_list)
485
+ messages.extend(message_param_or_list)
486
+ elif message_param_or_list: # Ensure it's not None before appending
487
+ messages.append(message_param_or_list)
459
488
 
460
489
  if llm_request.config.system_instruction:
461
490
  messages.insert(
@@ -573,7 +602,6 @@ class LiteLlm(BaseLlm):
573
602
  Attributes:
574
603
  model: The name of the LiteLlm model.
575
604
  llm_client: The LLM client to use for the model.
576
- model_config: The model config.
577
605
  """
578
606
 
579
607
  llm_client: LiteLLMClient = Field(default_factory=LiteLLMClient)
@@ -611,7 +639,8 @@ class LiteLlm(BaseLlm):
611
639
  LlmResponse: The model response.
612
640
  """
613
641
 
614
- logger.info(_build_request_log(llm_request))
642
+ self._maybe_append_user_content(llm_request)
643
+ logger.debug(_build_request_log(llm_request))
615
644
 
616
645
  messages, tools = _get_completion_inputs(llm_request)
617
646
 
@@ -628,6 +657,10 @@ class LiteLlm(BaseLlm):
628
657
  function_args = ""
629
658
  function_id = None
630
659
  completion_args["stream"] = True
660
+ aggregated_llm_response = None
661
+ aggregated_llm_response_with_tool_call = None
662
+ usage_metadata = None
663
+
631
664
  for part in self.llm_client.completion(**completion_args):
632
665
  for chunk, finish_reason in _model_response_to_chunk(part):
633
666
  if isinstance(chunk, FunctionChunk):
@@ -645,32 +678,55 @@ class LiteLlm(BaseLlm):
645
678
  ),
646
679
  is_partial=True,
647
680
  )
681
+ elif isinstance(chunk, UsageMetadataChunk):
682
+ usage_metadata = types.GenerateContentResponseUsageMetadata(
683
+ prompt_token_count=chunk.prompt_tokens,
684
+ candidates_token_count=chunk.completion_tokens,
685
+ total_token_count=chunk.total_tokens,
686
+ )
687
+
648
688
  if finish_reason == "tool_calls" and function_id:
649
- yield _message_to_generate_content_response(
650
- ChatCompletionAssistantMessage(
651
- role="assistant",
652
- content="",
653
- tool_calls=[
654
- ChatCompletionMessageToolCall(
655
- type="function",
656
- id=function_id,
657
- function=Function(
658
- name=function_name,
659
- arguments=function_args,
660
- ),
661
- )
662
- ],
689
+ aggregated_llm_response_with_tool_call = (
690
+ _message_to_generate_content_response(
691
+ ChatCompletionAssistantMessage(
692
+ role="assistant",
693
+ content="",
694
+ tool_calls=[
695
+ ChatCompletionMessageToolCall(
696
+ type="function",
697
+ id=function_id,
698
+ function=Function(
699
+ name=function_name,
700
+ arguments=function_args,
701
+ ),
702
+ )
703
+ ],
704
+ )
663
705
  )
664
706
  )
665
707
  function_name = ""
666
708
  function_args = ""
667
709
  function_id = None
668
710
  elif finish_reason == "stop" and text:
669
- yield _message_to_generate_content_response(
711
+ aggregated_llm_response = _message_to_generate_content_response(
670
712
  ChatCompletionAssistantMessage(role="assistant", content=text)
671
713
  )
672
714
  text = ""
673
715
 
716
+ # waiting until streaming ends to yield the llm_response as litellm tends
717
+ # to send chunk that contains usage_metadata after the chunk with
718
+ # finish_reason set to tool_calls or stop.
719
+ if aggregated_llm_response:
720
+ if usage_metadata:
721
+ aggregated_llm_response.usage_metadata = usage_metadata
722
+ usage_metadata = None
723
+ yield aggregated_llm_response
724
+
725
+ if aggregated_llm_response_with_tool_call:
726
+ if usage_metadata:
727
+ aggregated_llm_response_with_tool_call.usage_metadata = usage_metadata
728
+ yield aggregated_llm_response_with_tool_call
729
+
674
730
  else:
675
731
  response = await self.llm_client.acompletion(**completion_args)
676
732
  yield _model_response_to_generate_content_response(response)
@@ -37,7 +37,7 @@ class LlmRequest(BaseModel):
37
37
  """
38
38
 
39
39
  model_config = ConfigDict(arbitrary_types_allowed=True)
40
- """The model config."""
40
+ """The pydantic model config."""
41
41
 
42
42
  model: Optional[str] = None
43
43
  """The model name."""
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
  from typing import Any, Optional
18
18
 
19
19
  from google.genai import types
20
+ from pydantic import alias_generators
20
21
  from pydantic import BaseModel
21
22
  from pydantic import ConfigDict
22
23
 
@@ -40,8 +41,12 @@ class LlmResponse(BaseModel):
40
41
  custom_metadata: The custom metadata of the LlmResponse.
41
42
  """
42
43
 
43
- model_config = ConfigDict(extra='forbid')
44
- """The model config."""
44
+ model_config = ConfigDict(
45
+ extra='forbid',
46
+ alias_generator=alias_generators.to_camel,
47
+ populate_by_name=True,
48
+ )
49
+ """The pydantic model config."""
45
50
 
46
51
  content: Optional[types.Content] = None
47
52
  """The content of the response."""
@@ -80,6 +85,9 @@ class LlmResponse(BaseModel):
80
85
  NOTE: the entire dict must be JSON serializable.
81
86
  """
82
87
 
88
+ usage_metadata: Optional[types.GenerateContentResponseUsageMetadata] = None
89
+ """The usage metadata of the LlmResponse"""
90
+
83
91
  @staticmethod
84
92
  def create(
85
93
  generate_content_response: types.GenerateContentResponse,
@@ -93,18 +101,20 @@ class LlmResponse(BaseModel):
93
101
  Returns:
94
102
  The LlmResponse.
95
103
  """
96
-
104
+ usage_metadata = generate_content_response.usage_metadata
97
105
  if generate_content_response.candidates:
98
106
  candidate = generate_content_response.candidates[0]
99
107
  if candidate.content and candidate.content.parts:
100
108
  return LlmResponse(
101
109
  content=candidate.content,
102
110
  grounding_metadata=candidate.grounding_metadata,
111
+ usage_metadata=usage_metadata,
103
112
  )
104
113
  else:
105
114
  return LlmResponse(
106
115
  error_code=candidate.finish_reason,
107
116
  error_message=candidate.finish_message,
117
+ usage_metadata=usage_metadata,
108
118
  )
109
119
  else:
110
120
  if generate_content_response.prompt_feedback:
@@ -112,9 +122,11 @@ class LlmResponse(BaseModel):
112
122
  return LlmResponse(
113
123
  error_code=prompt_feedback.block_reason,
114
124
  error_message=prompt_feedback.block_reason_message,
125
+ usage_metadata=usage_metadata,
115
126
  )
116
127
  else:
117
128
  return LlmResponse(
118
129
  error_code='UNKNOWN_ERROR',
119
130
  error_message='Unknown error.',
131
+ usage_metadata=usage_metadata,
120
132
  )
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING
24
24
  if TYPE_CHECKING:
25
25
  from .base_llm import BaseLlm
26
26
 
27
- logger = logging.getLogger(__name__)
27
+ logger = logging.getLogger('google_adk.' + __name__)
28
28
 
29
29
 
30
30
  _llm_registry_dict: dict[str, type[BaseLlm]] = {}
google/adk/runners.py CHANGED
@@ -21,8 +21,8 @@ import threading
21
21
  from typing import AsyncGenerator
22
22
  from typing import Generator
23
23
  from typing import Optional
24
+ import warnings
24
25
 
25
- from deprecated import deprecated
26
26
  from google.genai import types
27
27
 
28
28
  from .agents.active_streaming_tool import ActiveStreamingTool
@@ -32,7 +32,6 @@ from .agents.invocation_context import new_invocation_context_id
32
32
  from .agents.live_request_queue import LiveRequestQueue
33
33
  from .agents.llm_agent import LlmAgent
34
34
  from .agents.run_config import RunConfig
35
- from .agents.run_config import StreamingMode
36
35
  from .artifacts.base_artifact_service import BaseArtifactService
37
36
  from .artifacts.in_memory_artifact_service import InMemoryArtifactService
38
37
  from .events.event import Event
@@ -42,9 +41,9 @@ from .sessions.base_session_service import BaseSessionService
42
41
  from .sessions.in_memory_session_service import InMemorySessionService
43
42
  from .sessions.session import Session
44
43
  from .telemetry import tracer
45
- from .tools.built_in_code_execution_tool import built_in_code_execution
44
+ from .tools._built_in_code_execution_tool import built_in_code_execution
46
45
 
47
- logger = logging.getLogger(__name__)
46
+ logger = logging.getLogger('google_adk.' + __name__)
48
47
 
49
48
 
50
49
  class Runner:
@@ -172,7 +171,7 @@ class Runner:
172
171
  The events generated by the agent.
173
172
  """
174
173
  with tracer.start_as_current_span('invocation'):
175
- session = self.session_service.get_session(
174
+ session = await self.session_service.get_session(
176
175
  app_name=self.app_name, user_id=user_id, session_id=session_id
177
176
  )
178
177
  if not session:
@@ -186,7 +185,7 @@ class Runner:
186
185
  root_agent = self.agent
187
186
 
188
187
  if new_message:
189
- self._append_new_message_to_session(
188
+ await self._append_new_message_to_session(
190
189
  session,
191
190
  new_message,
192
191
  invocation_context,
@@ -196,10 +195,10 @@ class Runner:
196
195
  invocation_context.agent = self._find_agent_to_run(session, root_agent)
197
196
  async for event in invocation_context.agent.run_async(invocation_context):
198
197
  if not event.partial:
199
- self.session_service.append_event(session=session, event=event)
198
+ await self.session_service.append_event(session=session, event=event)
200
199
  yield event
201
200
 
202
- def _append_new_message_to_session(
201
+ async def _append_new_message_to_session(
203
202
  self,
204
203
  session: Session,
205
204
  new_message: types.Content,
@@ -225,7 +224,7 @@ class Runner:
225
224
  if part.inline_data is None:
226
225
  continue
227
226
  file_name = f'artifact_{invocation_context.invocation_id}_{i}'
228
- self.artifact_service.save_artifact(
227
+ await self.artifact_service.save_artifact(
229
228
  app_name=self.app_name,
230
229
  user_id=session.user_id,
231
230
  session_id=session.id,
@@ -241,30 +240,57 @@ class Runner:
241
240
  author='user',
242
241
  content=new_message,
243
242
  )
244
- self.session_service.append_event(session=session, event=event)
243
+ await self.session_service.append_event(session=session, event=event)
245
244
 
246
245
  async def run_live(
247
246
  self,
248
247
  *,
249
- session: Session,
248
+ user_id: Optional[str] = None,
249
+ session_id: Optional[str] = None,
250
250
  live_request_queue: LiveRequestQueue,
251
251
  run_config: RunConfig = RunConfig(),
252
+ session: Optional[Session] = None,
252
253
  ) -> AsyncGenerator[Event, None]:
253
254
  """Runs the agent in live mode (experimental feature).
254
255
 
255
256
  Args:
256
- session: The session to use.
257
+ user_id: The user ID for the session. Required if `session` is None.
258
+ session_id: The session ID for the session. Required if `session` is
259
+ None.
257
260
  live_request_queue: The queue for live requests.
258
261
  run_config: The run config for the agent.
262
+ session: The session to use. This parameter is deprecated, please use
263
+ `user_id` and `session_id` instead.
259
264
 
260
265
  Yields:
261
- The events generated by the agent.
266
+ AsyncGenerator[Event, None]: An asynchronous generator that yields
267
+ `Event`
268
+ objects as they are produced by the agent during its live execution.
262
269
 
263
270
  .. warning::
264
271
  This feature is **experimental** and its API or behavior may change
265
272
  in future releases.
273
+
274
+ .. note::
275
+ Either `session` or both `user_id` and `session_id` must be provided.
266
276
  """
267
- # TODO: right now, only works for a single audio agent without FC.
277
+ if session is None and (user_id is None or session_id is None):
278
+ raise ValueError(
279
+ 'Either session or user_id and session_id must be provided.'
280
+ )
281
+ if session is not None:
282
+ warnings.warn(
283
+ 'The `session` parameter is deprecated. Please use `user_id` and'
284
+ ' `session_id` instead.',
285
+ DeprecationWarning,
286
+ stacklevel=2,
287
+ )
288
+ if not session:
289
+ session = self.session_service.get_session(
290
+ app_name=self.app_name, user_id=user_id, session_id=session_id
291
+ )
292
+ if not session:
293
+ raise ValueError(f'Session not found: {session_id}')
268
294
  invocation_context = self._new_invocation_context_for_live(
269
295
  session,
270
296
  live_request_queue=live_request_queue,
@@ -276,37 +302,29 @@ class Runner:
276
302
 
277
303
  invocation_context.active_streaming_tools = {}
278
304
  # TODO(hangfei): switch to use canonical_tools.
279
- for tool in invocation_context.agent.tools:
280
- # replicate a LiveRequestQueue for streaming tools that relis on
281
- # LiveRequestQueue
282
- from typing import get_type_hints
283
-
284
- type_hints = get_type_hints(tool)
285
- for arg_type in type_hints.values():
286
- if arg_type is LiveRequestQueue:
287
- if not invocation_context.active_streaming_tools:
288
- invocation_context.active_streaming_tools = {}
289
- active_streaming_tools = ActiveStreamingTool(
290
- stream=LiveRequestQueue()
291
- )
292
- invocation_context.active_streaming_tools[tool.__name__] = (
293
- active_streaming_tools
294
- )
305
+ # for shell agents, there is no tools associated with it so we should skip.
306
+ if hasattr(invocation_context.agent, 'tools'):
307
+ for tool in invocation_context.agent.tools:
308
+ # replicate a LiveRequestQueue for streaming tools that relis on
309
+ # LiveRequestQueue
310
+ from typing import get_type_hints
311
+
312
+ type_hints = get_type_hints(tool)
313
+ for arg_type in type_hints.values():
314
+ if arg_type is LiveRequestQueue:
315
+ if not invocation_context.active_streaming_tools:
316
+ invocation_context.active_streaming_tools = {}
317
+ active_streaming_tools = ActiveStreamingTool(
318
+ stream=LiveRequestQueue()
319
+ )
320
+ invocation_context.active_streaming_tools[tool.__name__] = (
321
+ active_streaming_tools
322
+ )
295
323
 
296
324
  async for event in invocation_context.agent.run_live(invocation_context):
297
- self.session_service.append_event(session=session, event=event)
325
+ await self.session_service.append_event(session=session, event=event)
298
326
  yield event
299
327
 
300
- def close_session(self, session: Session):
301
- """Closes a session and adds it to the memory service (experimental feature).
302
-
303
- Args:
304
- session: The session to close.
305
- """
306
- if self.memory_service:
307
- self.memory_service.add_session_to_memory(session)
308
- self.session_service.close_session(session=session)
309
-
310
328
  def _find_agent_to_run(
311
329
  self, session: Session, root_agent: BaseAgent
312
330
  ) -> BaseAgent:
@@ -391,7 +409,7 @@ class Runner:
391
409
  f'CFC is not supported for model: {model_name} in agent:'
392
410
  f' {self.agent.name}'
393
411
  )
394
- if built_in_code_execution not in self.agent.canonical_tools:
412
+ if built_in_code_execution not in self.agent.canonical_tools():
395
413
  self.agent.tools.append(built_in_code_execution)
396
414
 
397
415
  return InvocationContext(
@@ -430,6 +448,9 @@ class Runner:
430
448
  run_config.output_audio_transcription = (
431
449
  types.AudioTranscriptionConfig()
432
450
  )
451
+ if not run_config.input_audio_transcription:
452
+ # need this input transcription for agent transferring in live mode.
453
+ run_config.input_audio_transcription = types.AudioTranscriptionConfig()
433
454
  return self._new_invocation_context(
434
455
  session,
435
456
  live_request_queue=live_request_queue,
@@ -448,9 +469,11 @@ class InMemoryRunner(Runner):
448
469
  agent: The root agent to run.
449
470
  app_name: The application name of the runner. Defaults to
450
471
  'InMemoryRunner'.
472
+ _in_memory_session_service: Deprecated. Please don't use. The in-memory
473
+ session service for the runner.
451
474
  """
452
475
 
453
- def __init__(self, agent: LlmAgent, *, app_name: str = 'InMemoryRunner'):
476
+ def __init__(self, agent: BaseAgent, *, app_name: str = 'InMemoryRunner'):
454
477
  """Initializes the InMemoryRunner.
455
478
 
456
479
  Args:
@@ -458,10 +481,11 @@ class InMemoryRunner(Runner):
458
481
  app_name: The application name of the runner. Defaults to
459
482
  'InMemoryRunner'.
460
483
  """
484
+ self._in_memory_session_service = InMemorySessionService()
461
485
  super().__init__(
462
486
  app_name=app_name,
463
487
  agent=agent,
464
488
  artifact_service=InMemoryArtifactService(),
465
- session_service=InMemorySessionService(),
489
+ session_service=self._in_memory_session_service,
466
490
  memory_service=InMemoryMemoryService(),
467
491
  )
@@ -19,7 +19,7 @@ from .session import Session
19
19
  from .state import State
20
20
  from .vertex_ai_session_service import VertexAiSessionService
21
21
 
22
- logger = logging.getLogger(__name__)
22
+ logger = logging.getLogger('google_adk.' + __name__)
23
23
 
24
24
 
25
25
  __all__ = [
@@ -1,3 +1,17 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  """Utility functions for session service."""
2
16
 
3
17
  import base64
@@ -26,6 +26,7 @@ from .state import State
26
26
 
27
27
  class GetSessionConfig(BaseModel):
28
28
  """The configuration of getting a session."""
29
+
29
30
  num_recent_events: Optional[int] = None
30
31
  after_timestamp: Optional[float] = None
31
32
 
@@ -35,13 +36,8 @@ class ListSessionsResponse(BaseModel):
35
36
 
36
37
  The events and states are not set within each Session object.
37
38
  """
38
- sessions: list[Session] = Field(default_factory=list)
39
-
40
39
 
41
- class ListEventsResponse(BaseModel):
42
- """The response of listing events in a session."""
43
- events: list[Event] = Field(default_factory=list)
44
- next_page_token: Optional[str] = None
40
+ sessions: list[Session] = Field(default_factory=list)
45
41
 
46
42
 
47
43
  class BaseSessionService(abc.ABC):
@@ -51,7 +47,7 @@ class BaseSessionService(abc.ABC):
51
47
  """
52
48
 
53
49
  @abc.abstractmethod
54
- def create_session(
50
+ async def create_session(
55
51
  self,
56
52
  *,
57
53
  app_name: str,
@@ -71,10 +67,9 @@ class BaseSessionService(abc.ABC):
71
67
  Returns:
72
68
  session: The newly created session instance.
73
69
  """
74
- pass
75
70
 
76
71
  @abc.abstractmethod
77
- def get_session(
72
+ async def get_session(
78
73
  self,
79
74
  *,
80
75
  app_name: str,
@@ -83,39 +78,20 @@ class BaseSessionService(abc.ABC):
83
78
  config: Optional[GetSessionConfig] = None,
84
79
  ) -> Optional[Session]:
85
80
  """Gets a session."""
86
- pass
87
81
 
88
82
  @abc.abstractmethod
89
- def list_sessions(
83
+ async def list_sessions(
90
84
  self, *, app_name: str, user_id: str
91
85
  ) -> ListSessionsResponse:
92
86
  """Lists all the sessions."""
93
- pass
94
87
 
95
88
  @abc.abstractmethod
96
- def delete_session(
89
+ async def delete_session(
97
90
  self, *, app_name: str, user_id: str, session_id: str
98
91
  ) -> None:
99
92
  """Deletes a session."""
100
- pass
101
-
102
- @abc.abstractmethod
103
- def list_events(
104
- self,
105
- *,
106
- app_name: str,
107
- user_id: str,
108
- session_id: str,
109
- ) -> ListEventsResponse:
110
- """Lists events in a session."""
111
- pass
112
-
113
- def close_session(self, *, session: Session):
114
- """Closes a session."""
115
- # TODO: determine whether we want to finalize the session here.
116
- pass
117
93
 
118
- def append_event(self, session: Session, event: Event) -> Event:
94
+ async def append_event(self, session: Session, event: Event) -> Event:
119
95
  """Appends an event to a session object."""
120
96
  if event.partial:
121
97
  return event
@@ -123,7 +99,7 @@ class BaseSessionService(abc.ABC):
123
99
  session.events.append(event)
124
100
  return event
125
101
 
126
- def __update_session_state(self, session: Session, event: Event):
102
+ def __update_session_state(self, session: Session, event: Event) -> None:
127
103
  """Updates the session state based on the event."""
128
104
  if not event.actions or not event.actions.state_delta:
129
105
  return