Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2b71625
fix: preserve api key configuration on flow export
HimavarshaVS Mar 10, 2026
1169007
fix individual component's field
HimavarshaVS Mar 10, 2026
07cbd2a
Merge branch 'release-1.8.1' into fix-api-key-components
HimavarshaVS Mar 10, 2026
5b63df4
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 10, 2026
cbf321b
unhide var name
HimavarshaVS Mar 10, 2026
bf41739
Merge branch 'fix-api-key-components' of https://github.com/langflow-…
HimavarshaVS Mar 10, 2026
7853eb3
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 10, 2026
46fbbd5
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Mar 10, 2026
10d0bcd
Merge branch 'release-1.8.1' into fix-api-key-components
HimavarshaVS Mar 11, 2026
6709d70
fetch relevant provider keys
HimavarshaVS Mar 11, 2026
742b394
Merge branch 'fix-api-key-components' of https://github.com/langflow-…
HimavarshaVS Mar 11, 2026
61bdebc
update starter projects
HimavarshaVS Mar 11, 2026
bed96e3
update based on env var
HimavarshaVS Mar 11, 2026
40a7df7
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
e84b3fd
fetch only env variables
HimavarshaVS Mar 11, 2026
5d2b7ff
Merge branch 'fix-api-key-components' of https://github.com/langflow-…
HimavarshaVS Mar 11, 2026
005d86a
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
fd695d2
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Mar 11, 2026
1af3178
update starter projects
HimavarshaVS Mar 11, 2026
c4cf246
Merge branch 'fix-api-key-components' of https://github.com/langflow-…
HimavarshaVS Mar 11, 2026
0b396e9
fix ruff errors
HimavarshaVS Mar 11, 2026
ee09120
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
3682a2d
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Mar 11, 2026
e31a6b0
Merge branch 'release-1.8.1' into fix-api-key-components
HimavarshaVS Mar 11, 2026
8c4b4b8
don't remove api keys if chosen by user
HimavarshaVS Mar 11, 2026
5d49d33
Merge branch 'fix-api-key-components' of https://github.com/langflow-…
HimavarshaVS Mar 11, 2026
407883d
remove redundant code
HimavarshaVS Mar 11, 2026
73bbe78
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
a6cc000
fix update build config
HimavarshaVS Mar 11, 2026
611a505
Merge branch 'fix-api-key-components' of https://github.com/langflow-…
HimavarshaVS Mar 11, 2026
181395d
remove api keys refactor
HimavarshaVS Mar 11, 2026
8492f3c
only load values when exists in db
HimavarshaVS Mar 11, 2026
b3e81ab
modify other components
HimavarshaVS Mar 11, 2026
214a344
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
42b70da
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Mar 11, 2026
41e0cdb
Merge branch 'release-1.8.1' into fix-api-key-components
erichare Mar 11, 2026
4ebb1d3
Template updates
erichare Mar 11, 2026
d84ff61
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
d49c131
Component index update
erichare Mar 11, 2026
caa2b4e
Fix frontend test
erichare Mar 11, 2026
cffaafd
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 11, 2026
6a2f922
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Mar 11, 2026
9c8a8d1
Merge branch 'release-1.8.1' into fix-api-key-components
HimavarshaVS Mar 12, 2026
64165d6
preserve var names
HimavarshaVS Mar 12, 2026
1531a0e
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 12, 2026
b7d99d8
update caution for saving api keys
HimavarshaVS Mar 12, 2026
9bc95dd
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/backend/base/langflow/api/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
parse_value,
raise_error_if_astra_cloud_env,
remove_api_keys,
replace_api_key_with_env_var_name,
validate_is_component,
verify_public_flow_and_get_user,
)
Expand Down Expand Up @@ -75,6 +76,7 @@
"parse_value",
"raise_error_if_astra_cloud_env",
"remove_api_keys",
"replace_api_key_with_env_var_name",
"validate_is_component",
"verify_public_flow_and_get_user",
]
53 changes: 51 additions & 2 deletions src/backend/base/langflow/api/utils/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import re
import uuid
from ast import literal_eval
from datetime import timedelta
Expand Down Expand Up @@ -68,13 +69,61 @@ def has_api_terms(word: str):
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))


def _get_provider_from_template(template: dict) -> str | None:
"""Return provider name from template's model field, if any."""
model_field = template.get("model")
if not isinstance(model_field, dict):
return None
raw = model_field.get("value")
if isinstance(raw, list) and len(raw) > 0 and isinstance(raw[0], dict):
return raw[0].get("provider")
return None
Comment on lines +77 to +79

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚡️Codeflash found 11% (0.11x) speedup for _get_provider_from_template in src/backend/base/langflow/api/utils/core.py

⏱️ Runtime : 345 microseconds 312 microseconds (best of 103 runs)

📝 Explanation and details

The optimized code splits the compound conditional isinstance(raw, list) and len(raw) > 0 and isinstance(raw[0], dict) into separate checks, replacing the explicit length test with a try/except block around raw[0]. This eliminates one call to len() (which invokes the list's __len__ method) and one redundant raw[0] access in the success path—the original code indexed raw[0] twice (once in the condition, once in the return statement) while the optimized version indexes it once and binds it to first. Line profiler data shows the compound condition consumed 15.8% of original runtime; the refactored checks now distribute that cost across lighter operations. The 10% speedup comes from reducing method dispatch overhead and eliminating duplicate indexing, with no change in behavior or exceptions raised.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 1072 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
from copy import deepcopy

# imports
import pytest  # used for our unit tests
# import the function under test from the exact module path provided
from langflow.api.utils.core import _get_provider_from_template


def test_basic_returns_provider_string():
    # A normal template where model is a dict and value is a non-empty list
    # whose first element is a dict containing the 'provider' key.
    template = {"model": {"value": [{"provider": "openai", "id": "gpt-4"}]}}
    # Call the function and assert it returns the provider string
    assert _get_provider_from_template(template) == "openai"


def test_returns_none_when_provider_key_missing_in_first_item():
    # If the first dict in the value list does not have a 'provider' key,
    # the function should return None (raw[0].get("provider") -> None).
    template = {"model": {"value": [{} , {"provider": "should_not_be_seen"}]}}
    assert _get_provider_from_template(template) is None


@pytest.mark.parametrize(
    "model_value",
    [
        None,            # model key present but None -> not a dict -> None
        "a string",      # model is a string -> not a dict -> None
        123,             # model is an int -> not a dict -> None
        ["list"],        # model is a list -> not a dict -> None
    ],
)
def test_non_dict_model_returns_none(model_value):
    # For any model value that is not a dict, the function must return None.
    template = {"model": model_value}
    assert _get_provider_from_template(template) is None


def test_missing_model_key_returns_none():
    # If the template does not have a 'model' key at all, .get("model") -> None
    template = {"not_model": {"value": [{"provider": "x"}]}}
    assert _get_provider_from_template(template) is None


@pytest.mark.parametrize(
    "value_variant",
    [
        None,                # value missing / None -> not a list -> None
        "not a list",        # string -> not a list -> None
        {"a": "dict"},       # dict -> not a list -> None
        42,                  # int -> not a list -> None
    ],
)
def test_model_value_not_list_returns_none(value_variant):
    # If model exists and is a dict but its 'value' is not a list, we must get None.
    template = {"model": {"value": value_variant}}
    assert _get_provider_from_template(template) is None


def test_empty_list_value_returns_none():
    # An empty list means len(raw) == 0 -> function returns None.
    template = {"model": {"value": []}}
    assert _get_provider_from_template(template) is None


def test_first_item_not_dict_returns_none():
    # If the first element of the value list is not a dict, return None.
    template = {"model": {"value": ["string_first", {"provider": "x"}]}}
    assert _get_provider_from_template(template) is None


def test_provider_value_none_and_nonstring_are_returned_as_is():
    # If provider exists and is None, raw[0].get("provider") returns None.
    template_none = {"model": {"value": [{"provider": None}]}}
    assert _get_provider_from_template(template_none) is None

    # If provider is a non-string (e.g. integer), the function returns it unchanged.
    template_int = {"model": {"value": [{"provider": 123}]}}
    assert _get_provider_from_template(template_int) == 123

    # If provider is an empty string, that should be returned (it's a valid value).
    template_empty = {"model": {"value": [{"provider": ""}]}}
    assert _get_provider_from_template(template_empty) == ""


def test_function_does_not_mutate_input_template():
    # Ensure the function does not change the input object.
    original = {"model": {"value": [{"provider": "openai"}, {"provider": "other"}]}, "extra": 1}
    template = deepcopy(original)
    result = _get_provider_from_template(template)
    # The result must be correct...
    assert result == "openai"
    # ...and the template must remain byte-for-byte equal to the original deepcopy.
    assert template == original


def test_large_scale_consistency_and_correctness():
    # Build 1000 templates with a deterministic pattern so the test is repeatable.
    templates = []
    expected = []
    for i in range(1000):
        if i % 3 == 0:
            # Valid provider string for indices divisible by 3
            templates.append({"model": {"value": [{"provider": f"prov_{i}"}]}})
            expected.append(f"prov_{i}")
        elif i % 3 == 1:
            # Missing provider in first dict -> should return None
            templates.append({"model": {"value": [{}]}})
            expected.append(None)
        else:
            # model is not a dict -> should return None
            templates.append({"model": ["not_a_dict"]})
            expected.append(None)

    # Run the function over all templates and collect results.
    results = [_get_provider_from_template(t) for t in templates]

    # Assert the result list exactly matches the expected list.
    assert results == expected


def test_repeated_calls_are_deterministic():
    # Repeated calls with the same input must always produce the same output.
    template = {"model": {"value": [{"provider": "deterministic"}]}}
    first = _get_provider_from_template(template)
    # Call multiple times in a loop to simulate repeated usage.
    for _ in range(1000):
        assert _get_provider_from_template(template) == first
#------------------------------------------------
import pytest
from langflow.api.utils.core import _get_provider_from_template


def test_basic_provider_extraction():
    """Test basic case: extract provider from well-formed template."""
    template = {
        "model": {
            "value": [
                {"provider": "openai"}
            ]
        }
    }
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_multiple_items_in_value_list():
    """Test that only the first item in value list is checked."""
    template = {
        "model": {
            "value": [
                {"provider": "openai"},
                {"provider": "anthropic"}
            ]
        }
    }
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_provider_with_different_name():
    """Test extraction of provider with different name."""
    template = {
        "model": {
            "value": [
                {"provider": "anthropic"}
            ]
        }
    }
    result = _get_provider_from_template(template)
    assert result == "anthropic"


def test_no_model_field():
    """Test template without model field returns None."""
    template = {"other_field": "value"}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_field_is_none():
    """Test when model field is explicitly None."""
    template = {"model": None}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_field_is_string():
    """Test when model field is a string instead of dict."""
    template = {"model": "not_a_dict"}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_field_is_list():
    """Test when model field is a list instead of dict."""
    template = {"model": ["item1", "item2"]}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_is_none():
    """Test when model.value is None."""
    template = {"model": {"value": None}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_is_string():
    """Test when model.value is a string instead of list."""
    template = {"model": {"value": "not_a_list"}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_empty_list():
    """Test when model.value is an empty list."""
    template = {"model": {"value": []}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_first_item_not_dict():
    """Test when first item in value list is not a dict."""
    template = {"model": {"value": ["string_item"]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_first_item_dict_no_provider():
    """Test when first item is dict but has no provider field."""
    template = {"model": {"value": [{"other_field": "value"}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_first_item_dict_provider_none():
    """Test when provider field exists but is None."""
    template = {"model": {"value": [{"provider": None}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_empty_template():
    """Test with empty template dict."""
    template = {}
    result = _get_provider_from_template(template)
    assert result is None


def test_provider_empty_string():
    """Test when provider value is empty string."""
    template = {"model": {"value": [{"provider": ""}]}}
    result = _get_provider_from_template(template)
    assert result == ""


def test_provider_with_special_characters():
    """Test provider name containing special characters."""
    template = {"model": {"value": [{"provider": "open-ai_v2.0"}]}}
    result = _get_provider_from_template(template)
    assert result == "open-ai_v2.0"


def test_provider_with_numbers():
    """Test provider name with numeric characters."""
    template = {"model": {"value": [{"provider": "gpt4"}]}}
    result = _get_provider_from_template(template)
    assert result == "gpt4"


def test_provider_with_uppercase():
    """Test provider name with uppercase letters."""
    template = {"model": {"value": [{"provider": "OpenAI"}]}}
    result = _get_provider_from_template(template)
    assert result == "OpenAI"


def test_provider_unicode_characters():
    """Test provider name with unicode characters."""
    template = {"model": {"value": [{"provider": "供应商"}]}}
    result = _get_provider_from_template(template)
    assert result == "供应商"


def test_provider_with_spaces():
    """Test provider name containing spaces."""
    template = {"model": {"value": [{"provider": "my provider"}]}}
    result = _get_provider_from_template(template)
    assert result == "my provider"


def test_model_value_with_non_dict_followed_by_dict():
    """Test value list starting with non-dict and then dict."""
    template = {"model": {"value": ["string", {"provider": "openai"}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_field_is_integer():
    """Test when model field is an integer."""
    template = {"model": 42}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_field_is_list_of_dicts():
    """Test when model field itself is a list of dicts."""
    template = {"model": [{"value": "test"}]}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_list_with_only_numbers():
    """Test value list containing only numbers."""
    template = {"model": {"value": [1, 2, 3]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_list_with_only_strings():
    """Test value list containing only strings."""
    template = {"model": {"value": ["first", "second"]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_deeply_nested_template():
    """Test template with extra nesting levels."""
    template = {
        "outer": {
            "inner": {
                "model": {
                    "value": [{"provider": "openai"}]
                }
            }
        },
        "model": {
            "value": [{"provider": "anthropic"}]
        }
    }
    result = _get_provider_from_template(template)
    assert result == "anthropic"


def test_model_with_additional_fields():
    """Test model dict with additional fields beyond value."""
    template = {
        "model": {
            "value": [{"provider": "openai"}],
            "type": "string",
            "description": "Model field"
        }
    }
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_provider_dict_with_additional_fields():
    """Test provider dict with additional fields."""
    template = {
        "model": {
            "value": [
                {
                    "provider": "openai",
                    "name": "gpt-4",
                    "version": "2024"
                }
            ]
        }
    }
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_model_value_list_with_mixed_types():
    """Test value list with mixed types including dict at start."""
    template = {
        "model": {
            "value": [{"provider": "openai"}, 123, "string", None]
        }
    }
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_provider_numeric_value():
    """Test when provider value is a number."""
    template = {"model": {"value": [{"provider": 42}]}}
    result = _get_provider_from_template(template)
    assert result == 42


def test_provider_list_value():
    """Test when provider value is a list."""
    template = {"model": {"value": [{"provider": ["openai", "anthropic"]}]}}
    result = _get_provider_from_template(template)
    assert result == ["openai", "anthropic"]


def test_provider_dict_value():
    """Test when provider value is a dict."""
    template = {"model": {"value": [{"provider": {"name": "openai"}}]}}
    result = _get_provider_from_template(template)
    assert result == {"name": "openai"}


def test_model_value_nested_empty_list():
    """Test when value contains nested empty list."""
    template = {"model": {"value": [[]]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_with_false_as_first_item():
    """Test value list with False as first item."""
    template = {"model": {"value": [False]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_with_zero_as_first_item():
    """Test value list with 0 as first item."""
    template = {"model": {"value": [0]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_model_value_with_empty_dict_as_first_item():
    """Test value list with empty dict as first item."""
    template = {"model": {"value": [{}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_very_long_provider_name():
    """Test provider name that is very long."""
    long_name = "a" * 10000
    template = {"model": {"value": [{"provider": long_name}]}}
    result = _get_provider_from_template(template)
    assert result == long_name


def test_template_with_many_extra_fields():
    """Test template with many extra fields at root level."""
    template = {
        "field1": "value1",
        "field2": "value2",
        "field3": "value3",
        "model": {"value": [{"provider": "openai"}]},
        "field4": "value4",
        "field5": "value5"
    }
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_model_value_list_with_many_items():
    """Test value list with many items."""
    value_list = [
        {"provider": f"provider{i}"} for i in range(100)
    ]
    template = {"model": {"value": value_list}}
    result = _get_provider_from_template(template)
    assert result == "provider0"


def test_provider_boolean_true():
    """Test when provider value is boolean True."""
    template = {"model": {"value": [{"provider": True}]}}
    result = _get_provider_from_template(template)
    assert result is True


def test_provider_boolean_false():
    """Test when provider value is boolean False."""
    template = {"model": {"value": [{"provider": False}]}}
    result = _get_provider_from_template(template)
    assert result is False


def test_case_sensitivity_of_provider_key():
    """Test that 'provider' key is case-sensitive."""
    template = {"model": {"value": [{"Provider": "openai"}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_case_sensitivity_of_model_key():
    """Test that 'model' key is case-sensitive."""
    template = {"Model": {"value": [{"provider": "openai"}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_case_sensitivity_of_value_key():
    """Test that 'value' key is case-sensitive."""
    template = {"model": {"Value": [{"provider": "openai"}]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_large_template_with_many_top_level_keys():
    """Test performance with template containing many top-level keys."""
    template = {f"key_{i}": f"value_{i}" for i in range(1000)}
    template["model"] = {"value": [{"provider": "openai"}]}
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_large_value_list_access_first_item():
    """Test that function correctly accesses first item in large list."""
    large_list = [
        {"provider": f"provider_{i}"} for i in range(1000)
    ]
    template = {"model": {"value": large_list}}
    result = _get_provider_from_template(template)
    assert result == "provider_0"


def test_large_first_dict_with_many_keys():
    """Test first dict in value list with many keys."""
    first_dict = {f"key_{i}": f"value_{i}" for i in range(1000)}
    first_dict["provider"] = "openai"
    template = {"model": {"value": [first_dict]}}
    result = _get_provider_from_template(template)
    assert result == "openai"


def test_large_nested_dict_structure():
    """Test deeply nested dict structure."""
    current = {"provider": "openai"}
    for i in range(100):
        current = {"nested": current}
    template = {"model": {"value": [current]}}
    result = _get_provider_from_template(template)
    assert result is None


def test_template_processing_consistency_multiple_calls():
    """Test that multiple calls with same template return same result."""
    template = {"model": {"value": [{"provider": "openai"}]}}
    results = [_get_provider_from_template(template) for _ in range(1000)]
    assert all(r == "openai" for r in results)


def test_template_processing_consistency_none_cases():
    """Test consistency when template should return None."""
    template = {}
    results = [_get_provider_from_template(template) for _ in range(1000)]
    assert all(r is None for r in results)


def test_multiple_templates_different_providers():
    """Test processing multiple different templates."""
    templates = [
        {"model": {"value": [{"provider": f"provider_{i}"}]}}
        for i in range(100)
    ]
    results = [_get_provider_from_template(t) for t in templates]
    assert results == [f"provider_{i}" for i in range(100)]


def test_mixed_valid_and_invalid_templates():
    """Test mix of valid and invalid templates."""
    templates = [
        {"model": {"value": [{"provider": "openai"}]}},
        {},
        {"model": None},
        {"model": {"value": []}},
        {"model": {"value": [{"other": "field"}]}},
        {"model": {"value": [{"provider": "anthropic"}]}},
    ]
    results = [_get_provider_from_template(t) for t in templates]
    assert results == ["openai", None, None, None, None, "anthropic"]


def test_large_template_all_fields_populated():
    """Test large template with all possible fields populated."""
    template = {
        "model": {
            "value": [
                {
                    "provider": "openai",
                    "nested": {
                        "deep": {
                            "fields": [1, 2, 3]
                        }
                    }
                }
            ],
            "type": "string",
            "description": "test"
        },
        "other_field": "value",
        "nested_structure": {
            "level1": {
                "level2": {
                    "level3": "data"
                }
            }
        }
    }
    result = _get_provider_from_template(template)
    assert result == "openai"

To test or edit this optimization locally git merge codeflash/optimize-pr12129-2026-03-11T14.39.17

Suggested change
if isinstance(raw, list) and len(raw) > 0 and isinstance(raw[0], dict):
return raw[0].get("provider")
return None
if not isinstance(raw, list):
return None
try:
first = raw[0]
except IndexError:
return None
if not isinstance(first, dict):
return None
return first.get("provider")



def _looks_like_variable_name(value: Any) -> bool:
"""Return True if value looks like a variable name."""
if not value or not isinstance(value, str) or not value.strip():
return False
return bool(re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*", value.strip()))


def replace_api_key_with_env_var_name(flow: dict) -> dict:
"""Normalize api_key to a variable name when possible, never export raw keys."""
for node in flow.get("data", {}).get("nodes", []):
node_data = node.get("data")
if not isinstance(node_data, dict):
continue
node_inner = node_data.get("node")
if not isinstance(node_inner, dict):
continue
template = node_inner.get("template")
if not isinstance(template, dict):
continue
for value in template.values():
if isinstance(value, dict) and value.get("name") == "api_key" and value.get("password"):
current = value.get("value")
if _looks_like_variable_name(current):
break # keep user's custom variable name
# raw secret or other string: clear it
value["value"] = None
break
return flow


def remove_api_keys(flow: dict):
"""Remove api keys from flow data."""
"""Clear secret values from flow data."""
flow = replace_api_key_with_env_var_name(flow)
Comment thread
HimavarshaVS marked this conversation as resolved.
Outdated
for node in flow.get("data", {}).get("nodes", []):
node_data = node.get("data").get("node")
template = node_data.get("template")
for value in template.values():
if isinstance(value, dict) and "name" in value and has_api_terms(value["name"]) and value.get("password"):
if (
isinstance(value, dict)
and "name" in value
and has_api_terms(value["name"])
and value.get("password")
and value.get("name") != "api_key"
):
value["value"] = None

return flow
Expand Down
13 changes: 12 additions & 1 deletion src/backend/base/langflow/api/v1/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
from sqlmodel import and_, col, select
from sqlmodel.ext.asyncio.session import AsyncSession

from langflow.api.utils import CurrentActiveUser, DbSession, cascade_delete_flow, remove_api_keys, validate_is_component
from langflow.api.utils import (
CurrentActiveUser,
DbSession,
cascade_delete_flow,
remove_api_keys,
replace_api_key_with_env_var_name,
validate_is_component,
)
from langflow.api.v1.schemas import FlowListCreate
from langflow.helpers.user import get_user_by_flow_id_or_endpoint_name
from langflow.initial_setup.constants import STARTER_FOLDER_NAME
Expand Down Expand Up @@ -480,6 +487,8 @@ async def update_flow(
if flow.endpoint_name is None or flow.endpoint_name == "":
update_data["endpoint_name"] = None

# Never persist raw api_key; use env var name so import resolves from env
update_data = replace_api_key_with_env_var_name(update_data)
Comment thread
HimavarshaVS marked this conversation as resolved.
Outdated
if settings_service.settings.remove_api_keys:
update_data = remove_api_keys(update_data)

Expand Down Expand Up @@ -672,6 +681,8 @@ async def _update_existing_flow(
if "folder_id" not in update_data or update_data.get("folder_id") is None:
update_data.pop("folder_id", None)

# Never persist raw api_key; use env var name so import resolves from env
update_data = replace_api_key_with_env_var_name(update_data)
Comment thread
HimavarshaVS marked this conversation as resolved.
Outdated
if settings_service.settings.remove_api_keys:
update_data = remove_api_keys(update_data)

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n apply_provider_variable_config_to_build_config,\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import IntInput, MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n StrInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n info=\"Maximum number of tokens to generate. Field name varies by provider.\",\n advanced=True,\n range_spec=RangeSpec(min=1, max=128000, step=1, step_type=\"int\"),\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n max_tokens=getattr(self, \"max_tokens\", None),\n watsonx_url=getattr(self, \"base_url_ibm_watsonx\", None),\n watsonx_project_id=getattr(self, \"project_id\", None),\n ollama_base_url=getattr(self, \"ollama_base_url\", None),\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n # Update model options\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n # Hide all provider-specific fields by default\n for field in [\"api_key\", \"base_url_ibm_watsonx\", \"project_id\", \"ollama_base_url\"]:\n if field in build_config:\n build_config[field][\"show\"] = False\n build_config[field][\"required\"] = False\n\n # Show/configure provider-specific fields based on selected model\n # Get current model value - from field_value if model is being changed, otherwise from build_config\n current_model_value = field_value if field_name == \"model\" else build_config.get(\"model\", {}).get(\"value\")\n if isinstance(current_model_value, list) and len(current_model_value) > 0:\n selected_model = current_model_value[0]\n provider = selected_model.get(\"provider\", \"\")\n\n if provider:\n # Apply provider variable configuration (required_for_component, advanced, env var fallback)\n build_config = apply_provider_variable_config_to_build_config(build_config, provider)\n\n return build_config\n"
"value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n apply_provider_variable_config_to_build_config,\n get_language_model_options,\n get_llm,\n get_model_providers,\n get_provider_for_model_name,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import IntInput, MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n StrInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n info=\"Maximum number of tokens to generate. Field name varies by provider.\",\n advanced=True,\n range_spec=RangeSpec(min=1, max=128000, step=1, step_type=\"int\"),\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n max_tokens=getattr(self, \"max_tokens\", None),\n watsonx_url=getattr(self, \"base_url_ibm_watsonx\", None),\n watsonx_project_id=getattr(self, \"project_id\", None),\n ollama_base_url=getattr(self, \"ollama_base_url\", None),\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n # Update model options\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n # Hide all provider-specific fields by default\n for field in [\"api_key\", \"base_url_ibm_watsonx\", \"project_id\", \"ollama_base_url\"]:\n if field in build_config:\n build_config[field][\"show\"] = False\n build_config[field][\"required\"] = False\n\n current_model_value = field_value if field_name == \"model\" else build_config.get(\"model\", {}).get(\"value\")\n provider = \"\"\n if isinstance(current_model_value, list) and current_model_value:\n selected_model = current_model_value[0]\n provider = (selected_model.get(\"provider\") or \"\").strip()\n if not provider and selected_model.get(\"name\"):\n provider = get_provider_for_model_name(str(selected_model[\"name\"]))\n\n if provider:\n build_config = apply_provider_variable_config_to_build_config(build_config, provider)\n else:\n # No provider selected yet (e.g. new node): pre-fill api_key from first provider that has env configured\n for p in get_model_providers():\n build_config = apply_provider_variable_config_to_build_config(build_config, p)\n if build_config.get(\"api_key\", {}).get(\"value\"):\n break\n\n return build_config\n"
},
"input_value": {
"_input_type": "MessageInput",
Expand Down
Loading
Loading