Skip to content

fix: graph cache serialization of runtime components#13232

Open
Trojan7210 wants to merge 1 commit into
langflow-ai:mainfrom
Trojan7210:fix-graph-cache-runtime-component
Open

fix: graph cache serialization of runtime components#13232
Trojan7210 wants to merge 1 commit into
langflow-ai:mainfrom
Trojan7210:fix-graph-cache-runtime-component

Conversation

@Trojan7210
Copy link
Copy Markdown

@Trojan7210 Trojan7210 commented May 20, 2026

Fixes #8476

Summary

This avoids storing live runtime component instances in cached graph state.

When the Redis/Celery path caches a Graph, each Vertex is serialized as part of that graph. Some runtime component instances can hold non-serializable state such as thread-local console objects, which can make cache serialization fail even for simple built-in flows.

The patch omits custom_component from Vertex.__getstate__. The component instance is runtime state and can be re-instantiated from vertex data during build.

Scope

  • Focused LFX graph-cache serialization fix only.
  • Changes only src/lfx/src/lfx/graph/vertex/base.py and src/lfx/tests/unit/graph/graph/test_base.py.
  • No generated starter-project, dependency, or unrelated frontend changes.

Regression Coverage

The test injects an unserializable runtime component into a graph vertex, serializes a payload containing the graph, deserializes it, and asserts that custom_component is omitted from both the serialized state and the restored graph vertex.

Validation

  • uv run ruff check src/lfx/graph/vertex/base.py tests/unit/graph/graph/test_base.py
  • uv run pytest tests/unit/graph/graph/test_base.py -q (16 passed, 1 skipped)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Walkthrough

Vertex.getstate now clears the custom_component field during serialization to avoid pickling runtime-only component instances. A unit test injects an unserializable component into a vertex, pickles a payload containing the Graph, and asserts the component is omitted from serialized/deserialized state.

Changes

Graph vertex serialization cleanup

Layer / File(s) Summary
Vertex.getstate clears custom_component
src/lfx/src/lfx/graph/vertex/base.py
Vertex.__getstate__ sets custom_component to None to exclude runtime component instances from serialized state.
Graph serialization test with pickle support
src/lfx/tests/unit/graph/graph/test_base.py
Test imports pickle, injects an unserializable runtime component into a vertex, pickles a payload containing the Graph, and asserts both the vertex __getstate__() output and the deserialized graph vertex state have custom_component set to None.

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing graph cache serialization of runtime components to prevent pickle errors.
Linked Issues check ✅ Passed The PR directly addresses issue #8476 by preventing pickle errors when serializing Graph state with non-serializable runtime components by omitting custom_component from Vertex serialization.
Out of Scope Changes check ✅ Passed All changes are within scope: modifying Vertex.getstate to exclude custom_component and adding a test for cache serialization round-trips, both directly supporting the issue resolution.
Test Coverage For New Implementations ✅ Passed Test follows naming conventions, has implementation with assertions, simulates the pickle error with unserializable components, validates the fix correctly.
Test Quality And Coverage ✅ Passed Test covers main functionality (serialization with unserializable components), validates behavior through assertions, follows pytest patterns, would catch implementation regressions.
Test File Naming And Structure ✅ Passed Test follows correct patterns: test_*.py naming, pytest structure with descriptive function name, docstring, organized setup/execution/assertions, and edge case coverage for pickle serialization.
Excessive Mock Usage Warning ✅ Passed The new test uses minimal mocking (no Mock/patch imports) and real framework objects; the UnserializableComponent is a focused test double, not excessive mocking that obscures testing intent.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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.

🧹 Nitpick comments (1)
src/lfx/tests/unit/graph/graph/test_base.py (1)

110-130: ⚡ Quick win

Consider adding round-trip serialization test for cache path validation.

The test successfully verifies that pickling no longer raises when a vertex contains an unserializable component. However, since this fix targets the Redis/Celery cache path (serialize → cache → retrieve → deserialize), consider also testing the full round-trip:

deserialized_payload = pickle.loads(serialized_payload)
deserialized_graph = deserialized_payload["result"]
assert deserialized_graph.vertices[0].custom_component is None
# Optionally: verify the graph remains functional after deserialization

This would confirm that:

  1. Unpickling succeeds and produces a valid graph
  2. The custom_component field is correctly restored to None
  3. Cached graphs can be used after retrieval

