google-adk 1.1.1__py3-none-any.whl → 1.2.1__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 (70) hide show
  1. google/adk/agents/base_agent.py +0 -2
  2. google/adk/agents/invocation_context.py +3 -3
  3. google/adk/agents/parallel_agent.py +17 -7
  4. google/adk/agents/sequential_agent.py +8 -8
  5. google/adk/auth/auth_preprocessor.py +18 -17
  6. google/adk/cli/agent_graph.py +165 -23
  7. google/adk/cli/browser/assets/ADK-512-color.svg +9 -0
  8. google/adk/cli/browser/index.html +2 -2
  9. google/adk/cli/browser/{main-PKDNKWJE.js → main-CS5OLUMF.js} +59 -59
  10. google/adk/cli/browser/polyfills-FFHMD2TL.js +17 -0
  11. google/adk/cli/cli.py +9 -9
  12. google/adk/cli/cli_deploy.py +157 -0
  13. google/adk/cli/cli_tools_click.py +228 -99
  14. google/adk/cli/fast_api.py +119 -34
  15. google/adk/cli/utils/agent_loader.py +60 -44
  16. google/adk/cli/utils/envs.py +1 -1
  17. google/adk/cli/utils/evals.py +4 -2
  18. google/adk/code_executors/unsafe_local_code_executor.py +11 -0
  19. google/adk/errors/__init__.py +13 -0
  20. google/adk/errors/not_found_error.py +28 -0
  21. google/adk/evaluation/agent_evaluator.py +1 -1
  22. google/adk/evaluation/eval_sets_manager.py +36 -6
  23. google/adk/evaluation/evaluation_generator.py +5 -4
  24. google/adk/evaluation/local_eval_sets_manager.py +101 -6
  25. google/adk/evaluation/response_evaluator.py +5 -5
  26. google/adk/evaluation/trajectory_evaluator.py +9 -6
  27. google/adk/flows/llm_flows/agent_transfer.py +2 -2
  28. google/adk/flows/llm_flows/base_llm_flow.py +19 -0
  29. google/adk/flows/llm_flows/contents.py +4 -4
  30. google/adk/flows/llm_flows/functions.py +140 -127
  31. google/adk/memory/vertex_ai_rag_memory_service.py +2 -2
  32. google/adk/models/anthropic_llm.py +7 -10
  33. google/adk/models/google_llm.py +46 -18
  34. google/adk/models/lite_llm.py +63 -26
  35. google/adk/py.typed +0 -0
  36. google/adk/sessions/_session_util.py +10 -16
  37. google/adk/sessions/database_session_service.py +81 -66
  38. google/adk/sessions/vertex_ai_session_service.py +32 -6
  39. google/adk/telemetry.py +91 -24
  40. google/adk/tools/_automatic_function_calling_util.py +31 -25
  41. google/adk/tools/{function_parameter_parse_util.py → _function_parameter_parse_util.py} +9 -3
  42. google/adk/tools/_gemini_schema_util.py +158 -0
  43. google/adk/tools/apihub_tool/apihub_toolset.py +3 -2
  44. google/adk/tools/application_integration_tool/clients/connections_client.py +7 -0
  45. google/adk/tools/application_integration_tool/integration_connector_tool.py +5 -7
  46. google/adk/tools/base_tool.py +4 -8
  47. google/adk/tools/bigquery/bigquery_credentials.py +7 -3
  48. google/adk/tools/function_tool.py +4 -4
  49. google/adk/tools/langchain_tool.py +20 -13
  50. google/adk/tools/load_memory_tool.py +1 -0
  51. google/adk/tools/mcp_tool/conversion_utils.py +4 -2
  52. google/adk/tools/mcp_tool/mcp_session_manager.py +63 -5
  53. google/adk/tools/mcp_tool/mcp_tool.py +3 -2
  54. google/adk/tools/mcp_tool/mcp_toolset.py +15 -8
  55. google/adk/tools/openapi_tool/common/common.py +4 -43
  56. google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +0 -2
  57. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +4 -2
  58. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +4 -2
  59. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +7 -127
  60. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +2 -7
  61. google/adk/tools/transfer_to_agent_tool.py +8 -1
  62. google/adk/tools/vertex_ai_search_tool.py +8 -1
  63. google/adk/utils/variant_utils.py +51 -0
  64. google/adk/version.py +2 -2
  65. {google_adk-1.1.1.dist-info → google_adk-1.2.1.dist-info}/METADATA +8 -7
  66. {google_adk-1.1.1.dist-info → google_adk-1.2.1.dist-info}/RECORD +69 -63
  67. google/adk/cli/browser/polyfills-B6TNHZQ6.js +0 -17
  68. {google_adk-1.1.1.dist-info → google_adk-1.2.1.dist-info}/WHEEL +0 -0
  69. {google_adk-1.1.1.dist-info → google_adk-1.2.1.dist-info}/entry_points.txt +0 -0
  70. {google_adk-1.1.1.dist-info → google_adk-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -23,6 +23,7 @@ from typing import cast
23
23
  from typing import Optional
24
24
  from typing import TYPE_CHECKING
25
25
 
26
+ from google.genai import types
26
27
  from websockets.exceptions import ConnectionClosedOK
27
28
 
28
29
  from . import functions
@@ -50,6 +51,8 @@ if TYPE_CHECKING:
50
51
 
51
52
  logger = logging.getLogger('google_adk.' + __name__)
52
53
 
54
+ _ADK_AGENT_NAME_LABEL_KEY = 'adk_agent_name'
55
+
53
56
 
54
57
  class BaseLlmFlow(ABC):
55
58
  """A basic flow that calls the LLM in a loop until a final response is generated.
@@ -281,6 +284,12 @@ class BaseLlmFlow(ABC):
281
284
  yield event
282
285
  if not last_event or last_event.is_final_response():
283
286
  break
287
+ if last_event.partial:
288
+ # TODO: handle this in BaseLlm level.
289
+ raise ValueError(
290
+ f"Last event shouldn't be partial. LLM max output limit may be"
291
+ f' reached.'
292
+ )
284
293
 
285
294
  async def _run_one_step_async(
286
295
  self,
@@ -493,6 +502,16 @@ class BaseLlmFlow(ABC):
493
502
  yield response
494
503
  return
495
504
 
505
+ llm_request.config = llm_request.config or types.GenerateContentConfig()
506
+ llm_request.config.labels = llm_request.config.labels or {}
507
+
508
+ # Add agent name as a label to the llm_request. This will help with slicing
509
+ # the billing reports on a per-agent basis.
510
+ if _ADK_AGENT_NAME_LABEL_KEY not in llm_request.config.labels:
511
+ llm_request.config.labels[_ADK_AGENT_NAME_LABEL_KEY] = (
512
+ invocation_context.agent.name
513
+ )
514
+
496
515
  # Calls the LLM.
497
516
  llm = self.__get_llm(invocation_context)
498
517
  with tracer.start_as_current_span('call_llm'):
@@ -170,10 +170,10 @@ def _rearrange_events_for_latest_function_response(
170
170
  for idx in range(function_call_event_idx + 1, len(events) - 1):
171
171
  event = events[idx]
172
172
  function_responses = event.get_function_responses()
173
- if (
174
- function_responses
175
- and function_responses[0].id in function_responses_ids
176
- ):
173
+ if function_responses and any([
174
+ function_response.id in function_responses_ids
175
+ for function_response in function_responses
176
+ ]):
177
177
  function_response_events.append(event)
178
178
  function_response_events.append(events[-1])
179
179
 
@@ -32,8 +32,8 @@ from ...agents.invocation_context import InvocationContext
32
32
  from ...auth.auth_tool import AuthToolArguments
33
33
  from ...events.event import Event
34
34
  from ...events.event_actions import EventActions
35
+ from ...telemetry import trace_merged_tool_calls
35
36
  from ...telemetry import trace_tool_call
36
- from ...telemetry import trace_tool_response
37
37
  from ...telemetry import tracer
38
38
  from ...tools.base_tool import BaseTool
39
39
  from ...tools.tool_context import ToolContext
@@ -148,62 +148,69 @@ async def handle_function_calls_async(
148
148
  function_call,
149
149
  tools_dict,
150
150
  )
151
- # do not use "args" as the variable name, because it is a reserved keyword
152
- # in python debugger.
153
- function_args = function_call.args or {}
154
- function_response: Optional[dict] = None
155
-
156
- for callback in agent.canonical_before_tool_callbacks:
157
- function_response = callback(
158
- tool=tool, args=function_args, tool_context=tool_context
159
- )
160
- if inspect.isawaitable(function_response):
161
- function_response = await function_response
162
- if function_response:
163
- break
164
-
165
- if not function_response:
166
- function_response = await __call_tool_async(
167
- tool, args=function_args, tool_context=tool_context
168
- )
169
151
 
170
- for callback in agent.canonical_after_tool_callbacks:
171
- altered_function_response = callback(
152
+ with tracer.start_as_current_span(f'execute_tool {tool.name}'):
153
+ # do not use "args" as the variable name, because it is a reserved keyword
154
+ # in python debugger.
155
+ function_args = function_call.args or {}
156
+ function_response: Optional[dict] = None
157
+
158
+ for callback in agent.canonical_before_tool_callbacks:
159
+ function_response = callback(
160
+ tool=tool, args=function_args, tool_context=tool_context
161
+ )
162
+ if inspect.isawaitable(function_response):
163
+ function_response = await function_response
164
+ if function_response:
165
+ break
166
+
167
+ if not function_response:
168
+ function_response = await __call_tool_async(
169
+ tool, args=function_args, tool_context=tool_context
170
+ )
171
+
172
+ for callback in agent.canonical_after_tool_callbacks:
173
+ altered_function_response = callback(
174
+ tool=tool,
175
+ args=function_args,
176
+ tool_context=tool_context,
177
+ tool_response=function_response,
178
+ )
179
+ if inspect.isawaitable(altered_function_response):
180
+ altered_function_response = await altered_function_response
181
+ if altered_function_response is not None:
182
+ function_response = altered_function_response
183
+ break
184
+
185
+ if tool.is_long_running:
186
+ # Allow long running function to return None to not provide function response.
187
+ if not function_response:
188
+ continue
189
+
190
+ # Builds the function response event.
191
+ function_response_event = __build_response_event(
192
+ tool, function_response, tool_context, invocation_context
193
+ )
194
+ trace_tool_call(
172
195
  tool=tool,
173
196
  args=function_args,
174
- tool_context=tool_context,
175
- tool_response=function_response,
197
+ function_response_event=function_response_event,
176
198
  )
177
- if inspect.isawaitable(altered_function_response):
178
- altered_function_response = await altered_function_response
179
- if altered_function_response is not None:
180
- function_response = altered_function_response
181
- break
182
-
183
- if tool.is_long_running:
184
- # Allow long running function to return None to not provide function response.
185
- if not function_response:
186
- continue
187
-
188
- # Builds the function response event.
189
- function_response_event = __build_response_event(
190
- tool, function_response, tool_context, invocation_context
191
- )
192
- function_response_events.append(function_response_event)
199
+ function_response_events.append(function_response_event)
193
200
 
194
201
  if not function_response_events:
195
202
  return None
196
203
  merged_event = merge_parallel_function_response_events(
197
204
  function_response_events
198
205
  )
206
+
199
207
  if len(function_response_events) > 1:
200
208
  # this is needed for debug traces of parallel calls
201
209
  # individual response with tool.name is traced in __build_response_event
202
210
  # (we drop tool.name from span name here as this is merged event)
203
- with tracer.start_as_current_span('tool_response'):
204
- trace_tool_response(
205
- invocation_context=invocation_context,
206
- event_id=merged_event.id,
211
+ with tracer.start_as_current_span('execute_tool (merged)'):
212
+ trace_merged_tool_calls(
213
+ response_event_id=merged_event.id,
207
214
  function_response_event=merged_event,
208
215
  )
209
216
  return merged_event
@@ -225,65 +232,81 @@ async def handle_function_calls_live(
225
232
  tool, tool_context = _get_tool_and_context(
226
233
  invocation_context, function_call_event, function_call, tools_dict
227
234
  )
228
- # do not use "args" as the variable name, because it is a reserved keyword
229
- # in python debugger.
230
- function_args = function_call.args or {}
231
- function_response = None
232
- # # Calls the tool if before_tool_callback does not exist or returns None.
233
- # if agent.before_tool_callback:
234
- # function_response = agent.before_tool_callback(
235
- # tool, function_args, tool_context
236
- # )
237
- if agent.before_tool_callback:
238
- function_response = agent.before_tool_callback(
239
- tool=tool, args=function_args, tool_context=tool_context
240
- )
241
- if inspect.isawaitable(function_response):
242
- function_response = await function_response
235
+ with tracer.start_as_current_span(f'execute_tool {tool.name}'):
236
+ # do not use "args" as the variable name, because it is a reserved keyword
237
+ # in python debugger.
238
+ function_args = function_call.args or {}
239
+ function_response = None
240
+ # # Calls the tool if before_tool_callback does not exist or returns None.
241
+ # if agent.before_tool_callback:
242
+ # function_response = agent.before_tool_callback(
243
+ # tool, function_args, tool_context
244
+ # )
245
+ if agent.before_tool_callback:
246
+ function_response = agent.before_tool_callback(
247
+ tool=tool, args=function_args, tool_context=tool_context
248
+ )
249
+ if inspect.isawaitable(function_response):
250
+ function_response = await function_response
243
251
 
244
- if not function_response:
245
- function_response = await _process_function_live_helper(
246
- tool, tool_context, function_call, function_args, invocation_context
247
- )
252
+ if not function_response:
253
+ function_response = await _process_function_live_helper(
254
+ tool, tool_context, function_call, function_args, invocation_context
255
+ )
248
256
 
249
- # Calls after_tool_callback if it exists.
250
- # if agent.after_tool_callback:
251
- # new_response = agent.after_tool_callback(
252
- # tool,
253
- # function_args,
254
- # tool_context,
255
- # function_response,
256
- # )
257
- # if new_response:
258
- # function_response = new_response
259
- if agent.after_tool_callback:
260
- altered_function_response = agent.after_tool_callback(
257
+ # Calls after_tool_callback if it exists.
258
+ # if agent.after_tool_callback:
259
+ # new_response = agent.after_tool_callback(
260
+ # tool,
261
+ # function_args,
262
+ # tool_context,
263
+ # function_response,
264
+ # )
265
+ # if new_response:
266
+ # function_response = new_response
267
+ if agent.after_tool_callback:
268
+ altered_function_response = agent.after_tool_callback(
269
+ tool=tool,
270
+ args=function_args,
271
+ tool_context=tool_context,
272
+ tool_response=function_response,
273
+ )
274
+ if inspect.isawaitable(altered_function_response):
275
+ altered_function_response = await altered_function_response
276
+ if altered_function_response is not None:
277
+ function_response = altered_function_response
278
+
279
+ if tool.is_long_running:
280
+ # Allow async function to return None to not provide function response.
281
+ if not function_response:
282
+ continue
283
+
284
+ # Builds the function response event.
285
+ function_response_event = __build_response_event(
286
+ tool, function_response, tool_context, invocation_context
287
+ )
288
+ trace_tool_call(
261
289
  tool=tool,
262
290
  args=function_args,
263
- tool_context=tool_context,
264
- tool_response=function_response,
291
+ response_event_id=function_response_event.id,
292
+ function_response=function_response,
265
293
  )
266
- if inspect.isawaitable(altered_function_response):
267
- altered_function_response = await altered_function_response
268
- if altered_function_response is not None:
269
- function_response = altered_function_response
270
-
271
- if tool.is_long_running:
272
- # Allow async function to return None to not provide function response.
273
- if not function_response:
274
- continue
275
-
276
- # Builds the function response event.
277
- function_response_event = __build_response_event(
278
- tool, function_response, tool_context, invocation_context
279
- )
280
- function_response_events.append(function_response_event)
294
+ function_response_events.append(function_response_event)
281
295
 
282
296
  if not function_response_events:
283
297
  return None
284
298
  merged_event = merge_parallel_function_response_events(
285
299
  function_response_events
286
300
  )
301
+ if len(function_response_events) > 1:
302
+ # this is needed for debug traces of parallel calls
303
+ # individual response with tool.name is traced in __build_response_event
304
+ # (we drop tool.name from span name here as this is merged event)
305
+ with tracer.start_as_current_span('execute_tool (merged)'):
306
+ trace_merged_tool_calls(
307
+ response_event_id=merged_event.id,
308
+ function_response_event=merged_event,
309
+ )
287
310
  return merged_event
288
311
 
289
312
 
@@ -410,14 +433,12 @@ async def __call_tool_live(
410
433
  invocation_context: InvocationContext,
411
434
  ) -> AsyncGenerator[Event, None]:
412
435
  """Calls the tool asynchronously (awaiting the coroutine)."""
413
- with tracer.start_as_current_span(f'tool_call [{tool.name}]'):
414
- trace_tool_call(args=args)
415
- async for item in tool._call_live(
416
- args=args,
417
- tool_context=tool_context,
418
- invocation_context=invocation_context,
419
- ):
420
- yield item
436
+ async for item in tool._call_live(
437
+ args=args,
438
+ tool_context=tool_context,
439
+ invocation_context=invocation_context,
440
+ ):
441
+ yield item
421
442
 
422
443
 
423
444
  async def __call_tool_async(
@@ -426,9 +447,7 @@ async def __call_tool_async(
426
447
  tool_context: ToolContext,
427
448
  ) -> Any:
428
449
  """Calls the tool."""
429
- with tracer.start_as_current_span(f'tool_call [{tool.name}]'):
430
- trace_tool_call(args=args)
431
- return await tool.run_async(args=args, tool_context=tool_context)
450
+ return await tool.run_async(args=args, tool_context=tool_context)
432
451
 
433
452
 
434
453
  def __build_response_event(
@@ -437,35 +456,29 @@ def __build_response_event(
437
456
  tool_context: ToolContext,
438
457
  invocation_context: InvocationContext,
439
458
  ) -> Event:
440
- with tracer.start_as_current_span(f'tool_response [{tool.name}]'):
441
- # Specs requires the result to be a dict.
442
- if not isinstance(function_result, dict):
443
- function_result = {'result': function_result}
459
+ # Specs requires the result to be a dict.
460
+ if not isinstance(function_result, dict):
461
+ function_result = {'result': function_result}
444
462
 
445
- part_function_response = types.Part.from_function_response(
446
- name=tool.name, response=function_result
447
- )
448
- part_function_response.function_response.id = tool_context.function_call_id
463
+ part_function_response = types.Part.from_function_response(
464
+ name=tool.name, response=function_result
465
+ )
466
+ part_function_response.function_response.id = tool_context.function_call_id
449
467
 
450
- content = types.Content(
451
- role='user',
452
- parts=[part_function_response],
453
- )
468
+ content = types.Content(
469
+ role='user',
470
+ parts=[part_function_response],
471
+ )
454
472
 
455
- function_response_event = Event(
456
- invocation_id=invocation_context.invocation_id,
457
- author=invocation_context.agent.name,
458
- content=content,
459
- actions=tool_context.actions,
460
- branch=invocation_context.branch,
461
- )
473
+ function_response_event = Event(
474
+ invocation_id=invocation_context.invocation_id,
475
+ author=invocation_context.agent.name,
476
+ content=content,
477
+ actions=tool_context.actions,
478
+ branch=invocation_context.branch,
479
+ )
462
480
 
463
- trace_tool_response(
464
- invocation_context=invocation_context,
465
- event_id=function_response_event.id,
466
- function_response_event=function_response_event,
467
- )
468
- return function_response_event
481
+ return function_response_event
469
482
 
470
483
 
471
484
  def merge_parallel_function_response_events(
@@ -124,8 +124,8 @@ class VertexAiRagMemoryService(BaseMemoryService):
124
124
  for context in response.contexts.contexts:
125
125
  # filter out context that is not related
126
126
  # TODO: Add server side filtering by app_name and user_id.
127
- # if not context.source_display_name.startswith(f"{app_name}.{user_id}."):
128
- # continue
127
+ if not context.source_display_name.startswith(f"{app_name}.{user_id}."):
128
+ continue
129
129
  session_id = context.source_display_name.split(".")[-1]
130
130
  events = []
131
131
  if context.text:
@@ -135,6 +135,10 @@ def content_block_to_part(
135
135
  def message_to_generate_content_response(
136
136
  message: anthropic_types.Message,
137
137
  ) -> LlmResponse:
138
+ logger.info(
139
+ "Claude response: %s",
140
+ message.model_dump_json(indent=2, exclude_none=True),
141
+ )
138
142
 
139
143
  return LlmResponse(
140
144
  content=types.Content(
@@ -208,7 +212,7 @@ class Claude(BaseLlm):
208
212
  @staticmethod
209
213
  @override
210
214
  def supported_models() -> list[str]:
211
- return [r"claude-3-.*"]
215
+ return [r"claude-3-.*", r"claude-.*-4.*"]
212
216
 
213
217
  @override
214
218
  async def generate_content_async(
@@ -229,14 +233,11 @@ class Claude(BaseLlm):
229
233
  for tool in llm_request.config.tools[0].function_declarations
230
234
  ]
231
235
  tool_choice = (
232
- anthropic_types.ToolChoiceAutoParam(
233
- type="auto",
234
- # TODO: allow parallel tool use.
235
- disable_parallel_tool_use=True,
236
- )
236
+ anthropic_types.ToolChoiceAutoParam(type="auto")
237
237
  if llm_request.tools_dict
238
238
  else NOT_GIVEN
239
239
  )
240
+ # TODO(b/421255973): Enable streaming for anthropic models.
240
241
  message = self._anthropic_client.messages.create(
241
242
  model=llm_request.model,
242
243
  system=llm_request.config.system_instruction,
@@ -245,10 +246,6 @@ class Claude(BaseLlm):
245
246
  tool_choice=tool_choice,
246
247
  max_tokens=MAX_TOKEN,
247
248
  )
248
- logger.info(
249
- "Claude response: %s",
250
- message.model_dump_json(indent=2, exclude_none=True),
251
- )
252
249
  yield message_to_generate_content_response(message)
253
250
 
254
251
  @cached_property
@@ -18,6 +18,7 @@ from __future__ import annotations
18
18
  import contextlib
19
19
  from functools import cached_property
20
20
  import logging
21
+ import os
21
22
  import sys
22
23
  from typing import AsyncGenerator
23
24
  from typing import cast
@@ -28,6 +29,7 @@ from google.genai import types
28
29
  from typing_extensions import override
29
30
 
30
31
  from .. import version
32
+ from ..utils.variant_utils import GoogleLLMVariant
31
33
  from .base_llm import BaseLlm
32
34
  from .base_llm_connection import BaseLlmConnection
33
35
  from .gemini_llm_connection import GeminiLlmConnection
@@ -40,6 +42,8 @@ logger = logging.getLogger('google_adk.' + __name__)
40
42
 
41
43
  _NEW_LINE = '\n'
42
44
  _EXCLUDED_PART_FIELD = {'inline_data': {'data'}}
45
+ _AGENT_ENGINE_TELEMETRY_TAG = 'remote_reasoning_engine'
46
+ _AGENT_ENGINE_TELEMETRY_ENV_VARIABLE_NAME = 'GOOGLE_CLOUD_AGENT_ENGINE_ID'
43
47
 
44
48
 
45
49
  class Gemini(BaseLlm):
@@ -80,7 +84,7 @@ class Gemini(BaseLlm):
80
84
  Yields:
81
85
  LlmResponse: The model response.
82
86
  """
83
-
87
+ self._preprocess_request(llm_request)
84
88
  self._maybe_append_user_content(llm_request)
85
89
  logger.info(
86
90
  'Sending out request, model: %s, backend: %s, stream: %s',
@@ -97,6 +101,7 @@ class Gemini(BaseLlm):
97
101
  config=llm_request.config,
98
102
  )
99
103
  response = None
104
+ thought_text = ''
100
105
  text = ''
101
106
  usage_metadata = None
102
107
  # for sse, similar as bidi (see receive method in gemini_llm_connecton.py),
@@ -113,32 +118,43 @@ class Gemini(BaseLlm):
113
118
  and llm_response.content.parts
114
119
  and llm_response.content.parts[0].text
115
120
  ):
