Skip to content

Conversation

@pkusnail
Copy link

@pkusnail pkusnail commented Sep 25, 2025

Fix Redis Cache Serialization Issues with Rich Library Objects

📋 Summary

Fixes "Flow build failed cannot pickle 'ConsoleThreadLocals' object" error when using Redis caching. The issue occurs because Rich library objects contain unpickleable
threading primitives (threading.local, threading.RLock).

🔍 Problem

  • Error: cannot pickle 'ConsoleThreadLocals' object with Redis caching enabled
  • Root Cause: Rich library's ConsoleThreadLocals and Console objects contain non-serializable threading objects
  • Impact: Redis caching fails completely, forcing users to disable caching

🛠 Solution

Multi-layered approach combining Rich library fixes with comprehensive cache normalization:

  1. Rich Library Pickle Support
  • Adds custom getstate and setstate methods to Rich classes
  • Automatic setup during cache factory initialization with validation
  • Preserves essential state while recreating locks on deserialization
  1. Cache Normalization System
  • Converts complex objects to serialization-safe DTOs via normalize_for_cache()
  • Handles dynamic classes, functions, Pydantic models, and vertex snapshots
  • Replaces executable objects with descriptive placeholders
  1. Redis Cache Sanitization
  • Fallback _sanitize_for_pickle() method for problematic objects
  • Two-step serialization: direct pickling first, then sanitization
  • Handles dynamic InputSchema models and recursive references
  1. ChatService Integration
  • Integrated normalization into ChatService.set_cache()
  • Envelope structure with version tracking for cached data
  • Backward compatible with existing cache entries

📦 Dependencies

Requires custom Rich library fork with native pickle support:

The Rich fork provides the foundational fix by adding native pickle methods to Rich classes. This PR builds additional protection and normalization layers on top of
that core fix.

✅ Changes Made

Core Implementation:

  • src/lfx/src/lfx/serialization/normalizer.py - Main normalization logic
  • src/backend/base/langflow/services/cache/utils.py - Rich pickle support setup
  • src/backend/base/langflow/services/cache/service.py - Redis sanitization fallback
  • src/backend/base/langflow/services/cache/factory.py - Automatic Rich setup
  • src/backend/base/langflow/services/chat/service.py - Normalizer integration

Schema & Compatibility:

  • src/lfx/src/lfx/io/schema.py - Enhanced InputSchema registration
  • src/lfx/src/lfx/graph/graph/base.py - Graph cache restore compatibility

Testing:

  • src/backend/tests/unit/cache/test_*.py - Unit tests for normalizer and ChatService
  • scripts/run_local_cache_checks.py - Manual validation script

🧪 Testing

  • ✅ All existing tests pass
  • ✅ New unit tests for normalization and ChatService caching
  • ✅ Manual Redis testing shows successful Rich object caching
  • ✅ Backward compatibility maintained

📊 Impact

  • Fixes: Redis caching now works with Rich library objects
  • Performance: Minimal overhead, no impact on memory caching
  • Compatibility: Fully backward compatible, no breaking changes
  • Reliability: Multiple fallback layers prevent cache failures

Files Changed: 10 files, +347 lines

Summary by CodeRabbit

  • New Features

    • Normalized caching of complex objects for safer, portable storage (applied to chat cache).
    • Health check now reports Rich pickle support status.
  • Improvements

    • More robust cache serialization with graceful fallbacks and handling of “unbuilt” objects; backward compatible with existing cache data.
    • Optional Rich-based serialization is auto-enabled/validated with clearer runtime logging.
  • Tests

    • Unit tests added for normalization and chat cache behavior.
  • Chores

    • Local cache check script added.
    • Updated dependency to a Rich fork enabling pickle support.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds cache normalization and Rich pickling support across backend and lfx modules. Introduces normalize_for_cache, integrates normalization in ChatService caching, enhances RedisCache serialization with dill fallback and sanitization, adds utilities to enable/validate Rich pickling, updates health check to report Rich pickle status, adjusts lfx graph/io for normalized cache DTOs, and pins Rich to a pickle-support branch.

