fix: loop variable not accessible error (#7501)
* tests cases
* update to loop
* Update component.py
* 📝 (LoopTemplate.json): update value of a configuration key from "OPENAI_API_KEY" to "ANTHROPIC_API_KEY" in order to reflect the correct API key being used
* update json test loop
* fix: update test URL in loop-component.spec.ts to reflect correct reference
Changed the URL in the test case from "Artificial_intelligence" to "Human_intelligence" to ensure accurate testing of the loop component functionality.
* update FE tests
* [autofix.ci] apply automated fixes
---------
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
b5e33cea54
commit
e135b7f341
9 changed files with 1537 additions and 1396 deletions
|
|
@ -105,7 +105,7 @@ class LoopComponent(Component):
|
|||
aggregated = self.ctx.get(f"{self._id}_aggregated", [])
|
||||
|
||||
# Check if loop input is provided and append to aggregated list
|
||||
if self.data is not None and not isinstance(self.data, str) and len(aggregated) <= len(data_list):
|
||||
aggregated.append(self.data)
|
||||
if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list):
|
||||
aggregated.append(self.item)
|
||||
self.update_ctx({f"{self._id}_aggregated": aggregated})
|
||||
return aggregated
|
||||
|
|
|
|||
|
|
@ -720,6 +720,7 @@ class Component(CustomComponent):
|
|||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]:
|
||||
# It is a dict of attributes that are not inputs or outputs all the raw data it should have the loop input.
|
||||
return self.__dict__["_attributes"][name]
|
||||
if "_inputs" in self.__dict__ and name in self.__dict__["_inputs"]:
|
||||
return self.__dict__["_inputs"][name].value
|
||||
|
|
|
|||
|
|
@ -307,6 +307,8 @@ class Vertex:
|
|||
else:
|
||||
params[param_key] = self.graph.get_vertex(edge.source_id)
|
||||
elif param_key in self.output_names:
|
||||
# if the loop is run the param_key item will be set over here
|
||||
# validate the edge
|
||||
params[param_key] = self.graph.get_vertex(edge.source_id)
|
||||
return params
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,11 @@ class ParameterHandler:
|
|||
params[param_key].append(self.vertex.graph.get_vertex(edge.source_id))
|
||||
else:
|
||||
params[param_key] = self.process_non_list_edge_param(field, edge)
|
||||
elif param_key in self.vertex.output_names:
|
||||
# If the param_key is in the output_names, it means that the loop is run
|
||||
# if the loop is run the param_key item will be set over here
|
||||
# validate the edge
|
||||
params[param_key] = self.vertex.graph.get_vertex(edge.source_id)
|
||||
return params
|
||||
|
||||
def process_non_list_edge_param(self, field: dict, edge: CycleEdge) -> Any:
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@
|
|||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.io import DataInput, Output\nfrom langflow.schema import Data\n\n\nclass LoopComponent(Component):\n display_name = \"Loop\"\n description = (\n \"Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.\"\n )\n icon = \"infinity\"\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The initial list of Data objects to iterate over.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Item\", name=\"item\", method=\"item_output\", allows_loop=True),\n Output(display_name=\"Done\", name=\"done\", method=\"done_output\"),\n ]\n\n def initialize_data(self) -> None:\n \"\"\"Initialize the data list, context index, and aggregated list.\"\"\"\n if self.ctx.get(f\"{self._id}_initialized\", False):\n return\n\n # Ensure data is a list of Data objects\n data_list = self._validate_data(self.data)\n\n # Store the initial data and context variables\n self.update_ctx(\n {\n f\"{self._id}_data\": data_list,\n f\"{self._id}_index\": 0,\n f\"{self._id}_aggregated\": [],\n f\"{self._id}_initialized\": True,\n }\n )\n\n def _validate_data(self, data):\n \"\"\"Validate and return a list of Data objects.\"\"\"\n if isinstance(data, Data):\n return [data]\n if isinstance(data, list) and all(isinstance(item, Data) for item in data):\n return data\n msg = \"The 'data' input must be a list of Data objects or a single Data object.\"\n raise TypeError(msg)\n\n def evaluate_stop_loop(self) -> bool:\n \"\"\"Evaluate whether to stop item or done output.\"\"\"\n current_index = self.ctx.get(f\"{self._id}_index\", 0)\n data_length = len(self.ctx.get(f\"{self._id}_data\", []))\n return current_index > data_length\n\n def item_output(self) -> Data:\n \"\"\"Output the next item in the list or stop if done.\"\"\"\n self.initialize_data()\n current_item = Data(text=\"\")\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n return Data(text=\"\")\n\n # Get data list and current index\n data_list, current_index = self.loop_variables()\n if current_index < len(data_list):\n # Output current item and increment index\n try:\n current_item = data_list[current_index]\n except IndexError:\n current_item = Data(text=\"\")\n self.aggregated_output()\n self.update_ctx({f\"{self._id}_index\": current_index + 1})\n return current_item\n\n def done_output(self) -> Data:\n \"\"\"Trigger the done output when iteration is complete.\"\"\"\n self.initialize_data()\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n self.start(\"done\")\n\n return self.ctx.get(f\"{self._id}_aggregated\", [])\n self.stop(\"done\")\n return Data(text=\"\")\n\n def loop_variables(self):\n \"\"\"Retrieve loop variables from context.\"\"\"\n return (\n self.ctx.get(f\"{self._id}_data\", []),\n self.ctx.get(f\"{self._id}_index\", 0),\n )\n\n def aggregated_output(self) -> Data:\n \"\"\"Return the aggregated list once all items are processed.\"\"\"\n self.initialize_data()\n\n # Get data list and aggregated list\n data_list = self.ctx.get(f\"{self._id}_data\", [])\n aggregated = self.ctx.get(f\"{self._id}_aggregated\", [])\n\n # Check if loop input is provided and append to aggregated list\n if self.data is not None and not isinstance(self.data, str) and len(aggregated) <= len(data_list):\n aggregated.append(self.data)\n self.update_ctx({f\"{self._id}_aggregated\": aggregated})\n return aggregated\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.io import DataInput, Output\nfrom langflow.schema import Data\n\n\nclass LoopComponent(Component):\n display_name = \"Loop\"\n description = (\n \"Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.\"\n )\n icon = \"infinity\"\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The initial list of Data objects to iterate over.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Item\", name=\"item\", method=\"item_output\", allows_loop=True),\n Output(display_name=\"Done\", name=\"done\", method=\"done_output\"),\n ]\n\n def initialize_data(self) -> None:\n \"\"\"Initialize the data list, context index, and aggregated list.\"\"\"\n if self.ctx.get(f\"{self._id}_initialized\", False):\n return\n\n # Ensure data is a list of Data objects\n data_list = self._validate_data(self.data)\n\n # Store the initial data and context variables\n self.update_ctx(\n {\n f\"{self._id}_data\": data_list,\n f\"{self._id}_index\": 0,\n f\"{self._id}_aggregated\": [],\n f\"{self._id}_initialized\": True,\n }\n )\n\n def _validate_data(self, data):\n \"\"\"Validate and return a list of Data objects.\"\"\"\n if isinstance(data, Data):\n return [data]\n if isinstance(data, list) and all(isinstance(item, Data) for item in data):\n return data\n msg = \"The 'data' input must be a list of Data objects or a single Data object.\"\n raise TypeError(msg)\n\n def evaluate_stop_loop(self) -> bool:\n \"\"\"Evaluate whether to stop item or done output.\"\"\"\n current_index = self.ctx.get(f\"{self._id}_index\", 0)\n data_length = len(self.ctx.get(f\"{self._id}_data\", []))\n return current_index > data_length\n\n def item_output(self) -> Data:\n \"\"\"Output the next item in the list or stop if done.\"\"\"\n self.initialize_data()\n current_item = Data(text=\"\")\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n return Data(text=\"\")\n\n # Get data list and current index\n data_list, current_index = self.loop_variables()\n if current_index < len(data_list):\n # Output current item and increment index\n try:\n current_item = data_list[current_index]\n except IndexError:\n current_item = Data(text=\"\")\n self.aggregated_output()\n self.update_ctx({f\"{self._id}_index\": current_index + 1})\n return current_item\n\n def done_output(self) -> Data:\n \"\"\"Trigger the done output when iteration is complete.\"\"\"\n self.initialize_data()\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n self.start(\"done\")\n\n return self.ctx.get(f\"{self._id}_aggregated\", [])\n self.stop(\"done\")\n return Data(text=\"\")\n\n def loop_variables(self):\n \"\"\"Retrieve loop variables from context.\"\"\"\n return (\n self.ctx.get(f\"{self._id}_data\", []),\n self.ctx.get(f\"{self._id}_index\", 0),\n )\n\n def aggregated_output(self) -> Data:\n \"\"\"Return the aggregated list once all items are processed.\"\"\"\n self.initialize_data()\n\n # Get data list and aggregated list\n data_list = self.ctx.get(f\"{self._id}_data\", [])\n aggregated = self.ctx.get(f\"{self._id}_aggregated\", [])\n\n # Check if loop input is provided and append to aggregated list\n if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list):\n aggregated.append(self.item)\n self.update_ctx({f\"{self._id}_aggregated\": aggregated})\n return aggregated\n"
|
||||
},
|
||||
"data": {
|
||||
"_input_type": "DataInput",
|
||||
|
|
@ -595,7 +595,7 @@
|
|||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": "OPENAI_API_KEY"
|
||||
"value": "ANTHROPIC_API_KEY"
|
||||
},
|
||||
"base_url": {
|
||||
"_input_type": "MessageTextInput",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -75,7 +75,6 @@ test(
|
|||
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
|
||||
await expect(page.getByTestId("outputsChat Output")).toBeVisible();
|
||||
await expect(page.getByTestId("promptsPrompt")).toBeVisible();
|
||||
await expect(page.getByTestId("modelsAmazon Bedrock")).toBeVisible();
|
||||
await expect(page.getByTestId("helpersMessage History")).toBeVisible();
|
||||
await expect(page.getByTestId("langchain_utilitiesCSVAgent")).toBeVisible();
|
||||
await expect(
|
||||
|
|
@ -101,7 +100,6 @@ test(
|
|||
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
|
||||
await expect(page.getByTestId("outputsChat Output")).not.toBeVisible();
|
||||
await expect(page.getByTestId("promptsPrompt")).not.toBeVisible();
|
||||
await expect(page.getByTestId("modelsAmazon Bedrock")).not.toBeVisible();
|
||||
await expect(page.getByTestId("helpersMessage History")).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("agentsTool Calling Agent"),
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ test(
|
|||
const elementTestIds = [
|
||||
"outputsChat Output",
|
||||
"dataAPI Request",
|
||||
"modelsAmazon Bedrock",
|
||||
"vectorstoresAstra DB",
|
||||
"embeddingsAmazon Bedrock Embeddings",
|
||||
"langchain_utilitiesTool Calling Agent",
|
||||
|
|
@ -97,7 +96,6 @@ test(
|
|||
|
||||
const visibleModelSpecsTestIds = [
|
||||
"modelsAIML",
|
||||
"modelsAmazon Bedrock",
|
||||
"modelsAnthropic",
|
||||
"modelsAzure OpenAI",
|
||||
"modelsCohere",
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ test(
|
|||
.fill("https://en.wikipedia.org/wiki/Artificial_intelligence");
|
||||
await page
|
||||
.getByTestId("inputlist_str_urls_1")
|
||||
.fill("https://en.wikipedia.org/wiki/Artificial_intelligence");
|
||||
.fill("https://en.wikipedia.org/wiki/Human_intelligence");
|
||||
|
||||
await page.getByTestId("div-generic-node").nth(2).click();
|
||||
await page.getByTestId("int_int_number_of_fields").fill("1");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue