diff --git a/src/backend/base/langflow/components/agents/ToolCallingAgent.py b/src/backend/base/langflow/components/agents/ToolCallingAgent.py index 8c08d489b..a9403e861 100644 --- a/src/backend/base/langflow/components/agents/ToolCallingAgent.py +++ b/src/backend/base/langflow/components/agents/ToolCallingAgent.py @@ -1,19 +1,29 @@ -from typing import List, cast +from typing import List, Optional -from langchain.agents import AgentExecutor, BaseSingleActionAgent from langchain.agents.tool_calling_agent.base import create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate +<<<<<<< Updated upstream from langflow.custom import Component from langflow.io import BoolInput, HandleInput, MessageTextInput, Output from langflow.schema import Data +======= +from langchain.agents import AgentExecutor +from langchain_core.messages import BaseMessage +>>>>>>> Stashed changes from langflow.schema.message import Message +from langflow.custom import Component +from langflow.io import HandleInput, TextInput, BoolInput, Output +from langflow.schema import Data class ToolCallingAgentComponent(Component): display_name: str = "Tool Calling Agent" - description: str = "Agent that uses tools. Only models that are compatible with function calling are supported." + description: str = ( + "Agent that uses tools. Only models that are compatible with function calling are supported." + ) icon = "LangChain" + beta = True inputs = [ MessageTextInput( @@ -80,12 +90,12 @@ class ToolCallingAgentComponent(Component): agent = create_tool_calling_agent(self.llm, self.tools, prompt) runnable = AgentExecutor.from_agent_and_tools( - agent=cast(BaseSingleActionAgent, agent), + agent=agent, tools=self.tools, verbose=True, handle_parsing_errors=self.handle_parsing_errors, ) - input_dict: dict[str, str | list[dict[str, str]]] = {"input": self.input_value} + input_dict: dict[str, str | list[BaseMessage]] = {"input": self.input_value} if hasattr(self, "memory") and self.memory: input_dict["chat_history"] = self.convert_chat_history(self.memory) result = await runnable.ainvoke(input_dict) @@ -98,7 +108,7 @@ class ToolCallingAgentComponent(Component): return Message(text=result_string) - def convert_chat_history(self, chat_history: List[Data]) -> List[dict[str, str]]: + def convert_chat_history(self, chat_history: List[Data]) -> List[Dict[str, str]]: messages = [] for item in chat_history: role = "user" if item.sender == "User" else "assistant" diff --git a/src/backend/base/langflow/components/experimental/AgentComponent.py b/src/backend/base/langflow/components/deactivated/AgentComponent.py similarity index 100% rename from src/backend/base/langflow/components/experimental/AgentComponent.py rename to src/backend/base/langflow/components/deactivated/AgentComponent.py diff --git a/src/backend/base/langflow/components/helpers/CodeBlockExtractor.py b/src/backend/base/langflow/components/deactivated/CodeBlockExtractor.py similarity index 100% rename from src/backend/base/langflow/components/helpers/CodeBlockExtractor.py rename to src/backend/base/langflow/components/deactivated/CodeBlockExtractor.py diff --git a/src/backend/base/langflow/components/helpers/DocumentsToData.py b/src/backend/base/langflow/components/deactivated/DocumentsToData.py similarity index 100% rename from src/backend/base/langflow/components/helpers/DocumentsToData.py rename to src/backend/base/langflow/components/deactivated/DocumentsToData.py diff --git a/src/backend/base/langflow/components/experimental/Embed.py b/src/backend/base/langflow/components/deactivated/Embed.py similarity index 100% rename from src/backend/base/langflow/components/experimental/Embed.py rename to src/backend/base/langflow/components/deactivated/Embed.py diff --git a/src/backend/base/langflow/components/experimental/ExtractKeyFromData.py b/src/backend/base/langflow/components/deactivated/ExtractKeyFromData.py similarity index 100% rename from src/backend/base/langflow/components/experimental/ExtractKeyFromData.py rename to src/backend/base/langflow/components/deactivated/ExtractKeyFromData.py diff --git a/src/backend/base/langflow/components/experimental/ListFlows.py b/src/backend/base/langflow/components/deactivated/ListFlows.py similarity index 100% rename from src/backend/base/langflow/components/experimental/ListFlows.py rename to src/backend/base/langflow/components/deactivated/ListFlows.py diff --git a/src/backend/base/langflow/components/experimental/MergeData.py b/src/backend/base/langflow/components/deactivated/MergeData.py similarity index 100% rename from src/backend/base/langflow/components/experimental/MergeData.py rename to src/backend/base/langflow/components/deactivated/MergeData.py diff --git a/src/backend/base/langflow/components/experimental/Message.py b/src/backend/base/langflow/components/deactivated/Message.py similarity index 100% rename from src/backend/base/langflow/components/experimental/Message.py rename to src/backend/base/langflow/components/deactivated/Message.py diff --git a/src/backend/base/langflow/components/experimental/SelectivePassThrough.py b/src/backend/base/langflow/components/deactivated/SelectivePassThrough.py similarity index 100% rename from src/backend/base/langflow/components/experimental/SelectivePassThrough.py rename to src/backend/base/langflow/components/deactivated/SelectivePassThrough.py diff --git a/src/backend/base/langflow/components/helpers/ShouldRunNext.py b/src/backend/base/langflow/components/deactivated/ShouldRunNext.py similarity index 100% rename from src/backend/base/langflow/components/helpers/ShouldRunNext.py rename to src/backend/base/langflow/components/deactivated/ShouldRunNext.py diff --git a/src/backend/base/langflow/components/deactivated/SplitText.py b/src/backend/base/langflow/components/deactivated/SplitText.py new file mode 100644 index 000000000..73e87504c --- /dev/null +++ b/src/backend/base/langflow/components/deactivated/SplitText.py @@ -0,0 +1,69 @@ +from typing import List + +from langchain_text_splitters import CharacterTextSplitter +from langflow.custom import Component +from langflow.io import HandleInput, IntInput, Output, TextInput +from langflow.schema import Data +from langflow.utils.util import unescape_string + + +class SplitTextComponent(Component): + display_name: str = "Split Text" + description: str = "Split text into chunks based on specified criteria." + icon = "scissors-line-dashed" + + inputs = [ + HandleInput( + name="data_inputs", + display_name="Data Inputs", + info="The data to split.", + input_types=["Data"], + is_list=True, + ), + IntInput( + name="chunk_overlap", + display_name="Chunk Overlap", + info="Number of characters to overlap between chunks.", + value=200, + ), + IntInput( + name="chunk_size", + display_name="Chunk Size", + info="The maximum number of characters in each chunk.", + value=1000, + ), + TextInput( + name="separator", + display_name="Separator", + info="The character to split on. Defaults to newline.", + value="\n", + ), + ] + + outputs = [ + Output(display_name="Chunks", name="chunks", method="split_text"), + ] + + def _docs_to_data(self, docs): + data = [] + for doc in docs: + data.append(Data(text=doc.page_content, data=doc.metadata)) + return data + + def split_text(self) -> List[Data]: + separator = unescape_string(self.separator) + + documents = [] + for _input in self.data_inputs: + if isinstance(_input, Data): + documents.append(_input.to_lc_document()) + + splitter = CharacterTextSplitter( + chunk_overlap=self.chunk_overlap, + chunk_size=self.chunk_size, + separator=separator, + ) + docs = splitter.split_documents(documents) + data = self._docs_to_data(docs) + self.status = data + return data diff --git a/src/backend/base/langflow/components/experimental/StoreMessage.py b/src/backend/base/langflow/components/deactivated/StoreMessage.py similarity index 100% rename from src/backend/base/langflow/components/experimental/StoreMessage.py rename to src/backend/base/langflow/components/deactivated/StoreMessage.py diff --git a/src/backend/base/langflow/components/experimental/SubFlow.py b/src/backend/base/langflow/components/deactivated/SubFlow.py similarity index 100% rename from src/backend/base/langflow/components/experimental/SubFlow.py rename to src/backend/base/langflow/components/deactivated/SubFlow.py diff --git a/src/backend/base/langflow/components/experimental/__init__.py b/src/backend/base/langflow/components/deactivated/__init__.py similarity index 100% rename from src/backend/base/langflow/components/experimental/__init__.py rename to src/backend/base/langflow/components/deactivated/__init__.py diff --git a/src/backend/base/langflow/components/helpers/FilterData.py b/src/backend/base/langflow/components/helpers/FilterData.py index e589c2aa5..94551854d 100644 --- a/src/backend/base/langflow/components/helpers/FilterData.py +++ b/src/backend/base/langflow/components/helpers/FilterData.py @@ -9,6 +9,7 @@ class FilterDataComponent(Component): display_name = "Filter Data" description = "Filters a Data object based on a list of keys." icon = "filter" + beta = True inputs = [ DataInput( diff --git a/src/backend/base/langflow/components/helpers/IDGenerator.py b/src/backend/base/langflow/components/helpers/IDGenerator.py index 1e4e223b1..bf583229e 100644 --- a/src/backend/base/langflow/components/helpers/IDGenerator.py +++ b/src/backend/base/langflow/components/helpers/IDGenerator.py @@ -5,14 +5,12 @@ from langflow.custom import CustomComponent class UUIDGeneratorComponent(CustomComponent): - documentation: str = "http://docs.langflow.org/components/custom" display_name = "ID Generator" description = "Generates a unique ID." def update_build_config( self, build_config: dict, - field_value: Any, field_name: Optional[str] = None, ): if field_name == "unique_id": diff --git a/src/backend/base/langflow/components/helpers/MergeData.py b/src/backend/base/langflow/components/helpers/MergeData.py new file mode 100644 index 000000000..5d434d3cf --- /dev/null +++ b/src/backend/base/langflow/components/helpers/MergeData.py @@ -0,0 +1,26 @@ +from langflow.custom import CustomComponent +from langflow.schema import Data + + +class MergeDataComponent(CustomComponent): + display_name = "Merge Data" + description = "Combines multiple data sources into a single unified Data object." + beta: bool = True + + field_config = { + "data": {"display_name": "Data"}, + } + + def build(self, data: list[Data]) -> Data: + if not data: + return Data() + if len(data) == 1: + return data[0] + merged_data = Data() + for value in data: + if merged_data is None: + merged_data = value + else: + merged_data += value + self.status = merged_data + return merged_data diff --git a/src/backend/base/langflow/components/experimental/SplitText.py b/src/backend/base/langflow/components/helpers/SplitText.py similarity index 100% rename from src/backend/base/langflow/components/experimental/SplitText.py rename to src/backend/base/langflow/components/helpers/SplitText.py diff --git a/src/backend/base/langflow/components/helpers/StoreMessage.py b/src/backend/base/langflow/components/helpers/StoreMessage.py new file mode 100644 index 000000000..5d0abfbb9 --- /dev/null +++ b/src/backend/base/langflow/components/helpers/StoreMessage.py @@ -0,0 +1,22 @@ +from langflow.custom import CustomComponent +from langflow.memory import get_messages, store_message +from langflow.schema.message import Message + + +class StoreMessageComponent(CustomComponent): + display_name = "Store Message" + description = "Stores a chat message." + + def build_config(self): + return { + "message": {"display_name": "Message"}, + } + + def build( + self, + message: Message, + ) -> Message: + store_message(message, flow_id=self.graph.flow_id) + self.status = get_messages() + + return message diff --git a/src/backend/base/langflow/components/helpers/__init__.py b/src/backend/base/langflow/components/helpers/__init__.py index 1337aebd4..ab1f8d00a 100644 --- a/src/backend/base/langflow/components/helpers/__init__.py +++ b/src/backend/base/langflow/components/helpers/__init__.py @@ -1,15 +1,26 @@ +from .CombineText import CombineTextComponent from .CreateData import CreateDataComponent -from .CustomComponent import Component -from .ParseData import ParseDataComponent -from .DocumentsToData import DocumentsToDataComponent +from .CustomComponent import CustomComponent +from .FilterData import FilterDataComponent from .IDGenerator import UUIDGeneratorComponent +from .Memory import MemoryComponent +from .MergeData import MergeDataComponent +from .ParseData import ParseDataComponent +from .SplitText import SplitTextComponent +from .StoreMessage import StoreMessageComponent from .UpdateData import UpdateDataComponent + __all__ = [ - "Component", - "UpdateDataComponent", - "DocumentsToDataComponent", - "UUIDGeneratorComponent", - "ParseDataComponent", + "CombineTextComponent", "CreateDataComponent", + "CustomComponent", + "FilterDataComponent", + "UUIDGeneratorComponent", + "MemoryComponent", + "MergeDataComponent", + "ParseDataComponent", + "SplitTextComponent", + "StoreMessageComponent", + "UpdateDataComponent", ] diff --git a/src/backend/base/langflow/components/experimental/ConditionalRouter.py b/src/backend/base/langflow/components/prototypes/ConditionalRouter.py similarity index 100% rename from src/backend/base/langflow/components/experimental/ConditionalRouter.py rename to src/backend/base/langflow/components/prototypes/ConditionalRouter.py diff --git a/src/backend/base/langflow/components/experimental/FlowTool.py b/src/backend/base/langflow/components/prototypes/FlowTool.py similarity index 99% rename from src/backend/base/langflow/components/experimental/FlowTool.py rename to src/backend/base/langflow/components/prototypes/FlowTool.py index 103cd3c1f..a1c9bc4eb 100644 --- a/src/backend/base/langflow/components/experimental/FlowTool.py +++ b/src/backend/base/langflow/components/prototypes/FlowTool.py @@ -16,6 +16,7 @@ class FlowToolComponent(CustomComponent): description = "Construct a Tool from a function that runs the loaded Flow." field_order = ["flow_name", "name", "description", "return_direct"] trace_type = "tool" + beta = True def get_flow_names(self) -> List[str]: flow_datas = self.list_flows() diff --git a/src/backend/base/langflow/components/experimental/Listen.py b/src/backend/base/langflow/components/prototypes/Listen.py similarity index 100% rename from src/backend/base/langflow/components/experimental/Listen.py rename to src/backend/base/langflow/components/prototypes/Listen.py diff --git a/src/backend/base/langflow/components/experimental/Notify.py b/src/backend/base/langflow/components/prototypes/Notify.py similarity index 100% rename from src/backend/base/langflow/components/experimental/Notify.py rename to src/backend/base/langflow/components/prototypes/Notify.py diff --git a/src/backend/base/langflow/components/experimental/Pass.py b/src/backend/base/langflow/components/prototypes/Pass.py similarity index 98% rename from src/backend/base/langflow/components/experimental/Pass.py rename to src/backend/base/langflow/components/prototypes/Pass.py index d21fe0887..4e2f234c9 100644 --- a/src/backend/base/langflow/components/experimental/Pass.py +++ b/src/backend/base/langflow/components/prototypes/Pass.py @@ -9,6 +9,7 @@ class PassComponent(CustomComponent): display_name = "Pass" description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction." field_order = ["ignored_input", "forwarded_input"] + beta = True def build_config(self) -> dict: return { diff --git a/src/backend/base/langflow/components/experimental/PythonFunction.py b/src/backend/base/langflow/components/prototypes/PythonFunction.py similarity index 97% rename from src/backend/base/langflow/components/experimental/PythonFunction.py rename to src/backend/base/langflow/components/prototypes/PythonFunction.py index d832e2f5c..f0f238636 100644 --- a/src/backend/base/langflow/components/experimental/PythonFunction.py +++ b/src/backend/base/langflow/components/prototypes/PythonFunction.py @@ -9,6 +9,7 @@ class PythonFunctionComponent(CustomComponent): display_name = "Python Function" description = "Define a Python function." icon = "Python" + beta = True def build_config(self): return { diff --git a/src/backend/base/langflow/components/experimental/RunFlow.py b/src/backend/base/langflow/components/prototypes/RunFlow.py similarity index 100% rename from src/backend/base/langflow/components/experimental/RunFlow.py rename to src/backend/base/langflow/components/prototypes/RunFlow.py diff --git a/src/backend/base/langflow/components/experimental/RunnableExecutor.py b/src/backend/base/langflow/components/prototypes/RunnableExecutor.py similarity index 100% rename from src/backend/base/langflow/components/experimental/RunnableExecutor.py rename to src/backend/base/langflow/components/prototypes/RunnableExecutor.py diff --git a/src/backend/base/langflow/components/experimental/SQLExecutor.py b/src/backend/base/langflow/components/prototypes/SQLExecutor.py similarity index 100% rename from src/backend/base/langflow/components/experimental/SQLExecutor.py rename to src/backend/base/langflow/components/prototypes/SQLExecutor.py diff --git a/src/backend/base/langflow/components/prototypes/SubFlow.py b/src/backend/base/langflow/components/prototypes/SubFlow.py new file mode 100644 index 000000000..b0631ee99 --- /dev/null +++ b/src/backend/base/langflow/components/prototypes/SubFlow.py @@ -0,0 +1,115 @@ +from typing import Any, List, Optional + +from langflow.base.flow_processing.utils import build_data_from_result_data +from langflow.custom import CustomComponent +from langflow.graph.graph.base import Graph +from langflow.graph.schema import RunOutputs +from langflow.graph.vertex.base import Vertex +from langflow.helpers.flow import get_flow_inputs +from langflow.schema import Data +from langflow.schema.dotdict import dotdict +from langflow.template.field.base import Input +from loguru import logger + + +class SubFlowComponent(CustomComponent): + display_name = "Sub Flow" + description = ( + "Dynamically Generates a Component from a Flow. The output is a list of data with keys 'result' and 'message'." + ) + beta: bool = True + field_order = ["flow_name"] + + def get_flow_names(self) -> List[str]: + flow_datas = self.list_flows() + return [flow_data.data["name"] for flow_data in flow_datas] + + def get_flow(self, flow_name: str) -> Optional[Data]: + flow_datas = self.list_flows() + for flow_data in flow_datas: + if flow_data.data["name"] == flow_name: + return flow_data + return None + + def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None): + logger.debug(f"Updating build config with field value {field_value} and field name {field_name}") + if field_name == "flow_name": + build_config["flow_name"]["options"] = self.get_flow_names() + # Clean up the build config + for key in list(build_config.keys()): + if key not in self.field_order + ["code", "_type", "get_final_results_only"]: + del build_config[key] + if field_value is not None and field_name == "flow_name": + try: + flow_data = self.get_flow(field_value) + if not flow_data: + raise ValueError(f"Flow {field_value} not found.") + graph = Graph.from_payload(flow_data.data["data"]) + # Get all inputs from the graph + inputs = get_flow_inputs(graph) + # Add inputs to the build config + build_config = self.add_inputs_to_build_config(inputs, build_config) + except Exception as e: + logger.error(f"Error getting flow {field_value}: {str(e)}") + + return build_config + + def add_inputs_to_build_config(self, inputs: List[Vertex], build_config: dotdict): + new_fields: list[Input] = [] + for vertex in inputs: + field = Input( + display_name=vertex.display_name, + name=vertex.id, + info=vertex.description, + field_type="str", + value=None, + ) + new_fields.append(field) + logger.debug(new_fields) + for field in new_fields: + build_config[field.name] = field.to_dict() + return build_config + + def build_config(self): + return { + "input_value": { + "display_name": "Input Value", + "multiline": True, + }, + "flow_name": { + "display_name": "Flow Name", + "info": "The name of the flow to run.", + "options": [], + "real_time_refresh": True, + "refresh_button": True, + }, + "tweaks": { + "display_name": "Tweaks", + "info": "Tweaks to apply to the flow.", + }, + "get_final_results_only": { + "display_name": "Get Final Results Only", + "info": "If False, the output will contain all outputs from the flow.", + "advanced": True, + }, + } + + async def build(self, flow_name: str, get_final_results_only: bool = True, **kwargs) -> List[Data]: + tweaks = {key: {"input_value": value} for key, value in kwargs.items()} + run_outputs: List[Optional[RunOutputs]] = await self.run_flow( + tweaks=tweaks, + flow_name=flow_name, + ) + if not run_outputs: + return [] + run_output = run_outputs[0] + + data = [] + if run_output is not None: + for output in run_output.outputs: + if output: + data.extend(build_data_from_result_data(output, get_final_results_only)) + + self.status = data + logger.debug(data) + return data diff --git a/src/backend/base/langflow/components/prototypes/__init__.py b/src/backend/base/langflow/components/prototypes/__init__.py new file mode 100644 index 000000000..9b37551c0 --- /dev/null +++ b/src/backend/base/langflow/components/prototypes/__init__.py @@ -0,0 +1,39 @@ +# from .AgentComponent import AgentComponent +# from .ConditionalRouter import ConditionalRouterComponent +# from .ExtractKeyFromData import ExtractKeyFromDataComponent +# from .FlowTool import FlowToolComponent +# from .Listen import ListenComponent +# from .ListFlows import ListFlowsComponent +# from ..helpers.MergeData import MergeDataComponent +# from .Notify import NotifyComponent +# from .PythonFunction import PythonFunctionComponent +# from .RunFlow import RunFlowComponent +# from .RunnableExecutor import RunnableExecComponent +# from .SelectivePassThrough import SelectivePassThroughComponent +# from ..helpers.SplitText import SplitTextComponent +# from .SQLExecutor import SQLExecutorComponent +# from .SubFlow import SubFlowComponent + +from .ConditionalRouter import ConditionalRouterComponent +from .FlowTool import FlowToolComponent +from .Listen import ListenComponent +from .Notify import NotifyComponent +from .Pass import PassComponent +from .PythonFunction import PythonFunctionComponent +from .RunFlow import RunFlowComponent +from .RunnableExecutor import RunnableExecComponent +from .SQLExecutor import SQLExecutorComponent +from .SubFlow import SubFlowComponent + +__all__ = [ + "ConditionalRouterComponent", + "FlowToolComponent", + "ListenComponent", + "NotifyComponent", + "PassComponent", + "PythonFunctionComponent", + "RunFlowComponent", + "RunnableExecComponent", + "SQLExecutorComponent", + "SubFlowComponent", +]