Changes

Cohort / File(s) Summary
Cache serialization utilities and wiring
src/backend/base/langflow/services/cache/__init__.py, src/backend/base/langflow/services/cache/utils.py, src/backend/base/langflow/services/cache/factory.py, src/backend/base/langflow/services/cache/service.py
Introduces Rich pickling setup/validation and status checks; adds CacheMiss shim; wires setup on import; logs capability; enhances RedisCache.set to dill-pickle with warnings suppressed and sanitize fallback; safe logger imports.
ChatService normalization integration + tests
src/backend/base/langflow/services/chat/service.py, src/backend/tests/unit/cache/test_chatservice_cache.py, src/backend/tests/unit/cache/test_normalizer.py, src/lfx/src/lfx/serialization/normalizer.py
Adds normalize_for_cache and uses it in ChatService.set_cache to store normalized envelopes; adds tests for ChatService cache DTO and normalizer behavior; implements normalize_for_cache with cycle handling, class/callable descriptors, pydantic dumps, vertex DTO shaping, and fallbacks.
Health check Rich pickle status
src/backend/base/langflow/api/health_check_router.py
Adds rich_pickle field to HealthResponse; imports utils; computes and populates Rich pickle status with error handling; logger import fallback.
Graph/IO compatibility with normalized cache DTOs
src/lfx/src/lfx/graph/graph/base.py, src/lfx/src/lfx/io/schema.py
Graph build flow supports DTOs marked cache_vertex, mapping unbuilt placeholders to UnbuiltObject; InputSchema models get module registration to aid (de)serialization/importability.
Dependency update
src/backend/base/pyproject.toml
Switches rich dependency to a VCS branch with pickle support; enables direct references via Hatch metadata.
Local verification tool
scripts/run_local_cache_checks.py
Adds a standalone script to shim modules, load local normalizer, and assert shapes for normalizer/chatservice outputs; prints success on completion.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant ChatService
  participant Normalizer as normalize_for_cache
  participant Cache as CacheService/RedisCache

  Client->>ChatService: set_cache(key, data)
  ChatService->>Normalizer: normalize_for_cache(data)
  Normalizer-->>ChatService: normalized DTO (envelope v1)
  alt Async cache available
    ChatService->>Cache: upsert(key, normalized)
  else Fallback thread path
    ChatService->>Cache: upsert(key, normalized) (threaded)
  end
  Cache-->>ChatService: status
  ChatService-->>Client: {"status": true, "type": "normalized", "__envelope_version__": 1}
Loading
sequenceDiagram
  autonumber
  actor Client
  participant API as /health
  participant Utils as cache.utils
  participant Rich as rich Console/ThreadLocals

  Client->>API: GET /health
  API->>Utils: setup_rich_pickle_support()
  Utils->>Rich: patch for pickling (best-effort)
  Rich-->>Utils: success/failure
  API->>Utils: validate_rich_pickle_support()
  Utils-->>API: ok / not ok
  API-->>Client: HealthResponse{rich_pickle: status}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: introduce lfx package #9133 — Introduces and modifies lfx modules (including serialization normalizer and graph/io), directly connected to the normalization and DTO handling added here.

Suggested labels

enhancement, lgtm

Suggested reviewers

  • ogabrielluiz
  • jordanrfrazier

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly identifies the primary goal of the pull request—resolving Redis cache serialization failures caused by Rich library objects—and reflects the main changes described in the PR objectives. It is concise, specific, and follows conventional commit style without including unnecessary details. A reviewer scanning the history will immediately understand the fix being applied.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/base/langflow/services/chat/service.py (1)

41-47: Normalize cache contains to use str(key)
Apply to src/backend/base/langflow/services/chat/service.py:

@@ if isinstance(self.cache_service, AsyncBaseCacheService):
-            return await self.cache_service.contains(key)
+            return await self.cache_service.contains(str(key))
@@
-        return key in self.cache_service
+        return str(key) in self.cache_service

