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 60cc01046..b8a562a27 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 @@ -7,47 +7,28 @@ "data": { "sourceHandle": { "dataType": "Memory", - "id": "Memory-lzTrH", + "id": "Memory-80v92", "name": "messages_text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "CHAT_HISTORY", - "id": "Prompt-mySXT", - "inputTypes": ["Message", "Text"], + "id": "Prompt-TBdwC", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-Memory-lzTrH{œdataTypeœ:œMemoryœ,œidœ:œMemory-lzTrHœ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-mySXT{œfieldNameœ:œCHAT_HISTORYœ,œidœ:œPrompt-mySXTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Memory-80v92{œdataTypeœ:œMemoryœ,œidœ:œMemory-80v92œ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-TBdwC{œfieldNameœ:œCHAT_HISTORYœ,œidœ:œPrompt-TBdwCœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", "selected": false, - "source": "Memory-lzTrH", - "sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-lzTrHœ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-mySXT", - "targetHandle": "{œfieldNameœ: œCHAT_HISTORYœ, œidœ: œPrompt-mySXTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" - }, - { - "animated": false, - "className": "", - "data": { - "sourceHandle": { - "dataType": "ChatInput", - "id": "ChatInput-99hxg", - "name": "message", - "output_types": ["Message"] - }, - "targetHandle": { - "fieldName": "USER_INPUT", - "id": "Prompt-mySXT", - "inputTypes": ["Message", "Text"], - "type": "str" - } - }, - "id": "reactflow__edge-ChatInput-99hxg{œdataTypeœ:œChatInputœ,œidœ:œChatInput-99hxgœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-mySXT{œfieldNameœ:œUSER_INPUTœ,œidœ:œPrompt-mySXTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "selected": false, - "source": "ChatInput-99hxg", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-99hxgœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-mySXT", - "targetHandle": "{œfieldNameœ: œUSER_INPUTœ, œidœ: œPrompt-mySXTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "source": "Memory-80v92", + "sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-80v92œ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-TBdwC", + "targetHandle": "{œfieldNameœ: œCHAT_HISTORYœ, œidœ: œPrompt-TBdwCœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -55,23 +36,28 @@ "data": { "sourceHandle": { "dataType": "URL", - "id": "URL-ap9ge", + "id": "URL-VNBsH", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "EXAMPLE_COMPONENTS", - "id": "Prompt-mySXT", - "inputTypes": ["Message", "Text"], + "id": "Prompt-TBdwC", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-URL-ap9ge{œdataTypeœ:œURLœ,œidœ:œURL-ap9geœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-mySXT{œfieldNameœ:œEXAMPLE_COMPONENTSœ,œidœ:œPrompt-mySXTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-URL-VNBsH{œdataTypeœ:œURLœ,œidœ:œURL-VNBsHœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-TBdwC{œfieldNameœ:œEXAMPLE_COMPONENTSœ,œidœ:œPrompt-TBdwCœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", "selected": false, - "source": "URL-ap9ge", - "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-ap9geœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-mySXT", - "targetHandle": "{œfieldNameœ: œEXAMPLE_COMPONENTSœ, œidœ: œPrompt-mySXTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "source": "URL-VNBsH", + "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-VNBsHœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-TBdwC", + "targetHandle": "{œfieldNameœ: œEXAMPLE_COMPONENTSœ, œidœ: œPrompt-TBdwCœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -79,23 +65,28 @@ "data": { "sourceHandle": { "dataType": "URL", - "id": "URL-1bNqm", + "id": "URL-7lpSF", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "BASE_COMPONENT_CODE", - "id": "Prompt-mySXT", - "inputTypes": ["Message", "Text"], + "id": "Prompt-TBdwC", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-URL-1bNqm{œdataTypeœ:œURLœ,œidœ:œURL-1bNqmœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-mySXT{œfieldNameœ:œBASE_COMPONENT_CODEœ,œidœ:œPrompt-mySXTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-URL-7lpSF{œdataTypeœ:œURLœ,œidœ:œURL-7lpSFœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-TBdwC{œfieldNameœ:œBASE_COMPONENT_CODEœ,œidœ:œPrompt-TBdwCœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", "selected": false, - "source": "URL-1bNqm", - "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-1bNqmœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-mySXT", - "targetHandle": "{œfieldNameœ: œBASE_COMPONENT_CODEœ, œidœ: œPrompt-mySXTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "source": "URL-7lpSF", + "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-7lpSFœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-TBdwC", + "targetHandle": "{œfieldNameœ: œBASE_COMPONENT_CODEœ, œidœ: œPrompt-TBdwCœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -103,23 +94,28 @@ "data": { "sourceHandle": { "dataType": "URL", - "id": "URL-CIWR9", + "id": "URL-V6Rnb", "name": "text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "CUSTOM_COMPONENT_CODE", - "id": "Prompt-mySXT", - "inputTypes": ["Message", "Text"], + "id": "Prompt-TBdwC", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-URL-CIWR9{œdataTypeœ:œURLœ,œidœ:œURL-CIWR9œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-mySXT{œfieldNameœ:œCUSTOM_COMPONENT_CODEœ,œidœ:œPrompt-mySXTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-URL-V6Rnb{œdataTypeœ:œURLœ,œidœ:œURL-V6Rnbœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-TBdwC{œfieldNameœ:œCUSTOM_COMPONENT_CODEœ,œidœ:œPrompt-TBdwCœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", "selected": false, - "source": "URL-CIWR9", - "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-CIWR9œ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-mySXT", - "targetHandle": "{œfieldNameœ: œCUSTOM_COMPONENT_CODEœ, œidœ: œPrompt-mySXTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "source": "URL-V6Rnb", + "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-V6Rnbœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-TBdwC", + "targetHandle": "{œfieldNameœ: œCUSTOM_COMPONENT_CODEœ, œidœ: œPrompt-TBdwCœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -127,23 +123,27 @@ "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-mySXT", + "id": "Prompt-TBdwC", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "AnthropicModel-rdklm", - "inputTypes": ["Message"], + "id": "AnthropicModel-Ud197", + "inputTypes": [ + "Message" + ], "type": "str" } }, - "id": "reactflow__edge-Prompt-mySXT{œdataTypeœ:œPromptœ,œidœ:œPrompt-mySXTœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-rdklm{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-rdklmœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Prompt-TBdwC{œdataTypeœ:œPromptœ,œidœ:œPrompt-TBdwCœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-Ud197{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-Ud197œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Prompt-mySXT", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-mySXTœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "AnthropicModel-rdklm", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-rdklmœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Prompt-TBdwC", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-TBdwCœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "AnthropicModel-Ud197", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-Ud197œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -151,36 +151,1568 @@ "data": { "sourceHandle": { "dataType": "AnthropicModel", - "id": "AnthropicModel-rdklm", + "id": "AnthropicModel-Ud197", "name": "text_output", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-SPhqv", - "inputTypes": ["Data", "DataFrame", "Message"], + "id": "ChatOutput-lY6GY", + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], + "type": "other" + } + }, + "id": "reactflow__edge-AnthropicModel-Ud197{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-Ud197œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-lY6GY{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-lY6GYœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œotherœ}", + "selected": false, + "source": "AnthropicModel-Ud197", + "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-Ud197œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-lY6GY", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-lY6GYœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œotherœ}" + }, + { + "data": { + "sourceHandle": { + "dataType": "ChatInput", + "id": "ChatInput-1jL7Z", + "name": "message", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "USER_INPUT", + "id": "Prompt-TBdwC", + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, - "id": "reactflow__edge-AnthropicModel-rdklm{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-rdklmœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-SPhqv{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-SPhqvœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", - "selected": false, - "source": "AnthropicModel-rdklm", - "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-rdklmœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-SPhqv", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-SPhqvœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" + "id": "xy-edge__ChatInput-1jL7Z{œdataTypeœ:œChatInputœ,œidœ:œChatInput-1jL7Zœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-TBdwC{œfieldNameœ:œUSER_INPUTœ,œidœ:œPrompt-TBdwCœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "ChatInput-1jL7Z", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-1jL7Zœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-TBdwC", + "targetHandle": "{œfieldNameœ: œUSER_INPUTœ, œidœ: œPrompt-TBdwCœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" } ], "nodes": [ { "data": { - "description": "Get chat inputs from the Playground.", - "display_name": "Chat Input", - "id": "ChatInput-99hxg", + "description": "Retrieves stored chat messages from Langflow tables or an external memory.", + "display_name": "Chat Memory", + "id": "Memory-80v92", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, + "description": "Retrieves stored chat messages from Langflow tables or an external memory.", + "display_name": "Chat Memory", + "documentation": "", + "edited": false, + "field_order": [ + "memory", + "sender", + "sender_name", + "n_messages", + "session_id", + "order", + "template" + ], + "frozen": false, + "icon": "message-square-more", + "legacy": false, + "lf_version": "1.0.19.post2", + "metadata": {}, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Data", + "method": "retrieve_messages", + "name": "messages", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "retrieve_messages_as_text", + "name": "messages_text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "DataFrame", + "method": "as_dataframe", + "name": "dataframe", + "selected": "DataFrame", + "tool_mode": true, + "types": [ + "DataFrame" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.inputs import HandleInput\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import aget_messages\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER\n\n\nclass MemoryComponent(Component):\n display_name = \"Message History\"\n description = \"Retrieves stored chat messages from Langflow tables or an external memory.\"\n icon = \"message-square-more\"\n name = \"Memory\"\n\n inputs = [\n HandleInput(\n name=\"memory\",\n display_name=\"External Memory\",\n input_types=[\"Memory\"],\n info=\"Retrieve messages from an external memory. If empty, it will use the Langflow tables.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, \"Machine and User\"],\n value=\"Machine and User\",\n info=\"Filter by sender type.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Filter by sender name.\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Messages\",\n value=100,\n info=\"Number of messages to retrieve.\",\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 DropdownInput(\n name=\"order\",\n display_name=\"Order\",\n options=[\"Ascending\", \"Descending\"],\n value=\"Ascending\",\n info=\"Order of the messages.\",\n advanced=True,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {sender} or any other key in the message data.\",\n value=\"{sender_name}: {text}\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"messages\", method=\"retrieve_messages\"),\n Output(display_name=\"Message\", name=\"messages_text\", method=\"retrieve_messages_as_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def retrieve_messages(self) -> Data:\n sender = self.sender\n sender_name = self.sender_name\n session_id = self.session_id\n n_messages = self.n_messages\n order = \"DESC\" if self.order == \"Descending\" else \"ASC\"\n\n if sender == \"Machine and User\":\n sender = None\n\n if self.memory and not hasattr(self.memory, \"aget_messages\"):\n memory_name = type(self.memory).__name__\n err_msg = f\"External Memory object ({memory_name}) must have 'aget_messages' method.\"\n raise AttributeError(err_msg)\n\n if self.memory:\n # override session_id\n self.memory.session_id = session_id\n\n stored = await self.memory.aget_messages()\n # langchain memories are supposed to return messages in ascending order\n if order == \"DESC\":\n stored = stored[::-1]\n if n_messages:\n stored = stored[:n_messages]\n stored = [Message.from_lc_message(m) for m in stored]\n if sender:\n expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER\n stored = [m for m in stored if m.type == expected_type]\n else:\n stored = await aget_messages(\n sender=sender,\n sender_name=sender_name,\n session_id=session_id,\n limit=n_messages,\n order=order,\n )\n self.status = stored\n return stored\n\n async def retrieve_messages_as_text(self) -> Message:\n stored_text = data_to_text(self.template, await self.retrieve_messages())\n self.status = stored_text\n return Message(text=stored_text)\n\n async def as_dataframe(self) -> DataFrame:\n \"\"\"Convert the retrieved messages into a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the message data.\n \"\"\"\n messages = await self.retrieve_messages()\n return DataFrame(messages)\n" + }, + "memory": { + "_input_type": "HandleInput", + "advanced": false, + "display_name": "External Memory", + "dynamic": false, + "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", + "input_types": [ + "Memory" + ], + "list": false, + "name": "memory", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "n_messages": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Number of Messages", + "dynamic": false, + "info": "Number of messages to retrieve.", + "list": false, + "name": "n_messages", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "int", + "value": 100 + }, + "order": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "display_name": "Order", + "dynamic": false, + "info": "Order of the messages.", + "name": "order", + "options": [ + "Ascending", + "Descending" + ], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Ascending" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "display_name": "Sender Type", + "dynamic": false, + "info": "Filter by sender type.", + "name": "sender", + "options": [ + "Machine", + "User", + "Machine and User" + ], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Machine and User" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Filter by sender name.", + "input_types": [ + "Message" + ], + "list": false, + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Session ID", + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "input_types": [ + "Message" + ], + "list": false, + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "template": { + "_input_type": "MultilineInput", + "advanced": true, + "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" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{sender_name}: {text}" + } + }, + "tool_mode": false + }, + "type": "Memory" + }, + "dragging": false, + "height": 262, + "id": "Memory-80v92", + "measured": { + "height": 262, + "width": 360 + }, + "position": { + "x": 1832.6206210738642, + "y": 1150.8832220209044 + }, + "positionAbsolute": { + "x": 1830.6888981898887, + "y": 946.1205963195098 + }, + "selected": false, + "type": "genericNode", + "width": 320 + }, + { + "data": { + "description": "Create a prompt template with dynamic variables.", + "display_name": "Prompt", + "id": "Prompt-TBdwC", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": { + "template": [ + "BASE_COMPONENT_CODE", + "CUSTOM_COMPONENT_CODE", + "EXAMPLE_COMPONENTS", + "CHAT_HISTORY", + "USER_INPUT" + ] + }, + "description": "Create a prompt template with dynamic variables.", + "display_name": "Prompt", + "documentation": "", + "edited": false, + "field_order": [ + "template" + ], + "frozen": false, + "icon": "prompts", + "legacy": false, + "lf_version": "1.0.19.post2", + "metadata": {}, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Prompt Message", + "method": "build_prompt", + "name": "prompt", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "BASE_COMPONENT_CODE": { + "advanced": false, + "display_name": "BASE_COMPONENT_CODE", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message", + "Text" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "BASE_COMPONENT_CODE", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "CHAT_HISTORY": { + "advanced": false, + "display_name": "CHAT_HISTORY", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message", + "Text" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "CHAT_HISTORY", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "CUSTOM_COMPONENT_CODE": { + "advanced": false, + "display_name": "CUSTOM_COMPONENT_CODE", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message", + "Text" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "CUSTOM_COMPONENT_CODE", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "EXAMPLE_COMPONENTS": { + "advanced": false, + "display_name": "EXAMPLE_COMPONENTS", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message", + "Text" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "EXAMPLE_COMPONENTS", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "USER_INPUT": { + "advanced": false, + "display_name": "USER_INPUT", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message", + "Text" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "USER_INPUT", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n" + }, + "template": { + "_input_type": "PromptInput", + "advanced": false, + "display_name": "Template", + "dynamic": false, + "info": "", + "list": false, + "load_from_db": false, + "name": "template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "type": "prompt", + "value": "\nYou are an AI assistant specialized in creating Langflow components based on user requirements. Your task is to generate the code for a custom Langflow component according to the user's specifications.\n\nFirst, review the following code snippets for reference:\n\n\n{BASE_COMPONENT_CODE}\n\n\n\n{CUSTOM_COMPONENT_CODE}\n\n\n\n{EXAMPLE_COMPONENTS}\n\n\nNow, follow these steps to create a custom Langflow component:\n\n1. Analyze the user's input to determine the requirements for the component.\n2. Use an section to plan out the component structure and features based on the user's requirements.\n3. Generate the code for the custom component, using the provided code snippets as reference and inspiration.\n4. Provide a brief explanation of the component's functionality and how to use it.\n\nHere's the chat history and user input:\n\n\n{CHAT_HISTORY}\n\n\n\n{USER_INPUT}\n\n\nBased on the user's input, create a custom Langflow component that meets their requirements. Your response should include:\n\n1. \n Use this section to analyze the user's requirements and plan the component structure.\n\n\n2. \n Generate the complete code for the custom Langflow component here.\n\n\n3. \n Provide a brief explanation of the component's functionality and how to use it.\n\n\nRemember to:\n- Use the provided code snippets as a reference, but create a unique component tailored to the user's needs.\n- Include all necessary imports and class definitions.\n- Implement the required inputs, outputs, and any additional features specified by the user.\n- Use clear and descriptive variable names and comments to enhance code readability.\n- Ensure that the component follows Langflow best practices and conventions.\n\nIf the user's input is unclear or lacks specific details, make reasonable assumptions based on the context and explain these assumptions in your response.\n\n" + }, + "tool_placeholder": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Tool Placeholder", + "dynamic": false, + "info": "A placeholder input for tool mode.", + "input_types": [ + "Message" + ], + "list": false, + "load_from_db": false, + "name": "tool_placeholder", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "type": "Prompt" + }, + "dragging": false, + "height": 685, + "id": "Prompt-TBdwC", + "measured": { + "height": 685, + "width": 360 + }, + "position": { + "x": 2214.0288118788944, + "y": 514.3016755222201 + }, + "positionAbsolute": { + "x": 2219.5265974825707, + "y": 521.6320563271215 + }, + "selected": false, + "type": "genericNode", + "width": 320 + }, + { + "data": { + "id": "note-sVpiV", + "node": { + "description": "# Fetch Components code \n\nUsing the URL component we are extracting from Github, the code from a few classes to provide as example to the LLM. \n\nThis ensures we are always up to date with recent information from the codebase.", + "display_name": "", + "documentation": "", + "template": {} + }, + "type": "note" + }, + "dragging": false, + "height": 324, + "id": "note-sVpiV", + "measured": { + "height": 324, + "width": 328 + }, + "position": { + "x": 1428.2696830085167, + "y": -263.5483680075372 + }, + "positionAbsolute": { + "x": 1430.2014058924922, + "y": -19.30392196909918 + }, + "selected": false, + "type": "noteNode", + "width": 324 + }, + { + "data": { + "id": "note-56K9d", + "node": { + "description": "# 🛠️ Custom Component Generator 🚀\n\nHi! I'm here to help you create custom components for Langflow. Think of me as your technical partner who can help turn your ideas into working components! \n\n## 🎯 How to Work With Me\n\n### 1. 💭 Tell Me What You Want to Build\nSimply describe what you want your component to do in plain English. For example:\n- \"I need a component that sends Slack messages\"\n- \"I want to create a tool that can process CSV files\"\n- \"I need something that can translate text\"\n\n### 2. 📚 Share Any Relevant Information\nIf you're working with a specific:\n- 🔑 API or service (just share the documentation link or main endpoints)\n- 📄 File format\n- 🔄 Data structure\n- 🔧 Existing component you want to modify\n\n### 3. 🎨 Let Me Help Design It\nI'll help by:\n- 📊 Breaking down complex requirements into manageable pieces\n- 💡 Suggesting the best way to structure inputs and outputs\n- ⚙️ Creating the component code\n- 📝 Explaining how to use it\n\n### 4. 🔄 Iterative Refinement\nWe can then:\n- ✅ Test and refine the component\n- ⭐ Add features\n- 🔧 Modify behavior\n- 🛡️ Improve error handling\n- 📖 Add documentation\n\n## 🚀 What I Can Help With\n\nI can help create components that:\n- 📊 Process different file types (CSV, JSON, Excel, etc.)\n- 🔌 Integrate with external APIs\n- 🔄 Transform data\n- 🔀 Route messages\n- 🌐 Handle web requests\n- 🎯 Parse structured data\n- ✨ And much more!\n\n## 💡 Tips for Best Results\n\n1. **Be Specific** 🎯: The more details you provide about what you want to accomplish, the better I can help.\n\n2. **Share Examples** 📋: If you have example data or specific use cases, share them.\n\n3. **Ask Questions** ❓: Don't hesitate to ask for clarification or modifications.\n\nJust start by telling me what kind of component you'd like to create, and I'll guide you through the process! \n\nReady to build something awesome? 🚀 Let's get started!", + "display_name": "", + "documentation": "", + "template": {} + }, + "type": "note" + }, + "dragging": false, + "height": 573, + "id": "note-56K9d", + "measured": { + "height": 573, + "width": 623 + }, + "position": { + "x": 1002.7334076860311, + "y": 538.0401552689275 + }, + "positionAbsolute": { + "x": 807.6293964045135, + "y": 605.6504562080672 + }, + "resizing": false, + "selected": false, + "style": { + "height": 573, + "width": 324 + }, + "type": "noteNode", + "width": 619 + }, + { + "data": { + "id": "URL-7lpSF", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Fetch content from one or more URLs.", + "display_name": "URL", + "documentation": "", + "edited": false, + "field_order": [ + "urls", + "format" + ], + "frozen": false, + "icon": "layout-template", + "legacy": false, + "lf_version": "1.0.19.post2", + "metadata": {}, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Data", + "method": "fetch_content", + "name": "data", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Text", + "method": "fetch_content_text", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "DataFrame", + "method": "as_dataframe", + "name": "dataframe", + "selected": "DataFrame", + "tool_mode": true, + "types": [ + "DataFrame" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "clean_extra_whitespace": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Clean Extra Whitespace", + "dynamic": false, + "info": "Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.", + "list": false, + "list_add_label": "Add More", + "name": "clean_extra_whitespace", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import asyncio\nimport json\nimport re\n\nimport aiohttp\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = (\n \"Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, \"\n \"or JSON, with options for cleaning and separating multiple outputs.\"\n )\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=(\n \"Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML \"\n \"content, or 'JSON' to extract JSON from the HTML.\"\n ),\n options=[\"Text\", \"Raw HTML\", \"JSON\"],\n value=\"Text\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"separator\",\n display_name=\"Separator\",\n value=\"\\n\\n\",\n show=True,\n info=(\n \"Specify the separator to use between multiple outputs. Default for Text is '\\\\n\\\\n'. \"\n \"Default for Raw HTML is '\\\\n\\\\n'.\"\n ),\n ),\n BoolInput(\n name=\"clean_extra_whitespace\",\n display_name=\"Clean Extra Whitespace\",\n value=True,\n show=True,\n info=\"Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def validate_json_content(self, url: str) -> bool:\n \"\"\"Validates if the URL content is actually JSON.\"\"\"\n try:\n async with aiohttp.ClientSession() as session, session.get(url) as response:\n http_ok = 200\n if response.status != http_ok:\n return False\n\n content = await response.text()\n try:\n json.loads(content)\n except json.JSONDecodeError:\n return False\n else:\n return True\n except (aiohttp.ClientError, asyncio.TimeoutError):\n # Log specific error for debugging if needed\n return False\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update fields based on selected format.\"\"\"\n if field_name == \"format\":\n is_text_mode = field_value == \"Text\"\n is_json_mode = field_value == \"JSON\"\n build_config[\"separator\"][\"value\"] = \"\\n\\n\" if is_text_mode else \"\\n\\n\"\n build_config[\"clean_extra_whitespace\"][\"show\"] = is_text_mode\n build_config[\"separator\"][\"show\"] = not is_json_mode\n return build_config\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\"\n r\"(www\\.)?\"\n r\"([a-zA-Z0-9.-]+)\"\n r\"(\\.[a-zA-Z]{2,})?\"\n r\"(:\\d+)?\"\n r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n\n error_msg = \"Invalid URL - \" + string\n if not url_regex.match(string):\n raise ValueError(error_msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Fetch content based on selected format.\"\"\"\n urls = list({self.ensure_url(url.strip()) for url in self.urls if url.strip()})\n\n no_urls_msg = \"No valid URLs provided.\"\n if not urls:\n raise ValueError(no_urls_msg)\n\n # If JSON format is selected, validate JSON content first\n if self.format == \"JSON\":\n for url in urls:\n is_json = asyncio.run(self.validate_json_content(url))\n if not is_json:\n error_msg = \"Invalid JSON content from URL - \" + url\n raise ValueError(error_msg)\n\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n\n docs = loader.load()\n\n if self.format == \"JSON\":\n data = []\n for doc in docs:\n try:\n json_content = json.loads(doc.page_content)\n data_dict = {\"text\": json.dumps(json_content, indent=2), **json_content, **doc.metadata}\n data.append(Data(**data_dict))\n except json.JSONDecodeError as err:\n source = doc.metadata.get(\"source\", \"unknown URL\")\n error_msg = \"Invalid JSON content from \" + source\n raise ValueError(error_msg) from err\n return data\n\n return [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n\n def fetch_content_text(self) -> Message:\n \"\"\"Fetch content and return as formatted text.\"\"\"\n data = self.fetch_content()\n\n if self.format == \"JSON\":\n text_list = [item.text for item in data]\n result = \"\\n\".join(text_list)\n else:\n text_list = [item.text for item in data]\n if self.format == \"Text\" and self.clean_extra_whitespace:\n text_list = [re.sub(r\"\\n{3,}\", \"\\n\\n\", text) for text in text_list]\n result = self.separator.join(text_list)\n\n self.status = result\n return Message(text=result)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Return fetched content as a DataFrame.\"\"\"\n return DataFrame(self.fetch_content())\n" + }, + "format": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "display_name": "Output Format", + "dynamic": false, + "info": "Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML content, or 'JSON' to extract JSON from the HTML.", + "name": "format", + "options": [ + "Text", + "Raw HTML", + "JSON" + ], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Text" + }, + "separator": { + "_input_type": "StrInput", + "advanced": false, + "display_name": "Separator", + "dynamic": false, + "info": "Specify the separator to use between multiple outputs. Default for Text is '\\n\\n'. Default for Raw HTML is '\\n\\n'.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "separator", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n\n" + }, + "urls": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "URLs", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": true, + "load_from_db": false, + "name": "urls", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": [ + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/custom/custom_component/component.py" + ] + } + }, + "tool_mode": false + }, + "type": "URL" + }, + "dragging": false, + "height": 365, + "id": "URL-7lpSF", + "measured": { + "height": 365, + "width": 360 + }, + "position": { + "x": 1428.8876734924713, + "y": 32.523680276074714 + }, + "positionAbsolute": { + "x": 1436.3617127766433, + "y": 264.218898085405 + }, + "selected": false, + "type": "genericNode", + "width": 320 + }, + { + "data": { + "id": "URL-VNBsH", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Fetch content from one or more URLs.", + "display_name": "URL", + "documentation": "", + "edited": false, + "field_order": [ + "urls", + "format" + ], + "frozen": false, + "icon": "layout-template", + "legacy": false, + "lf_version": "1.0.19.post2", + "metadata": {}, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Data", + "method": "fetch_content", + "name": "data", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Text", + "method": "fetch_content_text", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "DataFrame", + "method": "as_dataframe", + "name": "dataframe", + "selected": "DataFrame", + "tool_mode": true, + "types": [ + "DataFrame" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "clean_extra_whitespace": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Clean Extra Whitespace", + "dynamic": false, + "info": "Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.", + "list": false, + "list_add_label": "Add More", + "name": "clean_extra_whitespace", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import asyncio\nimport json\nimport re\n\nimport aiohttp\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = (\n \"Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, \"\n \"or JSON, with options for cleaning and separating multiple outputs.\"\n )\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=(\n \"Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML \"\n \"content, or 'JSON' to extract JSON from the HTML.\"\n ),\n options=[\"Text\", \"Raw HTML\", \"JSON\"],\n value=\"Text\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"separator\",\n display_name=\"Separator\",\n value=\"\\n\\n\",\n show=True,\n info=(\n \"Specify the separator to use between multiple outputs. Default for Text is '\\\\n\\\\n'. \"\n \"Default for Raw HTML is '\\\\n\\\\n'.\"\n ),\n ),\n BoolInput(\n name=\"clean_extra_whitespace\",\n display_name=\"Clean Extra Whitespace\",\n value=True,\n show=True,\n info=\"Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def validate_json_content(self, url: str) -> bool:\n \"\"\"Validates if the URL content is actually JSON.\"\"\"\n try:\n async with aiohttp.ClientSession() as session, session.get(url) as response:\n http_ok = 200\n if response.status != http_ok:\n return False\n\n content = await response.text()\n try:\n json.loads(content)\n except json.JSONDecodeError:\n return False\n else:\n return True\n except (aiohttp.ClientError, asyncio.TimeoutError):\n # Log specific error for debugging if needed\n return False\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update fields based on selected format.\"\"\"\n if field_name == \"format\":\n is_text_mode = field_value == \"Text\"\n is_json_mode = field_value == \"JSON\"\n build_config[\"separator\"][\"value\"] = \"\\n\\n\" if is_text_mode else \"\\n\\n\"\n build_config[\"clean_extra_whitespace\"][\"show\"] = is_text_mode\n build_config[\"separator\"][\"show\"] = not is_json_mode\n return build_config\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\"\n r\"(www\\.)?\"\n r\"([a-zA-Z0-9.-]+)\"\n r\"(\\.[a-zA-Z]{2,})?\"\n r\"(:\\d+)?\"\n r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n\n error_msg = \"Invalid URL - \" + string\n if not url_regex.match(string):\n raise ValueError(error_msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Fetch content based on selected format.\"\"\"\n urls = list({self.ensure_url(url.strip()) for url in self.urls if url.strip()})\n\n no_urls_msg = \"No valid URLs provided.\"\n if not urls:\n raise ValueError(no_urls_msg)\n\n # If JSON format is selected, validate JSON content first\n if self.format == \"JSON\":\n for url in urls:\n is_json = asyncio.run(self.validate_json_content(url))\n if not is_json:\n error_msg = \"Invalid JSON content from URL - \" + url\n raise ValueError(error_msg)\n\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n\n docs = loader.load()\n\n if self.format == \"JSON\":\n data = []\n for doc in docs:\n try:\n json_content = json.loads(doc.page_content)\n data_dict = {\"text\": json.dumps(json_content, indent=2), **json_content, **doc.metadata}\n data.append(Data(**data_dict))\n except json.JSONDecodeError as err:\n source = doc.metadata.get(\"source\", \"unknown URL\")\n error_msg = \"Invalid JSON content from \" + source\n raise ValueError(error_msg) from err\n return data\n\n return [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n\n def fetch_content_text(self) -> Message:\n \"\"\"Fetch content and return as formatted text.\"\"\"\n data = self.fetch_content()\n\n if self.format == \"JSON\":\n text_list = [item.text for item in data]\n result = \"\\n\".join(text_list)\n else:\n text_list = [item.text for item in data]\n if self.format == \"Text\" and self.clean_extra_whitespace:\n text_list = [re.sub(r\"\\n{3,}\", \"\\n\\n\", text) for text in text_list]\n result = self.separator.join(text_list)\n\n self.status = result\n return Message(text=result)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Return fetched content as a DataFrame.\"\"\"\n return DataFrame(self.fetch_content())\n" + }, + "format": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "display_name": "Output Format", + "dynamic": false, + "info": "Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML content, or 'JSON' to extract JSON from the HTML.", + "name": "format", + "options": [ + "Text", + "Raw HTML", + "JSON" + ], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Text" + }, + "separator": { + "_input_type": "StrInput", + "advanced": false, + "display_name": "Separator", + "dynamic": false, + "info": "Specify the separator to use between multiple outputs. Default for Text is '\\n\\n'. Default for Raw HTML is '\\n\\n'.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "separator", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n\n" + }, + "urls": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "URLs", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": true, + "load_from_db": false, + "name": "urls", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": [ + "https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/agents/agent.py", + "https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/helpers/structured_output.py", + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/tools/calculator.py", + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/tools/tavily_search.py", + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/models/ollama.py", + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/logic/conditional_router.py", + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/data/file.py" + ] + } + }, + "tool_mode": false + }, + "type": "URL" + }, + "dragging": false, + "height": 661, + "id": "URL-VNBsH", + "measured": { + "height": 661, + "width": 360 + }, + "position": { + "x": 1831.5895760156684, + "y": 245.62940316018893 + }, + "positionAbsolute": { + "x": 1831.5895760156684, + "y": 245.62940316018893 + }, + "selected": false, + "type": "genericNode", + "width": 320 + }, + { + "data": { + "id": "URL-V6Rnb", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Fetch content from one or more URLs.", + "display_name": "URL", + "documentation": "", + "edited": false, + "field_order": [ + "urls", + "format" + ], + "frozen": false, + "icon": "layout-template", + "legacy": false, + "lf_version": "1.0.19.post2", + "metadata": {}, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Data", + "method": "fetch_content", + "name": "data", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Text", + "method": "fetch_content_text", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "DataFrame", + "method": "as_dataframe", + "name": "dataframe", + "selected": "DataFrame", + "tool_mode": true, + "types": [ + "DataFrame" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "clean_extra_whitespace": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Clean Extra Whitespace", + "dynamic": false, + "info": "Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.", + "list": false, + "list_add_label": "Add More", + "name": "clean_extra_whitespace", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import asyncio\nimport json\nimport re\n\nimport aiohttp\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = (\n \"Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, \"\n \"or JSON, with options for cleaning and separating multiple outputs.\"\n )\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=(\n \"Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML \"\n \"content, or 'JSON' to extract JSON from the HTML.\"\n ),\n options=[\"Text\", \"Raw HTML\", \"JSON\"],\n value=\"Text\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"separator\",\n display_name=\"Separator\",\n value=\"\\n\\n\",\n show=True,\n info=(\n \"Specify the separator to use between multiple outputs. Default for Text is '\\\\n\\\\n'. \"\n \"Default for Raw HTML is '\\\\n\\\\n'.\"\n ),\n ),\n BoolInput(\n name=\"clean_extra_whitespace\",\n display_name=\"Clean Extra Whitespace\",\n value=True,\n show=True,\n info=\"Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def validate_json_content(self, url: str) -> bool:\n \"\"\"Validates if the URL content is actually JSON.\"\"\"\n try:\n async with aiohttp.ClientSession() as session, session.get(url) as response:\n http_ok = 200\n if response.status != http_ok:\n return False\n\n content = await response.text()\n try:\n json.loads(content)\n except json.JSONDecodeError:\n return False\n else:\n return True\n except (aiohttp.ClientError, asyncio.TimeoutError):\n # Log specific error for debugging if needed\n return False\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update fields based on selected format.\"\"\"\n if field_name == \"format\":\n is_text_mode = field_value == \"Text\"\n is_json_mode = field_value == \"JSON\"\n build_config[\"separator\"][\"value\"] = \"\\n\\n\" if is_text_mode else \"\\n\\n\"\n build_config[\"clean_extra_whitespace\"][\"show\"] = is_text_mode\n build_config[\"separator\"][\"show\"] = not is_json_mode\n return build_config\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\"\n r\"(www\\.)?\"\n r\"([a-zA-Z0-9.-]+)\"\n r\"(\\.[a-zA-Z]{2,})?\"\n r\"(:\\d+)?\"\n r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n\n error_msg = \"Invalid URL - \" + string\n if not url_regex.match(string):\n raise ValueError(error_msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Fetch content based on selected format.\"\"\"\n urls = list({self.ensure_url(url.strip()) for url in self.urls if url.strip()})\n\n no_urls_msg = \"No valid URLs provided.\"\n if not urls:\n raise ValueError(no_urls_msg)\n\n # If JSON format is selected, validate JSON content first\n if self.format == \"JSON\":\n for url in urls:\n is_json = asyncio.run(self.validate_json_content(url))\n if not is_json:\n error_msg = \"Invalid JSON content from URL - \" + url\n raise ValueError(error_msg)\n\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n\n docs = loader.load()\n\n if self.format == \"JSON\":\n data = []\n for doc in docs:\n try:\n json_content = json.loads(doc.page_content)\n data_dict = {\"text\": json.dumps(json_content, indent=2), **json_content, **doc.metadata}\n data.append(Data(**data_dict))\n except json.JSONDecodeError as err:\n source = doc.metadata.get(\"source\", \"unknown URL\")\n error_msg = \"Invalid JSON content from \" + source\n raise ValueError(error_msg) from err\n return data\n\n return [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n\n def fetch_content_text(self) -> Message:\n \"\"\"Fetch content and return as formatted text.\"\"\"\n data = self.fetch_content()\n\n if self.format == \"JSON\":\n text_list = [item.text for item in data]\n result = \"\\n\".join(text_list)\n else:\n text_list = [item.text for item in data]\n if self.format == \"Text\" and self.clean_extra_whitespace:\n text_list = [re.sub(r\"\\n{3,}\", \"\\n\\n\", text) for text in text_list]\n result = self.separator.join(text_list)\n\n self.status = result\n return Message(text=result)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Return fetched content as a DataFrame.\"\"\"\n return DataFrame(self.fetch_content())\n" + }, + "format": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "display_name": "Output Format", + "dynamic": false, + "info": "Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML content, or 'JSON' to extract JSON from the HTML.", + "name": "format", + "options": [ + "Text", + "Raw HTML", + "JSON" + ], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Text" + }, + "separator": { + "_input_type": "StrInput", + "advanced": false, + "display_name": "Separator", + "dynamic": false, + "info": "Specify the separator to use between multiple outputs. Default for Text is '\\n\\n'. Default for Raw HTML is '\\n\\n'.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "separator", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n\n" + }, + "urls": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "URLs", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": true, + "load_from_db": false, + "name": "urls", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": [ + "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/custom_component/custom_component.py" + ] + } + }, + "tool_mode": false + }, + "type": "URL" + }, + "dragging": false, + "height": 365, + "id": "URL-V6Rnb", + "measured": { + "height": 365, + "width": 360 + }, + "position": { + "x": 1429.5084407373513, + "y": 632.4558314720756 + }, + "positionAbsolute": { + "x": 1436.982480021523, + "y": 651.1409296825055 + }, + "selected": false, + "type": "genericNode", + "width": 320 + }, + { + "data": { + "id": "AnthropicModel-Ud197", + "node": { + "base_classes": [ + "LanguageModel", + "Message" + ], + "beta": false, + "category": "models", + "conditional_paths": [], + "custom_fields": {}, + "description": "Generate text using Anthropic Chat&Completion LLMs with prefill support.", + "display_name": "Anthropic", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "system_message", + "stream", + "max_tokens", + "model_name", + "api_key", + "temperature", + "base_url", + "tool_model_enabled", + "prefill" + ], + "frozen": false, + "icon": "Anthropic", + "key": "AnthropicModel", + "legacy": false, + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "text_response", + "name": "text_output", + "required_inputs": [], + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Language Model", + "method": "build_model", + "name": "model_output", + "required_inputs": [ + "api_key" + ], + "selected": "LanguageModel", + "tool_mode": true, + "types": [ + "LanguageModel" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.0005851173668140926, + "template": { + "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": false, + "display_name": "Anthropic API Key", + "dynamic": false, + "info": "Your Anthropic API key.", + "input_types": [ + "Message" + ], + "load_from_db": true, + "name": "api_key", + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": true, + "show": true, + "title_case": false, + "type": "str", + "value": "ANTHROPIC_API_KEY" + }, + "base_url": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Anthropic API URL", + "dynamic": false, + "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "base_url", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "https://api.anthropic.com" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from typing import Any\n\nimport requests\nfrom loguru import logger\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic Chat&Completion LLMs with prefill support.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=\"https://api.anthropic.com\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"prefill\", display_name=\"Prefill\", info=\"Prefill text to guide the model's response.\", advanced=True\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens_to_sample=self.max_tokens,\n temperature=self.temperature,\n anthropic_api_url=self.base_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n for model in model_ids:\n model_with_tool = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n anthropic_api_url=self.base_url,\n )\n if not self.supports_tool_calling(model_with_tool):\n model_ids.remove(model)\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\"} and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"][\"value\"] = ids[0]\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Input", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "max_tokens": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Tokens", + "dynamic": false, + "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", + "list": false, + "list_add_label": "Add More", + "name": "max_tokens", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 4096 + }, + "model_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": true, + "dialog_inputs": {}, + "display_name": "Model Name", + "dynamic": false, + "info": "", + "name": "model_name", + "options": [ + "claude-3-5-sonnet-latest", + "claude-3-5-haiku-latest", + "claude-3-opus-latest", + "claude-3-5-sonnet-20240620", + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307" + ], + "options_metadata": [], + "placeholder": "", + "refresh_button": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "claude-3-5-sonnet-20241022" + }, + "prefill": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Prefill", + "dynamic": false, + "info": "Prefill text to guide the model's response.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "prefill", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "stream": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Stream", + "dynamic": false, + "info": "Stream the response from the model. Streaming works only in Chat.", + "list": false, + "list_add_label": "Add More", + "name": "stream", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "system_message": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "System Message", + "dynamic": false, + "info": "System message to pass to the model.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "system_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "langflow" + }, + "temperature": { + "_input_type": "SliderInput", + "advanced": true, + "display_name": "Temperature", + "dynamic": false, + "info": "Run inference with this temperature. Must by in the closed interval [0.0, 1.0].", + "max_label": "", + "max_label_icon": "", + "min_label": "", + "min_label_icon": "", + "name": "temperature", + "placeholder": "", + "range_spec": { + "max": 1, + "min": 0, + "step": 0.01, + "step_type": "float" + }, + "required": false, + "show": true, + "slider_buttons": false, + "slider_buttons_options": [], + "slider_input": false, + "title_case": false, + "tool_mode": false, + "type": "slider", + "value": 0.1 + }, + "tool_model_enabled": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Enable Tool Models", + "dynamic": false, + "info": "Select if you want to use models that can work with tools. If yes, only those models will be shown.", + "list": false, + "list_add_label": "Add More", + "name": "tool_model_enabled", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "AnthropicModel" + }, + "dragging": false, + "id": "AnthropicModel-Ud197", + "measured": { + "height": 750, + "width": 360 + }, + "position": { + "x": 2585.5577736139826, + "y": 454.98013556663204 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatInput-1jL7Z", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "category": "inputs", + "conditional_paths": [], + "custom_fields": {}, "description": "Get chat inputs from the Playground.", "display_name": "Chat Input", "documentation": "", @@ -198,9 +1730,10 @@ ], "frozen": false, "icon": "MessagesSquare", + "key": "ChatInput", "legacy": false, - "lf_version": "1.0.19.post2", "metadata": {}, + "minimized": true, "output_types": [], "outputs": [ { @@ -211,11 +1744,14 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], "pinned": false, + "score": 0.0020353564437605998, "template": { "_type": "Component", "background_color": { @@ -224,8 +1760,11 @@ "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, "name": "background_color", "placeholder": "", @@ -244,8 +1783,11 @@ "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, "name": "chat_icon", "placeholder": "", @@ -309,11 +1851,11 @@ "file_path": "", "info": "Files to be sent with the message.", "list": true, + "list_add_label": "Add More", "name": "files", "placeholder": "", "required": false, "show": true, - "temp_file": true, "title_case": false, "trace_as_metadata": true, "type": "file", @@ -322,11 +1864,13 @@ "input_value": { "_input_type": "MultilineInput", "advanced": false, + "copy_field": false, "display_name": "Text", "dynamic": false, "info": "Message to be passed as input.", "input_types": [], "list": false, + "list_add_label": "Add More", "load_from_db": false, "multiline": true, "name": "input_value", @@ -338,17 +1882,22 @@ "trace_as_input": true, "trace_as_metadata": true, "type": "str", - "value": "Failed to get YouTube transcripts: 1 validation error for Data\ndata\n Input should be a valid dictionary [type=dict_type, input_value=Document(metadata={'sourc...adding the API key and\"), input_type=Document]\n For further information visit https://errors.pydantic.dev/2.9/v/dict_type\n\n\nAlso, adapt the \"text\" bc it returns this \"Document(metadata={'source': 'UkV79sJAvz8'}, page_content=\"assembly AI is one of the leaders in transcription services so you can convert speech into text and they have many different products available on their platform and we can use assembly within langlow and the way we can get started is to First make an account with assembly Ai and once you get started you will be provided with an API key this is something we're going to need in langlow now back in link flow there are a few different components available and in this example we are using the start transcript component within this component we can provide a file and what I did is I uploaded a 1-hour talk by Andre karpati and this is intro to the large language models and this is an MP3 file so after adding the API key and then the audio file we can select a model that we want to use for the transcription there are two different options available here best and Nano now after you select the model you can either have the language detection on or leave defaults so I left everything in default and then I started the task and once we run the flow we get a transcript ID and attaching this component with the assembly AI pole transcript component we can now get the results and if we were to look at the results available able from this component there are quite a lot of fields that we can see as a result of this component and some of the most important ones you can see is the text from the transcript as you can see it's quite a large file and all of that was converted from speech to text easily by assembly AI it just took a few seconds and then we can see Word level timestamps if needed as what was spoken at what time the starting and end time for that and also the confidence if there are multiple speakers then it also identifies the speakers for us and then we can also see the utterances at different times so there's also word there's a full text and there's some additional information available here now we can use this data for many different things one is we can parse the transcript so we can just look at the full transcript that was available from this video or in this case this MP3 file and then we can also run to get subtitles and this could be used for any Services where we want to add subtitles in different formats so there is the SRT and the VT format available and the way this looks so I ran it for SRT We have basically the time stamps as well as the sentences those were converted from those time stamps and we can see that it goes on for the full length of the audio file and then if needed we can also convert that to vtt last thing is that if you have credits available in your assembly AI account you can also perform a summary of the audio or you could perhaps do some additional task so for example in our case we could say that create a summary of the transcript we could also say that create a blog post from the transcript or perhaps an essay from the transcript so we can get creative with the available information since the transcript of the file is now available and we can utilize that text for many different purposes the flow and the components should be available in the store be sure to add your API key in all of these components wherever it says to add the API key if not it might throw some errors and there are also some additional components available you can check those out based on your use cases as well give it a try and let us know if you found it helpful\")\" \n\nwe only want the page_content " + "value": "" }, "sender": { "_input_type": "DropdownInput", "advanced": true, "combobox": false, + "dialog_inputs": {}, "display_name": "Sender Type", "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], + "options_metadata": [], "placeholder": "", "required": false, "show": true, @@ -364,8 +1913,11 @@ "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, "name": "sender_name", "placeholder": "", @@ -384,8 +1936,11 @@ "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, "name": "session_id", "placeholder": "", @@ -405,11 +1960,13 @@ "dynamic": false, "info": "Store the message in the history.", "list": false, + "list_add_label": "Add More", "name": "should_store_message", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "bool", "value": true @@ -420,8 +1977,11 @@ "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, "name": "text_color", "placeholder": "", @@ -437,499 +1997,31 @@ }, "tool_mode": false }, + "showNode": false, "type": "ChatInput" }, "dragging": false, - "height": 231, - "id": "ChatInput-99hxg", + "id": "ChatInput-1jL7Z", "measured": { - "height": 231, - "width": 320 + "height": 74, + "width": 216 }, "position": { - "x": 1425.511811793499, - "y": 1250.8109912742284 - }, - "positionAbsolute": { - "x": 1436.7228707197569, - "y": 1045.2749109595 + "x": 1444.5468159526824, + "y": 1383.8217675906462 }, "selected": false, - "type": "genericNode", - "width": 320 + "type": "genericNode" }, { "data": { - "description": "Retrieves stored chat messages from Langflow tables or an external memory.", - "display_name": "Chat Memory", - "id": "Memory-lzTrH", + "id": "ChatOutput-lY6GY", "node": { - "base_classes": ["Data", "Message"], - "beta": false, - "conditional_paths": [], - "custom_fields": {}, - "description": "Retrieves stored chat messages from Langflow tables or an external memory.", - "display_name": "Chat Memory", - "documentation": "", - "edited": false, - "field_order": [ - "memory", - "sender", - "sender_name", - "n_messages", - "session_id", - "order", - "template" + "base_classes": [ + "Message" ], - "frozen": false, - "icon": "message-square-more", - "legacy": false, - "lf_version": "1.0.19.post2", - "metadata": {}, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Data", - "method": "retrieve_messages", - "name": "messages", - "selected": "Data", - "tool_mode": true, - "types": ["Data"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "Message", - "method": "retrieve_messages_as_text", - "name": "messages_text", - "selected": "Message", - "tool_mode": true, - "types": ["Message"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "DataFrame", - "method": "as_dataframe", - "name": "dataframe", - "selected": "DataFrame", - "tool_mode": true, - "types": ["DataFrame"], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "template": { - "_type": "Component", - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.inputs import HandleInput\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import aget_messages\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER\n\n\nclass MemoryComponent(Component):\n display_name = \"Message History\"\n description = \"Retrieves stored chat messages from Langflow tables or an external memory.\"\n icon = \"message-square-more\"\n name = \"Memory\"\n\n inputs = [\n HandleInput(\n name=\"memory\",\n display_name=\"External Memory\",\n input_types=[\"Memory\"],\n info=\"Retrieve messages from an external memory. If empty, it will use the Langflow tables.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, \"Machine and User\"],\n value=\"Machine and User\",\n info=\"Filter by sender type.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Filter by sender name.\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Messages\",\n value=100,\n info=\"Number of messages to retrieve.\",\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 DropdownInput(\n name=\"order\",\n display_name=\"Order\",\n options=[\"Ascending\", \"Descending\"],\n value=\"Ascending\",\n info=\"Order of the messages.\",\n advanced=True,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {sender} or any other key in the message data.\",\n value=\"{sender_name}: {text}\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"messages\", method=\"retrieve_messages\"),\n Output(display_name=\"Message\", name=\"messages_text\", method=\"retrieve_messages_as_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def retrieve_messages(self) -> Data:\n sender = self.sender\n sender_name = self.sender_name\n session_id = self.session_id\n n_messages = self.n_messages\n order = \"DESC\" if self.order == \"Descending\" else \"ASC\"\n\n if sender == \"Machine and User\":\n sender = None\n\n if self.memory and not hasattr(self.memory, \"aget_messages\"):\n memory_name = type(self.memory).__name__\n err_msg = f\"External Memory object ({memory_name}) must have 'aget_messages' method.\"\n raise AttributeError(err_msg)\n\n if self.memory:\n # override session_id\n self.memory.session_id = session_id\n\n stored = await self.memory.aget_messages()\n # langchain memories are supposed to return messages in ascending order\n if order == \"DESC\":\n stored = stored[::-1]\n if n_messages:\n stored = stored[:n_messages]\n stored = [Message.from_lc_message(m) for m in stored]\n if sender:\n expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER\n stored = [m for m in stored if m.type == expected_type]\n else:\n stored = await aget_messages(\n sender=sender,\n sender_name=sender_name,\n session_id=session_id,\n limit=n_messages,\n order=order,\n )\n self.status = stored\n return stored\n\n async def retrieve_messages_as_text(self) -> Message:\n stored_text = data_to_text(self.template, await self.retrieve_messages())\n self.status = stored_text\n return Message(text=stored_text)\n\n async def as_dataframe(self) -> DataFrame:\n \"\"\"Convert the retrieved messages into a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the message data.\n \"\"\"\n messages = await self.retrieve_messages()\n return DataFrame(messages)\n" - }, - "memory": { - "_input_type": "HandleInput", - "advanced": false, - "display_name": "External Memory", - "dynamic": false, - "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", - "input_types": ["Memory"], - "list": false, - "name": "memory", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "trace_as_metadata": true, - "type": "other", - "value": "" - }, - "n_messages": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Number of Messages", - "dynamic": false, - "info": "Number of messages to retrieve.", - "list": false, - "name": "n_messages", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "trace_as_metadata": true, - "type": "int", - "value": 100 - }, - "order": { - "_input_type": "DropdownInput", - "advanced": true, - "combobox": false, - "display_name": "Order", - "dynamic": false, - "info": "Order of the messages.", - "name": "order", - "options": ["Ascending", "Descending"], - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Ascending" - }, - "sender": { - "_input_type": "DropdownInput", - "advanced": true, - "combobox": false, - "display_name": "Sender Type", - "dynamic": false, - "info": "Filter by sender type.", - "name": "sender", - "options": ["Machine", "User", "Machine and User"], - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Machine and User" - }, - "sender_name": { - "_input_type": "MessageTextInput", - "advanced": true, - "display_name": "Sender Name", - "dynamic": false, - "info": "Filter by sender name.", - "input_types": ["Message"], - "list": false, - "load_from_db": false, - "name": "sender_name", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "" - }, - "session_id": { - "_input_type": "MessageTextInput", - "advanced": true, - "display_name": "Session ID", - "dynamic": false, - "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", - "input_types": ["Message"], - "list": false, - "load_from_db": false, - "name": "session_id", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "" - }, - "template": { - "_input_type": "MultilineInput", - "advanced": true, - "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"], - "list": false, - "load_from_db": false, - "multiline": true, - "name": "template", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "{sender_name}: {text}" - } - }, - "tool_mode": false - }, - "type": "Memory" - }, - "dragging": false, - "height": 262, - "id": "Memory-lzTrH", - "measured": { - "height": 262, - "width": 320 - }, - "position": { - "x": 1832.6206210738642, - "y": 1150.8832220209044 - }, - "positionAbsolute": { - "x": 1830.6888981898887, - "y": 946.1205963195098 - }, - "selected": false, - "type": "genericNode", - "width": 320 - }, - { - "data": { - "description": "Create a prompt template with dynamic variables.", - "display_name": "Prompt", - "id": "Prompt-mySXT", - "node": { - "base_classes": ["Message"], - "beta": false, - "conditional_paths": [], - "custom_fields": { - "template": [ - "BASE_COMPONENT_CODE", - "CUSTOM_COMPONENT_CODE", - "EXAMPLE_COMPONENTS", - "CHAT_HISTORY", - "USER_INPUT" - ] - }, - "description": "Create a prompt template with dynamic variables.", - "display_name": "Prompt", - "documentation": "", - "edited": false, - "field_order": ["template"], - "frozen": false, - "icon": "prompts", - "legacy": false, - "lf_version": "1.0.19.post2", - "metadata": {}, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Prompt Message", - "method": "build_prompt", - "name": "prompt", - "selected": "Message", - "tool_mode": true, - "types": ["Message"], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "template": { - "BASE_COMPONENT_CODE": { - "advanced": false, - "display_name": "BASE_COMPONENT_CODE", - "dynamic": false, - "field_type": "str", - "fileTypes": [], - "file_path": "", - "info": "", - "input_types": ["Message", "Text"], - "list": false, - "load_from_db": false, - "multiline": true, - "name": "BASE_COMPONENT_CODE", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "type": "str", - "value": "" - }, - "CHAT_HISTORY": { - "advanced": false, - "display_name": "CHAT_HISTORY", - "dynamic": false, - "field_type": "str", - "fileTypes": [], - "file_path": "", - "info": "", - "input_types": ["Message", "Text"], - "list": false, - "load_from_db": false, - "multiline": true, - "name": "CHAT_HISTORY", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "type": "str", - "value": "" - }, - "CUSTOM_COMPONENT_CODE": { - "advanced": false, - "display_name": "CUSTOM_COMPONENT_CODE", - "dynamic": false, - "field_type": "str", - "fileTypes": [], - "file_path": "", - "info": "", - "input_types": ["Message", "Text"], - "list": false, - "load_from_db": false, - "multiline": true, - "name": "CUSTOM_COMPONENT_CODE", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "type": "str", - "value": "" - }, - "EXAMPLE_COMPONENTS": { - "advanced": false, - "display_name": "EXAMPLE_COMPONENTS", - "dynamic": false, - "field_type": "str", - "fileTypes": [], - "file_path": "", - "info": "", - "input_types": ["Message", "Text"], - "list": false, - "load_from_db": false, - "multiline": true, - "name": "EXAMPLE_COMPONENTS", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "type": "str", - "value": "" - }, - "USER_INPUT": { - "advanced": false, - "display_name": "USER_INPUT", - "dynamic": false, - "field_type": "str", - "fileTypes": [], - "file_path": "", - "info": "", - "input_types": ["Message", "Text"], - "list": false, - "load_from_db": false, - "multiline": true, - "name": "USER_INPUT", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "type": "str", - "value": "" - }, - "_type": "Component", - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n" - }, - "template": { - "_input_type": "PromptInput", - "advanced": false, - "display_name": "Template", - "dynamic": false, - "info": "", - "list": false, - "load_from_db": false, - "name": "template", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "type": "prompt", - "value": "\nYou are an AI assistant specialized in creating Langflow components based on user requirements. Your task is to generate the code for a custom Langflow component according to the user's specifications.\n\nFirst, review the following code snippets for reference:\n\n\n{BASE_COMPONENT_CODE}\n\n\n\n{CUSTOM_COMPONENT_CODE}\n\n\n\n{EXAMPLE_COMPONENTS}\n\n\nNow, follow these steps to create a custom Langflow component:\n\n1. Analyze the user's input to determine the requirements for the component.\n2. Use an section to plan out the component structure and features based on the user's requirements.\n3. Generate the code for the custom component, using the provided code snippets as reference and inspiration.\n4. Provide a brief explanation of the component's functionality and how to use it.\n\nHere's the chat history and user input:\n\n\n{CHAT_HISTORY}\n\n\n\n{USER_INPUT}\n\n\nBased on the user's input, create a custom Langflow component that meets their requirements. Your response should include:\n\n1. \n Use this section to analyze the user's requirements and plan the component structure.\n\n\n2. \n Generate the complete code for the custom Langflow component here.\n\n\n3. \n Provide a brief explanation of the component's functionality and how to use it.\n\n\nRemember to:\n- Use the provided code snippets as a reference, but create a unique component tailored to the user's needs.\n- Include all necessary imports and class definitions.\n- Implement the required inputs, outputs, and any additional features specified by the user.\n- Use clear and descriptive variable names and comments to enhance code readability.\n- Ensure that the component follows Langflow best practices and conventions.\n\nIf the user's input is unclear or lacks specific details, make reasonable assumptions based on the context and explain these assumptions in your response.\n\n" - }, - "tool_placeholder": { - "_input_type": "MessageTextInput", - "advanced": true, - "display_name": "Tool Placeholder", - "dynamic": false, - "info": "A placeholder input for tool mode.", - "input_types": ["Message"], - "list": false, - "load_from_db": false, - "name": "tool_placeholder", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": true, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "" - } - }, - "tool_mode": false - }, - "type": "Prompt" - }, - "dragging": false, - "height": 685, - "id": "Prompt-mySXT", - "measured": { - "height": 685, - "width": 320 - }, - "position": { - "x": 2214.0288118788944, - "y": 514.3016755222201 - }, - "positionAbsolute": { - "x": 2219.5265974825707, - "y": 521.6320563271215 - }, - "selected": true, - "type": "genericNode", - "width": 320 - }, - { - "data": { - "description": "Display a chat message in the Playground.", - "display_name": "Chat Output", - "id": "ChatOutput-SPhqv", - "node": { - "base_classes": ["Message"], "beta": false, + "category": "outputs", "conditional_paths": [], "custom_fields": {}, "description": "Display a chat message in the Playground.", @@ -945,12 +2037,15 @@ "data_template", "background_color", "chat_icon", - "text_color" + "text_color", + "clean_data" ], "frozen": false, "icon": "MessagesSquare", + "key": "ChatOutput", "legacy": false, "metadata": {}, + "minimized": true, "output_types": [], "outputs": [ { @@ -961,11 +2056,14 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], "pinned": false, + "score": 0.003169567463043492, "template": { "_type": "Component", "background_color": { @@ -974,8 +2072,11 @@ "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, "name": "background_color", "placeholder": "", @@ -994,8 +2095,11 @@ "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, "name": "chat_icon", "placeholder": "", @@ -1050,8 +2154,11 @@ "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, "name": "data_template", "placeholder": "", @@ -1065,33 +2172,41 @@ "value": "{text}" }, "input_value": { - "_input_type": "MessageInput", + "_input_type": "HandleInput", "advanced": false, "display_name": "Text", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Data", "DataFrame", "Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, - "load_from_db": false, + "list_add_label": "Add More", "name": "input_value", "placeholder": "", "required": true, "show": true, "title_case": false, - "trace_as_input": true, "trace_as_metadata": true, - "type": "str", + "type": "other", "value": "" }, "sender": { "_input_type": "DropdownInput", "advanced": true, "combobox": false, + "dialog_inputs": {}, "display_name": "Sender Type", "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], + "options_metadata": [], "placeholder": "", "required": false, "show": true, @@ -1107,8 +2222,11 @@ "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, "name": "sender_name", "placeholder": "", @@ -1127,8 +2245,11 @@ "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, "name": "session_id", "placeholder": "", @@ -1148,11 +2269,13 @@ "dynamic": false, "info": "Store the message in the history.", "list": false, + "list_add_label": "Add More", "name": "should_store_message", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "bool", "value": true @@ -1163,8 +2286,11 @@ "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, "name": "text_color", "placeholder": "", @@ -1180,947 +2306,27 @@ }, "tool_mode": false }, + "showNode": false, "type": "ChatOutput" }, "dragging": false, - "height": 232, - "id": "ChatOutput-SPhqv", + "id": "ChatOutput-lY6GY", "measured": { - "height": 232, - "width": 320 + "height": 74, + "width": 216 }, "position": { - "x": 2947.267779013826, - "y": 891.8123698756774 - }, - "positionAbsolute": { - "x": 2947.267779013826, - "y": 891.8123698756774 - }, - "selected": false, - "type": "genericNode", - "width": 320 - }, - { - "data": { - "id": "note-FI4hN", - "node": { - "description": "# Fetch Components code \n\nUsing the URL component we are extracting from Github, the code from a few classes to provide as example to the LLM. \n\nThis ensures we are always up to date with recent information from the codebase.", - "display_name": "", - "documentation": "", - "template": {} - }, - "type": "note" - }, - "dragging": false, - "height": 324, - "id": "note-FI4hN", - "measured": { - "height": 324, - "width": 325 - }, - "position": { - "x": 1428.2696830085167, - "y": -263.5483680075372 - }, - "positionAbsolute": { - "x": 1430.2014058924922, - "y": -19.30392196909918 - }, - "selected": false, - "type": "noteNode", - "width": 324 - }, - { - "data": { - "id": "note-rp8sO", - "node": { - "description": "# 🛠️ Custom Component Generator 🚀\n\nHi! I'm here to help you create custom components for Langflow. Think of me as your technical partner who can help turn your ideas into working components! \n\n## 🎯 How to Work With Me\n\n### 1. 💭 Tell Me What You Want to Build\nSimply describe what you want your component to do in plain English. For example:\n- \"I need a component that sends Slack messages\"\n- \"I want to create a tool that can process CSV files\"\n- \"I need something that can translate text\"\n\n### 2. 📚 Share Any Relevant Information\nIf you're working with a specific:\n- 🔑 API or service (just share the documentation link or main endpoints)\n- 📄 File format\n- 🔄 Data structure\n- 🔧 Existing component you want to modify\n\n### 3. 🎨 Let Me Help Design It\nI'll help by:\n- 📊 Breaking down complex requirements into manageable pieces\n- 💡 Suggesting the best way to structure inputs and outputs\n- ⚙️ Creating the component code\n- 📝 Explaining how to use it\n\n### 4. 🔄 Iterative Refinement\nWe can then:\n- ✅ Test and refine the component\n- ⭐ Add features\n- 🔧 Modify behavior\n- 🛡️ Improve error handling\n- 📖 Add documentation\n\n## 🚀 What I Can Help With\n\nI can help create components that:\n- 📊 Process different file types (CSV, JSON, Excel, etc.)\n- 🔌 Integrate with external APIs\n- 🔄 Transform data\n- 🔀 Route messages\n- 🌐 Handle web requests\n- 🎯 Parse structured data\n- ✨ And much more!\n\n## 💡 Tips for Best Results\n\n1. **Be Specific** 🎯: The more details you provide about what you want to accomplish, the better I can help.\n\n2. **Share Examples** 📋: If you have example data or specific use cases, share them.\n\n3. **Ask Questions** ❓: Don't hesitate to ask for clarification or modifications.\n\nJust start by telling me what kind of component you'd like to create, and I'll guide you through the process! \n\nReady to build something awesome? 🚀 Let's get started!", - "display_name": "", - "documentation": "", - "template": {} - }, - "type": "note" - }, - "dragging": false, - "height": 573, - "id": "note-rp8sO", - "measured": { - "height": 573, - "width": 325 - }, - "position": { - "x": 1002.7334076860311, - "y": 538.0401552689275 - }, - "positionAbsolute": { - "x": 807.6293964045135, - "y": 605.6504562080672 - }, - "resizing": false, - "selected": false, - "style": { - "height": 573, - "width": 324 - }, - "type": "noteNode", - "width": 324 - }, - { - "data": { - "id": "URL-1bNqm", - "node": { - "base_classes": ["Data", "Message"], - "beta": false, - "conditional_paths": [], - "custom_fields": {}, - "description": "Fetch content from one or more URLs.", - "display_name": "URL", - "documentation": "", - "edited": false, - "field_order": ["urls", "format"], - "frozen": false, - "icon": "layout-template", - "legacy": false, - "lf_version": "1.0.19.post2", - "metadata": {}, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Data", - "method": "fetch_content", - "name": "data", - "selected": "Data", - "tool_mode": true, - "types": ["Data"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "Text", - "method": "fetch_content_text", - "name": "text", - "selected": "Message", - "tool_mode": true, - "types": ["Message"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "DataFrame", - "method": "as_dataframe", - "name": "dataframe", - "selected": "DataFrame", - "tool_mode": true, - "types": ["DataFrame"], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "template": { - "_type": "Component", - "clean_extra_whitespace": { - "_input_type": "BoolInput", - "advanced": false, - "display_name": "Clean Extra Whitespace", - "dynamic": false, - "info": "Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.", - "list": false, - "list_add_label": "Add More", - "name": "clean_extra_whitespace", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "bool", - "value": true - }, - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "import asyncio\nimport json\nimport re\n\nimport aiohttp\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = (\n \"Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, \"\n \"or JSON, with options for cleaning and separating multiple outputs.\"\n )\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=(\n \"Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML \"\n \"content, or 'JSON' to extract JSON from the HTML.\"\n ),\n options=[\"Text\", \"Raw HTML\", \"JSON\"],\n value=\"Text\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"separator\",\n display_name=\"Separator\",\n value=\"\\n\\n\",\n show=True,\n info=(\n \"Specify the separator to use between multiple outputs. Default for Text is '\\\\n\\\\n'. \"\n \"Default for Raw HTML is '\\\\n\\\\n'.\"\n ),\n ),\n BoolInput(\n name=\"clean_extra_whitespace\",\n display_name=\"Clean Extra Whitespace\",\n value=True,\n show=True,\n info=\"Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def validate_json_content(self, url: str) -> bool:\n \"\"\"Validates if the URL content is actually JSON.\"\"\"\n try:\n async with aiohttp.ClientSession() as session, session.get(url) as response:\n http_ok = 200\n if response.status != http_ok:\n return False\n\n content = await response.text()\n try:\n json.loads(content)\n except json.JSONDecodeError:\n return False\n else:\n return True\n except (aiohttp.ClientError, asyncio.TimeoutError):\n # Log specific error for debugging if needed\n return False\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update fields based on selected format.\"\"\"\n if field_name == \"format\":\n is_text_mode = field_value == \"Text\"\n is_json_mode = field_value == \"JSON\"\n build_config[\"separator\"][\"value\"] = \"\\n\\n\" if is_text_mode else \"\\n\\n\"\n build_config[\"clean_extra_whitespace\"][\"show\"] = is_text_mode\n build_config[\"separator\"][\"show\"] = not is_json_mode\n return build_config\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\"\n r\"(www\\.)?\"\n r\"([a-zA-Z0-9.-]+)\"\n r\"(\\.[a-zA-Z]{2,})?\"\n r\"(:\\d+)?\"\n r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n\n error_msg = \"Invalid URL - \" + string\n if not url_regex.match(string):\n raise ValueError(error_msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Fetch content based on selected format.\"\"\"\n urls = list({self.ensure_url(url.strip()) for url in self.urls if url.strip()})\n\n no_urls_msg = \"No valid URLs provided.\"\n if not urls:\n raise ValueError(no_urls_msg)\n\n # If JSON format is selected, validate JSON content first\n if self.format == \"JSON\":\n for url in urls:\n is_json = asyncio.run(self.validate_json_content(url))\n if not is_json:\n error_msg = \"Invalid JSON content from URL - \" + url\n raise ValueError(error_msg)\n\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n\n docs = loader.load()\n\n if self.format == \"JSON\":\n data = []\n for doc in docs:\n try:\n json_content = json.loads(doc.page_content)\n data_dict = {\"text\": json.dumps(json_content, indent=2), **json_content, **doc.metadata}\n data.append(Data(**data_dict))\n except json.JSONDecodeError as err:\n source = doc.metadata.get(\"source\", \"unknown URL\")\n error_msg = \"Invalid JSON content from \" + source\n raise ValueError(error_msg) from err\n return data\n\n return [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n\n def fetch_content_text(self) -> Message:\n \"\"\"Fetch content and return as formatted text.\"\"\"\n data = self.fetch_content()\n\n if self.format == \"JSON\":\n text_list = [item.text for item in data]\n result = \"\\n\".join(text_list)\n else:\n text_list = [item.text for item in data]\n if self.format == \"Text\" and self.clean_extra_whitespace:\n text_list = [re.sub(r\"\\n{3,}\", \"\\n\\n\", text) for text in text_list]\n result = self.separator.join(text_list)\n\n self.status = result\n return Message(text=result)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Return fetched content as a DataFrame.\"\"\"\n return DataFrame(self.fetch_content())\n" - }, - "format": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Output Format", - "dynamic": false, - "info": "Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML content, or 'JSON' to extract JSON from the HTML.", - "name": "format", - "options": ["Text", "Raw HTML", "JSON"], - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Text" - }, - "separator": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Separator", - "dynamic": false, - "info": "Specify the separator to use between multiple outputs. Default for Text is '\\n\\n'. Default for Raw HTML is '\\n\\n'.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "separator", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "\n\n" - }, - "urls": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "URLs", - "dynamic": false, - "info": "", - "input_types": ["Message"], - "list": true, - "load_from_db": false, - "name": "urls", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": true, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": [ - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/custom/custom_component/component.py" - ] - } - }, - "tool_mode": false - }, - "type": "URL" - }, - "dragging": false, - "height": 365, - "id": "URL-1bNqm", - "measured": { - "height": 365, - "width": 320 - }, - "position": { - "x": 1428.8876734924713, - "y": 32.523680276074714 - }, - "positionAbsolute": { - "x": 1436.3617127766433, - "y": 264.218898085405 - }, - "selected": false, - "type": "genericNode", - "width": 320 - }, - { - "data": { - "id": "URL-ap9ge", - "node": { - "base_classes": ["Data", "Message"], - "beta": false, - "conditional_paths": [], - "custom_fields": {}, - "description": "Fetch content from one or more URLs.", - "display_name": "URL", - "documentation": "", - "edited": false, - "field_order": ["urls", "format"], - "frozen": false, - "icon": "layout-template", - "legacy": false, - "lf_version": "1.0.19.post2", - "metadata": {}, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Data", - "method": "fetch_content", - "name": "data", - "selected": "Data", - "tool_mode": true, - "types": ["Data"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "Text", - "method": "fetch_content_text", - "name": "text", - "selected": "Message", - "tool_mode": true, - "types": ["Message"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "DataFrame", - "method": "as_dataframe", - "name": "dataframe", - "selected": "DataFrame", - "tool_mode": true, - "types": ["DataFrame"], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "template": { - "_type": "Component", - "clean_extra_whitespace": { - "_input_type": "BoolInput", - "advanced": false, - "display_name": "Clean Extra Whitespace", - "dynamic": false, - "info": "Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.", - "list": false, - "list_add_label": "Add More", - "name": "clean_extra_whitespace", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "bool", - "value": true - }, - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "import asyncio\nimport json\nimport re\n\nimport aiohttp\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = (\n \"Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, \"\n \"or JSON, with options for cleaning and separating multiple outputs.\"\n )\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=(\n \"Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML \"\n \"content, or 'JSON' to extract JSON from the HTML.\"\n ),\n options=[\"Text\", \"Raw HTML\", \"JSON\"],\n value=\"Text\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"separator\",\n display_name=\"Separator\",\n value=\"\\n\\n\",\n show=True,\n info=(\n \"Specify the separator to use between multiple outputs. Default for Text is '\\\\n\\\\n'. \"\n \"Default for Raw HTML is '\\\\n\\\\n'.\"\n ),\n ),\n BoolInput(\n name=\"clean_extra_whitespace\",\n display_name=\"Clean Extra Whitespace\",\n value=True,\n show=True,\n info=\"Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def validate_json_content(self, url: str) -> bool:\n \"\"\"Validates if the URL content is actually JSON.\"\"\"\n try:\n async with aiohttp.ClientSession() as session, session.get(url) as response:\n http_ok = 200\n if response.status != http_ok:\n return False\n\n content = await response.text()\n try:\n json.loads(content)\n except json.JSONDecodeError:\n return False\n else:\n return True\n except (aiohttp.ClientError, asyncio.TimeoutError):\n # Log specific error for debugging if needed\n return False\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update fields based on selected format.\"\"\"\n if field_name == \"format\":\n is_text_mode = field_value == \"Text\"\n is_json_mode = field_value == \"JSON\"\n build_config[\"separator\"][\"value\"] = \"\\n\\n\" if is_text_mode else \"\\n\\n\"\n build_config[\"clean_extra_whitespace\"][\"show\"] = is_text_mode\n build_config[\"separator\"][\"show\"] = not is_json_mode\n return build_config\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\"\n r\"(www\\.)?\"\n r\"([a-zA-Z0-9.-]+)\"\n r\"(\\.[a-zA-Z]{2,})?\"\n r\"(:\\d+)?\"\n r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n\n error_msg = \"Invalid URL - \" + string\n if not url_regex.match(string):\n raise ValueError(error_msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Fetch content based on selected format.\"\"\"\n urls = list({self.ensure_url(url.strip()) for url in self.urls if url.strip()})\n\n no_urls_msg = \"No valid URLs provided.\"\n if not urls:\n raise ValueError(no_urls_msg)\n\n # If JSON format is selected, validate JSON content first\n if self.format == \"JSON\":\n for url in urls:\n is_json = asyncio.run(self.validate_json_content(url))\n if not is_json:\n error_msg = \"Invalid JSON content from URL - \" + url\n raise ValueError(error_msg)\n\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n\n docs = loader.load()\n\n if self.format == \"JSON\":\n data = []\n for doc in docs:\n try:\n json_content = json.loads(doc.page_content)\n data_dict = {\"text\": json.dumps(json_content, indent=2), **json_content, **doc.metadata}\n data.append(Data(**data_dict))\n except json.JSONDecodeError as err:\n source = doc.metadata.get(\"source\", \"unknown URL\")\n error_msg = \"Invalid JSON content from \" + source\n raise ValueError(error_msg) from err\n return data\n\n return [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n\n def fetch_content_text(self) -> Message:\n \"\"\"Fetch content and return as formatted text.\"\"\"\n data = self.fetch_content()\n\n if self.format == \"JSON\":\n text_list = [item.text for item in data]\n result = \"\\n\".join(text_list)\n else:\n text_list = [item.text for item in data]\n if self.format == \"Text\" and self.clean_extra_whitespace:\n text_list = [re.sub(r\"\\n{3,}\", \"\\n\\n\", text) for text in text_list]\n result = self.separator.join(text_list)\n\n self.status = result\n return Message(text=result)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Return fetched content as a DataFrame.\"\"\"\n return DataFrame(self.fetch_content())\n" - }, - "format": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Output Format", - "dynamic": false, - "info": "Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML content, or 'JSON' to extract JSON from the HTML.", - "name": "format", - "options": ["Text", "Raw HTML", "JSON"], - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Text" - }, - "separator": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Separator", - "dynamic": false, - "info": "Specify the separator to use between multiple outputs. Default for Text is '\\n\\n'. Default for Raw HTML is '\\n\\n'.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "separator", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "\n\n" - }, - "urls": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "URLs", - "dynamic": false, - "info": "", - "input_types": ["Message"], - "list": true, - "load_from_db": false, - "name": "urls", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": true, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": [ - "https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/agents/agent.py", - "https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/helpers/structured_output.py", - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/tools/calculator.py", - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/tools/tavily_search.py", - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/models/ollama.py", - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/logic/conditional_router.py", - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/data/file.py" - ] - } - }, - "tool_mode": false - }, - "type": "URL" - }, - "dragging": false, - "height": 661, - "id": "URL-ap9ge", - "measured": { - "height": 661, - "width": 320 - }, - "position": { - "x": 1831.5895760156684, - "y": 245.62940316018893 - }, - "positionAbsolute": { - "x": 1831.5895760156684, - "y": 245.62940316018893 - }, - "selected": false, - "type": "genericNode", - "width": 320 - }, - { - "data": { - "id": "URL-CIWR9", - "node": { - "base_classes": ["Data", "Message"], - "beta": false, - "conditional_paths": [], - "custom_fields": {}, - "description": "Fetch content from one or more URLs.", - "display_name": "URL", - "documentation": "", - "edited": false, - "field_order": ["urls", "format"], - "frozen": false, - "icon": "layout-template", - "legacy": false, - "lf_version": "1.0.19.post2", - "metadata": {}, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Data", - "method": "fetch_content", - "name": "data", - "selected": "Data", - "tool_mode": true, - "types": ["Data"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "Text", - "method": "fetch_content_text", - "name": "text", - "selected": "Message", - "tool_mode": true, - "types": ["Message"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "DataFrame", - "method": "as_dataframe", - "name": "dataframe", - "selected": "DataFrame", - "tool_mode": true, - "types": ["DataFrame"], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "template": { - "_type": "Component", - "clean_extra_whitespace": { - "_input_type": "BoolInput", - "advanced": false, - "display_name": "Clean Extra Whitespace", - "dynamic": false, - "info": "Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.", - "list": false, - "list_add_label": "Add More", - "name": "clean_extra_whitespace", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "bool", - "value": true - }, - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "import asyncio\nimport json\nimport re\n\nimport aiohttp\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = (\n \"Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, \"\n \"or JSON, with options for cleaning and separating multiple outputs.\"\n )\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=(\n \"Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML \"\n \"content, or 'JSON' to extract JSON from the HTML.\"\n ),\n options=[\"Text\", \"Raw HTML\", \"JSON\"],\n value=\"Text\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"separator\",\n display_name=\"Separator\",\n value=\"\\n\\n\",\n show=True,\n info=(\n \"Specify the separator to use between multiple outputs. Default for Text is '\\\\n\\\\n'. \"\n \"Default for Raw HTML is '\\\\n\\\\n'.\"\n ),\n ),\n BoolInput(\n name=\"clean_extra_whitespace\",\n display_name=\"Clean Extra Whitespace\",\n value=True,\n show=True,\n info=\"Whether to clean excessive blank lines in the text output. Only applies to 'Text' format.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n async def validate_json_content(self, url: str) -> bool:\n \"\"\"Validates if the URL content is actually JSON.\"\"\"\n try:\n async with aiohttp.ClientSession() as session, session.get(url) as response:\n http_ok = 200\n if response.status != http_ok:\n return False\n\n content = await response.text()\n try:\n json.loads(content)\n except json.JSONDecodeError:\n return False\n else:\n return True\n except (aiohttp.ClientError, asyncio.TimeoutError):\n # Log specific error for debugging if needed\n return False\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update fields based on selected format.\"\"\"\n if field_name == \"format\":\n is_text_mode = field_value == \"Text\"\n is_json_mode = field_value == \"JSON\"\n build_config[\"separator\"][\"value\"] = \"\\n\\n\" if is_text_mode else \"\\n\\n\"\n build_config[\"clean_extra_whitespace\"][\"show\"] = is_text_mode\n build_config[\"separator\"][\"show\"] = not is_json_mode\n return build_config\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\"\n r\"(www\\.)?\"\n r\"([a-zA-Z0-9.-]+)\"\n r\"(\\.[a-zA-Z]{2,})?\"\n r\"(:\\d+)?\"\n r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n\n error_msg = \"Invalid URL - \" + string\n if not url_regex.match(string):\n raise ValueError(error_msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Fetch content based on selected format.\"\"\"\n urls = list({self.ensure_url(url.strip()) for url in self.urls if url.strip()})\n\n no_urls_msg = \"No valid URLs provided.\"\n if not urls:\n raise ValueError(no_urls_msg)\n\n # If JSON format is selected, validate JSON content first\n if self.format == \"JSON\":\n for url in urls:\n is_json = asyncio.run(self.validate_json_content(url))\n if not is_json:\n error_msg = \"Invalid JSON content from URL - \" + url\n raise ValueError(error_msg)\n\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n\n docs = loader.load()\n\n if self.format == \"JSON\":\n data = []\n for doc in docs:\n try:\n json_content = json.loads(doc.page_content)\n data_dict = {\"text\": json.dumps(json_content, indent=2), **json_content, **doc.metadata}\n data.append(Data(**data_dict))\n except json.JSONDecodeError as err:\n source = doc.metadata.get(\"source\", \"unknown URL\")\n error_msg = \"Invalid JSON content from \" + source\n raise ValueError(error_msg) from err\n return data\n\n return [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n\n def fetch_content_text(self) -> Message:\n \"\"\"Fetch content and return as formatted text.\"\"\"\n data = self.fetch_content()\n\n if self.format == \"JSON\":\n text_list = [item.text for item in data]\n result = \"\\n\".join(text_list)\n else:\n text_list = [item.text for item in data]\n if self.format == \"Text\" and self.clean_extra_whitespace:\n text_list = [re.sub(r\"\\n{3,}\", \"\\n\\n\", text) for text in text_list]\n result = self.separator.join(text_list)\n\n self.status = result\n return Message(text=result)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Return fetched content as a DataFrame.\"\"\"\n return DataFrame(self.fetch_content())\n" - }, - "format": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Output Format", - "dynamic": false, - "info": "Output Format. Use 'Text' to extract text from the HTML, 'Raw HTML' for the raw HTML content, or 'JSON' to extract JSON from the HTML.", - "name": "format", - "options": ["Text", "Raw HTML", "JSON"], - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Text" - }, - "separator": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Separator", - "dynamic": false, - "info": "Specify the separator to use between multiple outputs. Default for Text is '\\n\\n'. Default for Raw HTML is '\\n\\n'.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "separator", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "\n\n" - }, - "urls": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "URLs", - "dynamic": false, - "info": "", - "input_types": ["Message"], - "list": true, - "load_from_db": false, - "name": "urls", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": true, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": [ - "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/custom_component/custom_component.py" - ] - } - }, - "tool_mode": false - }, - "type": "URL" - }, - "dragging": false, - "height": 365, - "id": "URL-CIWR9", - "measured": { - "height": 365, - "width": 320 - }, - "position": { - "x": 1429.5084407373513, - "y": 632.4558314720756 - }, - "positionAbsolute": { - "x": 1436.982480021523, - "y": 651.1409296825055 - }, - "selected": false, - "type": "genericNode", - "width": 320 - }, - { - "data": { - "id": "AnthropicModel-rdklm", - "node": { - "base_classes": ["LanguageModel", "Message"], - "beta": false, - "category": "models", - "conditional_paths": [], - "custom_fields": {}, - "description": "Generate text using Anthropic Chat&Completion LLMs with prefill support.", - "display_name": "Anthropic", - "documentation": "", - "edited": false, - "field_order": [ - "input_value", - "system_message", - "stream", - "max_tokens", - "model_name", - "api_key", - "temperature", - "base_url", - "tool_model_enabled", - "prefill" - ], - "frozen": false, - "icon": "Anthropic", - "key": "AnthropicModel", - "legacy": false, - "metadata": {}, - "minimized": false, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Message", - "method": "text_response", - "name": "text_output", - "required_inputs": [], - "selected": "Message", - "tool_mode": true, - "types": ["Message"], - "value": "__UNDEFINED__" - }, - { - "allows_loop": false, - "cache": true, - "display_name": "Language Model", - "method": "build_model", - "name": "model_output", - "required_inputs": ["api_key"], - "selected": "LanguageModel", - "tool_mode": true, - "types": ["LanguageModel"], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "score": 0.0005851173668140926, - "template": { - "_type": "Component", - "api_key": { - "_input_type": "SecretStrInput", - "advanced": false, - "display_name": "Anthropic API Key", - "dynamic": false, - "info": "Your Anthropic API key.", - "input_types": ["Message"], - "load_from_db": true, - "name": "api_key", - "password": true, - "placeholder": "", - "real_time_refresh": true, - "required": true, - "show": true, - "title_case": false, - "type": "str", - "value": "ANTHROPIC_API_KEY" - }, - "base_url": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "Anthropic API URL", - "dynamic": false, - "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.", - "input_types": ["Message"], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "https://api.anthropic.com" - }, - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom loguru import logger\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic Chat&Completion LLMs with prefill support.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=\"https://api.anthropic.com\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"prefill\", display_name=\"Prefill\", info=\"Prefill text to guide the model's response.\", advanced=True\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens_to_sample=self.max_tokens,\n temperature=self.temperature,\n anthropic_api_url=self.base_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n for model in model_ids:\n model_with_tool = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n anthropic_api_url=self.base_url,\n )\n if not self.supports_tool_calling(model_with_tool):\n model_ids.remove(model)\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\"} and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"][\"value\"] = ids[0]\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n" - }, - "input_value": { - "_input_type": "MessageInput", - "advanced": false, - "display_name": "Input", - "dynamic": false, - "info": "", - "input_types": ["Message"], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "input_value", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "" - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "int", - "value": 4096 - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "info": "", - "name": "model_name", - "options": [ - "claude-3-5-sonnet-latest", - "claude-3-5-haiku-latest", - "claude-3-opus-latest", - "claude-3-5-sonnet-20240620", - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307" - ], - "options_metadata": [], - "placeholder": "", - "refresh_button": true, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "claude-3-5-sonnet-latest" - }, - "prefill": { - "_input_type": "MessageTextInput", - "advanced": true, - "display_name": "Prefill", - "dynamic": false, - "info": "Prefill text to guide the model's response.", - "input_types": ["Message"], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "prefill", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "" - }, - "stream": { - "_input_type": "BoolInput", - "advanced": true, - "display_name": "Stream", - "dynamic": false, - "info": "Stream the response from the model. Streaming works only in Chat.", - "list": false, - "list_add_label": "Add More", - "name": "stream", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "bool", - "value": false - }, - "system_message": { - "_input_type": "MultilineInput", - "advanced": false, - "display_name": "System Message", - "dynamic": false, - "info": "System message to pass to the model.", - "input_types": ["Message"], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "multiline": true, - "name": "system_message", - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "type": "str", - "value": "admin@admin.com" - }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "Run inference with this temperature. Must by in the closed interval [0.0, 1.0].", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "placeholder": "", - "range_spec": { - "max": 1, - "min": 0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "type": "slider", - "value": 0.1 - }, - "tool_model_enabled": { - "_input_type": "BoolInput", - "advanced": false, - "display_name": "Enable Tool Models", - "dynamic": false, - "info": "Select if you want to use models that can work with tools. If yes, only those models will be shown.", - "list": false, - "list_add_label": "Add More", - "name": "tool_model_enabled", - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "bool", - "value": true - } - }, - "tool_mode": false - }, - "showNode": true, - "type": "AnthropicModel" - }, - "dragging": false, - "id": "AnthropicModel-rdklm", - "measured": { - "height": 801, - "width": 320 - }, - "position": { - "x": 2585.5577736139826, - "y": 454.98013556663204 + "x": 2978.7260774697843, + "y": 1090.820738482898 }, "selected": false, "type": "genericNode" } ], "viewport": { - "x": -312.5858922909906, - "y": 143.0557404215083, - "zoom": 0.5176725959481484 + "x": -213.05377487995963, + "y": 163.9059453667432, + "zoom": 0.46721297887336727 } }, "description": "Generates well-structured code for custom components following Langflow's specifications.", @@ -2129,5 +2335,8 @@ "is_component": false, "last_tested_version": "1.2.0", "name": "Custom Component Generator", - "tags": ["coding", "web-scraping"] -} + "tags": [ + "coding", + "web-scraping" + ] +} \ No newline at end of file diff --git a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts index db405e929..72eea35f4 100644 --- a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts @@ -31,6 +31,16 @@ withEventDeliveryModes( timeout: 100000, }); + await page.waitForSelector('[data-testid="dropdown_str_model_name"]', { + timeout: 5000, + }); + + await page.getByTestId("dropdown_str_model_name").click(); + + await page.keyboard.press("Enter"); + + await page.waitForTimeout(1000); + try { await page .getByTestId("anchor-popover-anchor-input-api_key") @@ -40,28 +50,14 @@ withEventDeliveryModes( console.log("There's API already added"); } - await page.waitForSelector('[data-testid="dropdown_str_model_name"]', { - timeout: 5000, - }); + await page.getByTestId("playground-btn-flow-io").click(); - await page.getByTestId("dropdown_str_model_name").click(); + await page.getByTestId("button-send").last().click(); - await page.keyboard.press("Enter"); + await page.waitForTimeout(1000); - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); - - await page.getByText("built successfully").last().click({ - timeout: 15000, - }); - - await page.getByText("Playground", { exact: true }).last().click(); - await page - .getByText("No input message provided.", { exact: true }) - .last() - .isVisible(); - - await waitForOpenModalWithChatInput(page); + const stopButton = page.getByRole("button", { name: "Stop" }); + await stopButton.waitFor({ state: "hidden", timeout: 30000 * 3 }); const textContents = await getAllResponseMessage(page); expect(textContents.length).toBeGreaterThan(100);