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
@@ -19,12 +19,14 @@ from __future__ import annotations
19
19
  from functools import cached_property
20
20
  import logging
21
21
  import os
22
+ from typing import Any
22
23
  from typing import AsyncGenerator
23
24
  from typing import Generator
24
25
  from typing import Iterable
25
26
  from typing import Literal
26
- from typing import Optional, Union
27
+ from typing import Optional
27
28
  from typing import TYPE_CHECKING
29
+ from typing import Union
28
30
 
29
31
  from anthropic import AnthropicVertex
30
32
  from anthropic import NOT_GIVEN
@@ -41,7 +43,7 @@ if TYPE_CHECKING:
41
43
 
42
44
  __all__ = ["Claude"]
43
45
 
44
- logger = logging.getLogger(__name__)
46
+ logger = logging.getLogger("google_adk." + __name__)
45
47
 
46
48
  MAX_TOKEN = 1024
47
49
 
@@ -139,18 +141,36 @@ def message_to_generate_content_response(
139
141
  role="model",
140
142
  parts=[content_block_to_part(cb) for cb in message.content],
141
143
  ),
144
+ usage_metadata=types.GenerateContentResponseUsageMetadata(
145
+ prompt_token_count=message.usage.input_tokens,
146
+ candidates_token_count=message.usage.output_tokens,
147
+ total_token_count=(
148
+ message.usage.input_tokens + message.usage.output_tokens
149
+ ),
150
+ ),
142
151
  # TODO: Deal with these later.
143
152
  # finish_reason=to_google_genai_finish_reason(message.stop_reason),
144
- # usage_metadata=types.GenerateContentResponseUsageMetadata(
145
- # prompt_token_count=message.usage.input_tokens,
146
- # candidates_token_count=message.usage.output_tokens,
147
- # total_token_count=(
148
- # message.usage.input_tokens + message.usage.output_tokens
149
- # ),
150
- # ),
151
153
  )
152
154
 
153
155
 
156
+ def _update_type_string(value_dict: dict[str, Any]):
157
+ """Updates 'type' field to expected JSON schema format."""
158
+ if "type" in value_dict:
159
+ value_dict["type"] = value_dict["type"].lower()
160
+
161
+ if "items" in value_dict:
162
+ # 'type' field could exist for items as well, this would be the case if
163
+ # items represent primitive types.
164
+ _update_type_string(value_dict["items"])
165
+
166
+ if "properties" in value_dict["items"]:
167
+ # There could be properties as well on the items, especially if the items
168
+ # are complex object themselves. We recursively traverse each individual
169
+ # property as well and fix the "type" value.
170
+ for _, value in value_dict["items"]["properties"].items():
171
+ _update_type_string(value)
172
+
173
+
154
174
  def function_declaration_to_tool_param(
155
175
  function_declaration: types.FunctionDeclaration,
156
176
  ) -> anthropic_types.ToolParam:
@@ -163,8 +183,7 @@ def function_declaration_to_tool_param(
163
183
  ):
164
184
  for key, value in function_declaration.parameters.properties.items():
165
185
  value_dict = value.model_dump(exclude_none=True)
166
- if "type" in value_dict:
167
- value_dict["type"] = value_dict["type"].lower()
186
+ _update_type_string(value_dict)
168
187
  properties[key] = value_dict
169
188
 