Add a unit test asserting True for a non-string key (e.g., key = 123).

🧹 Nitpick comments (11)
src/backend/base/pyproject.toml (1)

136-138: Direct references enabled — confirm packaging/distribution strategy.

allow-direct-references = true is fine for internal builds, but sdists uploaded to PyPI often reject direct VCS pins and some environments block them. If you plan to ship to PyPI, consider:

  • Vendoring the patch, or
  • Publishing your fork as a versioned package and depending on that, or
  • Using wheels-only distribution for environments that can access VCS.

If distribution to PyPI is intended, verify your release pipeline can build and install without VCS access.

src/lfx/src/lfx/io/schema.py (2)

241-247: Registering dynamic model improves importability — also add a registry to avoid name collisions.

Overwriting module attribute "InputSchema" on repeated calls can collide; add a registry map for observability and uniqueness.

Apply this diff:

     model = create_model("InputSchema", **fields)
     model.model_rebuild()

-    # Register class on module to improve importability for serializers
+    # Register class on module to improve importability for serializers
     import sys
     current_module = sys.modules[__name__]
     model.__module__ = __name__
     setattr(current_module, "InputSchema", model)
+    # Keep a registry to avoid silent collisions
+    registry = getattr(current_module, "_INPUT_SCHEMA_REGISTRY", None)
+    if registry is None:
+        registry = {}
+        setattr(current_module, "_INPUT_SCHEMA_REGISTRY", registry)
+    registry[id(model)] = model

295-302: Also register inner models when present (param_key branch).

When param_key is used, the created inner_model remains unregistered; serializers/picklers may still encounter it. Register both.

Apply this diff:

     model.model_rebuild()

-    # Register class on module to improve importability for serializers
+    # Register class on module to improve importability for serializers
     import sys
     current_module = sys.modules[__name__]
     model.__module__ = __name__
     setattr(current_module, "InputSchema", model)
+    # If an inner model was created earlier, register it as well
+    try:
+        inner_model  # type: ignore[name-defined]
+    except NameError:
+        inner_model = None  # type: ignore[assignment]
+    if inner_model is not None:
+        inner_model.__module__ = __name__
+        setattr(current_module, "InputSchemaInner", inner_model)
+    registry = getattr(current_module, "_INPUT_SCHEMA_REGISTRY", None)
+    if registry is None:
+        registry = {}
+        setattr(current_module, "_INPUT_SCHEMA_REGISTRY", registry)
+    registry[id(model)] = model
+    if inner_model is not None:
+        registry[id(inner_model)] = inner_model
src/backend/base/langflow/services/cache/__init__.py (1)

6-8: Side-effectful setup at import time — consider centralizing in the factory.

It’s idempotent, but you’re also calling setup in the factory. Prefer a single setup point to avoid redundant work.

src/backend/tests/unit/cache/test_normalizer.py (1)

1-3: Minor: remove unused imports and add brief test docstrings.

Keeps tests lean and aligned with repo guidelines.

Apply this diff:

-import types
-from pydantic import BaseModel, create_model
+from pydantic import create_model

Optionally add one-line docstrings atop each test.

src/backend/tests/unit/cache/test_chatservice_cache.py (1)

43-47: Add assertion for envelope version for future-proofing.

Ensures the versioned contract remains intact.

Apply this diff:

     stored = fake.get("k1")
 
     assert stored["type"] == "normalized"
+    assert stored["__envelope_version__"] == 1
     result = stored["result"]
src/backend/base/langflow/services/cache/factory.py (1)

27-37: Enablement + validation flow — consider reflecting validation in the flag.

If validation fails, set the flag to False to reflect actual capability.

Apply this diff:

         self._rich_pickle_enabled = setup_rich_pickle_support()
         if self._rich_pickle_enabled:
             logger.debug("Rich pickle support enabled for cache serialization")
             # Optionally validate the support
