Skip to content

fix: handle non-serializable types in persistent session event storage#4741

Open
atian8179 wants to merge 1 commit intogoogle:mainfrom
atian8179:fix/database-session-serialization-error
Open

fix: handle non-serializable types in persistent session event storage#4741
atian8179 wants to merge 1 commit intogoogle:mainfrom
atian8179:fix/database-session-serialization-error

Conversation

@atian8179
Copy link

Problem

DatabaseSessionService and SqliteSessionService crash with PydanticSerializationError when events contain non-serializable objects (e.g., function references attached by MCP tools during tool resolution).

pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'function'>

This affects all agents using persistent session services with tools (severity: high).

Root Cause

StorageEvent.from_event() calls event.model_dump(exclude_none=True, mode="json") without a fallback parameter. When the Runner attaches non-serializable function objects to events before calling append_event(), Pydantic cannot serialize them and crashes.

InMemorySessionService is unaffected because it never serializes events.

Solution

Add Pydantic v2's fallback parameter to model_dump() / model_dump_json() calls in:

  • schemas/v1.py: StorageEvent.from_event()
  • sqlite_session_service.py: append_event()

The fallback converts non-serializable types to descriptive placeholder strings (<non-serializable: function>) instead of crashing. This preserves all serializable data while gracefully degrading for unknown types.

Testing

Added tests/unittests/sessions/test_storage_event_serialization.py with 3 tests:

All tests pass.

Fixes #4724

When using DatabaseSessionService or SqliteSessionService, events
containing non-serializable objects (e.g., function references from
MCP tools) cause PydanticSerializationError during model_dump().

Add fallback parameter to model_dump()/model_dump_json() calls in
session storage schemas to gracefully handle unknown types by
converting them to descriptive placeholder strings.

Fixes google#4724
@google-cla
Copy link

google-cla bot commented Mar 6, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where persistent session services would crash when attempting to serialize events containing non-serializable Python objects, such as function references. By introducing a Pydantic fallback mechanism, the system now gracefully handles these types, replacing them with descriptive placeholders instead of failing, thus ensuring the stability and reliability of event storage for agents utilizing tools.

Highlights

  • Problem Addressed: Resolved PydanticSerializationError crashes in DatabaseSessionService and SqliteSessionService when events contain non-serializable objects like function references.
  • Root Cause Identified: The issue stemmed from model_dump() and model_dump_json() calls lacking a fallback parameter, causing Pydantic to fail on unknown types.
  • Solution Implemented: Integrated Pydantic v2's fallback parameter into model_dump() and model_dump_json() calls in schemas/v1.py and sqlite_session_service.py.
  • Graceful Degradation: Non-serializable types are now converted to descriptive placeholder strings (e.g., <non-serializable: function>), ensuring serialization completes without crashing while preserving serializable data.
  • Comprehensive Testing: New unit tests were added to tests/unittests/sessions/test_storage_event_serialization.py to validate basic event roundtrips, handling of function objects in state_delta, and preservation of serializable fields.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/google/adk/sessions/schemas/v1.py
    • Modified StorageEvent.from_event to include a fallback parameter in event.model_dump() to handle non-serializable types.
  • src/google/adk/sessions/sqlite_session_service.py
    • Updated append_event to pass a fallback parameter to event.model_dump_json() for graceful serialization of non-serializable objects.
  • tests/unittests/sessions/test_storage_event_serialization.py
    • Added a new test file to verify StorageEvent serialization.
    • Included tests for basic event roundtrips.
    • Added a regression test for events containing function objects in state_delta.
    • Included a test to ensure serializable fields are preserved during the fallback process.
Activity
  • No human activity has occurred on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively resolves the PydanticSerializationError by using a fallback function during JSON serialization, which is a robust solution. The changes are correctly applied in both DatabaseSessionService and SqliteSessionService contexts. The addition of dedicated unit tests is excellent, ensuring the fix is correct and preventing future regressions. I have a couple of suggestions to improve code maintainability and clarity.

event_data=event.model_dump(
exclude_none=True,
mode="json",
fallback=lambda v: f"<non-serializable: {type(v).__name__}>",
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This lambda function is also used in sqlite_session_service.py. To avoid duplication and improve maintainability, consider defining it as a constant in a shared location, for example in src/google/adk/sessions/schemas/shared.py:

# In src/google/adk/sessions/schemas/shared.py
JSON_ENCODER_FALLBACK = lambda v: f"<non-serializable: {type(v).__name__}>"

You can then import and use this constant here and in sqlite_session_service.py.

event.model_dump_json(exclude_none=True),
event.model_dump_json(
exclude_none=True,
fallback=lambda v: f"<non-serializable: {type(v).__name__}>",
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This lambda function is a duplicate of the one in src/google/adk/sessions/schemas/v1.py. To adhere to the DRY (Don't Repeat Yourself) principle, it would be best to define this as a shared constant and reuse it in both places. This will make future changes to the serialization fallback logic easier to manage.

assert storage.event_data is not None
# The function should be serialized as a placeholder string
actions = storage.event_data.get("actions", {})
state_delta = actions.get("state_delta", actions.get("stateDelta", {}))
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The Event model is configured to use camelCase aliases for JSON serialization (alias_generator=alias_generators.to_camel). This means state_delta will be serialized as stateDelta. The current code checks for state_delta first, which will always be a miss, before falling back to stateDelta. For clarity and to accurately reflect the expected data structure, it's better to access stateDelta directly.

Suggested change
state_delta = actions.get("state_delta", actions.get("stateDelta", {}))
state_delta = actions.get("stateDelta", {})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DatabaseSessionService.append_event crashes with PydanticSerializationError: Unable to serialize unknown type: <class 'function'>

1 participant