Skip to content

Commit ae5c275

Browse files
authored
Merge branch 'main' into feat/add-metadata-parameter
2 parents 1171f3c + 6bc9c9f commit ae5c275

17 files changed

Lines changed: 340 additions & 593 deletions

File tree

.agents/skills/adk-sample-creator/SKILL.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ The `agent.py` should focus on demonstrating a specific feature or agent pattern
3030
> [!IMPORTANT]
3131
> **Model Selection**: Do not set the `model` parameter explicitly (e.g., `model="gemini-2.5-flash"`) on `Agent` instances in sample agents. Instead, let them default to the system-configured model, unless a specific model is explicitly requested by the user.
3232
33-
> [!IMPORTANT]
34-
> **Context Usage**: Prefer using the unified `Context` class (imported from
35-
> `google.adk`) instead of the legacy `ToolContext` or `CallbackContext`
36-
> aliases in callbacks and tool definitions.
37-
3833
Choose one of the following patterns:
3934

4035
#### Pattern A: Workflows (for complex graphs)

contributing/samples/core/nested_state/README.md

Lines changed: 0 additions & 65 deletions
This file was deleted.

contributing/samples/core/nested_state/__init__.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

contributing/samples/core/nested_state/agent.py

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/google/adk/a2a/converters/from_adk_event.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,19 @@ def _serialize_value(value: Any) -> Optional[Any]:
259259
logger.warning("Failed to serialize Pydantic model, falling back: %s", e)
260260
return str(value)
261261

262-
# Pass through JSON-native types as-is
263-
if isinstance(value, (dict, list, int, float, bool, str)):
262+
# Recurse into JSON-native containers so nested non-JSON-serializable
263+
# values (e.g. datetime) are still stringified, then pass through other
264+
# JSON-native scalars as-is.
265+
if isinstance(value, dict):
266+
# JSON object keys must be strings, so stringify any non-string key to
267+
# avoid a downstream TypeError when the metadata is JSON-encoded.
268+
return {
269+
(k if isinstance(k, str) else str(k)): _serialize_value(v)
270+
for k, v in value.items()
271+
}
272+
if isinstance(value, list):
273+
return [_serialize_value(item) for item in value]
274+
if isinstance(value, (int, float, bool, str)):
264275
return value
265276

266277
return str(value)

src/google/adk/evaluation/evaluation_generator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,9 @@ def convert_events_to_eval_invocations(
653653
if current_author == _USER_AUTHOR:
654654
# If the author is the user, then we just identify it and move on
655655
# to the next event.
656-
user_content = event.content
657-
invocation_timestamp = event.timestamp
656+
if event.content is not None:
657+
user_content = event.content
658+
invocation_timestamp = event.timestamp
658659
continue
659660

660661
if event.content and event.content.parts:

src/google/adk/flows/llm_flows/_code_execution.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ class DataFileUtil:
7070
_DATA_FILE_UTIL_MAP = {
7171
'text/csv': DataFileUtil(
7272
extension='.csv',
73-
loader_code_template="pd.read_csv('{filename}')",
73+
# Note: The template does not quote {filename} because repr() in
74+
# _get_data_file_preprocessing_code supplies quotes and escaping.
75+
loader_code_template='pd.read_csv({filename})',
7476
),
7577
}
7678

@@ -529,7 +531,7 @@ def _get_normalized_file_name(file_name: str) -> str:
529531

530532
var_name = _get_normalized_file_name(file.name)
531533
loader_code = _DATA_FILE_UTIL_MAP[file.mime_type].loader_code_template.format(
532-
filename=file.name
534+
filename=repr(file.name)
533535
)
534536
return f"""
535537
{_DATA_FILE_HELPER_LIB}

src/google/adk/models/lite_llm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ def _convert_reasoning_value_to_parts(reasoning_value: Any) -> List[types.Part]:
525525
continue
526526
thinking_text = block.get("thinking", "")
527527
signature = block.get("signature", "")
528-
if not thinking_text:
528+
if not thinking_text and not signature:
529529
continue
530530
part = types.Part(text=thinking_text, thought=True)
531531
if signature:

src/google/adk/telemetry/_serialization.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,24 @@
2626
def safe_json_serialize(obj: object) -> str:
2727
"""Convert any Python object to a JSON-serializable type or string.
2828
29+
Handles Pydantic `BaseModel` instances (common as tool return types) by
30+
calling `model_dump(mode="json")` before JSON encoding.
31+
2932
Args:
3033
obj: The object to serialize.
3134
3235
Returns:
3336
The JSON-serialized object string or `<not serializable>` if the object
3437
cannot be serialized.
3538
"""
39+
40+
def _default(o: object) -> object:
41+
if isinstance(o, BaseModel):
42+
return o.model_dump(mode="json")
43+
return "<not serializable>"
44+
3645
try:
37-
return json.dumps(
38-
obj, ensure_ascii=False, default=lambda o: "<not serializable>"
39-
)
46+
return json.dumps(obj, ensure_ascii=False, default=_default)
4047
except (TypeError, ValueError, OverflowError, RecursionError):
4148
return "<not serializable>"
4249

src/google/adk/tools/_function_parameter_parse_util.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def _is_default_value_compatible(
182182
or isinstance(annotation, typing_types.GenericAlias)
183183
or isinstance(annotation, typing_types.UnionType)
184184
):
185-
origin = get_origin(annotation)
185+
origin: Any = get_origin(annotation)
186186
if origin in (Union, typing_types.UnionType):
187187
return any(
188188
_is_default_value_compatible(default_value, arg)
@@ -208,6 +208,22 @@ def _is_default_value_compatible(
208208
for item in default_value
209209
)
210210

211+
if origin is tuple:
212+
if not isinstance(default_value, tuple):
213+
return False
214+
args = get_args(annotation)
215+
if len(args) == 2 and args[-1] is Ellipsis:
216+
return all(
217+
_is_default_value_compatible(item, args[0])
218+
for item in default_value
219+
)
220+
if len(args) != len(default_value):
221+
return False
222+
return all(
223+
_is_default_value_compatible(item, arg)
224+
for item, arg in zip(default_value, args)
225+
)
226+
211227
if origin is Literal:
212228
return default_value in get_args(annotation)
213229

@@ -299,7 +315,7 @@ def _parse_schema_from_parameter(
299315
or isinstance(param.annotation, typing_types.GenericAlias)
300316
or isinstance(param.annotation, typing_types.UnionType)
301317
):
302-
origin = get_origin(param.annotation)
318+
origin: Any = get_origin(param.annotation)
303319
args = get_args(param.annotation)
304320
if origin is dict:
305321
schema.type = types.Type.OBJECT
@@ -339,6 +355,43 @@ def _parse_schema_from_parameter(
339355
schema.default = param.default
340356
_raise_if_schema_unsupported(variant, schema)
341357
return schema
358+
if origin is tuple:
359+
# A genai array schema only carries a single `items` type, so only
360+
# homogeneous tuples can be represented. `tuple[T, ...]` maps to an
361+
# unbounded array, while a fixed-length homogeneous tuple
362+
# (e.g. `tuple[T, T]`) additionally pins min_items/max_items to the
363+
# arity. Heterogeneous tuples (e.g. `tuple[str, int]`) cannot be
364+
# represented and intentionally raise so that from_function_with_options
365+
# routes them through the standard unsupported-parameter handling.
366+
fixed_length = None
367+
if len(args) == 2 and args[-1] is Ellipsis:
368+
item_annotation = args[0]
369+
elif args and all(arg == args[0] for arg in args):
370+
item_annotation = args[0]
371+
fixed_length = len(args)
372+
else:
373+
raise ValueError(
374+
f'Tuple type {param.annotation} must use one repeated item type.'
375+
)
376+
schema.type = types.Type.ARRAY
377+
schema.items = _parse_schema_from_parameter(
378+
variant,
379+
inspect.Parameter(
380+
'item',
381+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
382+
annotation=item_annotation,
383+
),
384+
func_name,
385+
)
386+
if fixed_length is not None:
387+
schema.min_items = fixed_length
388+
schema.max_items = fixed_length
389+
if param.default is not inspect.Parameter.empty:
390+
if not _is_default_value_compatible(param.default, param.annotation):
391+
raise ValueError(default_value_error_msg)
392+
schema.default = param.default
393+
_raise_if_schema_unsupported(variant, schema)
394+
return schema
342395
if origin in (Union, typing_types.UnionType):
343396
schema.any_of = []
344397
schema.type = types.Type.OBJECT

0 commit comments

Comments
 (0)