Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/google/adk/events/event_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,42 @@

from __future__ import annotations

import json
from typing import Any
from typing import cast
from typing import Optional

from google.genai.types import Content
from pydantic import alias_generators
from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import Field
from pydantic import field_serializer

from ..auth.auth_tool import AuthConfig
from ..tools.tool_confirmation import ToolConfirmation
from .ui_widget import UiWidget


class EventCompaction(BaseModel):
def _make_json_serializable(obj: Any) -> Any:
"""Recursively converts an object to a JSON-serializable form.

Non-serializable leaf values (e.g. Python callables stored in session state)
are replaced with a descriptive string so the overall structure can still be
persisted without crashing.
"""
if isinstance(obj, dict):
return {k: _make_json_serializable(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [_make_json_serializable(v) for v in obj]
try:
json.dumps(obj)
return obj
except (TypeError, ValueError):
return f'<not serializable: {type(obj).__name__}>'


class EventCompaction(BaseModel): # type: ignore[misc]
"""The compaction of the events."""

model_config = ConfigDict(
Expand All @@ -48,7 +69,7 @@ class EventCompaction(BaseModel):
"""The compacted content of the events."""


class EventActions(BaseModel):
class EventActions(BaseModel): # type: ignore[misc]
"""Represents the actions attached to an event."""

model_config = ConfigDict(
Expand All @@ -67,6 +88,10 @@ class EventActions(BaseModel):
state_delta: dict[str, object] = Field(default_factory=dict)
"""Indicates that the event is updating the state with the given delta."""

@field_serializer('state_delta', mode='plain') # type: ignore[misc, untyped-decorator]
def _serialize_state_delta(self, value: dict[str, object]) -> dict[str, Any]:
return cast(dict[str, Any], _make_json_serializable(value))

artifact_delta: dict[str, int] = Field(default_factory=dict)
"""Indicates that the event is updating an artifact. key is the filename,
value is the version."""
Expand Down Expand Up @@ -107,6 +132,14 @@ class EventActions(BaseModel):
"""The agent state at the current event, used for checkpoint and resume. This
should only be set by ADK workflow."""

@field_serializer('agent_state', mode='plain') # type: ignore[misc, untyped-decorator]
def _serialize_agent_state(
self, value: Optional[dict[str, Any]]
) -> Optional[dict[str, Any]]:
if value is None:
return None
return cast(dict[str, Any], _make_json_serializable(value))
Comment on lines +139 to +141
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _make_json_serializable function correctly handles None values by returning None, so the explicit check for value is None is redundant. You can simplify this method by removing the conditional and calling _make_json_serializable directly on the value, which makes the code more concise.

    return cast(Optional[dict[str, Any]], _make_json_serializable(value))


rewind_before_invocation_id: Optional[str] = None
"""The invocation id to rewind to. This is only set for rewind event."""

Expand Down
Loading