The current test correctly validates the serialization behavior, but extending it to cover deserialization would provide stronger confidence that the cache round-trip works end-to-end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/tests/unit/graph/graph/test_base.py` around lines 110 - 130, Update
the test_graph_cache_serialization_omits_runtime_component_state to perform a
full serialize→deserialize round trip: use pickle.loads on serialized_payload to
produce deserialized_payload, extract deserialized_graph =
deserialized_payload["result"], and assert
deserialized_graph.vertices[0].custom_component is None (and optionally exercise
a simple graph operation to ensure deserialized_graph remains functional). This
adds cache-path validation by confirming pickle.loads succeeds and the runtime
component was cleared on restore.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lfx/tests/unit/graph/graph/test_base.py`:
- Around line 110-130: Update the
test_graph_cache_serialization_omits_runtime_component_state to perform a full
serialize→deserialize round trip: use pickle.loads on serialized_payload to
produce deserialized_payload, extract deserialized_graph =
deserialized_payload["result"], and assert
deserialized_graph.vertices[0].custom_component is None (and optionally exercise
a simple graph operation to ensure deserialized_graph remains functional). This
adds cache-path validation by confirming pickle.loads succeeds and the runtime
component was cleared on restore.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb9914a4-f704-43a2-b754-98ccf2e31476

📥 Commits

Reviewing files that changed from the base of the PR and between 500b5e9 and 16fb064.

📒 Files selected for processing (2)
  • src/lfx/src/lfx/graph/vertex/base.py
  • src/lfx/tests/unit/graph/graph/test_base.py

@Trojan7210 Trojan7210 changed the title Fix graph cache serialization of runtime components fix: graph cache serialization of runtime components May 20, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels May 20, 2026
@Trojan7210 Trojan7210 force-pushed the fix-graph-cache-runtime-component branch from 0ba124b to b03765b Compare May 20, 2026 08:01
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels May 20, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the vertex base.py to inspect __getstate__ implementation
fd "base.py" --full-path | grep "graph/vertex/base.py" | head -5
rg -n "__getstate__|__setstate__|custom_component" src/backend/base/langflow/graph/vertex/base.py -A3 -B3

# Read the test file
fd "test_base.py" --full-path | grep "graph/graph/test_base.py" | head -5
rg -n "test_graph_cache_serialization|pickle|custom_component" src/backend/tests/unit/graph/graph/test_base.py -A5 -B2

Length of output: 256


🏁 Script executed:

#!/bin/bash
# Check the full __getstate__ and __setstate__ in vertex base
grep -n "__getstate__\|__setstate__\|custom_component" src/backend/base/langflow/graph/vertex/base.py | head -40

# Also look at the full test function
grep -n "pickle\|test_graph_cache" src/backend/tests/unit/graph/graph/test_base.py | head -20

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Use gh CLI to get the PR diff directly
gh pr view 13232 --repo langflow-ai/langflow --json files,title,body | jq '.title,.body' | head -30
gh pr diff 13232 --repo langflow-ai/langflow 2>/dev/null | head -200

Length of output: 3909


@Atlantropa-hoi4: Thanks for the update and for adding the full serialize → deserialize round-trip. The PR is in good shape. Here is the review:


src/lfx/src/lfx/graph/vertex/base.py

The one-liner addition is correct and minimal:

state["custom_component"] = None  # Runtime component instances may hold non-serializable state.

Since there is no custom __setstate__, Python's default restore path calls self.__dict__.update(state), which sets custom_component = None on the deserialized vertex — matching the intent that the instance is re-built lazily during the next build() call. ✅


src/lfx/tests/unit/graph/graph/test_base.py

The test correctly validates three things:

  1. That injecting an unserializable component does not break pickling of the enclosing Graph.
  2. That __getstate__ returns None for custom_component.
  3. That the deserialized vertex also has custom_component is None.

One minor nit:

assert serialized_payload

This line is always truthy — pickle.dumps(...) always produces non-empty bytes. The real assertion is already implicit (an unserializable component would have raised TypeError during pickle.dumps). You could replace it with something more expressive:

assert isinstance(serialized_payload, bytes) and len(serialized_payload) > 0

…but the current form is harmless, and the subsequent assertions carry the meaningful signal. Low priority.


CI note

You're right about the Update Component Index job. It is a well-known GitHub Actions limitation: workflows in the upstream repo that reference a branch via ref: directly will not find branches that live only on a contributor's fork. Since the check is non-required and Validate PR is green, this is not a blocker for merge. The upstream maintainers can address it on their side if needed (e.g. using github.head_ref/github.event.pull_request.head.sha when the event originates from a fork).


Summary: The fix is correct, minimal, and well-tested. The only open item is the trivially-true assert serialized_payload assertion, which is cosmetic. Everything looks good for merge. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels May 20, 2026
@Trojan7210
Copy link
Copy Markdown
Author

CI follow-up: the required CI Success check is currently failing because frontend E2E shard 24 failed in tests/core/integrations/assistant-panel.spec.ts and coverage merge then failed. This PR only changes the LFX graph cache/runtime-component serialization path; the backend/LFX/unit checks passed, and my local targeted validation was:

  • uv run ruff check src/lfx/graph/vertex/base.py tests/unit/graph/graph/test_base.py
  • uv run pytest tests/unit/graph/graph/test_base.py -q (16 passed, 1 skipped)

I tried to rerun the failed CI jobs, but GitHub requires repository admin rights for that rerun.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels May 20, 2026
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.

Flows not working when using celery rabitmq and redis

1 participant