Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds test cases for loop component compatibility with the APIs, Loop component updates to support API #5615

Merged
merged 28 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0d65b9e
add loop component 🎁🎄
rodrigosnader Dec 24, 2024
8d95eeb
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 24, 2024
4886d91
fix: add loop component to init
rodrigosnader Dec 26, 2024
89c9e18
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 26, 2024
d4e5485
refactor(loop): rename loop input variable and improve code quality
ogabrielluiz Jan 6, 2025
e839deb
refactor(loop): add type hint to initialize_data method for improved …
ogabrielluiz Jan 6, 2025
02a0d7a
adding test
edwinjosechittilappilly Jan 7, 2025
dbb37ad
test cases added
edwinjosechittilappilly Jan 8, 2025
bd81d80
Update test_loop.py
edwinjosechittilappilly Jan 9, 2025
c7d1466
Merge branch 'main' into fix-loop
edwinjosechittilappilly Jan 10, 2025
f78d6a4
adding test
edwinjosechittilappilly Jan 7, 2025
20a27dc
test cases added
edwinjosechittilappilly Jan 8, 2025
3b8cb29
Update test_loop.py
edwinjosechittilappilly Jan 9, 2025
c344857
update with the new test case method!
edwinjosechittilappilly Jan 10, 2025
67a510c
Merge branch 'fix-loop' of https://github.com/langflow-ai/langflow in…
edwinjosechittilappilly Jan 10, 2025
b8fcfc6
Update test_loop.py
edwinjosechittilappilly Jan 10, 2025
afe3663
tests updates
edwinjosechittilappilly Jan 10, 2025
576f6fa
Update loop.py
edwinjosechittilappilly Jan 13, 2025
a5dcaa0
update fix
edwinjosechittilappilly Jan 13, 2025
4cb28da
issues loop issues
edwinjosechittilappilly Jan 13, 2025
ecc3785
Merge branch 'loop-component' into fix-loop
edwinjosechittilappilly Jan 13, 2025
a219350
reverting debug mode params
edwinjosechittilappilly Jan 13, 2025
160d38b
Merge branch 'fix-loop' of https://github.com/langflow-ai/langflow in…
edwinjosechittilappilly Jan 13, 2025
09a0774
solves lint errors and fix the tests
edwinjosechittilappilly Jan 13, 2025
6ae069a
fix: mypy error incompatible return value type
italojohnny Jan 14, 2025
bb335ea
Merge branch 'loop-component' into fix-loop
edwinjosechittilappilly Jan 14, 2025
a75976e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 14, 2025
df3cefd
Merge branch 'loop-component' into fix-loop
edwinjosechittilappilly Jan 14, 2025
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
80 changes: 51 additions & 29 deletions src/backend/base/langflow/components/logic/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,7 @@ def initialize_data(self) -> None:
return

# Ensure data is a list of Data objects
if isinstance(self.data, Data):
data_list: list[Data] = [self.data]
elif isinstance(self.data, list):
if not all(isinstance(item, Data) for item in self.data):
msg = "All items in the data list must be Data objects."
raise TypeError(msg)
data_list = self.data
else:
msg = "The 'data' input must be a list of Data objects or a single Data object."
raise TypeError(msg)
data_list = self._validate_data(self.data)

# Store the initial data and context variables
self.update_ctx(
Expand All @@ -47,25 +38,62 @@ def initialize_data(self) -> None:
}
)

def _validate_data(self, data):
"""Validate and return a list of Data objects."""
if isinstance(data, Data):
return [data]
if isinstance(data, list) and all(isinstance(item, Data) for item in data):
return data
msg = "The 'data' input must be a list of Data objects or a single Data object."
raise TypeError(msg)

def evaluate_stop_loop(self) -> bool:
"""Evaluate whether to stop item or done output."""
current_index = self.ctx.get(f"{self._id}_index", 0)
data_length = len(self.ctx.get(f"{self._id}_data", []))
return current_index > data_length

def item_output(self) -> Data:
"""Output the next item in the list."""
"""Output the next item in the list or stop if done."""
self.initialize_data()
current_item = Data(text="")