-            if validate_rich_pickle_support():
-                logger.debug("Rich pickle support validation successful")
-            else:
-                logger.warning("Rich pickle support validation failed")
+            ok = validate_rich_pickle_support()
+            if ok:
+                logger.debug("Rich pickle support validation successful")
+            else:
+                logger.warning("Rich pickle support validation failed")
+            self._rich_pickle_enabled = self._rich_pickle_enabled and ok
         else:
             logger.info("Rich pickle support could not be enabled")
src/backend/base/langflow/services/cache/service.py (1)

236-316: Reduce duplication by reusing normalize_for_cache

Consider delegating the fallback sanitization to lfx.serialization.normalizer.normalize_for_cache for consistency across cache paths and to avoid divergent behaviors.

scripts/run_local_cache_checks.py (2)

22-33: Import placement and type-ignore specificity

  • Move _types and _pickle imports to top (see prior diff) to fix E402.
  • Replace broad # type: ignore with a specific code (attr-defined).

Apply this diff:

-_normalizer = _load_normalizer()
-normalize_for_cache = _normalizer.normalize_for_cache  # type: ignore
+_normalizer = _load_normalizer()
+normalize_for_cache = _normalizer.normalize_for_cache  # type: ignore[attr-defined]

33-38: Limit dill shim impact

The shim forcibly injects a fake "dill" module. Consider scoping this to only when import fails to avoid masking a real dill install.

Apply this diff:

-# Provide a minimal dill shim for imports in cache.service
-_dill = _types.ModuleType("dill")
-_dill.dumps = lambda obj, *a, **k: _pickle.dumps(obj)
-_dill.loads = lambda b: _pickle.loads(b)
-sys.modules["dill"] = _dill
+# Provide a minimal dill shim only if dill is unavailable
+try:
+    import dill as _real_dill  # noqa: F401
+except Exception:
+    _dill = _types.ModuleType("dill")
+    _dill.dumps = lambda obj, *a, **k: _pickle.dumps(obj)
+    _dill.loads = lambda b: _pickle.loads(b)
+    sys.modules["dill"] = _dill
src/backend/base/langflow/services/cache/utils.py (1)

250-284: Validate with dill too (not just pickle)

Runtime uses dill for Redis serialization. Extend validation to try dill when available.

Apply this diff:

-        import pickle
-
-        from rich.console import Console
+        import pickle
+        from rich.console import Console
+        try:
+            import dill  # type: ignore[import-not-found]
+        except Exception:
+            dill = None
...
-        # Serialize and deserialize
-        pickled = pickle.dumps(test_data)
-        restored = pickle.loads(pickled)
+        # Serialize and deserialize (pickle)
+        pickled = pickle.dumps(test_data)
+        restored = pickle.loads(pickled)
...
-        validation_passed = "validation_test" in capture.get()
+        validation_passed = "validation_test" in capture.get()
+        # Also try dill if present
+        if dill is not None and validation_passed:
+            pickled2 = dill.dumps(test_data)
+            restored2 = dill.loads(pickled2)
+            with restored2["console"].capture() as cap2:
+                restored2["console"].print("validation_test_dill")
+            validation_passed = validation_passed and ("validation_test_dill" in cap2.get())
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 386d24d and 6c44278.

📒 Files selected for processing (13)
  • scripts/run_local_cache_checks.py (1 hunks)
  • src/backend/base/langflow/api/health_check_router.py (3 hunks)
  • src/backend/base/langflow/services/cache/__init__.py (1 hunks)
  • src/backend/base/langflow/services/cache/factory.py (2 hunks)
  • src/backend/base/langflow/services/cache/service.py (3 hunks)
  • src/backend/base/langflow/services/cache/utils.py (3 hunks)
  • src/backend/base/langflow/services/chat/service.py (2 hunks)
  • src/backend/base/pyproject.toml (2 hunks)
  • src/backend/tests/unit/cache/test_chatservice_cache.py (1 hunks)
  • src/backend/tests/unit/cache/test_normalizer.py (1 hunks)
  • src/lfx/src/lfx/graph/graph/base.py (2 hunks)
  • src/lfx/src/lfx/io/schema.py (2 hunks)
  • src/lfx/src/lfx/serialization/normalizer.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{src/backend/**/*.py,tests/**/*.py,Makefile}

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code