116
- text += llm_response.content.parts[0].text
121
+ part0 = llm_response.content.parts[0]
122
+ if part0.thought:
123
+ thought_text += part0.text
124
+ else:
125
+ text += part0.text
117
126
  llm_response.partial = True
118
- elif text and (
127
+ elif (thought_text or text) and (
119
128
  not llm_response.content
120
129
  or not llm_response.content.parts
121
130
  # don't yield the merged text event when receiving audio data
122
131
  or not llm_response.content.parts[0].inline_data
123
132
  ):
133
+ parts = []
134
+ if thought_text:
135
+ parts.append(types.Part(text=thought_text, thought=True))
136
+ if text:
137
+ parts.append(types.Part.from_text(text=text))
124
138
  yield LlmResponse(
125
- content=types.ModelContent(
126
- parts=[types.Part.from_text(text=text)],
127
- ),
128
- usage_metadata=usage_metadata,
139
+ content=types.ModelContent(parts=parts),
140
+ usage_metadata=llm_response.usage_metadata,
129
141
  )
142
+ thought_text = ''
130
143
  text = ''
131
144
  yield llm_response
132
145
  if (
133
- text
146
+ (text or thought_text)
134
147
  and response
135
148
  and response.candidates
136
149
  and response.candidates[0].finish_reason == types.FinishReason.STOP
137
150
  ):
151
+ parts = []
152
+ if thought_text:
153
+ parts.append(types.Part(text=thought_text, thought=True))
154
+ if text:
155
+ parts.append(types.Part.from_text(text=text))
138
156
  yield LlmResponse(
139
- content=types.ModelContent(
140
- parts=[types.Part.from_text(text=text)],
141
- ),
157
+ content=types.ModelContent(parts=parts),
142
158
  usage_metadata=usage_metadata,
143
159
  )
144
160
 
@@ -163,12 +179,18 @@ class Gemini(BaseLlm):
163
179
  )
