feat: Add LambdaFilter for enhanced data filtering (#7095)

* feat: add LambdaFilterComponent for dynamic data filtering

- Introduced LambdaFilterComponent to generate lambda functions for filtering or transforming structured data using LLMs.
- Updated __init__.py to include the new component.
- Added utility functions in data_structure.py for analyzing and inferring data types.
- Implemented unit tests for LambdaFilterComponent to ensure functionality and error handling.

* feat: enhance LambdaFilterComponent with new features and improvements

- Updated filter_instruction input to provide clearer guidance and examples.
- Added max_size input to specify character limits for large datasets.
- Renamed output from "Processed Data" to "Filtered Data" for clarity.
- Introduced new output "DataFrame" to return filtered data in DataFrame format.
- Improved data handling in filter_data method to ensure proper conversion of processed data to Data objects.
- Added as_dataframe method to return filtered data as a DataFrame.

This update enhances usability and functionality of the LambdaFilterComponent.

* [autofix.ci] apply automated fixes

* fix: ruff errors

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes (attempt 3/3)

* [autofix.ci] apply automated fixes

* updated the test file and fixed formatting issues

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
This commit is contained in:
Rodrigo Nader 2025-03-18 17:19:58 -03:00 • committed by GitHub
commit 5f227a7adb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 501 additions and 0 deletions

View file

@ -0,0 +1,122 @@
from unittest.mock import AsyncMock
import pytest
from langflow.components.processing.lambda_filter import LambdaFilterComponent
from langflow.schema import Data
from tests.base import ComponentTestBaseWithoutClient
class TestLambdaFilterComponent(ComponentTestBaseWithoutClient):
@pytest.fixture
def component_class(self):
return LambdaFilterComponent
@pytest.fixture
def default_kwargs(self):
return {
"data": [Data(data={"items": [{"name": "test1", "value": 10}, {"name": "test2", "value": 20}]})],
"llm": AsyncMock(),
"filter_instruction": "Filter items with value greater than 15",
"sample_size": 1000,
"max_size": 30000,
}
@pytest.fixture
def file_names_mapping(self):
return []
async def test_successful_lambda_generation(self, component_class, default_kwargs):
component = await self.component_setup(component_class, default_kwargs)
component.llm.ainvoke.return_value.content = "lambda x: [item for item in x['items'] if item['value'] > 15]"
# Execute filter
result = await component.filter_data()
# Assertions
assert isinstance(result, list)
assert len(result) == 1
assert result[0].name == "test2"
assert result[0].value == 20
async def test_invalid_lambda_response(self, component_class, default_kwargs):
component = await self.component_setup(component_class, default_kwargs)
component.llm.ainvoke.return_value.content = "invalid lambda syntax"
# Test exception handling
with pytest.raises(ValueError, match="Could not find lambda in response"):
await component.filter_data()
async def test_lambda_with_large_dataset(self, component_class, default_kwargs):
large_data = {"items": [{"name": f"test{i}", "value": i} for i in range(2000)]}
default_kwargs["data"] = [Data(data=large_data)]
default_kwargs["filter_instruction"] = "Filter items with value greater than 1500"
component = await self.component_setup(component_class, default_kwargs)
component.llm.ainvoke.return_value.content = "lambda x: [item for item in x['items'] if item['value'] > 1500]"
# Execute filter
result = await component.filter_data()
# Assertions
assert isinstance(result, list)
assert len(result) == 499 # Items with value from 1501 to 1999
assert all(item.value > 1500 for item in result)
async def test_lambda_with_complex_data_structure(self, component_class, default_kwargs):
complex_data = {
"categories": {
"A": [{"id": 1, "score": 90}, {"id": 2, "score": 85}],
"B": [{"id": 3, "score": 95}, {"id": 4, "score": 88}],
}
}
default_kwargs["data"] = [Data(data=complex_data)]
default_kwargs["filter_instruction"] = "Filter items with score greater than 90"
component = await self.component_setup(component_class, default_kwargs)
component.llm.ainvoke.return_value.content = (
"lambda x: [item for cat in x['categories'].values() for item in cat if item['score'] > 90]"
)
# Execute filter
result = await component.filter_data()
# Assertions
assert isinstance(result, list)
assert len(result) == 1
assert result[0].id == 3
assert result[0].score == 95
def test_validate_lambda(self, component_class):
component = component_class()
# Valid lambda
valid_lambda = "lambda x: x + 1"
assert component._validate_lambda(valid_lambda) is True
# Invalid lambda: missing 'lambda'
invalid_lambda_1 = "x: x + 1"
assert component._validate_lambda(invalid_lambda_1) is False
# Invalid lambda: missing ':'
invalid_lambda_2 = "lambda x x + 1"
assert component._validate_lambda(invalid_lambda_2) is False
def test_get_data_structure(self, component_class):
component = component_class()
test_data = {
"string": "test",
"number": 42,
"list": [1, 2, 3],
"dict": {"key": "value"},
"nested": {"a": [{"b": 1}]},
}
structure = component.get_data_structure(test_data)
# Assertions
assert structure["string"]["structure"] == "str"
assert structure["number"]["structure"] == "int"
assert structure["list"]["structure"] == "list(int)[size=3]"
assert structure["dict"]["structure"]["key"] == "str"
assert "structure" in structure["nested"]
assert "a" in structure["nested"]["structure"]
assert "list" in structure["nested"]["structure"]["a"]