google-adk 1.6.1__py3-none-any.whl → 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. google/adk/a2a/converters/event_converter.py +5 -85
  2. google/adk/a2a/converters/request_converter.py +1 -2
  3. google/adk/a2a/executor/a2a_agent_executor.py +45 -16
  4. google/adk/a2a/logs/log_utils.py +1 -2
  5. google/adk/a2a/utils/__init__.py +0 -0
  6. google/adk/a2a/utils/agent_card_builder.py +544 -0
  7. google/adk/a2a/utils/agent_to_a2a.py +118 -0
  8. google/adk/agents/__init__.py +5 -0
  9. google/adk/agents/agent_config.py +46 -0
  10. google/adk/agents/base_agent.py +239 -41
  11. google/adk/agents/callback_context.py +41 -0
  12. google/adk/agents/common_configs.py +79 -0
  13. google/adk/agents/config_agent_utils.py +184 -0
  14. google/adk/agents/config_schemas/AgentConfig.json +566 -0
  15. google/adk/agents/invocation_context.py +5 -1
  16. google/adk/agents/live_request_queue.py +15 -0
  17. google/adk/agents/llm_agent.py +201 -9
  18. google/adk/agents/loop_agent.py +35 -1
  19. google/adk/agents/parallel_agent.py +24 -3
  20. google/adk/agents/remote_a2a_agent.py +17 -5
  21. google/adk/agents/sequential_agent.py +22 -1
  22. google/adk/artifacts/gcs_artifact_service.py +110 -20
  23. google/adk/auth/auth_handler.py +3 -3
  24. google/adk/auth/credential_manager.py +23 -23
  25. google/adk/auth/credential_service/base_credential_service.py +6 -6
  26. google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
  27. google/adk/auth/credential_service/session_state_credential_service.py +8 -8
  28. google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
  29. google/adk/auth/oauth2_credential_util.py +2 -2
  30. google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
  31. google/adk/cli/agent_graph.py +3 -1
  32. google/adk/cli/browser/index.html +2 -2
  33. google/adk/cli/browser/main-W7QZBYAR.js +3914 -0
  34. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  35. google/adk/cli/cli_eval.py +87 -12
  36. google/adk/cli/cli_tools_click.py +143 -82
  37. google/adk/cli/fast_api.py +150 -69
  38. google/adk/cli/utils/agent_loader.py +35 -1
  39. google/adk/code_executors/base_code_executor.py +14 -19
  40. google/adk/code_executors/built_in_code_executor.py +4 -1
  41. google/adk/evaluation/base_eval_service.py +46 -2
  42. google/adk/evaluation/eval_metrics.py +4 -0
  43. google/adk/evaluation/eval_sets_manager.py +5 -1
  44. google/adk/evaluation/evaluation_generator.py +1 -1
  45. google/adk/evaluation/final_response_match_v2.py +2 -2
  46. google/adk/evaluation/gcs_eval_sets_manager.py +2 -1
  47. google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
  48. google/adk/evaluation/local_eval_service.py +389 -0
  49. google/adk/evaluation/local_eval_set_results_manager.py +2 -2
  50. google/adk/evaluation/local_eval_sets_manager.py +24 -9
  51. google/adk/evaluation/metric_evaluator_registry.py +16 -6
  52. google/adk/evaluation/vertex_ai_eval_facade.py +7 -1
  53. google/adk/events/event.py +7 -2
  54. google/adk/flows/llm_flows/auto_flow.py +6 -11
  55. google/adk/flows/llm_flows/base_llm_flow.py +66 -29
  56. google/adk/flows/llm_flows/contents.py +16 -10
  57. google/adk/flows/llm_flows/functions.py +89 -52
  58. google/adk/memory/in_memory_memory_service.py +21 -15
  59. google/adk/memory/vertex_ai_memory_bank_service.py +12 -10
  60. google/adk/models/anthropic_llm.py +46 -6
  61. google/adk/models/base_llm_connection.py +2 -0
  62. google/adk/models/gemini_llm_connection.py +17 -6
  63. google/adk/models/google_llm.py +46 -11
  64. google/adk/models/lite_llm.py +52 -22
  65. google/adk/plugins/__init__.py +17 -0
  66. google/adk/plugins/base_plugin.py +317 -0
  67. google/adk/plugins/plugin_manager.py +265 -0
  68. google/adk/runners.py +122 -18
  69. google/adk/sessions/database_session_service.py +51 -52
  70. google/adk/sessions/vertex_ai_session_service.py +27 -12
  71. google/adk/tools/__init__.py +2 -0
  72. google/adk/tools/_automatic_function_calling_util.py +20 -2
  73. google/adk/tools/agent_tool.py +15 -3
  74. google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
  75. google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
  76. google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
  77. google/adk/tools/base_tool.py +9 -9
  78. google/adk/tools/base_toolset.py +29 -5
  79. google/adk/tools/bigquery/__init__.py +3 -3
  80. google/adk/tools/bigquery/metadata_tool.py +2 -0
  81. google/adk/tools/bigquery/query_tool.py +15 -1
  82. google/adk/tools/computer_use/__init__.py +13 -0
  83. google/adk/tools/computer_use/base_computer.py +265 -0
  84. google/adk/tools/computer_use/computer_use_tool.py +166 -0
  85. google/adk/tools/computer_use/computer_use_toolset.py +220 -0
  86. google/adk/tools/enterprise_search_tool.py +4 -2
  87. google/adk/tools/exit_loop_tool.py +1 -0
  88. google/adk/tools/google_api_tool/google_api_tool.py +16 -1
  89. google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
  90. google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
  91. google/adk/tools/google_search_tool.py +4 -2
  92. google/adk/tools/langchain_tool.py +16 -6
  93. google/adk/tools/long_running_tool.py +21 -0
  94. google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
  95. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +5 -0
  96. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
  97. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
  98. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
  99. google/adk/tools/tool_context.py +0 -10
  100. google/adk/tools/url_context_tool.py +4 -2
  101. google/adk/tools/vertex_ai_search_tool.py +4 -2
  102. google/adk/utils/model_name_utils.py +90 -0
  103. google/adk/version.py +1 -1
  104. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/METADATA +3 -2
  105. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/RECORD +108 -91
  106. google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
  107. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
  108. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/WHEEL +0 -0
  109. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/entry_points.txt +0 -0
  110. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -23,15 +23,12 @@ from typing_extensions import override