Files:

  • src/backend/tests/unit/cache/test_normalizer.py
  • src/backend/base/langflow/services/cache/factory.py
  • src/backend/base/langflow/services/cache/utils.py
  • src/backend/base/langflow/services/chat/service.py
  • src/backend/base/langflow/api/health_check_router.py
  • src/backend/base/langflow/services/cache/__init__.py
  • src/backend/tests/unit/cache/test_chatservice_cache.py
  • src/backend/base/langflow/services/cache/service.py
src/backend/tests/unit/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

Test component integration within flows using create_flow, build_flow, and get_build_events utilities

Files:

  • src/backend/tests/unit/cache/test_normalizer.py
  • src/backend/tests/unit/cache/test_chatservice_cache.py
src/backend/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...

Files:

  • src/backend/tests/unit/cache/test_normalizer.py
  • src/backend/tests/unit/cache/test_chatservice_cache.py
🧬 Code graph analysis (7)
scripts/run_local_cache_checks.py (1)
src/lfx/src/lfx/serialization/normalizer.py (1)
  • normalize_for_cache (12-105)
src/backend/tests/unit/cache/test_normalizer.py (1)
src/lfx/src/lfx/serialization/normalizer.py (1)
  • normalize_for_cache (12-105)
src/backend/base/langflow/services/cache/factory.py (2)
src/backend/base/langflow/services/cache/service.py (2)
  • AsyncInMemoryCache (397-458)
  • RedisCache (179-394)
src/backend/base/langflow/services/cache/utils.py (2)
  • setup_rich_pickle_support (179-247)
  • validate_rich_pickle_support (250-283)
src/backend/base/langflow/services/chat/service.py (1)
src/lfx/src/lfx/serialization/normalizer.py (1)
  • normalize_for_cache (12-105)
src/backend/base/langflow/api/health_check_router.py (1)
src/backend/base/langflow/services/cache/utils.py (2)
  • is_rich_pickle_enabled (286-297)
  • validate_rich_pickle_support (250-283)
src/backend/base/langflow/services/cache/__init__.py (1)
src/backend/base/langflow/services/cache/utils.py (2)
  • is_rich_pickle_enabled (286-297)
  • setup_rich_pickle_support (179-247)
src/backend/tests/unit/cache/test_chatservice_cache.py (1)
src/backend/base/langflow/services/chat/service.py (2)
  • ChatService (13-71)
  • set_cache (23-46)
🪛 GitHub Check: Ruff Style Check (3.13)
scripts/run_local_cache_checks.py

[failure] 26-26: Ruff (E402)
scripts/run_local_cache_checks.py:26:1: E402 Module level import not at top of file


[failure] 23-23: Ruff (PGH003)
scripts/run_local_cache_checks.py:23:56: PGH003 Use specific rule codes when ignoring type issues


[failure] 16-16: Ruff (PT018)
scripts/run_local_cache_checks.py:16:5: PT018 Assertion should be broken down into multiple parts


[failure] 16-16: Ruff (S101)
scripts/run_local_cache_checks.py:16:5: S101 Use of assert detected


[failure] 14-14: Ruff (PTH118)
scripts/run_local_cache_checks.py:14:12: PTH118 os.path.join() should be replaced by Path with / operator


[failure] 10-10: Ruff (E402)
scripts/run_local_cache_checks.py:10:1: E402 Module level import not at top of file


[failure] 8-8: Ruff (PTH118)
scripts/run_local_cache_checks.py:8:20: PTH118 os.path.join() should be replaced by Path with / operator


[failure] 7-7: Ruff (PTH118)
scripts/run_local_cache_checks.py:7:20: PTH118 os.path.join() should be replaced by Path with / operator


[failure] 6-6: Ruff (PTH120)
scripts/run_local_cache_checks.py:6:24: PTH120 os.path.dirname() should be replaced by Path.parent