170
189
  return anthropic_types.ToolParam(
@@ -178,6 +197,12 @@ def function_declaration_to_tool_param(
178
197
 
179
198
 
180
199
  class Claude(BaseLlm):
200
+ """ "Integration with Claude models served from Vertex AI.
201
+
202
+ Attributes:
203
+ model: The name of the Claude model.
204
+ """
205
+
181
206
  model: str = "claude-3-5-sonnet-v2@20241022"
182
207
 
183
208
  @staticmethod
@@ -14,9 +14,9 @@
14
14
  from __future__ import annotations
15
15
 
16
16
  from abc import abstractmethod
17
- from typing import AsyncGenerator
18
- from typing import TYPE_CHECKING
17
+ from typing import AsyncGenerator, TYPE_CHECKING
19
18
 
19
+ from google.genai import types
20
20
  from pydantic import BaseModel
21
21
  from pydantic import ConfigDict
22
22
 
@@ -32,14 +32,13 @@ class BaseLlm(BaseModel):
32
32
 
33
33
  Attributes:
34
34
  model: The name of the LLM, e.g. gemini-1.5-flash or gemini-1.5-flash-001.
35
- model_config: The model config
36
35
  """
37
36
 
38
37
  model_config = ConfigDict(
39
38
  # This allows us to use arbitrary types in the model. E.g. PIL.Image.
40
39
  arbitrary_types_allowed=True,
41
40
  )
42
- """The model config."""
41
+ """The pydantic model config."""
43
42
 
44
43
  model: str
45
44
  """The name of the LLM, e.g. gemini-1.5-flash or gemini-1.5-flash-001."""
@@ -73,6 +72,48 @@ class BaseLlm(BaseModel):
73
72
  )
74
73
  yield # AsyncGenerator requires a yield statement in function body.
75
74
 
75
+ def _maybe_append_user_content(self, llm_request: LlmRequest):
76
+ """Appends a user content, so that model can continue to output.
77
+
78
+ Args:
79
+ llm_request: LlmRequest, the request to send to the Gemini model.
80
+ """
81
+ # If no content is provided, append a user content to hint model response
82
+ # using system instruction.
83
+ if not llm_request.contents:
84
+ llm_request.contents.append(
85
+ types.Content(
86
+ role='user',
87
+ parts=[
88
+ types.Part(
89
+ text=(
90
+ 'Handle the requests as specified in the System'
91
+ ' Instruction.'
92
+ )
93
+ )
94
+ ],
95
+ )
96
+ )
97
+ return
98
+
99
+ # Insert a user content to preserve user intent and to avoid empty
100
+ # model response.
101
+ if llm_request.contents[-1].role != 'user':
102
+ llm_request.contents.append(
103
+ types.Content(
104
+ role='user',
105
+ parts=[
106
+ types.Part(
107
+ text=(
108
+ 'Continue processing previous requests as instructed.'
109
+ ' Exit or provide a summary if no more outputs are'
110
+ ' needed.'
111
+ )
112
+ )
113
+ ],
114
+ )
115
+ )
116
+
76
117
  def connect(self, llm_request: LlmRequest) -> BaseLlmConnection:
77
118
  """Creates a live connection to the LLM.
78
119
 
@@ -21,7 +21,7 @@ from google.genai import types
21
21
  from .base_llm_connection import BaseLlmConnection
22
22
  from .llm_response import LlmResponse
23
23
 
24
- logger = logging.getLogger(__name__)
24
+ logger = logging.getLogger('google_adk.' + __name__)
25
25
 
26
26
 
27
27
  class GeminiLlmConnection(BaseLlmConnection):
@@ -145,7 +145,20 @@ class GeminiLlmConnection(BaseLlmConnection):
145
145
  yield self.__build_full_text_response(text)
146
146
  text = ''
147
147
  yield llm_response
148
-
148
+ if (
149
+ message.server_content.input_transcription
150
+ and message.server_content.input_transcription.text
151
+ ):
152
+ user_text = message.server_content.input_transcription.text
153
+ parts = [
154
+ types.Part.from_text(
155
+ text=user_text,
156
+ )
157
+ ]
158
+ llm_response = LlmResponse(
159
+ content=types.Content(role='user', parts=parts)
160
+ )
161
+ yield llm_response
149
162
  if (
150
163
  message.server_content.output_transcription
151
164
  and message.server_content.output_transcription.text
@@ -11,6 +11,8 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
15
+
14
16
  from __future__ import annotations
15
17
 
16
18
  import contextlib
@@ -34,7 +36,7 @@ from .llm_response import LlmResponse
34
36
  if TYPE_CHECKING:
35
37
  from .llm_request import LlmRequest
36
38
 
37
- logger = logging.getLogger(__name__)
39
+ logger = logging.getLogger('google_adk.' + __name__)
38
40
 
39
41
  _NEW_LINE = '\n'
40
42
  _EXCLUDED_PART_FIELD = {'inline_data': {'data'}}
@@ -121,6 +123,7 @@ class Gemini(BaseLlm):
121
123
  content=types.ModelContent(
122
124
  parts=[types.Part.from_text(text=text)],
123
125
  ),
126
+ usage_metadata=llm_response.usage_metadata,
124
127
  )
125
128
  text = ''
126
129
  yield llm_response
@@ -174,9 +177,13 @@ class Gemini(BaseLlm):
174
177
  @cached_property
175
178
  def _live_api_client(self) -> Client:
176
179
  if self._api_backend == 'vertex':
180
+ # use beta version for vertex api
181
+ api_version = 'v1beta1'
177
182
  # use default api version for vertex
178
183
  return Client(
179
- http_options=types.HttpOptions(headers=self._tracking_headers)
184
+ http_options=types.HttpOptions(
185
+ headers=self._tracking_headers, api_version=api_version
186
+ )
180
187
  )
181
188
  else:
182
189
  # use v1alpha for ml_dev
@@ -210,48 +217,6 @@ class Gemini(BaseLlm):
210
217
  ) as live_session:
211
218
  yield GeminiLlmConnection(live_session)
212
219
 
213
- def _maybe_append_user_content(self, llm_request: LlmRequest):
214
- """Appends a user content, so that model can continue to output.
215
-
216
- Args:
217
- llm_request: LlmRequest, the request to send to the Gemini model.
218
- """
219
- # If no content is provided, append a user content to hint model response
220
- # using system instruction.
221
- if not llm_request.contents:
222
- llm_request.contents.append(
223
- types.Content(
224
- role='user',
225
- parts=[
226
- types.Part(
227
- text=(
228
- 'Handle the requests as specified in the System'
229
- ' Instruction.'
230
- )
231
- )
232
- ],
233
- )
234
- )
235
- return
236
-
237
- # Insert a user content to preserve user intent and to avoid empty
238
- # model response.
239
- if llm_request.contents[-1].role != 'user':
240
- llm_request.contents.append(
241
- types.Content(
242
- role='user',
243
- parts=[
244
- types.Part(
245
- text=(
246
- 'Continue processing previous requests as instructed.'
247
- ' Exit or provide a summary if no more outputs are'
248
- ' needed.'
249
- )
250
- )
251
- ],
252
- )
253
- )
254
-
255
220
 
256
221
  def _build_function_declaration_log(
257
222
  func_decl: types.FunctionDeclaration,
@@ -0,0 +1,305 @@
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
+ from __future__ import annotations
15
+
16
+ import contextlib
17
+ from functools import cached_property
18
+ import logging
19
+ import sys
20
+ from typing import AsyncGenerator
21
+ from typing import cast
22
+ from typing import TYPE_CHECKING
23
+
24
+ from google.genai import Client
25
+ from google.genai import types
26
+ from typing_extensions import override
27
+
28
+ from .. import version
29
+ from .base_llm import BaseLlm
30
+ from .base_llm_connection import BaseLlmConnection
31
+ from .gemini_llm_connection import GeminiLlmConnection
32
+ from .llm_response import LlmResponse
33
+
34
+ if TYPE_CHECKING:
35
+ from .llm_request import LlmRequest
36
+
37
+ logger = None
38
+
39
+ _NEW_LINE = '\n'
40
+ _EXCLUDED_PART_FIELD = {'inline_data': {'data'}}
41
+
42
+
43
+ class Gemini(BaseLlm):
44
+ """Integration for Gemini models.
45
+
46
+ Attributes:
47
+ model: The name of the Gemini model.
48
+ """
49
+
50
+ model: str = 'gemini-1.5-flash'
51
+
52
+ @staticmethod
53
+ @override
54
+ def supported_models() -> list[str]:
55
+ """Provides the list of supported models.
56
+
57
+ Returns:
58
+ A list of supported models.
59
+ """
60
+
61
+ return [
62
+ r'gemini-.*',
63
+ # fine-tuned vertex endpoint pattern
64
+ r'projects\/.+\/locations\/.+\/endpoints\/.+',
65
+ # vertex gemini long name
66
+ r'projects\/.+\/locations\/.+\/publishers\/google\/models\/gemini.+',
67
+ ]
68
+
69
+ async def generate_content_async(
70
+ self, llm_request: LlmRequest, stream: bool = False
71
+ ) -> AsyncGenerator[LlmResponse, None]:
72
+ """Sends a request to the Gemini model.
73
+
74
+ Args:
75
+ llm_request: LlmRequest, the request to send to the Gemini model.
76
+ stream: bool = False, whether to do streaming call.
77
+
78
+ Yields:
79
+ LlmResponse: The model response.
80
+ """
81
+
82
+ self._maybe_append_user_content(llm_request)
83
+
84
+ global logger
85
+ if not logger:
86
+ logger = logging.getLogger(__name__)
87
+
88
+ logger.info(
89
+ 'Sending out request, model: %s, backend: %s, stream: %s',
90
+ llm_request.model,
91
+ self._api_backend,
92
+ stream,
93
+ )
94
+ logger.info(_build_request_log(llm_request))
95
+
96
+ print('********* Jack --> ')
97
+ for hh in logging.root.handlers:
98
+ print(hh, hh.level)
99
+ for hh in logger.handlers:
100
+ print(hh, hh.level)
101
+ print('********* Jack <-- ')
102
+
103
+ if stream:
104
+ responses = await self.api_client.aio.models.generate_content_stream(
105
+ model=llm_request.model,
106
+ contents=llm_request.contents,
107
+ config=llm_request.config,
108
+ )
109
+ response = None
110
+ text = ''
111
+ # for sse, similar as bidi (see receive method in gemini_llm_connecton.py),
112
+ # we need to mark those text content as partial and after all partial
113
+ # contents are sent, we send an accumulated event which contains all the
114
+ # previous partial content. The only difference is bidi rely on
115
+ # complete_turn flag to detect end while sse depends on finish_reason.
116
+ async for response in responses:
117
+ logger.info(_build_response_log(response))
118
+ llm_response = LlmResponse.create(response)
119
+ if (
120
+ llm_response.content
121
+ and llm_response.content.parts
122
+ and llm_response.content.parts[0].text
123
+ ):
124
+ text += llm_response.content.parts[0].text
125
+ llm_response.partial = True
126
+ elif text and (
127
+ not llm_response.content
128
+ or not llm_response.content.parts
129
+ # don't yield the merged text event when receiving audio data
130
+ or not llm_response.content.parts[0].inline_data
131
+ ):
132
+ yield LlmResponse(
133
+ content=types.ModelContent(
134
+ parts=[types.Part.from_text(text=text)],
135
+ ),
136
+ usage_metadata=llm_response.usage_metadata,
137
+ )
138
+ text = ''
139
+ yield llm_response
140
+ if (
141
+ text
142
+ and response
143
+ and response.candidates
144
+ and response.candidates[0].finish_reason == types.FinishReason.STOP
145
+ ):
146
+ yield LlmResponse(
147
+ content=types.ModelContent(
148
+ parts=[types.Part.from_text(text=text)],
149
+ ),
150
+ )
151
+
152
+ else:
153
+ response = await self.api_client.aio.models.generate_content(
154
+ model=llm_request.model,
155
+ contents=llm_request.contents,
156
+ config=llm_request.config,
157
+ )
158
+ logger.info(_build_response_log(response))
159
+ yield LlmResponse.create(response)
160
+
161
+ @cached_property
162
+ def api_client(self) -> Client:
163
+ """Provides the api client.
164
+
165
+ Returns:
166
+ The api client.
167
+ """
168
+ return Client(
169
+ http_options=types.HttpOptions(headers=self._tracking_headers)
170
+ )
171
+
172
+ @cached_property
173
+ def _api_backend(self) -> str:
174
+ return 'vertex' if self.api_client.vertexai else 'ml_dev'
175
+
176
+ @cached_property
177
+ def _tracking_headers(self) -> dict[str, str]:
178
+ framework_label = f'google-adk/{version.__version__}'
179
+ language_label = 'gl-python/' + sys.version.split()[0]
180
+ version_header_value = f'{framework_label} {language_label}'
181
+ tracking_headers = {
182
+ 'x-goog-api-client': version_header_value,
183
+ 'user-agent': version_header_value,
184
+ }
185
+ return tracking_headers
186
+
187
+ @cached_property
188
+ def _live_api_client(self) -> Client:
189
+ if self._api_backend == 'vertex':
190
+ # use beta version for vertex api
191
+ api_version = 'v1beta1'
192
+ # use default api version for vertex
193
+ return Client(
194
+ http_options=types.HttpOptions(
195
+ headers=self._tracking_headers, api_version=api_version
196
+ )
197
+ )
198
+ else:
199
+ # use v1alpha for ml_dev
200
+ api_version = 'v1alpha'
201
+ return Client(
202
+ http_options=types.HttpOptions(
203
+ headers=self._tracking_headers, api_version=api_version
204
+ )
205
+ )
206
+
207
+ @contextlib.asynccontextmanager
208
+ async def connect(self, llm_request: LlmRequest) -> BaseLlmConnection:
209
+ """Connects to the Gemini model and returns an llm connection.
210
+
211
+ Args:
212
+ llm_request: LlmRequest, the request to send to the Gemini model.
213
+
214
+ Yields:
215
+ BaseLlmConnection, the connection to the Gemini model.
216
+ """
217
+
218
+ llm_request.live_connect_config.system_instruction = types.Content(
219
+ role='system',
220
+ parts=[
221
+ types.Part.from_text(text=llm_request.config.system_instruction)
222
+ ],
223
+ )
224
+ llm_request.live_connect_config.tools = llm_request.config.tools
225
+ async with self._live_api_client.aio.live.connect(
226
+ model=llm_request.model, config=llm_request.live_connect_config
227
+ ) as live_session:
228
+ yield GeminiLlmConnection(live_session)
229
+
230
+
231
+ def _build_function_declaration_log(
232
+ func_decl: types.FunctionDeclaration,
233
+ ) -> str:
234
+ param_str = '{}'
235
+ if func_decl.parameters and func_decl.parameters.properties:
236
+ param_str = str({
237
+ k: v.model_dump(exclude_none=True)
238
+ for k, v in func_decl.parameters.properties.items()
239
+ })
240
+ return_str = 'None'
241
+ if func_decl.response:
242
+ return_str = str(func_decl.response.model_dump(exclude_none=True))
243
+ return f'{func_decl.name}: {param_str} -> {return_str}'
244
+
245
+
246
+ def _build_request_log(req: LlmRequest) -> str:
247
+ function_decls: list[types.FunctionDeclaration] = cast(
248
+ list[types.FunctionDeclaration],
249
+ req.config.tools[0].function_declarations if req.config.tools else [],
250
+ )
251
+ function_logs = (
252
+ [
253
+ _build_function_declaration_log(func_decl)
254
+ for func_decl in function_decls
255
+ ]
256
+ if function_decls
257
+ else []
258
+ )
259
+ contents_logs = [
260
+ content.model_dump_json(
261
+ exclude_none=True,
262
+ exclude={
263
+ 'parts': {
264
+ i: _EXCLUDED_PART_FIELD for i in range(len(content.parts))
265
+ }
266
+ },
267
+ )
268
+ for content in req.contents
269
+ ]
270
+
271
+ return f"""
272
+ LLM Request:
273
+ -----------------------------------------------------------
274
+ System Instruction:
275
+ {req.config.system_instruction}
276
+ -----------------------------------------------------------
277
+ Contents:
278
+ {_NEW_LINE.join(contents_logs)}
279
+ -----------------------------------------------------------
280
+ Functions:
281
+ {_NEW_LINE.join(function_logs)}
282
+ -----------------------------------------------------------
283
+ """
284
+
285
+
286
+ def _build_response_log(resp: types.GenerateContentResponse) -> str:
287
+ function_calls_text = []
288
+ if function_calls := resp.function_calls:
289
+ for func_call in function_calls:
290
+ function_calls_text.append(
291
+ f'name: {func_call.name}, args: {func_call.args}'
292
+ )
293
+ return f"""
294
+ LLM Response:
295
+ -----------------------------------------------------------
296
+ Text:
297
+ {resp.text}
298
+ -----------------------------------------------------------
299
+ Function calls:
300
+ {_NEW_LINE.join(function_calls_text)}
301
+ -----------------------------------------------------------
302
+ Raw response:
303
+ {resp.model_dump_json(exclude_none=True)}
304
+ -----------------------------------------------------------
305
+ """