23
23
 
24
24
  from . import _automatic_function_calling_util
25
25
  from ..memory.in_memory_memory_service import InMemoryMemoryService
26
- from ..runners import Runner
27
- from ..sessions.in_memory_session_service import InMemorySessionService
28
26
  from ._forwarding_artifact_service import ForwardingArtifactService
29
27
  from .base_tool import BaseTool
30
28
  from .tool_context import ToolContext
31
29
 
32
30
  if TYPE_CHECKING:
33
31
  from ..agents.base_agent import BaseAgent
34
- from ..agents.llm_agent import LlmAgent
35
32
 
36
33
 
37
34
  class AgentTool(BaseTool):
@@ -61,6 +58,7 @@ class AgentTool(BaseTool):
61
58
  @override
62
59
  def _get_declaration(self) -> types.FunctionDeclaration:
63
60
  from ..agents.llm_agent import LlmAgent
61
+ from ..utils.variant_utils import GoogleLLMVariant
64
62
 
65
63
  if isinstance(self.agent, LlmAgent) and self.agent.input_schema:
66
64
  result = _automatic_function_calling_util.build_function_declaration(
@@ -80,6 +78,17 @@ class AgentTool(BaseTool):
80
78
  description=self.agent.description,
81
79
  name=self.name,
82
80
  )
