From a4a70d4aea503309baad9c329eb28c5af1ca1559 Mon Sep 17 00:00:00 2001 From: Mendon Kissling <59585235+mendonk@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:59:12 -0500 Subject: [PATCH 1/6] docs: ui improvements (#6317) * use-yarn-not-npm * swap-api-menu-items * fix-errors * force-sidebar-open * increase-custom-css-values --- DEVELOPMENT.md | 4 ++-- docs/css/custom.css | 14 +++++++------- docs/docs/Components/components-data.md | 2 +- docs/docs/Components/components-processing.md | 4 ++-- docs/docs/Get-Started/get-started-quickstart.md | 2 +- docs/docusaurus.config.js | 4 ++-- docs/sidebars.js | 10 +++++----- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d0c76c7d80..f33fdd9e17 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -168,8 +168,8 @@ In the *Documentation Terminal* (from the project root directory), run the follo ```bash cd docs -npm install -npm run start +yarn install +yarn start ``` If the frontend service is running on port `3000` you might be prompted `Would you like to run the app on another port instead?`, in which case answer "yes". You will get output similar to: diff --git a/docs/css/custom.css b/docs/css/custom.css index 4f24ebc444..107fbc659f 100644 --- a/docs/css/custom.css +++ b/docs/css/custom.css @@ -205,32 +205,32 @@ body { height: auto; display: block; margin: 1rem auto; - max-height: 500px; + max-height: 600px; object-fit: contain; } /* Default size for most images */ .markdown img:not(.resized-image) { - max-width: 500px; + max-width: 600px; width: auto; } .resized-image { - width: 250px; - max-height: 400px; + width: 300px; + max-height: 500px; } /* Responsive images on mobile devices */ @media (max-width: 768px) { .markdown img:not(.resized-image) { max-width: 100%; - max-height: 400px; + max-height: 500px; } .resized-image { width: 100%; - max-width: 250px; - max-height: 300px; + max-width: 300px; + max-height: 400px; } } diff --git a/docs/docs/Components/components-data.md b/docs/docs/Components/components-data.md index 624eba31f4..d756fe31c5 100644 --- a/docs/docs/Components/components-data.md +++ b/docs/docs/Components/components-data.md @@ -210,7 +210,7 @@ curl -X POST \ To test the webhook component: 1. Add a **Webhook** component to the flow. -2. Connect the **Webhook** component's **Data** output to the **Data** input of a [Data to Message](/components-processing#parse-data-data-to-message) component. +2. Connect the **Webhook** component's **Data** output to the **Data** input of a [Data to Message](/components-processing#data-to-message) component. 3. Connect the **Data to Message** component's **Message** output to the **Text** input of a [Chat Output](/components-io#chat-output) component. 4. To send a POST request, copy the code from the **Webhook cURL** tab in the **API** pane and paste it into a terminal. 5. Send the POST request. diff --git a/docs/docs/Components/components-processing.md b/docs/docs/Components/components-processing.md index f3e26560a9..de1bf373dd 100644 --- a/docs/docs/Components/components-processing.md +++ b/docs/docs/Components/components-processing.md @@ -17,7 +17,7 @@ The component offers control over chunk size, overlap, and separator, which affe ## Alter metadata -This component modifies metadata of input objects. It can add new metadata, update existing metadata, and remove specified metadata fields. The component works with both [Message](/concepts-objects#message-object) and [Data](/concepts-objects) objects, and can also create a new Data object from user-provided text. +This component modifies metadata of input objects. It can add new metadata, update existing metadata, and remove specified metadata fields. The component works with both [Message](/concepts-objects#message-object) and [Data](/concepts-objects#data-object) objects, and can also create a new Data object from user-provided text. ### Inputs @@ -164,7 +164,7 @@ This component transforms structured data into human-readable text formats, allo This component is in **Beta** as of Langflow version 1.1.3, and is not yet fully supported. ::: -This component filters a [Data](/concepts-objects#data-objects) object based on a list of keys. +This component filters a [Data](/concepts-objects#data-object) object based on a list of keys. ### Inputs diff --git a/docs/docs/Get-Started/get-started-quickstart.md b/docs/docs/Get-Started/get-started-quickstart.md index 10f6ad659b..83602540b3 100644 --- a/docs/docs/Get-Started/get-started-quickstart.md +++ b/docs/docs/Get-Started/get-started-quickstart.md @@ -123,7 +123,7 @@ The [File](/components-data#file) component loads files from your local machine. 3. Click **Processing**, select the **Split Text** component, and then drag it to the canvas. The [Split Text](/components-processing#split-text) component splits the loaded text into smaller chunks. 4. Click **Processing**, select the **Parse Data** component, and then drag it to the canvas. -The [Parse Data](/components-processing#parse-data) component converts the data from the **Astra DB** component into plain text. +The [Data to Message](/components-processing#data-to-message) component converts the data from the **Astra DB** component into plain text. 5. Click **Embeddings**, select the **OpenAI Embeddings** component, and then drag it to the canvas. The [OpenAI Embeddings](/components-embedding-models#openai-embeddings) component generates embeddings for the user's input, which are compared to the vector data in the database. 6. Connect the new components into the existing flow, so your flow looks like this: diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index e1179c543f..ff34867689 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -40,7 +40,7 @@ const config = { docs: { routeBasePath: "/", // Serve the docs at the site's root sidebarPath: require.resolve("./sidebars.js"), // Use sidebars.js file - sidebarCollapsed: true, + sidebarCollapsed: false, beforeDefaultRemarkPlugins: [ [ remarkCodeHike, @@ -255,7 +255,7 @@ const config = { }, docs: { sidebar: { - hideable: true, + hideable: false, }, }, }), diff --git a/docs/sidebars.js b/docs/sidebars.js index dd02a6cf31..bd0c38f40e 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -140,16 +140,16 @@ module.exports = { type: "category", label: "API reference", items: [ - { - type: "doc", - id: "API-Reference/api-reference-api-examples", - label: "API examples", - }, { type: "link", label: "API documentation", href: "/api", }, + { + type: "doc", + id: "API-Reference/api-reference-api-examples", + label: "API examples", + }, ], }, { From b38b6aa6013d4b9f01e623709040e1021c9152b1 Mon Sep 17 00:00:00 2001 From: Edwin Jose Date: Thu, 13 Feb 2025 15:49:47 -0500 Subject: [PATCH 2/6] fix: inconsistent text table result for Message Type output, setting message.text as default status instead of Table as self.status (#6319) * update message output to display only the text * Update component.py * Update component.py * [autofix.ci] apply automated fixes * Update src/backend/base/langflow/custom/custom_component/component.py Co-authored-by: Gabriel Luiz Freitas Almeida --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida --- .../base/langflow/custom/custom_component/component.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index b43bef9f0d..6e40bcea55 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -981,17 +981,23 @@ class Component(CustomComponent): return {"repr": custom_repr, "raw": raw, "type": artifact_type} def _process_raw_result(self, result): - if len(self.outputs) == 1: - return self.status or self.extract_data(result) + """Process the raw result of the component.""" return self.extract_data(result) def extract_data(self, result): + """Extract the data from the result. this is where the self.status is set.""" + if isinstance(result, Message): + self.status = result.get_text() + return ( + self.status if self.status is not None else "No text available" + ) # Provide a default message if .text_key is missing if hasattr(result, "data"): return result.data if hasattr(result, "model_dump"): return result.model_dump() if isinstance(result, Data | dict | str): return result.data if isinstance(result, Data) else result + if self.status: return self.status return result From 79f91e3ac1de42673663763edc2415a68fb16fc7 Mon Sep 17 00:00:00 2001 From: Edwin Jose Date: Thu, 13 Feb 2025 16:16:53 -0500 Subject: [PATCH 3/6] feat: add template for loop component: ArXiv search result Translator (#6260) * Create LoopTemplate.json * Update LoopTemplate.json * refactor: Remove unused tag "all-templates" from LoopTemplate.json * refactor: Update LoopTemplate.json description and adjust dimensions for improved clarity * add-title --------- Co-authored-by: anovazzi1 Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com> --- .../starter_projects/LoopTemplate.json | 1926 +++++++++++++++++ 1 file changed, 1926 insertions(+) create mode 100644 src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json diff --git a/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json new file mode 100644 index 0000000000..de4abf151a --- /dev/null +++ b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json @@ -0,0 +1,1926 @@ +{ + "data": { + "edges": [ + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ArXivComponent", + "id": "ArXivComponent-LChQN", + "name": "papers", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "LoopComponent-3vpc1", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__ArXivComponent-LChQN{œdataTypeœ:œArXivComponentœ,œidœ:œArXivComponent-LChQNœ,œnameœ:œpapersœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œfieldNameœ:œdataœ,œidœ:œLoopComponent-3vpc1œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "ArXivComponent-LChQN", + "sourceHandle": "{œdataTypeœ: œArXivComponentœ, œidœ: œArXivComponent-LChQNœ, œnameœ: œpapersœ, œoutput_typesœ: [œDataœ]}", + "target": "LoopComponent-3vpc1", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œLoopComponent-3vpc1œ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-3vpc1", + "name": "item", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "ParseData-Pf12J", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}-ParseData-Pf12J{œfieldNameœ:œdataœ,œidœ:œParseData-Pf12Jœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "LoopComponent-3vpc1", + "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-Pf12J", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-Pf12Jœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-Pf12J", + "name": "text", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "AnthropicModel-beO6B", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ParseData-Pf12J{œdataTypeœ:œParseDataœ,œidœ:œParseData-Pf12Jœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-beO6B{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-beO6Bœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ParseData-Pf12J", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-Pf12Jœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "AnthropicModel-beO6B", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-beO6Bœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "AnthropicModel", + "id": "AnthropicModel-beO6B", + "name": "text_output", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "message", + "id": "MessagetoData-QRSBb", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__AnthropicModel-beO6B{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-beO6Bœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-QRSBb{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-QRSBbœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "AnthropicModel-beO6B", + "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-beO6Bœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "MessagetoData-QRSBb", + "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œMessagetoData-QRSBbœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "MessagetoData", + "id": "MessagetoData-QRSBb", + "name": "data", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-3vpc1", + "name": "item", + "output_types": [ + "Data" + ] + } + }, + "id": "xy-edge__MessagetoData-QRSBb{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-QRSBbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", + "source": "MessagetoData-QRSBb", + "sourceHandle": "{œdataTypeœ: œMessagetoDataœ, œidœ: œMessagetoData-QRSBbœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", + "target": "LoopComponent-3vpc1", + "targetHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-3vpc1", + "name": "done", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "ParseData-igEkj", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}-ParseData-igEkj{œfieldNameœ:œdataœ,œidœ:œParseData-igEkjœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "LoopComponent-3vpc1", + "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œdoneœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-igEkj", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-igEkjœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-igEkj", + "name": "text", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-UZgon", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ParseData-igEkj{œdataTypeœ:œParseDataœ,œidœ:œParseData-igEkjœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-UZgon{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-UZgonœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ParseData-igEkj", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-igEkjœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-UZgon", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-UZgonœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ChatInput", + "id": "ChatInput-m10vc", + "name": "message", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "search_query", + "id": "ArXivComponent-LChQN", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ChatInput-m10vc{œdataTypeœ:œChatInputœ,œidœ:œChatInput-m10vcœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ArXivComponent-LChQN{œfieldNameœ:œsearch_queryœ,œidœ:œArXivComponent-LChQNœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ChatInput-m10vc", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-m10vcœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ArXivComponent-LChQN", + "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œArXivComponent-LChQNœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + } + ], + "nodes": [ + { + "data": { + "id": "ArXivComponent-LChQN", + "node": { + "base_classes": [ + "Data" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Search and retrieve papers from arXiv.org", + "display_name": "arXiv", + "documentation": "", + "edited": false, + "field_order": [ + "search_query", + "search_type", + "max_results" + ], + "frozen": false, + "icon": "arXiv", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Papers", + "method": "search_papers", + "name": "papers", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import urllib.request\nfrom urllib.parse import urlparse\nfrom xml.etree.ElementTree import Element\n\nfrom defusedxml.ElementTree import fromstring\n\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, Output\nfrom langflow.schema import Data\n\n\nclass ArXivComponent(Component):\n display_name = \"arXiv\"\n description = \"Search and retrieve papers from arXiv.org\"\n icon = \"arXiv\"\n\n inputs = [\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"The search query for arXiv papers (e.g., 'quantum computing')\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Field\",\n info=\"The field to search in\",\n options=[\"all\", \"title\", \"abstract\", \"author\", \"cat\"], # cat is for category\n value=\"all\",\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"Maximum number of results to return\",\n value=10,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Papers\", name=\"papers\", method=\"search_papers\"),\n ]\n\n def build_query_url(self) -> str:\n \"\"\"Build the arXiv API query URL.\"\"\"\n base_url = \"http://export.arxiv.org/api/query?\"\n\n # Build the search query\n search_query = f\"{self.search_type}:{self.search_query}\"\n\n # URL parameters\n params = {\n \"search_query\": search_query,\n \"max_results\": str(self.max_results),\n }\n\n # Convert params to URL query string\n query_string = \"&\".join([f\"{k}={urllib.parse.quote(str(v))}\" for k, v in params.items()])\n\n return base_url + query_string\n\n def parse_atom_response(self, response_text: str) -> list[dict]:\n \"\"\"Parse the Atom XML response from arXiv.\"\"\"\n # Parse XML safely using defusedxml\n root = fromstring(response_text)\n\n # Define namespace dictionary for XML parsing\n ns = {\"atom\": \"http://www.w3.org/2005/Atom\", \"arxiv\": \"http://arxiv.org/schemas/atom\"}\n\n papers = []\n # Process each entry (paper)\n for entry in root.findall(\"atom:entry\", ns):\n paper = {\n \"id\": self._get_text(entry, \"atom:id\", ns),\n \"title\": self._get_text(entry, \"atom:title\", ns),\n \"summary\": self._get_text(entry, \"atom:summary\", ns),\n \"published\": self._get_text(entry, \"atom:published\", ns),\n \"updated\": self._get_text(entry, \"atom:updated\", ns),\n \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n \"arxiv_url\": self._get_link(entry, \"alternate\", ns),\n \"pdf_url\": self._get_link(entry, \"related\", ns),\n \"comment\": self._get_text(entry, \"arxiv:comment\", ns),\n \"journal_ref\": self._get_text(entry, \"arxiv:journal_ref\", ns),\n \"primary_category\": self._get_category(entry, ns),\n \"categories\": [cat.get(\"term\") for cat in entry.findall(\"atom:category\", ns)],\n }\n papers.append(paper)\n\n return papers\n\n def _get_text(self, element: Element, path: str, ns: dict) -> str | None:\n \"\"\"Safely extract text from an XML element.\"\"\"\n el = element.find(path, ns)\n return el.text.strip() if el is not None and el.text else None\n\n def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:\n \"\"\"Get link URL based on relation type.\"\"\"\n for link in element.findall(\"atom:link\", ns):\n if link.get(\"rel\") == rel:\n return link.get(\"href\")\n return None\n\n def _get_category(self, element: Element, ns: dict) -> str | None:\n \"\"\"Get primary category.\"\"\"\n cat = element.find(\"arxiv:primary_category\", ns)\n return cat.get(\"term\") if cat is not None else None\n\n def search_papers(self) -> list[Data]:\n \"\"\"Search arXiv and return results.\"\"\"\n try:\n # Build the query URL\n url = self.build_query_url()\n\n # Validate URL scheme and host\n parsed_url = urlparse(url)\n if parsed_url.scheme not in (\"http\", \"https\"):\n error_msg = f\"Invalid URL scheme: {parsed_url.scheme}\"\n raise ValueError(error_msg)\n if parsed_url.hostname != \"export.arxiv.org\":\n error_msg = f\"Invalid host: {parsed_url.hostname}\"\n raise ValueError(error_msg)\n\n # Create a custom opener that only allows http/https schemes\n class RestrictedHTTPHandler(urllib.request.HTTPHandler):\n def http_open(self, req):\n return super().http_open(req)\n\n class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):\n def https_open(self, req):\n return super().https_open(req)\n\n # Build opener with restricted handlers\n opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)\n urllib.request.install_opener(opener)\n\n # Make the request with validated URL using restricted opener\n response = opener.open(url)\n response_text = response.read().decode(\"utf-8\")\n\n # Parse the response\n papers = self.parse_atom_response(response_text)\n\n # Convert to Data objects\n results = [Data(data=paper) for paper in papers]\n self.status = results\n except (urllib.error.URLError, ValueError) as e:\n error_data = Data(data={\"error\": f\"Request error: {e!s}\"})\n self.status = error_data\n return [error_data]\n else:\n return results\n" + }, + "max_results": { + "_input_type": "IntInput", + "advanced": false, + "display_name": "Max Results", + "dynamic": false, + "info": "Maximum number of results to return", + "list": false, + "list_add_label": "Add More", + "name": "max_results", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, + "search_query": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Search Query", + "dynamic": false, + "info": "The search query for arXiv papers (e.g., 'quantum computing')", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "search_query", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "search_type": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Search Field", + "dynamic": false, + "info": "The field to search in", + "name": "search_type", + "options": [ + "all", + "title", + "abstract", + "author", + "cat" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "all" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ArXivComponent" + }, + "dragging": false, + "id": "ArXivComponent-LChQN", + "measured": { + "height": 395, + "width": 320 + }, + "position": { + "x": 81.59312530546094, + "y": 3.9397854556273906 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "LoopComponent-3vpc1", + "node": { + "base_classes": [ + "Data" + ], + "beta": false, + "category": "logic", + "conditional_paths": [], + "custom_fields": {}, + "description": "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.", + "display_name": "Loop", + "documentation": "", + "edited": false, + "field_order": [ + "data" + ], + "frozen": false, + "icon": "infinity", + "key": "LoopComponent", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": true, + "cache": true, + "display_name": "Item", + "method": "item_output", + "name": "item", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Done", + "method": "done_output", + "name": "done", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 2.220446049250313e-16, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "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.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", + "advanced": false, + "display_name": "Data", + "dynamic": false, + "info": "The initial list of Data objects to iterate over.", + "input_types": [ + "Data" + ], + "list": false, + "list_add_label": "Add More", + "name": "data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "LoopComponent" + }, + "dragging": false, + "id": "LoopComponent-3vpc1", + "measured": { + "height": 280, + "width": 320 + }, + "position": { + "x": 517.2087344858119, + "y": 55.497845490859035 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ParseData-Pf12J", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert Data objects into Messages using any {field_name} from input data.", + "display_name": "Data to Message", + "documentation": "", + "edited": false, + "field_order": [ + "data", + "template", + "sep" + ], + "frozen": false, + "icon": "message-square", + "key": "ParseData", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "parse_data", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Data List", + "method": "parse_data_as_list", + "name": "data_list", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.01857804455091699, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" + }, + "data": { + "_input_type": "DataInput", + "advanced": false, + "display_name": "Data", + "dynamic": false, + "info": "The data to convert to text.", + "input_types": [ + "Data" + ], + "list": true, + "list_add_label": "Add More", + "name": "data", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "sep": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Separator", + "dynamic": false, + "info": "", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sep", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n" + }, + "template": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Template", + "dynamic": false, + "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{data}" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ParseData" + }, + "dragging": false, + "id": "ParseData-Pf12J", + "measured": { + "height": 342, + "width": 320 + }, + "position": { + "x": 996.5733307455723, + "y": 35.284413822605146 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "AnthropicModel-beO6B", + "node": { + "base_classes": [ + "LanguageModel", + "Message" + ], + "beta": false, + "category": "models", + "conditional_paths": [], + "custom_fields": {}, + "description": "Generate text using Anthropic Chat&Completion LLMs with prefill support.", + "display_name": "Anthropic", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "system_message", + "stream", + "max_tokens", + "model_name", + "api_key", + "temperature", + "base_url", + "tool_model_enabled", + "prefill" + ], + "frozen": false, + "icon": "Anthropic", + "key": "AnthropicModel", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "text_response", + "name": "text_output", + "required_inputs": [], + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Language Model", + "method": "build_model", + "name": "model_output", + "required_inputs": [ + "api_key" + ], + "selected": "LanguageModel", + "tool_mode": true, + "types": [ + "LanguageModel" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.0005851173668140926, + "template": { + "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": false, + "display_name": "Anthropic API Key", + "dynamic": false, + "info": "Your Anthropic API key.", + "input_types": [ + "Message" + ], + "load_from_db": true, + "name": "api_key", + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": true, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "base_url": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Anthropic API URL", + "dynamic": false, + "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "base_url", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "https://api.anthropic.com" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from typing import Any\n\nimport requests\nfrom loguru import logger\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic Chat&Completion LLMs with prefill support.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=\"https://api.anthropic.com\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"prefill\", display_name=\"Prefill\", info=\"Prefill text to guide the model's response.\", advanced=True\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens_to_sample=self.max_tokens,\n temperature=self.temperature,\n anthropic_api_url=self.base_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n for model in model_ids:\n model_with_tool = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n anthropic_api_url=self.base_url,\n )\n if not self.supports_tool_calling(model_with_tool):\n model_ids.remove(model)\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if field_name in (\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\") and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"][\"value\"] = ids[0]\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Input", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "max_tokens": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Tokens", + "dynamic": false, + "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", + "list": false, + "list_add_label": "Add More", + "name": "max_tokens", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 4096 + }, + "model_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": true, + "dialog_inputs": {}, + "display_name": "Model Name", + "dynamic": false, + "info": "", + "name": "model_name", + "options": [ + "claude-3-5-sonnet-latest", + "claude-3-5-haiku-latest", + "claude-3-opus-latest", + "claude-3-5-sonnet-20240620", + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307" + ], + "options_metadata": [], + "placeholder": "", + "refresh_button": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "claude-3-5-sonnet-20241022" + }, + "prefill": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Prefill", + "dynamic": false, + "info": "Prefill text to guide the model's response.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "prefill", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "stream": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Stream", + "dynamic": false, + "info": "Stream the response from the model. Streaming works only in Chat.", + "list": false, + "list_add_label": "Add More", + "name": "stream", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "system_message": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "System Message", + "dynamic": false, + "info": "System message to pass to the model.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "system_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "Translate to Portuguese and output in structured format\nReturn only the JSON and no additional text." + }, + "temperature": { + "_input_type": "SliderInput", + "advanced": false, + "display_name": "Temperature", + "dynamic": false, + "info": "Run inference with this temperature. Must by in the closed interval [0.0, 1.0].", + "max_label": "", + "max_label_icon": "", + "min_label": "", + "min_label_icon": "", + "name": "temperature", + "placeholder": "", + "range_spec": { + "max": 1, + "min": 0, + "step": 0.01, + "step_type": "float" + }, + "required": false, + "show": true, + "slider_buttons": false, + "slider_buttons_options": [], + "slider_input": false, + "title_case": false, + "tool_mode": false, + "type": "slider", + "value": 0.1 + }, + "tool_model_enabled": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Enable Tool Models", + "dynamic": false, + "info": "Select if you want to use models that can work with tools. If yes, only those models will be shown.", + "list": false, + "list_add_label": "Add More", + "name": "tool_model_enabled", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "AnthropicModel" + }, + "dragging": false, + "id": "AnthropicModel-beO6B", + "measured": { + "height": 801, + "width": 320 + }, + "position": { + "x": 1493.0259340837038, + "y": -225.9942853677759 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "MessagetoData-QRSBb", + "node": { + "base_classes": [ + "Data" + ], + "beta": true, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert a Message object to a Data object", + "display_name": "Message to Data", + "documentation": "", + "edited": false, + "field_order": [ + "message" + ], + "frozen": false, + "icon": "message-square-share", + "key": "MessagetoData", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Data", + "method": "convert_message_to_data", + "name": "data", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.008222426499470714, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import MessageInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass MessageToDataComponent(Component):\n display_name = \"Message to Data\"\n description = \"Convert a Message object to a Data object\"\n icon = \"message-square-share\"\n beta = True\n name = \"MessagetoData\"\n\n inputs = [\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The Message object to convert to a Data object\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"convert_message_to_data\"),\n ]\n\n def convert_message_to_data(self) -> Data:\n if isinstance(self.message, Message):\n # Convert Message to Data\n return Data(data=self.message.data)\n\n msg = \"Error converting Message to Data: Input must be a Message object\"\n logger.opt(exception=True).debug(msg)\n self.status = msg\n return Data(data={\"error\": msg})\n" + }, + "message": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Message", + "dynamic": false, + "info": "The Message object to convert to a Data object", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "MessagetoData" + }, + "dragging": false, + "id": "MessagetoData-QRSBb", + "measured": { + "height": 230, + "width": 320 + }, + "position": { + "x": 1863.8805295746977, + "y": 440.56780240629814 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ParseData-igEkj", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert Data objects into Messages using any {field_name} from input data.", + "display_name": "Data to Message", + "documentation": "", + "edited": false, + "field_order": [ + "data", + "template", + "sep" + ], + "frozen": false, + "icon": "message-square", + "key": "ParseData", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "parse_data", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Data List", + "method": "parse_data_as_list", + "name": "data_list", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.008664293911514902, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" + }, + "data": { + "_input_type": "DataInput", + "advanced": false, + "display_name": "Data", + "dynamic": false, + "info": "The data to convert to text.", + "input_types": [ + "Data" + ], + "list": true, + "list_add_label": "Add More", + "name": "data", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "sep": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Separator", + "dynamic": false, + "info": "", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sep", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n" + }, + "template": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Template", + "dynamic": false, + "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{text}" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ParseData" + }, + "dragging": false, + "id": "ParseData-igEkj", + "measured": { + "height": 342, + "width": 320 + }, + "position": { + "x": 982.6945056277784, + "y": 823.4439549777719 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatOutput-UZgon", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Display a chat message in the Playground.", + "display_name": "Chat Output", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "data_template", + "background_color", + "chat_icon", + "text_color" + ], + "frozen": false, + "icon": "MessagesSquare", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": true, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "message_response", + "name": "message", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "background_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Background Color", + "dynamic": false, + "info": "The background color of the icon.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "background_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "chat_icon": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Icon", + "dynamic": false, + "info": "The icon of the message.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "chat_icon", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n MessageInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n message = self.input_value if isinstance(self.input_value, Message) else Message(text=self.input_value)\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" + }, + "data_template": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Data Template", + "dynamic": false, + "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "data_template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{text}" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Text", + "dynamic": false, + "info": "Message to be passed as output.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Type of sender.", + "name": "sender", + "options": [ + "Machine", + "User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Machine" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Name of the sender.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "AI" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Session ID", + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "should_store_message": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Store Messages", + "dynamic": false, + "info": "Store the message in the history.", + "list": false, + "list_add_label": "Add More", + "name": "should_store_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "text_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Text Color", + "dynamic": false, + "info": "The text color of the name", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "text_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": false, + "type": "ChatOutput" + }, + "dragging": false, + "id": "ChatOutput-UZgon", + "measured": { + "height": 66, + "width": 192 + }, + "position": { + "x": 1498.3739454769902, + "y": 1061.1606281984073 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatInput-m10vc", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Get chat inputs from the Playground.", + "display_name": "Chat Input", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "files", + "background_color", + "chat_icon", + "text_color" + ], + "frozen": false, + "icon": "MessagesSquare", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": true, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "message_response", + "name": "message", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "background_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Background Color", + "dynamic": false, + "info": "The background color of the icon.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "background_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "chat_icon": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Icon", + "dynamic": false, + "info": "The icon of the message.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "chat_icon", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" + }, + "files": { + "_input_type": "FileInput", + "advanced": true, + "display_name": "Files", + "dynamic": false, + "fileTypes": [ + "txt", + "md", + "mdx", + "csv", + "json", + "yaml", + "yml", + "xml", + "html", + "htm", + "pdf", + "docx", + "py", + "sh", + "sql", + "js", + "ts", + "tsx", + "jpg", + "jpeg", + "png", + "bmp", + "image" + ], + "file_path": "", + "info": "Files to be sent with the message.", + "list": true, + "list_add_label": "Add More", + "name": "files", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "file", + "value": "" + }, + "input_value": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Text", + "dynamic": false, + "info": "Message to be passed as input.", + "input_types": [], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Type of sender.", + "name": "sender", + "options": [ + "Machine", + "User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "User" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Name of the sender.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "User" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Session ID", + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "should_store_message": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Store Messages", + "dynamic": false, + "info": "Store the message in the history.", + "list": false, + "list_add_label": "Add More", + "name": "should_store_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "text_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Text Color", + "dynamic": false, + "info": "The text color of the name", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "text_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": false, + "type": "ChatInput" + }, + "dragging": false, + "id": "ChatInput-m10vc", + "measured": { + "height": 66, + "width": 192 + }, + "position": { + "x": -235.49853728839307, + "y": 107.75353484470551 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "note-g4YbJ", + "node": { + "description": "### 💡 Add your Anthropic API key here 👇", + "display_name": "", + "documentation": "", + "template": { + "backgroundColor": "transparent" + } + }, + "type": "note" + }, + "dragging": false, + "height": 324, + "id": "note-g4YbJ", + "measured": { + "height": 324, + "width": 358 + }, + "position": { + "x": 1479.4278434913201, + "y": -274.26903478612456 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 359 + }, + { + "data": { + "id": "note-e6tLL", + "node": { + "description": "# **Langflow Loop Component Template - ArXiv search result Translator** \nThis template translates research paper summaries on ArXiv into Portuguese and summarizes them. \n Using **Langflow’s looping mechanism**, the template iterates through multiple research papers, translates them with the **Anthropic** model component, and outputs an aggregated version of all translated papers. \n\n# # Quickstart \n 1. Add your Anthropic API key to the **Anthropic** component. \n2. In the **Playground**, enter a query related to a research topic (for example, “Quantum Computing Advancements”). \n\n The flow fetches a list of research papers from ArXiv matching the query. Each paper in the retrieved list is processed one-by-one using the Langflow **Loop component**. \n\n The abstract of each paper is translated into Portuguese by the **Anthropic** model component. \n\n Once all papers are translated, the system aggregates them into a **single structured output**.", + "display_name": "", + "documentation": "", + "template": {} + }, + "type": "note" + }, + "dragging": false, + "height": 647, + "id": "note-e6tLL", + "measured": { + "height": 647, + "width": 577 + }, + "position": { + "x": -890.9006297459302, + "y": -233.44894493951168 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 576 + } + ], + "viewport": { + "x": 440.1573282533791, + "y": 190.151319158377, + "zoom": 0.21962836650867892 + } + }, + "description": "This template iterates over search results using LoopComponent and translates each result into Portuguese automatically. 🚀", + "endpoint_name": "search_loop", + "gradient": "3", + "icon": "Infinity", + "id": "a004bc07-f6ca-4a02-b8f8-7fb8ebaed7b0", + "is_component": false, + "last_tested_version": "1.1.5", + "name": "Research Translation Loop", + "tags": [ + "chatbots", + "content-generation" + ] +} \ No newline at end of file From 0fbe1b0d09add07439412908c67a699e04690606 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Thu, 13 Feb 2025 17:14:51 -0600 Subject: [PATCH 4/6] feat: Add new tracking events for flow and data load executions (#6492) * add new events * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../src/customization/utils/analytics.ts | 17 +++++++++ src/frontend/src/stores/flowStore.ts | 38 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/customization/utils/analytics.ts b/src/frontend/src/customization/utils/analytics.ts index 03a7c92765..f40138e3eb 100644 --- a/src/frontend/src/customization/utils/analytics.ts +++ b/src/frontend/src/customization/utils/analytics.ts @@ -5,3 +5,20 @@ export const track = async ( ): Promise => { return; }; + +export const trackFlowBuild = async ( + flowName: string, + isError?: boolean, + properties?: Record, +): Promise => { + return; +}; + +export const trackDataLoaded = async ( + flowId?: string, + flowName?: string, + component?: string, + componentId?: string, +): Promise => { + return; +}; diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index b869a348ac..8564a5c3ed 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -2,7 +2,12 @@ import { BROKEN_EDGES_WARNING, componentsToIgnoreUpdate, } from "@/constants/constants"; -import { track } from "@/customization/utils/analytics"; +import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags"; +import { + track, + trackDataLoaded, + trackFlowBuild, +} from "@/customization/utils/analytics"; import { brokenEdgeMessage } from "@/utils/utils"; import { EdgeChange, @@ -19,7 +24,7 @@ import { MISSED_ERROR_ALERT, } from "../constants/alerts_constants"; import { BuildStatus } from "../constants/enums"; -import { VertexBuildTypeAPI } from "../types/api"; +import { LogsLogType, VertexBuildTypeAPI } from "../types/api"; import { ChatInputType, ChatOutputType } from "../types/chat"; import { AllNodeType, @@ -699,6 +704,28 @@ const useFlowStore = create((set, get) => ({ ...get().verticesBuild!.verticesIds, ...next_vertices_ids, ]; + if ( + ENABLE_DATASTAX_LANGFLOW && + vertexBuildData?.id?.includes("AstraDB") + ) { + const search_results: LogsLogType[] = Object.values( + vertexBuildData?.data?.logs?.search_results, + ); + search_results.forEach((log) => { + if ( + log.message.includes("Adding") && + log.message.includes("documents") && + log.message.includes("Vector Store") + ) { + trackDataLoaded( + get().currentFlow?.id, + get().currentFlow?.name, + "AstraDB Vector Store", + vertexBuildData?.id, + ); + } + }); + } get().updateVerticesBuild({ verticesIds: newIds, verticesLayers: newLayers, @@ -747,6 +774,9 @@ const useFlowStore = create((set, get) => ({ false, ); get().setIsBuilding(false); + trackFlowBuild(get().currentFlow?.name ?? "Unknown", false, { + flowId: get().currentFlow?.id, + }); }, onBuildUpdate: handleBuildUpdate, onBuildError: (title: string, list: string[], elementList) => { @@ -767,6 +797,10 @@ const useFlowStore = create((set, get) => ({ setErrorData({ list, title }); get().setIsBuilding(false); get().buildController.abort(); + trackFlowBuild(get().currentFlow?.name ?? "Unknown", true, { + flowId: get().currentFlow?.id, + error: list, + }); }, onBuildStart: (elementList) => { const idList = elementList From 0a631a68c2811bfee98a530f4998a27ddb8b204e Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Fri, 14 Feb 2025 11:20:08 -0300 Subject: [PATCH 5/6] fix: Update path filters, test tags and backend processing (#6613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (tests): update tag value from "@starter-project" to "@starter-projects" for consistency across test files. * 📝 (changes-filter.yaml): add new path "src/backend/base/langflow/custom/**" to starter-projects section ♻️ (changes-filter.yaml): remove path "src/backend/base/langflow/components/**" from components section and add it to starter-projects section ♻️ (changes-filter.yaml): add new path "src/backend/base/langflow/custom/**" to components section * 🐛 (component.py): fix a bug where the status was not being returned when there is only one output in the component * 🔧 (ci.yml): Expand path filter outputs for more comprehensive CI testing * fix docstring placement * ✨ (group.spec.ts): update click event modifiers to use "ControlOrMeta" for better cross-platform compatibility * ✨ (changes-filter.yaml): add new directory "src/backend/base/langflow/graph/" to starter-projects for processing in the build pipeline. * 🔄 Refactor graph data structures from sets to lists for performance and compatibility * 🔧 Optimize RunnableVerticesManager predecessor checks and type hints --------- Co-authored-by: Gabriel Luiz Freitas Almeida --- .github/changes-filter.yaml | 5 +++- .github/workflows/ci.yml | 9 +++++-- .../custom/custom_component/component.py | 2 ++ src/backend/base/langflow/graph/graph/base.py | 27 ++++++++++--------- .../graph/graph/runnable_vertices_manager.py | 9 ++++--- .../base/langflow/graph/graph/utils.py | 16 +++++------ .../base/langflow/graph/vertex/base.py | 6 ++--- .../langflow/graph/vertex/vertex_types.py | 2 +- .../tests/core/features/group.spec.ts | 14 +++++++--- .../core/integrations/Basic Prompting.spec.ts | 2 +- .../core/integrations/Blog Writer.spec.ts | 2 +- .../Custom Component Generator.spec.ts | 2 +- .../core/integrations/Document QA.spec.ts | 2 +- .../core/integrations/Dynamic Agent.spec.ts | 2 +- .../integrations/Hierarchical Agent.spec.ts | 2 +- .../Image Sentiment Analysis.spec.ts | 2 +- .../integrations/Instagram Copywriter.spec.ts | 2 +- .../core/integrations/Market Research.spec.ts | 2 +- .../core/integrations/Memory Chatbot.spec.ts | 2 +- .../core/integrations/Prompt Chaining.spec.ts | 2 +- .../SEO Keyword Generator.spec.ts | 2 +- .../core/integrations/SaaS Pricing.spec.ts | 2 +- .../Sequential Task Agent.spec.ts | 2 +- .../core/integrations/Simple Agent.spec.ts | 2 +- .../Travel Planning Agent.spec.ts | 2 +- .../Twitter Thread Generator.spec.ts | 2 +- .../core/integrations/Vector Store.spec.ts | 2 +- .../integrations/starter-projects.spec.ts | 2 +- .../regression/generalBugs-prompt.spec.ts | 2 +- .../features/starter-projects.spec.ts | 2 +- 30 files changed, 76 insertions(+), 56 deletions(-) diff --git a/.github/changes-filter.yaml b/.github/changes-filter.yaml index 2a85290882..925f237460 100644 --- a/.github/changes-filter.yaml +++ b/.github/changes-filter.yaml @@ -28,6 +28,8 @@ starter-projects: - "src/frontend/src/pages/MainPage/**" - "src/frontend/src/utils/reactflowUtils.ts" - "src/frontend/tests/extended/features/**" + - "src/backend/base/langflow/custom/**" + - "src/backend/base/langflow/graph/**" components: - "src/frontend/src/components/**" @@ -38,7 +40,6 @@ components: - "src/frontend/src/CustomNodes/**" - "src/frontend/src/style/**" - "src/frontend/src/utils/styleUtils.ts" - - "src/backend/base/langflow/components/**" - "src/frontend/tests/core/features/**" - "src/frontend/tests/core/integrations/**" - "src/frontend/tests/core/regression/**" @@ -49,6 +50,8 @@ components: - "src/backend/base/langflow/schema/**" - "src/backend/base/langflow/graph/**" - "src/backend/base/langflow/utils/**" + - "src/backend/base/langflow/custom/**" + - "src/backend/base/langflow/components/**" workspace: - "src/backend/base/langflow/inputs/**" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca18aa0398..6c084e519f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,6 +101,12 @@ jobs: frontend-tests: ${{ steps.filter.outputs.frontend-tests }} components-changes: ${{ steps.filter.outputs.components-changes }} starter-projects-changes: ${{ steps.filter.outputs.starter-projects-changes }} + starter-projects: ${{ steps.filter.outputs.starter-projects }} + components: ${{ steps.filter.outputs.components }} + workspace: ${{ steps.filter.outputs.workspace }} + api: ${{ steps.filter.outputs.api }} + database: ${{ steps.filter.outputs.database }} + steps: - name: Checkout code uses: actions/checkout@v4 @@ -119,11 +125,10 @@ jobs: uses: ./.github/workflows/python_test.yml with: python-versions: ${{ inputs.python-versions || '["3.10"]' }} - test-frontend: needs: path-filter name: Run Frontend Tests - if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' }} + if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' || needs.path-filter.outputs.starter-projects == 'true' || needs.path-filter.outputs.components == 'true' || needs.path-filter.outputs.workspace == 'true' || needs.path-filter.outputs.api == 'true' || needs.path-filter.outputs.database == 'true' }} uses: ./.github/workflows/typescript_test.yml with: tests_folder: ${{ inputs.frontend-tests-folder }} diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 6e40bcea55..1c502eaea8 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -982,6 +982,8 @@ class Component(CustomComponent): def _process_raw_result(self, result): """Process the raw result of the component.""" + if len(self.outputs) == 1: + return self.status or self.extract_data(result) return self.extract_data(result) def extract_data(self, result): diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 974a249355..557f0d373f 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -116,10 +116,10 @@ class Graph: self.top_level_vertices: list[str] = [] self.vertex_map: dict[str, Vertex] = {} - self.predecessor_map: dict[str, set[str]] = defaultdict(set) - self.successor_map: dict[str, set[str]] = defaultdict(set) + self.predecessor_map: dict[str, list[str]] = defaultdict(list) + self.successor_map: dict[str, list[str]] = defaultdict(list) self.in_degree_map: dict[str, int] = defaultdict(int) - self.parent_child_map: dict[str, set[str]] = defaultdict(set) + self.parent_child_map: dict[str, list[str]] = defaultdict(list) self._run_queue: deque[str] = deque() self._first_layer: list[str] = [] self._lock = asyncio.Lock() @@ -469,10 +469,10 @@ class Graph: self.add_edge(edge) source_id = edge["data"]["sourceHandle"]["id"] target_id = edge["data"]["targetHandle"]["id"] - self.predecessor_map[target_id].add(source_id) - self.successor_map[source_id].add(target_id) + self.predecessor_map[target_id].append(source_id) + self.successor_map[source_id].append(target_id) self.in_degree_map[target_id] += 1 - self.parent_child_map[source_id].add(target_id) + self.parent_child_map[source_id].append(target_id) def add_node(self, node: NodeData) -> None: self._vertices.append(node) @@ -1554,7 +1554,7 @@ class Graph: logger.debug("Graph processing complete") return self - def find_next_runnable_vertices(self, vertex_successors_ids: set[str]) -> list[str]: + def find_next_runnable_vertices(self, vertex_successors_ids: list[str]) -> list[str]: next_runnable_vertices = set() for v_id in sorted(vertex_successors_ids): if not self.is_vertex_runnable(v_id): @@ -2023,7 +2023,10 @@ class Graph: def build_in_degree(self, edges: list[CycleEdge]) -> dict[str, int]: in_degree: dict[str, int] = defaultdict(int) + for edge in edges: + # We don't need to count if a Component connects more than one + # time to the same vertex. in_degree[edge.target_id] += 1 for vertex in self.vertices: if vertex.id not in in_degree: @@ -2031,13 +2034,13 @@ class Graph: return in_degree @staticmethod - def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, set[str]], dict[str, set[str]]]: + def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, list[str]], dict[str, list[str]]]: """Returns the adjacency maps for the graph.""" - predecessor_map: dict[str, set[str]] = defaultdict(set) - successor_map: dict[str, set[str]] = defaultdict(set) + predecessor_map: dict[str, list[str]] = defaultdict(list) + successor_map: dict[str, list[str]] = defaultdict(list) for edge in edges: - predecessor_map[edge.target_id].add(edge.source_id) - successor_map[edge.source_id].add(edge.target_id) + predecessor_map[edge.target_id].append(edge.source_id) + successor_map[edge.source_id].append(edge.target_id) return predecessor_map, successor_map def __to_dict(self) -> dict[str, dict[str, list[str]]]: diff --git a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py index 2b11d16fa9..92edbc70d5 100644 --- a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py +++ b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py @@ -2,9 +2,9 @@ from collections import defaultdict class RunnableVerticesManager: - def __init__(self): + def __init__(self) -> None: self.run_map: dict[str, list[str]] = defaultdict(list) # Tracks successors of each vertex - self.run_predecessors: dict[str, set[str]] = defaultdict(set) # Tracks predecessors for each vertex + self.run_predecessors: dict[str, list[str]] = defaultdict(list) # Tracks predecessors for each vertex self.vertices_to_run: set[str] = set() # Set of vertices that are ready to run self.vertices_being_run: set[str] = set() # Set of vertices that are currently running self.cycle_vertices: set[str] = set() # Set of vertices that are in a cycle @@ -74,13 +74,14 @@ class RunnableVerticesManager: bool: True if all predecessor conditions are met, False otherwise """ # Get pending predecessors, return True if none exist - if not (pending := self.run_predecessors.get(vertex_id, set())): + pending = self.run_predecessors.get(vertex_id, []) + if not pending: return True # For cycle vertices, check if any pending predecessors are also in cycle # Using set intersection is faster than iteration if vertex_id in self.cycle_vertices: - return is_loop or not bool(pending.intersection(self.cycle_vertices)) + return is_loop or not bool(set(pending) & self.cycle_vertices) return False diff --git a/src/backend/base/langflow/graph/graph/utils.py b/src/backend/base/langflow/graph/graph/utils.py index d0a8ea174b..26650f3434 100644 --- a/src/backend/base/langflow/graph/graph/utils.py +++ b/src/backend/base/langflow/graph/graph/utils.py @@ -461,8 +461,8 @@ def find_cycle_vertices(edges): def layered_topological_sort( vertices_ids: set[str], in_degree_map: dict[str, int], - successor_map: dict[str, set[str]], - predecessor_map: dict[str, set[str]], + successor_map: dict[str, list[str]], + predecessor_map: dict[str, list[str]], start_id: str | None = None, cycle_vertices: set[str] | None = None, is_input_vertex: Callable[[str], bool] | None = None, # noqa: ARG001 @@ -780,8 +780,8 @@ def get_sorted_vertices( start_component_id: str | None = None, graph_dict: dict[str, Any] | None = None, in_degree_map: dict[str, int] | None = None, - successor_map: dict[str, set[str]] | None = None, - predecessor_map: dict[str, set[str]] | None = None, + successor_map: dict[str, list[str]] | None = None, + predecessor_map: dict[str, list[str]] | None = None, is_input_vertex: Callable[[str], bool] | None = None, get_vertex_predecessors: Callable[[str], list[str]] | None = None, get_vertex_successors: Callable[[str], list[str]] | None = None, @@ -826,18 +826,18 @@ def get_sorted_vertices( successor_map = {} for vertex_id in vertices_ids: if get_vertex_successors is not None: - successor_map[vertex_id] = set(get_vertex_successors(vertex_id)) + successor_map[vertex_id] = get_vertex_successors(vertex_id) else: - successor_map[vertex_id] = set() + successor_map[vertex_id] = [] # Build predecessor_map if not provided if predecessor_map is None: predecessor_map = {} for vertex_id in vertices_ids: if get_vertex_predecessors is not None: - predecessor_map[vertex_id] = set(get_vertex_predecessors(vertex_id)) + predecessor_map[vertex_id] = get_vertex_predecessors(vertex_id) else: - predecessor_map[vertex_id] = set() + predecessor_map[vertex_id] = [] # If we have a stop component, we need to filter out all vertices # that are not predecessors of the stop component diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index a354812e37..a40a77120c 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -79,7 +79,7 @@ class Vertex: self.built_object: Any = UnbuiltObject() self.built_result: Any = None self.built = False - self._successors_ids: set[str] | None = None + self._successors_ids: list[str] | None = None self.artifacts: dict[str, Any] = {} self.artifacts_raw: dict[str, Any] = {} self.artifacts_type: dict[str, str] = {} @@ -207,8 +207,8 @@ class Vertex: return self.graph.get_successors(self) @property - def successors_ids(self) -> set[str]: - return self.graph.successor_map.get(self.id, set()) + def successors_ids(self) -> list[str]: + return self.graph.successor_map.get(self.id, []) def __getstate__(self): state = self.__dict__.copy() diff --git a/src/backend/base/langflow/graph/vertex/vertex_types.py b/src/backend/base/langflow/graph/vertex/vertex_types.py index c6885000bc..cebc0cb9c2 100644 --- a/src/backend/base/langflow/graph/vertex/vertex_types.py +++ b/src/backend/base/langflow/graph/vertex/vertex_types.py @@ -466,7 +466,7 @@ class StateVertex(ComponentVertex): self.is_state = False @property - def successors_ids(self) -> set[str]: + def successors_ids(self) -> list[str]: if self._successors_ids is None: self.is_state = False return super().successors_ids diff --git a/src/frontend/tests/core/features/group.spec.ts b/src/frontend/tests/core/features/group.spec.ts index 4aa098fc48..67a88d18b0 100644 --- a/src/frontend/tests/core/features/group.spec.ts +++ b/src/frontend/tests/core/features/group.spec.ts @@ -17,16 +17,22 @@ test.describe("group node test", () => { await page.getByTestId("fit_view").first().click(); await page.getByTestId("title-OpenAI").click(); - await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] }); - await page.getByTestId("title-Prompt").click({ modifiers: ["Control"] }); - await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] }); + await page + .getByTestId("title-OpenAI") + .click({ modifiers: ["ControlOrMeta"] }); + await page + .getByTestId("title-Prompt") + .click({ modifiers: ["ControlOrMeta"] }); + await page + .getByTestId("title-OpenAI") + .click({ modifiers: ["ControlOrMeta"] }); await page.getByRole("button", { name: "Group" }).click(); await page.getByTestId("title-Group").click(); await page.getByTestId("edit-name-description-button").click(); await page.getByTestId("input-title-Group").first().fill("test"); await page.getByTestId("save-name-description-button").first().click(); - await page.keyboard.press("Control+g"); + await page.keyboard.press("ControlOrMeta+g"); await page.getByTestId("title-OpenAI").isVisible(); await page.getByTestId("title-Prompt").isVisible(); }, diff --git a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts index d50fdee462..04a4e93b52 100644 --- a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts +++ b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Basic Prompting (Hello, World)", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Blog Writer.spec.ts b/src/frontend/tests/core/integrations/Blog Writer.spec.ts index d9b14040d4..08fcc67f9e 100644 --- a/src/frontend/tests/core/integrations/Blog Writer.spec.ts +++ b/src/frontend/tests/core/integrations/Blog Writer.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Blog Writer", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts index 389790d5f1..235b98c322 100644 --- a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts @@ -7,7 +7,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Custom Component Generator", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.ANTHROPIC_API_KEY, diff --git a/src/frontend/tests/core/integrations/Document QA.spec.ts b/src/frontend/tests/core/integrations/Document QA.spec.ts index 2fa3bbf8a0..e1a26348c2 100644 --- a/src/frontend/tests/core/integrations/Document QA.spec.ts +++ b/src/frontend/tests/core/integrations/Document QA.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Document Q&A", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts b/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts index 6d54a00822..133e9701ae 100644 --- a/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test.skip( "Dynamic Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts b/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts index ba5ff76eec..299ac3c310 100644 --- a/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test.skip( "Hierarchical Tasks Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts b/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts index 3e3f1f8226..893929df80 100644 --- a/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts +++ b/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts @@ -15,7 +15,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "Image Sentiment Analysis", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts b/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts index ef72c7cebe..ac25fa8177 100644 --- a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts +++ b/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts @@ -8,7 +8,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Instagram Copywriter", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Market Research.spec.ts b/src/frontend/tests/core/integrations/Market Research.spec.ts index 6df987b52d..a6c1a859ff 100644 --- a/src/frontend/tests/core/integrations/Market Research.spec.ts +++ b/src/frontend/tests/core/integrations/Market Research.spec.ts @@ -8,7 +8,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Market Research", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts index db0603ed3e..0d12fb09b9 100644 --- a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts +++ b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Memory Chatbot", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts index be3a3e8cc6..262bcbb760 100644 --- a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts +++ b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Prompt Chaining", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts index 96d9cb3267..b9561d37a0 100644 --- a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts +++ b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "SEO Keyword Generator", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts index efadb0c477..6c047f77ca 100644 --- a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts +++ b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "SaaS Pricing", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts b/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts index a1d6fd0d3e..2d06e8b30d 100644 --- a/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test.skip( "Sequential Task Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Simple Agent.spec.ts b/src/frontend/tests/core/integrations/Simple Agent.spec.ts index e20e0444b5..3bfa8c893c 100644 --- a/src/frontend/tests/core/integrations/Simple Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Simple Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Simple Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts b/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts index d66e3227de..fb0458f514 100644 --- a/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts @@ -11,7 +11,7 @@ import { updateOldComponents } from "../../utils/update-old-components"; test( "Travel Planning Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts b/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts index bced1d125f..ba0a92815f 100644 --- a/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "Twitter Thread Generator", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Vector Store.spec.ts b/src/frontend/tests/core/integrations/Vector Store.spec.ts index 352508519d..9bff2b9f12 100644 --- a/src/frontend/tests/core/integrations/Vector Store.spec.ts +++ b/src/frontend/tests/core/integrations/Vector Store.spec.ts @@ -5,7 +5,7 @@ import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; test( "Vector Store RAG", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/starter-projects.spec.ts b/src/frontend/tests/core/integrations/starter-projects.spec.ts index f256523d26..cdf352bd8a 100644 --- a/src/frontend/tests/core/integrations/starter-projects.spec.ts +++ b/src/frontend/tests/core/integrations/starter-projects.spec.ts @@ -3,7 +3,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "vector store from starter projects should have its connections and nodes on the flow", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page, request }) => { const response = await request.get("/api/v1/starter-projects"); expect(response.status()).toBe(200); diff --git a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts index ae53e06742..5d20b440a0 100644 --- a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts @@ -5,7 +5,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "user must be able to edit an empty prompt", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/extended/features/starter-projects.spec.ts b/src/frontend/tests/extended/features/starter-projects.spec.ts index 04500f0f54..0730c99fe5 100644 --- a/src/frontend/tests/extended/features/starter-projects.spec.ts +++ b/src/frontend/tests/extended/features/starter-projects.spec.ts @@ -3,7 +3,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "user must be able to interact with starter projects", - { tag: ["@release", "@starter-projects"] }, + { tag: ["@release", "@starter-projectss"] }, async ({ page, context }) => { await awaitBootstrapTest(page); From 45e2f739d5f6497e8594fd439b7f5cfc667c277f Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Fri, 14 Feb 2025 13:59:56 -0300 Subject: [PATCH 6/6] fix: adds edge filtering only to parameters not hidden (#6270) Adds advanced filter to only filter by showing fields on click of edge --- src/frontend/src/utils/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 9cf131f5c8..9eef7f9209 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -266,6 +266,7 @@ export function groupByFamily( return ( template?.type && template?.show && + !template?.advanced && ((!excludeTypes.has(template.type) && baseClassesSet.has(template.type)) || (template?.input_types &&