From faac7ad007a3c2aa37a95b64d021db8cba60cf96 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Mon, 13 Apr 2026 11:10:36 -0700 Subject: [PATCH] fix: allow booleans, numbers, etc. in root-level tweaks (#12605) fix: allow booleans, numbers, etc. in root-level tweaks (#11830) Widen the Tweaks schema to accept bool, int, and float values alongside str and dict, so scalar tweaks like {"stream": false} are no longer rejected by Pydantic validation. bool is listed before int in the union to prevent Pydantic from coercing booleans to integers (since bool is a subclass of int). Adds unit tests for boolean and numeric root-level tweaks, as well as direct Tweaks Pydantic model validation tests. Co-authored-by: Alex Kuligowski Co-authored-by: Claude Opus 4.6 --- src/backend/tests/unit/test_process.py | 153 +++++++++++++++++++++++++ src/lfx/src/lfx/schema/graph.py | 4 +- 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/src/backend/tests/unit/test_process.py b/src/backend/tests/unit/test_process.py index 776c8ebcf3..e752b80b8f 100644 --- a/src/backend/tests/unit/test_process.py +++ b/src/backend/tests/unit/test_process.py @@ -310,6 +310,159 @@ async def test_load_langchain_object_with_cached_session(basic_graph_data): # assert graph1 == graph2 +def test_tweak_no_node_id_boolean(): + """Test that boolean tweaks at root level are applied to all matching nodes.""" + graph_data = { + "data": { + "nodes": [ + { + "id": "node1", + "data": { + "node": { + "template": { + "enabled": {"value": True, "type": "bool"}, + "param1": {"value": "hello", "type": "str"}, + } + } + }, + }, + { + "id": "node2", + "data": { + "node": { + "template": { + "enabled": {"value": True, "type": "bool"}, + } + } + }, + }, + ] + } + } + tweaks = {"enabled": False} + expected_result = { + "data": { + "nodes": [ + { + "id": "node1", + "data": { + "node": { + "template": { + "enabled": {"value": False, "type": "bool"}, + "param1": {"value": "hello", "type": "str"}, + } + } + }, + }, + { + "id": "node2", + "data": { + "node": { + "template": { + "enabled": {"value": False, "type": "bool"}, + } + } + }, + }, + ] + } + } + result = process_tweaks(graph_data, tweaks) + assert result == expected_result + + +def test_tweak_no_node_id_numeric(): + """Test that numeric tweaks at root level are applied to all matching nodes.""" + graph_data = { + "data": { + "nodes": [ + { + "id": "node1", + "data": { + "node": { + "template": { + "temperature": {"value": 1.0, "type": "float"}, + "max_tokens": {"value": 100, "type": "int"}, + } + } + }, + }, + { + "id": "node2", + "data": { + "node": { + "template": { + "temperature": {"value": 1.0, "type": "float"}, + } + } + }, + }, + ] + } + } + tweaks = {"temperature": 0.7, "max_tokens": 256} + expected_result = { + "data": { + "nodes": [ + { + "id": "node1", + "data": { + "node": { + "template": { + "temperature": {"value": 0.7, "type": "float"}, + "max_tokens": {"value": 256, "type": "int"}, + } + } + }, + }, + { + "id": "node2", + "data": { + "node": { + "template": { + "temperature": {"value": 0.7, "type": "float"}, + } + } + }, + }, + ] + } + } + result = process_tweaks(graph_data, tweaks) + assert result == expected_result + + +def test_tweaks_schema_accepts_bool(): + """Tweaks model must accept boolean root-level values without coercion.""" + from lfx.schema.graph import Tweaks + + tweaks = Tweaks(root={"stream": False, "enabled": True}) + assert tweaks.root["stream"] is False + assert tweaks.root["enabled"] is True + # Verify bool is preserved and not coerced to int + assert isinstance(tweaks.root["stream"], bool) + assert isinstance(tweaks.root["enabled"], bool) + + +def test_tweaks_schema_accepts_numerics(): + """Tweaks model must accept int and float root-level values.""" + from lfx.schema.graph import Tweaks + + tweaks = Tweaks(root={"temperature": 0.7, "max_tokens": 256}) + assert tweaks.root["temperature"] == 0.7 + assert tweaks.root["max_tokens"] == 256 + + +def test_tweaks_schema_rejects_invalid(): + """Tweaks model should still reject unsupported value types.""" + import pytest + from lfx.schema.graph import Tweaks + from pydantic import ValidationError + + with pytest.raises(ValidationError): + Tweaks(root={"param": [1, 2, 3]}) + + def test_apply_tweaks_code_override_prevention(): """Test that code tweaks are prevented and logged as warning.""" from unittest.mock import patch diff --git a/src/lfx/src/lfx/schema/graph.py b/src/lfx/src/lfx/schema/graph.py index 8e64606661..f12248423a 100644 --- a/src/lfx/src/lfx/schema/graph.py +++ b/src/lfx/src/lfx/schema/graph.py @@ -16,7 +16,7 @@ class InputValue(BaseModel): class Tweaks(RootModel): - root: dict[str, str | dict[str, Any]] = Field( + root: dict[str, bool | int | float | str | dict[str, Any]] = Field( description="A dictionary of tweaks to adjust the flow's execution. " "Allows customizing flow behavior dynamically. " "All tweaks are overridden by the input values.", @@ -26,6 +26,8 @@ class Tweaks(RootModel): "examples": [ { "parameter_name": "value", + "stream": False, + "temperature": 0.7, "Component Name": {"parameter_name": "value"}, "component_id": {"parameter_name": "value"}, }