81
+
82
+ # Set response schema for non-GEMINI_API variants
83
+ if self._api_variant != GoogleLLMVariant.GEMINI_API:
84
+ # Determine response type based on agent's output schema
85
+ if isinstance(self.agent, LlmAgent) and self.agent.output_schema:
86
+ # Agent has structured output schema - response is an object
87
+ result.response = types.Schema(type=types.Type.OBJECT)
88
+ else:
89
+ # Agent returns text - response is a string
90
+ result.response = types.Schema(type=types.Type.STRING)
91
+
83
92
  result.name = self.name
84
93
  return result
85
94
 
@@ -91,6 +100,8 @@ class AgentTool(BaseTool):
91
100
  tool_context: ToolContext,
92
101
  ) -> Any:
93
102
  from ..agents.llm_agent import LlmAgent
103
+ from ..runners import Runner
104
+ from ..sessions.in_memory_session_service import InMemorySessionService
94
105
 
95
106
  if self.skip_summarization:
96
107
  tool_context.actions.skip_summarization = True
@@ -116,6 +127,7 @@ class AgentTool(BaseTool):
116
127
  artifact_service=ForwardingArtifactService(tool_context),
117
128
  session_service=InMemorySessionService(),
118
129
  memory_service=InMemoryMemoryService(),
130
+ credential_service=tool_context._invocation_context.credential_service,
119
131
  )