164
180
 
165
181
  @cached_property
166
- def _api_backend(self) -> str:
167
- return 'vertex' if self.api_client.vertexai else 'ml_dev'
182
+ def _api_backend(self) -> GoogleLLMVariant:
183
+ return (
184
+ GoogleLLMVariant.VERTEX_AI
185
+ if self.api_client.vertexai
186
+ else GoogleLLMVariant.GEMINI_API
187
+ )
168
188
 
169
189
  @cached_property
170
190
  def _tracking_headers(self) -> dict[str, str]:
171
191
  framework_label = f'google-adk/{version.__version__}'
192
+ if os.environ.get(_AGENT_ENGINE_TELEMETRY_ENV_VARIABLE_NAME):
193
+ framework_label = f'{framework_label}+{_AGENT_ENGINE_TELEMETRY_TAG}'
172
194
  language_label = 'gl-python/' + sys.version.split()[0]
173
195
  version_header_value = f'{framework_label} {language_label}'
174
196
  tracking_headers = {
@@ -179,7 +201,7 @@ class Gemini(BaseLlm):
179
201
 
180
202
  @cached_property
181
203
  def _live_api_client(self) -> Client:
182
- if self._api_backend == 'vertex':
204
+ if self._api_backend == GoogleLLMVariant.VERTEX_AI:
183
205
  # use beta version for vertex api
184
206
  api_version = 'v1beta1'
185
207
  # use default api version for vertex
@@ -189,7 +211,7 @@ class Gemini(BaseLlm):
189
211
  )