# Get data list and current index
data_list: list[Data] = self.ctx.get(f"{self._id}_data", [])
current_index: int = self.ctx.get(f"{self._id}_index", 0)
if self.evaluate_stop_loop():
self.stop("item")
return Data(text="")

# Get data list and current index
data_list, current_index = self.loop_variables()
if current_index < len(data_list):
# Output current item and increment index
current_item: Data = data_list[current_index]
self.update_ctx({f"{self._id}_index": current_index + 1})
return current_item

# No more items to output
self.stop("item")
return None
try:
current_item = data_list[current_index]
except IndexError:
current_item = Data(text="")
self.aggregated_output()
self.update_ctx({f"{self._id}_index": current_index + 1})
return current_item

def done_output(self) -> Data:
"""Trigger the done output when iteration is complete."""
self.initialize_data()

if self.evaluate_stop_loop():
self.stop("item")
self.start("done")

return self.ctx.get(f"{self._id}_aggregated", [])
self.stop("done")
return Data(text="")

def loop_variables(self):
"""Retrieve loop variables from context."""
return (
self.ctx.get(f"{self._id}_data", []),
self.ctx.get(f"{self._id}_index", 0),
)

def aggregated_output(self) -> Data:
"""Return the aggregated list once all items are processed."""
self.initialize_data()

Expand All @@ -74,14 +102,8 @@ def done_output(self) -> Data:
aggregated = self.ctx.get(f"{self._id}_aggregated", [])

# Check if loop input is provided and append to aggregated list
if self.loop_input is not None:
if self.loop_input is not None and not isinstance(self.loop_input, str) and len(aggregated) <= len(data_list):
aggregated.append(self.loop_input)
self.update_ctx({f"{self._id}_aggregated": aggregated})

# Check if aggregation is complete
if len(aggregated) >= len(data_list):
return aggregated

# Not all items have been processed yet
self.stop("done")
return None
return aggregated
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,21 @@ def stop(self, output_name: str | None = None) -> None:
msg = f"Error stopping {self.display_name}: {e}"
raise ValueError(msg) from e

def start(self, output_name: str | None = None) -> None:
if not output_name and self._vertex and len(self._vertex.outputs) == 1:
output_name = self._vertex.outputs[0]["name"]
elif not output_name:
msg = "You must specify an output name to call start"
raise ValueError(msg)
if not self._vertex:
msg = "Vertex is not set"
raise ValueError(msg)
try:
self.graph.mark_branch(vertex_id=self._vertex.id, output_name=output_name, state="ACTIVE")
except Exception as e:
msg = f"Error starting {self.display_name}: {e}"
raise ValueError(msg) from e

def append_state(self, name: str, value: Any) -> None:
if not self._vertex:
msg = "Vertex is not set"
Expand Down
7 changes: 7 additions & 0 deletions src/backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def pytest_configure(config):
pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json"
pytest.SIMPLE_API_TEST = data_path / "SimpleAPITest.json"
pytest.MEMORY_CHATBOT_NO_LLM = data_path / "MemoryChatbotNoLLM.json"
pytest.LOOP_TEST = data_path / "LoopTest.json"
pytest.CODE_WITH_SYNTAX_ERROR = """
def get_text():
retun "Hello World"
Expand All @@ -121,6 +122,7 @@ def get_text():
pytest.TWO_OUTPUTS,
pytest.VECTOR_STORE_PATH,
pytest.MEMORY_CHATBOT_NO_LLM,
pytest.LOOP_TEST,
]:
assert path.exists(), f"File {path} does not exist. Available files: {list(data_path.iterdir())}"

Expand Down Expand Up @@ -324,6 +326,11 @@ def json_memory_chatbot_no_llm():
return pytest.MEMORY_CHATBOT_NO_LLM.read_text(encoding="utf-8")


@pytest.fixture
def json_loop_test():
return pytest.LOOP_TEST.read_text(encoding="utf-8")


@pytest.fixture(autouse=True)
def deactivate_tracing(monkeypatch):
monkeypatch.setenv("LANGFLOW_DEACTIVATE_TRACING", "true")
Expand Down
Loading
Loading