diff --git a/src/backend/base/langflow/components/data/api_request.py b/src/backend/base/langflow/components/data/api_request.py index 7201b7ab8..5772c7ab5 100644 --- a/src/backend/base/langflow/components/data/api_request.py +++ b/src/backend/base/langflow/components/data/api_request.py @@ -27,6 +27,7 @@ from langflow.io import ( TableInput, ) from langflow.schema import Data +from langflow.schema.dataframe import DataFrame from langflow.schema.dotdict import dotdict @@ -156,6 +157,7 @@ class APIRequestComponent(Component): outputs = [ Output(display_name="Data", name="data", method="make_requests"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] def _parse_json_value(self, value: Any) -> Any: @@ -641,3 +643,12 @@ class APIRequestComponent(Component): return {} # Return empty dictionary instead of None return processed_headers return {} + + async def as_dataframe(self) -> DataFrame: + """Convert the API response data into a DataFrame. + + Returns: + DataFrame: A DataFrame containing the API response data. + """ + data = await self.make_requests() + return DataFrame(data) diff --git a/src/backend/base/langflow/components/helpers/create_list.py b/src/backend/base/langflow/components/helpers/create_list.py index a978ffe1f..016c4da94 100644 --- a/src/backend/base/langflow/components/helpers/create_list.py +++ b/src/backend/base/langflow/components/helpers/create_list.py @@ -1,6 +1,7 @@ from langflow.custom import Component from langflow.inputs import StrInput from langflow.schema import Data +from langflow.schema.dataframe import DataFrame from langflow.template import Output @@ -22,9 +23,18 @@ class CreateListComponent(Component): outputs = [ Output(display_name="Data List", name="list", method="create_list"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] def create_list(self) -> list[Data]: data = [Data(text=text) for text in self.texts] self.status = data return data + + def as_dataframe(self) -> DataFrame: + """Convert the list of Data objects into a DataFrame. + + Returns: + DataFrame: A DataFrame containing the list data. + """ + return DataFrame(self.create_list()) diff --git a/src/backend/base/langflow/components/helpers/memory.py b/src/backend/base/langflow/components/helpers/memory.py index 07b16b4c9..6dd63781f 100644 --- a/src/backend/base/langflow/components/helpers/memory.py +++ b/src/backend/base/langflow/components/helpers/memory.py @@ -4,6 +4,7 @@ from langflow.inputs import HandleInput from langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output from langflow.memory import aget_messages from langflow.schema import Data +from langflow.schema.dataframe import DataFrame from langflow.schema.message import Message from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER @@ -70,6 +71,7 @@ class MemoryComponent(Component): outputs = [ Output(display_name="Data", name="messages", method="retrieve_messages"), Output(display_name="Message", name="messages_text", method="retrieve_messages_as_text"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] async def retrieve_messages(self) -> Data: @@ -116,3 +118,12 @@ class MemoryComponent(Component): stored_text = data_to_text(self.template, await self.retrieve_messages()) self.status = stored_text return Message(text=stored_text) + + async def as_dataframe(self) -> DataFrame: + """Convert the retrieved messages into a DataFrame. + + Returns: + DataFrame: A DataFrame containing the message data. + """ + messages = await self.retrieve_messages() + return DataFrame(messages) diff --git a/src/backend/base/langflow/components/processing/alter_metadata.py b/src/backend/base/langflow/components/processing/alter_metadata.py index 118f410a2..a0178d04c 100644 --- a/src/backend/base/langflow/components/processing/alter_metadata.py +++ b/src/backend/base/langflow/components/processing/alter_metadata.py @@ -1,7 +1,7 @@ from langflow.custom import Component from langflow.inputs import MessageTextInput from langflow.io import HandleInput, NestedDictInput, Output, StrInput -from langflow.schema import Data +from langflow.schema import Data, DataFrame class AlterMetadataComponent(Component): @@ -48,6 +48,12 @@ class AlterMetadataComponent(Component): info="List of Input objects each with added Metadata", method="process_output", ), + Output( + display_name="DataFrame", + name="dataframe", + info="Data objects as a DataFrame, with metadata as columns", + method="as_dataframe", + ), ] def _as_clean_dict(self, obj): @@ -88,3 +94,13 @@ class AlterMetadataComponent(Component): # Set the status for tracking/debugging purposes self.status = data_objects return data_objects + + def as_dataframe(self) -> DataFrame: + """Convert the processed data objects into a DataFrame. + + Returns: + DataFrame: A DataFrame where each row corresponds to a Data object, + with metadata fields as columns. + """ + data_list = self.process_output() + return DataFrame(data_list) diff --git a/src/backend/base/langflow/components/tools/arxiv.py b/src/backend/base/langflow/components/tools/arxiv.py index dde27e4f3..62f4f2e09 100644 --- a/src/backend/base/langflow/components/tools/arxiv.py +++ b/src/backend/base/langflow/components/tools/arxiv.py @@ -6,7 +6,7 @@ from defusedxml.ElementTree import fromstring from langflow.custom import Component from langflow.io import DropdownInput, IntInput, MessageTextInput, Output -from langflow.schema import Data +from langflow.schema import Data, DataFrame class ArXivComponent(Component): @@ -37,7 +37,8 @@ class ArXivComponent(Component): ] outputs = [ - Output(display_name="Papers", name="papers", method="search_papers"), + Output(display_name="Data", name="data", method="search_papers"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] def build_query_url(self) -> str: @@ -148,3 +149,14 @@ class ArXivComponent(Component): return [error_data] else: return results + + def as_dataframe(self) -> DataFrame: + """Convert the Arxiv search results to a DataFrame. + + Returns: + DataFrame: A DataFrame containing the search results. + """ + data = self.search_papers() + if isinstance(data, list): + return DataFrame(data=[d.data for d in data]) + return DataFrame(data=[data.data]) diff --git a/src/backend/base/langflow/components/tools/glean_search_api.py b/src/backend/base/langflow/components/tools/glean_search_api.py index b856526d6..b4e981fc3 100644 --- a/src/backend/base/langflow/components/tools/glean_search_api.py +++ b/src/backend/base/langflow/components/tools/glean_search_api.py @@ -10,7 +10,8 @@ from pydantic.v1 import Field from langflow.base.langchain_utilities.model import LCToolComponent from langflow.field_typing import Tool from langflow.inputs import IntInput, MultilineInput, NestedDictInput, SecretStrInput, StrInput -from langflow.schema import Data +from langflow.io import Output +from langflow.schema import Data, DataFrame class GleanSearchAPISchema(BaseModel): @@ -97,10 +98,15 @@ class GleanAPIWrapper(BaseModel): class GleanSearchAPIComponent(LCToolComponent): - display_name = "Glean Search API" - description = "Call Glean Search API" - name = "GleanAPI" - icon = "Glean" + display_name: str = "Glean Search API" + description: str = "Search using Glean's API." + documentation: str = "https://docs.langflow.org/Components/components-tools#glean-search-api" + icon: str = "GleanSearch" + + outputs = [ + Output(display_name="Data", name="data", method="run_model"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), + ] inputs = [ StrInput( @@ -157,3 +163,14 @@ class GleanSearchAPIComponent(LCToolComponent): glean_api_url=glean_api_url, glean_access_token=glean_access_token, ) + + def as_dataframe(self) -> DataFrame: + """Convert the Glean search results to a DataFrame. + + Returns: + DataFrame: A DataFrame containing the search results. + """ + data = self.run_model() + if isinstance(data, list): + return DataFrame(data=[d.data for d in data]) + return DataFrame(data=[data.data]) diff --git a/src/backend/base/langflow/components/tools/search.py b/src/backend/base/langflow/components/tools/search.py index 362cbdf21..c18aead54 100644 --- a/src/backend/base/langflow/components/tools/search.py +++ b/src/backend/base/langflow/components/tools/search.py @@ -5,7 +5,7 @@ from langchain_community.utilities.searchapi import SearchApiAPIWrapper from langflow.custom import Component from langflow.inputs import DictInput, DropdownInput, IntInput, MultilineInput, SecretStrInput from langflow.io import Output -from langflow.schema import Data +from langflow.schema import Data, DataFrame from langflow.schema.message import Message @@ -31,6 +31,7 @@ class SearchComponent(Component): outputs = [ Output(display_name="Data", name="data", method="fetch_content"), Output(display_name="Text", name="text", method="fetch_content_text"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] def _build_wrapper(self): @@ -77,3 +78,12 @@ class SearchComponent(Component): result_string += item.text + "\n" self.status = result_string return Message(text=result_string) + + def as_dataframe(self) -> DataFrame: + """Convert the search results to a DataFrame. + + Returns: + DataFrame: A DataFrame containing the search results. + """ + data = self.fetch_content() + return DataFrame(data) diff --git a/src/backend/base/langflow/components/tools/wikipedia.py b/src/backend/base/langflow/components/tools/wikipedia.py index a71bce575..ee9fa13c4 100644 --- a/src/backend/base/langflow/components/tools/wikipedia.py +++ b/src/backend/base/langflow/components/tools/wikipedia.py @@ -3,7 +3,7 @@ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper from langflow.custom import Component from langflow.inputs import BoolInput, IntInput, MessageTextInput, MultilineInput from langflow.io import Output -from langflow.schema import Data +from langflow.schema import Data, DataFrame from langflow.schema.message import Message @@ -28,7 +28,7 @@ class WikipediaComponent(Component): outputs = [ Output(display_name="Data", name="data", method="fetch_content"), - Output(display_name="Text", name="text", method="fetch_content_text"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] def fetch_content(self) -> list[Data]: @@ -53,3 +53,12 @@ class WikipediaComponent(Component): load_all_available_meta=self.load_all_available_meta, doc_content_chars_max=self.doc_content_chars_max, ) + + def as_dataframe(self) -> DataFrame: + """Convert the Wikipedia results to a DataFrame. + + Returns: + DataFrame: A DataFrame containing the Wikipedia results. + """ + data = self.fetch_content() + return DataFrame(data) diff --git a/src/backend/base/langflow/components/tools/wolfram_alpha_api.py b/src/backend/base/langflow/components/tools/wolfram_alpha_api.py index 410677c65..ad2c2fc7c 100644 --- a/src/backend/base/langflow/components/tools/wolfram_alpha_api.py +++ b/src/backend/base/langflow/components/tools/wolfram_alpha_api.py @@ -3,7 +3,8 @@ from langchain_community.utilities.wolfram_alpha import WolframAlphaAPIWrapper from langflow.base.langchain_utilities.model import LCToolComponent from langflow.field_typing import Tool from langflow.inputs import MultilineInput, SecretStrInput -from langflow.schema import Data +from langflow.io import Output +from langflow.schema import Data, DataFrame class WolframAlphaAPIComponent(LCToolComponent): @@ -12,6 +13,11 @@ class WolframAlphaAPIComponent(LCToolComponent): topics, delivering structured responses.""" name = "WolframAlphaAPI" + outputs = [ + Output(display_name="Data", name="data", method="run_model"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), + ] + inputs = [ MultilineInput( name="input_value", display_name="Input Query", info="Example query: 'What is the population of France?'" @@ -34,3 +40,12 @@ topics, delivering structured responses.""" def _build_wrapper(self) -> WolframAlphaAPIWrapper: return WolframAlphaAPIWrapper(wolfram_alpha_appid=self.app_id) + + def as_dataframe(self) -> DataFrame: + """Convert the Wolfram Alpha results to a DataFrame. + + Returns: + DataFrame: A DataFrame containing the query results. + """ + data = self.run_model() + return DataFrame(data) diff --git a/src/backend/base/langflow/components/tools/yahoo.py b/src/backend/base/langflow/components/tools/yahoo.py index 5874d5154..4fdc2c820 100644 --- a/src/backend/base/langflow/components/tools/yahoo.py +++ b/src/backend/base/langflow/components/tools/yahoo.py @@ -10,7 +10,7 @@ from pydantic import BaseModel, Field from langflow.custom import Component from langflow.inputs import DropdownInput, IntInput, MessageTextInput from langflow.io import Output -from langflow.schema import Data +from langflow.schema import Data, DataFrame from langflow.schema.message import Message @@ -79,6 +79,7 @@ to access financial data and market information from Yahoo Finance.""" outputs = [ Output(display_name="Data", name="data", method="fetch_content"), Output(display_name="Text", name="text", method="fetch_content_text"), + Output(display_name="DataFrame", name="dataframe", method="as_dataframe"), ] def run_model(self) -> list[Data]: @@ -140,3 +141,12 @@ to access financial data and market information from Yahoo Finance.""" data_list = [Data(text=result, data={"result": result})] return data_list + + def as_dataframe(self) -> DataFrame: + """Convert the Yahoo search results to a DataFrame. + + Returns: + DataFrame: A DataFrame containing the search results. + """ + data = self.fetch_content() + return DataFrame(data) 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 9a75bfffa..81d1031eb 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 @@ -567,6 +567,19 @@ "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, @@ -588,7 +601,7 @@ "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.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 ]\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" + "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", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json index 1f2cc1c83..d3d7a4d8e 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json @@ -1,40 +1,13 @@ { "data": { "edges": [ - { - "animated": false, - "className": "", - "data": { - "sourceHandle": { - "dataType": "ArXivComponent", - "id": "ArXivComponent-LChQN", - "name": "papers", - "output_types": [ - "Data" - ] - }, - "targetHandle": { - "fieldName": "data", - "id": "LoopComponent-3vpc1", - "inputTypes": [ - "Data" - ], - "type": "other" - } - }, - "id": "xy-edge__ArXivComponent-LChQN{œdataTypeœ:œArXivComponentœ,œidœ:œArXivComponent-LChQNœ,œnameœ:œpapersœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œfieldNameœ:œdataœ,œidœ:œLoopComponent-3vpc1œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "ArXivComponent-LChQN", - "sourceHandle": "{œdataTypeœ: œArXivComponentœ, œidœ: œArXivComponent-LChQNœ, œnameœ: œpapersœ, œoutput_typesœ: [œDataœ]}", - "target": "LoopComponent-3vpc1", - "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œLoopComponent-3vpc1œ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" - }, { "animated": false, "className": "", "data": { "sourceHandle": { "dataType": "LoopComponent", - "id": "LoopComponent-3vpc1", + "id": "LoopComponent-QhsXW", "name": "item", "output_types": [ "Data" @@ -42,18 +15,19 @@ }, "targetHandle": { "fieldName": "data", - "id": "ParseData-Pf12J", + "id": "ParseData-x6wkY", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}-ParseData-Pf12J{œfieldNameœ:œdataœ,œidœ:œParseData-Pf12Jœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "LoopComponent-3vpc1", - "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}", - "target": "ParseData-Pf12J", - "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-Pf12Jœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-LoopComponent-QhsXW{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-QhsXWœ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}-ParseData-x6wkY{œfieldNameœ:œdataœ,œidœ:œParseData-x6wkYœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "selected": false, + "source": "LoopComponent-QhsXW", + "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-QhsXWœ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-x6wkY", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-x6wkYœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -61,7 +35,7 @@ "data": { "sourceHandle": { "dataType": "ParseData", - "id": "ParseData-Pf12J", + "id": "ParseData-x6wkY", "name": "text", "output_types": [ "Message" @@ -69,18 +43,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "AnthropicModel-beO6B", + "id": "AnthropicModel-uuAY6", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ParseData-Pf12J{œdataTypeœ:œParseDataœ,œidœ:œParseData-Pf12Jœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-beO6B{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-beO6Bœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ParseData-Pf12J", - "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-Pf12Jœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "AnthropicModel-beO6B", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-beO6Bœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ParseData-x6wkY{œdataTypeœ:œParseDataœ,œidœ:œParseData-x6wkYœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-uuAY6{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-uuAY6œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ParseData-x6wkY", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-x6wkYœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "AnthropicModel-uuAY6", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-uuAY6œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -88,7 +63,7 @@ "data": { "sourceHandle": { "dataType": "AnthropicModel", - "id": "AnthropicModel-beO6B", + "id": "AnthropicModel-uuAY6", "name": "text_output", "output_types": [ "Message" @@ -96,18 +71,19 @@ }, "targetHandle": { "fieldName": "message", - "id": "MessagetoData-QRSBb", + "id": "MessagetoData-9Kbcj", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__AnthropicModel-beO6B{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-beO6Bœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-QRSBb{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-QRSBbœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "AnthropicModel-beO6B", - "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-beO6Bœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "MessagetoData-QRSBb", - "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œMessagetoData-QRSBbœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-AnthropicModel-uuAY6{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-uuAY6œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-9Kbcj{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-9Kbcjœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "AnthropicModel-uuAY6", + "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-uuAY6œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "MessagetoData-9Kbcj", + "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œMessagetoData-9Kbcjœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -115,7 +91,7 @@ "data": { "sourceHandle": { "dataType": "MessagetoData", - "id": "MessagetoData-QRSBb", + "id": "MessagetoData-9Kbcj", "name": "data", "output_types": [ "Data" @@ -123,18 +99,19 @@ }, "targetHandle": { "dataType": "LoopComponent", - "id": "LoopComponent-3vpc1", + "id": "LoopComponent-QhsXW", "name": "item", "output_types": [ "Data" ] } }, - "id": "xy-edge__MessagetoData-QRSBb{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-QRSBbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", - "source": "MessagetoData-QRSBb", - "sourceHandle": "{œdataTypeœ: œMessagetoDataœ, œidœ: œMessagetoData-QRSBbœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", - "target": "LoopComponent-3vpc1", - "targetHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}" + "id": "reactflow__edge-MessagetoData-9Kbcj{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-9Kbcjœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-QhsXW{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-QhsXWœ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", + "selected": false, + "source": "MessagetoData-9Kbcj", + "sourceHandle": "{œdataTypeœ: œMessagetoDataœ, œidœ: œMessagetoData-9Kbcjœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", + "target": "LoopComponent-QhsXW", + "targetHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-QhsXWœ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}" }, { "animated": false, @@ -142,7 +119,7 @@ "data": { "sourceHandle": { "dataType": "LoopComponent", - "id": "LoopComponent-3vpc1", + "id": "LoopComponent-QhsXW", "name": "done", "output_types": [ "Data" @@ -150,18 +127,19 @@ }, "targetHandle": { "fieldName": "data", - "id": "ParseData-igEkj", + "id": "ParseData-NpCtQ", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}-ParseData-igEkj{œfieldNameœ:œdataœ,œidœ:œParseData-igEkjœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "LoopComponent-3vpc1", - "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œdoneœ, œoutput_typesœ: [œDataœ]}", - "target": "ParseData-igEkj", - "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-igEkjœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-LoopComponent-QhsXW{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-QhsXWœ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}-ParseData-NpCtQ{œfieldNameœ:œdataœ,œidœ:œParseData-NpCtQœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "selected": false, + "source": "LoopComponent-QhsXW", + "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-QhsXWœ, œnameœ: œdoneœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-NpCtQ", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-NpCtQœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -169,7 +147,7 @@ "data": { "sourceHandle": { "dataType": "ParseData", - "id": "ParseData-igEkj", + "id": "ParseData-NpCtQ", "name": "text", "output_types": [ "Message" @@ -177,7 +155,7 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-UZgon", + "id": "ChatOutput-Uq5M0", "inputTypes": [ "Data", "DataFrame", @@ -186,11 +164,12 @@ "type": "str" } }, - "id": "xy-edge__ParseData-igEkj{œdataTypeœ:œParseDataœ,œidœ:œParseData-igEkjœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-UZgon{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-UZgonœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ParseData-igEkj", - "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-igEkjœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-UZgon", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-UZgonœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ParseData-NpCtQ{œdataTypeœ:œParseDataœ,œidœ:œParseData-NpCtQœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-Uq5M0{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-Uq5M0œ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ParseData-NpCtQ", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-NpCtQœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-Uq5M0", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-Uq5M0œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -198,7 +177,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-m10vc", + "id": "ChatInput-doR66", "name": "message", "output_types": [ "Message" @@ -206,24 +185,50 @@ }, "targetHandle": { "fieldName": "search_query", - "id": "ArXivComponent-LChQN", + "id": "ArXivComponent-Vwek5", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ChatInput-m10vc{œdataTypeœ:œChatInputœ,œidœ:œChatInput-m10vcœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ArXivComponent-LChQN{œfieldNameœ:œsearch_queryœ,œidœ:œArXivComponent-LChQNœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ChatInput-m10vc", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-m10vcœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "ArXivComponent-LChQN", - "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œArXivComponent-LChQNœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ChatInput-doR66{œdataTypeœ:œChatInputœ,œidœ:œChatInput-doR66œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ArXivComponent-Vwek5{œfieldNameœ:œsearch_queryœ,œidœ:œArXivComponent-Vwek5œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ChatInput-doR66", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-doR66œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ArXivComponent-Vwek5", + "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œArXivComponent-Vwek5œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "data": { + "sourceHandle": { + "dataType": "ArXivComponent", + "id": "ArXivComponent-Vwek5", + "name": "data", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "LoopComponent-QhsXW", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__ArXivComponent-Vwek5{œdataTypeœ:œArXivComponentœ,œidœ:œArXivComponent-Vwek5œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-QhsXW{œfieldNameœ:œdataœ,œidœ:œLoopComponent-QhsXWœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "ArXivComponent-Vwek5", + "sourceHandle": "{œdataTypeœ: œArXivComponentœ, œidœ: œArXivComponent-Vwek5œ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", + "target": "LoopComponent-QhsXW", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œLoopComponent-QhsXWœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" } ], "nodes": [ { "data": { - "id": "ArXivComponent-LChQN", + "id": "ArXivComponent-Vwek5", "node": { "base_classes": [ "Data" @@ -251,15 +256,28 @@ { "allows_loop": false, "cache": true, - "display_name": "Papers", + "display_name": "Data", "method": "search_papers", - "name": "papers", + "name": "data", "selected": "Data", "tool_mode": true, "types": [ "Data" ], "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, @@ -281,7 +299,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import urllib.request\nfrom urllib.parse import urlparse\nfrom xml.etree.ElementTree import Element\n\nfrom defusedxml.ElementTree import fromstring\n\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, Output\nfrom langflow.schema import Data\n\n\nclass ArXivComponent(Component):\n display_name = \"arXiv\"\n description = \"Search and retrieve papers from arXiv.org\"\n icon = \"arXiv\"\n\n inputs = [\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"The search query for arXiv papers (e.g., 'quantum computing')\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Field\",\n info=\"The field to search in\",\n options=[\"all\", \"title\", \"abstract\", \"author\", \"cat\"], # cat is for category\n value=\"all\",\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"Maximum number of results to return\",\n value=10,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Papers\", name=\"papers\", method=\"search_papers\"),\n ]\n\n def build_query_url(self) -> str:\n \"\"\"Build the arXiv API query URL.\"\"\"\n base_url = \"http://export.arxiv.org/api/query?\"\n\n # Build the search query\n search_query = f\"{self.search_type}:{self.search_query}\"\n\n # URL parameters\n params = {\n \"search_query\": search_query,\n \"max_results\": str(self.max_results),\n }\n\n # Convert params to URL query string\n query_string = \"&\".join([f\"{k}={urllib.parse.quote(str(v))}\" for k, v in params.items()])\n\n return base_url + query_string\n\n def parse_atom_response(self, response_text: str) -> list[dict]:\n \"\"\"Parse the Atom XML response from arXiv.\"\"\"\n # Parse XML safely using defusedxml\n root = fromstring(response_text)\n\n # Define namespace dictionary for XML parsing\n ns = {\"atom\": \"http://www.w3.org/2005/Atom\", \"arxiv\": \"http://arxiv.org/schemas/atom\"}\n\n papers = []\n # Process each entry (paper)\n for entry in root.findall(\"atom:entry\", ns):\n paper = {\n \"id\": self._get_text(entry, \"atom:id\", ns),\n \"title\": self._get_text(entry, \"atom:title\", ns),\n \"summary\": self._get_text(entry, \"atom:summary\", ns),\n \"published\": self._get_text(entry, \"atom:published\", ns),\n \"updated\": self._get_text(entry, \"atom:updated\", ns),\n \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n \"arxiv_url\": self._get_link(entry, \"alternate\", ns),\n \"pdf_url\": self._get_link(entry, \"related\", ns),\n \"comment\": self._get_text(entry, \"arxiv:comment\", ns),\n \"journal_ref\": self._get_text(entry, \"arxiv:journal_ref\", ns),\n \"primary_category\": self._get_category(entry, ns),\n \"categories\": [cat.get(\"term\") for cat in entry.findall(\"atom:category\", ns)],\n }\n papers.append(paper)\n\n return papers\n\n def _get_text(self, element: Element, path: str, ns: dict) -> str | None:\n \"\"\"Safely extract text from an XML element.\"\"\"\n el = element.find(path, ns)\n return el.text.strip() if el is not None and el.text else None\n\n def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:\n \"\"\"Get link URL based on relation type.\"\"\"\n for link in element.findall(\"atom:link\", ns):\n if link.get(\"rel\") == rel:\n return link.get(\"href\")\n return None\n\n def _get_category(self, element: Element, ns: dict) -> str | None:\n \"\"\"Get primary category.\"\"\"\n cat = element.find(\"arxiv:primary_category\", ns)\n return cat.get(\"term\") if cat is not None else None\n\n def search_papers(self) -> list[Data]:\n \"\"\"Search arXiv and return results.\"\"\"\n try:\n # Build the query URL\n url = self.build_query_url()\n\n # Validate URL scheme and host\n parsed_url = urlparse(url)\n if parsed_url.scheme not in {\"http\", \"https\"}:\n error_msg = f\"Invalid URL scheme: {parsed_url.scheme}\"\n raise ValueError(error_msg)\n if parsed_url.hostname != \"export.arxiv.org\":\n error_msg = f\"Invalid host: {parsed_url.hostname}\"\n raise ValueError(error_msg)\n\n # Create a custom opener that only allows http/https schemes\n class RestrictedHTTPHandler(urllib.request.HTTPHandler):\n def http_open(self, req):\n return super().http_open(req)\n\n class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):\n def https_open(self, req):\n return super().https_open(req)\n\n # Build opener with restricted handlers\n opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)\n urllib.request.install_opener(opener)\n\n # Make the request with validated URL using restricted opener\n response = opener.open(url)\n response_text = response.read().decode(\"utf-8\")\n\n # Parse the response\n papers = self.parse_atom_response(response_text)\n\n # Convert to Data objects\n results = [Data(data=paper) for paper in papers]\n self.status = results\n except (urllib.error.URLError, ValueError) as e:\n error_data = Data(data={\"error\": f\"Request error: {e!s}\"})\n self.status = error_data\n return [error_data]\n else:\n return results\n" + "value": "import urllib.request\nfrom urllib.parse import urlparse\nfrom xml.etree.ElementTree import Element\n\nfrom defusedxml.ElementTree import fromstring\n\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, Output\nfrom langflow.schema import Data, DataFrame\n\n\nclass ArXivComponent(Component):\n display_name = \"arXiv\"\n description = \"Search and retrieve papers from arXiv.org\"\n icon = \"arXiv\"\n\n inputs = [\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"The search query for arXiv papers (e.g., 'quantum computing')\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Field\",\n info=\"The field to search in\",\n options=[\"all\", \"title\", \"abstract\", \"author\", \"cat\"], # cat is for category\n value=\"all\",\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"Maximum number of results to return\",\n value=10,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"search_papers\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def build_query_url(self) -> str:\n \"\"\"Build the arXiv API query URL.\"\"\"\n base_url = \"http://export.arxiv.org/api/query?\"\n\n # Build the search query\n search_query = f\"{self.search_type}:{self.search_query}\"\n\n # URL parameters\n params = {\n \"search_query\": search_query,\n \"max_results\": str(self.max_results),\n }\n\n # Convert params to URL query string\n query_string = \"&\".join([f\"{k}={urllib.parse.quote(str(v))}\" for k, v in params.items()])\n\n return base_url + query_string\n\n def parse_atom_response(self, response_text: str) -> list[dict]:\n \"\"\"Parse the Atom XML response from arXiv.\"\"\"\n # Parse XML safely using defusedxml\n root = fromstring(response_text)\n\n # Define namespace dictionary for XML parsing\n ns = {\"atom\": \"http://www.w3.org/2005/Atom\", \"arxiv\": \"http://arxiv.org/schemas/atom\"}\n\n papers = []\n # Process each entry (paper)\n for entry in root.findall(\"atom:entry\", ns):\n paper = {\n \"id\": self._get_text(entry, \"atom:id\", ns),\n \"title\": self._get_text(entry, \"atom:title\", ns),\n \"summary\": self._get_text(entry, \"atom:summary\", ns),\n \"published\": self._get_text(entry, \"atom:published\", ns),\n \"updated\": self._get_text(entry, \"atom:updated\", ns),\n \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n \"arxiv_url\": self._get_link(entry, \"alternate\", ns),\n \"pdf_url\": self._get_link(entry, \"related\", ns),\n \"comment\": self._get_text(entry, \"arxiv:comment\", ns),\n \"journal_ref\": self._get_text(entry, \"arxiv:journal_ref\", ns),\n \"primary_category\": self._get_category(entry, ns),\n \"categories\": [cat.get(\"term\") for cat in entry.findall(\"atom:category\", ns)],\n }\n papers.append(paper)\n\n return papers\n\n def _get_text(self, element: Element, path: str, ns: dict) -> str | None:\n \"\"\"Safely extract text from an XML element.\"\"\"\n el = element.find(path, ns)\n return el.text.strip() if el is not None and el.text else None\n\n def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:\n \"\"\"Get link URL based on relation type.\"\"\"\n for link in element.findall(\"atom:link\", ns):\n if link.get(\"rel\") == rel:\n return link.get(\"href\")\n return None\n\n def _get_category(self, element: Element, ns: dict) -> str | None:\n \"\"\"Get primary category.\"\"\"\n cat = element.find(\"arxiv:primary_category\", ns)\n return cat.get(\"term\") if cat is not None else None\n\n def search_papers(self) -> list[Data]:\n \"\"\"Search arXiv and return results.\"\"\"\n try:\n # Build the query URL\n url = self.build_query_url()\n\n # Validate URL scheme and host\n parsed_url = urlparse(url)\n if parsed_url.scheme not in {\"http\", \"https\"}:\n error_msg = f\"Invalid URL scheme: {parsed_url.scheme}\"\n raise ValueError(error_msg)\n if parsed_url.hostname != \"export.arxiv.org\":\n error_msg = f\"Invalid host: {parsed_url.hostname}\"\n raise ValueError(error_msg)\n\n # Create a custom opener that only allows http/https schemes\n class RestrictedHTTPHandler(urllib.request.HTTPHandler):\n def http_open(self, req):\n return super().http_open(req)\n\n class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):\n def https_open(self, req):\n return super().https_open(req)\n\n # Build opener with restricted handlers\n opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)\n urllib.request.install_opener(opener)\n\n # Make the request with validated URL using restricted opener\n response = opener.open(url)\n response_text = response.read().decode(\"utf-8\")\n\n # Parse the response\n papers = self.parse_atom_response(response_text)\n\n # Convert to Data objects\n results = [Data(data=paper) for paper in papers]\n self.status = results\n except (urllib.error.URLError, ValueError) as e:\n error_data = Data(data={\"error\": f\"Request error: {e!s}\"})\n self.status = error_data\n return [error_data]\n else:\n return results\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Convert the Arxiv search results to a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the search results.\n \"\"\"\n data = self.search_papers()\n if isinstance(data, list):\n return DataFrame(data=[d.data for d in data])\n return DataFrame(data=[data.data])\n" }, "max_results": { "_input_type": "IntInput", @@ -357,10 +375,10 @@ "type": "ArXivComponent" }, "dragging": false, - "id": "ArXivComponent-LChQN", + "id": "ArXivComponent-Vwek5", "measured": { - "height": 395, - "width": 320 + "height": 495, + "width": 360 }, "position": { "x": 81.59312530546094, @@ -371,7 +389,7 @@ }, { "data": { - "id": "LoopComponent-3vpc1", + "id": "LoopComponent-QhsXW", "node": { "base_classes": [ "Data" @@ -474,10 +492,10 @@ "type": "LoopComponent" }, "dragging": false, - "id": "LoopComponent-3vpc1", + "id": "LoopComponent-QhsXW", "measured": { - "height": 280, - "width": 320 + "height": 314, + "width": 360 }, "position": { "x": 517.2087344858119, @@ -488,7 +506,7 @@ }, { "data": { - "id": "ParseData-Pf12J", + "id": "ParseData-x6wkY", "node": { "base_classes": [ "Data", @@ -639,10 +657,10 @@ "type": "ParseData" }, "dragging": false, - "id": "ParseData-Pf12J", + "id": "ParseData-x6wkY", "measured": { - "height": 342, - "width": 320 + "height": 383, + "width": 360 }, "position": { "x": 996.5733307455723, @@ -653,7 +671,7 @@ }, { "data": { - "id": "AnthropicModel-beO6B", + "id": "AnthropicModel-uuAY6", "node": { "base_classes": [ "LanguageModel", @@ -741,7 +759,7 @@ "show": true, "title_case": false, "type": "str", - "value": "ANTHROPIC_API_KEY" + "value": "" }, "base_url": { "_input_type": "MessageTextInput", @@ -975,10 +993,10 @@ "type": "AnthropicModel" }, "dragging": false, - "id": "AnthropicModel-beO6B", + "id": "AnthropicModel-uuAY6", "measured": { - "height": 801, - "width": 320 + "height": 896, + "width": 360 }, "position": { "x": 1493.0259340837038, @@ -989,7 +1007,7 @@ }, { "data": { - "id": "MessagetoData-QRSBb", + "id": "MessagetoData-9Kbcj", "node": { "base_classes": [ "Data" @@ -1080,10 +1098,10 @@ "type": "MessagetoData" }, "dragging": false, - "id": "MessagetoData-QRSBb", + "id": "MessagetoData-9Kbcj", "measured": { - "height": 230, - "width": 320 + "height": 257, + "width": 360 }, "position": { "x": 1863.8805295746977, @@ -1094,7 +1112,7 @@ }, { "data": { - "id": "ParseData-igEkj", + "id": "ParseData-NpCtQ", "node": { "base_classes": [ "Data", @@ -1245,10 +1263,10 @@ "type": "ParseData" }, "dragging": false, - "id": "ParseData-igEkj", + "id": "ParseData-NpCtQ", "measured": { - "height": 342, - "width": 320 + "height": 383, + "width": 360 }, "position": { "x": 982.6945056277784, @@ -1259,7 +1277,7 @@ }, { "data": { - "id": "ChatOutput-UZgon", + "id": "ChatOutput-Uq5M0", "node": { "base_classes": [ "Message" @@ -1554,10 +1572,10 @@ "type": "ChatOutput" }, "dragging": false, - "id": "ChatOutput-UZgon", + "id": "ChatOutput-Uq5M0", "measured": { - "height": 66, - "width": 192 + "height": 74, + "width": 216 }, "position": { "x": 1498.3739454769902, @@ -1568,7 +1586,7 @@ }, { "data": { - "id": "ChatInput-m10vc", + "id": "ChatInput-doR66", "node": { "base_classes": [ "Message" @@ -1862,10 +1880,10 @@ "type": "ChatInput" }, "dragging": false, - "id": "ChatInput-m10vc", + "id": "ChatInput-doR66", "measured": { - "height": 66, - "width": 192 + "height": 74, + "width": 216 }, "position": { "x": -235.49853728839307, @@ -1876,7 +1894,7 @@ }, { "data": { - "id": "note-g4YbJ", + "id": "note-MMi4A", "node": { "description": "### 💡 Add your Anthropic API key here 👇", "display_name": "", @@ -1889,10 +1907,10 @@ }, "dragging": false, "height": 324, - "id": "note-g4YbJ", + "id": "note-MMi4A", "measured": { "height": 324, - "width": 358 + "width": 361 }, "position": { "x": 1479.4278434913201, @@ -1905,7 +1923,7 @@ }, { "data": { - "id": "note-e6tLL", + "id": "note-CBAXQ", "node": { "description": "# **Langflow Loop Component Template - ArXiv search result Translator** \nThis template translates research paper summaries on ArXiv into Portuguese and summarizes them. \n Using **Langflow’s looping mechanism**, the template iterates through multiple research papers, translates them with the **Anthropic** model component, and outputs an aggregated version of all translated papers. \n\n## Quickstart \n 1. Add your Anthropic API key to the **Anthropic** component. \n2. In the **Playground**, enter a query related to a research topic (for example, “Quantum Computing Advancements”). \n\n The flow fetches a list of research papers from ArXiv matching the query. Each paper in the retrieved list is processed one-by-one using the Langflow **Loop component**. \n\n The abstract of each paper is translated into Portuguese by the **Anthropic** model component. \n\n Once all papers are translated, the system aggregates them into a **single structured output**.", "display_name": "", @@ -1916,10 +1934,10 @@ }, "dragging": false, "height": 647, - "id": "note-e6tLL", + "id": "note-CBAXQ", "measured": { "height": 647, - "width": 577 + "width": 580 }, "position": { "x": -890.9006297459302, @@ -1932,9 +1950,9 @@ } ], "viewport": { - "x": 440.1573282533791, - "y": 190.151319158377, - "zoom": 0.21962836650867892 + "x": 379.5589874665091, + "y": 421.54787373656177, + "zoom": 0.4567705571748025 } }, "description": "This template iterates over search results using LoopComponent and translates each result into Portuguese automatically. 🚀", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json index abdcf7ce7..d96e6967d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json @@ -2646,6 +2646,19 @@ "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, @@ -2667,7 +2680,7 @@ "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.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 ]\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" + "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", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json index 3eb66d375..93984bf5b 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json @@ -841,6 +841,19 @@ "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, @@ -862,7 +875,7 @@ "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.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 ]\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" + "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", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json index d89f812f0..c7f0bbd78 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json @@ -7,7 +7,7 @@ "data": { "sourceHandle": { "dataType": "APIRequest", - "id": "APIRequest-ooZcW", + "id": "APIRequest-KJLNf", "name": "component_as_tool", "output_types": [ "Tool" @@ -15,19 +15,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-6NKOE", + "id": "Agent-gZkrx", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-APIRequest-ooZcW{œdataTypeœ:œAPIRequestœ,œidœ:œAPIRequest-ooZcWœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-6NKOE{œfieldNameœ:œtoolsœ,œidœ:œAgent-6NKOEœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-APIRequest-KJLNf{œdataTypeœ:œAPIRequestœ,œidœ:œAPIRequest-KJLNfœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-gZkrx{œfieldNameœ:œtoolsœ,œidœ:œAgent-gZkrxœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", "selected": false, - "source": "APIRequest-ooZcW", - "sourceHandle": "{œdataTypeœ: œAPIRequestœ, œidœ: œAPIRequest-ooZcWœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-6NKOE", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-6NKOEœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "source": "APIRequest-KJLNf", + "sourceHandle": "{œdataTypeœ: œAPIRequestœ, œidœ: œAPIRequest-KJLNfœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-gZkrx", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-gZkrxœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -35,7 +35,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-aFhoH", + "id": "ChatInput-eumKo", "name": "message", "output_types": [ "Message" @@ -43,19 +43,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-6NKOE", + "id": "Agent-gZkrx", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-ChatInput-aFhoH{œdataTypeœ:œChatInputœ,œidœ:œChatInput-aFhoHœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-6NKOE{œfieldNameœ:œinput_valueœ,œidœ:œAgent-6NKOEœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ChatInput-eumKo{œdataTypeœ:œChatInputœ,œidœ:œChatInput-eumKoœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-gZkrx{œfieldNameœ:œinput_valueœ,œidœ:œAgent-gZkrxœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ChatInput-aFhoH", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-aFhoHœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-6NKOE", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-6NKOEœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ChatInput-eumKo", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-eumKoœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-gZkrx", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-gZkrxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -63,7 +63,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-6NKOE", + "id": "Agent-gZkrx", "name": "response", "output_types": [ "Message" @@ -71,7 +71,7 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-WuOt0", + "id": "ChatOutput-D4JWF", "inputTypes": [ "Data", "DataFrame", @@ -80,12 +80,12 @@ "type": "str" } }, - "id": "reactflow__edge-Agent-6NKOE{œdataTypeœ:œAgentœ,œidœ:œAgent-6NKOEœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-WuOt0{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-WuOt0œ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Agent-gZkrx{œdataTypeœ:œAgentœ,œidœ:œAgent-gZkrxœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-D4JWF{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-D4JWFœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Agent-6NKOE", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-6NKOEœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-WuOt0", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-WuOt0œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" + "source": "Agent-gZkrx", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-gZkrxœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-D4JWF", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-D4JWFœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ @@ -93,10 +93,11 @@ "data": { "description": "Make HTTP requests using URLs or cURL commands.", "display_name": "API Request", - "id": "APIRequest-ooZcW", + "id": "APIRequest-KJLNf", "node": { "base_classes": [ - "Data" + "Data", + "DataFrame" ], "beta": false, "conditional_paths": [], @@ -132,6 +133,7 @@ "hidden": null, "method": "to_toolkit", "name": "component_as_tool", + "options": null, "required_inputs": null, "selected": "Tool", "tool_mode": true, @@ -212,7 +214,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import asyncio\nimport json\nimport re\nimport tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nimport aiofiles\nimport aiofiles.os as aiofiles_os\nimport httpx\nimport validators\n\nfrom langflow.base.curl.parse import parse_context\nfrom langflow.custom import Component\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n StrInput,\n TableInput,\n)\nfrom langflow.schema import Data\nfrom langflow.schema.dotdict import dotdict\n\n\nclass APIRequestComponent(Component):\n display_name = \"API Request\"\n description = \"Make HTTP requests using URLs or cURL commands.\"\n icon = \"Globe\"\n name = \"APIRequest\"\n\n default_keys = [\"urls\", \"method\", \"query_params\"]\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n list=True,\n info=\"Enter one or more URLs, separated by commas.\",\n advanced=False,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"curl\",\n display_name=\"cURL\",\n info=(\n \"Paste a curl command to populate the fields. \"\n \"This will fill in the dictionary fields for headers and body.\"\n ),\n advanced=True,\n real_time_refresh=True,\n tool_mode=True,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Method\",\n options=[\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"],\n info=\"The HTTP method to use.\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"use_curl\",\n display_name=\"Use cURL\",\n value=False,\n info=\"Enable cURL mode to populate fields from a cURL command.\",\n real_time_refresh=True,\n ),\n DataInput(\n name=\"query_params\",\n display_name=\"Query Parameters\",\n info=\"The query parameters to append to the URL.\",\n advanced=True,\n ),\n TableInput(\n name=\"body\",\n display_name=\"Body\",\n info=\"The body to send with the request as a dictionary (for POST, PATCH, PUT).\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Parameter name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"description\": \"Parameter value\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n advanced=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request as a dictionary.\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[],\n advanced=True,\n input_types=[\"Data\"],\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n value=5,\n info=\"The timeout to use for the request.\",\n advanced=True,\n ),\n BoolInput(\n name=\"follow_redirects\",\n display_name=\"Follow Redirects\",\n value=True,\n info=\"Whether to follow http redirects.\",\n advanced=True,\n ),\n BoolInput(\n name=\"save_to_file\",\n display_name=\"Save to File\",\n value=False,\n info=\"Save the API response to a temporary file\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_httpx_metadata\",\n display_name=\"Include HTTPx Metadata\",\n value=False,\n info=(\n \"Include properties such as headers, status_code, response_headers, \"\n \"and redirection_history in the output.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"make_requests\"),\n ]\n\n def _parse_json_value(self, value: Any) -> Any:\n \"\"\"Parse a value that might be a JSON string.\"\"\"\n if not isinstance(value, str):\n return value\n\n try:\n parsed = json.loads(value)\n except json.JSONDecodeError:\n return value\n else:\n return parsed\n\n def _process_body(self, body: Any) -> dict:\n \"\"\"Process the body input into a valid dictionary.\n\n Args:\n body: The body to process, can be dict, str, or list\n Returns:\n Processed dictionary\n \"\"\"\n if body is None:\n return {}\n if isinstance(body, dict):\n return self._process_dict_body(body)\n if isinstance(body, str):\n return self._process_string_body(body)\n if isinstance(body, list):\n return self._process_list_body(body)\n\n return {}\n\n def _process_dict_body(self, body: dict) -> dict:\n \"\"\"Process dictionary body by parsing JSON values.\"\"\"\n return {k: self._parse_json_value(v) for k, v in body.items()}\n\n def _process_string_body(self, body: str) -> dict:\n \"\"\"Process string body by attempting JSON parse.\"\"\"\n try:\n return self._process_body(json.loads(body))\n except json.JSONDecodeError:\n return {\"data\": body}\n\n def _process_list_body(self, body: list) -> dict:\n \"\"\"Process list body by converting to key-value dictionary.\"\"\"\n processed_dict = {}\n\n try:\n for item in body:\n if not self._is_valid_key_value_item(item):\n continue\n\n key = item[\"key\"]\n value = self._parse_json_value(item[\"value\"])\n processed_dict[key] = value\n\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process body list: {e}\")\n return {} # Return empty dictionary instead of None\n\n return processed_dict\n\n def _is_valid_key_value_item(self, item: Any) -> bool:\n \"\"\"Check if an item is a valid key-value dictionary.\"\"\"\n return isinstance(item, dict) and \"key\" in item and \"value\" in item\n\n def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:\n \"\"\"Parse a cURL command and update build configuration.\n\n Args:\n curl: The cURL command to parse\n build_config: The build configuration to update\n Returns:\n Updated build configuration\n \"\"\"\n try:\n parsed = parse_context(curl)\n\n # Update basic configuration\n build_config[\"urls\"][\"value\"] = [parsed.url]\n build_config[\"method\"][\"value\"] = parsed.method.upper()\n build_config[\"headers\"][\"advanced\"] = True\n build_config[\"body\"][\"advanced\"] = True\n\n # Process headers\n headers_list = [{\"key\": k, \"value\": v} for k, v in parsed.headers.items()]\n build_config[\"headers\"][\"value\"] = headers_list\n\n if headers_list:\n build_config[\"headers\"][\"advanced\"] = False\n\n # Process body data\n if not parsed.data:\n build_config[\"body\"][\"value\"] = []\n elif parsed.data:\n try:\n json_data = json.loads(parsed.data)\n if isinstance(json_data, dict):\n body_list = [\n {\"key\": k, \"value\": json.dumps(v) if isinstance(v, dict | list) else str(v)}\n for k, v in json_data.items()\n ]\n build_config[\"body\"][\"value\"] = body_list\n build_config[\"body\"][\"advanced\"] = False\n else:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": json.dumps(json_data)}]\n build_config[\"body\"][\"advanced\"] = False\n except json.JSONDecodeError:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": parsed.data}]\n build_config[\"body\"][\"advanced\"] = False\n\n except Exception as exc:\n msg = f\"Error parsing curl: {exc}\"\n self.log(msg)\n raise ValueError(msg) from exc\n\n return build_config\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n if field_name == \"use_curl\":\n build_config = self._update_curl_mode(build_config, use_curl=field_value)\n\n # Fields that should not be reset\n preserve_fields = {\"timeout\", \"follow_redirects\", \"save_to_file\", \"include_httpx_metadata\", \"use_curl\"}\n\n # Mapping between input types and their reset values\n type_reset_mapping = {\n TableInput: [],\n BoolInput: False,\n IntInput: 0,\n FloatInput: 0.0,\n MessageTextInput: \"\",\n StrInput: \"\",\n MultilineInput: \"\",\n DropdownInput: \"GET\",\n DataInput: {},\n }\n\n for input_field in self.inputs:\n # Only reset if field is not in preserve list\n if input_field.name not in preserve_fields:\n reset_value = type_reset_mapping.get(type(input_field), None)\n build_config[input_field.name][\"value\"] = reset_value\n self.log(f\"Reset field {input_field.name} to {reset_value}\")\n elif field_name == \"method\" and not self.use_curl:\n build_config = self._update_method_fields(build_config, field_value)\n elif field_name == \"curl\" and self.use_curl and field_value:\n build_config = self.parse_curl(field_value, build_config)\n return build_config\n\n def _update_curl_mode(self, build_config: dotdict, *, use_curl: bool) -> dotdict:\n always_visible = [\"method\", \"use_curl\"]\n\n for field in self.inputs:\n field_name = field.name\n field_config = build_config.get(field_name)\n if isinstance(field_config, dict):\n if field_name in always_visible:\n field_config[\"advanced\"] = False\n elif field_name == \"urls\":\n field_config[\"advanced\"] = use_curl\n elif field_name == \"curl\":\n field_config[\"advanced\"] = not use_curl\n field_config[\"real_time_refresh\"] = use_curl\n elif field_name in {\"body\", \"headers\"}:\n field_config[\"advanced\"] = True # Always keep body and headers in advanced when use_curl is False\n else:\n field_config[\"advanced\"] = use_curl\n else:\n self.log(f\"Expected dict for build_config[{field_name}], got {type(field_config).__name__}\")\n\n if not use_curl:\n current_method = build_config.get(\"method\", {}).get(\"value\", \"GET\")\n build_config = self._update_method_fields(build_config, current_method)\n\n return build_config\n\n def _update_method_fields(self, build_config: dotdict, method: str) -> dotdict:\n common_fields = [\n \"urls\",\n \"method\",\n \"use_curl\",\n ]\n\n always_advanced_fields = [\n \"body\",\n \"headers\",\n \"timeout\",\n \"follow_redirects\",\n \"save_to_file\",\n \"include_httpx_metadata\",\n ]\n\n body_fields = [\"body\"]\n\n for field in self.inputs:\n field_name = field.name\n field_config = build_config.get(field_name)\n if isinstance(field_config, dict):\n if field_name in common_fields:\n field_config[\"advanced\"] = False\n elif field_name in body_fields:\n field_config[\"advanced\"] = method not in {\"POST\", \"PUT\", \"PATCH\"}\n elif field_name in always_advanced_fields:\n field_config[\"advanced\"] = True\n else:\n field_config[\"advanced\"] = True\n else:\n self.log(f\"Expected dict for build_config[{field_name}], got {type(field_config).__name__}\")\n\n return build_config\n\n async def make_request(\n self,\n client: httpx.AsyncClient,\n method: str,\n url: str,\n headers: dict | None = None,\n body: Any = None,\n timeout: int = 5,\n *,\n follow_redirects: bool = True,\n save_to_file: bool = False,\n include_httpx_metadata: bool = False,\n ) -> Data:\n method = method.upper()\n if method not in {\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"}:\n msg = f\"Unsupported method: {method}\"\n raise ValueError(msg)\n\n # Process body using the new helper method\n processed_body = self._process_body(body)\n redirection_history = []\n\n try:\n response = await client.request(\n method,\n url,\n headers=headers,\n json=processed_body,\n timeout=timeout,\n follow_redirects=follow_redirects,\n )\n\n redirection_history = [\n {\n \"url\": redirect.headers.get(\"Location\", str(redirect.url)),\n \"status_code\": redirect.status_code,\n }\n for redirect in response.history\n ]\n\n is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)\n response_headers = self._headers_to_dict(response.headers)\n\n metadata: dict[str, Any] = {\n \"source\": url,\n }\n\n if save_to_file:\n mode = \"wb\" if is_binary else \"w\"\n encoding = response.encoding if mode == \"w\" else None\n if file_path:\n # Ensure parent directory exists\n await aiofiles_os.makedirs(file_path.parent, exist_ok=True)\n if is_binary:\n async with aiofiles.open(file_path, \"wb\") as f:\n await f.write(response.content)\n await f.flush()\n else:\n async with aiofiles.open(file_path, \"w\", encoding=encoding) as f:\n await f.write(response.text)\n await f.flush()\n metadata[\"file_path\"] = str(file_path)\n\n if include_httpx_metadata:\n metadata.update(\n {\n \"headers\": headers,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n }\n )\n return Data(data=metadata)\n\n if is_binary:\n result = response.content\n else:\n try:\n result = response.json()\n except json.JSONDecodeError:\n self.log(\"Failed to decode JSON response\")\n result = response.text.encode(\"utf-8\")\n\n metadata.update({\"result\": result})\n\n if include_httpx_metadata:\n metadata.update(\n {\n \"headers\": headers,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n }\n )\n return Data(data=metadata)\n except httpx.TimeoutException:\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 408,\n \"error\": \"Request timed out\",\n },\n )\n except Exception as exc: # noqa: BLE001\n self.log(f\"Error making request to {url}\")\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 500,\n \"error\": str(exc),\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n },\n )\n\n def add_query_params(self, url: str, params: dict) -> str:\n url_parts = list(urlparse(url))\n query = dict(parse_qsl(url_parts[4]))\n query.update(params)\n url_parts[4] = urlencode(query)\n return urlunparse(url_parts)\n\n async def make_requests(self) -> list[Data]:\n method = self.method\n urls = [url.strip() for url in self.urls if url.strip()]\n headers = self.headers or {}\n body = self.body or {}\n timeout = self.timeout\n follow_redirects = self.follow_redirects\n save_to_file = self.save_to_file\n include_httpx_metadata = self.include_httpx_metadata\n\n if self.use_curl and self.curl:\n self._build_config = self.parse_curl(self.curl, dotdict())\n\n invalid_urls = [url for url in urls if not validators.url(url)]\n if invalid_urls:\n msg = f\"Invalid URLs provided: {invalid_urls}\"\n raise ValueError(msg)\n\n if isinstance(self.query_params, str):\n query_params = dict(parse_qsl(self.query_params))\n else:\n query_params = self.query_params.data if self.query_params else {}\n\n # Process headers here\n headers = self._process_headers(headers)\n\n # Process body\n body = self._process_body(body)\n\n bodies = [body] * len(urls)\n\n urls = [self.add_query_params(url, query_params) for url in urls]\n\n async with httpx.AsyncClient() as client:\n results = await asyncio.gather(\n *[\n self.make_request(\n client,\n method,\n u,\n headers,\n rec,\n timeout,\n follow_redirects=follow_redirects,\n save_to_file=save_to_file,\n include_httpx_metadata=include_httpx_metadata,\n )\n for u, rec in zip(urls, bodies, strict=False)\n ]\n )\n self.status = results\n return results\n\n async def _response_info(\n self, response: httpx.Response, *, with_file_path: bool = False\n ) -> tuple[bool, Path | None]:\n \"\"\"Determine the file path and whether the response content is binary.\n\n Args:\n response (Response): The HTTP response object.\n with_file_path (bool): Whether to save the response content to a file.\n\n Returns:\n Tuple[bool, Path | None]:\n A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).\n \"\"\"\n content_type = response.headers.get(\"Content-Type\", \"\")\n is_binary = \"application/octet-stream\" in content_type or \"application/binary\" in content_type\n\n if not with_file_path:\n return is_binary, None\n\n component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__\n\n # Create directory asynchronously\n await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)\n\n filename = None\n if \"Content-Disposition\" in response.headers:\n content_disposition = response.headers[\"Content-Disposition\"]\n filename_match = re.search(r'filename=\"(.+?)\"', content_disposition)\n if filename_match:\n extracted_filename = filename_match.group(1)\n filename = extracted_filename\n\n # Step 3: Infer file extension or use part of the request URL if no filename\n if not filename:\n # Extract the last segment of the URL path\n url_path = urlparse(str(response.request.url) if response.request else \"\").path\n base_name = Path(url_path).name # Get the last segment of the path\n if not base_name: # If the path ends with a slash or is empty\n base_name = \"response\"\n\n # Infer file extension\n content_type_to_extension = {\n \"text/plain\": \".txt\",\n \"application/json\": \".json\",\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"application/octet-stream\": \".bin\",\n }\n extension = content_type_to_extension.get(content_type, \".bin\" if is_binary else \".txt\")\n filename = f\"{base_name}{extension}\"\n\n # Step 4: Define the full file path\n file_path = component_temp_dir / filename\n\n # Step 5: Check if file exists asynchronously and handle accordingly\n try:\n # Try to create the file exclusively (x mode) to check existence\n async with aiofiles.open(file_path, \"x\") as _:\n pass # File created successfully, we can use this path\n except FileExistsError:\n # If file exists, append a timestamp to the filename\n timestamp = datetime.now(timezone.utc).strftime(\"%Y%m%d%H%M%S%f\")\n file_path = component_temp_dir / f\"{timestamp}-{filename}\"\n\n return is_binary, file_path\n\n def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:\n \"\"\"Convert HTTP headers to a dictionary with lowercased keys.\"\"\"\n return {k.lower(): v for k, v in headers.items()}\n\n def _process_headers(self, headers: Any) -> dict:\n \"\"\"Process the headers input into a valid dictionary.\n\n Args:\n headers: The headers to process, can be dict, str, or list\n Returns:\n Processed dictionary\n \"\"\"\n if headers is None:\n return {}\n if isinstance(headers, dict):\n return headers\n if isinstance(headers, list):\n processed_headers = {}\n try:\n for item in headers:\n if not self._is_valid_key_value_item(item):\n continue\n key = item[\"key\"]\n value = item[\"value\"]\n processed_headers[key] = value\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process headers list: {e}\")\n return {} # Return empty dictionary instead of None\n return processed_headers\n return {}\n" + "value": "import asyncio\nimport json\nimport re\nimport tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nimport aiofiles\nimport aiofiles.os as aiofiles_os\nimport httpx\nimport validators\n\nfrom langflow.base.curl.parse import parse_context\nfrom langflow.custom import Component\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n StrInput,\n TableInput,\n)\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.dotdict import dotdict\n\n\nclass APIRequestComponent(Component):\n display_name = \"API Request\"\n description = \"Make HTTP requests using URLs or cURL commands.\"\n icon = \"Globe\"\n name = \"APIRequest\"\n\n default_keys = [\"urls\", \"method\", \"query_params\"]\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n list=True,\n info=\"Enter one or more URLs, separated by commas.\",\n advanced=False,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"curl\",\n display_name=\"cURL\",\n info=(\n \"Paste a curl command to populate the fields. \"\n \"This will fill in the dictionary fields for headers and body.\"\n ),\n advanced=True,\n real_time_refresh=True,\n tool_mode=True,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Method\",\n options=[\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"],\n info=\"The HTTP method to use.\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"use_curl\",\n display_name=\"Use cURL\",\n value=False,\n info=\"Enable cURL mode to populate fields from a cURL command.\",\n real_time_refresh=True,\n ),\n DataInput(\n name=\"query_params\",\n display_name=\"Query Parameters\",\n info=\"The query parameters to append to the URL.\",\n advanced=True,\n ),\n TableInput(\n name=\"body\",\n display_name=\"Body\",\n info=\"The body to send with the request as a dictionary (for POST, PATCH, PUT).\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Parameter name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"description\": \"Parameter value\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n advanced=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request as a dictionary.\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[],\n advanced=True,\n input_types=[\"Data\"],\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n value=5,\n info=\"The timeout to use for the request.\",\n advanced=True,\n ),\n BoolInput(\n name=\"follow_redirects\",\n display_name=\"Follow Redirects\",\n value=True,\n info=\"Whether to follow http redirects.\",\n advanced=True,\n ),\n BoolInput(\n name=\"save_to_file\",\n display_name=\"Save to File\",\n value=False,\n info=\"Save the API response to a temporary file\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_httpx_metadata\",\n display_name=\"Include HTTPx Metadata\",\n value=False,\n info=(\n \"Include properties such as headers, status_code, response_headers, \"\n \"and redirection_history in the output.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"make_requests\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def _parse_json_value(self, value: Any) -> Any:\n \"\"\"Parse a value that might be a JSON string.\"\"\"\n if not isinstance(value, str):\n return value\n\n try:\n parsed = json.loads(value)\n except json.JSONDecodeError:\n return value\n else:\n return parsed\n\n def _process_body(self, body: Any) -> dict:\n \"\"\"Process the body input into a valid dictionary.\n\n Args:\n body: The body to process, can be dict, str, or list\n Returns:\n Processed dictionary\n \"\"\"\n if body is None:\n return {}\n if isinstance(body, dict):\n return self._process_dict_body(body)\n if isinstance(body, str):\n return self._process_string_body(body)\n if isinstance(body, list):\n return self._process_list_body(body)\n\n return {}\n\n def _process_dict_body(self, body: dict) -> dict:\n \"\"\"Process dictionary body by parsing JSON values.\"\"\"\n return {k: self._parse_json_value(v) for k, v in body.items()}\n\n def _process_string_body(self, body: str) -> dict:\n \"\"\"Process string body by attempting JSON parse.\"\"\"\n try:\n return self._process_body(json.loads(body))\n except json.JSONDecodeError:\n return {\"data\": body}\n\n def _process_list_body(self, body: list) -> dict:\n \"\"\"Process list body by converting to key-value dictionary.\"\"\"\n processed_dict = {}\n\n try:\n for item in body:\n if not self._is_valid_key_value_item(item):\n continue\n\n key = item[\"key\"]\n value = self._parse_json_value(item[\"value\"])\n processed_dict[key] = value\n\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process body list: {e}\")\n return {} # Return empty dictionary instead of None\n\n return processed_dict\n\n def _is_valid_key_value_item(self, item: Any) -> bool:\n \"\"\"Check if an item is a valid key-value dictionary.\"\"\"\n return isinstance(item, dict) and \"key\" in item and \"value\" in item\n\n def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:\n \"\"\"Parse a cURL command and update build configuration.\n\n Args:\n curl: The cURL command to parse\n build_config: The build configuration to update\n Returns:\n Updated build configuration\n \"\"\"\n try:\n parsed = parse_context(curl)\n\n # Update basic configuration\n build_config[\"urls\"][\"value\"] = [parsed.url]\n build_config[\"method\"][\"value\"] = parsed.method.upper()\n build_config[\"headers\"][\"advanced\"] = True\n build_config[\"body\"][\"advanced\"] = True\n\n # Process headers\n headers_list = [{\"key\": k, \"value\": v} for k, v in parsed.headers.items()]\n build_config[\"headers\"][\"value\"] = headers_list\n\n if headers_list:\n build_config[\"headers\"][\"advanced\"] = False\n\n # Process body data\n if not parsed.data:\n build_config[\"body\"][\"value\"] = []\n elif parsed.data:\n try:\n json_data = json.loads(parsed.data)\n if isinstance(json_data, dict):\n body_list = [\n {\"key\": k, \"value\": json.dumps(v) if isinstance(v, dict | list) else str(v)}\n for k, v in json_data.items()\n ]\n build_config[\"body\"][\"value\"] = body_list\n build_config[\"body\"][\"advanced\"] = False\n else:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": json.dumps(json_data)}]\n build_config[\"body\"][\"advanced\"] = False\n except json.JSONDecodeError:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": parsed.data}]\n build_config[\"body\"][\"advanced\"] = False\n\n except Exception as exc:\n msg = f\"Error parsing curl: {exc}\"\n self.log(msg)\n raise ValueError(msg) from exc\n\n return build_config\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n if field_name == \"use_curl\":\n build_config = self._update_curl_mode(build_config, use_curl=field_value)\n\n # Fields that should not be reset\n preserve_fields = {\"timeout\", \"follow_redirects\", \"save_to_file\", \"include_httpx_metadata\", \"use_curl\"}\n\n # Mapping between input types and their reset values\n type_reset_mapping = {\n TableInput: [],\n BoolInput: False,\n IntInput: 0,\n FloatInput: 0.0,\n MessageTextInput: \"\",\n StrInput: \"\",\n MultilineInput: \"\",\n DropdownInput: \"GET\",\n DataInput: {},\n }\n\n for input_field in self.inputs:\n # Only reset if field is not in preserve list\n if input_field.name not in preserve_fields:\n reset_value = type_reset_mapping.get(type(input_field), None)\n build_config[input_field.name][\"value\"] = reset_value\n self.log(f\"Reset field {input_field.name} to {reset_value}\")\n elif field_name == \"method\" and not self.use_curl:\n build_config = self._update_method_fields(build_config, field_value)\n elif field_name == \"curl\" and self.use_curl and field_value:\n build_config = self.parse_curl(field_value, build_config)\n return build_config\n\n def _update_curl_mode(self, build_config: dotdict, *, use_curl: bool) -> dotdict:\n always_visible = [\"method\", \"use_curl\"]\n\n for field in self.inputs:\n field_name = field.name\n field_config = build_config.get(field_name)\n if isinstance(field_config, dict):\n if field_name in always_visible:\n field_config[\"advanced\"] = False\n elif field_name == \"urls\":\n field_config[\"advanced\"] = use_curl\n elif field_name == \"curl\":\n field_config[\"advanced\"] = not use_curl\n field_config[\"real_time_refresh\"] = use_curl\n elif field_name in {\"body\", \"headers\"}:\n field_config[\"advanced\"] = True # Always keep body and headers in advanced when use_curl is False\n else:\n field_config[\"advanced\"] = use_curl\n else:\n self.log(f\"Expected dict for build_config[{field_name}], got {type(field_config).__name__}\")\n\n if not use_curl:\n current_method = build_config.get(\"method\", {}).get(\"value\", \"GET\")\n build_config = self._update_method_fields(build_config, current_method)\n\n return build_config\n\n def _update_method_fields(self, build_config: dotdict, method: str) -> dotdict:\n common_fields = [\n \"urls\",\n \"method\",\n \"use_curl\",\n ]\n\n always_advanced_fields = [\n \"body\",\n \"headers\",\n \"timeout\",\n \"follow_redirects\",\n \"save_to_file\",\n \"include_httpx_metadata\",\n ]\n\n body_fields = [\"body\"]\n\n for field in self.inputs:\n field_name = field.name\n field_config = build_config.get(field_name)\n if isinstance(field_config, dict):\n if field_name in common_fields:\n field_config[\"advanced\"] = False\n elif field_name in body_fields:\n field_config[\"advanced\"] = method not in {\"POST\", \"PUT\", \"PATCH\"}\n elif field_name in always_advanced_fields:\n field_config[\"advanced\"] = True\n else:\n field_config[\"advanced\"] = True\n else:\n self.log(f\"Expected dict for build_config[{field_name}], got {type(field_config).__name__}\")\n\n return build_config\n\n async def make_request(\n self,\n client: httpx.AsyncClient,\n method: str,\n url: str,\n headers: dict | None = None,\n body: Any = None,\n timeout: int = 5,\n *,\n follow_redirects: bool = True,\n save_to_file: bool = False,\n include_httpx_metadata: bool = False,\n ) -> Data:\n method = method.upper()\n if method not in {\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"}:\n msg = f\"Unsupported method: {method}\"\n raise ValueError(msg)\n\n # Process body using the new helper method\n processed_body = self._process_body(body)\n redirection_history = []\n\n try:\n response = await client.request(\n method,\n url,\n headers=headers,\n json=processed_body,\n timeout=timeout,\n follow_redirects=follow_redirects,\n )\n\n redirection_history = [\n {\n \"url\": redirect.headers.get(\"Location\", str(redirect.url)),\n \"status_code\": redirect.status_code,\n }\n for redirect in response.history\n ]\n\n is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)\n response_headers = self._headers_to_dict(response.headers)\n\n metadata: dict[str, Any] = {\n \"source\": url,\n }\n\n if save_to_file:\n mode = \"wb\" if is_binary else \"w\"\n encoding = response.encoding if mode == \"w\" else None\n if file_path:\n # Ensure parent directory exists\n await aiofiles_os.makedirs(file_path.parent, exist_ok=True)\n if is_binary:\n async with aiofiles.open(file_path, \"wb\") as f:\n await f.write(response.content)\n await f.flush()\n else:\n async with aiofiles.open(file_path, \"w\", encoding=encoding) as f:\n await f.write(response.text)\n await f.flush()\n metadata[\"file_path\"] = str(file_path)\n\n if include_httpx_metadata:\n metadata.update(\n {\n \"headers\": headers,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n }\n )\n return Data(data=metadata)\n\n if is_binary:\n result = response.content\n else:\n try:\n result = response.json()\n except json.JSONDecodeError:\n self.log(\"Failed to decode JSON response\")\n result = response.text.encode(\"utf-8\")\n\n metadata.update({\"result\": result})\n\n if include_httpx_metadata:\n metadata.update(\n {\n \"headers\": headers,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n }\n )\n return Data(data=metadata)\n except httpx.TimeoutException:\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 408,\n \"error\": \"Request timed out\",\n },\n )\n except Exception as exc: # noqa: BLE001\n self.log(f\"Error making request to {url}\")\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 500,\n \"error\": str(exc),\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n },\n )\n\n def add_query_params(self, url: str, params: dict) -> str:\n url_parts = list(urlparse(url))\n query = dict(parse_qsl(url_parts[4]))\n query.update(params)\n url_parts[4] = urlencode(query)\n return urlunparse(url_parts)\n\n async def make_requests(self) -> list[Data]:\n method = self.method\n urls = [url.strip() for url in self.urls if url.strip()]\n headers = self.headers or {}\n body = self.body or {}\n timeout = self.timeout\n follow_redirects = self.follow_redirects\n save_to_file = self.save_to_file\n include_httpx_metadata = self.include_httpx_metadata\n\n if self.use_curl and self.curl:\n self._build_config = self.parse_curl(self.curl, dotdict())\n\n invalid_urls = [url for url in urls if not validators.url(url)]\n if invalid_urls:\n msg = f\"Invalid URLs provided: {invalid_urls}\"\n raise ValueError(msg)\n\n if isinstance(self.query_params, str):\n query_params = dict(parse_qsl(self.query_params))\n else:\n query_params = self.query_params.data if self.query_params else {}\n\n # Process headers here\n headers = self._process_headers(headers)\n\n # Process body\n body = self._process_body(body)\n\n bodies = [body] * len(urls)\n\n urls = [self.add_query_params(url, query_params) for url in urls]\n\n async with httpx.AsyncClient() as client:\n results = await asyncio.gather(\n *[\n self.make_request(\n client,\n method,\n u,\n headers,\n rec,\n timeout,\n follow_redirects=follow_redirects,\n save_to_file=save_to_file,\n include_httpx_metadata=include_httpx_metadata,\n )\n for u, rec in zip(urls, bodies, strict=False)\n ]\n )\n self.status = results\n return results\n\n async def _response_info(\n self, response: httpx.Response, *, with_file_path: bool = False\n ) -> tuple[bool, Path | None]:\n \"\"\"Determine the file path and whether the response content is binary.\n\n Args:\n response (Response): The HTTP response object.\n with_file_path (bool): Whether to save the response content to a file.\n\n Returns:\n Tuple[bool, Path | None]:\n A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).\n \"\"\"\n content_type = response.headers.get(\"Content-Type\", \"\")\n is_binary = \"application/octet-stream\" in content_type or \"application/binary\" in content_type\n\n if not with_file_path:\n return is_binary, None\n\n component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__\n\n # Create directory asynchronously\n await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)\n\n filename = None\n if \"Content-Disposition\" in response.headers:\n content_disposition = response.headers[\"Content-Disposition\"]\n filename_match = re.search(r'filename=\"(.+?)\"', content_disposition)\n if filename_match:\n extracted_filename = filename_match.group(1)\n filename = extracted_filename\n\n # Step 3: Infer file extension or use part of the request URL if no filename\n if not filename:\n # Extract the last segment of the URL path\n url_path = urlparse(str(response.request.url) if response.request else \"\").path\n base_name = Path(url_path).name # Get the last segment of the path\n if not base_name: # If the path ends with a slash or is empty\n base_name = \"response\"\n\n # Infer file extension\n content_type_to_extension = {\n \"text/plain\": \".txt\",\n \"application/json\": \".json\",\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"application/octet-stream\": \".bin\",\n }\n extension = content_type_to_extension.get(content_type, \".bin\" if is_binary else \".txt\")\n filename = f\"{base_name}{extension}\"\n\n # Step 4: Define the full file path\n file_path = component_temp_dir / filename\n\n # Step 5: Check if file exists asynchronously and handle accordingly\n try:\n # Try to create the file exclusively (x mode) to check existence\n async with aiofiles.open(file_path, \"x\") as _:\n pass # File created successfully, we can use this path\n except FileExistsError:\n # If file exists, append a timestamp to the filename\n timestamp = datetime.now(timezone.utc).strftime(\"%Y%m%d%H%M%S%f\")\n file_path = component_temp_dir / f\"{timestamp}-{filename}\"\n\n return is_binary, file_path\n\n def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:\n \"\"\"Convert HTTP headers to a dictionary with lowercased keys.\"\"\"\n return {k.lower(): v for k, v in headers.items()}\n\n def _process_headers(self, headers: Any) -> dict:\n \"\"\"Process the headers input into a valid dictionary.\n\n Args:\n headers: The headers to process, can be dict, str, or list\n Returns:\n Processed dictionary\n \"\"\"\n if headers is None:\n return {}\n if isinstance(headers, dict):\n return headers\n if isinstance(headers, list):\n processed_headers = {}\n try:\n for item in headers:\n if not self._is_valid_key_value_item(item):\n continue\n key = item[\"key\"]\n value = item[\"value\"]\n processed_headers[key] = value\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process headers list: {e}\")\n return {} # Return empty dictionary instead of None\n return processed_headers\n return {}\n\n async def as_dataframe(self) -> DataFrame:\n \"\"\"Convert the API response data into a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the API response data.\n \"\"\"\n data = await self.make_requests()\n return DataFrame(data)\n" }, "curl": { "_input_type": "MultilineInput", @@ -519,6 +521,14 @@ "tags": [ "APIRequest-make_requests" ] + }, + { + "description": "as_dataframe() - Make HTTP requests using URLs or cURL commands.", + "name": "APIRequest-as_dataframe", + "status": true, + "tags": [ + "APIRequest-as_dataframe" + ] } ] }, @@ -573,7 +583,7 @@ "type": "APIRequest" }, "dragging": false, - "id": "APIRequest-ooZcW", + "id": "APIRequest-KJLNf", "measured": { "height": 523, "width": 360 @@ -582,12 +592,12 @@ "x": 99.03855391505124, "y": -381.36759080809065 }, - "selected": false, + "selected": true, "type": "genericNode" }, { "data": { - "id": "ChatInput-aFhoH", + "id": "ChatInput-eumKo", "node": { "base_classes": [ "Message" @@ -884,7 +894,7 @@ "type": "ChatInput" }, "dragging": false, - "id": "ChatInput-aFhoH", + "id": "ChatInput-eumKo", "measured": { "height": 74, "width": 216 @@ -898,7 +908,7 @@ }, { "data": { - "id": "ChatOutput-WuOt0", + "id": "ChatOutput-D4JWF", "node": { "base_classes": [ "Message" @@ -1195,7 +1205,7 @@ "showNode": false, "type": "ChatOutput" }, - "id": "ChatOutput-WuOt0", + "id": "ChatOutput-D4JWF", "measured": { "height": 74, "width": 216 @@ -1209,7 +1219,7 @@ }, { "data": { - "id": "note-2G3L2", + "id": "note-nfy3d", "node": { "description": "## Open the playground and ask anything about a Pokémon! ⚡ 🐹", "display_name": "", @@ -1222,7 +1232,7 @@ }, "dragging": false, "height": 324, - "id": "note-2G3L2", + "id": "note-nfy3d", "measured": { "height": 324, "width": 393 @@ -1238,7 +1248,7 @@ }, { "data": { - "id": "note-Vwu4L", + "id": "note-JAywo", "node": { "description": "# Pokédex Agent\n\nCollect research on Pokémon with a specialized **Agent** and the Pokédex API.\n\n## Prerequisites\n\n* An [OpenAI API key](https://platform.openai.com/)\n\n## Quickstart\n\n1. Paste your OpenAI API key in the **Agent** component.\n2. Click **Playground** and ask about your favorite Pokémon.\nThe **Agent** queries the Pokedex API and returns a formatted entry.", "display_name": "", @@ -1249,7 +1259,7 @@ }, "dragging": false, "height": 543, - "id": "note-Vwu4L", + "id": "note-JAywo", "measured": { "height": 543, "width": 352 @@ -1265,7 +1275,7 @@ }, { "data": { - "id": "note-kgDfT", + "id": "note-xMIW2", "node": { "description": "### 💡 Add your OpenAI API key here", "display_name": "", @@ -1278,7 +1288,7 @@ }, "dragging": false, "height": 324, - "id": "note-kgDfT", + "id": "note-xMIW2", "measured": { "height": 324, "width": 337 @@ -1294,7 +1304,7 @@ }, { "data": { - "id": "Agent-6NKOE", + "id": "Agent-gZkrx", "node": { "base_classes": [ "Message" @@ -1473,7 +1483,7 @@ "input_types": [ "Message" ], - "load_from_db": false, + "load_from_db": true, "name": "api_key", "password": true, "placeholder": "", @@ -1481,7 +1491,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "OPENAI_API_KEY" }, "code": { "advanced": true, @@ -1975,7 +1985,7 @@ "showNode": true, "type": "Agent" }, - "id": "Agent-6NKOE", + "id": "Agent-gZkrx", "measured": { "height": 698, "width": 360 diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json index cec6df532..255acfea4 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json @@ -7,7 +7,7 @@ "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-T6lKn", + "id": "Prompt-ODLKa", "name": "prompt", "output_types": [ "Message" @@ -15,19 +15,19 @@ }, "targetHandle": { "fieldName": "system_prompt", - "id": "Agent-RLteT", + "id": "Agent-RDNeT", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Prompt-T6lKn{œdataTypeœ:œPromptœ,œidœ:œPrompt-T6lKnœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-RLteT{œfieldNameœ:œsystem_promptœ,œidœ:œAgent-RLteTœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Prompt-ODLKa{œdataTypeœ:œPromptœ,œidœ:œPrompt-ODLKaœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-RDNeT{œfieldNameœ:œsystem_promptœ,œidœ:œAgent-RDNeTœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Prompt-T6lKn", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-T6lKnœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-RLteT", - "targetHandle": "{œfieldNameœ: œsystem_promptœ, œidœ: œAgent-RLteTœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Prompt-ODLKa", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-ODLKaœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-RDNeT", + "targetHandle": "{œfieldNameœ: œsystem_promptœ, œidœ: œAgent-RDNeTœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -35,7 +35,7 @@ "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-CiSeG", + "id": "Prompt-Gwn6h", "name": "prompt", "output_types": [ "Message" @@ -43,19 +43,19 @@ }, "targetHandle": { "fieldName": "system_prompt", - "id": "Agent-IOYVB", + "id": "Agent-uOZfx", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Prompt-CiSeG{œdataTypeœ:œPromptœ,œidœ:œPrompt-CiSeGœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-IOYVB{œfieldNameœ:œsystem_promptœ,œidœ:œAgent-IOYVBœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Prompt-Gwn6h{œdataTypeœ:œPromptœ,œidœ:œPrompt-Gwn6hœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-uOZfx{œfieldNameœ:œsystem_promptœ,œidœ:œAgent-uOZfxœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Prompt-CiSeG", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-CiSeGœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-IOYVB", - "targetHandle": "{œfieldNameœ: œsystem_promptœ, œidœ: œAgent-IOYVBœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Prompt-Gwn6h", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-Gwn6hœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-uOZfx", + "targetHandle": "{œfieldNameœ: œsystem_promptœ, œidœ: œAgent-uOZfxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -63,7 +63,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-IOYVB", + "id": "Agent-uOZfx", "name": "response", "output_types": [ "Message" @@ -71,7 +71,7 @@ }, "targetHandle": { "fieldName": "finance_agent_output", - "id": "Prompt-T6lKn", + "id": "Prompt-ODLKa", "inputTypes": [ "Message", "Text" @@ -79,12 +79,12 @@ "type": "str" } }, - "id": "reactflow__edge-Agent-IOYVB{œdataTypeœ:œAgentœ,œidœ:œAgent-IOYVBœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-T6lKn{œfieldNameœ:œfinance_agent_outputœ,œidœ:œPrompt-T6lKnœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Agent-uOZfx{œdataTypeœ:œAgentœ,œidœ:œAgent-uOZfxœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-ODLKa{œfieldNameœ:œfinance_agent_outputœ,œidœ:œPrompt-ODLKaœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", "selected": false, - "source": "Agent-IOYVB", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-IOYVBœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-T6lKn", - "targetHandle": "{œfieldNameœ: œfinance_agent_outputœ, œidœ: œPrompt-T6lKnœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "source": "Agent-uOZfx", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-uOZfxœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-ODLKa", + "targetHandle": "{œfieldNameœ: œfinance_agent_outputœ, œidœ: œPrompt-ODLKaœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -92,7 +92,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-GHRcw", + "id": "ChatInput-3FGZe", "name": "message", "output_types": [ "Message" @@ -100,19 +100,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-ChatInput-GHRcw{œdataTypeœ:œChatInputœ,œidœ:œChatInput-GHRcwœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-yrZkV{œfieldNameœ:œinput_valueœ,œidœ:œAgent-yrZkVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ChatInput-3FGZe{œdataTypeœ:œChatInputœ,œidœ:œChatInput-3FGZeœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-E8NYA{œfieldNameœ:œinput_valueœ,œidœ:œAgent-E8NYAœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ChatInput-GHRcw", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-GHRcwœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-yrZkV", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-yrZkVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ChatInput-3FGZe", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-3FGZeœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-E8NYA", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-E8NYAœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -120,7 +120,7 @@ "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-8FZo4", + "id": "Prompt-RIFxC", "name": "prompt", "output_types": [ "Message" @@ -128,19 +128,19 @@ }, "targetHandle": { "fieldName": "system_prompt", - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Prompt-8FZo4{œdataTypeœ:œPromptœ,œidœ:œPrompt-8FZo4œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-yrZkV{œfieldNameœ:œsystem_promptœ,œidœ:œAgent-yrZkVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Prompt-RIFxC{œdataTypeœ:œPromptœ,œidœ:œPrompt-RIFxCœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-E8NYA{œfieldNameœ:œsystem_promptœ,œidœ:œAgent-E8NYAœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Prompt-8FZo4", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-8FZo4œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-yrZkV", - "targetHandle": "{œfieldNameœ: œsystem_promptœ, œidœ: œAgent-yrZkVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Prompt-RIFxC", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-RIFxCœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-E8NYA", + "targetHandle": "{œfieldNameœ: œsystem_promptœ, œidœ: œAgent-E8NYAœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -148,7 +148,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "name": "response", "output_types": [ "Message" @@ -156,19 +156,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-IOYVB", + "id": "Agent-uOZfx", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Agent-yrZkV{œdataTypeœ:œAgentœ,œidœ:œAgent-yrZkVœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Agent-IOYVB{œfieldNameœ:œinput_valueœ,œidœ:œAgent-IOYVBœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Agent-E8NYA{œdataTypeœ:œAgentœ,œidœ:œAgent-E8NYAœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Agent-uOZfx{œfieldNameœ:œinput_valueœ,œidœ:œAgent-uOZfxœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Agent-yrZkV", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-yrZkVœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-IOYVB", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-IOYVBœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Agent-E8NYA", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-E8NYAœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-uOZfx", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-uOZfxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -176,7 +176,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "name": "response", "output_types": [ "Message" @@ -184,7 +184,7 @@ }, "targetHandle": { "fieldName": "research_agent_output", - "id": "Prompt-T6lKn", + "id": "Prompt-ODLKa", "inputTypes": [ "Message", "Text" @@ -192,12 +192,12 @@ "type": "str" } }, - "id": "reactflow__edge-Agent-yrZkV{œdataTypeœ:œAgentœ,œidœ:œAgent-yrZkVœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-T6lKn{œfieldNameœ:œresearch_agent_outputœ,œidœ:œPrompt-T6lKnœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Agent-E8NYA{œdataTypeœ:œAgentœ,œidœ:œAgent-E8NYAœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-ODLKa{œfieldNameœ:œresearch_agent_outputœ,œidœ:œPrompt-ODLKaœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", "selected": false, - "source": "Agent-yrZkV", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-yrZkVœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-T6lKn", - "targetHandle": "{œfieldNameœ: œresearch_agent_outputœ, œidœ: œPrompt-T6lKnœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "source": "Agent-E8NYA", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-E8NYAœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-ODLKa", + "targetHandle": "{œfieldNameœ: œresearch_agent_outputœ, œidœ: œPrompt-ODLKaœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -205,7 +205,7 @@ "data": { "sourceHandle": { "dataType": "CalculatorComponent", - "id": "CalculatorComponent-X0H1Z", + "id": "CalculatorComponent-qoEVQ", "name": "component_as_tool", "output_types": [ "Tool" @@ -213,19 +213,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-RLteT", + "id": "Agent-RDNeT", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-CalculatorComponent-X0H1Z{œdataTypeœ:œCalculatorComponentœ,œidœ:œCalculatorComponent-X0H1Zœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-RLteT{œfieldNameœ:œtoolsœ,œidœ:œAgent-RLteTœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-CalculatorComponent-qoEVQ{œdataTypeœ:œCalculatorComponentœ,œidœ:œCalculatorComponent-qoEVQœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-RDNeT{œfieldNameœ:œtoolsœ,œidœ:œAgent-RDNeTœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", "selected": false, - "source": "CalculatorComponent-X0H1Z", - "sourceHandle": "{œdataTypeœ: œCalculatorComponentœ, œidœ: œCalculatorComponent-X0H1Zœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-RLteT", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-RLteTœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "source": "CalculatorComponent-qoEVQ", + "sourceHandle": "{œdataTypeœ: œCalculatorComponentœ, œidœ: œCalculatorComponent-qoEVQœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-RDNeT", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-RDNeTœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -233,7 +233,7 @@ "data": { "sourceHandle": { "dataType": "YfinanceComponent", - "id": "YfinanceComponent-ZvUwB", + "id": "YfinanceComponent-LMwvY", "name": "component_as_tool", "output_types": [ "Tool" @@ -241,19 +241,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-IOYVB", + "id": "Agent-uOZfx", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-YfinanceComponent-ZvUwB{œdataTypeœ:œYfinanceComponentœ,œidœ:œYfinanceComponent-ZvUwBœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-IOYVB{œfieldNameœ:œtoolsœ,œidœ:œAgent-IOYVBœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-YfinanceComponent-LMwvY{œdataTypeœ:œYfinanceComponentœ,œidœ:œYfinanceComponent-LMwvYœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-uOZfx{œfieldNameœ:œtoolsœ,œidœ:œAgent-uOZfxœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", "selected": false, - "source": "YfinanceComponent-ZvUwB", - "sourceHandle": "{œdataTypeœ: œYfinanceComponentœ, œidœ: œYfinanceComponent-ZvUwBœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-IOYVB", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-IOYVBœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "source": "YfinanceComponent-LMwvY", + "sourceHandle": "{œdataTypeœ: œYfinanceComponentœ, œidœ: œYfinanceComponent-LMwvYœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-uOZfx", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-uOZfxœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -261,7 +261,7 @@ "data": { "sourceHandle": { "dataType": "TavilySearchComponent", - "id": "TavilySearchComponent-47Sep", + "id": "TavilySearchComponent-GOWJw", "name": "component_as_tool", "output_types": [ "Tool" @@ -269,19 +269,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-TavilySearchComponent-47Sep{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-47Sepœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-yrZkV{œfieldNameœ:œtoolsœ,œidœ:œAgent-yrZkVœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-TavilySearchComponent-GOWJw{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-GOWJwœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-E8NYA{œfieldNameœ:œtoolsœ,œidœ:œAgent-E8NYAœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", "selected": false, - "source": "TavilySearchComponent-47Sep", - "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-47Sepœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-yrZkV", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-yrZkVœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "source": "TavilySearchComponent-GOWJw", + "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-GOWJwœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-E8NYA", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-E8NYAœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -289,7 +289,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-RLteT", + "id": "Agent-RDNeT", "name": "response", "output_types": [ "Message" @@ -297,7 +297,7 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-8B8h5", + "id": "ChatOutput-MCPSV", "inputTypes": [ "Data", "DataFrame", @@ -306,12 +306,12 @@ "type": "other" } }, - "id": "reactflow__edge-Agent-RLteT{œdataTypeœ:œAgentœ,œidœ:œAgent-RLteTœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-8B8h5{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-8B8h5œ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-Agent-RDNeT{œdataTypeœ:œAgentœ,œidœ:œAgent-RDNeTœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-MCPSV{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-MCPSVœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œotherœ}", "selected": false, - "source": "Agent-RLteT", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-RLteTœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-8B8h5", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-8B8h5œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œotherœ}" + "source": "Agent-RDNeT", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-RDNeTœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-MCPSV", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-MCPSVœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œotherœ}" } ], "nodes": [ @@ -319,7 +319,7 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Finance Agent", - "id": "Agent-IOYVB", + "id": "Agent-uOZfx", "node": { "base_classes": [ "Message" @@ -918,10 +918,10 @@ }, "dragging": false, "height": 650, - "id": "Agent-IOYVB", + "id": "Agent-uOZfx", "measured": { "height": 650, - "width": 320 + "width": 360 }, "position": { "x": 45.70736046026991, @@ -939,7 +939,7 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Analysis & Editor Agent", - "id": "Agent-RLteT", + "id": "Agent-RDNeT", "node": { "base_classes": [ "Message" @@ -1538,10 +1538,10 @@ }, "dragging": false, "height": 650, - "id": "Agent-RLteT", + "id": "Agent-RDNeT", "measured": { "height": 650, - "width": 320 + "width": 360 }, "position": { "x": 815.1900903820148, @@ -1559,7 +1559,7 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-8FZo4", + "id": "Prompt-RIFxC", "node": { "base_classes": [ "Message" @@ -1671,10 +1671,10 @@ }, "dragging": false, "height": 260, - "id": "Prompt-8FZo4", + "id": "Prompt-RIFxC", "measured": { "height": 260, - "width": 320 + "width": 360 }, "position": { "x": -1142.2312935529987, @@ -1692,7 +1692,7 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-CiSeG", + "id": "Prompt-Gwn6h", "node": { "base_classes": [ "Message" @@ -1804,10 +1804,10 @@ }, "dragging": false, "height": 260, - "id": "Prompt-CiSeG", + "id": "Prompt-Gwn6h", "measured": { "height": 260, - "width": 320 + "width": 360 }, "position": { "x": -344.9674638932195, @@ -1825,7 +1825,7 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-T6lKn", + "id": "Prompt-ODLKa", "node": { "base_classes": [ "Message" @@ -1986,10 +1986,10 @@ }, "dragging": false, "height": 433, - "id": "Prompt-T6lKn", + "id": "Prompt-ODLKa", "measured": { "height": 433, - "width": 320 + "width": 360 }, "position": { "x": 416.02309796632085, @@ -2005,7 +2005,7 @@ }, { "data": { - "id": "ChatInput-GHRcw", + "id": "ChatInput-3FGZe", "node": { "base_classes": [ "Message" @@ -2287,10 +2287,10 @@ }, "dragging": false, "height": 234, - "id": "ChatInput-GHRcw", + "id": "ChatInput-3FGZe", "measured": { "height": 234, - "width": 320 + "width": 360 }, "position": { "x": -1510.6054210793818, @@ -2306,7 +2306,7 @@ }, { "data": { - "id": "note-HegN0", + "id": "note-QxoYR", "node": { "description": "# Sequential Tasks Agents\n\n## Overview\nThis flow demonstrates how to chain multiple AI agents for comprehensive research and analysis. Each agent specializes in different aspects of the research process, building upon the previous agent's work.\n\n## How to Use the Flow\n\n1. **Input Your Query** 🎯\n - Be specific and clear\n - Include key aspects you want analyzed\n - Examples:\n ```\n Good: \"Should I invest in Tesla (TSLA)? Focus on AI development impact\"\n Bad: \"Tell me about Tesla\"\n ```\n\n2. **Research Agent Process** 🔍\n - Utilizes Tavily Search for comprehensive research\n\n\n3. **Specialized Analysis** 📊\n - Each agent adds unique value:\n ```\n Research Agent → Deep Research & Context\n ↓\n Finance Agent → Data Analysis & Metrics\n ↓\n Editor Agent → Final Synthesis & Report\n ```\n\n4. **Output Format** 📝\n - Structured report\n - Embedded images and charts\n - Data-backed insights\n - Clear recommendations\n\n## Pro Tips\n\n### Query Construction\n- Include specific points of interest\n- Mention required metrics or data points\n- Specify time frames if relevant\n\n### Flow Customization\n- Modify agent prompts for different use cases\n- Add or remove tools as needed\n\n## Common Applications\n- Investment Research\n- Market Analysis\n- Competitive Intelligence\n- Industry Reports\n- Technology Impact Studies\n\n⚡ **Best Practice**: Start with a test query to understand the flow's capabilities before running complex analyses.\n\n---\n*Note: This flow template uses financial analysis as an example but can be adapted for any research-intensive task requiring multiple perspectives and data sources.*", "display_name": "", @@ -2317,10 +2317,10 @@ }, "dragging": false, "height": 800, - "id": "note-HegN0", + "id": "note-QxoYR", "measured": { "height": 800, - "width": 601 + "width": 604 }, "position": { "x": -2122.739127560837, @@ -2331,7 +2331,7 @@ "y": -1302.6582482086806 }, "resizing": false, - "selected": true, + "selected": false, "style": { "height": 800, "width": 600 @@ -2341,7 +2341,7 @@ }, { "data": { - "id": "note-K5y7T", + "id": "note-ZtXL3", "node": { "description": "## What Are Sequential Task Agents?\nA system where multiple AI agents work in sequence, each specializing in specific tasks and passing their output to the next agent in the chain. Think of it as an assembly line where each agent adds value to the final result.\n\n## How It Works\n1. **First Agent** → **Second Agent** → **Third Agent** → **Final Output**\n - Each agent receives input from the previous one\n - Processes and enhances the information\n - Passes refined output forward\n\n## Key Benefits\n- **Specialization**: Each agent focuses on specific tasks\n- **Progressive Refinement**: Information gets enhanced at each step\n- **Structured Output**: Final result combines multiple perspectives\n- **Quality Control**: Each agent validates and improves previous work\n\n## Building Your Own Sequence\n1. **Plan Your Chain**\n - Identify distinct tasks\n - Determine logical order\n - Define input/output requirements\n\n2. **Configure Agents**\n - Give each agent clear instructions\n - Ensure compatible outputs/inputs\n - Set appropriate tools for each agent\n\n3. **Connect the Flow**\n - Link agents in proper order\n - Test data flow between agents\n - Verify final output format\n\n## Example Applications\n- Research → Analysis → Report Writing\n- Data Collection → Processing → Visualization\n- Content Research → Writing → Editing\n- Market Analysis → Financial Review → Investment Advice\n\n⭐ **Pro Tip**: The strength of sequential agents comes from how well they complement each other's capabilities.\n\nThis template uses financial analysis as an example, but you can adapt it for any multi-step process requiring different expertise at each stage.", "display_name": "", @@ -2354,10 +2354,10 @@ }, "dragging": false, "height": 800, - "id": "note-K5y7T", + "id": "note-ZtXL3", "measured": { "height": 800, - "width": 601 + "width": 604 }, "position": { "x": -1423.4595108457968, @@ -2380,7 +2380,7 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Researcher Agent", - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "node": { "base_classes": [ "Message" @@ -2979,10 +2979,10 @@ }, "dragging": false, "height": 650, - "id": "Agent-yrZkV", + "id": "Agent-E8NYA", "measured": { "height": 650, - "width": 320 + "width": 360 }, "position": { "x": -715.1798010873374, @@ -2998,7 +2998,7 @@ }, { "data": { - "id": "note-66UOh", + "id": "note-n7ptl", "node": { "description": "## Get your API key at [https://tavily.com](https://tavily.com)\n", "display_name": "", @@ -3011,10 +3011,10 @@ }, "dragging": false, "height": 324, - "id": "note-66UOh", + "id": "note-n7ptl", "measured": { "height": 324, - "width": 348 + "width": 351 }, "position": { "x": -1144.3898055225054, @@ -3035,7 +3035,7 @@ }, { "data": { - "id": "note-4EW6x", + "id": "note-FhYcn", "node": { "description": "## Configure the agent by obtaining your OpenAI API key from [platform.openai.com](https://platform.openai.com). Under \"Model Provider\", choose:\n- OpenAI: Default, requires only API key\n- Anthropic/Azure/Groq/NVIDIA/SambaNova: Each requires their own API keys\n- Custom: Use your own model endpoint + authentication\n\nSelect model and input API key before running the flow.", "display_name": "", @@ -3048,10 +3048,10 @@ }, "dragging": false, "height": 324, - "id": "note-4EW6x", + "id": "note-FhYcn", "measured": { "height": 324, - "width": 371 + "width": 374 }, "position": { "x": -736.720927923848, @@ -3072,14 +3072,16 @@ }, { "data": { - "id": "YfinanceComponent-ZvUwB", + "description": "Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", + "display_name": "Yahoo Finance", + "id": "YfinanceComponent-LMwvY", "node": { "base_classes": [ "Data", + "DataFrame", "Message" ], "beta": false, - "category": "tools", "conditional_paths": [], "custom_fields": {}, "description": "Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", @@ -3093,20 +3095,22 @@ ], "frozen": false, "icon": "trending-up", - "key": "YfinanceComponent", "legacy": false, "metadata": {}, "minimized": false, "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Toolset", "hidden": null, "method": "to_toolkit", "name": "component_as_tool", + "options": null, "required_inputs": null, "selected": "Tool", + "tool_mode": true, "types": [ "Tool" ], @@ -3114,7 +3118,6 @@ } ], "pinned": false, - "score": 0.007568328950209746, "template": { "_type": "Component", "code": { @@ -3133,12 +3136,13 @@ "show": true, "title_case": false, "type": "code", - "value": "import ast\nimport pprint\nfrom enum import Enum\n\nimport yfinance as yf\nfrom langchain_core.tools import ToolException\nfrom loguru import logger\nfrom pydantic import BaseModel, Field\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DropdownInput, IntInput, MessageTextInput\nfrom langflow.io import Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass YahooFinanceMethod(Enum):\n GET_INFO = \"get_info\"\n GET_NEWS = \"get_news\"\n GET_ACTIONS = \"get_actions\"\n GET_ANALYSIS = \"get_analysis\"\n GET_BALANCE_SHEET = \"get_balance_sheet\"\n GET_CALENDAR = \"get_calendar\"\n GET_CASHFLOW = \"get_cashflow\"\n GET_INSTITUTIONAL_HOLDERS = \"get_institutional_holders\"\n GET_RECOMMENDATIONS = \"get_recommendations\"\n GET_SUSTAINABILITY = \"get_sustainability\"\n GET_MAJOR_HOLDERS = \"get_major_holders\"\n GET_MUTUALFUND_HOLDERS = \"get_mutualfund_holders\"\n GET_INSIDER_PURCHASES = \"get_insider_purchases\"\n GET_INSIDER_TRANSACTIONS = \"get_insider_transactions\"\n GET_INSIDER_ROSTER_HOLDERS = \"get_insider_roster_holders\"\n GET_DIVIDENDS = \"get_dividends\"\n GET_CAPITAL_GAINS = \"get_capital_gains\"\n GET_SPLITS = \"get_splits\"\n GET_SHARES = \"get_shares\"\n GET_FAST_INFO = \"get_fast_info\"\n GET_SEC_FILINGS = \"get_sec_filings\"\n GET_RECOMMENDATIONS_SUMMARY = \"get_recommendations_summary\"\n GET_UPGRADES_DOWNGRADES = \"get_upgrades_downgrades\"\n GET_EARNINGS = \"get_earnings\"\n GET_INCOME_STMT = \"get_income_stmt\"\n\n\nclass YahooFinanceSchema(BaseModel):\n symbol: str = Field(..., description=\"The stock symbol to retrieve data for.\")\n method: YahooFinanceMethod = Field(YahooFinanceMethod.GET_INFO, description=\"The type of data to retrieve.\")\n num_news: int | None = Field(5, description=\"The number of news articles to retrieve.\")\n\n\nclass YfinanceComponent(Component):\n display_name = \"Yahoo Finance\"\n description = \"\"\"Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) \\\nto access financial data and market information from Yahoo Finance.\"\"\"\n icon = \"trending-up\"\n\n inputs = [\n MessageTextInput(\n name=\"symbol\",\n display_name=\"Stock Symbol\",\n info=\"The stock symbol to retrieve data for (e.g., AAPL, GOOG).\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Data Method\",\n info=\"The type of data to retrieve.\",\n options=list(YahooFinanceMethod),\n value=\"get_news\",\n ),\n IntInput(\n name=\"num_news\",\n display_name=\"Number of News\",\n info=\"The number of news articles to retrieve (only applicable for get_news).\",\n value=5,\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 ]\n\n def run_model(self) -> list[Data]:\n return self.fetch_content()\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = \"\"\n for item in data:\n result_string += item.text + \"\\n\"\n self.status = result_string\n return Message(text=result_string)\n\n def _fetch_yfinance_data(self, ticker: yf.Ticker, method: YahooFinanceMethod, num_news: int | None) -> str:\n try:\n if method == YahooFinanceMethod.GET_INFO:\n result = ticker.info\n elif method == YahooFinanceMethod.GET_NEWS:\n result = ticker.news[:num_news]\n else:\n result = getattr(ticker, method.value)()\n return pprint.pformat(result)\n except Exception as e:\n error_message = f\"Error retrieving data: {e}\"\n logger.debug(error_message)\n self.status = error_message\n raise ToolException(error_message) from e\n\n def fetch_content(self) -> list[Data]:\n try:\n return self._yahoo_finance_tool(\n self.symbol,\n YahooFinanceMethod(self.method),\n self.num_news,\n )\n except ToolException:\n raise\n except Exception as e:\n error_message = f\"Unexpected error: {e}\"\n logger.debug(error_message)\n self.status = error_message\n raise ToolException(error_message) from e\n\n def _yahoo_finance_tool(\n self,\n symbol: str,\n method: YahooFinanceMethod,\n num_news: int | None = 5,\n ) -> list[Data]:\n ticker = yf.Ticker(symbol)\n result = self._fetch_yfinance_data(ticker, method, num_news)\n\n if method == YahooFinanceMethod.GET_NEWS:\n data_list = [\n Data(text=f\"{article['title']}: {article['link']}\", data=article)\n for article in ast.literal_eval(result)\n ]\n else:\n data_list = [Data(text=result, data={\"result\": result})]\n\n return data_list\n" + "value": "import ast\nimport pprint\nfrom enum import Enum\n\nimport yfinance as yf\nfrom langchain_core.tools import ToolException\nfrom loguru import logger\nfrom pydantic import BaseModel, Field\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DropdownInput, IntInput, MessageTextInput\nfrom langflow.io import Output\nfrom langflow.schema import Data, DataFrame\nfrom langflow.schema.message import Message\n\n\nclass YahooFinanceMethod(Enum):\n GET_INFO = \"get_info\"\n GET_NEWS = \"get_news\"\n GET_ACTIONS = \"get_actions\"\n GET_ANALYSIS = \"get_analysis\"\n GET_BALANCE_SHEET = \"get_balance_sheet\"\n GET_CALENDAR = \"get_calendar\"\n GET_CASHFLOW = \"get_cashflow\"\n GET_INSTITUTIONAL_HOLDERS = \"get_institutional_holders\"\n GET_RECOMMENDATIONS = \"get_recommendations\"\n GET_SUSTAINABILITY = \"get_sustainability\"\n GET_MAJOR_HOLDERS = \"get_major_holders\"\n GET_MUTUALFUND_HOLDERS = \"get_mutualfund_holders\"\n GET_INSIDER_PURCHASES = \"get_insider_purchases\"\n GET_INSIDER_TRANSACTIONS = \"get_insider_transactions\"\n GET_INSIDER_ROSTER_HOLDERS = \"get_insider_roster_holders\"\n GET_DIVIDENDS = \"get_dividends\"\n GET_CAPITAL_GAINS = \"get_capital_gains\"\n GET_SPLITS = \"get_splits\"\n GET_SHARES = \"get_shares\"\n GET_FAST_INFO = \"get_fast_info\"\n GET_SEC_FILINGS = \"get_sec_filings\"\n GET_RECOMMENDATIONS_SUMMARY = \"get_recommendations_summary\"\n GET_UPGRADES_DOWNGRADES = \"get_upgrades_downgrades\"\n GET_EARNINGS = \"get_earnings\"\n GET_INCOME_STMT = \"get_income_stmt\"\n\n\nclass YahooFinanceSchema(BaseModel):\n symbol: str = Field(..., description=\"The stock symbol to retrieve data for.\")\n method: YahooFinanceMethod = Field(YahooFinanceMethod.GET_INFO, description=\"The type of data to retrieve.\")\n num_news: int | None = Field(5, description=\"The number of news articles to retrieve.\")\n\n\nclass YfinanceComponent(Component):\n display_name = \"Yahoo Finance\"\n description = \"\"\"Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) \\\nto access financial data and market information from Yahoo Finance.\"\"\"\n icon = \"trending-up\"\n\n inputs = [\n MessageTextInput(\n name=\"symbol\",\n display_name=\"Stock Symbol\",\n info=\"The stock symbol to retrieve data for (e.g., AAPL, GOOG).\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Data Method\",\n info=\"The type of data to retrieve.\",\n options=list(YahooFinanceMethod),\n value=\"get_news\",\n ),\n IntInput(\n name=\"num_news\",\n display_name=\"Number of News\",\n info=\"The number of news articles to retrieve (only applicable for get_news).\",\n value=5,\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 def run_model(self) -> list[Data]:\n return self.fetch_content()\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = \"\"\n for item in data:\n result_string += item.text + \"\\n\"\n self.status = result_string\n return Message(text=result_string)\n\n def _fetch_yfinance_data(self, ticker: yf.Ticker, method: YahooFinanceMethod, num_news: int | None) -> str:\n try:\n if method == YahooFinanceMethod.GET_INFO:\n result = ticker.info\n elif method == YahooFinanceMethod.GET_NEWS:\n result = ticker.news[:num_news]\n else:\n result = getattr(ticker, method.value)()\n return pprint.pformat(result)\n except Exception as e:\n error_message = f\"Error retrieving data: {e}\"\n logger.debug(error_message)\n self.status = error_message\n raise ToolException(error_message) from e\n\n def fetch_content(self) -> list[Data]:\n try:\n return self._yahoo_finance_tool(\n self.symbol,\n YahooFinanceMethod(self.method),\n self.num_news,\n )\n except ToolException:\n raise\n except Exception as e:\n error_message = f\"Unexpected error: {e}\"\n logger.debug(error_message)\n self.status = error_message\n raise ToolException(error_message) from e\n\n def _yahoo_finance_tool(\n self,\n symbol: str,\n method: YahooFinanceMethod,\n num_news: int | None = 5,\n ) -> list[Data]:\n ticker = yf.Ticker(symbol)\n result = self._fetch_yfinance_data(ticker, method, num_news)\n\n if method == YahooFinanceMethod.GET_NEWS:\n data_list = [\n Data(text=f\"{article['title']}: {article['link']}\", data=article)\n for article in ast.literal_eval(result)\n ]\n else:\n data_list = [Data(text=result, data={\"result\": result})]\n\n return data_list\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Convert the Yahoo search results to a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the search results.\n \"\"\"\n data = self.fetch_content()\n return DataFrame(data)\n" }, "method": { "_input_type": "DropdownInput", "advanced": false, "combobox": false, + "dialog_inputs": {}, "display_name": "Data Method", "dynamic": false, "info": "The type of data to retrieve.", @@ -3170,6 +3174,7 @@ "get_earnings", "get_income_stmt" ], + "options_metadata": [], "placeholder": "", "required": false, "show": true, @@ -3255,37 +3260,56 @@ "table_schema": { "columns": [ { + "default": "None", "description": "Specify the name of the tool.", "disable_edit": false, "display_name": "Tool Name", "edit_mode": "inline", "filterable": false, "formatter": "text", + "hidden": false, "name": "name", "sortable": false, - "type": "text" + "type": "str" }, { + "default": "None", "description": "Describe the purpose of the tool.", "disable_edit": false, "display_name": "Tool Description", "edit_mode": "popover", "filterable": false, "formatter": "text", + "hidden": false, "name": "description", "sortable": false, - "type": "text" + "type": "str" }, { + "default": "None", "description": "The default identifiers for the tools and cannot be changed.", "disable_edit": true, "display_name": "Tool Identifiers", "edit_mode": "inline", "filterable": false, "formatter": "text", + "hidden": true, "name": "tags", "sortable": false, - "type": "text" + "type": "str" + }, + { + "default": true, + "description": "Indicates whether the tool is currently active. Set to True to activate this tool.", + "disable_edit": false, + "display_name": "Enable", + "edit_mode": "popover", + "filterable": true, + "formatter": "boolean", + "hidden": false, + "name": "status", + "sortable": true, + "type": "boolean" } ] }, @@ -3299,6 +3323,7 @@ { "description": "fetch_content() - Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", "name": "YfinanceComponent-fetch_content", + "status": true, "tags": [ "YfinanceComponent-fetch_content" ] @@ -3306,9 +3331,18 @@ { "description": "fetch_content_text() - Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", "name": "YfinanceComponent-fetch_content_text", + "status": true, "tags": [ "YfinanceComponent-fetch_content_text" ] + }, + { + "description": "as_dataframe() - Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) to access financial data and market information from Yahoo Finance.", + "name": "YfinanceComponent-as_dataframe", + "status": true, + "tags": [ + "YfinanceComponent-as_dataframe" + ] } ] } @@ -3319,21 +3353,21 @@ "type": "YfinanceComponent" }, "dragging": true, - "id": "YfinanceComponent-ZvUwB", + "id": "YfinanceComponent-LMwvY", "measured": { - "height": 519, - "width": 320 + "height": 581, + "width": 360 }, "position": { "x": -347.05382068428014, "y": -950.8279673971418 }, - "selected": false, + "selected": true, "type": "genericNode" }, { "data": { - "id": "CalculatorComponent-X0H1Z", + "id": "CalculatorComponent-qoEVQ", "node": { "base_classes": [ "Data" @@ -3508,10 +3542,10 @@ "type": "CalculatorComponent" }, "dragging": false, - "id": "CalculatorComponent-X0H1Z", + "id": "CalculatorComponent-qoEVQ", "measured": { - "height": 334, - "width": 320 + "height": 374, + "width": 360 }, "position": { "x": 418.5430081507146, @@ -3522,7 +3556,7 @@ }, { "data": { - "id": "TavilySearchComponent-47Sep", + "id": "TavilySearchComponent-GOWJw", "node": { "base_classes": [ "Data", @@ -3559,6 +3593,7 @@ "hidden": null, "method": "to_toolkit", "name": "component_as_tool", + "options": null, "required_inputs": null, "selected": "Tool", "tool_mode": true, @@ -3580,7 +3615,7 @@ "input_types": [ "Message" ], - "load_from_db": false, + "load_from_db": true, "name": "api_key", "password": true, "placeholder": "", @@ -3588,7 +3623,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "TAVILY_API_KEY" }, "code": { "advanced": true, @@ -3873,10 +3908,10 @@ "type": "TavilySearchComponent" }, "dragging": false, - "id": "TavilySearchComponent-47Sep", + "id": "TavilySearchComponent-GOWJw", "measured": { - "height": 437, - "width": 320 + "height": 489, + "width": 360 }, "position": { "x": -1138.848513020278, @@ -3887,7 +3922,7 @@ }, { "data": { - "id": "ChatOutput-8B8h5", + "id": "ChatOutput-MCPSV", "node": { "base_classes": [ "Message" @@ -4182,10 +4217,10 @@ "type": "ChatOutput" }, "dragging": false, - "id": "ChatOutput-8B8h5", + "id": "ChatOutput-MCPSV", "measured": { - "height": 66, - "width": 192 + "height": 74, + "width": 216 }, "position": { "x": 1262.4089496614665, @@ -4196,9 +4231,9 @@ } ], "viewport": { - "x": 854.5685722982503, - "y": 897.7897778064577, - "zoom": 0.38521832465848593 + "x": 796.4205708332455, + "y": 1011.1031568901649, + "zoom": 0.34584930104157496 } }, "description": "This Agent is designed to systematically execute a series of tasks following a meticulously predefined sequence. By adhering to this structured order, the Agent ensures that each task is completed efficiently and effectively, optimizing overall performance and maintaining a high level of accuracy.", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json index d99a3e804..eb0f72473 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json @@ -7,7 +7,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-e5naw", + "id": "Agent-PKpSO", "name": "response", "output_types": [ "Message" @@ -15,7 +15,7 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-NdG8s", + "id": "ChatOutput-8np0X", "inputTypes": [ "Data", "DataFrame", @@ -24,11 +24,12 @@ "type": "str" } }, - "id": "reactflow__edge-Agent-e5naw{œdataTypeœ:œAgentœ,œidœ:œAgent-e5nawœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-NdG8s{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-NdG8sœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", - "source": "Agent-e5naw", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-e5nawœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-NdG8s", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-NdG8sœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Agent-PKpSO{œdataTypeœ:œAgentœ,œidœ:œAgent-PKpSOœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-8np0X{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-8np0Xœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "Agent-PKpSO", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-PKpSOœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-8np0X", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-8np0Xœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -36,7 +37,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-PWZOu", + "id": "Agent-zOYup", "name": "response", "output_types": [ "Message" @@ -44,18 +45,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-e5naw", + "id": "Agent-PKpSO", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Agent-PWZOu{œdataTypeœ:œAgentœ,œidœ:œAgent-PWZOuœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Agent-e5naw{œfieldNameœ:œinput_valueœ,œidœ:œAgent-e5nawœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Agent-PWZOu", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-PWZOuœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-e5naw", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-e5nawœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Agent-zOYup{œdataTypeœ:œAgentœ,œidœ:œAgent-zOYupœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Agent-PKpSO{œfieldNameœ:œinput_valueœ,œidœ:œAgent-PKpSOœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "Agent-zOYup", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-zOYupœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-PKpSO", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-PKpSOœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -63,7 +65,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-3H5qa", + "id": "Agent-7K58a", "name": "response", "output_types": [ "Message" @@ -71,18 +73,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-PWZOu", + "id": "Agent-zOYup", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Agent-3H5qa{œdataTypeœ:œAgentœ,œidœ:œAgent-3H5qaœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Agent-PWZOu{œfieldNameœ:œinput_valueœ,œidœ:œAgent-PWZOuœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Agent-3H5qa", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-3H5qaœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-PWZOu", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-PWZOuœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Agent-7K58a{œdataTypeœ:œAgentœ,œidœ:œAgent-7K58aœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Agent-zOYup{œfieldNameœ:œinput_valueœ,œidœ:œAgent-zOYupœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "Agent-7K58a", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-7K58aœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-zOYup", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-zOYupœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -90,7 +93,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-LIxJY", + "id": "ChatInput-iZoDa", "name": "message", "output_types": [ "Message" @@ -98,18 +101,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-3H5qa", + "id": "Agent-7K58a", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-ChatInput-LIxJY{œdataTypeœ:œChatInputœ,œidœ:œChatInput-LIxJYœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-3H5qa{œfieldNameœ:œinput_valueœ,œidœ:œAgent-3H5qaœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ChatInput-LIxJY", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-LIxJYœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-3H5qa", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-3H5qaœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ChatInput-iZoDa{œdataTypeœ:œChatInputœ,œidœ:œChatInput-iZoDaœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-7K58a{œfieldNameœ:œinput_valueœ,œidœ:œAgent-7K58aœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ChatInput-iZoDa", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-iZoDaœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-7K58a", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-7K58aœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -117,7 +121,7 @@ "data": { "sourceHandle": { "dataType": "URL", - "id": "URL-XDhvs", + "id": "URL-j9slU", "name": "component_as_tool", "output_types": [ "Tool" @@ -125,18 +129,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-PWZOu", + "id": "Agent-zOYup", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-URL-XDhvs{œdataTypeœ:œURLœ,œidœ:œURL-XDhvsœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-PWZOu{œfieldNameœ:œtoolsœ,œidœ:œAgent-PWZOuœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", - "source": "URL-XDhvs", - "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-XDhvsœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-PWZOu", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-PWZOuœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-URL-j9slU{œdataTypeœ:œURLœ,œidœ:œURL-j9slUœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-zOYup{œfieldNameœ:œtoolsœ,œidœ:œAgent-zOYupœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "selected": false, + "source": "URL-j9slU", + "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-j9slUœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-zOYup", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-zOYupœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -144,7 +149,7 @@ "data": { "sourceHandle": { "dataType": "CalculatorComponent", - "id": "CalculatorComponent-N83tJ", + "id": "CalculatorComponent-L3y5A", "name": "component_as_tool", "output_types": [ "Tool" @@ -152,18 +157,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-e5naw", + "id": "Agent-PKpSO", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-CalculatorComponent-N83tJ{œdataTypeœ:œCalculatorComponentœ,œidœ:œCalculatorComponent-N83tJœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-e5naw{œfieldNameœ:œtoolsœ,œidœ:œAgent-e5nawœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", - "source": "CalculatorComponent-N83tJ", - "sourceHandle": "{œdataTypeœ: œCalculatorComponentœ, œidœ: œCalculatorComponent-N83tJœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-e5naw", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-e5nawœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-CalculatorComponent-L3y5A{œdataTypeœ:œCalculatorComponentœ,œidœ:œCalculatorComponent-L3y5Aœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-PKpSO{œfieldNameœ:œtoolsœ,œidœ:œAgent-PKpSOœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "selected": false, + "source": "CalculatorComponent-L3y5A", + "sourceHandle": "{œdataTypeœ: œCalculatorComponentœ, œidœ: œCalculatorComponent-L3y5Aœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-PKpSO", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-PKpSOœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -171,7 +177,7 @@ "data": { "sourceHandle": { "dataType": "SearchComponent", - "id": "SearchComponent-fF95b", + "id": "SearchComponent-8lQPB", "name": "component_as_tool", "output_types": [ "Tool" @@ -179,24 +185,25 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-3H5qa", + "id": "Agent-7K58a", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-SearchComponent-fF95b{œdataTypeœ:œSearchComponentœ,œidœ:œSearchComponent-fF95bœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-3H5qa{œfieldNameœ:œtoolsœ,œidœ:œAgent-3H5qaœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", - "source": "SearchComponent-fF95b", - "sourceHandle": "{œdataTypeœ: œSearchComponentœ, œidœ: œSearchComponent-fF95bœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-3H5qa", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-3H5qaœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-SearchComponent-8lQPB{œdataTypeœ:œSearchComponentœ,œidœ:œSearchComponent-8lQPBœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-7K58a{œfieldNameœ:œtoolsœ,œidœ:œAgent-7K58aœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "selected": false, + "source": "SearchComponent-8lQPB", + "sourceHandle": "{œdataTypeœ: œSearchComponentœ, œidœ: œSearchComponent-8lQPBœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-7K58a", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-7K58aœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" } ], "nodes": [ { "data": { - "id": "ChatInput-LIxJY", + "id": "ChatInput-iZoDa", "node": { "base_classes": [ "Message" @@ -467,7 +474,7 @@ }, "dragging": false, "height": 262, - "id": "ChatInput-LIxJY", + "id": "ChatInput-iZoDa", "measured": { "height": 262, "width": 360 @@ -488,7 +495,7 @@ "data": { "description": "Display a chat message in the Playground.", "display_name": "Chat Output", - "id": "ChatOutput-NdG8s", + "id": "ChatOutput-8np0X", "node": { "base_classes": [ "Message" @@ -770,7 +777,7 @@ }, "dragging": false, "height": 262, - "id": "ChatOutput-NdG8s", + "id": "ChatOutput-8np0X", "measured": { "height": 262, "width": 360 @@ -791,7 +798,7 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "City Selection Agent", - "id": "Agent-3H5qa", + "id": "Agent-7K58a", "node": { "base_classes": [ "Message" @@ -1390,7 +1397,7 @@ }, "dragging": true, "height": 725, - "id": "Agent-3H5qa", + "id": "Agent-7K58a", "measured": { "height": 725, "width": 360 @@ -1411,7 +1418,7 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Local Expert Agent", - "id": "Agent-PWZOu", + "id": "Agent-zOYup", "node": { "base_classes": [ "Message" @@ -2010,7 +2017,7 @@ }, "dragging": false, "height": 725, - "id": "Agent-PWZOu", + "id": "Agent-zOYup", "measured": { "height": 725, "width": 360 @@ -2031,7 +2038,7 @@ "data": { "description": "Define the agent's instructions, then enter a task to complete using tools.", "display_name": "Travel Concierge Agent", - "id": "Agent-e5naw", + "id": "Agent-PKpSO", "node": { "base_classes": [ "Message" @@ -2630,7 +2637,7 @@ }, "dragging": false, "height": 725, - "id": "Agent-e5naw", + "id": "Agent-PKpSO", "measured": { "height": 725, "width": 360 @@ -2649,7 +2656,7 @@ }, { "data": { - "id": "note-qLmmh", + "id": "note-gUKzO", "node": { "description": "# Travel Planning Agents \n\nThe travel planning system is a smart setup that uses several specialized \"agents\" to help plan incredible trips. Imagine each agent as a travel expert focusing on a part of your journey. Here's how it works:\n\n- **User-Friendly Start:** You start by telling the system about your travel needs—where you want to go and what you love to do.\n\n- **Data Collection:** The agents uses its tools to gather current info about various destinations, like the best travel times, weather, and costs.\n\n- **Three Key Agents:**\n - **City Selection Agent:** Picks the best places to visit based on your likes and current data.\n - **Local Expert Agent:** Gathers interesting details about what to do and see in the chosen city.\n - **Travel Concierge Agent:** Builds a day-by-day plan that includes where to stay, eat, and explore!\n\n- **Tools and Data:** Each agent uses tools to find and organize the latest information so you get recommendations that are both accurate and exciting.\n\n- **Final Plan:** Once everything is put together, you receive a complete, easy-to-follow travel itinerary, perfect for your adventure!\n", "display_name": "", @@ -2660,7 +2667,7 @@ }, "dragging": false, "height": 603, - "id": "note-qLmmh", + "id": "note-gUKzO", "measured": { "height": 603, "width": 328 @@ -2684,7 +2691,7 @@ }, { "data": { - "id": "note-lZk3i", + "id": "note-a0pJv", "node": { "description": "# **City Selection Agent**\n - **Purpose:** This agent evaluates potential travel destinations based on user input and external data sources.\n - **Core Functions:** Analyzes factors such as weather, local events, and travel costs to recommend optimal cities.\n - **Tools Utilized:** Employs APIs and data-fetching tools to gather real-time information for decision-making.\n", "display_name": "", @@ -2697,7 +2704,7 @@ }, "dragging": false, "height": 334, - "id": "note-lZk3i", + "id": "note-a0pJv", "measured": { "height": 334, "width": 328 @@ -2721,7 +2728,7 @@ }, { "data": { - "id": "note-twqCP", + "id": "note-ylJy2", "node": { "description": "# **Local Expert Agent**\n - **Purpose:** Focused on gathering and providing an in-depth guide to the selected city.\n - **Core Functions:** Compiles insights into cultural attractions, local customs, and unique experiences.\n - **Tools Utilized:** Uses web content fetchers and data APIs to collect detailed local insights and enhance the user understanding with hidden gems.\n", "display_name": "", @@ -2734,7 +2741,7 @@ }, "dragging": false, "height": 342, - "id": "note-twqCP", + "id": "note-ylJy2", "measured": { "height": 342, "width": 328 @@ -2758,7 +2765,7 @@ }, { "data": { - "id": "note-bhmU0", + "id": "note-LZBEx", "node": { "description": "# **Travel Concierge Agent**\n - **Purpose:** Crafts detailed travel itineraries that are customized to the traveler's interests and needs.\n - **Core Functions:** Offers a comprehensive daily schedule, including accommodations, dining spots, and activities.\n - **Tools Utilized:** Integrates calculators and data tools for accurate budget planning and itinerary logistics.", "display_name": "", @@ -2771,7 +2778,7 @@ }, "dragging": false, "height": 336, - "id": "note-bhmU0", + "id": "note-LZBEx", "measured": { "height": 336, "width": 328 @@ -2795,7 +2802,7 @@ }, { "data": { - "id": "note-pFFw3", + "id": "note-4IUQQ", "node": { "description": "## Configure the agent by obtaining your OpenAI API key from [platform.openai.com](https://platform.openai.com). Under \"Model Provider\", choose:\n- OpenAI: Default, requires only API key\n- Anthropic/Azure/Groq/NVIDIA: Each requires their own API keys\n- Custom: Use your own model endpoint + authentication\n\nSelect model and input API key before running the flow.", "display_name": "", @@ -2808,7 +2815,7 @@ }, "dragging": false, "height": 324, - "id": "note-pFFw3", + "id": "note-4IUQQ", "measured": { "height": 324, "width": 328 @@ -2830,7 +2837,7 @@ "data": { "description": "Load and retrieve data from specified URLs. Supports output in plain text, raw HTML, or JSON, with options for cleaning and separating multiple outputs.", "display_name": "URL", - "id": "URL-XDhvs", + "id": "URL-j9slU", "node": { "base_classes": [ "Data", @@ -3091,7 +3098,7 @@ "type": "URL" }, "dragging": false, - "id": "URL-XDhvs", + "id": "URL-j9slU", "measured": { "height": 660, "width": 360 @@ -3105,7 +3112,7 @@ }, { "data": { - "id": "CalculatorComponent-N83tJ", + "id": "CalculatorComponent-L3y5A", "node": { "base_classes": [ "Data" @@ -3282,7 +3289,7 @@ "type": "CalculatorComponent" }, "dragging": false, - "id": "CalculatorComponent-N83tJ", + "id": "CalculatorComponent-L3y5A", "measured": { "height": 374, "width": 360 @@ -3296,14 +3303,16 @@ }, { "data": { - "id": "SearchComponent-fF95b", + "description": "Call the searchapi.io API with result limiting", + "display_name": "Search API", + "id": "SearchComponent-8lQPB", "node": { "base_classes": [ "Data", + "DataFrame", "Message" ], "beta": false, - "category": "tools", "conditional_paths": [], "custom_fields": {}, "description": "Call the searchapi.io API with result limiting", @@ -3320,9 +3329,7 @@ ], "frozen": false, "icon": "SearchAPI", - "key": "SearchComponent", "legacy": false, - "lf_version": "1.1.1", "metadata": {}, "minimized": false, "output_types": [], @@ -3334,6 +3341,7 @@ "hidden": null, "method": "to_toolkit", "name": "component_as_tool", + "options": null, "required_inputs": null, "selected": "Tool", "tool_mode": true, @@ -3344,7 +3352,6 @@ } ], "pinned": false, - "score": 0.0030458160338519237, "template": { "_type": "Component", "api_key": { @@ -3356,7 +3363,7 @@ "input_types": [ "Message" ], - "load_from_db": true, + "load_from_db": false, "name": "api_key", "password": true, "placeholder": "", @@ -3382,7 +3389,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nfrom langchain_community.utilities.searchapi import SearchApiAPIWrapper\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DictInput, DropdownInput, IntInput, MultilineInput, SecretStrInput\nfrom langflow.io import Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass SearchComponent(Component):\n display_name: str = \"Search API\"\n description: str = \"Call the searchapi.io API with result limiting\"\n documentation: str = \"https://www.searchapi.io/docs/google\"\n icon = \"SearchAPI\"\n\n inputs = [\n DropdownInput(name=\"engine\", display_name=\"Engine\", value=\"google\", options=[\"google\", \"bing\", \"duckduckgo\"]),\n SecretStrInput(name=\"api_key\", display_name=\"SearchAPI API Key\", required=True),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input\",\n tool_mode=True,\n ),\n DictInput(name=\"search_params\", display_name=\"Search parameters\", advanced=True, is_list=True),\n IntInput(name=\"max_results\", display_name=\"Max Results\", value=5, advanced=True),\n IntInput(name=\"max_snippet_length\", display_name=\"Max Snippet Length\", value=100, advanced=True),\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 ]\n\n def _build_wrapper(self):\n return SearchApiAPIWrapper(engine=self.engine, searchapi_api_key=self.api_key)\n\n def run_model(self) -> list[Data]:\n return self.fetch_content()\n\n def fetch_content(self) -> list[Data]:\n wrapper = self._build_wrapper()\n\n def search_func(\n query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100\n ) -> list[Data]:\n params = params or {}\n full_results = wrapper.results(query=query, **params)\n organic_results = full_results.get(\"organic_results\", [])[:max_results]\n\n return [\n Data(\n text=result.get(\"snippet\", \"\"),\n data={\n \"title\": result.get(\"title\", \"\")[:max_snippet_length],\n \"link\": result.get(\"link\", \"\"),\n \"snippet\": result.get(\"snippet\", \"\")[:max_snippet_length],\n },\n )\n for result in organic_results\n ]\n\n results = search_func(\n self.input_value,\n self.search_params or {},\n self.max_results,\n self.max_snippet_length,\n )\n self.status = results\n return results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = \"\"\n for item in data:\n result_string += item.text + \"\\n\"\n self.status = result_string\n return Message(text=result_string)\n" + "value": "from typing import Any\n\nfrom langchain_community.utilities.searchapi import SearchApiAPIWrapper\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DictInput, DropdownInput, IntInput, MultilineInput, SecretStrInput\nfrom langflow.io import Output\nfrom langflow.schema import Data, DataFrame\nfrom langflow.schema.message import Message\n\n\nclass SearchComponent(Component):\n display_name: str = \"Search API\"\n description: str = \"Call the searchapi.io API with result limiting\"\n documentation: str = \"https://www.searchapi.io/docs/google\"\n icon = \"SearchAPI\"\n\n inputs = [\n DropdownInput(name=\"engine\", display_name=\"Engine\", value=\"google\", options=[\"google\", \"bing\", \"duckduckgo\"]),\n SecretStrInput(name=\"api_key\", display_name=\"SearchAPI API Key\", required=True),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input\",\n tool_mode=True,\n ),\n DictInput(name=\"search_params\", display_name=\"Search parameters\", advanced=True, is_list=True),\n IntInput(name=\"max_results\", display_name=\"Max Results\", value=5, advanced=True),\n IntInput(name=\"max_snippet_length\", display_name=\"Max Snippet Length\", value=100, advanced=True),\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 def _build_wrapper(self):\n return SearchApiAPIWrapper(engine=self.engine, searchapi_api_key=self.api_key)\n\n def run_model(self) -> list[Data]:\n return self.fetch_content()\n\n def fetch_content(self) -> list[Data]:\n wrapper = self._build_wrapper()\n\n def search_func(\n query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100\n ) -> list[Data]:\n params = params or {}\n full_results = wrapper.results(query=query, **params)\n organic_results = full_results.get(\"organic_results\", [])[:max_results]\n\n return [\n Data(\n text=result.get(\"snippet\", \"\"),\n data={\n \"title\": result.get(\"title\", \"\")[:max_snippet_length],\n \"link\": result.get(\"link\", \"\"),\n \"snippet\": result.get(\"snippet\", \"\")[:max_snippet_length],\n },\n )\n for result in organic_results\n ]\n\n results = search_func(\n self.input_value,\n self.search_params or {},\n self.max_results,\n self.max_snippet_length,\n )\n self.status = results\n return results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = \"\"\n for item in data:\n result_string += item.text + \"\\n\"\n self.status = result_string\n return Message(text=result_string)\n\n def as_dataframe(self) -> DataFrame:\n \"\"\"Convert the search results to a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the search results.\n \"\"\"\n data = self.fetch_content()\n return DataFrame(data)\n" }, "engine": { "_input_type": "DropdownInput", @@ -3411,6 +3418,7 @@ "input_value": { "_input_type": "MultilineInput", "advanced": false, + "copy_field": false, "display_name": "Input", "dynamic": false, "info": "", @@ -3558,6 +3566,19 @@ "name": "tags", "sortable": false, "type": "str" + }, + { + "default": true, + "description": "Indicates whether the tool is currently active. Set to True to activate this tool.", + "disable_edit": false, + "display_name": "Enable", + "edit_mode": "popover", + "filterable": true, + "formatter": "boolean", + "hidden": false, + "name": "status", + "sortable": true, + "type": "boolean" } ] }, @@ -3571,6 +3592,7 @@ { "description": "fetch_content(api_key: Message) - Call the searchapi.io API with result limiting", "name": "SearchComponent-fetch_content", + "status": true, "tags": [ "SearchComponent-fetch_content" ] @@ -3578,9 +3600,18 @@ { "description": "fetch_content_text(api_key: Message) - Call the searchapi.io API with result limiting", "name": "SearchComponent-fetch_content_text", + "status": true, "tags": [ "SearchComponent-fetch_content_text" ] + }, + { + "description": "as_dataframe(api_key: Message) - Call the searchapi.io API with result limiting", + "name": "SearchComponent-as_dataframe", + "status": true, + "tags": [ + "SearchComponent-as_dataframe" + ] } ] } @@ -3591,7 +3622,7 @@ "type": "SearchComponent" }, "dragging": false, - "id": "SearchComponent-fF95b", + "id": "SearchComponent-8lQPB", "measured": { "height": 536, "width": 360 @@ -3605,9 +3636,9 @@ } ], "viewport": { - "x": -473.30523608095336, - "y": 166.93251319338754, - "zoom": 0.41164535038506667 + "x": -422.4287774794177, + "y": 323.63311823261427, + "zoom": 0.36739682737950574 } }, "description": "Create a travel planning chatbot that uses specialized agents to craft personalized trip itineraries.",