google-adk 0.5.0__py3-none-any.whl → 1.1.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/base_agent.py +76 -30
- google/adk/agents/callback_context.py +2 -6
- google/adk/agents/llm_agent.py +122 -30
- google/adk/agents/loop_agent.py +1 -1
- google/adk/agents/parallel_agent.py +7 -0
- google/adk/agents/readonly_context.py +8 -0
- google/adk/agents/run_config.py +1 -1
- google/adk/agents/sequential_agent.py +31 -0
- google/adk/agents/transcription_entry.py +4 -2
- google/adk/artifacts/gcs_artifact_service.py +1 -1
- google/adk/artifacts/in_memory_artifact_service.py +1 -1
- google/adk/auth/auth_credential.py +10 -2
- 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 +4 -4
- google/adk/cli/browser/{main-ULN5R5I5.js → main-PKDNKWJE.js} +59 -60
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +10 -9
- google/adk/cli/cli_deploy.py +7 -2
- google/adk/cli/cli_eval.py +109 -115
- google/adk/cli/cli_tools_click.py +179 -67
- google/adk/cli/fast_api.py +248 -197
- google/adk/cli/utils/agent_loader.py +137 -0
- google/adk/cli/utils/cleanup.py +40 -0
- google/adk/cli/utils/common.py +23 -0
- google/adk/cli/utils/evals.py +83 -0
- google/adk/cli/utils/logs.py +8 -5
- google/adk/code_executors/__init__.py +3 -1
- google/adk/code_executors/built_in_code_executor.py +52 -0
- google/adk/code_executors/code_execution_utils.py +2 -1
- google/adk/code_executors/container_code_executor.py +0 -1
- google/adk/code_executors/vertex_ai_code_executor.py +6 -8
- google/adk/evaluation/__init__.py +1 -1
- google/adk/evaluation/agent_evaluator.py +168 -128
- google/adk/evaluation/eval_case.py +104 -0
- google/adk/evaluation/eval_metrics.py +74 -0
- google/adk/evaluation/eval_result.py +86 -0
- google/adk/evaluation/eval_set.py +39 -0
- google/adk/evaluation/eval_set_results_manager.py +47 -0
- google/adk/evaluation/eval_sets_manager.py +43 -0
- google/adk/evaluation/evaluation_generator.py +88 -113
- google/adk/evaluation/evaluator.py +58 -0
- google/adk/evaluation/local_eval_set_results_manager.py +113 -0
- google/adk/evaluation/local_eval_sets_manager.py +264 -0
- google/adk/evaluation/response_evaluator.py +106 -1
- google/adk/evaluation/trajectory_evaluator.py +84 -2
- google/adk/events/event.py +6 -1
- google/adk/events/event_actions.py +6 -1
- google/adk/examples/base_example_provider.py +1 -0
- google/adk/examples/example_util.py +3 -2
- google/adk/flows/llm_flows/_code_execution.py +9 -1
- google/adk/flows/llm_flows/audio_transcriber.py +4 -3
- google/adk/flows/llm_flows/base_llm_flow.py +58 -21
- google/adk/flows/llm_flows/contents.py +3 -1
- google/adk/flows/llm_flows/functions.py +9 -8
- google/adk/flows/llm_flows/instructions.py +18 -80
- google/adk/flows/llm_flows/single_flow.py +2 -2
- google/adk/memory/__init__.py +1 -1
- google/adk/memory/_utils.py +23 -0
- google/adk/memory/base_memory_service.py +23 -21
- google/adk/memory/in_memory_memory_service.py +57 -25
- google/adk/memory/memory_entry.py +37 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +38 -15
- google/adk/models/anthropic_llm.py +16 -9
- google/adk/models/base_llm.py +2 -1
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +11 -11
- google/adk/models/google_llm.py +12 -2
- google/adk/models/lite_llm.py +80 -23
- google/adk/models/llm_response.py +16 -3
- google/adk/models/registry.py +1 -1
- google/adk/runners.py +98 -42
- google/adk/sessions/__init__.py +1 -1
- google/adk/sessions/_session_util.py +2 -1
- google/adk/sessions/base_session_service.py +6 -33
- google/adk/sessions/database_session_service.py +57 -67
- google/adk/sessions/in_memory_session_service.py +106 -24
- google/adk/sessions/session.py +3 -0
- google/adk/sessions/vertex_ai_session_service.py +44 -51
- google/adk/telemetry.py +7 -2
- google/adk/tools/__init__.py +4 -7
- google/adk/tools/_memory_entry_utils.py +30 -0
- google/adk/tools/agent_tool.py +10 -10
- google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
- google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
- google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +111 -85
- google/adk/tools/application_integration_tool/clients/connections_client.py +28 -1
- google/adk/tools/application_integration_tool/clients/integration_client.py +7 -5
- google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
- google/adk/tools/base_toolset.py +96 -0
- google/adk/tools/bigquery/__init__.py +28 -0
- google/adk/tools/bigquery/bigquery_credentials.py +216 -0
- google/adk/tools/bigquery/bigquery_tool.py +116 -0
- google/adk/tools/{built_in_code_execution_tool.py → enterprise_search_tool.py} +17 -11
- google/adk/tools/function_parameter_parse_util.py +9 -2
- google/adk/tools/function_tool.py +33 -3
- google/adk/tools/get_user_choice_tool.py +1 -0
- google/adk/tools/google_api_tool/__init__.py +24 -70
- google/adk/tools/google_api_tool/google_api_tool.py +12 -6
- google/adk/tools/google_api_tool/{google_api_tool_set.py → google_api_toolset.py} +57 -55
- google/adk/tools/google_api_tool/google_api_toolsets.py +108 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
- google/adk/tools/google_search_tool.py +2 -2
- google/adk/tools/langchain_tool.py +96 -49
- google/adk/tools/load_memory_tool.py +14 -5
- google/adk/tools/mcp_tool/__init__.py +3 -2
- google/adk/tools/mcp_tool/conversion_utils.py +6 -2
- google/adk/tools/mcp_tool/mcp_session_manager.py +80 -69
- google/adk/tools/mcp_tool/mcp_tool.py +35 -32
- google/adk/tools/mcp_tool/mcp_toolset.py +99 -194
- google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
- google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
- google/adk/tools/openapi_tool/common/common.py +5 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +27 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +36 -32
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
- 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/toolbox_toolset.py +107 -0
- google/adk/tools/transfer_to_agent_tool.py +0 -1
- google/adk/utils/__init__.py +13 -0
- google/adk/utils/instructions_utils.py +131 -0
- google/adk/version.py +1 -1
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +18 -19
- google_adk-1.1.0.dist-info/RECORD +200 -0
- google/adk/agents/remote_agent.py +0 -50
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
- google/adk/cli/fast_api.py.orig +0 -728
- google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
- google/adk/tools/toolbox_tool.py +0 -46
- google_adk-0.5.0.dist-info/RECORD +0 -180
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -25,10 +25,12 @@ from typing import TYPE_CHECKING
|
|
25
25
|
|
26
26
|
from websockets.exceptions import ConnectionClosedOK
|
27
27
|
|
28
|
+
from . import functions
|
28
29
|
from ...agents.base_agent import BaseAgent
|
29
30
|
from ...agents.callback_context import CallbackContext
|
30
31
|
from ...agents.invocation_context import InvocationContext
|
31
32
|
from ...agents.live_request_queue import LiveRequestQueue
|
33
|
+
from ...agents.readonly_context import ReadonlyContext
|
32
34
|
from ...agents.run_config import StreamingMode
|
33
35
|
from ...agents.transcription_entry import TranscriptionEntry
|
34
36
|
from ...events.event import Event
|
@@ -39,7 +41,6 @@ from ...telemetry import trace_call_llm
|
|
39
41
|
from ...telemetry import trace_send_data
|
40
42
|
from ...telemetry import tracer
|
41
43
|
from ...tools.tool_context import ToolContext
|
42
|
-
from . import functions
|
43
44
|
|
44
45
|
if TYPE_CHECKING:
|
45
46
|
from ...agents.llm_agent import LlmAgent
|
@@ -47,7 +48,7 @@ if TYPE_CHECKING:
|
|
47
48
|
from ._base_llm_processor import BaseLlmRequestProcessor
|
48
49
|
from ._base_llm_processor import BaseLlmResponseProcessor
|
49
50
|
|
50
|
-
logger = logging.getLogger(__name__)
|
51
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
51
52
|
|
52
53
|
|
53
54
|
class BaseLlmFlow(ABC):
|
@@ -88,7 +89,12 @@ class BaseLlmFlow(ABC):
|
|
88
89
|
if invocation_context.transcription_cache:
|
89
90
|
from . import audio_transcriber
|
90
91
|
|
91
|
-
audio_transcriber = audio_transcriber.AudioTranscriber(
|
92
|
+
audio_transcriber = audio_transcriber.AudioTranscriber(
|
93
|
+
init_client=True
|
94
|
+
if invocation_context.run_config.input_audio_transcription
|
95
|
+
is None
|
96
|
+
else False
|
97
|
+
)
|
92
98
|
contents = audio_transcriber.transcribe_file(invocation_context)
|
93
99
|
logger.debug('Sending history to model: %s', contents)
|
94
100
|
await llm_connection.send_history(contents)
|
@@ -129,6 +135,18 @@ class BaseLlmFlow(ABC):
|
|
129
135
|
# cancel the tasks that belongs to the closed connection.
|
130
136
|
send_task.cancel()
|
131
137
|
await llm_connection.close()
|
138
|
+
if (
|
139
|
+
event.content
|
140
|
+
and event.content.parts
|
141
|
+
and event.content.parts[0].function_response
|
142
|
+
and event.content.parts[0].function_response.name
|
143
|
+
== 'task_completed'
|
144
|
+
):
|
145
|
+
# this is used for sequential agent to signal the end of the agent.
|
146
|
+
await asyncio.sleep(1)
|
147
|
+
# cancel the tasks that belongs to the closed connection.
|
148
|
+
send_task.cancel()
|
149
|
+
return
|
132
150
|
finally:
|
133
151
|
# Clean up
|
134
152
|
if not send_task.done():
|
@@ -176,9 +194,12 @@ class BaseLlmFlow(ABC):
|
|
176
194
|
# Cache audio data here for transcription
|
177
195
|
if not invocation_context.transcription_cache:
|
178
196
|
invocation_context.transcription_cache = []
|
179
|
-
invocation_context.
|
180
|
-
|
181
|
-
|
197
|
+
if not invocation_context.run_config.input_audio_transcription:
|
198
|
+
# if the live model's input transcription is not enabled, then
|
199
|
+
# we use our onwn audio transcriber to achieve that.
|
200
|
+
invocation_context.transcription_cache.append(
|
201
|
+
TranscriptionEntry(role='user', data=live_request.blob)
|
202
|
+
)
|
182
203
|
await llm_connection.send_realtime(live_request.blob)
|
183
204
|
if live_request.content:
|
184
205
|
await llm_connection.send_content(live_request.content)
|
@@ -191,14 +212,22 @@ class BaseLlmFlow(ABC):
|
|
191
212
|
llm_request: LlmRequest,
|
192
213
|
) -> AsyncGenerator[Event, None]:
|
193
214
|
"""Receive data from model and process events using BaseLlmConnection."""
|
194
|
-
|
215
|
+
|
216
|
+
def get_author_for_event(llm_response):
|
195
217
|
"""Get the author of the event.
|
196
218
|
|
197
219
|
When the model returns transcription, the author is "user". Otherwise, the
|
198
|
-
author is the agent.
|
220
|
+
author is the agent name(not 'model').
|
221
|
+
|
222
|
+
Args:
|
223
|
+
llm_response: The LLM response from the LLM call.
|
199
224
|
"""
|
200
|
-
if
|
201
|
-
|
225
|
+
if (
|
226
|
+
llm_response
|
227
|
+
and llm_response.content
|
228
|
+
and llm_response.content.role == 'user'
|
229
|
+
):
|
230
|
+
return 'user'
|
202
231
|
else:
|
203
232
|
return invocation_context.agent.name
|
204
233
|
|
@@ -209,7 +238,7 @@ class BaseLlmFlow(ABC):
|
|
209
238
|
model_response_event = Event(
|
210
239
|
id=Event.new_id(),
|
211
240
|
invocation_id=invocation_context.invocation_id,
|
212
|
-
author=
|
241
|
+
author=get_author_for_event(llm_response),
|
213
242
|
)
|
214
243
|
async for event in self._postprocess_live(
|
215
244
|
invocation_context,
|
@@ -220,13 +249,20 @@ class BaseLlmFlow(ABC):
|
|
220
249
|
if (
|
221
250
|
event.content
|
222
251
|
and event.content.parts
|
223
|
-
and event.content.parts[0].
|
252
|
+
and event.content.parts[0].inline_data is None
|
224
253
|
and not event.partial
|
225
254
|
):
|
255
|
+
# This can be either user data or transcription data.
|
256
|
+
# when output transcription enabled, it will contain model's
|
257
|
+
# transcription.
|
258
|
+
# when input transcription enabled, it will contain user
|
259
|
+
# transcription.
|
226
260
|
if not invocation_context.transcription_cache:
|
227
261
|
invocation_context.transcription_cache = []
|
228
262
|
invocation_context.transcription_cache.append(
|
229
|
-
TranscriptionEntry(
|
263
|
+
TranscriptionEntry(
|
264
|
+
role=event.content.role, data=event.content
|
265
|
+
)
|
230
266
|
)
|
231
267
|
yield event
|
232
268
|
# Give opportunity for other tasks to run.
|
@@ -261,6 +297,7 @@ class BaseLlmFlow(ABC):
|
|
261
297
|
|
262
298
|
# Calls the LLM.
|
263
299
|
model_response_event = Event(
|
300
|
+
id=Event.new_id(),
|
264
301
|
invocation_id=invocation_context.invocation_id,
|
265
302
|
author=invocation_context.agent.name,
|
266
303
|
branch=invocation_context.branch,
|
@@ -272,8 +309,8 @@ class BaseLlmFlow(ABC):
|
|
272
309
|
async for event in self._postprocess_async(
|
273
310
|
invocation_context, llm_request, llm_response, model_response_event
|
274
311
|
):
|
275
|
-
#
|
276
|
-
|
312
|
+
# Update the mutable event id to avoid conflict
|
313
|
+
model_response_event.id = Event.new_id()
|
277
314
|
yield event
|
278
315
|
|
279
316
|
async def _preprocess_async(
|
@@ -291,7 +328,9 @@ class BaseLlmFlow(ABC):
|
|
291
328
|
yield event
|
292
329
|
|
293
330
|
# Run processors for tools.
|
294
|
-
for tool in agent.canonical_tools
|
331
|
+
for tool in await agent.canonical_tools(
|
332
|
+
ReadonlyContext(invocation_context)
|
333
|
+
):
|
295
334
|
tool_context = ToolContext(invocation_context)
|
296
335
|
await tool.process_llm_request(
|
297
336
|
tool_context=tool_context, llm_request=llm_request
|
@@ -433,14 +472,12 @@ class BaseLlmFlow(ABC):
|
|
433
472
|
yield event
|
434
473
|
|
435
474
|
def _get_agent_to_run(
|
436
|
-
self, invocation_context: InvocationContext,
|
475
|
+
self, invocation_context: InvocationContext, agent_name: str
|
437
476
|
) -> BaseAgent:
|
438
477
|
root_agent = invocation_context.agent.root_agent
|
439
|
-
agent_to_run = root_agent.find_agent(
|
478
|
+
agent_to_run = root_agent.find_agent(agent_name)
|
440
479
|
if not agent_to_run:
|
441
|
-
raise ValueError(
|
442
|
-
f'Agent {transfer_to_agent} not found in the agent tree.'
|
443
|
-
)
|
480
|
+
raise ValueError(f'Agent {agent_name} not found in the agent tree.')
|
444
481
|
return agent_to_run
|
445
482
|
|
446
483
|
async def _call_llm_async(
|
@@ -15,7 +15,9 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
import copy
|
18
|
-
from typing import AsyncGenerator
|
18
|
+
from typing import AsyncGenerator
|
19
|
+
from typing import Generator
|
20
|
+
from typing import Optional
|
19
21
|
|
20
22
|
from google.genai import types
|
21
23
|
from typing_extensions import override
|
@@ -41,7 +41,7 @@ from ...tools.tool_context import ToolContext
|
|
41
41
|
AF_FUNCTION_CALL_ID_PREFIX = 'adk-'
|
42
42
|
REQUEST_EUC_FUNCTION_CALL_NAME = 'adk_request_credential'
|
43
43
|
|
44
|
-
logger = logging.getLogger(__name__)
|
44
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
45
45
|
|
46
46
|
|
47
47
|
def generate_client_function_call_id() -> str:
|
@@ -106,7 +106,7 @@ def generate_auth_event(
|
|
106
106
|
args=AuthToolArguments(
|
107
107
|
function_call_id=function_call_id,
|
108
108
|
auth_config=auth_config,
|
109
|
-
).model_dump(exclude_none=True),
|
109
|
+
).model_dump(exclude_none=True, by_alias=True),
|
110
110
|
)
|
111
111
|
request_euc_function_call.id = generate_client_function_call_id()
|
112
112
|
long_running_tool_ids.add(request_euc_function_call.id)
|
@@ -153,22 +153,22 @@ async def handle_function_calls_async(
|
|
153
153
|
function_args = function_call.args or {}
|
154
154
|
function_response: Optional[dict] = None
|
155
155
|
|
156
|
-
|
157
|
-
|
158
|
-
function_response = agent.before_tool_callback(
|
156
|
+
for callback in agent.canonical_before_tool_callbacks:
|
157
|
+
function_response = callback(
|
159
158
|
tool=tool, args=function_args, tool_context=tool_context
|
160
159
|
)
|
161
160
|
if inspect.isawaitable(function_response):
|
162
161
|
function_response = await function_response
|
162
|
+
if function_response:
|
163
|
+
break
|
163
164
|
|
164
165
|
if not function_response:
|
165
166
|
function_response = await __call_tool_async(
|
166
167
|
tool, args=function_args, tool_context=tool_context
|
167
168
|
)
|
168
169
|
|
169
|
-
|
170
|
-
|
171
|
-
altered_function_response = agent.after_tool_callback(
|
170
|
+
for callback in agent.canonical_after_tool_callbacks:
|
171
|
+
altered_function_response = callback(
|
172
172
|
tool=tool,
|
173
173
|
args=function_args,
|
174
174
|
tool_context=tool_context,
|
@@ -178,6 +178,7 @@ async def handle_function_calls_async(
|
|
178
178
|
altered_function_response = await altered_function_response
|
179
179
|
if altered_function_response is not None:
|
180
180
|
function_response = altered_function_response
|
181
|
+
break
|
181
182
|
|
182
183
|
if tool.is_long_running:
|
183
184
|
# Allow long running function to return None to not provide function response.
|
@@ -26,6 +26,7 @@ from typing_extensions import override
|
|
26
26
|
from ...agents.readonly_context import ReadonlyContext
|
27
27
|
from ...events.event import Event
|
28
28
|
from ...sessions.state import State
|
29
|
+
from ...utils import instructions_utils
|
29
30
|
from ._base_llm_processor import BaseLlmRequestProcessor
|
30
31
|
|
31
32
|
if TYPE_CHECKING:
|
@@ -53,16 +54,28 @@ class _InstructionsLlmRequestProcessor(BaseLlmRequestProcessor):
|
|
53
54
|
if (
|
54
55
|
isinstance(root_agent, LlmAgent) and root_agent.global_instruction
|
55
56
|
): # not empty str
|
56
|
-
raw_si =
|
57
|
-
|
57
|
+
raw_si, bypass_state_injection = (
|
58
|
+
await root_agent.canonical_global_instruction(
|
59
|
+
ReadonlyContext(invocation_context)
|
60
|
+
)
|
58
61
|
)
|
59
|
-
si =
|
62
|
+
si = raw_si
|
63
|
+
if not bypass_state_injection:
|
64
|
+
si = await instructions_utils.inject_session_state(
|
65
|
+
raw_si, ReadonlyContext(invocation_context)
|
66
|
+
)
|
60
67
|
llm_request.append_instructions([si])
|
61
68
|
|
62
69
|
# Appends agent instructions if set.
|
63
70
|
if agent.instruction: # not empty str
|
64
|
-
raw_si = agent.canonical_instruction(
|
65
|
-
|
71
|
+
raw_si, bypass_state_injection = await agent.canonical_instruction(
|
72
|
+
ReadonlyContext(invocation_context)
|
73
|
+
)
|
74
|
+
si = raw_si
|
75
|
+
if not bypass_state_injection:
|
76
|
+
si = await instructions_utils.inject_session_state(
|
77
|
+
raw_si, ReadonlyContext(invocation_context)
|
78
|
+
)
|
66
79
|
llm_request.append_instructions([si])
|
67
80
|
|
68
81
|
# Maintain async generator behavior
|
@@ -71,78 +84,3 @@ class _InstructionsLlmRequestProcessor(BaseLlmRequestProcessor):
|
|
71
84
|
|
72
85
|
|
73
86
|
request_processor = _InstructionsLlmRequestProcessor()
|
74
|
-
|
75
|
-
|
76
|
-
async def _populate_values(
|
77
|
-
instruction_template: str,
|
78
|
-
context: InvocationContext,
|
79
|
-
) -> str:
|
80
|
-
"""Populates values in the instruction template, e.g. state, artifact, etc."""
|
81
|
-
|
82
|
-
async def _async_sub(pattern, repl_async_fn, string) -> str:
|
83
|
-
result = []
|
84
|
-
last_end = 0
|
85
|
-
for match in re.finditer(pattern, string):
|
86
|
-
result.append(string[last_end : match.start()])
|
87
|
-
replacement = await repl_async_fn(match)
|
88
|
-
result.append(replacement)
|
89
|
-
last_end = match.end()
|
90
|
-
result.append(string[last_end:])
|
91
|
-
return ''.join(result)
|
92
|
-
|
93
|
-
async def _replace_match(match) -> str:
|
94
|
-
var_name = match.group().lstrip('{').rstrip('}').strip()
|
95
|
-
optional = False
|
96
|
-
if var_name.endswith('?'):
|
97
|
-
optional = True
|
98
|
-
var_name = var_name.removesuffix('?')
|
99
|
-
if var_name.startswith('artifact.'):
|
100
|
-
var_name = var_name.removeprefix('artifact.')
|
101
|
-
if context.artifact_service is None:
|
102
|
-
raise ValueError('Artifact service is not initialized.')
|
103
|
-
artifact = await context.artifact_service.load_artifact(
|
104
|
-
app_name=context.session.app_name,
|
105
|
-
user_id=context.session.user_id,
|
106
|
-
session_id=context.session.id,
|
107
|
-
filename=var_name,
|
108
|
-
)
|
109
|
-
if not var_name:
|
110
|
-
raise KeyError(f'Artifact {var_name} not found.')
|
111
|
-
return str(artifact)
|
112
|
-
else:
|
113
|
-
if not _is_valid_state_name(var_name):
|
114
|
-
return match.group()
|
115
|
-
if var_name in context.session.state:
|
116
|
-
return str(context.session.state[var_name])
|
117
|
-
else:
|
118
|
-
if optional:
|
119
|
-
return ''
|
120
|
-
else:
|
121
|
-
raise KeyError(f'Context variable not found: `{var_name}`.')
|
122
|
-
|
123
|
-
return await _async_sub(r'{+[^{}]*}+', _replace_match, instruction_template)
|
124
|
-
|
125
|
-
|
126
|
-
def _is_valid_state_name(var_name):
|
127
|
-
"""Checks if the variable name is a valid state name.
|
128
|
-
|
129
|
-
Valid state is either:
|
130
|
-
- Valid identifier
|
131
|
-
- <Valid prefix>:<Valid identifier>
|
132
|
-
All the others will just return as it is.
|
133
|
-
|
134
|
-
Args:
|
135
|
-
var_name: The variable name to check.
|
136
|
-
|
137
|
-
Returns:
|
138
|
-
True if the variable name is a valid state name, False otherwise.
|
139
|
-
"""
|
140
|
-
parts = var_name.split(':')
|
141
|
-
if len(parts) == 1:
|
142
|
-
return var_name.isidentifier()
|
143
|
-
|
144
|
-
if len(parts) == 2:
|
145
|
-
prefixes = [State.APP_PREFIX, State.USER_PREFIX, State.TEMP_PREFIX]
|
146
|
-
if (parts[0] + ':') in prefixes:
|
147
|
-
return parts[1].isidentifier()
|
148
|
-
return False
|
@@ -16,16 +16,16 @@
|
|
16
16
|
|
17
17
|
import logging
|
18
18
|
|
19
|
-
from ...auth import auth_preprocessor
|
20
19
|
from . import _code_execution
|
21
20
|
from . import _nl_planning
|
22
21
|
from . import basic
|
23
22
|
from . import contents
|
24
23
|
from . import identity
|
25
24
|
from . import instructions
|
25
|
+
from ...auth import auth_preprocessor
|
26
26
|
from .base_llm_flow import BaseLlmFlow
|
27
27
|
|
28
|
-
logger = logging.getLogger(__name__)
|
28
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
29
29
|
|
30
30
|
|
31
31
|
class SingleFlow(BaseLlmFlow):
|
google/adk/memory/__init__.py
CHANGED
@@ -16,7 +16,7 @@ import logging
|
|
16
16
|
from .base_memory_service import BaseMemoryService
|
17
17
|
from .in_memory_memory_service import InMemoryMemoryService
|
18
18
|
|
19
|
-
logger = logging.getLogger(__name__)
|
19
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
20
20
|
|
21
21
|
__all__ = [
|
22
22
|
'BaseMemoryService',
|
@@ -0,0 +1,23 @@
|
|
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
|
+
|
15
|
+
|
16
|
+
from __future__ import annotations
|
17
|
+
|
18
|
+
from datetime import datetime
|
19
|
+
|
20
|
+
|
21
|
+
def format_timestamp(timestamp: float) -> str:
|
22
|
+
"""Formats the timestamp of the memory entry."""
|
23
|
+
return datetime.fromtimestamp(timestamp).isoformat()
|
@@ -12,46 +12,44 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
import abc
|
16
15
|
|
17
|
-
from
|
18
|
-
from pydantic import Field
|
16
|
+
from __future__ import annotations
|
19
17
|
|
20
|
-
from
|
21
|
-
from
|
18
|
+
from abc import ABC
|
19
|
+
from abc import abstractmethod
|
20
|
+
from typing import TYPE_CHECKING
|
22
21
|
|
22
|
+
from pydantic import BaseModel
|
23
|
+
from pydantic import Field
|
23
24
|
|
24
|
-
|
25
|
-
"""Represents a single memory retrieval result.
|
26
|
-
|
27
|
-
Attributes:
|
28
|
-
session_id: The session id associated with the memory.
|
29
|
-
events: A list of events in the session.
|
30
|
-
"""
|
25
|
+
from .memory_entry import MemoryEntry
|
31
26
|
|
32
|
-
|
33
|
-
|
27
|
+
if TYPE_CHECKING:
|
28
|
+
from ..sessions.session import Session
|
34
29
|
|
35
30
|
|
36
31
|
class SearchMemoryResponse(BaseModel):
|
37
32
|
"""Represents the response from a memory search.
|
38
33
|
|
39
34
|
Attributes:
|
40
|
-
memories: A list of memory
|
35
|
+
memories: A list of memory entries that relate to the search query.
|
41
36
|
"""
|
42
37
|
|
43
|
-
memories: list[
|
38
|
+
memories: list[MemoryEntry] = Field(default_factory=list)
|
44
39
|
|
45
40
|
|
46
|
-
class BaseMemoryService(
|
41
|
+
class BaseMemoryService(ABC):
|
47
42
|
"""Base class for memory services.
|
48
43
|
|
49
44
|
The service provides functionalities to ingest sessions into memory so that
|
50
45
|
the memory can be used for user queries.
|
51
46
|
"""
|
52
47
|
|
53
|
-
@
|
54
|
-
async def add_session_to_memory(
|
48
|
+
@abstractmethod
|
49
|
+
async def add_session_to_memory(
|
50
|
+
self,
|
51
|
+
session: Session,
|
52
|
+
):
|
55
53
|
"""Adds a session to the memory service.
|
56
54
|
|
57
55
|
A session may be added multiple times during its lifetime.
|
@@ -60,9 +58,13 @@ class BaseMemoryService(abc.ABC):
|
|
60
58
|
session: The session to add.
|
61
59
|
"""
|
62
60
|
|
63
|
-
@
|
61
|
+
@abstractmethod
|
64
62
|
async def search_memory(
|
65
|
-
self,
|
63
|
+
self,
|
64
|
+
*,
|
65
|
+
app_name: str,
|
66
|
+
user_id: str,
|
67
|
+
query: str,
|
66
68
|
) -> SearchMemoryResponse:
|
67
69
|
"""Searches for sessions that match the query.
|
68
70
|
|
@@ -12,11 +12,31 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
|
16
|
-
from
|
15
|
+
|
16
|
+
from __future__ import annotations
|
17
|
+
|
18
|
+
import re
|
19
|
+
from typing import TYPE_CHECKING
|
20
|
+
|
21
|
+
from typing_extensions import override
|
22
|
+
|
23
|
+
from . import _utils
|
17
24
|
from .base_memory_service import BaseMemoryService
|
18
|
-
from .base_memory_service import MemoryResult
|
19
25
|
from .base_memory_service import SearchMemoryResponse
|
26
|
+
from .memory_entry import MemoryEntry
|
27
|
+
|
28
|
+
if TYPE_CHECKING:
|
29
|
+
from ..events.event import Event
|
30
|
+
from ..sessions.session import Session
|
31
|
+
|
32
|
+
|
33
|
+
def _user_key(app_name: str, user_id: str):
|
34
|
+
return f'{app_name}/{user_id}'
|
35
|
+
|
36
|
+
|
37
|
+
def _extract_words_lower(text: str) -> set[str]:
|
38
|
+
"""Extracts words from a string and converts them to lowercase."""
|
39
|
+
return set([word.lower() for word in re.findall(r'[A-Za-z]+', text)])
|
20
40
|
|
21
41
|
|
22
42
|
class InMemoryMemoryService(BaseMemoryService):
|
@@ -26,37 +46,49 @@ class InMemoryMemoryService(BaseMemoryService):
|
|
26
46
|
"""
|
27
47
|
|
28
48
|
def __init__(self):
|
29
|
-
self.
|
30
|
-
"""
|
49
|
+
self._session_events: dict[str, dict[str, list[Event]]] = {}
|
50
|
+
"""Keys are app_name/user_id, session_id. Values are session event lists."""
|
31
51
|
|
52
|
+
@override
|
32
53
|
async def add_session_to_memory(self, session: Session):
|
33
|
-
|
34
|
-
self.
|
35
|
-
|
54
|
+
user_key = _user_key(session.app_name, session.user_id)
|
55
|
+
self._session_events[user_key] = self._session_events.get(
|
56
|
+
_user_key(session.app_name, session.user_id), {}
|
57
|
+
)
|
58
|
+
self._session_events[user_key][session.id] = [
|
59
|
+
event
|
60
|
+
for event in session.events
|
61
|
+
if event.content and event.content.parts
|
36
62
|
]
|
37
63
|
|
64
|
+
@override
|
38
65
|
async def search_memory(
|
39
66
|
self, *, app_name: str, user_id: str, query: str
|
40
67
|
) -> SearchMemoryResponse:
|
41
|
-
|
42
|
-
|
68
|
+
user_key = _user_key(app_name, user_id)
|
69
|
+
if user_key not in self._session_events:
|
70
|
+
return SearchMemoryResponse()
|
71
|
+
|
72
|
+
words_in_query = set(query.lower().split())
|
43
73
|
response = SearchMemoryResponse()
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
matched_events = []
|
48
|
-
for event in events:
|
74
|
+
|
75
|
+
for session_events in self._session_events[user_key].values():
|
76
|
+
for event in session_events:
|
49
77
|
if not event.content or not event.content.parts:
|
50
78
|
continue
|
51
|
-
|
52
|
-
|
53
|
-
for keyword in keywords:
|
54
|
-
if keyword in text:
|
55
|
-
matched_events.append(event)
|
56
|
-
break
|
57
|
-
if matched_events:
|
58
|
-
session_id = key.split('/')[-1]
|
59
|
-
response.memories.append(
|
60
|
-
MemoryResult(session_id=session_id, events=matched_events)
|
79
|
+
words_in_event = _extract_words_lower(
|
80
|
+
' '.join([part.text for part in event.content.parts if part.text])
|
61
81
|
)
|
82
|
+
if not words_in_event:
|
83
|
+
continue
|
84
|
+
|
85
|
+
if any(query_word in words_in_event for query_word in words_in_query):
|
86
|
+
response.memories.append(
|
87
|
+
MemoryEntry(
|
88
|
+
content=event.content,
|
89
|
+
author=event.author,
|
90
|
+
timestamp=_utils.format_timestamp(event.timestamp),
|
91
|
+
)
|
92
|
+
)
|
93
|
+
|
62
94
|
return response
|
@@ -0,0 +1,37 @@
|
|
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
|
+
|
15
|
+
|
16
|
+
from __future__ import annotations
|
17
|
+
|
18
|
+
from typing import Optional
|
19
|
+
|
20
|
+
from google.genai import types
|
21
|
+
from pydantic import BaseModel
|
22
|
+
|
23
|
+
|
24
|
+
class MemoryEntry(BaseModel):
|
25
|
+
"""Represent one memory entry."""
|
26
|
+
|
27
|
+
content: types.Content
|
28
|
+
"""The main content of the memory."""
|
29
|
+
|
30
|
+
author: Optional[str] = None
|
31
|
+
"""The author of the memory."""
|
32
|
+
|
33
|
+
timestamp: Optional[str] = None
|
34
|
+
"""The timestamp when the original content of this memory happened.
|
35
|
+
|
36
|
+
This string will be forwarded to LLM. Preferred format is ISO 8601 format.
|
37
|
+
"""
|