120
132
  session = await runner.session_service.create_session(
121
133
  app_name=self.agent.name,
@@ -35,27 +35,25 @@ from .clients.apihub_client import APIHubClient
35
35
  class APIHubToolset(BaseToolset):
36
36
  """APIHubTool generates tools from a given API Hub resource.
37
37
 
38
- Examples:
38
+ Examples::
39
39
 
40
- ```
41
- apihub_toolset = APIHubToolset(
42
- apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
43
- service_account_json="...",
44
- tool_filter=lambda tool, ctx=None: tool.name in ('my_tool',
45
- 'my_other_tool')
46
- )
47
-
48
- # Get all available tools
49
- agent = LlmAgent(tools=apihub_toolset)
40
+ apihub_toolset = APIHubToolset(
41
+ apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
42
+ service_account_json="...",
43
+ tool_filter=lambda tool, ctx=None: tool.name in ('my_tool',
44
+ 'my_other_tool')
45
+ )
50
46
 
51
- ```
47
+ # Get all available tools
48
+ agent = LlmAgent(tools=apihub_toolset)
52
49
 
53
50
  **apihub_resource_name** is the resource name from API Hub. It must include
54
- API name, and can optionally include API version and spec name.
55
- - If apihub_resource_name includes a spec resource name, the content of that
56
- spec will be used for generating the tools.
57
- - If apihub_resource_name includes only an api or a version name, the
58
- first spec of the first version of that API will be used.
51
+ API name, and can optionally include API version and spec name.
52
+
53
+ - If apihub_resource_name includes a spec resource name, the content of that
54
+ spec will be used for generating the tools.
55
+ - If apihub_resource_name includes only an api or a version name, the
56
+ first spec of the first version of that API will be used.
59
57
  """
60
58
 
61
59
  def __init__(
@@ -78,44 +76,45 @@ class APIHubToolset(BaseToolset):
78
76
  ):
79
77
  """Initializes the APIHubTool with the given parameters.
80
78
 
81
- Examples:
82
- ```
83
- apihub_toolset = APIHubToolset(
84
- apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
85
- service_account_json="...",
86
- )
79
+ Examples::
87
80
 
88
- # Get all available tools
89
- agent = LlmAgent(tools=[apihub_toolset])
81
+ apihub_toolset = APIHubToolset(
82
+ apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
83
+ service_account_json="...",
84
+ )
90
85
 
91
- apihub_toolset = APIHubToolset(
92
- apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
93
- service_account_json="...",
94
- tool_filter = ['my_tool']
95
- )
96
- # Get a specific tool
97
- agent = LlmAgent(tools=[
98
- ...,
99
- apihub_toolset,
100
- ])
101
- ```
86
+ # Get all available tools
87
+ agent = LlmAgent(tools=[apihub_toolset])
88
+
89
+ apihub_toolset = APIHubToolset(
90
+ apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
91
+ service_account_json="...",
92
+ tool_filter = ['my_tool']
93
+ )
94
+ # Get a specific tool
95
+ agent = LlmAgent(tools=[
96
+ ...,
97
+ apihub_toolset,
98
+ ])
102
99
 
103
100
  **apihub_resource_name** is the resource name from API Hub. It must include
104
101
  API name, and can optionally include API version and spec name.
102
+
105
103
  - If apihub_resource_name includes a spec resource name, the content of that
106
104
  spec will be used for generating the tools.
107
105
  - If apihub_resource_name includes only an api or a version name, the
108
106
  first spec of the first version of that API will be used.
109
107
 
110
108
  Example:
109
+
111
110
  * projects/xxx/locations/us-central1/apis/apiname/...
112
111
  * https://console.cloud.google.com/apigee/api-hub/apis/apiname?project=xxx
113
112
 
114
113
  Args:
115
114
  apihub_resource_name: The resource name of the API in API Hub.
116
- Example: `projects/test-project/locations/us-central1/apis/test-api`.
117
- access_token: Google Access token. Generate with gcloud cli `gcloud auth
118
- auth print-access-token`. Used for fetching API Specs from API Hub.
115
+ Example: ``projects/test-project/locations/us-central1/apis/test-api``.
116
+ access_token: Google Access token. Generate with gcloud cli
117
+ ``gcloud auth auth print-access-token``. Used for fetching API Specs from API Hub.
119
118
  service_account_json: The service account config as a json string.
120
119
  Required if not using default service credential. It is used for
121
120
  creating the API Hub client and fetching the API Specs from API Hub.
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import logging
16
18
  from typing import List
17
19
  from typing import Optional
@@ -42,43 +44,39 @@ logger = logging.getLogger("google_adk." + __name__)
42
44
  # TODO(cheliu): Apply a common toolset interface
43
45
  class ApplicationIntegrationToolset(BaseToolset):
44
46
  """ApplicationIntegrationToolset generates tools from a given Application
45
-
46
47
  Integration or Integration Connector resource.
47
- Example Usage:
48
- ```
49
- # Get all available tools for an integration with api trigger
50
- application_integration_toolset = ApplicationIntegrationToolset(
51
48
 
52
- project="test-project",
53
- location="us-central1"
54
- integration="test-integration",
55
- triggers=["api_trigger/test_trigger"],
56
- service_account_credentials={...},
57
- )
49
+ Example Usage::
58
50
 
59
- # Get all available tools for a connection using entity operations and
60
- # actions
61
- # Note: Find the list of supported entity operations and actions for a
62
- connection
63
- # using integration connector apis:
64
- #
65
- https://cloud.google.com/integration-connectors/docs/reference/rest/v1/projects.locations.connections.connectionSchemaMetadata
66
- application_integration_toolset = ApplicationIntegrationToolset(
67
- project="test-project",
68
- location="us-central1"
69
- connection="test-connection",
70
- entity_operations=["EntityId1": ["LIST","CREATE"], "EntityId2": []],
71
- #empty list for actions means all operations on the entity are supported
72
- actions=["action1"],
73
- service_account_credentials={...},
74
- )
51
+ # Get all available tools for an integration with api trigger
52
+ application_integration_toolset = ApplicationIntegrationToolset(
53
+ project="test-project",
54
+ location="us-central1"
55
+ integration="test-integration",
56
+ triggers=["api_trigger/test_trigger"],
57
+ service_account_credentials={...},
58
+ )
59
+
60
+ # Get all available tools for a connection using entity operations and
61
+ # actions
62
+ # Note: Find the list of supported entity operations and actions for a
63
+ # connection using integration connector apis:
64
+ # https://cloud.google.com/integration-connectors/docs/reference/rest/v1/projects.locations.connections.connectionSchemaMetadata
65
+ application_integration_toolset = ApplicationIntegrationToolset(
66
+ project="test-project",
67
+ location="us-central1"
68
+ connection="test-connection",
69
+ entity_operations=["EntityId1": ["LIST","CREATE"], "EntityId2": []],
70
+ #empty list for actions means all operations on the entity are supported
71
+ actions=["action1"],
72
+ service_account_credentials={...},
73
+ )
75
74
 
76
- # Feed the toolset to agent
77
- agent = LlmAgent(tools=[
78
- ...,
79
- application_integration_toolset,
80
- ])
81
- ```
75
+ # Feed the toolset to agent
76
+ agent = LlmAgent(tools=[
77
+ ...,
78
+ application_integration_toolset,
79
+ ])
82
80
  """
83
81
 
84
82
  def __init__(
@@ -122,11 +120,11 @@ class ApplicationIntegrationToolset(BaseToolset):
122
120
 
123
121
  Raises:
124
122
  ValueError: If none of the following conditions are met:
125
- - `integration` is provided.
126
- - `connection` is provided and at least one of `entity_operations`
127
- or `actions` is provided.
123
+ - ``integration`` is provided.
124
+ - ``connection`` is provided and at least one of ``entity_operations``
125
+ or ``actions`` is provided.
128
126
  Exception: If there is an error during the initialization of the
129
- integration or connection client.
127
+ integration or connection client.
130
128
  """
131
129
  super().__init__(tool_filter=tool_filter)
132
130
  self.project = project
@@ -45,13 +45,12 @@ class IntegrationConnectorTool(BaseTool):
45
45
  * Generates request params and body
46
46
  * Attaches auth credentials to API call.
47
47
 
48
- Example:
49
- ```
48
+ Example::
49
+
50
50
  # Each API operation in the spec will be turned into its own tool
51
51
  # Name of the tool is the operationId of that operation, in snake case
52
52
  operations = OperationGenerator().parse(openapi_spec_dict)
53
53
  tool = [RestApiTool.from_parsed_operation(o) for o in operations]
54
- ```
55
54
  """
56
55
 
57
56
  EXCLUDE_FIELDS = [
@@ -49,11 +49,11 @@ class BaseTool(ABC):
49
49
  def _get_declaration(self) -> Optional[types.FunctionDeclaration]:
50
50
  """Gets the OpenAPI specification of this tool in the form of a FunctionDeclaration.
51
51
 
52
- NOTE
53
- - Required if subclass uses the default implementation of
54
- `process_llm_request` to add function declaration to LLM request.
55
- - Otherwise, can be skipped, e.g. for a built-in GoogleSearch tool for
56
- Gemini.
52
+ NOTE:
53
+ - Required if subclass uses the default implementation of
54
+ `process_llm_request` to add function declaration to LLM request.
55
+ - Otherwise, can be skipped, e.g. for a built-in GoogleSearch tool for
56
+ Gemini.
57
57
 
58
58
  Returns:
59
59
  The FunctionDeclaration of this tool, or None if it doesn't need to be
@@ -66,10 +66,10 @@ class BaseTool(ABC):
66
66
  ) -> Any:
67
67
  """Runs the tool with the given arguments and context.
68
68
 
69
- NOTE
70
- - Required if this tool needs to run at the client side.
71
- - Otherwise, can be skipped, e.g. for a built-in GoogleSearch tool for
72
- Gemini.
69
+ NOTE:
70
+ - Required if this tool needs to run at the client side.
71
+ - Otherwise, can be skipped, e.g. for a built-in GoogleSearch tool for
72
+ Gemini.
73
73
 
74
74
  Args:
75
75
  args: The LLM-filled arguments.
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
15
16
 
16
17
  from abc import ABC
17
18
  from abc import abstractmethod
@@ -19,11 +20,16 @@ from typing import List
19
20
  from typing import Optional
20
21
  from typing import Protocol
21
22
  from typing import runtime_checkable
23
+ from typing import TYPE_CHECKING
22
24
  from typing import Union
23
25
 
24
26
  from ..agents.readonly_context import ReadonlyContext
25
27
  from .base_tool import BaseTool
26
28
 
29
+ if TYPE_CHECKING:
30
+ from ..models.llm_request import LlmRequest
31
+ from .tool_context import ToolContext
32
+
27
33
 
28
34
  @runtime_checkable
29
35
  class ToolPredicate(Protocol):
@@ -64,7 +70,7 @@ class BaseToolset(ABC):
64
70
  """Return all tools in the toolset based on the provided context.
65
71
 
66
72
  Args:
67
- readony_context (ReadonlyContext, optional): Context used to filter tools
73
+ readonly_context (ReadonlyContext, optional): Context used to filter tools
68
74
  available to the agent. If None, all tools in the toolset are returned.
69
75
 
70
76
  Returns:
@@ -75,10 +81,11 @@ class BaseToolset(ABC):
75
81
  async def close(self) -> None:
76
82
  """Performs cleanup and releases resources held by the toolset.
77
83
 
78
- NOTE: This method is invoked, for example, at the end of an agent server's
79
- lifecycle or when the toolset is no longer needed. Implementations
80
- should ensure that any open connections, files, or other managed
81
- resources are properly released to prevent leaks.
84
+ NOTE:
85
+ This method is invoked, for example, at the end of an agent server's
86
+ lifecycle or when the toolset is no longer needed. Implementations
87
+ should ensure that any open connections, files, or other managed
88
+ resources are properly released to prevent leaks.
82
89
  """
83
90
 
84
91
  def _is_tool_selected(
@@ -94,3 +101,20 @@ class BaseToolset(ABC):
94
101
  return tool.name in self.tool_filter
95
102
 
96
103
  return False
104
+
105
+ async def process_llm_request(
106
+ self, *, tool_context: ToolContext, llm_request: LlmRequest
107
+ ) -> None:
108
+ """Processes the outgoing LLM request for this toolset. This method will be
109
+ called before each tool processes the llm request.
110
+
111
+ Use cases:
112
+ - Instead of let each tool process the llm request, we can let the toolset
113
+ process the llm request. e.g. ComputerUseToolset can add computer use
114
+ tool to the llm request.
115
+
116
+ Args:
117
+ tool_context: The context of the tool.
118
+ llm_request: The outgoing LLM request, mutable this method.
119
+ """
120
+ pass
@@ -20,11 +20,11 @@ definition. The rationales to have customized tool are:
20
20
 
21
21
  1. BigQuery APIs have functions overlaps and LLM can't tell what tool to use
22
22
  2. BigQuery APIs have a lot of parameters with some rarely used, which are not
23
- LLM-friendly
23
+ LLM-friendly
24
24
  3. We want to provide more high-level tools like forecasting, RAG, segmentation,
25
- etc.
25
+ etc.
26
26
  4. We want to provide extra access guardrails in those tools. For example,
27
- execute_sql can't arbitrarily mutate existing data.
27
+ execute_sql can't arbitrarily mutate existing data.
28
28
  """
29
29
 
30
30
  from .bigquery_credentials import BigQueryCredentialsConfig
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from google.auth.credentials import Credentials
16
18
  from google.cloud import bigquery
17
19
 
@@ -12,7 +12,10 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import functools
18
+ import json
16
19
  import types
17
20
  from typing import Callable
18
21
 
@@ -159,7 +162,18 @@ def execute_sql(
159
162
  project=project_id,
160
163
  max_results=MAX_DOWNLOADED_QUERY_RESULT_ROWS,
161
164
  )
162
- rows = [{key: val for key, val in row.items()} for row in row_iterator]
165
+ rows = []
166
+ for row in row_iterator:
167
+ row_values = {}
168
+ for key, val in row.items():
169
+ try:
170
+ # if the json serialization of the value succeeds, use it as is
171
+ json.dumps(val)
172
+ except:
173
+ val = str(val)
174
+ row_values[key] = val
175
+ rows.append(row_values)
176
+
163
177
  result = {"status": "SUCCESS", "rows": rows}
164
178
  if (
165
179
  MAX_DOWNLOADED_QUERY_RESULT_ROWS is not None
@@ -0,0 +1,13 @@
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.