[failure] 6-6: Ruff (PTH120)
scripts/run_local_cache_checks.py:6:8: PTH120 os.path.dirname() should be replaced by Path.parent

🪛 GitHub Actions: Ruff Style Check
scripts/run_local_cache_checks.py

[error] 6-6: PTH120: os.path.dirname() should be replaced by Path.parent.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (49)
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 31/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 40/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 38/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 19/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 28/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 36/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 32/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 39/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 17/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 25/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 23/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 34/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 27/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 29/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 30/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 35/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 33/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 22/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 26/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 9/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 16/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 24/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 10/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 21/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 18/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 13/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 20/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 15/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 4/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 14/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 12/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 11/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 1/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 3/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 6/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 8/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 5/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 2/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 7/40
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
  • GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
  • GitHub Check: Test Starter Templates
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Run Ruff Check and Format
  • GitHub Check: Update Starter Projects
🔇 Additional comments (13)
src/backend/base/langflow/services/cache/__init__.py (1)

2-2: Public export looks good.

Exposing is_rich_pickle_enabled via all aligns with the health check and consumers.

Also applies to: 15-15

src/backend/tests/unit/cache/test_normalizer.py (1)

7-50: Good coverage for dynamic classes/functions, pydantic models, and vertex placeholders.

The assertions exercise the critical paths in normalize_for_cache.

src/backend/base/langflow/services/chat/service.py (1)

34-39: Normalization envelope before caching — LGTM.

Storing a normalized DTO with an envelope and version makes cache entries safer and evolvable.

src/backend/base/langflow/api/health_check_router.py (1)

71-83: Health check Rich pickle path — LGTM with clear states.

The status states are explicit and safe; errors don’t leak details.

src/backend/tests/unit/cache/test_chatservice_cache.py (1)

22-47: Solid test of normalization path through ChatService.

Covers type flag, vertex marker, and placeholder replacement.

src/backend/base/langflow/services/cache/factory.py (1)

59-65: Helpful runtime logging for Redis cache creation.

Clear messaging if Rich serialization isn’t available.

src/lfx/src/lfx/graph/graph/base.py (2)

33-33: LGTM: Import of UnbuiltObject for DTO compatibility

Importing UnbuiltObject aligns with the new cache DTO shape.


1535-1556: LGTM: DTO-based vertex cache hydration with UnbuiltObject placeholder

The new branch correctly handles "cache_vertex" payloads, defaulting safely and preserving backwards compatibility. The UnbuiltObject placeholder avoids executing cached objects.

Please verify UnbuiltObject is available from lfx.graph.utils in all runtimes (server/tests).

src/lfx/src/lfx/serialization/normalizer.py (1)

12-105: LGTM: Comprehensive, cache-safe normalization with cycle protection

Solid handling of primitives, pydantic models, classes/callables, containers, generators, and vertex snapshots. Set→list conversion avoids unhashables. Matches DTO expectations elsewhere.

src/backend/base/langflow/services/cache/utils.py (3)

12-19: LGTM: Resilient logger import with fallback

Good defensive import; avoids hard dependency on lfx logger.


30-37: LGTM: CacheMiss sentinel

Simple and effective compatibility shim.


286-298: Expose status check is fine; ensure setup is called early

is_rich_pickle_enabled is fine. Ensure setup_rich_pickle_support runs early in cache factory init; otherwise this check will incorrectly return False.

Can you confirm setup_rich_pickle_support() is invoked during startup (e.g., in cache/init.py or factory)? If not, I can suggest a minimal init hook.

src/backend/base/langflow/services/cache/service.py (1)

325-351: Declare and pin dill in your dependencies
Ensure dill is added (e.g., in pyproject.toml or requirements.txt) with an explicit version constraint.

@pkusnail pkusnail changed the title Fix Redis Cache Serialization Issues with Rich Library Objects fix: Redis Cache Serialization Issues with Rich Library Objects Sep 25, 2025
@github-actions github-actions bot added bug Something isn't working and removed bug Something isn't working labels Sep 25, 2025
@github-actions github-actions bot added bug Something isn't working and removed bug Something isn't working labels Sep 26, 2025
pkusnail and others added 20 commits September 26, 2025 15:41
…bjects