190
212
  )
191
213
  else:
192
- # use v1alpha for ml_dev
214
+ # use v1alpha for using API KEY from Google AI Studio
193
215
  api_version = 'v1alpha'
194
216
  return Client(
195
217
  http_options=types.HttpOptions(
@@ -220,6 +242,12 @@ class Gemini(BaseLlm):
220
242
  ) as live_session:
221
243
  yield GeminiLlmConnection(live_session)
222
244
 
245
+ def _preprocess_request(self, llm_request: LlmRequest) -> None:
246
+
247
+ if llm_request.config and self._api_backend == GoogleLLMVariant.GEMINI_API:
248
+ # Using API key from Google AI Studio to call model doesn't support labels.
249
+ llm_request.config.labels = None
250
+
223
251
 
224
252
  def _build_function_declaration_log(
225
253
  func_decl: types.FunctionDeclaration,
@@ -230,10 +258,10 @@ def _build_function_declaration_log(
230
258
  k: v.model_dump(exclude_none=True)
231
259
  for k, v in func_decl.parameters.properties.items()
232
260
  })
233
- return_str = 'None'
261
+ return_str = ''
234
262
  if func_decl.response:
235
- return_str = str(func_decl.response.model_dump(exclude_none=True))
236
- return f'{func_decl.name}: {param_str} -> {return_str}'
263
+ return_str = '-> ' + str(func_decl.response.model_dump(exclude_none=True))
264
+ return f'{func_decl.name}: {param_str} {return_str}'
237
265
 
238
266
 
239
267
  def _build_request_log(req: LlmRequest) -> str: