From f47ed4f1f15f0df954801b7f77ff947af3faa8cf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 06:54:48 +0000 Subject: [PATCH] Fix Flow persistence with nested Pydantic models (issue #2929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use CrewJSONEncoder in SQLiteFlowPersistence.save_state() to properly serialize nested Pydantic models - Add comprehensive test for nested Pydantic model persistence - Resolves RuntimeError: Object of type CustomPydanticModel is not JSON serializable Co-Authored-By: João --- src/crewai/flow/persistence/sqlite.py | 3 +- tests/test_flow_persistence.py | 44 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/crewai/flow/persistence/sqlite.py b/src/crewai/flow/persistence/sqlite.py index 8b2a0f3f2..8f3d0b80f 100644 --- a/src/crewai/flow/persistence/sqlite.py +++ b/src/crewai/flow/persistence/sqlite.py @@ -11,6 +11,7 @@ from typing import Any, Dict, Optional, Union from pydantic import BaseModel from crewai.flow.persistence.base import FlowPersistence +from crewai.utilities.crew_json_encoder import CrewJSONEncoder class SQLiteFlowPersistence(FlowPersistence): @@ -103,7 +104,7 @@ class SQLiteFlowPersistence(FlowPersistence): flow_uuid, method_name, datetime.now(timezone.utc).isoformat(), - json.dumps(state_dict), + json.dumps(state_dict, cls=CrewJSONEncoder), ), ) diff --git a/tests/test_flow_persistence.py b/tests/test_flow_persistence.py index cf3bb22f0..664c8b78b 100644 --- a/tests/test_flow_persistence.py +++ b/tests/test_flow_persistence.py @@ -208,3 +208,47 @@ def test_persist_decorator_verbose_logging(tmp_path, caplog): flow = VerboseFlow(persistence=persistence) flow.kickoff() assert "Saving flow state" in caplog.text + + +def test_nested_pydantic_model_persistence(tmp_path): + """Test persistence with nested Pydantic models (issue #2929).""" + from pydantic import Field + + db_path = os.path.join(tmp_path, "test_flows.db") + persistence = SQLiteFlowPersistence(db_path) + + class CustomObject(BaseModel): + field_x: float | None = Field(description="foo bar", default=None) + + class CustomState(FlowState): + custom_field: CustomObject | None = None + + class NestedPydanticFlow(Flow[CustomState]): + initial_state = CustomState + + @start() + @persist(persistence, verbose=True) + def set_nested_object(self): + self.state.custom_field = CustomObject(field_x=42.0) + + flow = NestedPydanticFlow(persistence=persistence) + flow.kickoff() + + saved_state = persistence.load_state(flow.state.id) + assert saved_state is not None + assert saved_state["custom_field"] is not None + assert saved_state["custom_field"]["field_x"] == 42.0 + + class NullNestedFlow(Flow[CustomState]): + initial_state = CustomState + + @start() + @persist(persistence) + def set_null_nested(self): + self.state.custom_field = None + + flow2 = NullNestedFlow(persistence=persistence) + flow2.kickoff() + saved_state2 = persistence.load_state(flow2.state.id) + assert saved_state2 is not None + assert saved_state2["custom_field"] is None