diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json new file mode 100644 index 000000000..7441fd150 --- /dev/null +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -0,0 +1,3282 @@ +{ + "data": { + "edges": [ + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "YouTubeCommentsComponent", + "id": "YouTubeCommentsComponent-10bJT", + "name": "comments", + "output_types": [ + "DataFrame" + ] + }, + "targetHandle": { + "fieldName": "df", + "id": "BatchRunComponent-Y6Aec", + "inputTypes": [ + "DataFrame" + ], + "type": "other" + } + }, + "id": "reactflow__edge-YouTubeCommentsComponent-10bJT{œdataTypeœ:œYouTubeCommentsComponentœ,œidœ:œYouTubeCommentsComponent-10bJTœ,œnameœ:œcommentsœ,œoutput_typesœ:[œDataFrameœ]}-BatchRunComponent-Y6Aec{œfieldNameœ:œdfœ,œidœ:œBatchRunComponent-Y6Aecœ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}", + "selected": false, + "source": "YouTubeCommentsComponent-10bJT", + "sourceHandle": "{œdataTypeœ: œYouTubeCommentsComponentœ, œidœ: œYouTubeCommentsComponent-10bJTœ, œnameœ: œcommentsœ, œoutput_typesœ: [œDataFrameœ]}", + "target": "BatchRunComponent-Y6Aec", + "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œBatchRunComponent-Y6Aecœ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "OpenAIModel", + "id": "OpenAIModel-QgyC5", + "name": "model_output", + "output_types": [ + "LanguageModel" + ] + }, + "targetHandle": { + "fieldName": "model", + "id": "BatchRunComponent-Y6Aec", + "inputTypes": [ + "LanguageModel" + ], + "type": "other" + } + }, + "id": "reactflow__edge-OpenAIModel-QgyC5{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-QgyC5œ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-BatchRunComponent-Y6Aec{œfieldNameœ:œmodelœ,œidœ:œBatchRunComponent-Y6Aecœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}", + "selected": false, + "source": "OpenAIModel-QgyC5", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-QgyC5œ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}", + "target": "BatchRunComponent-Y6Aec", + "targetHandle": "{œfieldNameœ: œmodelœ, œidœ: œBatchRunComponent-Y6Aecœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "BatchRunComponent", + "id": "BatchRunComponent-Y6Aec", + "name": "batch_results", + "output_types": [ + "DataFrame" + ] + }, + "targetHandle": { + "fieldName": "df", + "id": "ParseDataFrame-Ni6HW", + "inputTypes": [ + "DataFrame" + ], + "type": "other" + } + }, + "id": "reactflow__edge-BatchRunComponent-Y6Aec{œdataTypeœ:œBatchRunComponentœ,œidœ:œBatchRunComponent-Y6Aecœ,œnameœ:œbatch_resultsœ,œoutput_typesœ:[œDataFrameœ]}-ParseDataFrame-Ni6HW{œfieldNameœ:œdfœ,œidœ:œParseDataFrame-Ni6HWœ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}", + "selected": false, + "source": "BatchRunComponent-Y6Aec", + "sourceHandle": "{œdataTypeœ: œBatchRunComponentœ, œidœ: œBatchRunComponent-Y6Aecœ, œnameœ: œbatch_resultsœ, œoutput_typesœ: [œDataFrameœ]}", + "target": "ParseDataFrame-Ni6HW", + "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œParseDataFrame-Ni6HWœ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ParseDataFrame", + "id": "ParseDataFrame-Ni6HW", + "name": "text", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "analysis", + "id": "Prompt-ozK70", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "reactflow__edge-ParseDataFrame-Ni6HW{œdataTypeœ:œParseDataFrameœ,œidœ:œParseDataFrame-Ni6HWœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-ozK70{œfieldNameœ:œanalysisœ,œidœ:œPrompt-ozK70œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ParseDataFrame-Ni6HW", + "sourceHandle": "{œdataTypeœ: œParseDataFrameœ, œidœ: œParseDataFrame-Ni6HWœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-ozK70", + "targetHandle": "{œfieldNameœ: œanalysisœ, œidœ: œPrompt-ozK70œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "Prompt", + "id": "Prompt-ozK70", + "name": "prompt", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "Agent-U1bxR", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "reactflow__edge-Prompt-ozK70{œdataTypeœ:œPromptœ,œidœ:œPrompt-ozK70œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-U1bxR{œfieldNameœ:œinput_valueœ,œidœ:œAgent-U1bxRœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "Prompt-ozK70", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-ozK70œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-U1bxR", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-U1bxRœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "Agent", + "id": "Agent-U1bxR", + "name": "response", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-BILzx", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "reactflow__edge-Agent-U1bxR{œdataTypeœ:œAgentœ,œidœ:œAgent-U1bxRœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-BILzx{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-BILzxœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "Agent-U1bxR", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-U1bxRœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-BILzx", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BILzxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "YouTubeTranscripts", + "id": "YouTubeTranscripts-wPf1w", + "name": "component_as_tool", + "output_types": [ + "Tool" + ] + }, + "targetHandle": { + "fieldName": "tools", + "id": "Agent-U1bxR", + "inputTypes": [ + "Tool" + ], + "type": "other" + } + }, + "id": "reactflow__edge-YouTubeTranscripts-wPf1w{œdataTypeœ:œYouTubeTranscriptsœ,œidœ:œYouTubeTranscripts-wPf1wœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-U1bxR{œfieldNameœ:œtoolsœ,œidœ:œAgent-U1bxRœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "selected": false, + "source": "YouTubeTranscripts-wPf1w", + "sourceHandle": "{œdataTypeœ: œYouTubeTranscriptsœ, œidœ: œYouTubeTranscripts-wPf1wœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-U1bxR", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-U1bxRœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ChatInput", + "id": "ChatInput-VrWjf", + "name": "message", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_text", + "id": "ConditionalRouter-CfANV", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ChatInput-VrWjf{œdataTypeœ:œChatInputœ,œidœ:œChatInput-VrWjfœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ConditionalRouter-CfANV{œfieldNameœ:œinput_textœ,œidœ:œConditionalRouter-CfANVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ChatInput-VrWjf", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-VrWjfœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ConditionalRouter-CfANV", + "targetHandle": "{œfieldNameœ: œinput_textœ, œidœ: œConditionalRouter-CfANVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ChatInput", + "id": "ChatInput-VrWjf", + "name": "message", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "message", + "id": "ConditionalRouter-CfANV", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ChatInput-VrWjf{œdataTypeœ:œChatInputœ,œidœ:œChatInput-VrWjfœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ConditionalRouter-CfANV{œfieldNameœ:œmessageœ,œidœ:œConditionalRouter-CfANVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ChatInput-VrWjf", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-VrWjfœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ConditionalRouter-CfANV", + "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œConditionalRouter-CfANVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ConditionalRouter", + "id": "ConditionalRouter-CfANV", + "name": "true_result", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "video_url", + "id": "YouTubeCommentsComponent-10bJT", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ConditionalRouter-CfANV{œdataTypeœ:œConditionalRouterœ,œidœ:œConditionalRouter-CfANVœ,œnameœ:œtrue_resultœ,œoutput_typesœ:[œMessageœ]}-YouTubeCommentsComponent-10bJT{œfieldNameœ:œvideo_urlœ,œidœ:œYouTubeCommentsComponent-10bJTœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ConditionalRouter-CfANV", + "sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-CfANVœ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}", + "target": "YouTubeCommentsComponent-10bJT", + "targetHandle": "{œfieldNameœ: œvideo_urlœ, œidœ: œYouTubeCommentsComponent-10bJTœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ConditionalRouter", + "id": "ConditionalRouter-CfANV", + "name": "true_result", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "url", + "id": "Prompt-ozK70", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ConditionalRouter-CfANV{œdataTypeœ:œConditionalRouterœ,œidœ:œConditionalRouter-CfANVœ,œnameœ:œtrue_resultœ,œoutput_typesœ:[œMessageœ]}-Prompt-ozK70{œfieldNameœ:œurlœ,œidœ:œPrompt-ozK70œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "selected": false, + "source": "ConditionalRouter-CfANV", + "sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-CfANVœ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-ozK70", + "targetHandle": "{œfieldNameœ: œurlœ, œidœ: œPrompt-ozK70œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + } + ], + "nodes": [ + { + "data": { + "id": "BatchRunComponent-Y6Aec", + "node": { + "base_classes": [ + "DataFrame" + ], + "beta": true, + "category": "helpers", + "conditional_paths": [], + "custom_fields": {}, + "description": "Runs a language model over each row of a DataFrame's text column and returns a new DataFrame with two columns: 'text_input' (the original text) and 'model_response' containing the model's response.", + "display_name": "Batch Run", + "documentation": "", + "edited": false, + "field_order": [ + "model", + "system_message", + "df", + "column_name" + ], + "frozen": false, + "icon": "List", + "key": "BatchRunComponent", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Batch Results", + "method": "run_batch", + "name": "batch_results", + "selected": "DataFrame", + "tool_mode": true, + "types": [ + "DataFrame" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.007568328950209746, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom langflow.custom import Component\nfrom langflow.io import DataFrameInput, HandleInput, MultilineInput, Output, StrInput\nfrom langflow.schema import DataFrame\n\nif TYPE_CHECKING:\n from langchain_core.runnables import Runnable\n\n\nclass BatchRunComponent(Component):\n display_name = \"Batch Run\"\n description = (\n \"Runs a language model over each row of a DataFrame's text column and returns a new \"\n \"DataFrame with two columns: 'text_input' (the original text) and 'model_response' \"\n \"containing the model's response.\"\n )\n icon = \"List\"\n beta = True\n\n inputs = [\n HandleInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Connect the 'Language Model' output from your LLM component here.\",\n input_types=[\"LanguageModel\"],\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"Multi-line system instruction for all rows in the DataFrame.\",\n required=False,\n ),\n DataFrameInput(\n name=\"df\",\n display_name=\"DataFrame\",\n info=\"The DataFrame whose column (specified by 'column_name') we'll treat as text messages.\",\n ),\n StrInput(\n name=\"column_name\",\n display_name=\"Column Name\",\n info=\"The name of the DataFrame column to treat as text messages. Default='text'.\",\n value=\"text\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Batch Results\",\n name=\"batch_results\",\n method=\"run_batch\",\n info=\"A DataFrame with two columns: 'text_input' and 'model_response'.\",\n ),\n ]\n\n async def run_batch(self) -> DataFrame:\n \"\"\"For each row in df[column_name], combine that text with system_message, then invoke the model asynchronously.\n\n Returns a new DataFrame of the same length, with columns 'text_input' and 'model_response'.\n \"\"\"\n model: Runnable = self.model\n system_msg = self.system_message or \"\"\n df: DataFrame = self.df\n col_name = self.column_name or \"text\"\n\n if col_name not in df.columns:\n msg = f\"Column '{col_name}' not found in the DataFrame.\"\n raise ValueError(msg)\n\n # Convert the specified column to a list of strings\n user_texts = df[col_name].astype(str).tolist()\n\n # Prepare the batch of conversations\n conversations = [\n [{\"role\": \"system\", \"content\": system_msg}, {\"role\": \"user\", \"content\": text}]\n if system_msg\n else [{\"role\": \"user\", \"content\": text}]\n for text in user_texts\n ]\n model = model.with_config(\n {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n )\n\n responses = await model.abatch(conversations)\n\n # Build the final data, each row has 'text_input' + 'model_response'\n rows = []\n for original_text, response in zip(user_texts, responses, strict=False):\n resp_text = response.content if hasattr(response, \"content\") else str(response)\n\n row = {\"text_input\": original_text, \"model_response\": resp_text}\n rows.append(row)\n\n # Convert to a new DataFrame\n return DataFrame(rows) # Langflow DataFrame from a list of dicts\n" + }, + "column_name": { + "_input_type": "StrInput", + "advanced": false, + "display_name": "Column Name", + "dynamic": false, + "info": "The name of the DataFrame column to treat as text messages. Default='text'.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "column_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "text" + }, + "df": { + "_input_type": "DataFrameInput", + "advanced": false, + "display_name": "DataFrame", + "dynamic": false, + "info": "The DataFrame whose column (specified by 'column_name') we'll treat as text messages.", + "input_types": [ + "DataFrame" + ], + "list": false, + "list_add_label": "Add More", + "name": "df", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "model": { + "_input_type": "HandleInput", + "advanced": false, + "display_name": "Language Model", + "dynamic": false, + "info": "Connect the 'Language Model' output from your LLM component here.", + "input_types": [ + "LanguageModel" + ], + "list": false, + "list_add_label": "Add More", + "name": "model", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "system_message": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "System Message", + "dynamic": false, + "info": "Multi-line system instruction for all rows in the DataFrame.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "system_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "You are a sentiment analysis AI specialized in analyzing YouTube comments. Your task is to determine the emotional tone of each comment.\n\nAnalyze:\n- Word choice and tone\n- Emotional language \n- Context clues (emojis, punctuation, caps)\n\nClassify sentiment as:\n- Positive\n- Negative \n- Neutral\n- Mixed\n- Sarcastic\n\nFormat:\n\n\n[Brief 1-2 sentence analysis focusing on key emotional indicators]\n\n\n[Sentiment category]\n\n\n\nExample:\nComment: \"Wow, this video is absolutely amazing! The creator did an incredible job explaining complex topics so clearly. 👏👏👏\"\n\n\nUses enthusiastic words (\"amazing\", \"incredible\") with applause emojis, showing strong appreciation for the content's clarity.\n\n\nPositive\n\n" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "BatchRunComponent" + }, + "dragging": false, + "id": "BatchRunComponent-Y6Aec", + "measured": { + "height": 477, + "width": 320 + }, + "position": { + "x": 635.0665302273813, + "y": 5950.62139498088 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "YouTubeCommentsComponent-10bJT", + "node": { + "base_classes": [ + "DataFrame" + ], + "beta": false, + "category": "youtube", + "conditional_paths": [], + "custom_fields": {}, + "description": "Retrieves and analyzes comments from YouTube videos.", + "display_name": "YouTube Comments", + "documentation": "", + "edited": false, + "field_order": [ + "video_url", + "api_key", + "max_results", + "sort_by", + "include_replies", + "include_metrics" + ], + "frozen": false, + "icon": "YouTube", + "key": "YouTubeCommentsComponent", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Comments", + "method": "get_video_comments", + "name": "comments", + "selected": "DataFrame", + "tool_mode": true, + "types": [ + "DataFrame" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.1676812955549333, + "template": { + "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": false, + "display_name": "YouTube API Key", + "dynamic": false, + "info": "Your YouTube Data API key.", + "input_types": [ + "Message" + ], + "load_from_db": false, + "name": "api_key", + "password": true, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from contextlib import contextmanager\n\nimport pandas as pd\nfrom googleapiclient.discovery import build\nfrom googleapiclient.errors import HttpError\n\nfrom langflow.custom import Component\nfrom langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput\nfrom langflow.schema import DataFrame\nfrom langflow.template import Output\n\n\nclass YouTubeCommentsComponent(Component):\n \"\"\"A component that retrieves comments from YouTube videos.\"\"\"\n\n display_name: str = \"YouTube Comments\"\n description: str = \"Retrieves and analyzes comments from YouTube videos.\"\n icon: str = \"YouTube\"\n\n # Constants\n COMMENTS_DISABLED_STATUS = 403\n NOT_FOUND_STATUS = 404\n API_MAX_RESULTS = 100\n\n inputs = [\n MessageTextInput(\n name=\"video_url\",\n display_name=\"Video URL\",\n info=\"The URL of the YouTube video to get comments from.\",\n tool_mode=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"YouTube API Key\",\n info=\"Your YouTube Data API key.\",\n required=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n value=20,\n info=\"The maximum number of comments to return.\",\n ),\n DropdownInput(\n name=\"sort_by\",\n display_name=\"Sort By\",\n options=[\"time\", \"relevance\"],\n value=\"relevance\",\n info=\"Sort comments by time or relevance.\",\n ),\n BoolInput(\n name=\"include_replies\",\n display_name=\"Include Replies\",\n value=False,\n info=\"Whether to include replies to comments.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_metrics\",\n display_name=\"Include Metrics\",\n value=True,\n info=\"Include metrics like like count and reply count.\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(name=\"comments\", display_name=\"Comments\", method=\"get_video_comments\"),\n ]\n\n def _extract_video_id(self, video_url: str) -> str:\n \"\"\"Extracts the video ID from a YouTube URL.\"\"\"\n import re\n\n patterns = [\n r\"(?:youtube\\.com\\/watch\\?v=|youtu.be\\/|youtube.com\\/embed\\/)([^&\\n?#]+)\",\n r\"youtube.com\\/shorts\\/([^&\\n?#]+)\",\n ]\n\n for pattern in patterns:\n match = re.search(pattern, video_url)\n if match:\n return match.group(1)\n\n return video_url.strip()\n\n def _process_reply(self, reply: dict, parent_id: str, *, include_metrics: bool = True) -> dict:\n \"\"\"Process a single reply comment.\"\"\"\n reply_snippet = reply[\"snippet\"]\n reply_data = {\n \"comment_id\": reply[\"id\"],\n \"parent_comment_id\": parent_id,\n \"author\": reply_snippet[\"authorDisplayName\"],\n \"text\": reply_snippet[\"textDisplay\"],\n \"published_at\": reply_snippet[\"publishedAt\"],\n \"is_reply\": True,\n }\n if include_metrics:\n reply_data[\"like_count\"] = reply_snippet[\"likeCount\"]\n reply_data[\"reply_count\"] = 0 # Replies can't have replies\n\n return reply_data\n\n def _process_comment(\n self, item: dict, *, include_metrics: bool = True, include_replies: bool = False\n ) -> list[dict]:\n \"\"\"Process a single comment thread.\"\"\"\n comment = item[\"snippet\"][\"topLevelComment\"][\"snippet\"]\n comment_id = item[\"snippet\"][\"topLevelComment\"][\"id\"]\n\n # Basic comment data\n processed_comments = [\n {\n \"comment_id\": comment_id,\n \"parent_comment_id\": \"\", # Empty for top-level comments\n \"author\": comment[\"authorDisplayName\"],\n \"author_channel_url\": comment.get(\"authorChannelUrl\", \"\"),\n \"text\": comment[\"textDisplay\"],\n \"published_at\": comment[\"publishedAt\"],\n \"updated_at\": comment[\"updatedAt\"],\n \"is_reply\": False,\n }\n ]\n\n # Add metrics if requested\n if include_metrics:\n processed_comments[0].update(\n {\n \"like_count\": comment[\"likeCount\"],\n \"reply_count\": item[\"snippet\"][\"totalReplyCount\"],\n }\n )\n\n # Add replies if requested\n if include_replies and item[\"snippet\"][\"totalReplyCount\"] > 0 and \"replies\" in item:\n for reply in item[\"replies\"][\"comments\"]:\n reply_data = self._process_reply(reply, parent_id=comment_id, include_metrics=include_metrics)\n processed_comments.append(reply_data)\n\n return processed_comments\n\n @contextmanager\n def youtube_client(self):\n \"\"\"Context manager for YouTube API client.\"\"\"\n client = build(\"youtube\", \"v3\", developerKey=self.api_key)\n try:\n yield client\n finally:\n client.close()\n\n def get_video_comments(self) -> DataFrame:\n \"\"\"Retrieves comments from a YouTube video and returns as DataFrame.\"\"\"\n try:\n # Extract video ID from URL\n video_id = self._extract_video_id(self.video_url)\n\n # Use context manager for YouTube API client\n with self.youtube_client() as youtube:\n comments_data = []\n results_count = 0\n request = youtube.commentThreads().list(\n part=\"snippet,replies\",\n videoId=video_id,\n maxResults=min(self.API_MAX_RESULTS, self.max_results),\n order=self.sort_by,\n textFormat=\"plainText\",\n )\n\n while request and results_count < self.max_results:\n response = request.execute()\n\n for item in response.get(\"items\", []):\n if results_count >= self.max_results:\n break\n\n comments = self._process_comment(\n item, include_metrics=self.include_metrics, include_replies=self.include_replies\n )\n comments_data.extend(comments)\n results_count += 1\n\n # Get the next page if available and needed\n if \"nextPageToken\" in response and results_count < self.max_results:\n request = youtube.commentThreads().list(\n part=\"snippet,replies\",\n videoId=video_id,\n maxResults=min(self.API_MAX_RESULTS, self.max_results - results_count),\n order=self.sort_by,\n textFormat=\"plainText\",\n pageToken=response[\"nextPageToken\"],\n )\n else:\n request = None\n\n # Convert to DataFrame\n comments_df = pd.DataFrame(comments_data)\n\n # Add video metadata\n comments_df[\"video_id\"] = video_id\n comments_df[\"video_url\"] = self.video_url\n\n # Sort columns for better organization\n column_order = [\n \"video_id\",\n \"video_url\",\n \"comment_id\",\n \"parent_comment_id\",\n \"is_reply\",\n \"author\",\n \"author_channel_url\",\n \"text\",\n \"published_at\",\n \"updated_at\",\n ]\n\n if self.include_metrics:\n column_order.extend([\"like_count\", \"reply_count\"])\n\n comments_df = comments_df[column_order]\n\n return DataFrame(comments_df)\n\n except HttpError as e:\n error_message = f\"YouTube API error: {e!s}\"\n if e.resp.status == self.COMMENTS_DISABLED_STATUS:\n error_message = \"Comments are disabled for this video or API quota exceeded.\"\n elif e.resp.status == self.NOT_FOUND_STATUS:\n error_message = \"Video not found.\"\n\n return DataFrame(pd.DataFrame({\"error\": [error_message]}))\n" + }, + "include_metrics": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Metrics", + "dynamic": false, + "info": "Include metrics like like count and reply count.", + "list": false, + "list_add_label": "Add More", + "name": "include_metrics", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "include_replies": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Replies", + "dynamic": false, + "info": "Whether to include replies to comments.", + "list": false, + "list_add_label": "Add More", + "name": "include_replies", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "max_results": { + "_input_type": "IntInput", + "advanced": false, + "display_name": "Max Results", + "dynamic": false, + "info": "The maximum number of comments to return.", + "list": false, + "list_add_label": "Add More", + "name": "max_results", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 10 + }, + "sort_by": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sort By", + "dynamic": false, + "info": "Sort comments by time or relevance.", + "name": "sort_by", + "options": [ + "time", + "relevance" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "relevance" + }, + "video_url": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Video URL", + "dynamic": false, + "info": "The URL of the YouTube video to get comments from.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "video_url", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "YouTubeCommentsComponent" + }, + "dragging": false, + "id": "YouTubeCommentsComponent-10bJT", + "measured": { + "height": 493, + "width": 320 + }, + "position": { + "x": 191.60600144515274, + "y": 6042.239455161904 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "OpenAIModel-QgyC5", + "node": { + "base_classes": [ + "LanguageModel", + "Message" + ], + "beta": false, + "category": "models", + "conditional_paths": [], + "custom_fields": {}, + "description": "Generates text using OpenAI LLMs.", + "display_name": "OpenAI", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "system_message", + "stream", + "max_tokens", + "model_kwargs", + "json_mode", + "model_name", + "openai_api_base", + "api_key", + "temperature", + "seed" + ], + "frozen": false, + "icon": "OpenAI", + "key": "OpenAIModel", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "text_response", + "name": "text_output", + "required_inputs": [], + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Language Model", + "method": "build_model", + "name": "model_output", + "required_inputs": [ + "api_key" + ], + "selected": "LanguageModel", + "tool_mode": true, + "types": [ + "LanguageModel" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.001, + "template": { + "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": false, + "display_name": "OpenAI API Key", + "dynamic": false, + "info": "The OpenAI API Key to use for the OpenAI model.", + "input_types": [ + "Message" + ], + "load_from_db": true, + "name": "api_key", + "password": true, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "str", + "value": "OPENAI_API_KEY" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[0],\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Input", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "json_mode": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "JSON Mode", + "dynamic": false, + "info": "If True, it will output JSON regardless of passing a schema.", + "list": false, + "list_add_label": "Add More", + "name": "json_mode", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "max_retries": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Retries", + "dynamic": false, + "info": "The maximum number of retries to make when generating.", + "list": false, + "list_add_label": "Add More", + "name": "max_retries", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 5 + }, + "max_tokens": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Tokens", + "dynamic": false, + "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", + "list": false, + "list_add_label": "Add More", + "name": "max_tokens", + "placeholder": "", + "range_spec": { + "max": 128000, + "min": 0, + "step": 0.1, + "step_type": "float" + }, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": "" + }, + "model_kwargs": { + "_input_type": "DictInput", + "advanced": true, + "display_name": "Model Kwargs", + "dynamic": false, + "info": "Additional keyword arguments to pass to the model.", + "list": false, + "list_add_label": "Add More", + "name": "model_kwargs", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "type": "dict", + "value": {} + }, + "model_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Model Name", + "dynamic": false, + "info": "", + "name": "model_name", + "options": [ + "gpt-4o-mini", + "gpt-4o", + "gpt-4-turbo", + "gpt-4-turbo-preview", + "gpt-4", + "gpt-3.5-turbo", + "gpt-3.5-turbo-0125" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "gpt-4o-mini" + }, + "openai_api_base": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "OpenAI API Base", + "dynamic": false, + "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "openai_api_base", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "seed": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Seed", + "dynamic": false, + "info": "The seed controls the reproducibility of the job.", + "list": false, + "list_add_label": "Add More", + "name": "seed", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 1 + }, + "stream": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Stream", + "dynamic": false, + "info": "Stream the response from the model. Streaming works only in Chat.", + "list": false, + "list_add_label": "Add More", + "name": "stream", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "system_message": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "System Message", + "dynamic": false, + "info": "System message to pass to the model.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "system_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "temperature": { + "_input_type": "SliderInput", + "advanced": false, + "display_name": "Temperature", + "dynamic": false, + "info": "", + "max_label": "", + "max_label_icon": "", + "min_label": "", + "min_label_icon": "", + "name": "temperature", + "placeholder": "", + "range_spec": { + "max": 2, + "min": 0, + "step": 0.01, + "step_type": "float" + }, + "required": false, + "show": true, + "slider_buttons": false, + "slider_buttons_options": [], + "slider_input": false, + "title_case": false, + "tool_mode": false, + "type": "slider", + "value": 0.1 + }, + "timeout": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Timeout", + "dynamic": false, + "info": "The timeout for requests to OpenAI completion API.", + "list": false, + "list_add_label": "Add More", + "name": "timeout", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 700 + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "OpenAIModel" + }, + "dragging": false, + "id": "OpenAIModel-QgyC5", + "measured": { + "height": 651, + "width": 320 + }, + "position": { + "x": 192.79820011992825, + "y": 5360.663143346016 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ParseDataFrame-Ni6HW", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert a DataFrame into plain text following a specified template. Each column in the DataFrame is treated as a possible template key, e.g. {col_name}.", + "display_name": "Parse DataFrame", + "documentation": "", + "edited": false, + "field_order": [ + "df", + "template", + "sep" + ], + "frozen": false, + "icon": "braces", + "key": "ParseDataFrame", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Text", + "method": "parse_data", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.007568328950209746, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.io import DataFrameInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataFrameComponent(Component):\n display_name = \"Parse DataFrame\"\n description = (\n \"Convert a DataFrame into plain text following a specified template. \"\n \"Each column in the DataFrame is treated as a possible template key, e.g. {col_name}.\"\n )\n icon = \"braces\"\n name = \"ParseDataFrame\"\n\n inputs = [\n DataFrameInput(name=\"df\", display_name=\"DataFrame\", info=\"The DataFrame to convert to text rows.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=(\n \"The template for formatting each row. \"\n \"Use placeholders matching column names in the DataFrame, for example '{col1}', '{col2}'.\"\n ),\n value=\"{text}\",\n ),\n StrInput(\n name=\"sep\",\n display_name=\"Separator\",\n advanced=True,\n value=\"\\n\",\n info=\"String that joins all row texts when building the single Text output.\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Text\",\n name=\"text\",\n info=\"All rows combined into a single text, each row formatted by the template and separated by `sep`.\",\n method=\"parse_data\",\n ),\n ]\n\n def _clean_args(self):\n dataframe = self.df\n template = self.template or \"{text}\"\n sep = self.sep or \"\\n\"\n return dataframe, template, sep\n\n def parse_data(self) -> Message:\n \"\"\"Converts each row of the DataFrame into a formatted string using the template.\n\n then joins them with `sep`. Returns a single combined string as a Message.\n \"\"\"\n dataframe, template, sep = self._clean_args()\n\n lines = []\n # For each row in the DataFrame, build a dict and format\n for _, row in dataframe.iterrows():\n row_dict = row.to_dict()\n text_line = template.format(**row_dict) # e.g. template=\"{text}\", row_dict={\"text\": \"Hello\"}\n lines.append(text_line)\n\n # Join all lines with the provided separator\n result_string = sep.join(lines)\n self.status = result_string # store in self.status for UI logs\n return Message(text=result_string)\n" + }, + "df": { + "_input_type": "DataFrameInput", + "advanced": false, + "display_name": "DataFrame", + "dynamic": false, + "info": "The DataFrame to convert to text rows.", + "input_types": [ + "DataFrame" + ], + "list": false, + "list_add_label": "Add More", + "name": "df", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "sep": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Separator", + "dynamic": false, + "info": "String that joins all row texts when building the single Text output.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sep", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n" + }, + "template": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Template", + "dynamic": false, + "info": "The template for formatting each row. Use placeholders matching column names in the DataFrame, for example '{col1}', '{col2}'.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{model_response}" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ParseDataFrame" + }, + "dragging": false, + "id": "ParseDataFrame-Ni6HW", + "measured": { + "height": 331, + "width": 320 + }, + "position": { + "x": 993.5819211529395, + "y": 6076.201261805775 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "Agent-U1bxR", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "category": "agents", + "conditional_paths": [], + "custom_fields": {}, + "description": "Define the agent's instructions, then enter a task to complete using tools.", + "display_name": "YT-Insight", + "documentation": "", + "edited": false, + "field_order": [ + "agent_llm", + "max_tokens", + "model_kwargs", + "json_mode", + "model_name", + "openai_api_base", + "api_key", + "temperature", + "seed", + "system_prompt", + "tools", + "input_value", + "handle_parsing_errors", + "verbose", + "max_iterations", + "agent_description", + "memory", + "sender", + "sender_name", + "n_messages", + "session_id", + "order", + "template", + "add_current_date_tool" + ], + "frozen": false, + "icon": "bot", + "key": "Agent", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Response", + "method": "message_response", + "name": "response", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 1.1732828199964098e-19, + "template": { + "_type": "Component", + "add_current_date_tool": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Current Date", + "dynamic": false, + "info": "If true, will add a tool to the agent that returns the current date.", + "list": false, + "list_add_label": "Add More", + "name": "add_current_date_tool", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "agent_description": { + "_input_type": "MultilineInput", + "advanced": true, + "display_name": "Agent Description [Deprecated]", + "dynamic": false, + "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "agent_description", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "A helpful assistant with access to the following tools:" + }, + "agent_llm": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Model Provider", + "dynamic": false, + "info": "The provider of the language model that the agent will use to generate responses.", + "input_types": [], + "name": "agent_llm", + "options": [ + "Amazon Bedrock", + "Anthropic", + "Azure OpenAI", + "Google Generative AI", + "Groq", + "NVIDIA", + "OpenAI", + "SambaNova", + "Custom" + ], + "options_metadata": [], + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "OpenAI" + }, + "api_key": { + "_input_type": "SecretStrInput", + "advanced": false, + "display_name": "OpenAI API Key", + "dynamic": false, + "info": "The OpenAI API Key to use for the OpenAI model.", + "input_types": [ + "Message" + ], + "load_from_db": true, + "name": "api_key", + "password": true, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "str", + "value": "OPENAI_API_KEY" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Validate tools\n if not self.tools:\n msg = \"Tools are required to run the agent. Please add at least one tool.\"\n raise ValueError(msg)\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools,\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def to_toolkit(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n" + }, + "handle_parsing_errors": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Handle Parse Errors", + "dynamic": false, + "info": "Should the Agent fix errors when reading user input for better processing?", + "list": false, + "list_add_label": "Add More", + "name": "handle_parsing_errors", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "input_value": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Input", + "dynamic": false, + "info": "The input provided by the user for the agent to process.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "json_mode": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "JSON Mode", + "dynamic": false, + "info": "If True, it will output JSON regardless of passing a schema.", + "list": false, + "list_add_label": "Add More", + "name": "json_mode", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "max_iterations": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Iterations", + "dynamic": false, + "info": "The maximum number of attempts the agent can make to complete its task before it stops.", + "list": false, + "list_add_label": "Add More", + "name": "max_iterations", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 15 + }, + "max_retries": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Retries", + "dynamic": false, + "info": "The maximum number of retries to make when generating.", + "list": false, + "list_add_label": "Add More", + "name": "max_retries", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 5 + }, + "max_tokens": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Tokens", + "dynamic": false, + "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", + "list": false, + "list_add_label": "Add More", + "name": "max_tokens", + "placeholder": "", + "range_spec": { + "max": 128000, + "min": 0, + "step": 0.1, + "step_type": "float" + }, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": "" + }, + "memory": { + "_input_type": "HandleInput", + "advanced": true, + "display_name": "External Memory", + "dynamic": false, + "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.", + "input_types": [ + "Memory" + ], + "list": false, + "list_add_label": "Add More", + "name": "memory", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "model_kwargs": { + "_input_type": "DictInput", + "advanced": true, + "display_name": "Model Kwargs", + "dynamic": false, + "info": "Additional keyword arguments to pass to the model.", + "list": false, + "list_add_label": "Add More", + "name": "model_kwargs", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "type": "dict", + "value": {} + }, + "model_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": true, + "dialog_inputs": {}, + "display_name": "Model Name", + "dynamic": false, + "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", + "name": "model_name", + "options": [ + "gpt-4o-mini", + "gpt-4o", + "gpt-4-turbo", + "gpt-4-turbo-preview", + "gpt-4", + "gpt-3.5-turbo", + "gpt-3.5-turbo-0125" + ], + "options_metadata": [], + "placeholder": "", + "real_time_refresh": false, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "gpt-4o-mini" + }, + "n_messages": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Number of Messages", + "dynamic": false, + "info": "Number of messages to retrieve.", + "list": false, + "list_add_label": "Add More", + "name": "n_messages", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 100 + }, + "openai_api_base": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "OpenAI API Base", + "dynamic": false, + "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "openai_api_base", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "order": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Order", + "dynamic": false, + "info": "Order of the messages.", + "name": "order", + "options": [ + "Ascending", + "Descending" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_metadata": true, + "type": "str", + "value": "Ascending" + }, + "seed": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Seed", + "dynamic": false, + "info": "The seed controls the reproducibility of the job.", + "list": false, + "list_add_label": "Add More", + "name": "seed", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 1 + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Filter by sender type.", + "name": "sender", + "options": [ + "Machine", + "User", + "Machine and User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Machine and User" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Filter by sender name.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Session ID", + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "system_prompt": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Agent Instructions", + "dynamic": false, + "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "system_prompt", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "You are a specialized assistant focused on comprehensive YouTube video analysis. Your main responsibilities are:\n\n1. Extract video transcripts using YouTubeTranscripts-get_message_output tool\n2. Process sentiment analysis from YouTube comments provided in XML format\n3. Create comprehensive analysis by combining:\n - Video content (from transcript)\n - Audience reception (from comment sentiment analysis)\n\nYour analysis should:\n- Identify main themes and topics from the video transcript\n- Evaluate audience sentiment patterns from provided comments\n- Highlight any disconnect between video content and audience reception\n- Provide actionable insights based on both content and reception\n\nInput received:\n- Video transcript (obtained through tool)\n- Sentiment analysis of comments in XML format containing:\n \n : Detailed analysis of comment\n : Positive/Neutral/Negative\n \n\nOutput format:\n1. Content Summary: Key points from transcript\n2. Audience Reception: Pattern analysis from sentiment data\n3. Synthesis: Combined analysis of content and reception\n4. Recommendations: Based on analysis" + }, + "temperature": { + "_input_type": "SliderInput", + "advanced": true, + "display_name": "Temperature", + "dynamic": false, + "info": "", + "max_label": "", + "max_label_icon": "", + "min_label": "", + "min_label_icon": "", + "name": "temperature", + "placeholder": "", + "range_spec": { + "max": 2, + "min": 0, + "step": 0.01, + "step_type": "float" + }, + "required": false, + "show": true, + "slider_buttons": false, + "slider_buttons_options": [], + "slider_input": false, + "title_case": false, + "tool_mode": false, + "type": "slider", + "value": 0.1 + }, + "template": { + "_input_type": "MultilineInput", + "advanced": true, + "display_name": "Template", + "dynamic": false, + "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{sender_name}: {text}" + }, + "timeout": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Timeout", + "dynamic": false, + "info": "The timeout for requests to OpenAI completion API.", + "list": false, + "list_add_label": "Add More", + "name": "timeout", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 700 + }, + "tools": { + "_input_type": "HandleInput", + "advanced": false, + "display_name": "Tools", + "dynamic": false, + "info": "These are the tools that the agent can use to help with tasks.", + "input_types": [ + "Tool" + ], + "list": true, + "list_add_label": "Add More", + "name": "tools", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "verbose": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Verbose", + "dynamic": false, + "info": "", + "list": false, + "list_add_label": "Add More", + "name": "verbose", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "Agent" + }, + "dragging": false, + "id": "Agent-U1bxR", + "measured": { + "height": 618, + "width": 320 + }, + "position": { + "x": 1982.1085644220088, + "y": 6262.507022218113 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "Prompt-ozK70", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": { + "template": [ + "url", + "analysis" + ] + }, + "description": "Create a prompt template with dynamic variables.", + "display_name": "Prompt", + "documentation": "", + "edited": false, + "error": null, + "field_order": [ + "template", + "tool_placeholder" + ], + "frozen": false, + "full_path": null, + "icon": "prompts", + "is_composition": null, + "is_input": null, + "is_output": null, + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "name": "", + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Prompt Message", + "method": "build_prompt", + "name": "prompt", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "analysis": { + "advanced": false, + "display_name": "analysis", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "analysis", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n" + }, + "template": { + "_input_type": "PromptInput", + "advanced": false, + "display_name": "Template", + "dynamic": false, + "info": "", + "list": false, + "list_add_label": "Add More", + "name": "template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "type": "prompt", + "value": "\nVideo URL:\n{url}\n\n\n Comment Analysis:\n{analysis}" + }, + "tool_placeholder": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Tool Placeholder", + "dynamic": false, + "info": "A placeholder input for tool mode.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "tool_placeholder", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "url": { + "advanced": false, + "display_name": "url", + "dynamic": false, + "field_type": "str", + "fileTypes": [], + "file_path": "", + "info": "", + "input_types": [ + "Message" + ], + "list": false, + "load_from_db": false, + "multiline": true, + "name": "url", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "Prompt" + }, + "dragging": false, + "id": "Prompt-ozK70", + "measured": { + "height": 417, + "width": 320 + }, + "position": { + "x": 1575.3649919098807, + "y": 6461.250996552967 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatOutput-BILzx", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "category": "outputs", + "conditional_paths": [], + "custom_fields": {}, + "description": "Display a chat message in the Playground.", + "display_name": "Chat Output", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "data_template", + "background_color", + "chat_icon", + "text_color" + ], + "frozen": false, + "icon": "MessagesSquare", + "key": "ChatOutput", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": true, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "message_response", + "name": "message", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.007568328950209746, + "template": { + "_type": "Component", + "background_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Background Color", + "dynamic": false, + "info": "The background color of the icon.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "background_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "chat_icon": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Icon", + "dynamic": false, + "info": "The icon of the message.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "chat_icon", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n MessageInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n message = self.input_value if isinstance(self.input_value, Message) else Message(text=self.input_value)\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" + }, + "data_template": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Data Template", + "dynamic": false, + "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "data_template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{text}" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Text", + "dynamic": false, + "info": "Message to be passed as output.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Type of sender.", + "name": "sender", + "options": [ + "Machine", + "User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Machine" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Name of the sender.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "AI" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Session ID", + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "should_store_message": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Store Messages", + "dynamic": false, + "info": "Store the message in the history.", + "list": false, + "list_add_label": "Add More", + "name": "should_store_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "text_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Text Color", + "dynamic": false, + "info": "The text color of the name", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "text_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ChatOutput" + }, + "dragging": false, + "id": "ChatOutput-BILzx", + "measured": { + "height": 228, + "width": 320 + }, + "position": { + "x": 2365.8487393880428, + "y": 6653.021671139973 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "YouTubeTranscripts-wPf1w", + "node": { + "base_classes": [ + "DataFrame", + "Message" + ], + "beta": false, + "category": "youtube", + "conditional_paths": [], + "custom_fields": {}, + "description": "Extracts spoken content from YouTube videos with both DataFrame and text output options.", + "display_name": "YouTube Transcripts", + "documentation": "", + "edited": false, + "field_order": [ + "url", + "chunk_size_seconds", + "translation" + ], + "frozen": false, + "icon": "YouTube", + "key": "YouTubeTranscripts", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Toolset", + "hidden": null, + "method": "to_toolkit", + "name": "component_as_tool", + "required_inputs": null, + "selected": "Tool", + "tool_mode": true, + "types": [ + "Tool" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 7.568328950209746e-6, + "template": { + "_type": "Component", + "chunk_size_seconds": { + "_input_type": "IntInput", + "advanced": false, + "display_name": "Chunk Size (seconds)", + "dynamic": false, + "info": "The size of each transcript chunk in seconds.", + "list": false, + "list_add_label": "Add More", + "name": "chunk_size_seconds", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 60 + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import pandas as pd\nimport youtube_transcript_api\nfrom langchain_community.document_loaders import YoutubeLoader\nfrom langchain_community.document_loaders.youtube import TranscriptFormat\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DropdownInput, IntInput, MultilineInput\nfrom langflow.schema import DataFrame, Message\nfrom langflow.template import Output\n\n\nclass YouTubeTranscriptsComponent(Component):\n \"\"\"A component that extracts spoken content from YouTube videos as transcripts.\"\"\"\n\n display_name: str = \"YouTube Transcripts\"\n description: str = \"Extracts spoken content from YouTube videos with both DataFrame and text output options.\"\n icon: str = \"YouTube\"\n name = \"YouTubeTranscripts\"\n\n inputs = [\n MultilineInput(\n name=\"url\",\n display_name=\"Video URL\",\n info=\"Enter the YouTube video URL to get transcripts from.\",\n tool_mode=True,\n required=True,\n ),\n IntInput(\n name=\"chunk_size_seconds\",\n display_name=\"Chunk Size (seconds)\",\n value=60,\n info=\"The size of each transcript chunk in seconds.\",\n ),\n DropdownInput(\n name=\"translation\",\n display_name=\"Translation Language\",\n advanced=True,\n options=[\"\", \"en\", \"es\", \"fr\", \"de\", \"it\", \"pt\", \"ru\", \"ja\", \"ko\", \"hi\", \"ar\", \"id\"],\n info=\"Translate the transcripts to the specified language. Leave empty for no translation.\",\n ),\n ]\n\n outputs = [\n Output(name=\"dataframe\", display_name=\"Chunks\", method=\"get_dataframe_output\"),\n Output(name=\"message\", display_name=\"Transcript\", method=\"get_message_output\"),\n ]\n\n def _load_transcripts(self, *, as_chunks: bool = True):\n \"\"\"Internal method to load transcripts from YouTube.\"\"\"\n loader = YoutubeLoader.from_youtube_url(\n self.url,\n transcript_format=TranscriptFormat.CHUNKS if as_chunks else TranscriptFormat.TEXT,\n chunk_size_seconds=self.chunk_size_seconds,\n translation=self.translation or None,\n )\n return loader.load()\n\n def get_dataframe_output(self) -> DataFrame:\n \"\"\"Provides transcript output as a DataFrame with timestamp and text columns.\"\"\"\n try:\n transcripts = self._load_transcripts(as_chunks=True)\n\n # Create DataFrame with timestamp and text columns\n data = []\n for doc in transcripts:\n start_seconds = int(doc.metadata[\"start_seconds\"])\n start_minutes = start_seconds // 60\n start_seconds %= 60\n timestamp = f\"{start_minutes:02d}:{start_seconds:02d}\"\n data.append({\"timestamp\": timestamp, \"text\": doc.page_content})\n return DataFrame(pd.DataFrame(data))\n\n except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:\n return DataFrame(pd.DataFrame({\"error\": [f\"Failed to get YouTube transcripts: {exc!s}\"]}))\n\n def get_message_output(self) -> Message:\n \"\"\"Provides transcript output as continuous text.\"\"\"\n try:\n transcripts = self._load_transcripts(as_chunks=False)\n result = transcripts[0].page_content\n return Message(text=result)\n\n except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:\n error_msg = f\"Failed to get YouTube transcripts: {exc!s}\"\n return Message(text=error_msg)\n" + }, + "tools_metadata": { + "_input_type": "TableInput", + "advanced": false, + "display_name": "Edit tools", + "dynamic": false, + "info": "", + "is_list": true, + "list_add_label": "Add More", + "name": "tools_metadata", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "table_icon": "Hammer", + "table_options": { + "block_add": true, + "block_delete": true, + "block_edit": true, + "block_filter": true, + "block_hide": true, + "block_select": true, + "block_sort": true, + "description": "Modify tool names and descriptions to help agents understand when to use each tool.", + "field_parsers": { + "commands": "commands", + "name": [ + "snake_case", + "no_blank" + ] + }, + "hide_options": true + }, + "table_schema": { + "columns": [ + { + "description": "Specify the name of the tool.", + "disable_edit": false, + "display_name": "Tool Name", + "edit_mode": "inline", + "filterable": false, + "formatter": "text", + "name": "name", + "sortable": false, + "type": "text" + }, + { + "description": "Describe the purpose of the tool.", + "disable_edit": false, + "display_name": "Tool Description", + "edit_mode": "popover", + "filterable": false, + "formatter": "text", + "name": "description", + "sortable": false, + "type": "text" + }, + { + "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", + "name": "tags", + "sortable": false, + "type": "text" + } + ] + }, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "trigger_icon": "Hammer", + "trigger_text": "", + "type": "table", + "value": [ + { + "description": "get_dataframe_output(url: Message) - Extracts spoken content from YouTube videos with both DataFrame and text output options.", + "name": "YouTubeTranscripts-get_dataframe_output", + "tags": [ + "YouTubeTranscripts-get_dataframe_output" + ] + }, + { + "description": "get_message_output(url: Message) - Extracts spoken content from YouTube videos with both DataFrame and text output options.", + "name": "YouTubeTranscripts-get_message_output", + "tags": [ + "YouTubeTranscripts-get_message_output" + ] + } + ] + }, + "translation": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Translation Language", + "dynamic": false, + "info": "Translate the transcripts to the specified language. Leave empty for no translation.", + "name": "translation", + "options": [ + "", + "en", + "es", + "fr", + "de", + "it", + "pt", + "ru", + "ja", + "ko", + "hi", + "ar", + "id" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "url": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Video URL", + "dynamic": false, + "info": "Enter the YouTube video URL to get transcripts from.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "url", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": true + }, + "showNode": true, + "type": "YouTubeTranscripts" + }, + "dragging": false, + "id": "YouTubeTranscripts-wPf1w", + "measured": { + "height": 413, + "width": 320 + }, + "position": { + "x": 1577.7800211610804, + "y": 5995.403751540062 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatInput-VrWjf", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "category": "inputs", + "conditional_paths": [], + "custom_fields": {}, + "description": "Get chat inputs from the Playground.", + "display_name": "URL video", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "files", + "background_color", + "chat_icon", + "text_color" + ], + "frozen": false, + "icon": "MessagesSquare", + "key": "ChatInput", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": true, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "message_response", + "name": "message", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.0020353564437605998, + "template": { + "_type": "Component", + "background_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Background Color", + "dynamic": false, + "info": "The background color of the icon.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "background_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "chat_icon": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Icon", + "dynamic": false, + "info": "The icon of the message.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "chat_icon", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" + }, + "files": { + "_input_type": "FileInput", + "advanced": true, + "display_name": "Files", + "dynamic": false, + "fileTypes": [ + "txt", + "md", + "mdx", + "csv", + "json", + "yaml", + "yml", + "xml", + "html", + "htm", + "pdf", + "docx", + "py", + "sh", + "sql", + "js", + "ts", + "tsx", + "jpg", + "jpeg", + "png", + "bmp", + "image" + ], + "file_path": "", + "info": "Files to be sent with the message.", + "list": true, + "list_add_label": "Add More", + "name": "files", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "file", + "value": "" + }, + "input_value": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Text", + "dynamic": false, + "info": "Message to be passed as input.", + "input_types": [], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "https://www.youtube.com/watch?v=8f61j3W-27U&ab_channel=Langflow" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Type of sender.", + "name": "sender", + "options": [ + "Machine", + "User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "User" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Name of the sender.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "User" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Session ID", + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "should_store_message": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Store Messages", + "dynamic": false, + "info": "Store the message in the history.", + "list": false, + "list_add_label": "Add More", + "name": "should_store_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "text_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Text Color", + "dynamic": false, + "info": "The text color of the name", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "text_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ChatInput" + }, + "dragging": false, + "id": "ChatInput-VrWjf", + "measured": { + "height": 227, + "width": 320 + }, + "position": { + "x": -894.6710448870117, + "y": 6583.98979261026 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "note-n9LmZ", + "node": { + "description": "# Batch Run component\n\nThis component processes a DataFrame by running each row through a Language Model (LLM). Perfect for batch analysis, sentiment scoring, or content generation at scale.\n\n## How It Works\n1. Accepts a DataFrame with text data.\n2. Routes each row through your chosen LLM.\n3. Returns new DataFrame with `text_input` and `model_response`.\n\n", + "display_name": "", + "documentation": "", + "template": { + "backgroundColor": "neutral" + } + }, + "type": "note" + }, + "dragging": false, + "height": 522, + "id": "note-n9LmZ", + "measured": { + "height": 522, + "width": 325 + }, + "position": { + "x": 631.7137680312561, + "y": 5413.536732789538 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 324 + }, + { + "data": { + "id": "note-piUDG", + "node": { + "description": "## Set up the YouTube API\n1. Go to [Google Cloud Console](https://console.cloud.google.com).\n2. Create a new project or select existing one.\n3. Enable YouTube Data API v3:\n - Navigate to APIs & Services > Library.\n - Search \"YouTube Data API v3\".\n - Click Enable.\n4. Create credentials:\n - Go to APIs & Services > Credentials.\n - Click Create Credentials > API Key.\n5. Copy your new API key for use in the component.\n\n⚠️ Remember to:\n- Restrict the API key to YouTube Data API v3 only.\n- Set appropriate quotas and restrictions.\n", + "display_name": "", + "documentation": "", + "template": { + "backgroundColor": "neutral" + } + }, + "type": "note" + }, + "dragging": false, + "height": 486, + "id": "note-piUDG", + "measured": { + "height": 486, + "width": 325 + }, + "position": { + "x": 1579.119903578572, + "y": 5472.458002453355 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 324 + }, + { + "data": { + "id": "note-qq8Uo", + "node": { + "description": "# 🎥 YouTube Video Analysis\nThis flow performs comprehensive analysis of YouTube videos.\n1. Extract video comments and transcripts.\n2. Run sentiment analysis on comments using LLM.\n3. Combine transcript content and comment sentiment for comprehensive video analysis.\n\n## Prerequisites\n- OpenAI API Key\n- YouTube Data API v3 key", + "display_name": "", + "documentation": "", + "template": { + "backgroundColor": "neutral" + } + }, + "type": "note" + }, + "dragging": false, + "height": 454, + "id": "note-qq8Uo", + "measured": { + "height": 454, + "width": 433 + }, + "position": { + "x": -1366.105596485301, + "y": 6422.034220724462 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 432 + }, + { + "data": { + "id": "ConditionalRouter-CfANV", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "category": "logic", + "conditional_paths": [], + "custom_fields": {}, + "description": "Routes an input message to a corresponding output based on text comparison.", + "display_name": "If-Else", + "documentation": "", + "edited": false, + "field_order": [ + "input_text", + "match_text", + "operator", + "case_sensitive", + "message", + "max_iterations", + "default_route" + ], + "frozen": false, + "icon": "split", + "key": "ConditionalRouter", + "legacy": false, + "lf_version": "1.1.3", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "True", + "method": "true_response", + "name": "true_result", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "False", + "method": "false_response", + "name": "false_result", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.001, + "template": { + "_type": "Component", + "case_sensitive": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Case Sensitive", + "dynamic": false, + "info": "If true, the comparison will be case sensitive.", + "list": false, + "list_add_label": "Add More", + "name": "case_sensitive", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import re\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\n\n\nclass ConditionalRouterComponent(Component):\n display_name = \"If-Else\"\n description = \"Routes an input message to a corresponding output based on text comparison.\"\n icon = \"split\"\n name = \"ConditionalRouter\"\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self.__iteration_updated = False\n\n inputs = [\n MessageTextInput(\n name=\"input_text\",\n display_name=\"Text Input\",\n info=\"The primary text input for the operation.\",\n required=True,\n ),\n MessageTextInput(\n name=\"match_text\",\n display_name=\"Match Text\",\n info=\"The text input to compare against.\",\n required=True,\n ),\n DropdownInput(\n name=\"operator\",\n display_name=\"Operator\",\n options=[\"equals\", \"not equals\", \"contains\", \"starts with\", \"ends with\", \"regex\"],\n info=\"The operator to apply for comparing the texts.\",\n value=\"equals\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"case_sensitive\",\n display_name=\"Case Sensitive\",\n info=\"If true, the comparison will be case sensitive.\",\n value=False,\n ),\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The message to pass through either route.\",\n ),\n IntInput(\n name=\"max_iterations\",\n display_name=\"Max Iterations\",\n info=\"The maximum number of iterations for the conditional router.\",\n value=10,\n advanced=True,\n ),\n DropdownInput(\n name=\"default_route\",\n display_name=\"Default Route\",\n options=[\"true_result\", \"false_result\"],\n info=\"The default route to take when max iterations are reached.\",\n value=\"false_result\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"True\", name=\"true_result\", method=\"true_response\"),\n Output(display_name=\"False\", name=\"false_result\", method=\"false_response\"),\n ]\n\n def _pre_run_setup(self):\n self.__iteration_updated = False\n\n def evaluate_condition(self, input_text: str, match_text: str, operator: str, *, case_sensitive: bool) -> bool:\n if not case_sensitive and operator != \"regex\":\n input_text = input_text.lower()\n match_text = match_text.lower()\n\n if operator == \"equals\":\n return input_text == match_text\n if operator == \"not equals\":\n return input_text != match_text\n if operator == \"contains\":\n return match_text in input_text\n if operator == \"starts with\":\n return input_text.startswith(match_text)\n if operator == \"ends with\":\n return input_text.endswith(match_text)\n if operator == \"regex\":\n try:\n return bool(re.match(match_text, input_text))\n except re.error:\n return False # Return False if the regex is invalid\n return False\n\n def iterate_and_stop_once(self, route_to_stop: str):\n if not self.__iteration_updated:\n self.update_ctx({f\"{self._id}_iteration\": self.ctx.get(f\"{self._id}_iteration\", 0) + 1})\n self.__iteration_updated = True\n if self.ctx.get(f\"{self._id}_iteration\", 0) >= self.max_iterations and route_to_stop == self.default_route:\n route_to_stop = \"true_result\" if route_to_stop == \"false_result\" else \"false_result\"\n self.stop(route_to_stop)\n\n def true_response(self) -> Message:\n result = self.evaluate_condition(\n self.input_text, self.match_text, self.operator, case_sensitive=self.case_sensitive\n )\n if result:\n self.status = self.message\n self.iterate_and_stop_once(\"false_result\")\n return self.message\n self.iterate_and_stop_once(\"true_result\")\n return Message(content=\"\")\n\n def false_response(self) -> Message:\n result = self.evaluate_condition(\n self.input_text, self.match_text, self.operator, case_sensitive=self.case_sensitive\n )\n if not result:\n self.status = self.message\n self.iterate_and_stop_once(\"true_result\")\n return self.message\n self.iterate_and_stop_once(\"false_result\")\n return Message(content=\"\")\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n if field_name == \"operator\":\n if field_value == \"regex\":\n build_config.pop(\"case_sensitive\", None)\n\n # Ensure case_sensitive is present for all other operators\n elif \"case_sensitive\" not in build_config:\n case_sensitive_input = next(\n (input_field for input_field in self.inputs if input_field.name == \"case_sensitive\"), None\n )\n if case_sensitive_input:\n build_config[\"case_sensitive\"] = case_sensitive_input.to_dict()\n return build_config\n" + }, + "default_route": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Default Route", + "dynamic": false, + "info": "The default route to take when max iterations are reached.", + "name": "default_route", + "options": [ + "true_result", + "false_result" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "false_result" + }, + "input_text": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Text Input", + "dynamic": false, + "info": "The primary text input for the operation.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_text", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "match_text": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Match Text", + "dynamic": false, + "info": "The text input to compare against.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "match_text", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "^(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/)([A-Za-z0-9_-]{11})(?:&\\S*)?$" + }, + "max_iterations": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Iterations", + "dynamic": false, + "info": "The maximum number of iterations for the conditional router.", + "list": false, + "list_add_label": "Add More", + "name": "max_iterations", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 10 + }, + "message": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Message", + "dynamic": false, + "info": "The message to pass through either route.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "operator": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Operator", + "dynamic": false, + "info": "The operator to apply for comparing the texts.", + "name": "operator", + "options": [ + "equals", + "not equals", + "contains", + "starts with", + "ends with", + "regex" + ], + "options_metadata": [], + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "regex" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ConditionalRouter" + }, + "dragging": false, + "id": "ConditionalRouter-CfANV", + "measured": { + "height": 541, + "width": 320 + }, + "position": { + "x": -352.80314888328695, + "y": 6273.805228201546 + }, + "selected": false, + "type": "genericNode" + } + ], + "viewport": { + "x": 437.31759577518767, + "y": -4009.9990846628743, + "zoom": 0.6745353230252618 + } + }, + "description": "The YouTube Analysis flow extracts video comments and transcripts, analyzing sentiment patterns and content themes.", + "endpoint_name": null, + "icon": "Youtube", + "id": "3b0d43c2-dc2c-4906-8a3e-850702b534c4", + "is_component": false, + "last_tested_version": "1.1.3", + "name": "Youtube Analysis", + "tags": [ + "agents", + "assistants" + ] +} \ No newline at end of file