Implements custom __getstate__ and __setstate__ methods for Rich library's
ConsoleThreadLocals and Console classes to enable Redis caching compatibility.
Fixes 'cannot pickle ConsoleThreadLocals object' error when using Redis cache.
Automatically setup and validate Rich pickle serialization when creating
cache services. Includes comprehensive logging for Redis cache creation
with Rich object serialization status.
Automatically initialize Rich pickle support when the cache module is
imported, ensuring ConsoleThreadLocals serialization compatibility
across all cache service implementations.
Extends health check response to include rich_pickle field indicating
the status of Rich library serialization support. Enables monitoring
of ConsoleThreadLocals pickle compatibility in production environments.
Refactor try-except blocks to use else clauses instead of early returns,
following TRY300 linting rule. Maintains identical functionality while
improving code style compliance.
- Implement normalize_for_cache() to convert complex objects to descriptors
- Handle classes, functions, Pydantic models, and dynamic instances
- Support vertex snapshots with built_object placeholders
- Include cycle protection and recursive container handling
- Pass all ruff checks with proper exception handling
- Add cache_solution.md documenting normalizer architecture
- Add unit tests for ChatService cache and normalizer functionality
- Add run_local_cache_checks.py script for validation
- Update ChatService to use normalize_for_cache
- Update graph base for cache restore compatibility
- Add notes.md for implementation tracking
- Add __init__.py for test package structure
- Add _sanitize_for_pickle method to handle problematic dynamic schemas
- Replace InputSchema classes and instances with placeholder references
- Handle callable objects with path-like representations
- Filter out dynamically created components to prevent dill issues
- Maintain two-step serialization fallback for Redis compatibility
- Add setup_rich_pickle_support function for ConsoleThreadLocals serialization
- Add validate_rich_pickle_support for testing pickle functionality
- Add is_rich_pickle_enabled status checker for monitoring
- Implement custom __getstate__ and __setstate__ methods for Rich Console objects
- Enable Redis compatibility for Rich library's threading.local objects
- Import normalize_for_cache in ChatService for safe Redis serialization
- Add envelope structure with version tracking for cached data
- Integrate user's normalizer architecture into chat caching pipeline
- Enable cache restoration compatibility with vertex snapshots
- Register InputSchema class to module for improved serialization compatibility
- Set model module to __name__ for proper importability by serializers
- Enable cache-safe dynamic model creation and registration
- Support both schema_to_langflow_inputs and create_input_schema workflows
- Remove cache_solution.md and notes.md from git tracking
- Keep files locally for review purposes
- Documentation will remain unversioned for now
Address all ruff linting violations in scripts/run_local_cache_checks.py:
- Convert os.path operations to pathlib.Path usage (PTH120, PTH118)
- Move imports to appropriate locations (E402)
- Replace assert statements with proper error handling (S101, PT018)
- Add comprehensive docstrings for better code coverage (D1)
- Fix lambda argument names and type ignore annotations
- Resolve whitespace and formatting issues (W293)
- Add security annotations for pickle usage (S301)

The script maintains full functionality while meeting CI/CD standards.
- Fix f-string usage in logging statements (G004)
- Replace broad exception handling with specific exception types (BLE001)
- Add proper logging instead of silent exception handling (S110)
- Specify rule codes for type ignore comments (PGH003)
- Fix variable naming to follow lowercase convention (N806)
- Ensure all cache serialization error handling provides debug information
…es/sets), Rich pickling exclusions, pin rich SHA
@github-actions github-actions bot added bug Something isn't working and removed bug Something isn't working labels Sep 26, 2025
@github-actions github-actions bot added bug Something isn't working and removed bug Something isn't working labels Sep 26, 2025
@sonarqubecloud
Copy link

@pkusnail
Copy link
Author

Hi @Cristhianzl, this PR looks ready to review (mergeable=MERGEABLE). Could you please take a look when you have a moment? Thank you! 🙏

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant