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.
- google/adk/agents/active_streaming_tool.py +1 -0
- google/adk/agents/base_agent.py +91 -47
- google/adk/agents/base_agent.py.orig +330 -0
- google/adk/agents/callback_context.py +4 -9
- google/adk/agents/invocation_context.py +1 -0
- google/adk/agents/langgraph_agent.py +1 -0
- google/adk/agents/live_request_queue.py +1 -0
- google/adk/agents/llm_agent.py +172 -35
- google/adk/agents/loop_agent.py +1 -1
- google/adk/agents/parallel_agent.py +7 -0
- google/adk/agents/readonly_context.py +7 -1
- google/adk/agents/run_config.py +5 -1
- google/adk/agents/sequential_agent.py +31 -0
- google/adk/agents/transcription_entry.py +5 -2
- google/adk/artifacts/base_artifact_service.py +5 -10
- google/adk/artifacts/gcs_artifact_service.py +9 -9
- google/adk/artifacts/in_memory_artifact_service.py +6 -6
- google/adk/auth/auth_credential.py +9 -5
- google/adk/auth/auth_preprocessor.py +7 -1
- google/adk/auth/auth_tool.py +3 -4
- google/adk/cli/agent_graph.py +5 -5
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/{main-HWIBUY2R.js → main-QOEMUXM4.js} +58 -58
- google/adk/cli/cli.py +7 -7
- google/adk/cli/cli_deploy.py +7 -2
- google/adk/cli/cli_eval.py +181 -106
- google/adk/cli/cli_tools_click.py +147 -62
- google/adk/cli/fast_api.py +340 -158
- google/adk/cli/fast_api.py.orig +822 -0
- google/adk/cli/utils/common.py +23 -0
- google/adk/cli/utils/evals.py +83 -1
- google/adk/cli/utils/logs.py +13 -5
- google/adk/code_executors/__init__.py +3 -1
- google/adk/code_executors/built_in_code_executor.py +52 -0
- google/adk/evaluation/__init__.py +1 -1
- google/adk/evaluation/agent_evaluator.py +168 -128
- google/adk/evaluation/eval_case.py +102 -0
- google/adk/evaluation/eval_set.py +37 -0
- google/adk/evaluation/eval_sets_manager.py +42 -0
- google/adk/evaluation/evaluation_constants.py +1 -0
- google/adk/evaluation/evaluation_generator.py +89 -114
- google/adk/evaluation/evaluator.py +56 -0
- google/adk/evaluation/local_eval_sets_manager.py +264 -0
- google/adk/evaluation/response_evaluator.py +107 -3
- google/adk/evaluation/trajectory_evaluator.py +83 -2
- google/adk/events/event.py +7 -1
- google/adk/events/event_actions.py +7 -1
- google/adk/examples/example.py +1 -0
- google/adk/examples/example_util.py +3 -2
- google/adk/flows/__init__.py +0 -1
- google/adk/flows/llm_flows/_code_execution.py +19 -11
- google/adk/flows/llm_flows/audio_transcriber.py +4 -3
- google/adk/flows/llm_flows/base_llm_flow.py +86 -22
- google/adk/flows/llm_flows/basic.py +3 -0
- google/adk/flows/llm_flows/functions.py +10 -9
- google/adk/flows/llm_flows/instructions.py +28 -9
- google/adk/flows/llm_flows/single_flow.py +1 -1
- google/adk/memory/__init__.py +1 -1
- google/adk/memory/_utils.py +23 -0
- google/adk/memory/base_memory_service.py +25 -21
- google/adk/memory/base_memory_service.py.orig +76 -0
- google/adk/memory/in_memory_memory_service.py +59 -27
- google/adk/memory/memory_entry.py +37 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +40 -17
- google/adk/models/anthropic_llm.py +36 -11
- google/adk/models/base_llm.py +45 -4
- google/adk/models/gemini_llm_connection.py +15 -2
- google/adk/models/google_llm.py +9 -44
- google/adk/models/google_llm.py.orig +305 -0
- google/adk/models/lite_llm.py +94 -38
- google/adk/models/llm_request.py +1 -1
- google/adk/models/llm_response.py +15 -3
- google/adk/models/registry.py +1 -1
- google/adk/runners.py +68 -44
- google/adk/sessions/__init__.py +1 -1
- google/adk/sessions/_session_util.py +14 -0
- google/adk/sessions/base_session_service.py +8 -32
- google/adk/sessions/database_session_service.py +58 -61
- google/adk/sessions/in_memory_session_service.py +108 -26
- google/adk/sessions/session.py +4 -0
- google/adk/sessions/vertex_ai_session_service.py +23 -45
- google/adk/telemetry.py +3 -0
- google/adk/tools/__init__.py +4 -7
- google/adk/tools/{built_in_code_execution_tool.py → _built_in_code_execution_tool.py} +11 -0
- google/adk/tools/_memory_entry_utils.py +30 -0
- google/adk/tools/agent_tool.py +16 -13
- google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
- google/adk/tools/application_integration_tool/application_integration_toolset.py +107 -85
- google/adk/tools/application_integration_tool/clients/connections_client.py +29 -25
- google/adk/tools/application_integration_tool/clients/integration_client.py +6 -6
- google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
- google/adk/tools/base_toolset.py +58 -0
- google/adk/tools/enterprise_search_tool.py +65 -0
- google/adk/tools/function_parameter_parse_util.py +2 -2
- google/adk/tools/google_api_tool/__init__.py +18 -70
- google/adk/tools/google_api_tool/google_api_tool.py +11 -5
- google/adk/tools/google_api_tool/google_api_toolset.py +126 -0
- google/adk/tools/google_api_tool/google_api_toolsets.py +102 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
- google/adk/tools/langchain_tool.py +96 -49
- google/adk/tools/load_artifacts_tool.py +4 -4
- google/adk/tools/load_memory_tool.py +16 -5
- google/adk/tools/mcp_tool/__init__.py +3 -2
- google/adk/tools/mcp_tool/conversion_utils.py +1 -1
- google/adk/tools/mcp_tool/mcp_session_manager.py +167 -16
- google/adk/tools/mcp_tool/mcp_session_manager.py.orig +322 -0
- google/adk/tools/mcp_tool/mcp_tool.py +12 -12
- google/adk/tools/mcp_tool/mcp_toolset.py +155 -195
- google/adk/tools/openapi_tool/common/common.py +2 -5
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +32 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +43 -33
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
- google/adk/tools/preload_memory_tool.py +27 -18
- google/adk/tools/retrieval/__init__.py +1 -1
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
- google/adk/tools/tool_context.py +4 -4
- google/adk/tools/toolbox_toolset.py +79 -0
- google/adk/tools/transfer_to_agent_tool.py +0 -1
- google/adk/version.py +1 -1
- {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/METADATA +7 -5
- google_adk-1.0.0.dist-info/RECORD +195 -0
- google/adk/agents/remote_agent.py +0 -50
- google/adk/tools/google_api_tool/google_api_tool_set.py +0 -110
- google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
- google/adk/tools/toolbox_tool.py +0 -46
- google_adk-0.4.0.dist-info/RECORD +0 -179
- {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/WHEEL +0 -0
- {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/entry_points.txt +0 -0
- {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
|
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
|
-
|
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
|
google/adk/models/base_llm.py
CHANGED
@@ -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
|
google/adk/models/google_llm.py
CHANGED
@@ -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(
|
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
|
+
"""
|