From d143fe49397245da6e84f2597a188f2e4b41cb91 Mon Sep 17 00:00:00 2001 From: Mendon Kissling <59585235+mendonk@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:34:49 -0500 Subject: [PATCH 1/3] docs: add-xai-component (#6714) * add-xai-component * docs: update xAI component documentation with Grok link --- docs/docs/Components/components-models.md | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/docs/Components/components-models.md b/docs/docs/Components/components-models.md index dfdf697056..f1943ded46 100644 --- a/docs/docs/Components/components-models.md +++ b/docs/docs/Components/components-models.md @@ -496,4 +496,29 @@ For more information, see [Google Vertex AI documentation](https://cloud.google. |--------|---------------|-----------------------------------------------------| | model | LanguageModel | An instance of ChatVertexAI configured with the specified parameters. | +## xAI + +This component generates text using xAI models like [Grok](https://x.ai/grok). + +For more information, see the [xAI documentation](https://x.ai/). + +### Inputs + +| Name | Type | Description | +|----------------|---------------|-----------------------------------------------------------------| +| max_tokens | Integer | Maximum number of tokens to generate. Set to `0` for unlimited. Range: `0-128000`. | +| model_kwargs | Dictionary | Additional keyword arguments for the model. | +| json_mode | Boolean | If `True`, outputs JSON regardless of passing a schema. | +| model_name | String | The xAI model to use. Default: `grok-2-latest`. | +| base_url | String | Base URL for API requests. Default: `https://api.x.ai/v1`. | +| api_key | SecretString | Your xAI API key for authentication. | +| temperature | Float | Controls randomness in the output. Range: `[0.0, 2.0]`. Default: `0.1`. | +| seed | Integer | Controls reproducibility of the job. | + +### Outputs + +| Name | Type | Description | +|-------|---------------|------------------------------------------------------------------| +| model | LanguageModel | An instance of ChatOpenAI configured with the specified parameters. | + From 69df913a147cdc233026d3f702c9eb9669be59fd Mon Sep 17 00:00:00 2001 From: Edwin Jose Date: Wed, 19 Feb 2025 12:48:28 -0500 Subject: [PATCH 2/3] feat: updated ChatOutput component that can accept Data, Dataframe and Message (#6643) * update chatoutput * [autofix.ci] apply automated fixes * update * tests * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Update chat.py * update template * fix lint errors * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix: rename variable for clarity in chat output component test * [autofix.ci] apply automated fixes * fix: enable loading from database for API key in starter project configurations * update templates * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: cristhianzl --- .../base/langflow/components/outputs/chat.py | 91 ++- .../Basic Prompt Chaining.json | 34 +- .../starter_projects/Basic Prompting.json | 177 ++++- .../starter_projects/Blog Writer.json | 257 +++++-- .../Custom Component Maker.json | 381 +++++++-- .../starter_projects/Document Q&A.json | 244 ++++-- .../Financial Report Parser.json | 28 +- .../starter_projects/Gmail Agent.json | 38 +- .../Graph Vector Store RAG.json | 550 ++++++++++--- .../Image Sentiment Analysis.json | 243 ++++-- .../Instagram Copywriter.json | 726 ++++++++++++------ .../starter_projects/LoopTemplate.json | 326 ++++++-- .../starter_projects/Market Research.json | 531 ++++++++----- .../starter_projects/Meeting Summary.json | 86 ++- .../starter_projects/Memory Chatbot.json | 237 ++++-- .../starter_projects/News Aggregator.json | 32 +- .../Portfolio Website Code Generator.json | 338 ++++++-- .../starter_projects/Price Deal Finder.json | 38 +- .../starter_projects/Research Agent.json | 454 ++++++++--- .../SEO Keyword Generator.json | 191 ++++- .../starter_projects/SaaS Pricing.json | 209 +++-- .../Sequential Tasks Agents .json | 542 ++++++++++--- .../starter_projects/Simple Agent.json | 250 ++++-- .../Travel Planning Agents.json | 432 ++++++++--- .../Twitter Thread Generator.json | 358 +++++++-- .../starter_projects/Vector Store RAG.json | 545 +++++++------ .../starter_projects/Youtube Analysis.json | 30 +- .../outputs/test_chat_output_component.py | 98 +++ .../outputs/test_output_components.py | 34 +- 29 files changed, 5561 insertions(+), 1939 deletions(-) create mode 100644 src/backend/tests/unit/components/outputs/test_chat_output_component.py diff --git a/src/backend/base/langflow/components/outputs/chat.py b/src/backend/base/langflow/components/outputs/chat.py index 484221beb2..705dd3d230 100644 --- a/src/backend/base/langflow/components/outputs/chat.py +++ b/src/backend/base/langflow/components/outputs/chat.py @@ -1,6 +1,11 @@ +from typing import Any + from langflow.base.io.chat import ChatComponent from langflow.inputs import BoolInput -from langflow.io import DropdownInput, MessageInput, MessageTextInput, Output +from langflow.inputs.inputs import HandleInput +from langflow.io import DropdownInput, MessageTextInput, Output +from langflow.schema.data import Data +from langflow.schema.dataframe import DataFrame from langflow.schema.message import Message from langflow.schema.properties import Source from langflow.utils.constants import ( @@ -18,10 +23,12 @@ class ChatOutput(ChatComponent): minimized = True inputs = [ - MessageInput( + HandleInput( name="input_value", display_name="Text", info="Message to be passed as output.", + input_types=["Data", "DataFrame", "Message"], + required=True, ), BoolInput( name="should_store_message", @@ -76,6 +83,13 @@ class ChatOutput(ChatComponent): info="The text color of the name", advanced=True, ), + BoolInput( + name="clean_data", + display_name="Basic Clean Data", + value=True, + info="Whether to clean the data", + advanced=True, + ), ] outputs = [ Output( @@ -92,16 +106,35 @@ class ChatOutput(ChatComponent): if display_name: source_dict["display_name"] = display_name if source: - source_dict["source"] = source + # Handle case where source is a ChatOpenAI object + if hasattr(source, "model_name"): + source_dict["source"] = source.model_name + elif hasattr(source, "model"): + source_dict["source"] = str(source.model) + else: + source_dict["source"] = str(source) return Source(**source_dict) async def message_response(self) -> Message: + # First convert the input to string if needed + text = self.convert_to_string() + + # Get source properties source, icon, display_name, source_id = self.get_properties_from_source_component() background_color = self.background_color text_color = self.text_color if self.chat_icon: icon = self.chat_icon - message = self.input_value if isinstance(self.input_value, Message) else Message(text=self.input_value) + + # Create or use existing Message object + if isinstance(self.input_value, Message): + message = self.input_value + # Update message properties + message.text = text + else: + message = Message(text=text) + + # Set message properties message.sender = self.sender message.sender_name = self.sender_name message.session_id = self.session_id @@ -110,12 +143,54 @@ class ChatOutput(ChatComponent): message.properties.icon = icon message.properties.background_color = background_color message.properties.text_color = text_color - if self.session_id and isinstance(message, Message) and self.should_store_message: - stored_message = await self.send_message( - message, - ) + + # Store message if needed + if self.session_id and self.should_store_message: + stored_message = await self.send_message(message) self.message.value = stored_message message = stored_message self.status = message return message + + def _validate_input(self) -> None: + """Validate the input data and raise ValueError if invalid.""" + if self.input_value is None: + msg = "Input data cannot be None" + raise ValueError(msg) + if not isinstance(self.input_value, Data | DataFrame | Message | str | list): + msg = f"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}" + raise TypeError(msg) + + def _safe_convert(self, data: Any) -> str: + """Safely convert input data to string.""" + try: + if isinstance(data, str): + return data + if isinstance(data, Message): + return data.get_text() + if isinstance(data, Data): + if data.get_text() is None: + msg = "Empty Data object" + raise ValueError(msg) + return data.get_text() + if isinstance(data, DataFrame): + if self.clean_data: + # Remove empty rows + data = data.dropna(how="all") + # Remove empty lines in each cell + data = data.replace(r"^\s*$", "", regex=True) + # Replace multiple newlines with a single newline + data = data.replace(r"\n+", "\n", regex=True) + return data.to_markdown(index=False) + return str(data) + except (ValueError, TypeError, AttributeError) as e: + msg = f"Error converting data: {e!s}" + raise ValueError(msg) from e + + def convert_to_string(self) -> str: + """Convert input data to string with proper error handling.""" + self._validate_input() + if isinstance(self.input_value, list): + return "\n".join([self._safe_convert(item) for item in self.input_value]) + return self._safe_convert(self.input_value) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json index 380c524f53..3746611ae3 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json @@ -178,6 +178,8 @@ "fieldName": "input_value", "id": "ChatOutput-KXQMh", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -188,7 +190,7 @@ "source": "OpenAIModel-dXMRv", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-dXMRvœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-KXQMh", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-KXQMhœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-KXQMhœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -709,6 +711,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -725,7 +745,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -756,13 +776,15 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -1314,7 +1336,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, @@ -1696,7 +1718,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, @@ -2078,7 +2100,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json index c8a810d89b..2831bd1b3a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json @@ -8,12 +8,16 @@ "dataType": "ChatInput", "id": "ChatInput-jFwUm", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-OcXkl", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -30,12 +34,16 @@ "dataType": "Prompt", "id": "Prompt-3SM2g", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-OcXkl", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -52,12 +60,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-OcXkl", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-gDYiJ", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -65,7 +79,7 @@ "source": "OpenAIModel-OcXkl", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-OcXklœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-gDYiJ", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-gDYiJœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-gDYiJœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -75,7 +89,9 @@ "display_name": "Chat Input", "id": "ChatInput-jFwUm", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -106,7 +122,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -119,7 +137,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -138,7 +158,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -235,7 +257,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -249,7 +274,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -267,7 +294,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -302,7 +331,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -344,7 +375,9 @@ "display_name": "Prompt", "id": "Prompt-3SM2g", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -354,7 +387,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -369,7 +404,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -418,7 +455,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -533,7 +572,9 @@ "data": { "id": "ChatOutput-gDYiJ", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -566,7 +607,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -579,7 +622,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -599,7 +644,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -613,6 +660,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -629,7 +694,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -637,7 +702,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -657,12 +724,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -678,7 +749,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -694,7 +768,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -714,7 +790,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -750,7 +828,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -792,7 +872,10 @@ "data": { "id": "OpenAIModel-OcXkl", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -831,7 +914,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -840,10 +925,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -856,7 +945,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -891,7 +982,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1073,7 +1166,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1170,5 +1265,7 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Basic Prompting", - "tags": ["chatbots"] -} + "tags": [ + "chatbots" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json index c84d6013fd..6434c19424 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json @@ -9,12 +9,17 @@ "dataType": "ParseData", "id": "ParseData-4Sckw", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "references", "id": "Prompt-65R68", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -32,12 +37,17 @@ "dataType": "TextInput", "id": "TextInput-t88FI", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "instructions", "id": "Prompt-65R68", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -54,12 +64,16 @@ "dataType": "Prompt", "id": "Prompt-65R68", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-MyAsQ", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -76,12 +90,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-MyAsQ", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-BE4YI", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -89,7 +109,7 @@ "source": "OpenAIModel-MyAsQ", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-MyAsQœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-BE4YI", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BE4YIœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BE4YIœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "className": "", @@ -98,12 +118,16 @@ "dataType": "URL", "id": "URL-EPEnt", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-4Sckw", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -121,7 +145,9 @@ "display_name": "Parse Data", "id": "ParseData-4Sckw", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -129,7 +155,11 @@ "display_name": "Parse Data", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "legacy": false, @@ -147,7 +177,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -158,7 +190,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -188,7 +222,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "name": "data", "placeholder": "", @@ -221,7 +257,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -264,17 +302,24 @@ "display_name": "Prompt", "id": "Prompt-65R68", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["references", "instructions"] + "template": [ + "references", + "instructions" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -290,7 +335,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -323,7 +370,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -344,7 +394,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -379,7 +432,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -422,7 +477,9 @@ "display_name": "Instructions", "id": "TextInput-t88FI", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -430,7 +487,9 @@ "display_name": "Instructions", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -446,7 +505,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -477,7 +538,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -520,7 +583,9 @@ "display_name": "Chat Output", "id": "ChatOutput-BE4YI", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -551,7 +616,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -564,7 +631,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -583,7 +652,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -596,6 +667,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -612,14 +701,16 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -637,12 +728,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -656,7 +751,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -670,7 +768,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -688,7 +788,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -723,7 +825,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -872,7 +976,10 @@ "data": { "id": "OpenAIModel-MyAsQ", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -911,7 +1018,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -920,10 +1029,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -937,7 +1050,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -972,7 +1087,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1154,7 +1271,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1239,7 +1358,11 @@ "data": { "id": "URL-EPEnt", "node": { - "base_classes": ["Data", "DataFrame", "Message"], + "base_classes": [ + "Data", + "DataFrame", + "Message" + ], "beta": false, "category": "data", "conditional_paths": [], @@ -1248,7 +1371,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "key": "URL", @@ -1265,7 +1391,9 @@ "name": "data", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -1276,7 +1404,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1287,7 +1417,9 @@ "name": "dataframe", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -1322,7 +1454,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -1339,7 +1474,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "list_add_label": "Add URL", "load_from_db": false, @@ -1352,7 +1489,10 @@ "trace_as_input": true, "trace_as_metadata": true, "type": "str", - "value": ["https://langflow.org/", "https://docs.langflow.org/"] + "value": [ + "https://langflow.org/", + "https://docs.langflow.org/" + ] } }, "tool_mode": false @@ -1388,5 +1528,8 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Blog Writer", - "tags": ["chatbots", "content-generation"] -} + "tags": [ + "chatbots", + "content-generation" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json index a5259b58e3..53c53e7f46 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json @@ -9,12 +9,17 @@ "dataType": "Memory", "id": "Memory-v5bRq", "name": "messages_text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "CHAT_HISTORY", "id": "Prompt-QIyYT", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -33,12 +38,17 @@ "dataType": "ChatInput", "id": "ChatInput-Z88lk", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "USER_INPUT", "id": "Prompt-QIyYT", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -57,12 +67,17 @@ "dataType": "URL", "id": "URL-wNOPd", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "EXAMPLE_COMPONENTS", "id": "Prompt-QIyYT", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -80,12 +95,17 @@ "dataType": "URL", "id": "URL-pFAuR", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "BASE_COMPONENT_CODE", "id": "Prompt-QIyYT", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -103,12 +123,17 @@ "dataType": "URL", "id": "URL-KARv3", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "CUSTOM_COMPONENT_CODE", "id": "Prompt-QIyYT", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -125,12 +150,16 @@ "dataType": "Prompt", "id": "Prompt-QIyYT", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "AnthropicModel-RQQCm", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -147,12 +176,18 @@ "dataType": "AnthropicModel", "id": "AnthropicModel-RQQCm", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-Jj8M6", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -160,7 +195,7 @@ "source": "AnthropicModel-RQQCm", "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-RQQCmœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-Jj8M6", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-Jj8M6œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-Jj8M6œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -170,7 +205,9 @@ "display_name": "Chat Input", "id": "ChatInput-Z88lk", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -204,7 +241,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -217,7 +256,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -237,7 +278,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -340,7 +383,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -356,7 +402,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -376,7 +424,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -412,7 +462,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -456,7 +508,10 @@ "display_name": "Chat Memory", "id": "Memory-v5bRq", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -488,7 +543,9 @@ "name": "messages", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -499,7 +556,9 @@ "name": "messages_text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -530,7 +589,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -565,7 +626,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -583,7 +647,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -599,7 +667,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -619,7 +689,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -639,7 +711,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -684,7 +758,9 @@ "display_name": "Prompt", "id": "Prompt-QIyYT", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -700,7 +776,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -716,7 +794,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -730,7 +810,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -750,7 +833,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -770,7 +856,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -790,7 +879,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -810,7 +902,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -865,7 +960,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -909,7 +1006,9 @@ "display_name": "Chat Output", "id": "ChatOutput-Jj8M6", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -942,7 +1041,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -955,7 +1056,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -975,7 +1078,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -989,6 +1094,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1005,7 +1128,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1013,7 +1136,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -1033,12 +1158,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -1054,7 +1183,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -1070,7 +1202,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1090,7 +1224,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1126,7 +1262,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -1233,7 +1371,10 @@ "data": { "id": "URL-pFAuR", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1241,7 +1382,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "legacy": false, @@ -1257,7 +1401,9 @@ "name": "data", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -1268,7 +1414,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1279,7 +1427,9 @@ "name": "dataframe", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -1312,7 +1462,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "placeholder": "", "required": false, "show": true, @@ -1328,7 +1481,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "load_from_db": false, "name": "urls", @@ -1372,7 +1527,10 @@ "data": { "id": "URL-wNOPd", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1380,7 +1538,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "legacy": false, @@ -1396,7 +1557,9 @@ "name": "data", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -1407,7 +1570,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1418,7 +1583,9 @@ "name": "dataframe", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -1451,7 +1618,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "placeholder": "", "required": false, "show": true, @@ -1467,7 +1637,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "load_from_db": false, "name": "urls", @@ -1517,7 +1689,10 @@ "data": { "id": "URL-KARv3", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1525,7 +1700,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "legacy": false, @@ -1541,7 +1719,9 @@ "name": "data", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -1552,7 +1732,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1563,7 +1745,9 @@ "name": "dataframe", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -1596,7 +1780,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "placeholder": "", "required": false, "show": true, @@ -1612,7 +1799,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "load_from_db": false, "name": "urls", @@ -1656,7 +1845,10 @@ "data": { "id": "AnthropicModel-RQQCm", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1694,7 +1886,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1703,10 +1897,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1720,7 +1918,9 @@ "display_name": "Anthropic API Key", "dynamic": false, "info": "Your Anthropic API key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1738,7 +1938,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1778,7 +1980,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1847,7 +2051,9 @@ "display_name": "Prefill", "dynamic": false, "info": "Prefill text to guide the model's response.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1886,7 +2092,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1983,5 +2191,8 @@ "is_component": false, "last_tested_version": "1.1.0", "name": "Custom Component Generator", - "tags": ["coding", "web-scraping"] -} + "tags": [ + "coding", + "web-scraping" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json index 17a41abdde..5f782aa244 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json @@ -8,12 +8,16 @@ "dataType": "File", "id": "File-GwJQZ", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-BbvKb", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -31,12 +35,17 @@ "dataType": "ParseData", "id": "ParseData-BbvKb", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "Document", "id": "Prompt-yvZHT", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -54,12 +63,16 @@ "dataType": "ChatInput", "id": "ChatInput-li477", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-atkmo", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -77,12 +90,16 @@ "dataType": "Prompt", "id": "Prompt-yvZHT", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-atkmo", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -100,12 +117,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-atkmo", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-8pgwS", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -114,7 +137,7 @@ "source": "OpenAIModel-atkmo", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-atkmoœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-8pgwS", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-8pgwSœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-8pgwSœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -124,7 +147,9 @@ "display_name": "Chat Input", "id": "ChatInput-li477", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -155,7 +180,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -168,7 +195,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -187,7 +216,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -284,7 +315,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -298,7 +332,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -316,7 +352,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -351,7 +389,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -393,7 +433,9 @@ "display_name": "Chat Output", "id": "ChatOutput-8pgwS", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -427,7 +469,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -440,7 +484,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -460,7 +506,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -474,6 +522,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -490,7 +556,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -498,7 +564,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -518,12 +586,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -539,7 +611,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -555,7 +630,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -575,7 +652,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -611,7 +690,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -655,7 +736,9 @@ "display_name": "Parse Data", "id": "ParseData-BbvKb", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -663,7 +746,11 @@ "display_name": "Parse Data", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "legacy": false, @@ -681,7 +768,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -692,7 +781,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -722,7 +813,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "name": "data", "placeholder": "", @@ -755,7 +848,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -863,7 +958,9 @@ "data": { "id": "File-GwJQZ", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -892,7 +989,9 @@ "required_inputs": [], "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -955,7 +1054,10 @@ "display_name": "Server File Path", "dynamic": false, "info": "Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.", - "input_types": ["Data", "Message"], + "input_types": [ + "Data", + "Message" + ], "list": true, "name": "file_path", "placeholder": "", @@ -1102,18 +1204,24 @@ "display_name": "Prompt", "id": "Prompt-yvZHT", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["Document"] + "template": [ + "Document" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "prompts", @@ -1134,7 +1242,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1148,7 +1258,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1201,7 +1314,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1243,7 +1358,10 @@ "data": { "id": "OpenAIModel-atkmo", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1282,7 +1400,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1291,10 +1411,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1308,7 +1432,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1343,7 +1469,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1525,7 +1653,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1621,5 +1751,9 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Document Q&A", - "tags": ["rag", "q-a", "openai"] -} + "tags": [ + "rag", + "q-a", + "openai" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json index 0fe4a2f82d..94905ada60 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json @@ -99,6 +99,8 @@ "fieldName": "input_value", "id": "ChatOutput-ZtLkt", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -108,7 +110,7 @@ "source": "ParseData-hLbU0", "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-hLbU0œ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-ZtLkt", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-ZtLktœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-ZtLktœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -1198,6 +1200,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1214,7 +1234,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1246,6 +1266,8 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, @@ -1253,7 +1275,7 @@ "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json index 71b981786b..09d70465e4 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json @@ -45,6 +45,8 @@ "fieldName": "input_value", "id": "ChatOutput-lgshI", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -55,7 +57,7 @@ "source": "Agent-jnpdC", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-jnpdCœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-lgshI", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-lgshIœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-lgshIœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "className": "", @@ -246,7 +248,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, @@ -1161,6 +1163,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1177,7 +1197,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1209,6 +1229,8 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, @@ -1216,7 +1238,7 @@ "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -1493,7 +1515,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "COMPOSIO_API_KEY" }, "app_credentials": { "_input_type": "SecretStrInput", @@ -1795,8 +1817,10 @@ "is_component": false, "locked": false, "name": "Gmail Agent", - "tags": ["agents"], + "tags": [ + "agents" + ], "updated_at": "2025-02-14T09:35:52+00:00", "user_id": "baa8aaab-c242-4191-b79d-761bdb5a393c", "webhook": false -} +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json index f937d9235b..4507e7f51f 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json @@ -9,12 +9,16 @@ "dataType": "OpenAIEmbeddings", "id": "OpenAIEmbeddings-jyvkr", "name": "embeddings", - "output_types": ["Embeddings"] + "output_types": [ + "Embeddings" + ] }, "targetHandle": { "fieldName": "embedding_model", "id": "AstraDBGraph-jr8pY", - "inputTypes": ["Embeddings"], + "inputTypes": [ + "Embeddings" + ], "type": "other" } }, @@ -33,12 +37,16 @@ "dataType": "ChatInput", "id": "ChatInput-ZCSfi", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "search_query", "id": "AstraDBGraph-jr8pY", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -57,12 +65,16 @@ "dataType": "AstraDBGraph", "id": "AstraDBGraph-jr8pY", "name": "search_results", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-T6FGT", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -81,12 +93,17 @@ "dataType": "ParseData", "id": "ParseData-T6FGT", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "context", "id": "Prompt-2M2d5", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -105,12 +122,17 @@ "dataType": "ChatInput", "id": "ChatInput-ZCSfi", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "question", "id": "Prompt-2M2d5", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -129,12 +151,16 @@ "dataType": "Prompt", "id": "Prompt-2M2d5", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-a26gL", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -153,12 +179,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-a26gL", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-XL9ho", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -167,7 +199,7 @@ "source": "OpenAIModel-a26gL", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-a26gLœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-XL9ho", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-XL9hoœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-XL9hoœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -177,12 +209,17 @@ "dataType": "URL", "id": "URL-fyWIL", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data_input", "id": "LanguageRecursiveTextSplitter-jefpx", - "inputTypes": ["Document", "Data"], + "inputTypes": [ + "Document", + "Data" + ], "type": "other" } }, @@ -201,12 +238,17 @@ "dataType": "LanguageRecursiveTextSplitter", "id": "LanguageRecursiveTextSplitter-jefpx", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data_input", "id": "HtmlLinkExtractor-exHgk", - "inputTypes": ["Document", "Data"], + "inputTypes": [ + "Document", + "Data" + ], "type": "other" } }, @@ -225,12 +267,16 @@ "dataType": "HtmlLinkExtractor", "id": "HtmlLinkExtractor-exHgk", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "ingest_data", "id": "AstraDBGraph-FX0tA", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -249,12 +295,16 @@ "dataType": "OpenAIEmbeddings", "id": "OpenAIEmbeddings-83wEc", "name": "embeddings", - "output_types": ["Embeddings"] + "output_types": [ + "Embeddings" + ] }, "targetHandle": { "fieldName": "embedding_model", "id": "AstraDBGraph-FX0tA", - "inputTypes": ["Embeddings"], + "inputTypes": [ + "Embeddings" + ], "type": "other" } }, @@ -271,7 +321,9 @@ "data": { "id": "ChatInput-ZCSfi", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -305,7 +357,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -318,7 +372,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -338,7 +394,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -441,7 +499,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -457,7 +518,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -477,7 +540,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -513,7 +578,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -555,7 +622,9 @@ "data": { "id": "OpenAIEmbeddings-jyvkr", "node": { - "base_classes": ["Embeddings"], + "base_classes": [ + "Embeddings" + ], "beta": false, "category": "embeddings", "conditional_paths": [], @@ -601,10 +670,14 @@ "display_name": "Embeddings", "method": "build_embeddings", "name": "embeddings", - "required_inputs": ["openai_api_key"], + "required_inputs": [ + "openai_api_key" + ], "selected": "Embeddings", "tool_mode": true, - "types": ["Embeddings"], + "types": [ + "Embeddings" + ], "value": "__UNDEFINED__" } ], @@ -634,7 +707,9 @@ "display_name": "Client", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "client", @@ -704,7 +779,9 @@ "display_name": "Deployment", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "deployment", @@ -810,7 +887,9 @@ "display_name": "OpenAI API Base", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_api_base", @@ -830,7 +909,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "openai_api_key", "password": true, @@ -847,7 +928,9 @@ "display_name": "OpenAI API Type", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_api_type", @@ -867,7 +950,9 @@ "display_name": "OpenAI API Version", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_api_version", @@ -887,7 +972,9 @@ "display_name": "OpenAI Organization", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_organization", @@ -907,7 +994,9 @@ "display_name": "OpenAI Proxy", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_proxy", @@ -991,7 +1080,9 @@ "display_name": "TikToken Model Name", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tiktoken_model_name", @@ -1035,7 +1126,9 @@ "display_name": "Astra DB Graph", "id": "AstraDBGraph-jr8pY", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1081,10 +1174,16 @@ "display_name": "Search Results", "method": "search_documents", "name": "search_results", - "required_inputs": ["api_endpoint", "collection_name", "token"], + "required_inputs": [ + "api_endpoint", + "collection_name", + "token" + ], "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -1096,7 +1195,9 @@ "required_inputs": [], "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -1109,7 +1210,9 @@ "display_name": "API Endpoint", "dynamic": false, "info": "API endpoint URL for the Astra DB service.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": false, "name": "api_endpoint", "password": true, @@ -1248,7 +1351,9 @@ "display_name": "Embedding Model", "dynamic": false, "info": "Allows an embedding model configuration.", - "input_types": ["Embeddings"], + "input_types": [ + "Embeddings" + ], "list": false, "name": "embedding_model", "placeholder": "", @@ -1265,7 +1370,9 @@ "display_name": "Ingest Data", "dynamic": false, "info": "", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": false, "name": "ingest_data", "placeholder": "", @@ -1330,7 +1437,9 @@ "tool_mode": false, "trace_as_metadata": true, "type": "str", - "value": [""] + "value": [ + "" + ] }, "metadata_indexing_include": { "_input_type": "StrInput", @@ -1358,7 +1467,11 @@ "dynamic": false, "info": "Optional distance metric for vector comparisons in the vector store.", "name": "metric", - "options": ["cosine", "dot_product", "euclidean"], + "options": [ + "cosine", + "dot_product", + "euclidean" + ], "placeholder": "", "required": false, "show": true, @@ -1426,7 +1539,9 @@ "display_name": "Search Query", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1491,7 +1606,10 @@ "dynamic": false, "info": "Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.", "name": "setup_mode", - "options": ["Sync", "Off"], + "options": [ + "Sync", + "Off" + ], "placeholder": "", "required": false, "show": true, @@ -1507,7 +1625,9 @@ "display_name": "Astra DB Application Token", "dynamic": false, "info": "Authentication token for accessing Astra DB.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": false, "name": "token", "password": true, @@ -1542,7 +1662,10 @@ "display_name": "Parse Data", "id": "ParseData-T6FGT", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1550,7 +1673,11 @@ "display_name": "Parse Data", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "legacy": false, @@ -1569,7 +1696,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1580,7 +1709,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -1611,7 +1742,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "name": "data", "placeholder": "", @@ -1648,7 +1781,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1685,17 +1820,25 @@ "data": { "id": "Prompt-2M2d5", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["context", "question"] + "template": [ + "context", + "question" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template", "tool_placeholder"], + "field_order": [ + "template", + "tool_placeholder" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -1711,7 +1854,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1744,7 +1889,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1764,7 +1912,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1800,7 +1951,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1836,7 +1989,10 @@ "data": { "id": "OpenAIModel-a26gL", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1873,7 +2029,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1882,10 +2040,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1898,7 +2060,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1933,7 +2097,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -2099,7 +2265,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "system_message", @@ -2180,7 +2348,9 @@ "data": { "id": "ChatOutput-XL9ho", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2214,7 +2384,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -2227,7 +2399,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -2247,7 +2421,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -2261,6 +2437,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -2277,7 +2471,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -2285,7 +2479,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -2305,12 +2501,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -2326,7 +2526,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -2342,7 +2545,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -2362,7 +2567,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -2398,7 +2605,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -2434,7 +2643,10 @@ "data": { "id": "URL-fyWIL", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "category": "data", "conditional_paths": [], @@ -2443,7 +2655,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "key": "URL", @@ -2461,7 +2676,9 @@ "name": "data", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -2472,7 +2689,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2483,7 +2702,9 @@ "name": "dataframe", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -2517,7 +2738,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "placeholder": "", "required": false, "show": true, @@ -2533,7 +2757,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "load_from_db": false, "name": "urls", @@ -2585,7 +2811,9 @@ "data": { "id": "AstraDBGraph-FX0tA", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "vectorstores", "conditional_paths": [], @@ -2633,10 +2861,16 @@ "display_name": "Search Results", "method": "search_documents", "name": "search_results", - "required_inputs": ["api_endpoint", "collection_name", "token"], + "required_inputs": [ + "api_endpoint", + "collection_name", + "token" + ], "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -2648,7 +2882,9 @@ "required_inputs": [], "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -2662,7 +2898,9 @@ "display_name": "API Endpoint", "dynamic": false, "info": "API endpoint URL for the Astra DB service.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": false, "name": "api_endpoint", "password": true, @@ -2801,7 +3039,9 @@ "display_name": "Embedding Model", "dynamic": false, "info": "Allows an embedding model configuration.", - "input_types": ["Embeddings"], + "input_types": [ + "Embeddings" + ], "list": false, "name": "embedding_model", "placeholder": "", @@ -2818,7 +3058,9 @@ "display_name": "Ingest Data", "dynamic": false, "info": "", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": false, "name": "ingest_data", "placeholder": "", @@ -2911,7 +3153,11 @@ "dynamic": false, "info": "Optional distance metric for vector comparisons in the vector store.", "name": "metric", - "options": ["cosine", "dot_product", "euclidean"], + "options": [ + "cosine", + "dot_product", + "euclidean" + ], "placeholder": "", "required": false, "show": true, @@ -2978,7 +3224,9 @@ "display_name": "Search Query", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -3042,7 +3290,10 @@ "dynamic": false, "info": "Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.", "name": "setup_mode", - "options": ["Sync", "Off"], + "options": [ + "Sync", + "Off" + ], "placeholder": "", "required": false, "show": true, @@ -3058,7 +3309,9 @@ "display_name": "Astra DB Application Token", "dynamic": false, "info": "Authentication token for accessing Astra DB.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": false, "name": "token", "password": true, @@ -3092,7 +3345,9 @@ "data": { "id": "LanguageRecursiveTextSplitter-jefpx", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "langchain_utilities", "conditional_paths": [], @@ -3125,7 +3380,9 @@ "required_inputs": [], "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -3236,7 +3493,10 @@ "display_name": "Input", "dynamic": false, "info": "The texts to split.", - "input_types": ["Document", "Data"], + "input_types": [ + "Document", + "Data" + ], "list": false, "name": "data_input", "placeholder": "", @@ -3272,7 +3532,9 @@ "data": { "id": "HtmlLinkExtractor-exHgk", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "langchain_utilities", "conditional_paths": [], @@ -3281,7 +3543,11 @@ "display_name": "HTML Link Extractor", "documentation": "https://python.langchain.com/v0.2/api_reference/community/graph_vectorstores/langchain_community.graph_vectorstores.extractors.html_link_extractor.HtmlLinkExtractor.html", "edited": false, - "field_order": ["kind", "drop_fragments", "data_input"], + "field_order": [ + "kind", + "drop_fragments", + "data_input" + ], "frozen": false, "icon": "LangChain", "key": "HtmlLinkExtractor", @@ -3300,7 +3566,9 @@ "required_inputs": [], "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -3332,7 +3600,10 @@ "display_name": "Input", "dynamic": false, "info": "The texts from which to extract links.", - "input_types": ["Document", "Data"], + "input_types": [ + "Document", + "Data" + ], "list": false, "name": "data_input", "placeholder": "", @@ -3403,7 +3674,9 @@ "data": { "id": "OpenAIEmbeddings-83wEc", "node": { - "base_classes": ["Embeddings"], + "base_classes": [ + "Embeddings" + ], "beta": false, "category": "embeddings", "conditional_paths": [], @@ -3450,10 +3723,14 @@ "display_name": "Embeddings", "method": "build_embeddings", "name": "embeddings", - "required_inputs": ["openai_api_key"], + "required_inputs": [ + "openai_api_key" + ], "selected": "Embeddings", "tool_mode": true, - "types": ["Embeddings"], + "types": [ + "Embeddings" + ], "value": "__UNDEFINED__" } ], @@ -3484,7 +3761,9 @@ "display_name": "Client", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "client", @@ -3556,7 +3835,9 @@ "display_name": "Deployment", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "deployment", @@ -3666,7 +3947,9 @@ "display_name": "OpenAI API Base", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_api_base", @@ -3686,7 +3969,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "openai_api_key", "password": true, @@ -3703,7 +3988,9 @@ "display_name": "OpenAI API Type", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_api_type", @@ -3723,7 +4010,9 @@ "display_name": "OpenAI API Version", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_api_version", @@ -3743,7 +4032,9 @@ "display_name": "OpenAI Organization", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_organization", @@ -3763,7 +4054,9 @@ "display_name": "OpenAI Proxy", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "openai_proxy", @@ -3851,7 +4144,9 @@ "display_name": "TikToken Model Name", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tiktoken_model_name", @@ -3976,5 +4271,8 @@ "is_component": false, "last_tested_version": "1.1.1", "name": "Graph RAG", - "tags": ["rag", "q-a"] -} + "tags": [ + "rag", + "q-a" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json index 5848ada195..d8fec22daa 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json @@ -9,12 +9,16 @@ "dataType": "StructuredOutputComponent", "id": "StructuredOutputComponent-XYoUc", "name": "structured_output", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-HzweJ", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -33,12 +37,18 @@ "dataType": "ParseData", "id": "ParseData-HzweJ", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-xQxLm", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -47,7 +57,7 @@ "source": "ParseData-HzweJ", "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-HzweJœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-xQxLm", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-xQxLmœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-xQxLmœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "className": "", @@ -56,12 +66,16 @@ "dataType": "ChatInput", "id": "ChatInput-rAWlE", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-cqeNw", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -79,12 +93,16 @@ "dataType": "Prompt", "id": "Prompt-AzK6t", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-cqeNw", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -102,12 +120,16 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-cqeNw", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "StructuredOutputComponent-XYoUc", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -125,12 +147,16 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-cqeNw", "name": "model_output", - "output_types": ["LanguageModel"] + "output_types": [ + "LanguageModel" + ] }, "targetHandle": { "fieldName": "llm", "id": "StructuredOutputComponent-XYoUc", - "inputTypes": ["LanguageModel"], + "inputTypes": [ + "LanguageModel" + ], "type": "other" } }, @@ -149,7 +175,9 @@ "display_name": "Chat Input", "id": "ChatInput-rAWlE", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -183,7 +211,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -196,7 +226,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -216,7 +248,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -319,7 +353,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -335,7 +372,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -355,7 +394,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -391,7 +432,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -435,7 +478,9 @@ "display_name": "Chat Output", "id": "ChatOutput-xQxLm", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -469,7 +514,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -482,7 +529,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -502,7 +551,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -516,6 +567,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -532,7 +601,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -540,7 +609,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -560,12 +631,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -581,7 +656,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -597,7 +675,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -617,7 +697,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -653,7 +735,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -732,7 +816,9 @@ "display_name": "Structured Output", "id": "StructuredOutputComponent-XYoUc", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -760,7 +846,9 @@ "method": "build_structured_output", "name": "structured_output", "selected": "Data", - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -791,7 +879,9 @@ "display_name": "Input message", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -811,7 +901,9 @@ "display_name": "Language Model", "dynamic": false, "info": "The language model to use to generate the structured output.", - "input_types": ["LanguageModel"], + "input_types": [ + "LanguageModel" + ], "list": false, "name": "llm", "placeholder": "", @@ -955,7 +1047,9 @@ "data": { "id": "ParseData-HzweJ", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -963,7 +1057,11 @@ "display_name": "Parse Data", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "legacy": false, @@ -981,7 +1079,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -992,7 +1092,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -1023,7 +1125,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "name": "data", "placeholder": "", @@ -1059,7 +1163,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1104,7 +1210,9 @@ "display_name": "Prompt", "id": "Prompt-AzK6t", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1114,7 +1222,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -1130,7 +1240,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1179,7 +1291,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1221,7 +1335,10 @@ "data": { "id": "OpenAIModel-cqeNw", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1260,7 +1377,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1269,10 +1388,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1286,7 +1409,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1321,7 +1446,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1503,7 +1630,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1599,5 +1728,7 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Image Sentiment Analysis", - "tags": ["classification"] -} + "tags": [ + "classification" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json index b1e90ea21a..653ea8ee12 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json @@ -7,22 +7,27 @@ "data": { "sourceHandle": { "dataType": "TextInput", - "id": "TextInput-OSpmP", + "id": "TextInput-jcEYO", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "guidelines", - "id": "Prompt-jlObO", - "inputTypes": ["Message", "Text"], + "id": "Prompt-5uTbs", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-TextInput-OSpmP{œdataTypeœ:œTextInputœ,œidœ:œTextInput-OSpmPœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-jlObO{œfieldNameœ:œguidelinesœ,œidœ:œPrompt-jlObOœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "TextInput-OSpmP", - "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-OSpmPœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-jlObO", - "targetHandle": "{œfieldNameœ: œguidelinesœ, œidœ: œPrompt-jlObOœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-TextInput-jcEYO{œdataTypeœ:œTextInputœ,œidœ:œTextInput-jcEYOœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-5uTbs{œfieldNameœ:œguidelinesœ,œidœ:œPrompt-5uTbsœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "TextInput-jcEYO", + "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-jcEYOœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-5uTbs", + "targetHandle": "{œfieldNameœ: œguidelinesœ, œidœ: œPrompt-5uTbsœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -30,22 +35,26 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-jogsq", + "id": "ChatInput-RiIAB", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-NNcr1", - "inputTypes": ["Message"], + "id": "Agent-nxMr2", + "inputTypes": [ + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-ChatInput-jogsq{œdataTypeœ:œChatInputœ,œidœ:œChatInput-jogsqœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-NNcr1{œfieldNameœ:œinput_valueœ,œidœ:œAgent-NNcr1œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ChatInput-jogsq", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-jogsqœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-NNcr1", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-NNcr1œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ChatInput-RiIAB{œdataTypeœ:œChatInputœ,œidœ:œChatInput-RiIABœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-nxMr2{œfieldNameœ:œinput_valueœ,œidœ:œAgent-nxMr2œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ChatInput-RiIAB", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-RiIABœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-nxMr2", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-nxMr2œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -53,185 +62,223 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-NNcr1", + "id": "Agent-nxMr2", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "context", - "id": "Prompt-jlObO", - "inputTypes": ["Message", "Text"], + "id": "Prompt-5uTbs", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-Agent-NNcr1{œdataTypeœ:œAgentœ,œidœ:œAgent-NNcr1œ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-jlObO{œfieldNameœ:œcontextœ,œidœ:œPrompt-jlObOœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "Agent-NNcr1", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-NNcr1œ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-jlObO", - "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-jlObOœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" - }, - { - "animated": false, - "className": "", - "data": { - "sourceHandle": { - "dataType": "Prompt", - "id": "Prompt-HtPaM", - "name": "prompt", - "output_types": ["Message"] - }, - "targetHandle": { - "fieldName": "input_value", - "id": "ChatOutput-L2u2A", - "inputTypes": ["Message"], - "type": "str" - } - }, - "id": "reactflow__edge-Prompt-HtPaM{œdataTypeœ:œPromptœ,œidœ:œPrompt-HtPaMœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-L2u2A{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-L2u2Aœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Prompt-HtPaM", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-HtPaMœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-L2u2A", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-L2u2Aœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Agent-nxMr2{œdataTypeœ:œAgentœ,œidœ:œAgent-nxMr2œ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-5uTbs{œfieldNameœ:œcontextœ,œidœ:œPrompt-5uTbsœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "Agent-nxMr2", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-nxMr2œ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-5uTbs", + "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-5uTbsœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "TavilySearchComponent", - "id": "TavilySearchComponent-mnzRD", + "id": "TavilySearchComponent-XbcIl", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", - "id": "Agent-NNcr1", - "inputTypes": ["Tool"], + "id": "Agent-nxMr2", + "inputTypes": [ + "Tool" + ], "type": "other" } }, - "id": "reactflow__edge-TavilySearchComponent-mnzRD{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-mnzRDœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-NNcr1{œfieldNameœ:œtoolsœ,œidœ:œAgent-NNcr1œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", - "source": "TavilySearchComponent-mnzRD", - "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-mnzRDœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-NNcr1", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-NNcr1œ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-TavilySearchComponent-XbcIl{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-XbcIlœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-nxMr2{œfieldNameœ:œtoolsœ,œidœ:œAgent-nxMr2œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "source": "TavilySearchComponent-XbcIl", + "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-XbcIlœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-nxMr2", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-nxMr2œ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-jlObO", + "id": "Prompt-5uTbs", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "OpenAIModel-qQ00F", - "inputTypes": ["Message"], + "id": "OpenAIModel-BUxp5", + "inputTypes": [ + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-Prompt-jlObO{œdataTypeœ:œPromptœ,œidœ:œPrompt-jlObOœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-qQ00F{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-qQ00Fœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Prompt-jlObO", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-jlObOœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "OpenAIModel-qQ00F", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-qQ00Fœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Prompt-5uTbs{œdataTypeœ:œPromptœ,œidœ:œPrompt-5uTbsœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-BUxp5{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-BUxp5œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "Prompt-5uTbs", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-5uTbsœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "OpenAIModel-BUxp5", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-BUxp5œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "OpenAIModel", - "id": "OpenAIModel-qQ00F", + "id": "OpenAIModel-BUxp5", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "post", - "id": "Prompt-qF4uD", - "inputTypes": ["Message", "Text"], + "id": "Prompt-vNJrn", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-OpenAIModel-qQ00F{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-qQ00Fœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-qF4uD{œfieldNameœ:œpostœ,œidœ:œPrompt-qF4uDœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "OpenAIModel-qQ00F", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-qQ00Fœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-qF4uD", - "targetHandle": "{œfieldNameœ: œpostœ, œidœ: œPrompt-qF4uDœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-OpenAIModel-BUxp5{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-BUxp5œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-vNJrn{œfieldNameœ:œpostœ,œidœ:œPrompt-vNJrnœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "OpenAIModel-BUxp5", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-BUxp5œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-vNJrn", + "targetHandle": "{œfieldNameœ: œpostœ, œidœ: œPrompt-vNJrnœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "OpenAIModel", - "id": "OpenAIModel-qQ00F", + "id": "OpenAIModel-BUxp5", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "post", - "id": "Prompt-HtPaM", - "inputTypes": ["Message", "Text"], + "id": "Prompt-kfn20", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-OpenAIModel-qQ00F{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-qQ00Fœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-HtPaM{œfieldNameœ:œpostœ,œidœ:œPrompt-HtPaMœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "OpenAIModel-qQ00F", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-qQ00Fœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-HtPaM", - "targetHandle": "{œfieldNameœ: œpostœ, œidœ: œPrompt-HtPaMœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-OpenAIModel-BUxp5{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-BUxp5œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-kfn20{œfieldNameœ:œpostœ,œidœ:œPrompt-kfn20œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "OpenAIModel-BUxp5", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-BUxp5œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-kfn20", + "targetHandle": "{œfieldNameœ: œpostœ, œidœ: œPrompt-kfn20œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-qF4uD", + "id": "Prompt-vNJrn", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "OpenAIModel-vHqv4", - "inputTypes": ["Message"], + "id": "OpenAIModel-EuPBl", + "inputTypes": [ + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-Prompt-qF4uD{œdataTypeœ:œPromptœ,œidœ:œPrompt-qF4uDœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-vHqv4{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-vHqv4œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Prompt-qF4uD", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-qF4uDœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "OpenAIModel-vHqv4", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-vHqv4œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Prompt-vNJrn{œdataTypeœ:œPromptœ,œidœ:œPrompt-vNJrnœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-EuPBl{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-EuPBlœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "Prompt-vNJrn", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-vNJrnœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "OpenAIModel-EuPBl", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-EuPBlœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "OpenAIModel", - "id": "OpenAIModel-vHqv4", + "id": "OpenAIModel-EuPBl", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "image_description", - "id": "Prompt-HtPaM", - "inputTypes": ["Message", "Text"], + "id": "Prompt-kfn20", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-OpenAIModel-vHqv4{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-vHqv4œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-HtPaM{œfieldNameœ:œimage_descriptionœ,œidœ:œPrompt-HtPaMœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "OpenAIModel-vHqv4", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-vHqv4œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-HtPaM", - "targetHandle": "{œfieldNameœ: œimage_descriptionœ, œidœ: œPrompt-HtPaMœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-OpenAIModel-EuPBl{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-EuPBlœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-kfn20{œfieldNameœ:œimage_descriptionœ,œidœ:œPrompt-kfn20œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "OpenAIModel-EuPBl", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-EuPBlœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-kfn20", + "targetHandle": "{œfieldNameœ: œimage_descriptionœ, œidœ: œPrompt-kfn20œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + }, + { + "data": { + "sourceHandle": { + "dataType": "Prompt", + "id": "Prompt-kfn20", + "name": "prompt", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-AZo3Y", + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__Prompt-kfn20{œdataTypeœ:œPromptœ,œidœ:œPrompt-kfn20œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-AZo3Y{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-AZo3Yœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", + "source": "Prompt-kfn20", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-kfn20œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-AZo3Y", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-AZo3Yœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ { "data": { - "id": "ChatInput-jogsq", + "id": "ChatInput-RiIAB", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -265,7 +312,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -278,7 +327,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -297,7 +348,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -398,7 +451,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -413,7 +469,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -432,7 +490,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -467,7 +527,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -486,10 +548,10 @@ }, "dragging": false, "height": 234, - "id": "ChatInput-jogsq", + "id": "ChatInput-RiIAB", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 5183.264962599111, @@ -507,19 +569,26 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-jlObO", + "id": "Prompt-5uTbs", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["context", "guidelines"] + "template": [ + "context", + "guidelines" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -535,7 +604,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -568,7 +639,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -588,7 +662,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -624,7 +701,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -645,10 +724,10 @@ }, "dragging": false, "height": 433, - "id": "Prompt-jlObO", + "id": "Prompt-5uTbs", "measured": { "height": 433, - "width": 320 + "width": 360 }, "position": { "x": 6044.447585613556, @@ -664,9 +743,11 @@ }, { "data": { - "id": "TextInput-OSpmP", + "id": "TextInput-jcEYO", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -674,7 +755,9 @@ "display_name": "Text Input", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -690,7 +773,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -721,7 +806,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -741,10 +828,10 @@ }, "dragging": false, "height": 234, - "id": "TextInput-OSpmP", + "id": "TextInput-jcEYO", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 5672.768365094557, @@ -762,19 +849,25 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-qF4uD", + "id": "Prompt-vNJrn", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["post"] + "template": [ + "post" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -790,7 +883,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -823,7 +918,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -859,7 +957,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -880,10 +980,10 @@ }, "dragging": false, "height": 347, - "id": "Prompt-qF4uD", + "id": "Prompt-vNJrn", "measured": { "height": 347, - "width": 320 + "width": 360 }, "position": { "x": 6818.9410289594325, @@ -901,9 +1001,11 @@ "data": { "description": "Display a chat message in the Playground.", "display_name": "Chat Output", - "id": "ChatOutput-L2u2A", + "id": "ChatOutput-AZo3Y", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -936,7 +1038,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -949,7 +1053,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -969,7 +1075,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -983,6 +1091,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -999,7 +1125,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1007,7 +1133,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -1027,12 +1155,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -1048,7 +1180,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -1064,7 +1199,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1084,7 +1221,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1120,7 +1259,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -1141,10 +1282,10 @@ }, "dragging": false, "height": 234, - "id": "ChatOutput-L2u2A", + "id": "ChatOutput-AZo3Y", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 7980.617825443558, @@ -1162,9 +1303,11 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Agent", - "id": "Agent-NNcr1", + "id": "Agent-nxMr2", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1214,7 +1357,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1243,7 +1388,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1294,7 +1441,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1345,7 +1494,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -1437,7 +1588,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -1531,7 +1684,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1565,7 +1721,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1581,7 +1741,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1601,7 +1763,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1621,7 +1785,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1658,7 +1824,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1697,7 +1865,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1731,10 +1901,10 @@ }, "dragging": false, "height": 650, - "id": "Agent-NNcr1", + "id": "Agent-nxMr2", "measured": { "height": 650, - "width": 320 + "width": 360 }, "position": { "x": 5665.465212822881, @@ -1752,19 +1922,26 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-HtPaM", + "id": "Prompt-kfn20", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["post", "image_description"] + "template": [ + "post", + "image_description" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -1780,7 +1957,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1813,7 +1992,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1833,7 +2015,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1869,7 +2054,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1890,10 +2077,10 @@ }, "dragging": false, "height": 433, - "id": "Prompt-HtPaM", + "id": "Prompt-kfn20", "measured": { "height": 433, - "width": 320 + "width": 360 }, "position": { "x": 7613.837241084599, @@ -1909,7 +2096,7 @@ }, { "data": { - "id": "note-8p0Lz", + "id": "note-MxQ6f", "node": { "description": "# Instagram Copywriter \n\nWelcome to the Instagram Copywriter! This flow helps you create compelling Instagram posts with AI-generated content and image prompts.\n\n## Instructions\n1. Enter Your Topic\n - In the Chat Input, enter a brief description of the topic you want to post about.\n - Example: \"Create a post about meditation and its benefits\"\n\n2. Review the Generated Content\n - The flow will use AI to research your topic and generate a formatted Instagram post.\n - The post will include an opening line, main content, emojis, a call-to-action, and hashtags.\n\n3. Check the Image Prompt\n - The flow will also generate a detailed image prompt based on your post content.\n - This prompt can be used with image generation tools to create a matching visual.\n\n4. Copy the Final Output\n - The Chat Output will display the complete Instagram post text followed by the image generation prompt.\n - Copy this output to use in your Instagram content creation process.\n\n5. Refine if Needed\n - If you're not satisfied with the result, you can adjust the input or modify the OpenAI model settings for different outputs.\n\nRemember: Keep your initial topic input clear and concise for best results! 🎨✨", "display_name": "", @@ -1922,10 +2109,10 @@ }, "dragging": false, "height": 648, - "id": "note-8p0Lz", + "id": "note-MxQ6f", "measured": { "height": 648, - "width": 555 + "width": 558 }, "position": { "x": 4492.051129290571, @@ -1946,7 +2133,7 @@ }, { "data": { - "id": "note-yOL03", + "id": "note-sZVfP", "node": { "description": "**Text Input (Guidelines Prompt)**\n - NOTE: \"Contains Instagram post formatting rules. Don't modify this component as it maintains format consistency.\"\n - Maintains fixed guidelines for:\n * Opening structure\n * Main content\n * Emoji usage\n * Call to Action (CTA)\n * Hashtags\n\n4. **First Prompt + OpenAI Sequence**\n - NOTE: \"Generates initial post content following Instagram guidelines\"\n - Settings:\n * Temperature: 0.7 (good balance between creativity and consistency)\n * Input: Receives research context\n * Output: Generates formatted post text\n\n", "display_name": "", @@ -1959,10 +2146,10 @@ }, "dragging": false, "height": 325, - "id": "note-yOL03", + "id": "note-sZVfP", "measured": { "height": 325, - "width": 326 + "width": 329 }, "position": { "x": 5666.120349284508, @@ -1979,7 +2166,7 @@ }, { "data": { - "id": "note-FH7oq", + "id": "note-psLpP", "node": { "description": "**Second Prompt + OpenAI Sequence**\n - NOTE: \"Transforms the generated post into a prompt for image generation\"\n - Settings:\n * Temperature: 0.7\n * Input: Receives generated post\n * Output: Creates detailed description for image generation\n\n", "display_name": "", @@ -1992,10 +2179,10 @@ }, "dragging": false, "height": 325, - "id": "note-FH7oq", + "id": "note-psLpP", "measured": { "height": 325, - "width": 326 + "width": 329 }, "position": { "x": 6786.375917286389, @@ -2011,7 +2198,7 @@ }, { "data": { - "id": "note-wvs7l", + "id": "note-4jPBZ", "node": { "description": "**Final Prompt**\n - NOTE: \"Combines Instagram post with image prompt in a final format\"\n - Structure:\n * First part: Complete Instagram post\n * Second part: Image generation prompt\n * Separator: Uses \"**Prompt:**\" to divide sections\n\n7. **Chat Output (Final Output)**\n - NOTE: \"Presents the combined final result that can be copied and used directly\"\n\nGENERAL USAGE TIPS:\n- Keep initial inputs clear and specific\n- Don't modify pre-defined Instagram guidelines\n- If style adjustments are needed, only modify the OpenAI models' temperature\n- Verify all connections are correct before running\n- Final result will always have two parts: post + image prompt\n\nFLOW CONSIDERATIONS:\n- All tools connect only to the Tool Calling Agent\n- The flow is unidirectional (no loops)\n- Each prompt template maintains specific formatting\n- Temperatures are set for optimal creativity/consistency balance\n\nTROUBLESHOOTING NOTES:\n- If output is too creative: Lower temperature", "display_name": "", @@ -2024,10 +2211,10 @@ }, "dragging": false, "height": 325, - "id": "note-wvs7l", + "id": "note-4jPBZ", "measured": { "height": 325, - "width": 326 + "width": 329 }, "position": { "x": 7606.419013912975, @@ -2043,7 +2230,7 @@ }, { "data": { - "id": "note-Hj9Ay", + "id": "note-WwfNL", "node": { "description": "# 🔑 Tavily AI Search Needs API Key\n\nYou can get 1000 searches/month free [here](https://tavily.com/) ", "display_name": "", @@ -2056,10 +2243,10 @@ }, "dragging": false, "height": 325, - "id": "note-Hj9Ay", + "id": "note-WwfNL", "measured": { "height": 325, - "width": 326 + "width": 329 }, "position": { "x": 5174.678177457385, @@ -2075,9 +2262,12 @@ }, { "data": { - "id": "TavilySearchComponent-mnzRD", + "id": "TavilySearchComponent-XbcIl", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2112,7 +2302,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -2125,8 +2317,10 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": ["Message"], - "load_from_db": true, + "input_types": [ + "Message" + ], + "load_from_db": false, "name": "api_key", "password": true, "placeholder": "", @@ -2214,7 +2408,9 @@ "display_name": "Search Query", "dynamic": false, "info": "The search query you want to execute with Tavily.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2238,7 +2434,10 @@ "dynamic": false, "info": "The depth of the search.", "name": "search_depth", - "options": ["basic", "advanced"], + "options": [ + "basic", + "advanced" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2258,7 +2457,12 @@ "dynamic": false, "info": "The time range back from the current date to include in the search results.", "name": "time_range", - "options": ["day", "week", "month", "year"], + "options": [ + "day", + "week", + "month", + "year" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2293,7 +2497,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -2347,12 +2554,16 @@ { "description": "fetch_content(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content", - "tags": ["TavilySearchComponent-fetch_content"] + "tags": [ + "TavilySearchComponent-fetch_content" + ] }, { "description": "fetch_content_text(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content_text", - "tags": ["TavilySearchComponent-fetch_content_text"] + "tags": [ + "TavilySearchComponent-fetch_content_text" + ] } ] }, @@ -2365,7 +2576,10 @@ "dynamic": false, "info": "The category of the search.", "name": "topic", - "options": ["general", "news"], + "options": [ + "general", + "news" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2383,10 +2597,10 @@ "type": "TavilySearchComponent" }, "dragging": false, - "id": "TavilySearchComponent-mnzRD", + "id": "TavilySearchComponent-XbcIl", "measured": { - "height": 435, - "width": 320 + "height": 489, + "width": 360 }, "position": { "x": 5176.638828210268, @@ -2397,9 +2611,12 @@ }, { "data": { - "id": "OpenAIModel-qQ00F", + "id": "OpenAIModel-BUxp5", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -2438,7 +2655,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2447,10 +2666,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -2464,7 +2687,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -2499,7 +2724,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2681,7 +2908,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2750,10 +2979,10 @@ "type": "OpenAIModel" }, "dragging": false, - "id": "OpenAIModel-qQ00F", + "id": "OpenAIModel-BUxp5", "measured": { - "height": 653, - "width": 320 + "height": 734, + "width": 360 }, "position": { "x": 6411.984293767987, @@ -2764,9 +2993,12 @@ }, { "data": { - "id": "OpenAIModel-vHqv4", + "id": "OpenAIModel-EuPBl", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -2805,7 +3037,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2814,10 +3048,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -2831,7 +3069,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -2866,7 +3106,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3048,7 +3290,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3117,10 +3361,10 @@ "type": "OpenAIModel" }, "dragging": false, - "id": "OpenAIModel-vHqv4", + "id": "OpenAIModel-EuPBl", "measured": { - "height": 653, - "width": 320 + "height": 734, + "width": 360 }, "position": { "x": 7206.924894456788, @@ -3131,9 +3375,9 @@ } ], "viewport": { - "x": -1398.4765287647906, - "y": -468.6581744031994, - "zoom": 0.3251044072386861 + "x": -6905.9718468947285, + "y": -3158.1897396901054, + "zoom": 1.0286473888530476 } }, "description": " Create engaging Instagram posts with AI-generated content and image prompts, streamlining social media content creation.", @@ -3144,5 +3388,9 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Instagram Copywriter", - "tags": ["content-generation", "chatbots", "agents"] -} + "tags": [ + "content-generation", + "chatbots", + "agents" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json index 3e715d038b..1068a88761 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json @@ -9,12 +9,16 @@ "dataType": "ArXivComponent", "id": "ArXivComponent-LChQN", "name": "papers", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "LoopComponent-3vpc1", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -32,12 +36,16 @@ "dataType": "LoopComponent", "id": "LoopComponent-3vpc1", "name": "item", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-Pf12J", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -55,12 +63,16 @@ "dataType": "ParseData", "id": "ParseData-Pf12J", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "AnthropicModel-beO6B", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -78,12 +90,16 @@ "dataType": "AnthropicModel", "id": "AnthropicModel-beO6B", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "message", "id": "MessagetoData-QRSBb", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -101,13 +117,17 @@ "dataType": "MessagetoData", "id": "MessagetoData-QRSBb", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "dataType": "LoopComponent", "id": "LoopComponent-3vpc1", "name": "item", - "output_types": ["Data"] + "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œ]}", @@ -124,12 +144,16 @@ "dataType": "LoopComponent", "id": "LoopComponent-3vpc1", "name": "done", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-igEkj", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -147,12 +171,18 @@ "dataType": "ParseData", "id": "ParseData-igEkj", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-UZgon", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -160,7 +190,7 @@ "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œ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-UZgonœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -170,12 +200,16 @@ "dataType": "ChatInput", "id": "ChatInput-m10vc", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "search_query", "id": "ArXivComponent-LChQN", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -191,7 +225,9 @@ "data": { "id": "ArXivComponent-LChQN", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -199,7 +235,11 @@ "display_name": "arXiv", "documentation": "", "edited": false, - "field_order": ["search_query", "search_type", "max_results"], + "field_order": [ + "search_query", + "search_type", + "max_results" + ], "frozen": false, "icon": "arXiv", "legacy": false, @@ -216,7 +256,9 @@ "name": "papers", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -265,7 +307,9 @@ "display_name": "Search Query", "dynamic": false, "info": "The search query for arXiv papers (e.g., 'quantum computing')", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -289,7 +333,13 @@ "dynamic": false, "info": "The field to search in", "name": "search_type", - "options": ["all", "title", "abstract", "author", "cat"], + "options": [ + "all", + "title", + "abstract", + "author", + "cat" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -323,7 +373,9 @@ "data": { "id": "LoopComponent-3vpc1", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "logic", "conditional_paths": [], @@ -332,7 +384,9 @@ "display_name": "Loop", "documentation": "", "edited": false, - "field_order": ["data"], + "field_order": [ + "data" + ], "frozen": false, "icon": "infinity", "key": "LoopComponent", @@ -350,7 +404,9 @@ "name": "item", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -361,7 +417,9 @@ "name": "done", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -393,7 +451,9 @@ "display_name": "Data", "dynamic": false, "info": "The initial list of Data objects to iterate over.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": false, "list_add_label": "Add More", "name": "data", @@ -430,7 +490,10 @@ "data": { "id": "ParseData-Pf12J", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "category": "processing", "conditional_paths": [], @@ -439,7 +502,11 @@ "display_name": "Data to Message", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "key": "ParseData", @@ -459,7 +526,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -470,7 +539,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -502,7 +573,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "list_add_label": "Add More", "name": "data", @@ -541,7 +614,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -580,7 +655,10 @@ "data": { "id": "AnthropicModel-beO6B", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -619,7 +697,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -628,10 +708,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -645,7 +729,9 @@ "display_name": "Anthropic API Key", "dynamic": false, "info": "Your Anthropic API key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -663,7 +749,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -703,7 +791,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -772,7 +862,9 @@ "display_name": "Prefill", "dynamic": false, "info": "Prefill text to guide the model's response.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -811,7 +903,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -897,7 +991,9 @@ "data": { "id": "MessagetoData-QRSBb", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": true, "category": "processing", "conditional_paths": [], @@ -906,7 +1002,9 @@ "display_name": "Message to Data", "documentation": "", "edited": false, - "field_order": ["message"], + "field_order": [ + "message" + ], "frozen": false, "icon": "message-square-share", "key": "MessagetoData", @@ -924,7 +1022,9 @@ "name": "data", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -956,7 +1056,9 @@ "display_name": "Message", "dynamic": false, "info": "The Message object to convert to a Data object", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -994,7 +1096,10 @@ "data": { "id": "ParseData-igEkj", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "category": "processing", "conditional_paths": [], @@ -1003,7 +1108,11 @@ "display_name": "Data to Message", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "key": "ParseData", @@ -1023,7 +1132,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1034,7 +1145,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -1066,7 +1179,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "list_add_label": "Add More", "name": "data", @@ -1105,7 +1220,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1144,7 +1261,9 @@ "data": { "id": "ChatOutput-UZgon", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1179,7 +1298,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1192,7 +1313,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1213,7 +1336,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1228,6 +1353,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1244,7 +1387,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1252,7 +1395,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1273,13 +1418,17 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -1297,7 +1446,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -1314,7 +1466,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1335,7 +1489,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1374,7 +1530,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1412,7 +1570,9 @@ "data": { "id": "ChatInput-m10vc", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1447,7 +1607,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1460,7 +1622,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1481,7 +1645,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1588,7 +1754,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -1605,7 +1774,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1626,7 +1797,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1665,7 +1838,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1770,5 +1945,8 @@ "is_component": false, "last_tested_version": "1.1.5", "name": "Research Translation Loop", - "tags": ["chatbots", "content-generation"] -} + "tags": [ + "chatbots", + "content-generation" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json index 3b4a38c363..50aefd95a0 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json @@ -3,145 +3,168 @@ "edges": [ { "animated": false, - "className": "not-running", + "className": "", "data": { "sourceHandle": { "dataType": "StructuredOutputComponent", - "id": "StructuredOutputComponent-EguHE", + "id": "StructuredOutputComponent-Kqbq4", "name": "structured_output", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", - "id": "ParseData-68wKj", - "inputTypes": ["Data"], + "id": "ParseData-7XOFR", + "inputTypes": [ + "Data" + ], "type": "other" } }, - "id": "reactflow__edge-StructuredOutputComponent-EguHE{œdataTypeœ:œStructuredOutputComponentœ,œidœ:œStructuredOutputComponent-EguHEœ,œnameœ:œstructured_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-68wKj{œfieldNameœ:œdataœ,œidœ:œParseData-68wKjœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-StructuredOutputComponent-Kqbq4{œdataTypeœ:œStructuredOutputComponentœ,œidœ:œStructuredOutputComponent-Kqbq4œ,œnameœ:œstructured_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-7XOFR{œfieldNameœ:œdataœ,œidœ:œParseData-7XOFRœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", "selected": false, - "source": "StructuredOutputComponent-EguHE", - "sourceHandle": "{œdataTypeœ: œStructuredOutputComponentœ, œidœ: œStructuredOutputComponent-EguHEœ, œnameœ: œstructured_outputœ, œoutput_typesœ: [œDataœ]}", - "target": "ParseData-68wKj", - "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-68wKjœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "source": "StructuredOutputComponent-Kqbq4", + "sourceHandle": "{œdataTypeœ: œStructuredOutputComponentœ, œidœ: œStructuredOutputComponent-Kqbq4œ, œnameœ: œstructured_outputœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-7XOFR", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-7XOFRœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { "animated": false, - "className": "not-running", - "data": { - "sourceHandle": { - "dataType": "ParseData", - "id": "ParseData-68wKj", - "name": "text", - "output_types": ["Message"] - }, - "targetHandle": { - "fieldName": "input_value", - "id": "ChatOutput-VA5gt", - "inputTypes": ["Message"], - "type": "str" - } - }, - "id": "reactflow__edge-ParseData-68wKj{œdataTypeœ:œParseDataœ,œidœ:œParseData-68wKjœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-VA5gt{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-VA5gtœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "selected": false, - "source": "ParseData-68wKj", - "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-68wKjœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-VA5gt", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-VA5gtœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" - }, - { - "animated": false, - "className": "ran", + "className": "", "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-FDuyL", + "id": "ChatInput-tMLRq", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-rqbrW", - "inputTypes": ["Message"], + "id": "Agent-dcKuR", + "inputTypes": [ + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-ChatInput-FDuyL{œdataTypeœ:œChatInputœ,œidœ:œChatInput-FDuyLœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-rqbrW{œfieldNameœ:œinput_valueœ,œidœ:œAgent-rqbrWœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ChatInput-tMLRq{œdataTypeœ:œChatInputœ,œidœ:œChatInput-tMLRqœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-dcKuR{œfieldNameœ:œinput_valueœ,œidœ:œAgent-dcKuRœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ChatInput-FDuyL", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-FDuyLœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-rqbrW", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-rqbrWœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ChatInput-tMLRq", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-tMLRqœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-dcKuR", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-dcKuRœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": true, - "className": "running", + "className": "", "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-rqbrW", + "id": "Agent-dcKuR", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "StructuredOutputComponent-EguHE", - "inputTypes": ["Message"], + "id": "StructuredOutputComponent-Kqbq4", + "inputTypes": [ + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-Agent-rqbrW{œdataTypeœ:œAgentœ,œidœ:œAgent-rqbrWœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-StructuredOutputComponent-EguHE{œfieldNameœ:œinput_valueœ,œidœ:œStructuredOutputComponent-EguHEœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Agent-dcKuR{œdataTypeœ:œAgentœ,œidœ:œAgent-dcKuRœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-StructuredOutputComponent-Kqbq4{œfieldNameœ:œinput_valueœ,œidœ:œStructuredOutputComponent-Kqbq4œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Agent-rqbrW", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-rqbrWœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "StructuredOutputComponent-EguHE", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œStructuredOutputComponent-EguHEœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Agent-dcKuR", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-dcKuRœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "StructuredOutputComponent-Kqbq4", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œStructuredOutputComponent-Kqbq4œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, - "className": "ran", + "className": "", "data": { "sourceHandle": { "dataType": "TavilySearchComponent", - "id": "TavilySearchComponent-EoHje", + "id": "TavilySearchComponent-cGK9T", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", - "id": "Agent-rqbrW", - "inputTypes": ["Tool"], + "id": "Agent-dcKuR", + "inputTypes": [ + "Tool" + ], "type": "other" } }, - "id": "reactflow__edge-TavilySearchComponent-EoHje{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-EoHjeœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-rqbrW{œfieldNameœ:œtoolsœ,œidœ:œAgent-rqbrWœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", - "source": "TavilySearchComponent-EoHje", - "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-EoHjeœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-rqbrW", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-rqbrWœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-TavilySearchComponent-cGK9T{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-cGK9Tœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-dcKuR{œfieldNameœ:œtoolsœ,œidœ:œAgent-dcKuRœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "source": "TavilySearchComponent-cGK9T", + "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-cGK9Tœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-dcKuR", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-dcKuRœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, - "className": "not-running", + "className": "", "data": { "sourceHandle": { "dataType": "OpenAIModel", - "id": "OpenAIModel-aSzMF", + "id": "OpenAIModel-prL67", "name": "model_output", - "output_types": ["LanguageModel"] + "output_types": [ + "LanguageModel" + ] }, "targetHandle": { "fieldName": "llm", - "id": "StructuredOutputComponent-EguHE", - "inputTypes": ["LanguageModel"], + "id": "StructuredOutputComponent-Kqbq4", + "inputTypes": [ + "LanguageModel" + ], "type": "other" } }, - "id": "reactflow__edge-OpenAIModel-aSzMF{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-aSzMFœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-StructuredOutputComponent-EguHE{œfieldNameœ:œllmœ,œidœ:œStructuredOutputComponent-EguHEœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}", - "source": "OpenAIModel-aSzMF", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-aSzMFœ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}", - "target": "StructuredOutputComponent-EguHE", - "targetHandle": "{œfieldNameœ: œllmœ, œidœ: œStructuredOutputComponent-EguHEœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-OpenAIModel-prL67{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-prL67œ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-StructuredOutputComponent-Kqbq4{œfieldNameœ:œllmœ,œidœ:œStructuredOutputComponent-Kqbq4œ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}", + "source": "OpenAIModel-prL67", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-prL67œ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}", + "target": "StructuredOutputComponent-Kqbq4", + "targetHandle": "{œfieldNameœ: œllmœ, œidœ: œStructuredOutputComponent-Kqbq4œ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}" + }, + { + "data": { + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-7XOFR", + "name": "text", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-JrLxU", + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ParseData-7XOFR{œdataTypeœ:œParseDataœ,œidœ:œParseData-7XOFRœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-JrLxU{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-JrLxUœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", + "source": "ParseData-7XOFR", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-7XOFRœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-JrLxU", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-JrLxUœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -149,9 +172,11 @@ "data": { "description": "Get chat inputs from the Playground.", "display_name": "Chat Input", - "id": "ChatInput-FDuyL", + "id": "ChatInput-tMLRq", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -185,7 +210,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -198,7 +225,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -217,7 +246,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -318,7 +349,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -333,7 +367,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -352,7 +388,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -387,7 +425,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -406,10 +446,10 @@ }, "dragging": false, "height": 234, - "id": "ChatInput-FDuyL", + "id": "ChatInput-tMLRq", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 472.38251755471583, @@ -427,9 +467,11 @@ "data": { "description": "Display a chat message in the Playground.", "display_name": "Chat Output", - "id": "ChatOutput-VA5gt", + "id": "ChatOutput-JrLxU", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -463,7 +505,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -476,7 +520,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -496,7 +542,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -510,6 +558,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -526,7 +592,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -534,7 +600,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -554,12 +622,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -575,7 +647,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -591,7 +666,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -611,7 +688,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -647,7 +726,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -668,10 +749,10 @@ }, "dragging": false, "height": 234, - "id": "ChatOutput-VA5gt", + "id": "ChatOutput-JrLxU", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 2518.282039019285, @@ -681,13 +762,13 @@ "x": 2518.282039019285, "y": 855.3686932779933 }, - "selected": false, + "selected": true, "type": "genericNode", "width": 320 }, { "data": { - "id": "note-nklZw", + "id": "note-IHTZn", "node": { "description": "The StructuredOutputComponent, when utilized with our company information schema, performs the following functions:\n\n1. Accepts an input query regarding a company.\n2. Employs a Language Model (LLM) to analyze the query.\n3. Instructs the LLM to generate a structured response adhering to the predefined schema:\n - Domain\n - LinkedIn URL\n - Cheapest Plan\n - Has Free Trial\n - Has Enterprise Plan\n - Has API\n - Market\n - Pricing Tiers\n - Key Features\n - Target Industries\n\n4. Validates the LLM output against this schema.\n5. Returns a Data object containing the company information structured according to the schema.\n\nIn essence, this component transforms a free-text query about a company into a structured, consistent dataset, facilitating subsequent analysis and application of the information.", "display_name": "", @@ -700,10 +781,10 @@ }, "dragging": false, "height": 324, - "id": "note-nklZw", + "id": "note-IHTZn", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 2089.5869930853464, @@ -724,7 +805,7 @@ }, { "data": { - "id": "note-PXFPj", + "id": "note-oro5s", "node": { "description": "PURPOSE:\nConverts unstructured company research into standardized JSON format\n\nKEY FUNCTIONS:\n- Extracts specific business data points\n- Validates and formats information\n- Ensures data consistency\n\nINPUT:\n- Raw company research data\n\nOUTPUT:\nStructured JSON with:\n- Domain information\n- Social links\n- Pricing details\n- Feature availability\n- Market classification\n- Product features\n- Industry focus\n\nRULES:\n1. Uses strict boolean values\n2. Standardizes pricing formats\n3. Validates market categories\n4. Handles missing data consistently", "display_name": "", @@ -737,10 +818,10 @@ }, "dragging": false, "height": 324, - "id": "note-PXFPj", + "id": "note-oro5s", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 1237.6627823432912, @@ -761,7 +842,7 @@ }, { "data": { - "id": "note-TgJSC", + "id": "note-b5iUI", "node": { "description": "# Market Research\nThis flow helps you gather comprehensive information about companies for sales and business intelligence purposes.\n\n## Instructions\n\n1. Enter Company Name\n - In the Chat Input node, type the name of the company you want to research\n - Example inputs: \"Salesforce.com\", \"Shopify\", \"Zoom Video Communications\"\n\n2. Initiate Research\n - The Agent will use the Tavily AI Search tool to gather information\n - It will focus on key areas like pricing, features, and market positioning\n\n3. Review Structured Output\n - The flow will generate a structured JSON output with standardized fields\n - This includes domain, LinkedIn URL, pricing details, and key features\n\n4. Examine Formatted Results\n - The Parse Data component will convert the JSON into a readable format\n - You'll see a comprehensive company profile with organized sections\n\n5. Analyze and Use Data\n - Use the generated information for sales prospecting, competitive analysis, or market research\n - The structured format allows for easy comparison between different companies\n\nRemember: Always verify critical information from official sources before making business decisions! 🔍💼", "display_name": "", @@ -774,10 +855,10 @@ }, "dragging": false, "height": 324, - "id": "note-TgJSC", + "id": "note-b5iUI", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 244.92297036777086, @@ -800,9 +881,11 @@ "data": { "description": "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information or creating consistent outputs.", "display_name": "Structured Output", - "id": "StructuredOutputComponent-EguHE", + "id": "StructuredOutputComponent-Kqbq4", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -830,7 +913,9 @@ "method": "build_structured_output", "name": "structured_output", "selected": "Data", - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -861,7 +946,9 @@ "display_name": "Input message", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -881,7 +968,9 @@ "display_name": "Language Model", "dynamic": false, "info": "The language model to use to generate the structured output.", - "input_types": ["LanguageModel"], + "input_types": [ + "LanguageModel" + ], "list": false, "name": "llm", "placeholder": "", @@ -1052,10 +1141,10 @@ }, "dragging": false, "height": 541, - "id": "StructuredOutputComponent-EguHE", + "id": "StructuredOutputComponent-Kqbq4", "measured": { "height": 541, - "width": 320 + "width": 360 }, "position": { "x": 1716.7237308033855, @@ -1071,9 +1160,11 @@ }, { "data": { - "id": "ParseData-68wKj", + "id": "ParseData-7XOFR", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "helpers", "conditional_paths": [], @@ -1082,7 +1173,11 @@ "display_name": "Parse Data", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "key": "ParseData", @@ -1101,7 +1196,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1112,7 +1209,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -1143,7 +1242,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "name": "data", "placeholder": "", @@ -1178,7 +1279,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1198,10 +1301,10 @@ }, "dragging": false, "height": 302, - "id": "ParseData-68wKj", + "id": "ParseData-7XOFR", "measured": { "height": 302, - "width": 320 + "width": 360 }, "position": { "x": 2139.05558520377, @@ -1219,9 +1322,11 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Agent", - "id": "Agent-rqbrW", + "id": "Agent-dcKuR", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1272,7 +1377,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1301,7 +1408,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1352,7 +1461,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1361,7 +1472,7 @@ "show": true, "title_case": false, "type": "str", - "value": "TAVILY_API_KEY" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, @@ -1403,7 +1514,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -1495,7 +1608,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -1589,7 +1704,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1623,7 +1741,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1639,7 +1761,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1659,7 +1783,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1679,7 +1805,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1716,7 +1844,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1755,7 +1885,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1789,10 +1921,10 @@ }, "dragging": false, "height": 650, - "id": "Agent-rqbrW", + "id": "Agent-dcKuR", "measured": { "height": 650, - "width": 320 + "width": 360 }, "position": { "x": 1287.5681517817056, @@ -1804,7 +1936,7 @@ }, { "data": { - "id": "note-KpRgA", + "id": "note-7JQqd", "node": { "description": "# 🔑 Tavily AI Search Needs API Key\n\nYou can get 1000 searches/month free [here](https://tavily.com/) ", "display_name": "", @@ -1817,10 +1949,10 @@ }, "dragging": false, "height": 324, - "id": "note-KpRgA", + "id": "note-7JQqd", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 878.7898510090017, @@ -1838,9 +1970,12 @@ "data": { "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", - "id": "TavilySearchComponent-EoHje", + "id": "TavilySearchComponent-cGK9T", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1875,7 +2010,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -1888,8 +2025,10 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": ["Message"], - "load_from_db": true, + "input_types": [ + "Message" + ], + "load_from_db": false, "name": "api_key", "password": true, "placeholder": "", @@ -1977,7 +2116,9 @@ "display_name": "Search Query", "dynamic": false, "info": "The search query you want to execute with Tavily.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2001,7 +2142,10 @@ "dynamic": false, "info": "The depth of the search.", "name": "search_depth", - "options": ["basic", "advanced"], + "options": [ + "basic", + "advanced" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2021,7 +2165,12 @@ "dynamic": false, "info": "The time range back from the current date to include in the search results.", "name": "time_range", - "options": ["day", "week", "month", "year"], + "options": [ + "day", + "week", + "month", + "year" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2056,7 +2205,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -2110,12 +2262,16 @@ { "description": "fetch_content(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content", - "tags": ["TavilySearchComponent-fetch_content"] + "tags": [ + "TavilySearchComponent-fetch_content" + ] }, { "description": "fetch_content_text(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content_text", - "tags": ["TavilySearchComponent-fetch_content_text"] + "tags": [ + "TavilySearchComponent-fetch_content_text" + ] } ] }, @@ -2128,7 +2284,10 @@ "dynamic": false, "info": "The category of the search.", "name": "topic", - "options": ["general", "news"], + "options": [ + "general", + "news" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2146,10 +2305,10 @@ "type": "TavilySearchComponent" }, "dragging": false, - "id": "TavilySearchComponent-EoHje", + "id": "TavilySearchComponent-cGK9T", "measured": { - "height": 435, - "width": 320 + "height": 489, + "width": 360 }, "position": { "x": 875.7686789989679, @@ -2160,9 +2319,12 @@ }, { "data": { - "id": "OpenAIModel-aSzMF", + "id": "OpenAIModel-prL67", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -2201,7 +2363,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2210,10 +2374,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -2227,7 +2395,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -2262,7 +2432,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2444,7 +2616,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2513,10 +2687,10 @@ "type": "OpenAIModel" }, "dragging": false, - "id": "OpenAIModel-aSzMF", + "id": "OpenAIModel-prL67", "measured": { - "height": 653, - "width": 320 + "height": 734, + "width": 360 }, "position": { "x": 1718.9581068990763, @@ -2527,9 +2701,9 @@ } ], "viewport": { - "x": -55.02773146550817, - "y": 192.3971314158145, - "zoom": 0.4774432638923554 + "x": -49.933183848022736, + "y": 46.49268690110034, + "zoom": 0.4973930841732965 } }, "description": "Researches companies, extracts key business data, and presents structured information for efficient analysis. ", @@ -2540,5 +2714,8 @@ "is_component": false, "last_tested_version": "1.1.1", "name": "Market Research", - "tags": ["assistants", "agents"] -} + "tags": [ + "assistants", + "agents" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json index 3ef9aa89fe..8f6896726c 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json @@ -102,6 +102,8 @@ "fieldName": "input_value", "id": "ChatOutput-l7B6O", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -112,7 +114,7 @@ "source": "OpenAIModel-iudDZ", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-iudDZœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-l7B6O", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-l7B6Oœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-l7B6Oœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -130,6 +132,8 @@ "fieldName": "input_value", "id": "ChatOutput-BMxpl", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -140,7 +144,7 @@ "source": "ParseData-LUfjb", "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-LUfjbœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-BMxpl", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BMxplœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BMxplœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -158,6 +162,8 @@ "fieldName": "input_value", "id": "ChatOutput-04Red", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -168,7 +174,7 @@ "source": "OpenAIModel-8fyum", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-8fyumœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-04Red", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-04Redœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-04Redœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -344,7 +350,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "ASSEMBLYAI_API_KEY" }, "code": { "advanced": true, @@ -1218,6 +1224,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1234,7 +1258,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1265,13 +1289,15 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -1499,6 +1525,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1515,7 +1559,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1546,13 +1590,15 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -2161,6 +2207,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -2177,7 +2241,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -2208,13 +2272,15 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json index 8c154dfe68..07232eaf84 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json @@ -8,12 +8,17 @@ "dataType": "Memory", "id": "Memory-gWJrq", "name": "messages_text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "memory", "id": "Prompt-yhdMP", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -31,12 +36,16 @@ "dataType": "ChatInput", "id": "ChatInput-PEO9d", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-63o3Q", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -54,12 +63,16 @@ "dataType": "Prompt", "id": "Prompt-yhdMP", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-63o3Q", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -77,12 +90,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-63o3Q", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-BIXzI", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -91,7 +110,7 @@ "source": "OpenAIModel-63o3Q", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-63o3Qœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-BIXzI", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BIXzIœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BIXzIœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -99,7 +118,9 @@ "data": { "id": "ChatInput-PEO9d", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -133,7 +154,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -146,7 +169,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -166,7 +191,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -269,7 +296,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -285,7 +315,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -305,7 +337,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -341,7 +375,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -385,7 +421,9 @@ "display_name": "Chat Output", "id": "ChatOutput-BIXzI", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -419,7 +457,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -432,7 +472,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -452,7 +494,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -466,6 +510,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -482,7 +544,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -490,7 +552,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -510,12 +574,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -531,7 +599,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -547,7 +618,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -567,7 +640,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -603,7 +678,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -712,7 +789,10 @@ "data": { "id": "Memory-gWJrq", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -744,7 +824,9 @@ "name": "messages", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" }, { @@ -755,7 +837,9 @@ "name": "messages_text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -786,7 +870,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -821,7 +907,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -839,7 +928,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -855,7 +948,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -875,7 +970,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -895,7 +992,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -938,18 +1037,24 @@ "data": { "id": "Prompt-yhdMP", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["memory"] + "template": [ + "memory" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "prompts", @@ -970,7 +1075,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1003,7 +1110,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1038,7 +1148,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1080,7 +1192,10 @@ "data": { "id": "OpenAIModel-63o3Q", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1119,7 +1234,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1128,10 +1245,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1145,7 +1266,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1180,7 +1303,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1362,7 +1487,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1458,5 +1585,9 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Memory Chatbot", - "tags": ["chatbots", "openai", "assistants"] -} + "tags": [ + "chatbots", + "openai", + "assistants" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json index 5e2488579d..f932ae61ba 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json @@ -44,6 +44,8 @@ "fieldName": "input_value", "id": "ChatOutput-SyzjF", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -53,7 +55,7 @@ "source": "Agent-VOnBt", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-VOnBtœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-SyzjF", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-SyzjFœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-SyzjFœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -205,7 +207,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "AGENTQL_API_KEY" }, "code": { "advanced": true, @@ -827,6 +829,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -843,7 +863,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -875,6 +895,8 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, @@ -882,7 +904,7 @@ "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -1181,7 +1203,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json index 972aaf86b1..43ec97160a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json @@ -9,12 +9,16 @@ "dataType": "Prompt", "id": "Prompt-ysecC", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "StructuredOutput-TArXO", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -32,12 +36,16 @@ "dataType": "StructuredOutput", "id": "StructuredOutput-TArXO", "name": "structured_output", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-x7Rgx", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -55,12 +63,16 @@ "dataType": "ParseData", "id": "ParseData-sGhWo", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "resume", "id": "Prompt-ysecC", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -78,12 +90,16 @@ "dataType": "AnthropicModel", "id": "AnthropicModel-sHFTc", "name": "model_output", - "output_types": ["LanguageModel"] + "output_types": [ + "LanguageModel" + ] }, "targetHandle": { "fieldName": "llm", "id": "StructuredOutput-TArXO", - "inputTypes": ["LanguageModel"], + "inputTypes": [ + "LanguageModel" + ], "type": "other" } }, @@ -101,12 +117,16 @@ "dataType": "ParseData", "id": "ParseData-x7Rgx", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "AnthropicModel-IrjAe", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -124,12 +144,16 @@ "dataType": "TextInput", "id": "TextInput-CPTOR", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "AnthropicModel-IrjAe", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -147,12 +171,18 @@ "dataType": "AnthropicModel", "id": "AnthropicModel-IrjAe", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-d1tmJ", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -160,7 +190,7 @@ "source": "AnthropicModel-IrjAe", "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-IrjAeœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-d1tmJ", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-d1tmJœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-d1tmJœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "className": "", @@ -169,12 +199,16 @@ "dataType": "File", "id": "File-JgIx7", "name": "data", - "output_types": ["Data"] + "output_types": [ + "Data" + ] }, "targetHandle": { "fieldName": "data", "id": "ParseData-sGhWo", - "inputTypes": ["Data"], + "inputTypes": [ + "Data" + ], "type": "other" } }, @@ -190,18 +224,25 @@ "data": { "id": "Prompt-ysecC", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["resume"] + "template": [ + "resume" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, "error": null, - "field_order": ["template", "tool_placeholder"], + "field_order": [ + "template", + "tool_placeholder" + ], "frozen": false, "full_path": null, "icon": "prompts", @@ -223,7 +264,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -256,7 +299,9 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -292,7 +337,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -330,7 +377,10 @@ "data": { "id": "ParseData-sGhWo", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -338,7 +388,11 @@ "display_name": "Data to Message", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "legacy": false, @@ -357,7 +411,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -368,7 +424,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -399,7 +457,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "list_add_label": "Add More", "name": "data", @@ -438,7 +498,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -477,7 +539,9 @@ "data": { "id": "StructuredOutput-TArXO", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -509,7 +573,9 @@ "name": "structured_output", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -540,7 +606,9 @@ "display_name": "Input Message", "dynamic": false, "info": "The input message to the language model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -561,7 +629,9 @@ "display_name": "Language Model", "dynamic": false, "info": "The language model to use to generate the structured output.", - "input_types": ["LanguageModel"], + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", "name": "llm", @@ -780,7 +850,10 @@ "data": { "id": "ParseData-x7Rgx", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -788,7 +861,11 @@ "display_name": "Data to Message", "documentation": "", "edited": false, - "field_order": ["data", "template", "sep"], + "field_order": [ + "data", + "template", + "sep" + ], "frozen": false, "icon": "message-square", "legacy": false, @@ -807,7 +884,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -818,7 +897,9 @@ "name": "data_list", "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -849,7 +930,9 @@ "display_name": "Data", "dynamic": false, "info": "The data to convert to text.", - "input_types": ["Data"], + "input_types": [ + "Data" + ], "list": true, "list_add_label": "Add More", "name": "data", @@ -888,7 +971,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -927,7 +1012,9 @@ "data": { "id": "TextInput-CPTOR", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "inputs", "conditional_paths": [], @@ -936,7 +1023,9 @@ "display_name": "Text Input", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "key": "TextInput", @@ -954,7 +1043,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -986,7 +1077,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1025,7 +1118,10 @@ "data": { "id": "AnthropicModel-sHFTc", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1064,7 +1160,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1073,10 +1171,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1090,7 +1192,9 @@ "display_name": "Anthropic API Key", "dynamic": false, "info": "Your Anthropic API key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1108,7 +1212,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1148,7 +1254,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1217,7 +1325,9 @@ "display_name": "Prefill", "dynamic": false, "info": "Prefill text to guide the model's response.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1256,7 +1366,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1342,7 +1454,10 @@ "data": { "id": "AnthropicModel-IrjAe", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1381,7 +1496,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1390,10 +1507,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1407,7 +1528,9 @@ "display_name": "Anthropic API Key", "dynamic": false, "info": "Your Anthropic API key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1425,7 +1548,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1465,7 +1590,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1534,7 +1661,9 @@ "display_name": "Prefill", "dynamic": false, "info": "Prefill text to guide the model's response.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1573,7 +1702,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1659,7 +1790,9 @@ "data": { "id": "ChatOutput-d1tmJ", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "outputs", "conditional_paths": [], @@ -1696,7 +1829,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1710,7 +1845,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1731,7 +1868,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1746,6 +1885,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1762,7 +1919,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1770,7 +1927,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1791,13 +1950,17 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -1815,7 +1978,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -1832,7 +1998,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1853,7 +2021,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1892,7 +2062,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1930,7 +2102,9 @@ "data": { "id": "File-JgIx7", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "data", "conditional_paths": [], @@ -1966,7 +2140,9 @@ "required_inputs": [], "selected": "Data", "tool_mode": true, - "types": ["Data"], + "types": [ + "Data" + ], "value": "__UNDEFINED__" } ], @@ -2034,7 +2210,10 @@ "display_name": "Server File Path", "dynamic": false, "info": "Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.", - "input_types": ["Data", "Message"], + "input_types": [ + "Data", + "Message" + ], "list": true, "list_add_label": "Add More", "name": "file_path", @@ -2338,5 +2517,8 @@ "is_component": false, "last_tested_version": "1.1.4", "name": "Portfolio Website Code Generator", - "tags": ["chatbots", "coding"] -} + "tags": [ + "chatbots", + "coding" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json index b4d3c3c77e..c192e564ca 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json @@ -101,6 +101,8 @@ "fieldName": "input_value", "id": "ChatOutput-QZLoV", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -111,7 +113,7 @@ "source": "Agent-7MSQT", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-7MSQTœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-QZLoV", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-QZLoVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-QZLoVœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -525,6 +527,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -541,7 +561,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -573,6 +593,8 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, @@ -580,7 +602,7 @@ "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -786,7 +808,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "TAVILY_API_KEY" }, "code": { "advanced": true, @@ -1132,7 +1154,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "AGENTQL_API_KEY" }, "code": { "advanced": true, @@ -1503,7 +1525,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, @@ -2129,9 +2151,9 @@ "id": "ab091b94-13c1-42af-9f9b-3689e99fb0bf", "is_component": false, "last_tested_version": "1.1.5", + "name": "Price Deal Finder", "tags": [ "web-scraping", "agents" - ], - "name": "Price Deal Finder" + ] } \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json index 99e7ccb41f..f81c6f77c9 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json @@ -9,12 +9,17 @@ "dataType": "ChatInput", "id": "ChatInput-0hxgs", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Prompt-xI9hs", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -33,12 +38,16 @@ "dataType": "Prompt", "id": "Prompt-Zc4Af", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-Gbt8L", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -57,12 +66,17 @@ "dataType": "Agent", "id": "Agent-Gbt8L", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "search_results", "id": "Prompt-xI9hs", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -80,12 +94,16 @@ "dataType": "TavilySearchComponent", "id": "TavilySearchComponent-622t2", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-Gbt8L", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -102,12 +120,16 @@ "dataType": "Prompt", "id": "Prompt-bxgAA", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-iiRQd", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -124,12 +146,16 @@ "dataType": "ChatInput", "id": "ChatInput-0hxgs", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-iiRQd", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -146,12 +172,17 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-iiRQd", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "previous_response", "id": "Prompt-Zc4Af", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -168,12 +199,16 @@ "dataType": "Prompt", "id": "Prompt-xI9hs", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-rQIeM", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -190,12 +225,16 @@ "dataType": "Prompt", "id": "Prompt-ZbmDf", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-rQIeM", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -212,20 +251,26 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-rQIeM", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-C4LXq", - "inputTypes": ["Message"], + "id": "ChatOutput-joWRG", + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-OpenAIModel-rQIeM{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-rQIeMœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-C4LXq{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-C4LXqœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "OpenAIModel-rQIeM", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-rQIeMœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-C4LXq", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-C4LXqœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-OpenAIModel-zDrcf{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-zDrcfœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-joWRG{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-joWRGœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "OpenAIModel-zDrcf", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-zDrcfœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-joWRG", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-joWRGœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -235,17 +280,23 @@ "display_name": "Prompt", "id": "Prompt-Zc4Af", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["previous_response"] + "template": [ + "previous_response" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -261,7 +312,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -294,7 +347,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -330,7 +386,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -372,7 +430,9 @@ "data": { "id": "ChatInput-0hxgs", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "inputs", "conditional_paths": [], @@ -408,7 +468,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -421,7 +483,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -440,7 +504,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -541,7 +607,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -556,7 +625,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -575,7 +646,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -610,7 +683,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -652,7 +727,9 @@ "display_name": "Chat Output", "id": "ChatOutput-C4LXq", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -686,7 +763,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -699,7 +778,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -719,7 +800,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -733,6 +816,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -749,7 +850,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -757,7 +858,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -777,12 +880,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -798,7 +905,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -814,7 +924,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -834,7 +946,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -870,7 +984,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -914,17 +1030,24 @@ "display_name": "Prompt", "id": "Prompt-xI9hs", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["search_results", "input_value"] + "template": [ + "search_results", + "input_value" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -940,7 +1063,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -973,7 +1098,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -993,7 +1121,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1029,7 +1160,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1110,7 +1243,9 @@ "display_name": "Agent", "id": "Agent-Gbt8L", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1161,7 +1296,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1190,7 +1327,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1241,7 +1380,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1292,7 +1433,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -1384,7 +1527,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -1478,7 +1623,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1512,7 +1660,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1528,7 +1680,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1548,7 +1702,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1568,7 +1724,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1605,7 +1763,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1644,7 +1804,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1701,7 +1863,9 @@ "display_name": "Prompt", "id": "Prompt-bxgAA", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1711,7 +1875,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -1727,7 +1893,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1776,7 +1944,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1820,7 +1990,9 @@ "display_name": "Prompt", "id": "Prompt-ZbmDf", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1830,7 +2002,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -1846,7 +2020,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1895,7 +2071,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1969,7 +2147,10 @@ "data": { "id": "TavilySearchComponent-622t2", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2004,7 +2185,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -2017,7 +2200,9 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -2106,7 +2291,9 @@ "display_name": "Search Query", "dynamic": false, "info": "The search query you want to execute with Tavily.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2130,7 +2317,10 @@ "dynamic": false, "info": "The depth of the search.", "name": "search_depth", - "options": ["basic", "advanced"], + "options": [ + "basic", + "advanced" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2150,7 +2340,12 @@ "dynamic": false, "info": "The time range back from the current date to include in the search results.", "name": "time_range", - "options": ["day", "week", "month", "year"], + "options": [ + "day", + "week", + "month", + "year" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2185,7 +2380,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -2239,12 +2437,16 @@ { "description": "fetch_content(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content", - "tags": ["TavilySearchComponent-fetch_content"] + "tags": [ + "TavilySearchComponent-fetch_content" + ] }, { "description": "fetch_content_text(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content_text", - "tags": ["TavilySearchComponent-fetch_content_text"] + "tags": [ + "TavilySearchComponent-fetch_content_text" + ] } ] }, @@ -2257,7 +2459,10 @@ "dynamic": false, "info": "The category of the search.", "name": "topic", - "options": ["general", "news"], + "options": [ + "general", + "news" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2291,7 +2496,10 @@ "data": { "id": "OpenAIModel-iiRQd", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -2330,7 +2538,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2339,10 +2549,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -2356,8 +2570,10 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], - "load_from_db": false, + "input_types": [ + "Message" + ], + "load_from_db": true, "name": "api_key", "password": true, "placeholder": "", @@ -2391,7 +2607,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2573,7 +2791,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2658,7 +2878,10 @@ "data": { "id": "OpenAIModel-rQIeM", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -2697,7 +2920,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2706,10 +2931,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -2723,8 +2952,10 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], - "load_from_db": false, + "input_types": [ + "Message" + ], + "load_from_db": true, "name": "api_key", "password": true, "placeholder": "", @@ -2758,7 +2989,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2940,7 +3173,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3036,5 +3271,8 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Research Agent", - "tags": ["assistants", "agents"] -} + "tags": [ + "assistants", + "agents" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json index a753473d0c..6fbbb04374 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json @@ -8,12 +8,16 @@ "dataType": "Prompt", "id": "Prompt-a6SIY", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-brPVM", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -31,12 +35,16 @@ "dataType": "Prompt", "id": "Prompt-jkCpO", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-brPVM", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -54,12 +62,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-brPVM", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-oE2ic", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -68,7 +82,7 @@ "source": "OpenAIModel-brPVM", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-brPVMœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-oE2ic", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-oE2icœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-oE2icœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -78,7 +92,9 @@ "display_name": "Prompt", "id": "Prompt-jkCpO", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -95,7 +111,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -111,7 +129,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -144,7 +164,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -164,7 +187,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -184,7 +210,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -204,7 +233,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -224,7 +256,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -244,7 +279,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -280,7 +318,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -359,7 +399,9 @@ "display_name": "Prompt", "id": "Prompt-a6SIY", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -369,7 +411,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -385,7 +429,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -434,7 +480,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -478,7 +526,9 @@ "display_name": "Chat Output", "id": "ChatOutput-oE2ic", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -512,7 +562,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -525,7 +577,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -545,7 +599,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -559,6 +615,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -575,7 +649,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -583,7 +657,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -603,12 +679,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -624,7 +704,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -640,7 +723,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -660,7 +745,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -696,7 +783,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -768,7 +857,10 @@ "data": { "id": "OpenAIModel-brPVM", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -807,7 +899,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -816,10 +910,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -833,7 +931,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -868,7 +968,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1050,7 +1152,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1146,5 +1250,8 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "SEO Keyword Generator", - "tags": ["chatbots", "assistants"] -} + "tags": [ + "chatbots", + "assistants" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json index 217c1962e5..d4d05d6721 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json @@ -9,12 +9,18 @@ "dataType": "Agent", "id": "Agent-GgGua", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-SplPb", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -22,7 +28,7 @@ "source": "Agent-GgGua", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-GgGuaœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-SplPb", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-SplPbœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-SplPbœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -32,12 +38,16 @@ "dataType": "Prompt", "id": "Prompt-tqP9Q", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-GgGua", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -54,12 +64,16 @@ "dataType": "CalculatorComponent", "id": "CalculatorComponent-LyoFm", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-GgGua", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -77,7 +91,9 @@ "display_name": "Prompt", "id": "Prompt-tqP9Q", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -93,7 +109,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -142,7 +160,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -162,7 +183,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -182,7 +206,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -202,7 +229,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -222,7 +252,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -258,7 +291,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -302,7 +337,9 @@ "display_name": "Chat Output", "id": "ChatOutput-SplPb", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -349,7 +386,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -369,7 +408,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -383,6 +424,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -399,7 +458,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -407,7 +466,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -427,12 +488,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -448,7 +513,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -464,7 +532,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -484,7 +554,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -520,7 +592,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -599,7 +673,9 @@ "display_name": "Agent", "id": "Agent-GgGua", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -679,7 +755,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -730,7 +808,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -781,7 +861,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -873,7 +955,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -967,7 +1051,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1001,7 +1088,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1017,7 +1108,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1037,7 +1130,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1057,7 +1152,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1094,7 +1191,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1133,7 +1232,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1188,7 +1289,9 @@ "data": { "id": "CalculatorComponent-LyoFm", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -1197,7 +1300,9 @@ "display_name": "Calculator", "documentation": "", "edited": false, - "field_order": ["expression"], + "field_order": [ + "expression" + ], "frozen": false, "icon": "calculator", "key": "CalculatorComponent", @@ -1215,7 +1320,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -1247,7 +1354,9 @@ "display_name": "Expression", "dynamic": false, "info": "The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1287,7 +1396,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -1338,7 +1450,9 @@ { "description": "evaluate_expression() - Perform basic arithmetic operations on a given expression.", "name": "None-evaluate_expression", - "tags": ["None-evaluate_expression"] + "tags": [ + "None-evaluate_expression" + ] } ] } @@ -1376,5 +1490,8 @@ "is_component": false, "last_tested_version": "1.1.1", "name": "SaaS Pricing", - "tags": ["agents", "assistants"] -} + "tags": [ + "agents", + "assistants" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json index 943b69d725..8b1649f3b9 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json @@ -9,12 +9,16 @@ "dataType": "Prompt", "id": "Prompt-0tvfj", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_prompt", "id": "Agent-uHv68", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -32,12 +36,16 @@ "dataType": "Prompt", "id": "Prompt-YbDgD", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_prompt", "id": "Agent-uO2c7", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -55,20 +63,26 @@ "dataType": "Agent", "id": "Agent-uHv68", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-aOzn2", - "inputTypes": ["Message"], + "id": "ChatOutput-DqyyQ", + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-Agent-uHv68{œdataTypeœ:œAgentœ,œidœ:œAgent-uHv68œ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-aOzn2{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-aOzn2œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Agent-uHv68", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-uHv68œ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-aOzn2", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-aOzn2œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Agent-9YXRo{œdataTypeœ:œAgentœ,œidœ:œAgent-9YXRoœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-DqyyQ{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-DqyyQœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "Agent-9YXRo", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-9YXRoœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-DqyyQ", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-DqyyQœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -78,12 +92,17 @@ "dataType": "Agent", "id": "Agent-uO2c7", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "finance_agent_output", "id": "Prompt-0tvfj", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -101,12 +120,16 @@ "dataType": "ChatInput", "id": "ChatInput-yzskz", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-6Gznl", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -124,12 +147,16 @@ "dataType": "Prompt", "id": "Prompt-Sxdul", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_prompt", "id": "Agent-6Gznl", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -147,12 +174,16 @@ "dataType": "Agent", "id": "Agent-6Gznl", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-uO2c7", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -170,12 +201,17 @@ "dataType": "Agent", "id": "Agent-6Gznl", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "research_agent_output", "id": "Prompt-0tvfj", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -192,12 +228,16 @@ "dataType": "CalculatorComponent", "id": "CalculatorComponent-WMC2W", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-uHv68", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -214,12 +254,16 @@ "dataType": "YfinanceComponent", "id": "YfinanceComponent-BIM81", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-uO2c7", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -236,12 +280,16 @@ "dataType": "TavilySearchComponent", "id": "TavilySearchComponent-YUDsR", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-6Gznl", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -259,7 +307,9 @@ "display_name": "Chat Output", "id": "ChatOutput-aOzn2", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -293,7 +343,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -306,7 +358,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -326,7 +380,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -340,6 +396,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -356,7 +430,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -364,7 +438,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -384,12 +460,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -405,7 +485,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -421,7 +504,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -441,7 +526,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -477,7 +564,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -521,7 +610,9 @@ "display_name": "Finance Agent", "id": "Agent-uO2c7", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -572,7 +663,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -601,7 +694,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -652,7 +747,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -703,7 +800,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -795,7 +894,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -889,7 +990,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -923,7 +1027,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -939,7 +1047,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -959,7 +1069,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -979,7 +1091,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1016,7 +1130,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1055,7 +1171,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1112,7 +1230,9 @@ "display_name": "Analysis & Editor Agent", "id": "Agent-uHv68", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1163,7 +1283,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1192,7 +1314,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1243,7 +1367,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1294,7 +1420,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -1386,7 +1514,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -1480,7 +1610,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1514,7 +1647,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1530,7 +1667,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1550,7 +1689,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1570,7 +1711,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1607,7 +1750,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1646,7 +1791,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1703,7 +1850,9 @@ "display_name": "Prompt", "id": "Prompt-Sxdul", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1714,7 +1863,9 @@ "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": true, "full_path": null, "icon": "prompts", @@ -1735,7 +1886,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1784,7 +1937,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1828,7 +1983,9 @@ "display_name": "Prompt", "id": "Prompt-YbDgD", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1839,7 +1996,9 @@ "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "prompts", @@ -1860,7 +2019,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1909,7 +2070,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1953,18 +2116,25 @@ "display_name": "Prompt", "id": "Prompt-0tvfj", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["research_agent_output", "finance_agent_output"] + "template": [ + "research_agent_output", + "finance_agent_output" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "prompts", @@ -1985,7 +2155,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -2018,7 +2190,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2038,7 +2213,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2074,7 +2252,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -2116,7 +2296,9 @@ "data": { "id": "ChatInput-yzskz", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2150,7 +2332,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -2163,7 +2347,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -2183,7 +2369,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -2286,7 +2474,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -2302,7 +2493,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -2322,7 +2515,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -2358,7 +2553,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -2474,7 +2671,9 @@ "display_name": "Researcher Agent", "id": "Agent-6Gznl", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2525,7 +2724,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -2554,7 +2755,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2605,7 +2808,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -2656,7 +2861,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -2748,7 +2955,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -2842,7 +3051,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -2876,7 +3088,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -2892,7 +3108,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -2912,7 +3130,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -2932,7 +3152,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2969,7 +3191,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -3008,7 +3232,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -3137,7 +3363,10 @@ "data": { "id": "YfinanceComponent-BIM81", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -3146,7 +3375,11 @@ "display_name": "Yahoo Finance", "documentation": "", "edited": false, - "field_order": ["symbol", "method", "num_news"], + "field_order": [ + "symbol", + "method", + "num_news" + ], "frozen": false, "icon": "trending-up", "key": "YfinanceComponent", @@ -3163,7 +3396,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -3257,7 +3492,9 @@ "display_name": "Stock Symbol", "dynamic": false, "info": "The stock symbol to retrieve data for (e.g., AAPL, GOOG).", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3297,7 +3534,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -3348,12 +3588,16 @@ { "description": "fetch_content() - Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", "name": "YfinanceComponent-fetch_content", - "tags": ["YfinanceComponent-fetch_content"] + "tags": [ + "YfinanceComponent-fetch_content" + ] }, { "description": "fetch_content_text() - Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", "name": "YfinanceComponent-fetch_content_text", - "tags": ["YfinanceComponent-fetch_content_text"] + "tags": [ + "YfinanceComponent-fetch_content_text" + ] } ] } @@ -3380,7 +3624,9 @@ "data": { "id": "CalculatorComponent-WMC2W", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -3389,7 +3635,9 @@ "display_name": "Calculator", "documentation": "", "edited": false, - "field_order": ["expression"], + "field_order": [ + "expression" + ], "frozen": false, "icon": "calculator", "key": "CalculatorComponent", @@ -3406,7 +3654,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -3438,7 +3688,9 @@ "display_name": "Expression", "dynamic": false, "info": "The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3478,7 +3730,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -3529,7 +3784,9 @@ { "description": "evaluate_expression() - Perform basic arithmetic operations on a given expression.", "name": "CalculatorComponent-evaluate_expression", - "tags": ["CalculatorComponent-evaluate_expression"] + "tags": [ + "CalculatorComponent-evaluate_expression" + ] } ] } @@ -3556,7 +3813,10 @@ "data": { "id": "TavilySearchComponent-YUDsR", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -3591,7 +3851,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -3604,7 +3866,9 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -3693,7 +3957,9 @@ "display_name": "Search Query", "dynamic": false, "info": "The search query you want to execute with Tavily.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3717,7 +3983,10 @@ "dynamic": false, "info": "The depth of the search.", "name": "search_depth", - "options": ["basic", "advanced"], + "options": [ + "basic", + "advanced" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3737,7 +4006,12 @@ "dynamic": false, "info": "The time range back from the current date to include in the search results.", "name": "time_range", - "options": ["day", "week", "month", "year"], + "options": [ + "day", + "week", + "month", + "year" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3772,7 +4046,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -3826,12 +4103,16 @@ { "description": "fetch_content(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content", - "tags": ["TavilySearchComponent-fetch_content"] + "tags": [ + "TavilySearchComponent-fetch_content" + ] }, { "description": "fetch_content_text(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "name": "TavilySearchComponent-fetch_content_text", - "tags": ["TavilySearchComponent-fetch_content_text"] + "tags": [ + "TavilySearchComponent-fetch_content_text" + ] } ] }, @@ -3844,7 +4125,10 @@ "dynamic": false, "info": "The category of the search.", "name": "topic", - "options": ["general", "news"], + "options": [ + "general", + "news" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3889,5 +4173,9 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Sequential Tasks Agents", - "tags": ["assistants", "agents", "web-scraping"] -} + "tags": [ + "assistants", + "agents", + "web-scraping" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json index f5a2feaae9..add24f2054 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json @@ -9,12 +9,16 @@ "dataType": "ChatInput", "id": "ChatInput-vSKY8", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-EPjLx", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -33,12 +37,18 @@ "dataType": "Agent", "id": "Agent-EPjLx", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-x6tZi", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -47,7 +57,7 @@ "source": "Agent-EPjLx", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-EPjLxœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-x6tZi", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-x6tZiœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-x6tZiœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "className": "", @@ -56,12 +66,16 @@ "dataType": "CalculatorComponent", "id": "CalculatorComponent-zxpIu", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-EPjLx", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -78,12 +92,16 @@ "dataType": "URL", "id": "URL-yM3pY", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-EPjLx", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -101,7 +119,9 @@ "display_name": "Agent", "id": "Agent-EPjLx", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -181,7 +201,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -232,7 +254,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -283,7 +307,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -375,7 +401,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -469,7 +497,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -503,7 +534,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -519,7 +554,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -539,7 +576,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -559,7 +598,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -596,7 +637,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -635,7 +678,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -684,7 +729,9 @@ "data": { "id": "ChatInput-vSKY8", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -731,7 +778,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -751,7 +800,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -854,7 +905,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -870,7 +924,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -890,7 +946,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -926,7 +984,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -964,7 +1024,9 @@ "display_name": "Chat Output", "id": "ChatOutput-x6tZi", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1011,7 +1073,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -1031,7 +1095,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -1045,6 +1111,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1061,7 +1145,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1069,7 +1153,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -1089,12 +1175,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -1110,7 +1200,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -1126,7 +1219,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1146,7 +1241,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1182,7 +1279,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -1219,7 +1318,11 @@ "display_name": "URL", "id": "URL-yM3pY", "node": { - "base_classes": ["Data", "DataFrame", "Message"], + "base_classes": [ + "Data", + "DataFrame", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1227,7 +1330,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "legacy": false, @@ -1243,7 +1349,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -1276,7 +1384,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "placeholder": "", "required": false, "show": true, @@ -1311,7 +1422,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -1362,17 +1476,23 @@ { "description": "fetch_content() - Load and retrive data from specified URLs.", "name": "URL-fetch_content", - "tags": ["URL-fetch_content"] + "tags": [ + "URL-fetch_content" + ] }, { "description": "fetch_content_text() - Load and retrive data from specified URLs.", "name": "URL-fetch_content_text", - "tags": ["URL-fetch_content_text"] + "tags": [ + "URL-fetch_content_text" + ] }, { "description": "as_dataframe() - Load and retrive data from specified URLs.", "name": "URL-as_dataframe", - "tags": ["URL-as_dataframe"] + "tags": [ + "URL-as_dataframe" + ] } ] }, @@ -1382,7 +1502,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "list_add_label": "Add URL", "load_from_db": false, @@ -1470,7 +1592,9 @@ "data": { "id": "CalculatorComponent-zxpIu", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -1479,7 +1603,9 @@ "display_name": "Calculator", "documentation": "", "edited": false, - "field_order": ["expression"], + "field_order": [ + "expression" + ], "frozen": false, "icon": "calculator", "key": "CalculatorComponent", @@ -1498,7 +1624,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -1530,7 +1658,9 @@ "display_name": "Expression", "dynamic": false, "info": "The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1570,7 +1700,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -1621,7 +1754,9 @@ { "description": "evaluate_expression() - Perform basic arithmetic operations on a given expression.", "name": "None-evaluate_expression", - "tags": ["None-evaluate_expression"] + "tags": [ + "None-evaluate_expression" + ] } ] } @@ -1657,5 +1792,8 @@ "is_component": false, "last_tested_version": "1.1.1", "name": "Simple Agent", - "tags": ["assistants", "agents"] -} + "tags": [ + "assistants", + "agents" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json index eec0557197..887ac836f6 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json @@ -9,12 +9,18 @@ "dataType": "Agent", "id": "Agent-8PETJ", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-1fycm", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -22,7 +28,7 @@ "source": "Agent-8PETJ", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-8PETJœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-1fycm", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-1fycmœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-1fycmœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -32,12 +38,16 @@ "dataType": "Agent", "id": "Agent-Mb2Ep", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-8PETJ", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -55,12 +65,16 @@ "dataType": "Agent", "id": "Agent-VEG7r", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-Mb2Ep", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -78,12 +92,16 @@ "dataType": "ChatInput", "id": "ChatInput-Nbu0b", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-VEG7r", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -101,12 +119,16 @@ "dataType": "URL", "id": "URL-hJKTA", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-Mb2Ep", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -124,12 +146,16 @@ "dataType": "CalculatorComponent", "id": "CalculatorComponent-T0153", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-8PETJ", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -147,12 +173,16 @@ "dataType": "SearchComponent", "id": "SearchComponent-a9OCR", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-VEG7r", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -168,7 +198,9 @@ "data": { "id": "ChatInput-Nbu0b", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -199,7 +231,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -212,7 +246,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -231,7 +267,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -332,7 +370,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -347,7 +388,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -366,7 +409,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -401,7 +446,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -443,7 +490,9 @@ "display_name": "Chat Output", "id": "ChatOutput-1fycm", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -477,7 +526,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -490,7 +541,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -510,7 +563,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -524,6 +579,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -540,7 +613,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -548,7 +621,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -568,12 +643,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -589,7 +668,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -605,7 +687,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -625,7 +709,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -661,7 +747,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -705,7 +793,9 @@ "display_name": "City Selection Agent", "id": "Agent-VEG7r", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -756,7 +846,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -785,7 +877,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -836,7 +930,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -887,7 +983,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -979,7 +1077,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -1073,7 +1173,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1107,7 +1210,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1123,7 +1230,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1143,7 +1252,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1163,7 +1274,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1200,7 +1313,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1239,7 +1354,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1296,7 +1413,9 @@ "display_name": "Local Expert Agent", "id": "Agent-Mb2Ep", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1347,7 +1466,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1376,7 +1497,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1427,7 +1550,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1478,7 +1603,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -1570,7 +1697,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -1664,7 +1793,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -1698,7 +1830,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -1714,7 +1850,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1734,7 +1872,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -1754,7 +1894,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1791,7 +1933,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1830,7 +1974,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -1887,7 +2033,9 @@ "display_name": "Travel Concierge Agent", "id": "Agent-8PETJ", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1938,7 +2086,9 @@ "name": "response", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1967,7 +2117,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2018,7 +2170,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -2069,7 +2223,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", @@ -2161,7 +2317,9 @@ "display_name": "External Memory", "dynamic": false, "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], + "input_types": [ + "Memory" + ], "list": false, "name": "memory", "placeholder": "", @@ -2255,7 +2413,10 @@ "dynamic": false, "info": "Order of the messages.", "name": "order", - "options": ["Ascending", "Descending"], + "options": [ + "Ascending", + "Descending" + ], "placeholder": "", "required": false, "show": true, @@ -2289,7 +2450,11 @@ "dynamic": false, "info": "Filter by sender type.", "name": "sender", - "options": ["Machine", "User", "Machine and User"], + "options": [ + "Machine", + "User", + "Machine and User" + ], "placeholder": "", "required": false, "show": true, @@ -2305,7 +2470,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Filter by sender name.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -2325,7 +2492,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -2345,7 +2514,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2382,7 +2553,9 @@ "display_name": "Template", "dynamic": false, "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -2421,7 +2594,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "name": "tools", "placeholder": "", @@ -2655,7 +2830,11 @@ "data": { "id": "URL-hJKTA", "node": { - "base_classes": ["Data", "DataFrame", "Message"], + "base_classes": [ + "Data", + "DataFrame", + "Message" + ], "beta": false, "category": "data", "conditional_paths": [], @@ -2664,7 +2843,10 @@ "display_name": "URL", "documentation": "", "edited": false, - "field_order": ["urls", "format"], + "field_order": [ + "urls", + "format" + ], "frozen": false, "icon": "layout-template", "key": "URL", @@ -2683,7 +2865,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -2718,7 +2902,10 @@ "dynamic": false, "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.", "name": "format", - "options": ["Text", "Raw HTML"], + "options": [ + "Text", + "Raw HTML" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2754,7 +2941,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -2805,17 +2995,23 @@ { "description": "fetch_content() - Load and retrive data from specified URLs.", "name": "URL-fetch_content", - "tags": ["URL-fetch_content"] + "tags": [ + "URL-fetch_content" + ] }, { "description": "fetch_content_text() - Load and retrive data from specified URLs.", "name": "URL-fetch_content_text", - "tags": ["URL-fetch_content_text"] + "tags": [ + "URL-fetch_content_text" + ] }, { "description": "as_dataframe() - Load and retrive data from specified URLs.", "name": "URL-as_dataframe", - "tags": ["URL-as_dataframe"] + "tags": [ + "URL-as_dataframe" + ] } ] }, @@ -2825,7 +3021,9 @@ "display_name": "URLs", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": true, "list_add_label": "Add URL", "load_from_db": false, @@ -2863,7 +3061,9 @@ "data": { "id": "CalculatorComponent-T0153", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -2872,7 +3072,9 @@ "display_name": "Calculator", "documentation": "", "edited": false, - "field_order": ["expression"], + "field_order": [ + "expression" + ], "frozen": false, "icon": "calculator", "key": "CalculatorComponent", @@ -2891,7 +3093,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -2923,7 +3127,9 @@ "display_name": "Expression", "dynamic": false, "info": "The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2963,7 +3169,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -3014,7 +3223,9 @@ { "description": "evaluate_expression() - Perform basic arithmetic operations on a given expression.", "name": "CalculatorComponent-evaluate_expression", - "tags": ["CalculatorComponent-evaluate_expression"] + "tags": [ + "CalculatorComponent-evaluate_expression" + ] } ] } @@ -3041,7 +3252,10 @@ "data": { "id": "SearchComponent-a9OCR", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -3076,7 +3290,9 @@ "name": "component_as_tool", "required_inputs": null, "selected": "Tool", - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -3090,7 +3306,9 @@ "display_name": "SearchAPI API Key", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -3128,7 +3346,11 @@ "dynamic": false, "info": "", "name": "engine", - "options": ["google", "bing", "duckduckgo"], + "options": [ + "google", + "bing", + "duckduckgo" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3145,7 +3367,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3240,7 +3464,10 @@ "description": "Modify tool names and descriptions to help agents understand when to use each tool.", "field_parsers": { "commands": "commands", - "name": ["snake_case", "no_blank"] + "name": [ + "snake_case", + "no_blank" + ] }, "hide_options": true }, @@ -3291,12 +3518,16 @@ { "description": "fetch_content(api_key: Message) - Call the searchapi.io API with result limiting", "name": "SearchComponent-fetch_content", - "tags": ["SearchComponent-fetch_content"] + "tags": [ + "SearchComponent-fetch_content" + ] }, { "description": "fetch_content_text(api_key: Message) - Call the searchapi.io API with result limiting", "name": "SearchComponent-fetch_content_text", - "tags": ["SearchComponent-fetch_content_text"] + "tags": [ + "SearchComponent-fetch_content_text" + ] } ] } @@ -3334,5 +3565,8 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Travel Planning Agents", - "tags": ["agents", "openai"] -} + "tags": [ + "agents", + "openai" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json index c2d3e5468e..46cea655e2 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json @@ -9,12 +9,17 @@ "dataType": "TextInput", "id": "TextInput-eClq5", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "CONTENT_GUIDELINES", "id": "Prompt-AWZtN", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -33,12 +38,17 @@ "dataType": "TextInput", "id": "TextInput-IpoG7", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "OUTPUT_FORMAT", "id": "Prompt-AWZtN", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -57,12 +67,17 @@ "dataType": "TextInput", "id": "TextInput-npraC", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "OUTPUT_LANGUAGE", "id": "Prompt-AWZtN", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -81,12 +96,17 @@ "dataType": "TextInput", "id": "TextInput-EZaR7", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "PROFILE_DETAILS", "id": "Prompt-AWZtN", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -105,12 +125,17 @@ "dataType": "TextInput", "id": "TextInput-fKGcs", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "PROFILE_TYPE", "id": "Prompt-AWZtN", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -129,12 +154,17 @@ "dataType": "TextInput", "id": "TextInput-92vEK", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "TONE_AND_STYLE", "id": "Prompt-AWZtN", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -152,12 +182,16 @@ "dataType": "ChatInput", "id": "ChatInput-ECcN8", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "OpenAIModel-p0R9m", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -175,12 +209,16 @@ "dataType": "Prompt", "id": "Prompt-AWZtN", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_message", "id": "OpenAIModel-p0R9m", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -198,12 +236,18 @@ "dataType": "OpenAIModel", "id": "OpenAIModel-p0R9m", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-0jDYx", - "inputTypes": ["Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -212,7 +256,7 @@ "source": "OpenAIModel-p0R9m", "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-p0R9mœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-0jDYx", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-0jDYxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-0jDYxœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -220,7 +264,9 @@ "data": { "id": "ChatInput-ECcN8", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -254,7 +300,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -267,7 +315,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -286,7 +336,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -387,7 +439,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -402,7 +457,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -421,7 +478,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -456,7 +515,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -496,7 +557,9 @@ "data": { "id": "TextInput-eClq5", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -504,7 +567,9 @@ "display_name": "Content Guidelines", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -520,7 +585,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -551,7 +618,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -594,7 +663,9 @@ "display_name": "Chat Output", "id": "ChatOutput-0jDYx", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -628,7 +699,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -641,7 +714,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -661,7 +736,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -675,6 +752,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -691,7 +786,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -699,7 +794,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "data_template", @@ -719,12 +816,16 @@ "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -740,7 +841,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -756,7 +860,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -776,7 +882,9 @@ "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"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -812,7 +920,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -854,7 +964,9 @@ "data": { "id": "TextInput-IpoG7", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -862,7 +974,9 @@ "display_name": "Output Format", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -878,7 +992,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -909,7 +1025,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -950,7 +1068,9 @@ "data": { "id": "TextInput-npraC", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -958,7 +1078,9 @@ "display_name": "Output Language", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -974,7 +1096,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1005,7 +1129,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1046,7 +1172,9 @@ "data": { "id": "TextInput-EZaR7", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1054,7 +1182,9 @@ "display_name": "Profile Details", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -1070,7 +1200,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1101,7 +1233,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1142,7 +1276,9 @@ "data": { "id": "TextInput-92vEK", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1150,7 +1286,9 @@ "display_name": "Tone And Style", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -1166,7 +1304,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1197,7 +1337,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1238,7 +1380,9 @@ "data": { "id": "TextInput-fKGcs", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1246,7 +1390,9 @@ "display_name": "Profile Type", "documentation": "", "edited": false, - "field_order": ["input_value"], + "field_order": [ + "input_value" + ], "frozen": false, "icon": "type", "legacy": false, @@ -1262,7 +1408,9 @@ "name": "text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1293,7 +1441,9 @@ "display_name": "Text", "dynamic": false, "info": "Text to be passed as input.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1373,7 +1523,9 @@ "display_name": "Prompt", "id": "Prompt-AWZtN", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1390,7 +1542,9 @@ "display_name": "Prompt", "documentation": "", "edited": false, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "icon": "prompts", "legacy": false, @@ -1406,7 +1560,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1420,7 +1576,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1440,7 +1599,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1460,7 +1622,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1480,7 +1645,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1500,7 +1668,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1520,7 +1691,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1575,7 +1749,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1617,7 +1793,10 @@ "data": { "id": "OpenAIModel-p0R9m", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -1656,7 +1835,9 @@ "required_inputs": [], "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -1665,10 +1846,14 @@ "display_name": "Language Model", "method": "build_model", "name": "model_output", - "required_inputs": ["api_key"], + "required_inputs": [ + "api_key" + ], "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -1682,7 +1867,9 @@ "display_name": "OpenAI API Key", "dynamic": false, "info": "The OpenAI API Key to use for the OpenAI model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "load_from_db": true, "name": "api_key", "password": true, @@ -1717,7 +1904,9 @@ "display_name": "Input", "dynamic": false, "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1899,7 +2088,9 @@ "display_name": "System Message", "dynamic": false, "info": "System message to pass to the model.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1995,5 +2186,8 @@ "is_component": false, "last_tested_version": "1.0.19.post2", "name": "Twitter Thread Generator", - "tags": ["chatbots", "content-generation"] -} + "tags": [ + "chatbots", + "content-generation" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json index a6417b8dda..585ca5a77b 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json @@ -7,7 +7,7 @@ "data": { "sourceHandle": { "dataType": "ParseData", - "id": "ParseData-t1SQn", + "id": "ParseData-mnAoF", "name": "text", "output_types": [ "Message" @@ -15,7 +15,7 @@ }, "targetHandle": { "fieldName": "context", - "id": "Prompt-MGcZw", + "id": "Prompt-hdHKZ", "inputTypes": [ "Message", "Text" @@ -23,11 +23,11 @@ "type": "str" } }, - "id": "reactflow__edge-ParseData-t1SQn{œdataTypeœ:œParseDataœ,œidœ:œParseData-t1SQnœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-MGcZw{œfieldNameœ:œcontextœ,œidœ:œPrompt-MGcZwœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "ParseData-t1SQn", - "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-t1SQnœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-MGcZw", - "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-MGcZwœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ParseData-mnAoF{œdataTypeœ:œParseDataœ,œidœ:œParseData-mnAoFœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-hdHKZ{œfieldNameœ:œcontextœ,œidœ:œPrompt-hdHKZœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "ParseData-mnAoF", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-mnAoFœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-hdHKZ", + "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-hdHKZœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -35,7 +35,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-zJJGs", + "id": "ChatInput-8NQlz", "name": "message", "output_types": [ "Message" @@ -43,7 +43,7 @@ }, "targetHandle": { "fieldName": "question", - "id": "Prompt-MGcZw", + "id": "Prompt-hdHKZ", "inputTypes": [ "Message", "Text" @@ -51,11 +51,11 @@ "type": "str" } }, - "id": "reactflow__edge-ChatInput-zJJGs{œdataTypeœ:œChatInputœ,œidœ:œChatInput-zJJGsœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-MGcZw{œfieldNameœ:œquestionœ,œidœ:œPrompt-MGcZwœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "ChatInput-zJJGs", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-zJJGsœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-MGcZw", - "targetHandle": "{œfieldNameœ: œquestionœ, œidœ: œPrompt-MGcZwœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ChatInput-8NQlz{œdataTypeœ:œChatInputœ,œidœ:œChatInput-8NQlzœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-hdHKZ{œfieldNameœ:œquestionœ,œidœ:œPrompt-hdHKZœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "ChatInput-8NQlz", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-8NQlzœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-hdHKZ", + "targetHandle": "{œfieldNameœ: œquestionœ, œidœ: œPrompt-hdHKZœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -63,7 +63,7 @@ "data": { "sourceHandle": { "dataType": "File", - "id": "File-FJNXb", + "id": "File-7fTNc", "name": "data", "output_types": [ "Data" @@ -71,25 +71,25 @@ }, "targetHandle": { "fieldName": "data_inputs", - "id": "SplitText-FIjro", + "id": "SplitText-oI0KK", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "reactflow__edge-File-FJNXb{œdataTypeœ:œFileœ,œidœ:œFile-FJNXbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-SplitText-FIjro{œfieldNameœ:œdata_inputsœ,œidœ:œSplitText-FIjroœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "File-FJNXb", - "sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-FJNXbœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", - "target": "SplitText-FIjro", - "targetHandle": "{œfieldNameœ: œdata_inputsœ, œidœ: œSplitText-FIjroœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-File-7fTNc{œdataTypeœ:œFileœ,œidœ:œFile-7fTNcœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-SplitText-oI0KK{œfieldNameœ:œdata_inputsœ,œidœ:œSplitText-oI0KKœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "File-7fTNc", + "sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-7fTNcœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", + "target": "SplitText-oI0KK", + "targetHandle": "{œfieldNameœ: œdata_inputsœ, œidœ: œSplitText-oI0KKœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { "className": "", "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-MGcZw", + "id": "Prompt-hdHKZ", "name": "prompt", "output_types": [ "Message" @@ -97,50 +97,25 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "OpenAIModel-8NmZL", + "id": "OpenAIModel-690Qx", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Prompt-MGcZw{œdataTypeœ:œPromptœ,œidœ:œPrompt-MGcZwœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-8NmZL{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-8NmZLœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Prompt-MGcZw", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-MGcZwœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "OpenAIModel-8NmZL", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-8NmZLœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Prompt-hdHKZ{œdataTypeœ:œPromptœ,œidœ:œPrompt-hdHKZœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-690Qx{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-690Qxœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "Prompt-hdHKZ", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-hdHKZœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "OpenAIModel-690Qx", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-690Qxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "className": "", - "data": { - "sourceHandle": { - "dataType": "OpenAIModel", - "id": "OpenAIModel-8NmZL", - "name": "text_output", - "output_types": [ - "Message" - ] - }, - "targetHandle": { - "fieldName": "input_value", - "id": "ChatOutput-dMbC5", - "inputTypes": [ - "Message" - ], - "type": "str" - } - }, - "id": "reactflow__edge-OpenAIModel-8NmZL{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-8NmZLœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-dMbC5{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-dMbC5œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "OpenAIModel-8NmZL", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-8NmZLœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-dMbC5", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-dMbC5œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" - }, - { "data": { "sourceHandle": { "dataType": "OpenAIEmbeddings", - "id": "OpenAIEmbeddings-Au4nR", + "id": "OpenAIEmbeddings-u7coL", "name": "embeddings", "output_types": [ "Embeddings" @@ -148,24 +123,25 @@ }, "targetHandle": { "fieldName": "embedding_model", - "id": "AstraDB-kvbzj", + "id": "AstraDB-UZ35f", "inputTypes": [ "Embeddings" ], "type": "other" } }, - "id": "xy-edge__OpenAIEmbeddings-Au4nR{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-Au4nRœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-kvbzj{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-kvbzjœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", - "source": "OpenAIEmbeddings-Au4nR", - "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-Au4nRœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", - "target": "AstraDB-kvbzj", - "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-kvbzjœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-OpenAIEmbeddings-u7coL{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-u7coLœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-UZ35f{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-UZ35fœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", + "source": "OpenAIEmbeddings-u7coL", + "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-u7coLœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", + "target": "AstraDB-UZ35f", + "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-UZ35fœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" }, { + "className": "", "data": { "sourceHandle": { "dataType": "SplitText", - "id": "SplitText-FIjro", + "id": "SplitText-oI0KK", "name": "chunks", "output_types": [ "Data" @@ -173,24 +149,25 @@ }, "targetHandle": { "fieldName": "ingest_data", - "id": "AstraDB-kvbzj", + "id": "AstraDB-UZ35f", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "xy-edge__SplitText-FIjro{œdataTypeœ:œSplitTextœ,œidœ:œSplitText-FIjroœ,œnameœ:œchunksœ,œoutput_typesœ:[œDataœ]}-AstraDB-kvbzj{œfieldNameœ:œingest_dataœ,œidœ:œAstraDB-kvbzjœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "SplitText-FIjro", - "sourceHandle": "{œdataTypeœ: œSplitTextœ, œidœ: œSplitText-FIjroœ, œnameœ: œchunksœ, œoutput_typesœ: [œDataœ]}", - "target": "AstraDB-kvbzj", - "targetHandle": "{œfieldNameœ: œingest_dataœ, œidœ: œAstraDB-kvbzjœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-SplitText-oI0KK{œdataTypeœ:œSplitTextœ,œidœ:œSplitText-oI0KKœ,œnameœ:œchunksœ,œoutput_typesœ:[œDataœ]}-AstraDB-UZ35f{œfieldNameœ:œingest_dataœ,œidœ:œAstraDB-UZ35fœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "SplitText-oI0KK", + "sourceHandle": "{œdataTypeœ: œSplitTextœ, œidœ: œSplitText-oI0KKœ, œnameœ: œchunksœ, œoutput_typesœ: [œDataœ]}", + "target": "AstraDB-UZ35f", + "targetHandle": "{œfieldNameœ: œingest_dataœ, œidœ: œAstraDB-UZ35fœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { + "className": "", "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-zJJGs", + "id": "ChatInput-8NQlz", "name": "message", "output_types": [ "Message" @@ -198,24 +175,25 @@ }, "targetHandle": { "fieldName": "search_query", - "id": "AstraDB-sfnha", + "id": "AstraDB-Etytu", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ChatInput-zJJGs{œdataTypeœ:œChatInputœ,œidœ:œChatInput-zJJGsœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-AstraDB-sfnha{œfieldNameœ:œsearch_queryœ,œidœ:œAstraDB-sfnhaœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ChatInput-zJJGs", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-zJJGsœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "AstraDB-sfnha", - "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œAstraDB-sfnhaœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ChatInput-8NQlz{œdataTypeœ:œChatInputœ,œidœ:œChatInput-8NQlzœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-AstraDB-Etytu{œfieldNameœ:œsearch_queryœ,œidœ:œAstraDB-Etytuœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ChatInput-8NQlz", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-8NQlzœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "AstraDB-Etytu", + "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œAstraDB-Etytuœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { + "className": "", "data": { "sourceHandle": { "dataType": "OpenAIEmbeddings", - "id": "OpenAIEmbeddings-XD5Ju", + "id": "OpenAIEmbeddings-eerk9", "name": "embeddings", "output_types": [ "Embeddings" @@ -223,24 +201,25 @@ }, "targetHandle": { "fieldName": "embedding_model", - "id": "AstraDB-sfnha", + "id": "AstraDB-Etytu", "inputTypes": [ "Embeddings" ], "type": "other" } }, - "id": "xy-edge__OpenAIEmbeddings-XD5Ju{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-XD5Juœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-sfnha{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-sfnhaœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", - "source": "OpenAIEmbeddings-XD5Ju", - "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-XD5Juœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", - "target": "AstraDB-sfnha", - "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-sfnhaœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-OpenAIEmbeddings-eerk9{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-eerk9œ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-Etytu{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-Etytuœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", + "source": "OpenAIEmbeddings-eerk9", + "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-eerk9œ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", + "target": "AstraDB-Etytu", + "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-Etytuœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" }, { + "className": "", "data": { "sourceHandle": { "dataType": "AstraDB", - "id": "AstraDB-sfnha", + "id": "AstraDB-Etytu", "name": "search_results", "output_types": [ "Data" @@ -248,18 +227,45 @@ }, "targetHandle": { "fieldName": "data", - "id": "ParseData-t1SQn", + "id": "ParseData-mnAoF", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "xy-edge__AstraDB-sfnha{œdataTypeœ:œAstraDBœ,œidœ:œAstraDB-sfnhaœ,œnameœ:œsearch_resultsœ,œoutput_typesœ:[œDataœ]}-ParseData-t1SQn{œfieldNameœ:œdataœ,œidœ:œParseData-t1SQnœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "AstraDB-sfnha", - "sourceHandle": "{œdataTypeœ: œAstraDBœ, œidœ: œAstraDB-sfnhaœ, œnameœ: œsearch_resultsœ, œoutput_typesœ: [œDataœ]}", - "target": "ParseData-t1SQn", - "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-t1SQnœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-AstraDB-Etytu{œdataTypeœ:œAstraDBœ,œidœ:œAstraDB-Etytuœ,œnameœ:œsearch_resultsœ,œoutput_typesœ:[œDataœ]}-ParseData-mnAoF{œfieldNameœ:œdataœ,œidœ:œParseData-mnAoFœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "AstraDB-Etytu", + "sourceHandle": "{œdataTypeœ: œAstraDBœ, œidœ: œAstraDB-Etytuœ, œnameœ: œsearch_resultsœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-mnAoF", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-mnAoFœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "data": { + "sourceHandle": { + "dataType": "OpenAIModel", + "id": "OpenAIModel-690Qx", + "name": "text_output", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-zpEiC", + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__OpenAIModel-690Qx{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-690Qxœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-zpEiC{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-zpEiCœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", + "source": "OpenAIModel-690Qx", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-690Qxœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-zpEiC", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-zpEiCœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -267,7 +273,7 @@ "data": { "description": "Get chat inputs from the Playground.", "display_name": "Chat Input", - "id": "ChatInput-zJJGs", + "id": "ChatInput-8NQlz", "node": { "base_classes": [ "Message" @@ -531,10 +537,10 @@ }, "dragging": false, "height": 234, - "id": "ChatInput-zJJGs", + "id": "ChatInput-8NQlz", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 743.9745420290319, @@ -552,7 +558,7 @@ "data": { "description": "Convert Data into plain text following a specified template.", "display_name": "Parse Data", - "id": "ParseData-t1SQn", + "id": "ParseData-mnAoF", "node": { "base_classes": [ "Message" @@ -688,10 +694,10 @@ }, "dragging": false, "height": 350, - "id": "ParseData-t1SQn", + "id": "ParseData-mnAoF", "measured": { "height": 350, - "width": 320 + "width": 360 }, "position": { "x": 1606.0595305373527, @@ -709,7 +715,7 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-MGcZw", + "id": "Prompt-hdHKZ", "node": { "base_classes": [ "Message" @@ -868,10 +874,10 @@ }, "dragging": false, "height": 433, - "id": "Prompt-MGcZw", + "id": "Prompt-hdHKZ", "measured": { "height": 433, - "width": 320 + "width": 360 }, "position": { "x": 1977.9097981422992, @@ -889,7 +895,7 @@ "data": { "description": "Split text into chunks based on specified criteria.", "display_name": "Split Text", - "id": "SplitText-FIjro", + "id": "SplitText-oI0KK", "node": { "base_classes": [ "Data" @@ -1036,10 +1042,10 @@ }, "dragging": false, "height": 475, - "id": "SplitText-FIjro", + "id": "SplitText-oI0KK", "measured": { "height": 475, - "width": 320 + "width": 360 }, "position": { "x": 1683.4543896546102, @@ -1055,7 +1061,7 @@ }, { "data": { - "id": "note-piPEI", + "id": "note-8MY1o", "node": { "description": "## 🐕 2. Retriever Flow\n\nThis flow answers your questions with contextual data retrieved from your vector database.\n\nOpen the **Playground** and ask, \n\n```\nWhat is this document about?\n```\n", "display_name": "", @@ -1068,10 +1074,10 @@ }, "dragging": false, "height": 324, - "id": "note-piPEI", + "id": "note-8MY1o", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 374.388314931542, @@ -1092,7 +1098,7 @@ }, { "data": { - "id": "note-e0H33", + "id": "note-gi1uB", "node": { "description": "## 📖 README\n\nLoad your data into a vector database with the 📚 **Load Data** flow, and then use your data as chat context with the 🐕 **Retriever** flow.\n\n**🚨 Add your OpenAI API key as a global variable to easily add it to all of the OpenAI components in this flow.** \n\n**Quick start**\n1. Run the 📚 **Load Data** flow.\n2. Run the 🐕 **Retriever** flow.\n\n**Next steps** \n\n- Experiment by changing the prompt and the loaded data to see how the bot's responses change. \n\nFor more info, see the [Langflow docs](https://docs.langflow.org/starter-projects-vector-store-rag).", "display_name": "Read Me", @@ -1105,10 +1111,10 @@ }, "dragging": false, "height": 324, - "id": "note-e0H33", + "id": "note-gi1uB", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 94.28986613312418, @@ -1131,7 +1137,7 @@ "data": { "description": "Display a chat message in the Playground.", "display_name": "Chat Output", - "id": "ChatOutput-dMbC5", + "id": "ChatOutput-zpEiC", "node": { "base_classes": [ "Message" @@ -1222,6 +1228,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -1238,7 +1262,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -1269,13 +1293,15 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_input": true, @@ -1393,10 +1419,10 @@ }, "dragging": false, "height": 234, - "id": "ChatOutput-dMbC5", + "id": "ChatOutput-zpEiC", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": 2738.611008351098, @@ -1412,7 +1438,7 @@ }, { "data": { - "id": "OpenAIEmbeddings-XD5Ju", + "id": "OpenAIEmbeddings-eerk9", "node": { "base_classes": [ "Embeddings" @@ -1709,7 +1735,7 @@ "show": true, "title_case": false, "type": "str", - "value": "OPENAI_API_KEY" + "value": "" }, "openai_api_type": { "_input_type": "MessageTextInput", @@ -1892,10 +1918,10 @@ }, "dragging": false, "height": 320, - "id": "OpenAIEmbeddings-XD5Ju", + "id": "OpenAIEmbeddings-eerk9", "measured": { "height": 320, - "width": 320 + "width": 360 }, "position": { "x": 825.435626932521, @@ -1911,7 +1937,7 @@ }, { "data": { - "id": "note-EReHj", + "id": "note-CW9QR", "node": { "description": "## 📚 1. Load Data Flow\n\nRun this first! Load data from a local file and embed it into the vector database.\n\nSelect a Database and a Collection, or create new ones. \n\nClick ▶️ **Run component** on the **Astra DB** component to load your data.\n\n* If you're using OSS Langflow, add your Astra DB Application Token to the Astra DB component.\n\n#### Next steps:\n Experiment by changing the prompt and the contextual data to see how the retrieval flow's responses change.", "display_name": "", @@ -1924,10 +1950,10 @@ }, "dragging": false, "height": 324, - "id": "note-EReHj", + "id": "note-CW9QR", "measured": { "height": 324, - "width": 325 + "width": 328 }, "position": { "x": 955.3277857006676, @@ -1948,7 +1974,7 @@ }, { "data": { - "id": "OpenAIEmbeddings-Au4nR", + "id": "OpenAIEmbeddings-u7coL", "node": { "base_classes": [ "Embeddings" @@ -2245,7 +2271,7 @@ "show": true, "title_case": false, "type": "str", - "value": "OPENAI_API_KEY" + "value": "" }, "openai_api_type": { "_input_type": "MessageTextInput", @@ -2428,10 +2454,10 @@ }, "dragging": false, "height": 320, - "id": "OpenAIEmbeddings-Au4nR", + "id": "OpenAIEmbeddings-u7coL", "measured": { "height": 320, - "width": 320 + "width": 360 }, "position": { "x": 1690.9220896443658, @@ -2447,7 +2473,7 @@ }, { "data": { - "id": "File-FJNXb", + "id": "File-7fTNc", "node": { "base_classes": [ "Data" @@ -2673,10 +2699,10 @@ }, "dragging": false, "height": 367, - "id": "File-FJNXb", + "id": "File-7fTNc", "measured": { "height": 367, - "width": 320 + "width": 360 }, "position": { "x": 1314.0643184619548, @@ -2692,7 +2718,7 @@ }, { "data": { - "id": "note-t3HXf", + "id": "note-WQR48", "node": { "description": "### 💡 Add your OpenAI API key here 👇", "display_name": "", @@ -2705,10 +2731,10 @@ }, "dragging": false, "height": 324, - "id": "note-t3HXf", + "id": "note-WQR48", "measured": { "height": 324, - "width": 324 + "width": 326 }, "position": { "x": 1692.2322233423606, @@ -2724,7 +2750,7 @@ }, { "data": { - "id": "note-OAFzt", + "id": "note-yMoT0", "node": { "description": "### 💡 Add your OpenAI API key here 👇", "display_name": "", @@ -2737,10 +2763,10 @@ }, "dragging": false, "height": 324, - "id": "note-OAFzt", + "id": "note-yMoT0", "measured": { "height": 324, - "width": 324 + "width": 326 }, "position": { "x": 824.1003268813427, @@ -2756,7 +2782,7 @@ }, { "data": { - "id": "note-romN9", + "id": "note-nfo9c", "node": { "description": "### 💡 Add your OpenAI API key here 👇", "display_name": "", @@ -2769,10 +2795,10 @@ }, "dragging": false, "height": 324, - "id": "note-romN9", + "id": "note-nfo9c", "measured": { "height": 324, - "width": 324 + "width": 326 }, "position": { "x": 2350.297636215281, @@ -2788,7 +2814,7 @@ }, { "data": { - "id": "OpenAIModel-8NmZL", + "id": "OpenAIModel-690Qx", "node": { "base_classes": [ "LanguageModel", @@ -3156,10 +3182,10 @@ "type": "OpenAIModel" }, "dragging": false, - "id": "OpenAIModel-8NmZL", + "id": "OpenAIModel-690Qx", "measured": { - "height": 656, - "width": 320 + "height": 734, + "width": 360 }, "position": { "x": 2365.714820732046, @@ -3170,7 +3196,7 @@ }, { "data": { - "id": "AstraDB-kvbzj", + "id": "AstraDB-UZ35f", "node": { "base_classes": [ "Data", @@ -3358,16 +3384,35 @@ ], "name": "create_collection", "template": { - "new_collection_name": { - "_input_type": "StrInput", + "dimension": { + "_input_type": "IntInput", "advanced": false, - "display_name": "Name", + "display_name": "Dimensions (Required only for `Bring your own`)", "dynamic": false, - "info": "Name of the new collection to create in Astra DB.", + "info": "Dimensions of the embeddings to generate.", "list": false, "list_add_label": "Add More", - "load_from_db": false, - "name": "new_collection_name", + "name": "dimension", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 1024 + }, + "embedding_generation_model": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Embedding model", + "dynamic": false, + "info": "Model to use for generating embeddings.", + "name": "embedding_generation_model", + "options": [], + "options_metadata": [], "placeholder": "", "required": true, "show": true, @@ -3401,17 +3446,16 @@ "type": "str", "value": "" }, - "embedding_generation_model": { - "_input_type": "DropdownInput", + "new_collection_name": { + "_input_type": "StrInput", "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Embedding model", + "display_name": "Name", "dynamic": false, - "info": "Model to use for generating embeddings.", - "name": "embedding_generation_model", - "options": [], - "options_metadata": [], + "info": "Name of the new collection to create in Astra DB.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "new_collection_name", "placeholder": "", "required": true, "show": true, @@ -3420,24 +3464,6 @@ "trace_as_metadata": true, "type": "str", "value": "" - }, - "dimension": { - "_input_type": "IntInput", - "advanced": false, - "display_name": "Dimensions (Required only for `Bring your own`)", - "dynamic": false, - "info": "Dimensions of the embeddings to generate.", - "list": false, - "list_add_label": "Add More", - "name": "dimension", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "int", - "value": 1024 } } } @@ -3498,25 +3524,6 @@ ], "name": "create_database", "template": { - "new_database_name": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Name", - "dynamic": false, - "info": "Name of the new database to create in Astra DB.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "new_database_name", - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "" - }, "cloud_provider": { "_input_type": "DropdownInput", "advanced": false, @@ -3542,6 +3549,25 @@ "type": "str", "value": "" }, + "new_database_name": { + "_input_type": "StrInput", + "advanced": false, + "display_name": "Name", + "dynamic": false, + "info": "Name of the new database to create in Astra DB.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "new_database_name", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "region": { "_input_type": "DropdownInput", "advanced": false, @@ -3827,7 +3853,7 @@ "show": true, "title_case": false, "type": "str", - "value": "ASTRA_DB_APPLICATION_TOKEN" + "value": "" } }, "tool_mode": false @@ -3836,10 +3862,10 @@ "type": "AstraDB" }, "dragging": false, - "id": "AstraDB-kvbzj", + "id": "AstraDB-UZ35f", "measured": { - "height": 532, - "width": 320 + "height": 594, + "width": 360 }, "position": { "x": 2066.56373433188, @@ -3850,7 +3876,7 @@ }, { "data": { - "id": "AstraDB-sfnha", + "id": "AstraDB-Etytu", "node": { "base_classes": [ "Data", @@ -4037,16 +4063,35 @@ ], "name": "create_collection", "template": { - "new_collection_name": { - "_input_type": "StrInput", + "dimension": { + "_input_type": "IntInput", "advanced": false, - "display_name": "Name", + "display_name": "Dimensions (Required only for `Bring your own`)", "dynamic": false, - "info": "Name of the new collection to create in Astra DB.", + "info": "Dimensions of the embeddings to generate.", "list": false, "list_add_label": "Add More", - "load_from_db": false, - "name": "new_collection_name", + "name": "dimension", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 1024 + }, + "embedding_generation_model": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Embedding model", + "dynamic": false, + "info": "Model to use for generating embeddings.", + "name": "embedding_generation_model", + "options": [], + "options_metadata": [], "placeholder": "", "required": true, "show": true, @@ -4080,17 +4125,16 @@ "type": "str", "value": "" }, - "embedding_generation_model": { - "_input_type": "DropdownInput", + "new_collection_name": { + "_input_type": "StrInput", "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Embedding model", + "display_name": "Name", "dynamic": false, - "info": "Model to use for generating embeddings.", - "name": "embedding_generation_model", - "options": [], - "options_metadata": [], + "info": "Name of the new collection to create in Astra DB.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "new_collection_name", "placeholder": "", "required": true, "show": true, @@ -4099,24 +4143,6 @@ "trace_as_metadata": true, "type": "str", "value": "" - }, - "dimension": { - "_input_type": "IntInput", - "advanced": false, - "display_name": "Dimensions (Required only for `Bring your own`)", - "dynamic": false, - "info": "Dimensions of the embeddings to generate.", - "list": false, - "list_add_label": "Add More", - "name": "dimension", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "int", - "value": 1024 } } } @@ -4177,25 +4203,6 @@ ], "name": "create_database", "template": { - "new_database_name": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Name", - "dynamic": false, - "info": "Name of the new database to create in Astra DB.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "new_database_name", - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "" - }, "cloud_provider": { "_input_type": "DropdownInput", "advanced": false, @@ -4221,6 +4228,25 @@ "type": "str", "value": "" }, + "new_database_name": { + "_input_type": "StrInput", + "advanced": false, + "display_name": "Name", + "dynamic": false, + "info": "Name of the new database to create in Astra DB.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "new_database_name", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "region": { "_input_type": "DropdownInput", "advanced": false, @@ -4252,7 +4278,14 @@ "info": "The Database name for the Astra DB instance.", "name": "database_name", "options": [], - "options_metadata": [], + "options_metadata": [ + { + "api_endpoint": "https://deb10a81-3c5d-4fd3-8b1b-945915d2835b-us-east-2.apps.astra.datastax.com", + "collections": 10, + "icon": "data", + "status": null + } + ], "placeholder": "", "real_time_refresh": true, "refresh_button": true, @@ -4506,7 +4539,7 @@ "show": true, "title_case": false, "type": "str", - "value": "ASTRA_DB_APPLICATION_TOKEN" + "value": "" } }, "tool_mode": false @@ -4515,10 +4548,10 @@ "type": "AstraDB" }, "dragging": false, - "id": "AstraDB-sfnha", + "id": "AstraDB-Etytu", "measured": { - "height": 532, - "width": 320 + "height": 594, + "width": 360 }, "position": { "x": 1220.0616335680065, @@ -4529,9 +4562,9 @@ } ], "viewport": { - "x": 23.946958998386208, - "y": -163.43184624766263, - "zoom": 0.44406917240373706 + "x": 27.16134293669745, + "y": -178.0488836602833, + "zoom": 0.48566409515519327 } }, "description": "Load your data for chat context with Retrieval Augmented Generation.", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index 52d5e0746d..a24b687a1f 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -157,6 +157,8 @@ "fieldName": "input_value", "id": "ChatOutput-W5R97", "inputTypes": [ + "Data", + "DataFrame", "Message" ], "type": "str" @@ -167,7 +169,7 @@ "source": "Agent-Px7Zt", "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-Px7Ztœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", "target": "ChatOutput-W5R97", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-W5R97œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-W5R97œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -558,7 +560,7 @@ "input_types": [ "Message" ], - "load_from_db": false, + "load_from_db": true, "name": "api_key", "password": true, "placeholder": "", @@ -2176,6 +2178,24 @@ "type": "str", "value": "" }, + "clean_data": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Basic Clean Data", + "dynamic": false, + "info": "Whether to clean the data", + "list": false, + "list_add_label": "Add More", + "name": "clean_data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "code": { "advanced": true, "dynamic": true, @@ -2192,7 +2212,7 @@ "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" + "value": "from typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\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 HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\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 BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\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 # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\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\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\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\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if not isinstance(self.input_value, Data | DataFrame | Message | str | list):\n msg = f\"Expected Data or DataFrame or Message or str, got {type(self.input_value).__name__}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n return data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n return self._safe_convert(self.input_value)\n" }, "data_template": { "_input_type": "MessageTextInput", @@ -2224,6 +2244,8 @@ "dynamic": false, "info": "Message to be passed as output.", "input_types": [ + "Data", + "DataFrame", "Message" ], "list": false, @@ -2231,7 +2253,7 @@ "load_from_db": false, "name": "input_value", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, diff --git a/src/backend/tests/unit/components/outputs/test_chat_output_component.py b/src/backend/tests/unit/components/outputs/test_chat_output_component.py new file mode 100644 index 0000000000..c698624bd9 --- /dev/null +++ b/src/backend/tests/unit/components/outputs/test_chat_output_component.py @@ -0,0 +1,98 @@ +import pytest +from langflow.components.outputs import ChatOutput +from langflow.schema.data import Data +from langflow.schema.dataframe import DataFrame +from langflow.schema.message import Message +from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI + +from tests.base import ComponentTestBaseWithClient + + +@pytest.mark.usefixtures("client") +class TestChatOutput(ComponentTestBaseWithClient): + @pytest.fixture + def component_class(self): + return ChatOutput + + @pytest.fixture + def default_kwargs(self): + return { + "input_value": "Hello, how are you?", + "should_store_message": True, + "sender": MESSAGE_SENDER_AI, + "sender_name": MESSAGE_SENDER_NAME_AI, + "session_id": "test_session_123", + "data_template": "{text}", + "background_color": "#f0f0f0", + "chat_icon": "🤖", + "text_color": "#000000", + "clean_data": True, + } + + @pytest.fixture + def file_names_mapping(self): + return [ + {"version": "1.0.19", "module": "outputs", "file_name": "ChatOutput"}, + {"version": "1.1.0", "module": "outputs", "file_name": "chat"}, + {"version": "1.1.1", "module": "outputs", "file_name": "chat"}, + ] + + async def test_process_string_input(self, component_class, default_kwargs): + """Test processing a simple string input.""" + component = component_class(**default_kwargs) + input_text = "Hello, this is a test message" + component.input_value = input_text + result = await component.message_response() + assert result.text == input_text + assert result.sender == MESSAGE_SENDER_AI + assert result.sender_name == MESSAGE_SENDER_NAME_AI + + async def test_process_data_input(self, component_class, default_kwargs): + """Test processing a Data object input.""" + component = component_class(**default_kwargs) + data = Data(text="Test data message") + component.input_value = data + result = await component.message_response() + assert result.text == "Test data message" + assert result.sender == MESSAGE_SENDER_AI + + async def test_process_dataframe_input(self, component_class, default_kwargs): + """Test processing a DataFrame input.""" + component = component_class(**default_kwargs) + sample_df = DataFrame(data={"col1": ["A", "B"], "col2": [1, 2]}) + component.input_value = sample_df + result = await component.message_response() + assert "col1" in result.text + assert "col2" in result.text + assert "A" in result.text + assert "B" in result.text + + async def test_process_message_input(self, component_class, default_kwargs): + """Test processing a Message object input.""" + component = component_class(**default_kwargs) + message = Message(text="Test message content") + component.input_value = message + result = await component.message_response() + assert result.text == "Test message content" + assert result.sender == MESSAGE_SENDER_AI + + async def test_process_list_input(self, component_class, default_kwargs): + """Test processing a list of inputs.""" + component = component_class(**default_kwargs) + input_list = ["First message", Data(text="Second message"), Message(text="Third message")] + component.input_value = input_list + result = await component.message_response() + assert "First message" in result.text + assert "Second message" in result.text + assert "Third message" in result.text + + async def test_invalid_input(self, component_class, default_kwargs): + """Test handling of invalid input.""" + component = component_class(**default_kwargs) + component.input_value = None + with pytest.raises(ValueError, match="Input data cannot be None"): + await component.message_response() + + component.input_value = 123 # Invalid type + with pytest.raises(TypeError, match="Expected Data or DataFrame or Message or str"): + await component.message_response() diff --git a/src/backend/tests/unit/components/outputs/test_output_components.py b/src/backend/tests/unit/components/outputs/test_output_components.py index 2ffe9aa3cd..fd698d3881 100644 --- a/src/backend/tests/unit/components/outputs/test_output_components.py +++ b/src/backend/tests/unit/components/outputs/test_output_components.py @@ -1,37 +1,7 @@ import pytest -from langflow.components.outputs import ChatOutput, TextOutputComponent -from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI +from langflow.components.outputs import TextOutputComponent -from tests.base import ComponentTestBaseWithClient, ComponentTestBaseWithoutClient - - -@pytest.mark.usefixtures("client") -class TestChatOutput(ComponentTestBaseWithClient): - @pytest.fixture - def component_class(self): - return ChatOutput - - @pytest.fixture - def default_kwargs(self): - return { - "input_value": "Hello, how are you?", - "should_store_message": True, - "sender": MESSAGE_SENDER_AI, - "sender_name": MESSAGE_SENDER_NAME_AI, - "session_id": "test_session_123", - "data_template": "{text}", - "background_color": "#f0f0f0", - "chat_icon": "🤖", - "text_color": "#000000", - } - - @pytest.fixture - def file_names_mapping(self): - return [ - {"version": "1.0.19", "module": "outputs", "file_name": "ChatOutput"}, - {"version": "1.1.0", "module": "outputs", "file_name": "chat"}, - {"version": "1.1.1", "module": "outputs", "file_name": "chat"}, - ] +from tests.base import ComponentTestBaseWithoutClient class TestTextOutputComponent(ComponentTestBaseWithoutClient): From b43bf8f783545a73c4392446010ab17f16131fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kopeck=C3=BD?= Date: Wed, 19 Feb 2025 20:10:26 +0100 Subject: [PATCH 3/3] feat: add apify actors integration (#5862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial apify actor component version * clean code mess * add apify integrations docs page, manual tests protocol, remove temp scripts * fix lint type issue * fix lint and format issues * rename run_actor.py to apify_actor.py * actor -> Actor * update component description and docs link * add wcc link * refactor _get_actor_input_schema_from_build * actor_input -> run_input * refactor - make suitable methods static * static methods remove _ before name, docs actor_input -> run_input * update docs image * improve docs * fix typos, rename test .md to run_actor.md * remove the actor link, that is not clickable * rename ApifyRunActor -> ApifyActors, improve docs * code refactor, added concrete examples * take input_schema arg instead of build * fix typo * add custom user-agent * remove beta label * Update docs/docs/Integrations/Apify/integrations-apify.md Co-authored-by: Jiří Spilka * toolify_actor_id_str -> actor_id_to_tool_name * add simple flow example withtou an agent, removed actor list, added simple how to * fix typos * improve how-to section * remove usege from the component section * improve example flows section * remove unnecessary sentence * format * fix submodel serialization * LCToolComponent -> Component * flatten output remove question mark * add actor run logs to component logs * fix grammar, typos and docstrings * [autofix.ci] apply automated fixes --------- Co-authored-by: Jiří Spilka Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Edwin Jose --- .../Integrations/Apify/apify_agent_flow.png | Bin 0 -> 137379 bytes .../Apify/apify_agent_flow_simple.png | Bin 0 -> 119473 bytes .../Integrations/Apify/apify_flow_wcc.png | Bin 0 -> 81959 bytes .../Integrations/Apify/integrations-apify.md | 63 ++++ docs/sidebars.js | 1 + pyproject.toml | 1 + .../langflow/components/apify/__init__.py | 5 + .../langflow/components/apify/apify_actor.py | 324 ++++++++++++++++++ .../components/apify/apify_actor.md | 13 + src/frontend/src/icons/Apify/Apify.jsx | 27 ++ src/frontend/src/icons/Apify/apify.svg | 14 + src/frontend/src/icons/Apify/index.tsx | 8 + src/frontend/src/utils/styleUtils.ts | 5 + uv.lock | 25 ++ 14 files changed, 486 insertions(+) create mode 100644 docs/docs/Integrations/Apify/apify_agent_flow.png create mode 100644 docs/docs/Integrations/Apify/apify_agent_flow_simple.png create mode 100644 docs/docs/Integrations/Apify/apify_flow_wcc.png create mode 100644 docs/docs/Integrations/Apify/integrations-apify.md create mode 100644 src/backend/base/langflow/components/apify/__init__.py create mode 100644 src/backend/base/langflow/components/apify/apify_actor.py create mode 100644 src/backend/tests/integration/components/apify/apify_actor.md create mode 100644 src/frontend/src/icons/Apify/Apify.jsx create mode 100644 src/frontend/src/icons/Apify/apify.svg create mode 100644 src/frontend/src/icons/Apify/index.tsx diff --git a/docs/docs/Integrations/Apify/apify_agent_flow.png b/docs/docs/Integrations/Apify/apify_agent_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..fb93d1ba14b10f6746b7dbd199560879e2efdf92 GIT binary patch literal 137379 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 literal 0 HcmV?d00001 diff --git a/docs/docs/Integrations/Apify/apify_agent_flow_simple.png b/docs/docs/Integrations/Apify/apify_agent_flow_simple.png new file mode 100644 index 0000000000000000000000000000000000000000..ee6ca2b8f14fc2bf25db0066f980f8db5b5fd9fe GIT binary patch literal 119473 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ÜL-yL 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 UV%aRWmH=ML^6GLZS+l4A3u7P2ssI20 literal 0 HcmV?d00001 diff --git a/docs/docs/Integrations/Apify/apify_flow_wcc.png b/docs/docs/Integrations/Apify/apify_flow_wcc.png new file mode 100644 index 0000000000000000000000000000000000000000..776560b555764d6065bccbafd64251cca3a4dd6d GIT binary patch literal 81959 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 literal 0 HcmV?d00001 diff --git a/docs/docs/Integrations/Apify/integrations-apify.md b/docs/docs/Integrations/Apify/integrations-apify.md new file mode 100644 index 0000000000..464b641bb2 --- /dev/null +++ b/docs/docs/Integrations/Apify/integrations-apify.md @@ -0,0 +1,63 @@ +--- +title: Apify +slug: /integrations-apify +--- + +# Integrate Apify with Langflow + +[Apify](https://apify.com/) is a web scraping and data extraction platform. It provides an app store with more than three thousand ready-made cloud tools called Actors. + +The Apify components allow you to run Apify Actors in your flow to accomplish tasks such as: + +- Crawling websites and extracting text content +- Scraping social media platforms like Instagram and Facebook +- Extracting data from Google Maps +- Inserting data into a PostgreSQL/MySQL/MSSQL database +- Running various other automation tasks + +More info about Apify: + +- [Website](https://apify.com/) +- [Apify Actor Store](https://apify.com/store) +- [Actor Whitepaper](https://whitepaper.actor/) + +## Prerequisites + +You need an **Apify API token**. You can create a free account on [Apify](https://apify.com/) and generate your API key in the Apify Console. [Get a Free API key here](https://docs.apify.com/platform/integrations/api). + +Enter the key in the *Apify Token* field in all components that require the key. + +## Example flows + +### Extract website text content in Markdown format + +Use the [Website Content Crawler Actor](https://apify.com/apify/website-content-crawler) to extract text content in Markdown format from a website and process it in your flow. +![Apify Flow - Website Content Crawler](./apify_flow_wcc.png) + +### Analyze and process website content with an Agent + +Building on the previous example, this flow not only extracts website content using the [Website Content Crawler Actor](https://apify.com/apify/website-content-crawler) but also processes and analyzes it with an agent. The agent takes the extracted data and transforms it into summaries, insights, or structured responses, making the information more actionable. Unlike simple extraction, this approach enables automated content understanding and contextual processing. +![Apify Agent Flow - Simple](./apify_agent_flow_simple.png) + +### Search and analyze social media profiles with an Agent + +Perform comprehensive social media research with multiple Apify Actors. Start with the [Google Search Results Scraper Actor](https://apify.com/apify/google-search-scraper) to find relevant social media profiles, then use the [TikTok Data Extractor Actor](https://apify.com/clockworks/free-tiktok-scraper) to gather data and videos. The agent streamlines the process by collecting links from Google and retrieving content from TikTok, enabling deeper analysis of a person, brand, or topic. +![Apify Agent Flow](./apify_agent_flow.png) + +## Components + +### Apify Actors + +This component allows you to run Apify Actors in your flow. It can be used manually by providing run input or integrated as a tool for an AI Agent. When used with an AI Agent, the agent can leverage the Apify Actors to perform various tasks. + +- **Input**: + - Apify Token: Your API key. + - Actor: The Apify Actor to run. Example: `apify/website-content-crawler`. + - Run Input: The JSON input for configuring the Actor run. + +- **Output**: + - Actor Run Result: The JSON response containing the output of the Actor run. + +## How to use Apify Actors in Langflow + +First, you need to pick an Actor that you want to use in your flow from the [Apify Actor Store](https://apify.com/store). Then, create the **Apify Actors** component and input your Apify API token and the Actor ID. You can find the Actor ID in the Apify Actor Store, for instance, the [Website Content Crawler](https://apify.com/apify/website-content-crawler) has Actor ID `apify/website-content-crawler`. Now you can either connect the **Tool** output to an AI Agent or configure the Run input JSON manually and run the component to retrieve data from the **Output Data**. Example Run input can be obtained from the Actor details page in the Apify Actor Store. See the **JSON Example** in the input schema section [here](https://apify.com/apify/website-content-crawler/input-schema). diff --git a/docs/sidebars.js b/docs/sidebars.js index bd0c38f40e..40d1001fcd 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -100,6 +100,7 @@ module.exports = { type: "category", label: "Integrations", items: [ + "Integrations/Apify/integrations-apify", "Integrations/integrations-assemblyai", "Integrations/Composio/integrations-composio", "Integrations/integrations-langfuse", diff --git a/pyproject.toml b/pyproject.toml index 9f08c1aa99..ac92e0b1c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,6 +112,7 @@ dependencies = [ "scrapegraph-py>=1.12.0", "pydantic-ai>=0.0.19", "smolagents>=1.8.0", + "apify-client>=1.8.1", ] [tool.uv.sources] diff --git a/src/backend/base/langflow/components/apify/__init__.py b/src/backend/base/langflow/components/apify/__init__.py new file mode 100644 index 0000000000..1cb5e7c393 --- /dev/null +++ b/src/backend/base/langflow/components/apify/__init__.py @@ -0,0 +1,5 @@ +from .apify_actor import ApifyActorsComponent + +__all__ = [ + "ApifyActorsComponent", +] diff --git a/src/backend/base/langflow/components/apify/apify_actor.py b/src/backend/base/langflow/components/apify/apify_actor.py new file mode 100644 index 0000000000..7d6c6414ad --- /dev/null +++ b/src/backend/base/langflow/components/apify/apify_actor.py @@ -0,0 +1,324 @@ +import json +import string +from typing import Any, cast + +from apify_client import ApifyClient +from langchain_community.document_loaders.apify_dataset import ApifyDatasetLoader +from langchain_core.tools import BaseTool +from pydantic import BaseModel, Field, field_serializer + +from langflow.custom import Component +from langflow.field_typing import Tool +from langflow.inputs.inputs import BoolInput +from langflow.io import MultilineInput, Output, SecretStrInput, StrInput +from langflow.schema import Data + +MAX_DESCRIPTION_LEN = 250 + + +class ApifyActorsComponent(Component): + display_name = "Apify Actors" + description = ( + "Use Apify Actors to extract data from hundreds of places fast. " + "This component can be used in a flow to retrieve data or as a tool with an agent." + ) + documentation: str = "http://docs.langflow.org/integrations-apify" + icon = "Apify" + name = "ApifyActors" + + inputs = [ + SecretStrInput( + name="apify_token", + display_name="Apify Token", + info="The API token for the Apify account.", + required=True, + password=True, + ), + StrInput( + name="actor_id", + display_name="Actor", + info=( + "Actor name from Apify store to run. For example 'apify/website-content-crawler' " + "to use the Website Content Crawler Actor." + ), + required=True, + ), + # multiline input is more pleasant to use than the nested dict input + MultilineInput( + name="run_input", + display_name="Run input", + info=( + 'The JSON input for the Actor run. For example for the "apify/website-content-crawler" Actor: ' + '{"startUrls":[{"url":"https://docs.apify.com/academy/web-scraping-for-beginners"}],"maxCrawlDepth":0}' + ), + value="{}", + required=True, + ), + MultilineInput( + name="dataset_fields", + display_name="Output fields", + info=( + "Fields to extract from the dataset, split by commas. " + "Other fields will be ignored. Dots in nested structures will be replaced by underscores. " + "Sample input: 'text, metadata.title'. " + "Sample output: {'text': 'page content here', 'metadata_title': 'page title here'}. " + "For example, for the 'apify/website-content-crawler' Actor, you can extract the 'markdown' field, " + "which is the content of the website in markdown format." + ), + ), + BoolInput( + name="flatten_dataset", + display_name="Flatten output", + info=( + "The output dataset will be converted from a nested format to a flat structure. " + "Dots in nested structure will be replaced by underscores. " + "This is useful for further processing of the Data object. " + "For example, {'a': {'b': 1}} will be flattened to {'a_b': 1}." + ), + ), + ] + + outputs = [ + Output(display_name="Output", name="output", type_=list[Data], method="run_model"), + Output(display_name="Tool", name="tool", type_=Tool, method="build_tool"), + ] + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._apify_client: ApifyClient | None = None + + def run_model(self) -> list[Data]: + """Run the Actor and return node output.""" + _input = json.loads(self.run_input) + fields = ApifyActorsComponent.parse_dataset_fields(self.dataset_fields) if self.dataset_fields else None + res = self._run_actor(self.actor_id, _input, fields=fields) + if self.flatten_dataset: + res = [ApifyActorsComponent.flatten(item) for item in res] + data = [Data(data=item) for item in res] + + self.status = data + return data + + def build_tool(self) -> Tool: + """Build a tool for an agent that runs the Apify Actor.""" + actor_id = self.actor_id + + build = self._get_actor_latest_build(actor_id) + readme = build.get("readme", "")[:250] + "..." + if not (input_schema_str := build.get("inputSchema")): + msg = "Input schema not found" + raise ValueError(msg) + input_schema = json.loads(input_schema_str) + properties, required = ApifyActorsComponent.get_actor_input_schema_from_build(input_schema) + properties = {"run_input": properties} + + # works from input schema + _info = [ + ( + "JSON encoded as a string with input schema (STRICTLY FOLLOW JSON FORMAT AND SCHEMA):\n\n" + f"{json.dumps(properties, separators=(',', ':'))}" + ) + ] + if required: + _info.append("\n\nRequired fields:\n" + "\n".join(required)) + + info = "".join(_info) + + input_model_cls = ApifyActorsComponent.create_input_model_class(info) + tool_cls = ApifyActorsComponent.create_tool_class(self, readme, input_model_cls, actor_id) + + return cast("Tool", tool_cls()) + + @staticmethod + def create_tool_class( + parent: "ApifyActorsComponent", readme: str, input_model: type[BaseModel], actor_id: str + ) -> type[BaseTool]: + """Create a tool class that runs an Apify Actor.""" + + class ApifyActorRun(BaseTool): + """Tool that runs Apify Actors.""" + + name: str = f"apify_actor_{ApifyActorsComponent.actor_id_to_tool_name(actor_id)}" + description: str = ( + "Run an Apify Actor with the given input. " + "Here is a part of the currently loaded Actor README:\n\n" + f"{readme}\n\n" + ) + + args_schema: type[BaseModel] = input_model + + @field_serializer("args_schema") + def serialize_args_schema(self, args_schema): + return args_schema.schema() + + def _run(self, run_input: str | dict) -> str: + """Use the Apify Actor.""" + input_dict = json.loads(run_input) if isinstance(run_input, str) else run_input + + # retrieve if nested, just in case + input_dict = input_dict.get("run_input", input_dict) + + res = parent._run_actor(actor_id, input_dict) + return "\n\n".join([ApifyActorsComponent.dict_to_json_str(item) for item in res]) + + return ApifyActorRun + + @staticmethod + def create_input_model_class(description: str) -> type[BaseModel]: + """Create a Pydantic model class for the Actor input.""" + + class ActorInput(BaseModel): + """Input for the Apify Actor tool.""" + + run_input: str = Field(..., description=description) + + return ActorInput + + def _get_apify_client(self) -> ApifyClient: + """Get the Apify client. + + Is created if not exists or token changes. + """ + if not self.apify_token: + msg = "API token is required." + raise ValueError(msg) + # when token changes, create a new client + if self._apify_client is None or self._apify_client.token != self.apify_token: + self._apify_client = ApifyClient(self.apify_token) + if httpx_client := self._apify_client.http_client.httpx_client: + httpx_client.headers["user-agent"] += "; Origin/langflow" + return self._apify_client + + def _get_actor_latest_build(self, actor_id: str) -> dict: + """Get the latest build of an Actor from the default build tag.""" + client = self._get_apify_client() + actor = client.actor(actor_id=actor_id) + if not (actor_info := actor.get()): + msg = f"Actor {actor_id} not found." + raise ValueError(msg) + + default_build_tag = actor_info.get("defaultRunOptions", {}).get("build") + latest_build_id = actor_info.get("taggedBuilds", {}).get(default_build_tag, {}).get("buildId") + + if (build := client.build(latest_build_id).get()) is None: + msg = f"Build {latest_build_id} not found." + raise ValueError(msg) + + return build + + @staticmethod + def get_actor_input_schema_from_build(input_schema: dict) -> tuple[dict, list[str]]: + """Get the input schema from the Actor build. + + Trim the description to 250 characters. + """ + properties = input_schema.get("properties", {}) + required = input_schema.get("required", []) + + properties_out: dict = {} + for item, meta in properties.items(): + properties_out[item] = {} + if desc := meta.get("description"): + properties_out[item]["description"] = ( + desc[:MAX_DESCRIPTION_LEN] + "..." if len(desc) > MAX_DESCRIPTION_LEN else desc + ) + for key_name in ("type", "default", "prefill", "enum"): + if value := meta.get(key_name): + properties_out[item][key_name] = value + + return properties_out, required + + def _get_run_dataset_id(self, run_id: str) -> str: + """Get the dataset id from the run id.""" + client = self._get_apify_client() + run = client.run(run_id=run_id) + if (dataset := run.dataset().get()) is None: + msg = "Dataset not found" + raise ValueError(msg) + if (did := dataset.get("id")) is None: + msg = "Dataset id not found" + raise ValueError(msg) + return did + + @staticmethod + def dict_to_json_str(d: dict) -> str: + """Convert a dictionary to a JSON string.""" + return json.dumps(d, separators=(",", ":"), default=lambda _: "") + + @staticmethod + def actor_id_to_tool_name(actor_id: str) -> str: + """Turn actor_id into a valid tool name. + + Tool name must only contain letters, numbers, underscores, dashes, + and cannot contain spaces. + """ + valid_chars = string.ascii_letters + string.digits + "_-" + return "".join(char if char in valid_chars else "_" for char in actor_id) + + def _run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]: + """Run an Apify Actor and return the output dataset. + + Args: + actor_id: Actor name from Apify store to run. + run_input: JSON input for the Actor. + fields: List of fields to extract from the dataset. Other fields will be ignored. + """ + client = self._get_apify_client() + if (details := client.actor(actor_id=actor_id).call(run_input=run_input, wait_secs=1)) is None: + msg = "Actor run details not found" + raise ValueError(msg) + if (run_id := details.get("id")) is None: + msg = "Run id not found" + raise ValueError(msg) + + if (run_client := client.run(run_id)) is None: + msg = "Run client not found" + raise ValueError(msg) + + # stream logs + with run_client.log().stream() as response: + if response: + for line in response.iter_lines(): + self.log(line) + run_client.wait_for_finish() + + dataset_id = self._get_run_dataset_id(run_id) + + loader = ApifyDatasetLoader( + dataset_id=dataset_id, + dataset_mapping_function=lambda item: item + if not fields + else {k.replace(".", "_"): ApifyActorsComponent.get_nested_value(item, k) for k in fields}, + ) + return loader.load() + + @staticmethod + def get_nested_value(data: dict[str, Any], key: str) -> Any: + """Get a nested value from a dictionary.""" + keys = key.split(".") + value = data + for k in keys: + if not isinstance(value, dict) or k not in value: + return None + value = value[k] + return value + + @staticmethod + def parse_dataset_fields(dataset_fields: str) -> list[str]: + """Convert a string of comma-separated fields into a list of fields.""" + dataset_fields = dataset_fields.replace("'", "").replace('"', "").replace("`", "") + return [field.strip() for field in dataset_fields.split(",")] + + @staticmethod + def flatten(d: dict) -> dict: + """Flatten a nested dictionary.""" + + def items(): + for key, value in d.items(): + if isinstance(value, dict): + for subkey, subvalue in ApifyActorsComponent.flatten(value).items(): + yield key + "_" + subkey, subvalue + else: + yield key, value + + return dict(items()) diff --git a/src/backend/tests/integration/components/apify/apify_actor.md b/src/backend/tests/integration/components/apify/apify_actor.md new file mode 100644 index 0000000000..7196993e45 --- /dev/null +++ b/src/backend/tests/integration/components/apify/apify_actor.md @@ -0,0 +1,13 @@ +# Apify Actors component tests + +This component was tested manually with various Apify Actors. The component was tested with manual runs and with an AI Agent as a tool. + +## Test cases + +### Run Apify Actors manually +Apify Actor input was manually configured and the component was run to retrieve data. +When invalid input was provided, the component returned an error message with details. + +### Run Apify Actors with AI Agent +Multiple Apify Actors components with different Actors were connected to an AI Agent. +The agent was given a task that required running multiple Apify Actors to complete. diff --git a/src/frontend/src/icons/Apify/Apify.jsx b/src/frontend/src/icons/Apify/Apify.jsx new file mode 100644 index 0000000000..4a60350914 --- /dev/null +++ b/src/frontend/src/icons/Apify/Apify.jsx @@ -0,0 +1,27 @@ +const SvgApifyLogo = (props) => ( + + + + + +); +export default SvgApifyLogo; diff --git a/src/frontend/src/icons/Apify/apify.svg b/src/frontend/src/icons/Apify/apify.svg new file mode 100644 index 0000000000..65dce4c198 --- /dev/null +++ b/src/frontend/src/icons/Apify/apify.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/frontend/src/icons/Apify/index.tsx b/src/frontend/src/icons/Apify/index.tsx new file mode 100644 index 0000000000..bd83ec4dc3 --- /dev/null +++ b/src/frontend/src/icons/Apify/index.tsx @@ -0,0 +1,8 @@ +import React, { forwardRef } from "react"; +import SvgApifyLogo from "./Apify"; + +export const ApifyIcon = forwardRef>( + (props, ref) => { + return ; + }, +); diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 586c61f0dd..05b950e5da 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -240,6 +240,7 @@ import { AWSIcon } from "../icons/AWS"; import { AgentQLIcon } from "../icons/AgentQL"; import { AirbyteIcon } from "../icons/Airbyte"; import { AnthropicIcon } from "../icons/Anthropic"; +import { ApifyIcon } from "../icons/Apify"; import { ArXivIcon } from "../icons/ArXiv"; import { ArizeIcon } from "../icons/Arize"; import { AssemblyAIIcon } from "../icons/AssemblyAI"; @@ -318,6 +319,7 @@ import { MistralIcon } from "../icons/mistral"; import { SupabaseIcon } from "../icons/supabase"; import { XAIIcon } from "../icons/xAI"; import { iconsType } from "../types/components"; + export const BG_NOISE = "url(data:image/png;base64,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)"; @@ -520,6 +522,8 @@ export const SIDEBAR_CATEGORIES = [ ]; export const SIDEBAR_BUNDLES = [ + // Add apify + { display_name: "Apify", name: "apify", icon: "Apify" }, { display_name: "LangChain", name: "langchain_utilities", icon: "LangChain" }, { display_name: "AgentQL", name: "agentql", icon: "AgentQL" }, { display_name: "AssemblyAI", name: "assemblyai", icon: "AssemblyAI" }, @@ -741,6 +745,7 @@ export const nodeIconsLucide: iconsType = { SearchAPI: SearchAPIIcon, Wikipedia: WikipediaIcon, Arize: ArizeIcon, + Apify: ApifyIcon, //Node Icons model_specs: FileSliders, diff --git a/uv.lock b/uv.lock index f9ec2c8f5f..21a9c349eb 100644 --- a/uv.lock +++ b/uv.lock @@ -228,6 +228,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 }, ] +[[package]] +name = "apify-client" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "apify-shared" }, + { name = "httpx" }, + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d7/37d955bff90f6dfc353642e4b4075db3dd9abf8f5111ed35152be3dfc3da/apify_client-1.8.1.tar.gz", hash = "sha256:2be1be7879570655bddeebf126833efe94cabb95b3755592845e92c20c70c674", size = 48422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/0b/42bb740ba9ff4cd540cc3c22687e3c07a823a5bdecb18a714de6df183a3a/apify_client-1.8.1-py3-none-any.whl", hash = "sha256:cfa6df3816c436204e37457fba28981a0ef6a7602cde372463f0f078eee64747", size = 73532 }, +] + +[[package]] +name = "apify-shared" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/12/3c6639116aba2b851b38bfcd5d81bb91f733f041c12c1d547028bd075a08/apify_shared-1.2.1.tar.gz", hash = "sha256:986557e2b01c584aa57258fb4af83d32ecb6979c0d804ccbfda7c0e79a2d00b1", size = 13597 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/ff/9d60edc7602587a2e83f86687c2a1794d6ec7711599dbd0fb075e12d6abd/apify_shared-1.2.1-py3-none-any.whl", hash = "sha256:cee136729c41c215796d8ca9f7b2e5736995d44f9471663d61827d16f592df9b", size = 12404 }, +] + [[package]] name = "appdirs" version = "1.4.4" @@ -4033,6 +4056,7 @@ source = { editable = "." } dependencies = [ { name = "ag2" }, { name = "aiofile" }, + { name = "apify-client" }, { name = "arize-phoenix-otel" }, { name = "assemblyai" }, { name = "astra-assistants", extra = ["tools"] }, @@ -4191,6 +4215,7 @@ dev = [ requires-dist = [ { name = "ag2", specifier = ">=0.1.0" }, { name = "aiofile", specifier = ">=3.9.0,<4.0.0" }, + { name = "apify-client", specifier = ">=1.8.1" }, { name = "arize-phoenix-otel", specifier = ">=0.6.1" }, { name = "assemblyai", specifier = "==0.35.1" }, { name = "astra-assistants", extras = ["tools"], specifier = "~=2.2.9" },