From f08c18f54a6af0a3fe4e7115e0aac38b4169ffe1 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:52:28 -0300 Subject: [PATCH] feat: loop components handle ui and logic (#5744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added backend to allow loop on output * Added custom edge for looping components * Added allows_loop to output type * Added output_types to target handle if its a loop * Fixed valid_connection to allow loops * Added the loop handle to the outputs * Added infinity icon * Fixed clean edges to not delete loop edge * Implement loop checking before build. * Implemented looping indicator * Fixed belzier path * [autofix.ci] apply automated fixes * 🔧 (reactflowUtils.ts): refactor cleanEdges and detectBrokenEdges functions to improve code readability and maintainability by extracting repeated logic into variables and reducing code duplication. * [autofix.ci] apply automated fixes * Add from_loop_target_handle method to TargetHandle class and update type field * Enhance Edge class to handle loop target handles and validate loop edges * Add output_names attribute and get_value_from_output_names method to Vertex class * Add overlap check for input and output names in Component class * Fix default value assignment in ComponentVertex to handle output names correctly * Clarify error message for missing attributes in Component class * Added backend to allow loop on output * Added custom edge for looping components * Added allows_loop to output type * Added output_types to target handle if its a loop * Fixed valid_connection to allow loops * Added the loop handle to the outputs * Added infinity icon * Fixed clean edges to not delete loop edge * Implement loop checking before build. * Implemented looping indicator * Fixed belzier path * [autofix.ci] apply automated fixes * 🔧 (reactflowUtils.ts): refactor cleanEdges and detectBrokenEdges functions to improve code readability and maintainability by extracting repeated logic into variables and reducing code duplication. * [autofix.ci] apply automated fixes * Add from_loop_target_handle method to TargetHandle class and update type field * Enhance Edge class to handle loop target handles and validate loop edges * Add output_names attribute and get_value_from_output_names method to Vertex class * Add overlap check for input and output names in Component class * Fix default value assignment in ComponentVertex to handle output names correctly * Clarify error message for missing attributes in Component class * feat: add loop component 🎁🎄 (#5429) * add loop component 🎁🎄 * [autofix.ci] apply automated fixes * fix: add loop component to init * [autofix.ci] apply automated fixes * refactor(loop): rename loop input variable and improve code quality - Renamed 'loop' input to 'loop_input' for clarity. - Simplified logic for checking loop input and aggregating results. - Enhanced type hints for better code readability and maintainability. * refactor(loop): add type hint to initialize_data method for improved clarity * fix: mypy error incompatible return value type * feat: adds test cases for loop component compatibility with the APIs, Loop component updates to support API (#5615) * add loop component 🎁🎄 * [autofix.ci] apply automated fixes * fix: add loop component to init * [autofix.ci] apply automated fixes * refactor(loop): rename loop input variable and improve code quality - Renamed 'loop' input to 'loop_input' for clarity. - Simplified logic for checking loop input and aggregating results. - Enhanced type hints for better code readability and maintainability. * refactor(loop): add type hint to initialize_data method for improved clarity * adding test * test cases added * Update test_loop.py * adding test * test cases added * Update test_loop.py * update with the new test case method! * Update test_loop.py * tests updates * Update loop.py * update fix * issues loop issues * reverting debug mode params * solves lint errors and fix the tests * fix: mypy error incompatible return value type * [autofix.ci] apply automated fixes --------- Co-authored-by: Rodrigo Nader Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: italojohnny * feat: improve model input fields for Cohere component (#5712) feat: improve model input fields for cohere component 1. Make api_key field required 2. Convert temperature to SliderInput with range 0-2 3. Add info description to temperature slider * refactor: improve naming consistency in DataCombiner component (#5471) * refactor: improve naming consistency in DataCombiner component - Rename MergeOperation to DataOperation - Rename component to DataCombinerComponent - Convert operation enum values to uppercase - Update method names for consistency * [autofix.ci] apply automated fixes * fix: resolved linting errors in __init__.py * [autofix.ci] apply automated fixes * Changed operation names to capitalize only first letter * refactor: rename DataCombinerComponent to MergeDataComponent for better clarity and backwards compatibility * [autofix.ci] apply automated fixes * fix: Translate Portuguese text to English in merge_data.py * feat: add required to data_inputs in MergeDataComponent --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Edwin Jose * refactor: Refactor Wikipedia API component (#5432) * refactor(wikipedia): Refactor Wikipedia API component * test: add unit tests for WikipediaAPIComponent * [autofix.ci] apply automated fixes * refactor: improve WikipediaAPIComponent tests and fix lint issues * [autofix.ci] apply automated fixes * fix: resolve lint issues in WikipediaAPIComponent tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Edwin Jose * fix: pass slider input values correctly, add test (#5735) * ✨ (base.py): Update field validation to include "slider" type in addition to "float" type for better parameter handling 📝 (constants.py): Add "slider" type to the list of DIRECT_TYPES for consistency and completeness * ✅ (test_inputs.py): add unit test for SliderInput class to ensure it initializes with correct value * 🐛 (base.py): fix comparison of field type with a list by changing it to a set to ensure correct condition evaluation * [autofix.ci] apply automated fixes * fix format * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * feat: make AWS credentials required in bedrock component (#5710) 1. Make aws_access_key_id field required 2. Make aws_secret_access_key field required * chore: update test durations (#5736) Co-authored-by: ogabrielluiz <24829397+ogabrielluiz@users.noreply.github.com> * feat: add truncation to ResultDataResponse (#5704) * chore: Update dependencies and improve platform markers in configuration files - Added 'hypothesis' version 6.123.17 to dev-dependencies in pyproject.toml. - Updated platform markers from 'sys_platform' to 'platform_system' for better compatibility in uv.lock, affecting multiple packages including 'jinxed', 'colorama', and 'appnope'. - Ensured consistency in platform checks across various dependencies to enhance cross-platform support. This update improves the project's dependency management and ensures better compatibility across different operating systems. * feat: Enhance ResultDataResponse serialization with truncation support - Introduced a new method `_serialize_and_truncate` to handle serialization and truncation of various data types, including strings, bytes, datetime, Decimal, UUID, and BaseModel instances. - Updated the `serialize_results` method to utilize the new truncation logic for both individual results and dictionary outputs. - Enhanced the `serialize_model` method to ensure all relevant fields are serialized and truncated according to the defined maximum text length. This update improves the handling of large data outputs, ensuring that responses remain concise and manageable. * fix: Reduce MAX_TEXT_LENGTH in constants.py from 99999 to 20000 This change lowers the maximum text length limit to improve data handling and ensure more manageable output sizes across the application. * test: Add comprehensive unit tests for ResultDataResponse and VertexBuildResponse - Introduced a new test suite in `test_api_schemas.py` to validate the serialization and truncation behavior of `ResultDataResponse` and `VertexBuildResponse`. - Implemented tests for handling long strings, special data types, nested structures, and combined fields, ensuring proper serialization and truncation. - Enhanced coverage for logging and output handling, verifying that all fields are correctly processed and truncated as per the defined maximum text length. - Utilized Hypothesis for property-based testing to ensure robustness and reliability of the serialization logic. This update significantly improves the test coverage for the API response schemas, ensuring better data handling and output management. * feat: Add function to validate models with tool calling function and related fixes in agent component (#5720) * Update nvidia.py * update agent experience with improving model selection update agent experience with improving model selection and making only the tool calling models available. * variable clean up * [autofix.ci] apply automated fixes * Update src/backend/base/langflow/base/models/model_input_constants.py Co-authored-by: Gabriel Luiz Freitas Almeida * Update src/backend/base/langflow/base/models/model_input_constants.py Co-authored-by: Gabriel Luiz Freitas Almeida * added default models * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * format errors solved * [autofix.ci] apply automated fixes * Update model.py --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida * feat: assistants agent improvements (#5581) * assistants agent improvements * remove alembic init file * vector store / file upload support * use sync file object (required by sdk) * steps * self.tools initialization * improvements for edwin * add name and switch to MultilineInput * ci fixes * refactor: enhance flow type safety and clean up unused code (#5669) * 📝 (use-save-flow.ts): add AllNodeType and EdgeType imports to improve type safety in useSaveFlow hook 📝 (index.tsx): remove unused setNoticeData function to clean up code and improve readability * refactor: Remove unused code in GeneralPage component * refactor: Remove unused code in cardComponent/index.tsx --------- Co-authored-by: anovazzi1 * feat: Add `required=True` to essential inputs across Langflow components (#5739) * fix: add required validation to input fields Ensures mandatory fields are properly marked as required across components. * fix: add required validation to input fields Ensures mandatory fields are properly marked as required across components. * fix: add required validation to input fields field: model_name * fix: add required validation to input fields field: model and base_url * fix: add required validation to input fields input: mistral_api_key * fix: add required validation to input fields inputs: model, base_url, nvidia_api_key * fix: add required validation to input fields inputs: model, base_url * fix: add required validation to input fields input: openai_api_key * fix: add required validation to input fields inputs: message, embedding_model * fix: add required validation to input fields inputs: model_name, credentials * fix: add required validation to input fields inputs: aws_secret_access_key, aws_access_key_id * fix: add required validation to input fields inputs: input_text, match_text * fix: add required validation to input fields inputs: input_message * fix: add required validation to input fields inputs: input_value * fix: add required validation to input fields input: data_input * fix: add required validation to input fields inputs: input_value * fix: add required validation to input fields input: data_input * fix: add required validation to input fields input: data_input * fix: add required validation to input fields input: data_input * fix: add required validation to input fields input: data_input * fix: add required validation to input fields inputs: data_inputs, embeddings * fix: add required validation to input fields inputs: api_key, input_value * fix: add required validation to input fields inputs: password, username, openai_api_key, prompt * fix: add required validation to input fields inputs: api_key, transcription_result * fix: add required validation to input fields inputs: api_key, transcription_result, prompt * fix: add required validation to input fields input: prompt * fix: add required validation to input fields input: api_key * fix: add required validation to input fields inputs: api_key, transcript_id * fix: add required validation to input fields inputs: audio_file, api_key * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * feat: make YouTube Transcripts URL field required (#5686) feat: Enhance YouTube Transcripts component by adding required field validation to URL input This change ensures that users provide a video URL before using the YouTube Transcripts component, preventing potential runtime errors due to missing video source. * fix: Fix memory leak when creating components (#5733) Fix memory leak when creating components * test: Update API key requirements and test configurations for frontend tests (#5752) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: italojohnny Co-authored-by: Edwin Jose Co-authored-by: Vinícios Batista da Silva Co-authored-by: Raphael Valdetaro <79842132+raphaelchristi@users.noreply.github.com> Co-authored-by: Cristhian Zanforlin Lousa Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ogabrielluiz <24829397+ogabrielluiz@users.noreply.github.com> Co-authored-by: Sebastián Estévez Co-authored-by: anovazzi1 Co-authored-by: VICTOR CORREA GOMES <112295415+Vigtu@users.noreply.github.com> Co-authored-by: Christophe Bornet Co-authored-by: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> * Updated loop.py component * [autofix.ci] apply automated fixes * update test file * fix: handle None values in input names and improve type hints * [autofix.ci] apply automated fixes * Added loop component test * Added comments * test: add 'allow_loop' field to Output dictionary in test_output_to_dict method * fix: correct key name in Output dictionary from 'allow_loop' to 'allows_loop' in test_output_to_dict method * Updated frontend loop test * Updated examples * 🐛 (generalBugs-shard-9.spec.ts): Fix incorrect test selector for chat memory output element 🐛 (loop-component.spec.ts): Fix incorrect test selector for chat output element 🐛 (generalBugs-shard-3.spec.ts): Fix incorrect test selector for open AI model output element * [autofix.ci] apply automated fixes * refactor: update return type in AgentComponent to use dotdict for build_config This change modifies the return statement in the AgentComponent class to utilize a dotdict for the build_config, enhancing the structure and accessibility of the returned configuration data. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: cristhianzl Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: Rodrigo Nader Co-authored-by: italojohnny Co-authored-by: Edwin Jose Co-authored-by: Vinícios Batista da Silva Co-authored-by: Raphael Valdetaro <79842132+raphaelchristi@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ogabrielluiz <24829397+ogabrielluiz@users.noreply.github.com> Co-authored-by: Sebastián Estévez Co-authored-by: anovazzi1 Co-authored-by: VICTOR CORREA GOMES <112295415+Vigtu@users.noreply.github.com> Co-authored-by: Christophe Bornet --- .../base/langflow/components/agents/agent.py | 2 +- .../langflow/components/logic/__init__.py | 2 + .../base/langflow/components/logic/loop.py | 111 ++ .../custom/custom_component/component.py | 19 +- .../custom_component/custom_component.py | 15 + src/backend/base/langflow/graph/edge/base.py | 16 +- .../base/langflow/graph/edge/schema.py | 69 +- .../base/langflow/graph/vertex/base.py | 18 +- .../langflow/graph/vertex/vertex_types.py | 7 +- .../Basic Prompt Chaining.json | 11 + .../starter_projects/Basic Prompting.json | 5 + .../starter_projects/Blog Writer.json | 10 + .../Custom Component Maker.json | 16 + .../starter_projects/Document Q&A.json | 8 + .../Graph Vector Store RAG.json | 18 + .../Image Sentiment Analysis.json | 7 + .../Instagram Copywriter.json | 13 +- .../starter_projects/Market Research.json | 9 +- .../starter_projects/Memory Chatbot.json | 7 + .../starter_projects/Research Agent.json | 13 +- .../SEO Keyword Generator.json | 5 + .../starter_projects/SaaS Pricing.json | 5 +- .../Sequential Tasks Agents .json | 14 +- .../starter_projects/Simple Agent.json | 5 +- .../Travel Planning Agents.json | 11 +- .../Twitter Thread Generator.json | 11 + .../starter_projects/Vector Store RAG.json | 16 + .../base/langflow/template/field/base.py | 13 +- src/backend/tests/conftest.py | 7 + src/backend/tests/data/LoopTest.json | 1604 +++++++++++++++++ .../tests/unit/api/v1/test_endpoints.py | 12 +- .../components/agents/test_agent_component.py | 6 +- .../tests/unit/components/logic/__init__.py | 0 .../tests/unit/components/logic/test_loop.py | 91 + src/backend/tests/unit/test_schema.py | 1 + src/frontend/src/CustomEdges/index.tsx | 16 +- .../components/NodeOutputfield/index.tsx | 64 +- .../src/constants/alerts_constants.tsx | 2 + src/frontend/src/stores/flowStore.ts | 20 + src/frontend/src/types/api/index.ts | 2 +- src/frontend/src/types/flow/index.ts | 2 + src/frontend/src/utils/reactflowUtils.ts | 151 +- src/frontend/src/utils/styleUtils.ts | 2 + .../extended/features/loop-component.spec.ts | 243 +++ 44 files changed, 2594 insertions(+), 85 deletions(-) create mode 100644 src/backend/base/langflow/components/logic/loop.py create mode 100644 src/backend/tests/data/LoopTest.json create mode 100644 src/backend/tests/unit/components/logic/__init__.py create mode 100644 src/backend/tests/unit/components/logic/test_loop.py create mode 100644 src/frontend/tests/extended/features/loop-component.spec.ts diff --git a/src/backend/base/langflow/components/agents/agent.py b/src/backend/base/langflow/components/agents/agent.py index c6669f5eb..de2342398 100644 --- a/src/backend/base/langflow/components/agents/agent.py +++ b/src/backend/base/langflow/components/agents/agent.py @@ -264,4 +264,4 @@ class AgentComponent(ToolCallingAgentComponent): build_config = await update_component_build_config( component_class, build_config, field_value, "model_name" ) - return {k: v.to_dict() if hasattr(v, "to_dict") else v for k, v in build_config.items()} + return dotdict({k: v.to_dict() if hasattr(v, "to_dict") else v for k, v in build_config.items()}) diff --git a/src/backend/base/langflow/components/logic/__init__.py b/src/backend/base/langflow/components/logic/__init__.py index 7833224ed..40e84cd1d 100644 --- a/src/backend/base/langflow/components/logic/__init__.py +++ b/src/backend/base/langflow/components/logic/__init__.py @@ -2,6 +2,7 @@ from .conditional_router import ConditionalRouterComponent from .data_conditional_router import DataConditionalRouterComponent from .flow_tool import FlowToolComponent from .listen import ListenComponent +from .loop import LoopComponent from .notify import NotifyComponent from .pass_message import PassMessageComponent from .run_flow import RunFlowComponent @@ -12,6 +13,7 @@ __all__ = [ "DataConditionalRouterComponent", "FlowToolComponent", "ListenComponent", + "LoopComponent", "NotifyComponent", "PassMessageComponent", "RunFlowComponent", diff --git a/src/backend/base/langflow/components/logic/loop.py b/src/backend/base/langflow/components/logic/loop.py new file mode 100644 index 000000000..781eb0e73 --- /dev/null +++ b/src/backend/base/langflow/components/logic/loop.py @@ -0,0 +1,111 @@ +from langflow.custom import Component +from langflow.io import DataInput, Output +from langflow.schema import Data + + +class LoopComponent(Component): + display_name = "Loop" + description = ( + "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs." + ) + icon = "infinity" + + inputs = [ + DataInput( + name="data", + display_name="Data", + info="The initial list of Data objects to iterate over.", + ), + ] + + outputs = [ + Output(display_name="Item", name="item", method="item_output", allows_loop=True), + Output(display_name="Done", name="done", method="done_output"), + ] + + def initialize_data(self) -> None: + """Initialize the data list, context index, and aggregated list.""" + if self.ctx.get(f"{self._id}_initialized", False): + return + + # Ensure data is a list of Data objects + data_list = self._validate_data(self.data) + + # Store the initial data and context variables + self.update_ctx( + { + f"{self._id}_data": data_list, + f"{self._id}_index": 0, + f"{self._id}_aggregated": [], + f"{self._id}_initialized": True, + } + ) + + def _validate_data(self, data): + """Validate and return a list of Data objects.""" + if isinstance(data, Data): + return [data] + if isinstance(data, list) and all(isinstance(item, Data) for item in data): + return data + msg = "The 'data' input must be a list of Data objects or a single Data object." + raise TypeError(msg) + + def evaluate_stop_loop(self) -> bool: + """Evaluate whether to stop item or done output.""" + current_index = self.ctx.get(f"{self._id}_index", 0) + data_length = len(self.ctx.get(f"{self._id}_data", [])) + return current_index > data_length + + def item_output(self) -> Data: + """Output the next item in the list or stop if done.""" + self.initialize_data() + current_item = Data(text="") + + if self.evaluate_stop_loop(): + self.stop("item") + return Data(text="") + + # Get data list and current index + data_list, current_index = self.loop_variables() + if current_index < len(data_list): + # Output current item and increment index + try: + current_item = data_list[current_index] + except IndexError: + current_item = Data(text="") + self.aggregated_output() + self.update_ctx({f"{self._id}_index": current_index + 1}) + return current_item + + def done_output(self) -> Data: + """Trigger the done output when iteration is complete.""" + self.initialize_data() + + if self.evaluate_stop_loop(): + self.stop("item") + self.start("done") + + return self.ctx.get(f"{self._id}_aggregated", []) + self.stop("done") + return Data(text="") + + def loop_variables(self): + """Retrieve loop variables from context.""" + return ( + self.ctx.get(f"{self._id}_data", []), + self.ctx.get(f"{self._id}_index", 0), + ) + + def aggregated_output(self) -> Data: + """Return the aggregated list once all items are processed.""" + self.initialize_data() + + # Get data list and aggregated list + data_list = self.ctx.get(f"{self._id}_data", []) + aggregated = self.ctx.get(f"{self._id}_aggregated", []) + + # Check if loop input is provided and append to aggregated list + if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list): + aggregated.append(self.item) + self.update_ctx({f"{self._id}_aggregated": aggregated}) + return aggregated diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 86748c31f..9dd8db95f 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -97,6 +97,9 @@ class Component(CustomComponent): def __init__(self, **kwargs) -> None: # Initialize instance-specific attributes first + if overlap := self._there_is_overlap_in_inputs_and_outputs(): + msg = f"Inputs and outputs have overlapping names: {overlap}" + raise ValueError(msg) self._output_logs: dict[str, list[Log]] = {} self._current_output: str = "" self._metadata: dict = {} @@ -157,6 +160,19 @@ class Component(CustomComponent): self.set_class_code() self._set_output_required_inputs() + def _there_is_overlap_in_inputs_and_outputs(self) -> set[str]: + """Check the `.name` of inputs and outputs to see if there is overlap. + + Returns: + set[str]: Set of names that overlap between inputs and outputs. + """ + # Create sets of input and output names for O(1) lookup + input_names = {input_.name for input_ in self.inputs if input_.name is not None} + output_names = {output.name for output in self.outputs} + + # Return the intersection of the sets + return input_names & output_names + @property def ctx(self): if not hasattr(self, "graph") or self.graph is None: @@ -676,7 +692,7 @@ class Component(CustomComponent): return PlaceholderGraph( flow_id=flow_id, user_id=str(user_id), session_id=session_id, context={}, flow_name=flow_name ) - msg = f"{name} not found in {self.__class__.__name__}" + msg = f"Attribute {name} not found in {self.__class__.__name__}" raise AttributeError(msg) def _set_input_value(self, name: str, value: Any) -> None: @@ -808,6 +824,7 @@ class Component(CustomComponent): for key, input_obj in self._inputs.items(): if key not in attributes and key not in self._attributes: attributes[key] = input_obj.value or None + self._attributes.update(attributes) def _set_outputs(self, outputs: list[dict]) -> None: diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index 9cb9e1523..9107dcc54 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -138,6 +138,21 @@ class CustomComponent(BaseComponent): msg = f"Error stopping {self.display_name}: {e}" raise ValueError(msg) from e + def start(self, output_name: str | None = None) -> None: + if not output_name and self._vertex and len(self._vertex.outputs) == 1: + output_name = self._vertex.outputs[0]["name"] + elif not output_name: + msg = "You must specify an output name to call start" + raise ValueError(msg) + if not self._vertex: + msg = "Vertex is not set" + raise ValueError(msg) + try: + self.graph.mark_branch(vertex_id=self._vertex.id, output_name=output_name, state="ACTIVE") + except Exception as e: + msg = f"Error starting {self.display_name}: {e}" + raise ValueError(msg) from e + def append_state(self, name: str, value: Any) -> None: if not self._vertex: msg = "Vertex is not set" diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index e0b1545a8..013662e7a 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -26,7 +26,10 @@ class Edge: self.source_handle: SourceHandle = SourceHandle(**self._source_handle) if isinstance(self._target_handle, dict): try: - self.target_handle: TargetHandle = TargetHandle(**self._target_handle) + if "name" in self._target_handle: + self.target_handle: TargetHandle = TargetHandle.from_loop_target_handle(self._target_handle) + else: + self.target_handle = TargetHandle(**self._target_handle) except Exception as e: if "inputTypes" in self._target_handle and self._target_handle["inputTypes"] is None: # Check if self._target_handle['fieldName'] @@ -79,6 +82,17 @@ class Edge: def _validate_handles(self, source, target) -> None: if self.target_handle.input_types is None: self.valid_handles = self.target_handle.type in self.source_handle.output_types + elif self.target_handle.type is None: + # ! This is not a good solution + # This is a loop edge + # If the target_handle.type is None, it means it's a loop edge + # and we should check if the source_handle.output_types is not empty + # and if the target_handle.input_types is empty or if any of the source_handle.output_types + # is in the target_handle.input_types + self.valid_handles = bool(self.source_handle.output_types) and ( + not self.target_handle.input_types + or any(output_type in self.target_handle.input_types for output_type in self.source_handle.output_types) + ) elif self.source_handle.output_types is not None: self.valid_handles = ( diff --git a/src/backend/base/langflow/graph/edge/schema.py b/src/backend/base/langflow/graph/edge/schema.py index 2a03dff5f..72efd8669 100644 --- a/src/backend/base/langflow/graph/edge/schema.py +++ b/src/backend/base/langflow/graph/edge/schema.py @@ -6,6 +6,32 @@ from typing_extensions import TypedDict from langflow.helpers.base_model import BaseModel +class SourceHandleDict(TypedDict, total=False): + baseClasses: list[str] + dataType: str + id: str + name: str | None + output_types: list[str] + + +class TargetHandleDict(TypedDict): + fieldName: str + id: str + inputTypes: list[str] | None + type: str + + +class EdgeDataDetails(TypedDict): + sourceHandle: SourceHandleDict + targetHandle: TargetHandleDict + + +class EdgeData(TypedDict, total=False): + source: str + target: str + data: EdgeDataDetails + + class ResultPair(BaseModel): result: Any extra: Any @@ -45,7 +71,22 @@ class TargetHandle(BaseModel): input_types: list[str] = Field( default_factory=list, alias="inputTypes", description="List of input types for the target handle." ) - type: str = Field(..., description="Type of the target handle.") + type: str = Field(None, description="Type of the target handle.") + + @classmethod + def from_loop_target_handle(cls, target_handle: TargetHandleDict) -> "TargetHandle": + # The target handle is a loop edge + # The target handle is a dict with the following keys: + # - name: str + # - id: str + # - inputTypes: list[str] + # - type: str + # It is built from an Output, which is why it has a different structure + return cls( + field_name=target_handle.get("name"), + id=target_handle.get("id"), + input_types=target_handle.get("output_types"), + ) class SourceHandle(BaseModel): @@ -69,29 +110,3 @@ class SourceHandle(BaseModel): raise ValueError(msg) v = splits[1] return v - - -class SourceHandleDict(TypedDict, total=False): - baseClasses: list[str] - dataType: str - id: str - name: str | None - output_types: list[str] - - -class TargetHandleDict(TypedDict): - fieldName: str - id: str - inputTypes: list[str] | None - type: str - - -class EdgeDataDetails(TypedDict): - sourceHandle: SourceHandleDict - targetHandle: TargetHandleDict - - -class EdgeData(TypedDict, total=False): - source: str - target: str - data: EdgeDataDetails diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 05177d111..f65958285 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -105,6 +105,9 @@ class Vertex: self.build_times: list[float] = [] self.state = VertexStates.ACTIVE self.log_transaction_tasks: set[asyncio.Task] = set() + self.output_names: list[str] = [ + output["name"] for output in self.outputs if isinstance(output, dict) and "name" in output + ] def set_input_value(self, name: str, value: Any) -> None: if self.custom_component is None: @@ -262,20 +265,19 @@ class Vertex: self.base_type = base_type break + def get_value_from_output_names(self, key: str): + if key in self.output_names: + return self.graph.get_vertex(key) + return None + def get_value_from_template_dict(self, key: str): template_dict = self.data.get("node", {}).get("template", {}) + if key not in template_dict: msg = f"Key {key} not found in template dict" raise ValueError(msg) return template_dict.get(key, {}).get("value") - def get_task(self): - # using the task_id, get the task from celery - # and return it - from celery.result import AsyncResult - - return AsyncResult(self.task_id) - def _set_params_from_normal_edge(self, params: dict, edge: Edge, template_dict: dict): param_key = edge.target_param @@ -299,6 +301,8 @@ class Vertex: else: params[param_key] = self.graph.get_vertex(edge.source_id) + elif param_key in self.output_names: + params[param_key] = self.graph.get_vertex(edge.source_id) return params def build_params(self) -> None: diff --git a/src/backend/base/langflow/graph/vertex/vertex_types.py b/src/backend/base/langflow/graph/vertex/vertex_types.py index 27a21a8b0..038daea23 100644 --- a/src/backend/base/langflow/graph/vertex/vertex_types.py +++ b/src/backend/base/langflow/graph/vertex/vertex_types.py @@ -97,11 +97,14 @@ class ComponentVertex(Vertex): """ flow_id = self.graph.flow_id if not self.built: - default_value = UNDEFINED + default_value: Any = UNDEFINED for edge in self.get_edge_with_target(requester.id): # We need to check if the edge is a normal edge if edge.is_cycle and edge.target_param: - default_value = requester.get_value_from_template_dict(edge.target_param) + if edge.target_param in requester.output_names: + default_value = None + else: + default_value = requester.get_value_from_template_dict(edge.target_param) if flow_id: self._log_transaction_async(source=self, target=requester, flow_id=str(flow_id), status="error") diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json index e38d26937..fb784c675 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json @@ -222,6 +222,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -349,6 +350,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -638,6 +640,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -909,6 +912,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -1041,6 +1045,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1053,6 +1058,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1353,6 +1359,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1365,6 +1372,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1654,6 +1662,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -1852,6 +1861,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1864,6 +1874,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json index fbc76da34..2e9f7312b 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json @@ -110,6 +110,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -390,6 +391,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -591,6 +593,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -875,6 +878,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -887,6 +891,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json index 507a1b6c5..31c0c9a9a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json @@ -167,6 +167,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "fetch_content", @@ -178,6 +179,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "fetch_content_text", @@ -189,6 +191,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -312,6 +315,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "parse_data", @@ -323,6 +327,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Data List", "method": "parse_data_as_list", @@ -462,6 +467,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -626,6 +632,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -731,6 +738,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -998,6 +1006,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1010,6 +1019,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json index 12ea0b5c1..8cb2f862f 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json @@ -234,6 +234,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -530,6 +531,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "retrieve_messages", @@ -541,6 +543,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "retrieve_messages_as_text", @@ -773,6 +776,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -1014,6 +1018,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1348,6 +1353,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1360,6 +1366,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1643,6 +1650,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "fetch_content", @@ -1654,6 +1662,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "fetch_content_text", @@ -1665,6 +1674,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -1789,6 +1799,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "fetch_content", @@ -1800,6 +1811,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "fetch_content_text", @@ -1811,6 +1823,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -1941,6 +1954,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "fetch_content", @@ -1952,6 +1966,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "fetch_content_text", @@ -1963,6 +1978,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json index edc2e3d91..362606c95 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json @@ -166,6 +166,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -449,6 +450,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -720,6 +722,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "parse_data", @@ -731,6 +734,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Data List", "method": "parse_data_as_list", @@ -904,6 +908,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -916,6 +921,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1234,6 +1240,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "load_files", @@ -1482,6 +1489,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json index 869109f75..0a2ed0794 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json @@ -348,6 +348,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -661,6 +662,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Embeddings", "method": "build_embeddings", @@ -1163,6 +1165,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Search Results", "method": "search_documents", @@ -1179,6 +1182,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -1677,6 +1681,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "parse_data", @@ -1688,6 +1693,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Data List", "method": "parse_data_as_list", @@ -1831,6 +1837,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -2003,6 +2010,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -2015,6 +2023,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -2317,6 +2326,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -2587,6 +2597,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "fetch_content", @@ -2598,6 +2609,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "fetch_content_text", @@ -2609,6 +2621,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -2768,6 +2781,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Search Results", "method": "search_documents", @@ -2784,6 +2798,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -3280,6 +3295,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "transform_data", @@ -3464,6 +3480,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "transform_data", @@ -3622,6 +3639,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Embeddings", "method": "build_embeddings", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json index eb76c8ec5..c6b73cf02 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json @@ -198,6 +198,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -495,6 +496,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1024,6 +1026,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "parse_data", @@ -1035,6 +1038,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Data List", "method": "parse_data_as_list", @@ -1188,6 +1192,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1200,6 +1205,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1489,6 +1495,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json index 53f4df92f..cc32a9c06 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json @@ -309,6 +309,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -599,6 +600,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -766,6 +768,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -883,6 +886,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -895,6 +899,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1190,6 +1195,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -1343,6 +1349,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1640,6 +1647,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -1756,7 +1764,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -2208,6 +2216,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -2220,6 +2229,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -2516,6 +2526,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json index 6100a3f42..5f117d2d8 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json @@ -205,6 +205,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -498,6 +499,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1176,6 +1178,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1188,6 +1191,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1481,6 +1485,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "parse_data", @@ -1492,6 +1497,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Data List", "method": "parse_data_as_list", @@ -1658,6 +1664,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -1774,7 +1781,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json index 86fea53cf..2fef450e7 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json @@ -145,6 +145,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -442,6 +443,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -781,6 +783,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -793,6 +796,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1085,6 +1089,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "retrieve_messages", @@ -1096,6 +1101,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "retrieve_messages_as_text", @@ -1328,6 +1334,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json index be7ee1e3e..58265282e 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json @@ -314,6 +314,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -468,6 +469,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -761,6 +763,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1039,6 +1042,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -1468,6 +1472,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1480,6 +1485,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1784,6 +1790,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1796,6 +1803,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -2149,6 +2157,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -2265,7 +2274,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -2706,6 +2715,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -2831,6 +2841,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json index 46bddf47d..585512ae8 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json @@ -123,6 +123,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -413,6 +414,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -540,6 +542,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -822,6 +825,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -834,6 +838,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json index f4e8f5dce..6feee5afa 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json @@ -117,6 +117,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -363,6 +364,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -696,6 +698,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -812,7 +815,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json index f6fc64ad2..33cd0e7c0 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents .json @@ -333,6 +333,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -631,6 +632,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -747,7 +749,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -1211,6 +1213,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -1327,7 +1330,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -1774,6 +1777,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -1905,6 +1909,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -2039,6 +2044,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -2214,6 +2220,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -2851,6 +2858,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -2967,7 +2975,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json index f7b557a6c..5611012c4 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json @@ -137,6 +137,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -253,7 +254,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -692,6 +693,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -987,6 +989,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json index f36de09f2..ffa08b858 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json @@ -222,6 +222,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -515,6 +516,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1291,6 +1293,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -1407,7 +1410,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -1871,6 +1874,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -1987,7 +1991,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", @@ -2451,6 +2455,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Response", "method": "message_response", @@ -2567,7 +2572,7 @@ "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.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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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 {k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()}\n" + "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\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.utils import update_component_build_config\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 llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n except Exception as e:\n # Log the error for debugging purposes\n logger.error(f\"Error retrieving language model: {e}\")\n raise\n\n try:\n self.chat_history = await self.get_memory_data()\n except Exception as e:\n logger.error(f\"Error retrieving chat history: {e}\")\n raise\n\n if self.add_current_date_tool:\n try:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n # Convert CurrentDateComponent to a StructuredTool\n current_date_tool = (await CurrentDateComponent().to_toolkit()).pop(0)\n # current_date_tool = CurrentDateComponent().to_toolkit()[0]\n if isinstance(current_date_tool, StructuredTool):\n self.tools.append(current_date_tool)\n else:\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n except Exception as e:\n logger.error(f\"Error adding current date tool: {e}\")\n raise\n\n if not self.tools:\n msg = \"Tools are required to run the agent.\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\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 except Exception as e:\n logger.error(f\"Error setting up the agent: {e}\")\n raise\n\n return await self.run_agent(agent)\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().set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if isinstance(self.agent_llm, str):\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\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 return (\n self._build_llm_model(component_class, inputs, prefix),\n display_name,\n )\n except Exception as e:\n msg = f\"Error building {self.agent_llm} language model\"\n raise ValueError(msg) from e\n return self.agent_llm, None\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" }, "handle_parsing_errors": { "_input_type": "BoolInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json index 17acee8e2..2abfe0bf3 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json @@ -286,6 +286,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -565,6 +566,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -678,6 +680,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -690,6 +693,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -985,6 +989,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1252,6 +1257,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1350,6 +1356,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1448,6 +1455,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1546,6 +1554,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1644,6 +1653,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1786,6 +1796,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json index 771c4b76e..0e9c1235a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json @@ -297,6 +297,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -573,6 +574,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "parse_data", @@ -584,6 +586,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Data List", "method": "parse_data_as_list", @@ -729,6 +732,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Prompt Message", "method": "build_prompt", @@ -895,6 +899,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Chunks", "method": "split_text", @@ -906,6 +911,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -1129,6 +1135,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "text_response", @@ -1141,6 +1148,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "Language Model", "method": "build_model", @@ -1436,6 +1444,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Message", "method": "message_response", @@ -1723,6 +1732,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Embeddings", "method": "build_embeddings", @@ -2248,6 +2258,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Embeddings", "method": "build_embeddings", @@ -2724,6 +2735,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Data", "method": "load_files", @@ -3058,6 +3070,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Search Results", "method": "search_documents", @@ -3075,6 +3088,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", @@ -3491,6 +3505,7 @@ "output_types": [], "outputs": [ { + "allows_loop": false, "cache": true, "display_name": "Search Results", "method": "search_documents", @@ -3508,6 +3523,7 @@ "value": "__UNDEFINED__" }, { + "allows_loop": false, "cache": true, "display_name": "DataFrame", "method": "as_dataframe", diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index b9835fdf7..808aee7ef 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -7,7 +7,15 @@ from typing import ( # type: ignore[attr-defined] _UnionGenericAlias, # type: ignore[attr-defined] ) -from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer, model_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_serializer, + field_validator, + model_serializer, + model_validator, +) from langflow.field_typing import Text from langflow.field_typing.range_spec import RangeSpec @@ -189,6 +197,9 @@ class Output(BaseModel): required_inputs: list[str] | None = Field(default=None) """List of required inputs for this output.""" + allows_loop: bool = Field(default=False) + """Specifies if the output allows looping.""" + def to_dict(self): return self.model_dump(by_alias=True, exclude_none=True) diff --git a/src/backend/tests/conftest.py b/src/backend/tests/conftest.py index 5e67b7196..1b77e8b83 100644 --- a/src/backend/tests/conftest.py +++ b/src/backend/tests/conftest.py @@ -103,6 +103,7 @@ def pytest_configure(config): pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json" pytest.SIMPLE_API_TEST = data_path / "SimpleAPITest.json" pytest.MEMORY_CHATBOT_NO_LLM = data_path / "MemoryChatbotNoLLM.json" + pytest.LOOP_TEST = data_path / "LoopTest.json" pytest.CODE_WITH_SYNTAX_ERROR = """ def get_text(): retun "Hello World" @@ -121,6 +122,7 @@ def get_text(): pytest.TWO_OUTPUTS, pytest.VECTOR_STORE_PATH, pytest.MEMORY_CHATBOT_NO_LLM, + pytest.LOOP_TEST, ]: assert path.exists(), f"File {path} does not exist. Available files: {list(data_path.iterdir())}" @@ -324,6 +326,11 @@ def json_memory_chatbot_no_llm(): return pytest.MEMORY_CHATBOT_NO_LLM.read_text(encoding="utf-8") +@pytest.fixture +def json_loop_test(): + return pytest.LOOP_TEST.read_text(encoding="utf-8") + + @pytest.fixture(autouse=True) def deactivate_tracing(monkeypatch): monkeypatch.setenv("LANGFLOW_DEACTIVATE_TRACING", "true") diff --git a/src/backend/tests/data/LoopTest.json b/src/backend/tests/data/LoopTest.json new file mode 100644 index 000000000..85ea69554 --- /dev/null +++ b/src/backend/tests/data/LoopTest.json @@ -0,0 +1,1604 @@ +{ + "id": "8e67f676-9b85-4c65-8524-fd651b94c1f5", + "data": { + "nodes": [ + { + "id": "ParseData-COhje", + "type": "genericNode", + "position": { + "x": 1519.4837108212814, + "y": 724.0614553725009 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "data": { + "tool_mode": false, + "trace_as_metadata": true, + "list": true, + "trace_as_input": true, + "required": false, + "placeholder": "", + "show": true, + "name": "data", + "value": "", + "display_name": "Data", + "advanced": false, + "input_types": [ + "Data" + ], + "dynamic": false, + "info": "The data to convert to text.", + "title_case": false, + "type": "other", + "_input_type": "DataInput" + }, + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Text\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "sep": { + "tool_mode": false, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "sep", + "value": "\n", + "display_name": "Separator", + "advanced": true, + "dynamic": false, + "info": "", + "title_case": false, + "type": "str", + "_input_type": "StrInput" + }, + "template": { + "tool_mode": false, + "trace_as_input": true, + "multiline": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "template", + "value": "{text}", + "display_name": "Template", + "advanced": false, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.", + "title_case": false, + "type": "str", + "_input_type": "MultilineInput" + } + }, + "description": "Convert Data into plain text following a specified template.", + "icon": "braces", + "base_classes": [ + "Data", + "Message" + ], + "display_name": "Parse Data", + "documentation": "", + "minimized": false, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Message" + ], + "selected": "Message", + "name": "text", + "display_name": "Text", + "method": "parse_data", + "value": "__UNDEFINED__", + "cache": true + }, + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "data_list", + "display_name": "Data List", + "method": "parse_data_as_list", + "value": "__UNDEFINED__", + "cache": true, + "hidden": true + } + ], + "field_order": [ + "data", + "template", + "sep" + ], + "beta": false, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "category": "processing", + "key": "ParseData", + "score": 0.007568328950209746, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "ParseData", + "id": "ParseData-COhje" + }, + "selected": false, + "measured": { + "width": 320, + "height": 294 + }, + "dragging": false + }, + { + "id": "MessagetoData-5hKU0", + "type": "genericNode", + "position": { + "x": 2223.9136430536987, + "y": 1721.2952637105518 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "value": "from loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import MessageInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass MessageToDataComponent(Component):\n display_name = \"Message to Data\"\n description = \"Convert a Message object to a Data object\"\n icon = \"message-square-share\"\n beta = True\n name = \"MessagetoData\"\n\n inputs = [\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The Message object to convert to a Data object\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"convert_message_to_data\"),\n ]\n\n def convert_message_to_data(self) -> Data:\n if isinstance(self.message, Message):\n # Convert Message to Data\n return Data(data=self.message.data)\n\n msg = \"Error converting Message to Data: Input must be a Message object\"\n logger.opt(exception=True).debug(msg)\n self.status = msg\n return Data(data={\"error\": msg})\n", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "message": { + "trace_as_input": true, + "tool_mode": false, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "message", + "value": "", + "display_name": "Message", + "advanced": false, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The Message object to convert to a Data object", + "title_case": false, + "type": "str", + "_input_type": "MessageInput" + } + }, + "description": "Convert a Message object to a Data object", + "icon": "message-square-share", + "base_classes": [ + "Data" + ], + "display_name": "Message to Data", + "documentation": "", + "minimized": false, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "data", + "display_name": "Data", + "method": "convert_message_to_data", + "value": "__UNDEFINED__", + "cache": true + } + ], + "field_order": [ + "message" + ], + "beta": true, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "MessagetoData", + "id": "MessagetoData-5hKU0" + }, + "selected": false, + "measured": { + "width": 320, + "height": 230 + }, + "dragging": false + }, + { + "id": "MessagetoData-nUvB5", + "type": "genericNode", + "position": { + "x": -334.897840488358, + "y": 553.0914016416309 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "value": "from loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import MessageInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass MessageToDataComponent(Component):\n display_name = \"Message to Data\"\n description = \"Convert a Message object to a Data object\"\n icon = \"message-square-share\"\n beta = True\n name = \"MessagetoData\"\n\n inputs = [\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The Message object to convert to a Data object\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"convert_message_to_data\"),\n ]\n\n def convert_message_to_data(self) -> Data:\n if isinstance(self.message, Message):\n # Convert Message to Data\n return Data(data=self.message.data)\n\n msg = \"Error converting Message to Data: Input must be a Message object\"\n logger.opt(exception=True).debug(msg)\n self.status = msg\n return Data(data={\"error\": msg})\n", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "message": { + "trace_as_input": true, + "tool_mode": false, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "message", + "value": "", + "display_name": "Message", + "advanced": false, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The Message object to convert to a Data object", + "title_case": false, + "type": "str", + "_input_type": "MessageInput" + } + }, + "description": "Convert a Message object to a Data object", + "icon": "message-square-share", + "base_classes": [ + "Data" + ], + "display_name": "Message to Data", + "documentation": "", + "minimized": false, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "data", + "display_name": "Data", + "method": "convert_message_to_data", + "value": "__UNDEFINED__", + "cache": true + } + ], + "field_order": [ + "message" + ], + "beta": true, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "MessagetoData", + "id": "MessagetoData-nUvB5" + }, + "selected": false, + "measured": { + "width": 320, + "height": 230 + }, + "dragging": false + }, + { + "id": "ChatInput-OhyAs", + "type": "genericNode", + "position": { + "x": -780.5070511367146, + "y": 477.3880139482486 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "files": { + "trace_as_metadata": true, + "file_path": "", + "fileTypes": [ + "txt", + "md", + "mdx", + "csv", + "json", + "yaml", + "yml", + "xml", + "html", + "htm", + "pdf", + "docx", + "py", + "sh", + "sql", + "js", + "ts", + "tsx", + "jpg", + "jpeg", + "png", + "bmp", + "image" + ], + "list": true, + "required": false, + "placeholder": "", + "show": true, + "name": "files", + "value": "", + "display_name": "Files", + "advanced": true, + "dynamic": false, + "info": "Files to be sent with the message.", + "title_case": false, + "type": "file", + "_input_type": "FileInput" + }, + "background_color": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "background_color", + "value": "", + "display_name": "Background Color", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The background color of the icon.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "chat_icon": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "chat_icon", + "value": "", + "display_name": "Icon", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The icon of the message.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "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", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "input_value": { + "tool_mode": false, + "trace_as_input": true, + "multiline": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "input_value", + "value": "Sentence 1. Sentence 2. Sentence 3", + "display_name": "Text", + "advanced": false, + "input_types": [], + "dynamic": false, + "info": "Message to be passed as input.", + "title_case": false, + "type": "str", + "_input_type": "MultilineInput" + }, + "sender": { + "tool_mode": false, + "trace_as_metadata": true, + "options": [ + "Machine", + "User" + ], + "combobox": false, + "required": false, + "placeholder": "", + "show": true, + "name": "sender", + "value": "User", + "display_name": "Sender Type", + "advanced": true, + "dynamic": false, + "info": "Type of sender.", + "title_case": false, + "type": "str", + "_input_type": "DropdownInput" + }, + "sender_name": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "sender_name", + "value": "User", + "display_name": "Sender Name", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "Name of the sender.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "session_id": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "session_id", + "value": "", + "display_name": "Session ID", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "should_store_message": { + "tool_mode": false, + "trace_as_metadata": true, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "should_store_message", + "value": true, + "display_name": "Store Messages", + "advanced": true, + "dynamic": false, + "info": "Store the message in the history.", + "title_case": false, + "type": "bool", + "_input_type": "BoolInput" + }, + "text_color": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "text_color", + "value": "", + "display_name": "Text Color", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The text color of the name", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + } + }, + "description": "Get chat inputs from the Playground.", + "icon": "MessagesSquare", + "base_classes": [ + "Message" + ], + "display_name": "Chat Input", + "documentation": "", + "minimized": true, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Message" + ], + "selected": "Message", + "name": "message", + "display_name": "Message", + "method": "message_response", + "value": "__UNDEFINED__", + "cache": true + } + ], + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "files", + "background_color", + "chat_icon", + "text_color" + ], + "beta": false, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "ChatInput", + "id": "ChatInput-OhyAs" + }, + "selected": false, + "measured": { + "width": 320, + "height": 230 + }, + "dragging": false + }, + { + "id": "SplitText-CHgY9", + "type": "genericNode", + "position": { + "x": 37.5698068780533, + "y": 627.736322287764 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "data_inputs": { + "trace_as_metadata": true, + "list": true, + "required": true, + "placeholder": "", + "show": true, + "name": "data_inputs", + "value": "", + "display_name": "Data Inputs", + "advanced": false, + "input_types": [ + "Data" + ], + "dynamic": false, + "info": "The data to split.", + "title_case": false, + "type": "other", + "_input_type": "HandleInput" + }, + "chunk_overlap": { + "tool_mode": false, + "trace_as_metadata": true, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "chunk_overlap", + "value": 0, + "display_name": "Chunk Overlap", + "advanced": false, + "dynamic": false, + "info": "Number of characters to overlap between chunks.", + "title_case": false, + "type": "int", + "_input_type": "IntInput" + }, + "chunk_size": { + "tool_mode": false, + "trace_as_metadata": true, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "chunk_size", + "value": 10, + "display_name": "Chunk Size", + "advanced": false, + "dynamic": false, + "info": "The maximum number of characters in each chunk.", + "title_case": false, + "type": "int", + "_input_type": "IntInput" + }, + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "value": "from langchain_text_splitters import CharacterTextSplitter\n\nfrom langflow.custom import Component\nfrom langflow.io import HandleInput, IntInput, MessageTextInput, Output\nfrom langflow.schema import Data, DataFrame\nfrom langflow.utils.util import unescape_string\n\n\nclass SplitTextComponent(Component):\n display_name: str = \"Split Text\"\n description: str = \"Split text into chunks based on specified criteria.\"\n icon = \"scissors-line-dashed\"\n name = \"SplitText\"\n\n inputs = [\n HandleInput(\n name=\"data_inputs\",\n display_name=\"Data Inputs\",\n info=\"The data to split.\",\n input_types=[\"Data\"],\n is_list=True,\n required=True,\n ),\n IntInput(\n name=\"chunk_overlap\",\n display_name=\"Chunk Overlap\",\n info=\"Number of characters to overlap between chunks.\",\n value=200,\n ),\n IntInput(\n name=\"chunk_size\",\n display_name=\"Chunk Size\",\n info=\"The maximum number of characters in each chunk.\",\n value=1000,\n ),\n MessageTextInput(\n name=\"separator\",\n display_name=\"Separator\",\n info=\"The character to split on. Defaults to newline.\",\n value=\"\\n\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Chunks\", name=\"chunks\", method=\"split_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def _docs_to_data(self, docs):\n return [Data(text=doc.page_content, data=doc.metadata) for doc in docs]\n\n def split_text(self) -> list[Data]:\n separator = unescape_string(self.separator)\n\n documents = [_input.to_lc_document() for _input in self.data_inputs if isinstance(_input, Data)]\n\n splitter = CharacterTextSplitter(\n chunk_overlap=self.chunk_overlap,\n chunk_size=self.chunk_size,\n separator=separator,\n )\n docs = splitter.split_documents(documents)\n data = self._docs_to_data(docs)\n self.status = data\n return data\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.split_text())\n", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "separator": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "separator", + "value": ".", + "display_name": "Separator", + "advanced": false, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The character to split on. Defaults to newline.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + } + }, + "description": "Split text into chunks based on specified criteria.", + "icon": "scissors-line-dashed", + "base_classes": [ + "Data", + "DataFrame" + ], + "display_name": "Split Text", + "documentation": "", + "minimized": false, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "chunks", + "display_name": "Chunks", + "method": "split_text", + "value": "__UNDEFINED__", + "cache": true + }, + { + "types": [ + "DataFrame" + ], + "selected": "DataFrame", + "name": "dataframe", + "display_name": "DataFrame", + "method": "as_dataframe", + "value": "__UNDEFINED__", + "cache": true + } + ], + "field_order": [ + "data_inputs", + "chunk_overlap", + "chunk_size", + "separator" + ], + "beta": false, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "SplitText", + "id": "SplitText-CHgY9" + }, + "selected": false, + "measured": { + "width": 320, + "height": 507 + }, + "dragging": false + }, + { + "id": "ParseData-5THFG", + "type": "genericNode", + "position": { + "x": 1236.7156922443157, + "y": 1290 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "data": { + "tool_mode": false, + "trace_as_metadata": true, + "list": true, + "trace_as_input": true, + "required": false, + "placeholder": "", + "show": true, + "name": "data", + "value": "", + "display_name": "Data", + "advanced": false, + "input_types": [ + "Data" + ], + "dynamic": false, + "info": "The data to convert to text.", + "title_case": false, + "type": "other", + "_input_type": "DataInput" + }, + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Text\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "sep": { + "tool_mode": false, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "sep", + "value": "\n", + "display_name": "Separator", + "advanced": true, + "dynamic": false, + "info": "", + "title_case": false, + "type": "str", + "_input_type": "StrInput" + }, + "template": { + "tool_mode": false, + "trace_as_input": true, + "multiline": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "template", + "value": "{text}", + "display_name": "Template", + "advanced": false, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.", + "title_case": false, + "type": "str", + "_input_type": "MultilineInput" + } + }, + "description": "Convert Data into plain text following a specified template.", + "icon": "braces", + "base_classes": [ + "Data", + "Message" + ], + "display_name": "Parse Data", + "documentation": "", + "minimized": false, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Message" + ], + "selected": "Message", + "name": "text", + "display_name": "Text", + "method": "parse_data", + "value": "__UNDEFINED__", + "cache": true + }, + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "data_list", + "display_name": "Data List", + "method": "parse_data_as_list", + "value": "__UNDEFINED__", + "cache": true + } + ], + "field_order": [ + "data", + "template", + "sep" + ], + "beta": false, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "category": "processing", + "key": "ParseData", + "score": 0.007568328950209746, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "ParseData", + "id": "ParseData-5THFG" + }, + "selected": false, + "measured": { + "width": 320, + "height": 342 + }, + "dragging": false + }, + { + "id": "ChatOutput-ErUad", + "type": "genericNode", + "position": { + "x": 1655.6855744894217, + "y": 1385.085466714244 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "background_color": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "background_color", + "value": "", + "display_name": "Background Color", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The background color of the icon.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "chat_icon": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "chat_icon", + "value": "", + "display_name": "Icon", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The icon of the message.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "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", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + }, + "data_template": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "data_template", + "value": "{text}", + "display_name": "Data Template", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "input_value": { + "trace_as_input": true, + "tool_mode": false, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "input_value", + "value": "", + "display_name": "Text", + "advanced": false, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "Message to be passed as output.", + "title_case": false, + "type": "str", + "_input_type": "MessageInput" + }, + "sender": { + "tool_mode": false, + "trace_as_metadata": true, + "options": [ + "Machine", + "User" + ], + "combobox": false, + "required": false, + "placeholder": "", + "show": true, + "name": "sender", + "value": "Machine", + "display_name": "Sender Type", + "advanced": true, + "dynamic": false, + "info": "Type of sender.", + "title_case": false, + "type": "str", + "_input_type": "DropdownInput" + }, + "sender_name": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "sender_name", + "value": "AI", + "display_name": "Sender Name", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "Name of the sender.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "session_id": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "session_id", + "value": "", + "display_name": "Session ID", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + }, + "should_store_message": { + "tool_mode": false, + "trace_as_metadata": true, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "should_store_message", + "value": true, + "display_name": "Store Messages", + "advanced": true, + "dynamic": false, + "info": "Store the message in the history.", + "title_case": false, + "type": "bool", + "_input_type": "BoolInput" + }, + "text_color": { + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "load_from_db": false, + "list": false, + "required": false, + "placeholder": "", + "show": true, + "name": "text_color", + "value": "", + "display_name": "Text Color", + "advanced": true, + "input_types": [ + "Message" + ], + "dynamic": false, + "info": "The text color of the name", + "title_case": false, + "type": "str", + "_input_type": "MessageTextInput" + } + }, + "description": "Display a chat message in the Playground.", + "icon": "MessagesSquare", + "base_classes": [ + "Message" + ], + "display_name": "Chat Output", + "documentation": "", + "minimized": true, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Message" + ], + "selected": "Message", + "name": "message", + "display_name": "Message", + "method": "message_response", + "value": "__UNDEFINED__", + "cache": true + } + ], + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "data_template", + "background_color", + "chat_icon", + "text_color" + ], + "beta": false, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "category": "outputs", + "key": "ChatOutput", + "score": 0.003169567463043492, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "ChatOutput", + "id": "ChatOutput-ErUad" + }, + "selected": true, + "measured": { + "width": 320, + "height": 230 + }, + "dragging": false + }, + { + "id": "LoopComponent-2PZYG", + "type": "genericNode", + "position": { + "x": 708.8336784322848, + "y": 1171.0111908227896 + }, + "data": { + "node": { + "template": { + "_type": "Component", + "data": { + "tool_mode": false, + "trace_as_metadata": true, + "list": false, + "list_add_label": "Add More", + "trace_as_input": true, + "required": false, + "placeholder": "", + "show": true, + "name": "data", + "value": "", + "display_name": "Data", + "advanced": false, + "input_types": [ + "Data" + ], + "dynamic": false, + "info": "The initial list of Data objects to iterate over.", + "title_case": false, + "type": "other", + "_input_type": "DataInput" + }, + "code": { + "type": "code", + "required": true, + "placeholder": "", + "list": false, + "show": true, + "multiline": true, + "value": "from langflow.custom import Component\nfrom langflow.io import DataInput, Output\nfrom langflow.schema import Data\n\n\nclass LoopComponent(Component):\n display_name = \"Loop\"\n description = (\n \"Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.\"\n )\n icon = \"infinity\"\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The initial list of Data objects to iterate over.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Item\", name=\"item\", method=\"item_output\", allows_loop=True),\n Output(display_name=\"Done\", name=\"done\", method=\"done_output\"),\n ]\n\n def initialize_data(self) -> None:\n \"\"\"Initialize the data list, context index, and aggregated list.\"\"\"\n if self.ctx.get(f\"{self._id}_initialized\", False):\n return\n\n # Ensure data is a list of Data objects\n data_list = self._validate_data(self.data)\n\n # Store the initial data and context variables\n self.update_ctx(\n {\n f\"{self._id}_data\": data_list,\n f\"{self._id}_index\": 0,\n f\"{self._id}_aggregated\": [],\n f\"{self._id}_initialized\": True,\n }\n )\n\n def _validate_data(self, data):\n \"\"\"Validate and return a list of Data objects.\"\"\"\n if isinstance(data, Data):\n return [data]\n if isinstance(data, list) and all(isinstance(item, Data) for item in data):\n return data\n msg = \"The 'data' input must be a list of Data objects or a single Data object.\"\n raise TypeError(msg)\n\n def evaluate_stop_loop(self) -> bool:\n \"\"\"Evaluate whether to stop item or done output.\"\"\"\n current_index = self.ctx.get(f\"{self._id}_index\", 0)\n data_length = len(self.ctx.get(f\"{self._id}_data\", []))\n return current_index > data_length\n\n def item_output(self) -> Data:\n \"\"\"Output the next item in the list or stop if done.\"\"\"\n self.initialize_data()\n current_item = Data(text=\"\")\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n return Data(text=\"\")\n\n # Get data list and current index\n data_list, current_index = self.loop_variables()\n if current_index < len(data_list):\n # Output current item and increment index\n try:\n current_item = data_list[current_index]\n except IndexError:\n current_item = Data(text=\"\")\n self.aggregated_output()\n self.update_ctx({f\"{self._id}_index\": current_index + 1})\n return current_item\n\n def done_output(self) -> Data:\n \"\"\"Trigger the done output when iteration is complete.\"\"\"\n self.initialize_data()\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n self.start(\"done\")\n\n return self.ctx.get(f\"{self._id}_aggregated\", [])\n self.stop(\"done\")\n return Data(text=\"\")\n\n def loop_variables(self):\n \"\"\"Retrieve loop variables from context.\"\"\"\n return (\n self.ctx.get(f\"{self._id}_data\", []),\n self.ctx.get(f\"{self._id}_index\", 0),\n )\n\n def aggregated_output(self) -> Data:\n \"\"\"Return the aggregated list once all items are processed.\"\"\"\n self.initialize_data()\n\n # Get data list and aggregated list\n data_list = self.ctx.get(f\"{self._id}_data\", [])\n aggregated = self.ctx.get(f\"{self._id}_aggregated\", [])\n\n # Check if loop input is provided and append to aggregated list\n if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list):\n aggregated.append(self.item)\n self.update_ctx({f\"{self._id}_aggregated\": aggregated})\n return aggregated\n", + "fileTypes": [], + "file_path": "", + "password": false, + "name": "code", + "advanced": true, + "dynamic": true, + "info": "", + "load_from_db": false, + "title_case": false + } + }, + "description": "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.", + "icon": "infinity", + "base_classes": [ + "Data" + ], + "display_name": "Loop", + "documentation": "", + "minimized": false, + "custom_fields": {}, + "output_types": [], + "pinned": false, + "conditional_paths": [], + "frozen": false, + "outputs": [ + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "item", + "display_name": "Item", + "method": "item_output", + "value": "__UNDEFINED__", + "cache": true, + "allows_loop": true + }, + { + "types": [ + "Data" + ], + "selected": "Data", + "name": "done", + "display_name": "Done", + "method": "done_output", + "value": "__UNDEFINED__", + "cache": true, + "allows_loop": false + } + ], + "field_order": [ + "data" + ], + "beta": false, + "legacy": false, + "edited": false, + "metadata": {}, + "tool_mode": false, + "lf_version": "1.1.1" + }, + "showNode": true, + "type": "LoopComponent", + "id": "LoopComponent-2PZYG", + "description": "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.", + "display_name": "Loop" + }, + "selected": false, + "measured": { + "width": 320, + "height": 280 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "ChatInput-OhyAs", + "sourceHandle": "{œdataTypeœ:œChatInputœ,œidœ:œChatInput-OhyAsœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}", + "target": "MessagetoData-nUvB5", + "targetHandle": "{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-nUvB5œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "data": { + "targetHandle": { + "fieldName": "message", + "id": "MessagetoData-nUvB5", + "inputTypes": [ + "Message" + ], + "type": "str" + }, + "sourceHandle": { + "dataType": "ChatInput", + "id": "ChatInput-OhyAs", + "name": "message", + "output_types": [ + "Message" + ] + } + }, + "id": "reactflow__edge-ChatInput-OhyAs{œdataTypeœ:œChatInputœ,œidœ:œChatInput-OhyAsœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-nUvB5{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-nUvB5œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "className": "", + "animated": false, + "selected": false + }, + { + "source": "MessagetoData-nUvB5", + "sourceHandle": "{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-nUvB5œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}", + "target": "SplitText-CHgY9", + "targetHandle": "{œfieldNameœ:œdata_inputsœ,œidœ:œSplitText-CHgY9œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "data": { + "targetHandle": { + "fieldName": "data_inputs", + "id": "SplitText-CHgY9", + "inputTypes": [ + "Data" + ], + "type": "other" + }, + "sourceHandle": { + "dataType": "MessagetoData", + "id": "MessagetoData-nUvB5", + "name": "data", + "output_types": [ + "Data" + ] + } + }, + "id": "reactflow__edge-MessagetoData-nUvB5{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-nUvB5œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-SplitText-CHgY9{œfieldNameœ:œdata_inputsœ,œidœ:œSplitText-CHgY9œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "animated": false, + "className": "" + }, + { + "source": "ParseData-COhje", + "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-COhjeœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}", + "target": "MessagetoData-5hKU0", + "targetHandle": "{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-5hKU0œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "data": { + "targetHandle": { + "fieldName": "message", + "id": "MessagetoData-5hKU0", + "inputTypes": [ + "Message" + ], + "type": "str" + }, + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-COhje", + "name": "text", + "output_types": [ + "Message" + ] + } + }, + "id": "reactflow__edge-ParseData-COhje{œdataTypeœ:œParseDataœ,œidœ:œParseData-COhjeœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-5hKU0{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-5hKU0œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "animated": false, + "className": "" + }, + { + "source": "ParseData-5THFG", + "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-5THFGœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}", + "target": "ChatOutput-ErUad", + "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-ErUadœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "data": { + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-ErUad", + "inputTypes": [ + "Message" + ], + "type": "str" + }, + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-5THFG", + "name": "text", + "output_types": [ + "Message" + ] + } + }, + "id": "reactflow__edge-ParseData-5THFG{œdataTypeœ:œParseDataœ,œidœ:œParseData-5THFGœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-ErUad{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-ErUadœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "animated": false, + "className": "" + }, + { + "source": "SplitText-CHgY9", + "sourceHandle": "{œdataTypeœ:œSplitTextœ,œidœ:œSplitText-CHgY9œ,œnameœ:œchunksœ,œoutput_typesœ:[œDataœ]}", + "target": "LoopComponent-2PZYG", + "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œLoopComponent-2PZYGœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "data": { + "targetHandle": { + "fieldName": "data", + "id": "LoopComponent-2PZYG", + "inputTypes": [ + "Data" + ], + "type": "other" + }, + "sourceHandle": { + "dataType": "SplitText", + "id": "SplitText-CHgY9", + "name": "chunks", + "output_types": [ + "Data" + ] + } + }, + "id": "reactflow__edge-SplitText-CHgY9{œdataTypeœ:œSplitTextœ,œidœ:œSplitText-CHgY9œ,œnameœ:œchunksœ,œoutput_typesœ:[œDataœ]}-LoopComponent-2PZYG{œfieldNameœ:œdataœ,œidœ:œLoopComponent-2PZYGœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "animated": false, + "className": "" + }, + { + "source": "LoopComponent-2PZYG", + "sourceHandle": "{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-2PZYGœ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", + "target": "ParseData-COhje", + "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-COhjeœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "data": { + "targetHandle": { + "fieldName": "data", + "id": "ParseData-COhje", + "inputTypes": [ + "Data" + ], + "type": "other" + }, + "sourceHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-2PZYG", + "name": "item", + "output_types": [ + "Data" + ] + } + }, + "id": "reactflow__edge-LoopComponent-2PZYG{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-2PZYGœ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}-ParseData-COhje{œfieldNameœ:œdataœ,œidœ:œParseData-COhjeœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "animated": false, + "className": "" + }, + { + "source": "LoopComponent-2PZYG", + "sourceHandle": "{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-2PZYGœ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}", + "target": "ParseData-5THFG", + "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-5THFGœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "data": { + "targetHandle": { + "fieldName": "data", + "id": "ParseData-5THFG", + "inputTypes": [ + "Data" + ], + "type": "other" + }, + "sourceHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-2PZYG", + "name": "done", + "output_types": [ + "Data" + ] + } + }, + "id": "reactflow__edge-LoopComponent-2PZYG{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-2PZYGœ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}-ParseData-5THFG{œfieldNameœ:œdataœ,œidœ:œParseData-5THFGœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "animated": false, + "className": "" + }, + { + "source": "MessagetoData-5hKU0", + "sourceHandle": "{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-5hKU0œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}", + "target": "LoopComponent-2PZYG", + "targetHandle": "{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-2PZYGœ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", + "data": { + "targetHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-2PZYG", + "name": "item", + "output_types": [ + "Data" + ] + }, + "sourceHandle": { + "dataType": "MessagetoData", + "id": "MessagetoData-5hKU0", + "name": "data", + "output_types": [ + "Data" + ] + } + }, + "id": "xy-edge__MessagetoData-5hKU0{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-5hKU0œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-2PZYG{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-2PZYGœ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", + "animated": false, + "className": "" + } + ], + "viewport": { + "x": -26.190028726327284, + "y": -104.77756356670614, + "zoom": 0.45277436236404656 + } + }, + "description": "test loop", + "name": "LoopTest", + "last_tested_version": "1.1.1", + "endpoint_name": null, + "is_component": false +} \ No newline at end of file diff --git a/src/backend/tests/unit/api/v1/test_endpoints.py b/src/backend/tests/unit/api/v1/test_endpoints.py index a048d0e10..9bcd127b6 100644 --- a/src/backend/tests/unit/api/v1/test_endpoints.py +++ b/src/backend/tests/unit/api/v1/test_endpoints.py @@ -88,12 +88,12 @@ async def test_update_component_model_name_options(client: AsyncClient, logged_i assert "template" in result assert "model_name" in result["template"] assert isinstance(result["template"]["model_name"]["options"], list) - assert ( - len(result["template"]["model_name"]["options"]) > 0 - ), f"Model names: {result['template']['model_name']['options']}" - assert ( - current_model_names != result["template"]["model_name"]["options"] - ), f"Current model names: {current_model_names}, New model names: {result['template']['model_name']['options']}" + assert len(result["template"]["model_name"]["options"]) > 0, ( + f"Model names: {result['template']['model_name']['options']}" + ) + assert current_model_names != result["template"]["model_name"]["options"], ( + f"Current model names: {current_model_names}, New model names: {result['template']['model_name']['options']}" + ) # Now test with Custom provider template["agent_llm"]["value"] = "Custom" request.field_value = "Custom" diff --git a/src/backend/tests/unit/components/agents/test_agent_component.py b/src/backend/tests/unit/components/agents/test_agent_component.py index ba83920a8..45efa5a71 100644 --- a/src/backend/tests/unit/components/agents/test_agent_component.py +++ b/src/backend/tests/unit/components/agents/test_agent_component.py @@ -78,9 +78,9 @@ class TestAgentComponent(ComponentTestBaseWithoutClient): assert all(provider in updated_config["agent_llm"]["options"] for provider in MODEL_PROVIDERS_DICT) assert "Anthropic" in updated_config["agent_llm"]["options"] assert updated_config["agent_llm"]["input_types"] == [] - assert any( - "sonnet" in option.lower() for option in updated_config["model_name"]["options"] - ), f"Options: {updated_config['model_name']['options']}" + assert any("sonnet" in option.lower() for option in updated_config["model_name"]["options"]), ( + f"Options: {updated_config['model_name']['options']}" + ) # Test updating build config for Custom updated_config = await component.update_build_config(build_config, "Custom", "agent_llm") diff --git a/src/backend/tests/unit/components/logic/__init__.py b/src/backend/tests/unit/components/logic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/unit/components/logic/test_loop.py b/src/backend/tests/unit/components/logic/test_loop.py new file mode 100644 index 000000000..208ee54f5 --- /dev/null +++ b/src/backend/tests/unit/components/logic/test_loop.py @@ -0,0 +1,91 @@ +from uuid import UUID + +import pytest +from httpx import AsyncClient +from langflow.components.logic.loop import LoopComponent +from langflow.memory import aget_messages +from langflow.schema.data import Data +from langflow.services.database.models.flow import FlowCreate +from orjson import orjson + +from tests.base import ComponentTestBaseWithClient + +TEXT = ( + "lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet. " + "lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet. " + "lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet." +) + + +class TestLoopComponentWithAPI(ComponentTestBaseWithClient): + @pytest.fixture + def component_class(self): + """Return the component class to test.""" + return LoopComponent + + @pytest.fixture + def file_names_mapping(self): + """Return an empty list since this component doesn't have version-specific files.""" + return [] + + @pytest.fixture + def default_kwargs(self): + """Return the default kwargs for the component.""" + return { + "data": [[Data(text="Hello World")]], + "loop_input": [Data(text=TEXT)], + } + + def test_latest_version(self, default_kwargs) -> None: + """Test that the component works with the latest version.""" + result = LoopComponent(**default_kwargs) + assert result is not None, "Component returned None for the latest version." + + async def _create_flow(self, client, json_loop_test, logged_in_headers): + vector_store = orjson.loads(json_loop_test) + data = vector_store["data"] + vector_store = FlowCreate(name="Flow", description="description", data=data, endpoint_name="f") + response = await client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers) + response.raise_for_status() + return response.json()["id"] + + async def check_messages(self, flow_id): + messages = await aget_messages(flow_id=UUID(flow_id), order="ASC") + assert len(messages) == 2 + assert messages[0].session_id == flow_id + assert messages[0].sender == "User" + assert messages[0].sender_name == "User" + assert messages[0].text != "" + assert messages[1].session_id == flow_id + assert messages[1].sender == "Machine" + assert messages[1].sender_name == "AI" + assert len(messages[1].text) > 0 + + async def test_build_flow_loop(self, client, json_loop_test, logged_in_headers): + # TODO: Add a test for the loop where the loop component gets updated even the component in json + flow_id = await self._create_flow(client, json_loop_test, logged_in_headers) + + async with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r: + async for line in r.aiter_lines(): + # httpx split by \n, but ndjson sends two \n for each line + if line: + # Process the line if needed + pass + + await self.check_messages(flow_id) + + async def test_run_flow_loop(self, client: AsyncClient, created_api_key, json_loop_test, logged_in_headers): + flow_id = await self._create_flow(client, json_loop_test, logged_in_headers) + headers = {"x-api-key": created_api_key.api_key} + payload = { + "input_value": TEXT, + "input_type": "chat", + "session_id": f"{flow_id}run", + "output_type": "chat", + "tweaks": {}, + } + response = await client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) + data = response.json() + assert "outputs" in data + assert "session_id" in data + assert len(data["outputs"][-1]["outputs"]) > 0 diff --git a/src/backend/tests/unit/test_schema.py b/src/backend/tests/unit/test_schema.py index ea29f61f5..19f222cac 100644 --- a/src/backend/tests/unit/test_schema.py +++ b/src/backend/tests/unit/test_schema.py @@ -133,6 +133,7 @@ class TestOutput: def test_output_to_dict(self): output_obj = Output(name="test_output") assert output_obj.to_dict() == { + "allows_loop": False, "types": [], "name": "test_output", "display_name": "test_output", diff --git a/src/frontend/src/CustomEdges/index.tsx b/src/frontend/src/CustomEdges/index.tsx index e4db9bcb7..28ae265ce 100644 --- a/src/frontend/src/CustomEdges/index.tsx +++ b/src/frontend/src/CustomEdges/index.tsx @@ -1,4 +1,5 @@ import useFlowStore from "@/stores/flowStore"; +import { scapeJSONParse } from "@/utils/reactflowUtils"; import { BaseEdge, EdgeProps, getBezierPath, Position } from "@xyflow/react"; export function DefaultEdge({ @@ -17,10 +18,17 @@ export function DefaultEdge({ const sourceNode = getNode(source); const targetNode = getNode(target); + const targetHandleObject = scapeJSONParse(targetHandleId!); + const sourceXNew = (sourceNode?.position.x ?? 0) + (sourceNode?.measured?.width ?? 0); const targetXNew = targetNode?.position.x ?? 0; + const distance = 200 + 0.1 * (Math.abs(sourceXNew - targetXNew) / 2); + const distanceY = 200 + 0.3 * Math.abs(sourceY - targetY); + + const edgePathLoop = `M ${sourceXNew} ${sourceY} C ${sourceXNew + distance} ${sourceY + distanceY}, ${targetXNew - distance} ${targetY + distanceY}, ${targetXNew} ${targetY}`; + const [edgePath] = getBezierPath({ sourceX: sourceXNew, sourceY, @@ -30,5 +38,11 @@ export function DefaultEdge({ targetY, }); - return ; + return ( + + ); } diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx index f161e4c53..1aa6f08ec 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx @@ -1,7 +1,8 @@ +import { Badge } from "@/components/ui/badge"; import { ICON_STROKE_WIDTH } from "@/constants/constants"; +import { targetHandleType } from "@/types/flow"; import { useUpdateNodeInternals } from "@xyflow/react"; import { cloneDeep } from "lodash"; -import { TextSearch } from "lucide-react"; import { memo, useCallback, useEffect, useMemo, useRef } from "react"; import ForwardedIconComponent, { default as IconComponent, @@ -14,6 +15,7 @@ import { NodeOutputFieldComponentType } from "../../../../types/components"; import { getGroupOutputNodeId, scapedJSONStringfy, + scapeJSONParse, } from "../../../../utils/reactflowUtils"; import { cn, @@ -205,6 +207,18 @@ function NodeOutputField({ [edges, id], ); + const looping = useMemo(() => { + return edges.some((edge) => { + const targetHandleObject: targetHandleType = scapeJSONParse( + edge.targetHandle!, + ); + return ( + targetHandleObject.output_types && + edge.sourceHandle === scapedJSONStringfy(id) + ); + }); + }, [edges, id]); + const handleUpdateOutputHide = useCallback( (value?: boolean) => { setNode(data.id, (oldNode) => { @@ -235,6 +249,41 @@ function NodeOutputField({ } }, [disabledOutput, data.node?.outputs, handleUpdateOutputHide, index]); + const LoopHandle = useMemo(() => { + if (data.node?.outputs![index].allows_loop) { + return ( + + ); + } + }, [ + nodes, + tooltipTitle, + id, + title, + edges, + data.id, + myData, + colors, + setFilterEdge, + showNode, + data?.type, + colorName, + ]); + const Handle = useMemo( () => ( + {LoopHandle}
+ {data.node?.outputs![index].allows_loop && ( + + + + )} handleUpdateOutputHide()} @@ -324,7 +379,7 @@ function NodeOutputField({ : "Please build the component first" } > -
+
+ {looping && ( + + Looping + + )}
diff --git a/src/frontend/src/constants/alerts_constants.tsx b/src/frontend/src/constants/alerts_constants.tsx index 679b19da1..62c2a0d19 100644 --- a/src/frontend/src/constants/alerts_constants.tsx +++ b/src/frontend/src/constants/alerts_constants.tsx @@ -1,5 +1,7 @@ // ERROR export const MISSED_ERROR_ALERT = "Oops! Looks like you missed something"; +export const INCOMPLETE_LOOP_ERROR_ALERT = + "The flow has an incomplete loop. Check your connections and try again."; export const INVALID_FILE_ALERT = "Please select a valid file. Only these file types are allowed:"; export const CONSOLE_ERROR_MSG = "Error occurred while uploading file"; diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index 016cfac7f..db980623d 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -41,6 +41,7 @@ import { scapedJSONStringfy, unselectAllNodesEdges, updateGroupRecursion, + validateEdge, validateNodes, } from "../utils/reactflowUtils"; import { getInputsAndOutputs } from "../utils/storeUtils"; @@ -606,6 +607,25 @@ const useFlowStore = create((set, get) => ({ const setSuccessData = useAlertStore.getState().setSuccessData; const setErrorData = useAlertStore.getState().setErrorData; const setNoticeData = useAlertStore.getState().setNoticeData; + + const edges = get().edges; + let error = false; + for (const edge of edges) { + const errors = validateEdge(edge, get().nodes, edges); + if (errors.length > 0) { + error = true; + setErrorData({ + title: MISSED_ERROR_ALERT, + list: errors, + }); + } + } + if (error) { + get().setIsBuilding(false); + get().setLockChat(false); + throw new Error("Invalid components"); + } + function validateSubgraph(nodes: string[]) { const errorsObjs = validateNodes( get().nodes.filter((node) => nodes.includes(node.id)), diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 6d9e0bba2..650f4f80f 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -4,7 +4,6 @@ import { UseQueryOptions, UseQueryResult, } from "@tanstack/react-query"; -import { Edge, Node, Viewport } from "@xyflow/react"; import { ChatInputType, ChatOutputType } from "../chat"; import { FlowType } from "../flow"; //kind and class are just representative names to represent the actual structure of the object received by the API @@ -103,6 +102,7 @@ export type OutputFieldType = { display_name: string; hidden?: boolean; proxy?: OutputFieldProxyType; + allows_loop?: boolean; }; export type errorsTypeAPI = { function: { errors: Array }; diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index 197c521b1..881d842eb 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -101,8 +101,10 @@ export type sourceHandleType = { //left side export type targetHandleType = { inputTypes?: string[]; + output_types?: string[]; type: string; fieldName: string; + name?: string; id: string; proxy?: { field: string; id: string }; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 8aa4dff72..6d1442a4b 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -2,9 +2,11 @@ import { getLeftHandleId, getRightHandleId, } from "@/CustomNodes/utils/get-handle-id"; +import { INCOMPLETE_LOOP_ERROR_ALERT } from "@/constants/alerts_constants"; import { Connection, Edge, + getOutgoers, Node, OnSelectionChangeParams, ReactFlowJsonObject, @@ -18,8 +20,8 @@ import { IS_MAC, LANGFLOW_SUPPORTED_TYPES, OUTPUT_TYPES, - SUCCESS_BUILD, specialCharsRegex, + SUCCESS_BUILD, } from "../constants/constants"; import { DESCRIPTIONS } from "../flow_constants"; import { @@ -68,14 +70,39 @@ export function cleanEdges(nodes: AllNodeType[], edges: EdgeType[]) { if (targetHandle) { const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle); const field = targetHandleObject.fieldName; - const id: targetHandleType = { - type: targetNode.data.node!.template[field]?.type, - fieldName: field, - id: targetNode.data.id, - inputTypes: targetNode.data.node!.template[field]?.input_types, - }; - if (targetNode.data.node!.template[field]?.proxy) { - id.proxy = targetNode.data.node!.template[field]?.proxy; + let id: targetHandleType | sourceHandleType; + + const templateFieldType = targetNode.data.node!.template[field]?.type; + const inputTypes = targetNode.data.node!.template[field]?.input_types; + const hasProxy = targetNode.data.node!.template[field]?.proxy; + + if ( + !field && + targetHandleObject.name && + targetNode.type === "genericNode" + ) { + const dataType = targetNode.data.type; + const outputTypes = + targetNode.data.node!.outputs?.find( + (output) => output.name === targetHandleObject.name, + )?.types ?? []; + + id = { + dataType: dataType ?? "", + name: targetHandleObject.name, + id: targetNode.data.id, + output_types: outputTypes, + }; + } else { + id = { + type: templateFieldType, + fieldName: field, + id: targetNode.data.id, + inputTypes: inputTypes, + }; + if (hasProxy) { + id.proxy = targetNode.data.node!.template[field]?.proxy; + } } if (scapedJSONStringfy(id) !== targetHandle) { newEdges = newEdges.filter((e) => e.id !== edge.id); @@ -132,7 +159,9 @@ export function detectBrokenEdgesEdges(nodes: AllNodeType[], edges: Edge[]) { displayName: targetNode.data.node!.display_name, field: targetNode.data.node!.template[targetHandleObject.fieldName] - ?.display_name ?? targetHandleObject.fieldName, + ?.display_name ?? + targetHandleObject.fieldName ?? + targetHandleObject.name, }, }; } @@ -161,14 +190,39 @@ export function detectBrokenEdgesEdges(nodes: AllNodeType[], edges: Edge[]) { if (targetHandle) { const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle); const field = targetHandleObject.fieldName; - const id: targetHandleType = { - type: targetNode.data.node!.template[field]?.type, - fieldName: field, - id: targetNode.data.id, - inputTypes: targetNode.data.node!.template[field]?.input_types, - }; - if (targetNode.data.node!.template[field]?.proxy) { - id.proxy = targetNode.data.node!.template[field]?.proxy; + let id: sourceHandleType | targetHandleType; + + const templateFieldType = targetNode.data.node!.template[field]?.type; + const inputTypes = targetNode.data.node!.template[field]?.input_types; + const hasProxy = targetNode.data.node!.template[field]?.proxy; + + if ( + !field && + targetHandleObject.name && + targetNode.type === "genericNode" + ) { + const dataType = targetNode.data.type; + const outputTypes = + targetNode.data.node!.outputs?.find( + (output) => output.name === targetHandleObject.name, + )?.types ?? []; + + id = { + dataType: dataType ?? "", + name: targetHandleObject.name, + id: targetNode.data.id, + output_types: outputTypes, + }; + } else { + id = { + type: templateFieldType, + fieldName: field, + id: targetNode.data.id, + inputTypes: inputTypes, + }; + if (hasProxy) { + id.proxy = targetNode.data.node!.template[field]?.proxy; + } } if (scapedJSONStringfy(id) !== targetHandle) { newEdges = newEdges.filter((e) => e.id !== edge.id); @@ -219,7 +273,7 @@ export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, nodes: AllNodeType[], edges: EdgeType[], -) { +): boolean { if (source === target) { return false; } @@ -229,6 +283,13 @@ export function isValidConnection( targetHandleObject.inputTypes?.some( (n) => n === sourceHandleObject.dataType, ) || + (targetHandleObject.output_types && + (targetHandleObject.output_types?.some( + (n) => n === sourceHandleObject.dataType, + ) || + sourceHandleObject.output_types.some((t) => + targetHandleObject.output_types?.some((n) => n === t), + ))) || sourceHandleObject.output_types.some( (t) => targetHandleObject.inputTypes?.some((n) => n === t) || @@ -241,9 +302,15 @@ export function isValidConnection( return true; } } else if ( - (!targetNode.template[targetHandleObject.fieldName].list && + targetHandleObject.output_types && + !edges.find((e) => e.targetHandle === targetHandle) + ) { + return true; + } else if ( + !targetHandleObject.output_types && + ((!targetNode.template[targetHandleObject.fieldName].list && !edges.find((e) => e.targetHandle === targetHandle)) || - targetNode.template[targetHandleObject.fieldName].list + targetNode.template[targetHandleObject.fieldName].list) ) { return true; } @@ -485,9 +552,51 @@ Array<{ id: string; errors: Array }> { id: n.id, errors: validateNode(n, edges), })); + return nodeMap.filter((n) => n.errors?.length); } +export function validateEdge( + e: EdgeType, + nodes: AllNodeType[], + edges: EdgeType[], +): Array { + const targetHandleObject: targetHandleType = scapeJSONParse(e.targetHandle!); + + const loop = hasLoop(e, nodes, edges); + if (targetHandleObject.output_types && !loop) { + return [INCOMPLETE_LOOP_ERROR_ALERT]; + } + return []; +} + +function hasLoop( + e: EdgeType, + nodes: AllNodeType[], + edges: EdgeType[], +): boolean { + const source = e.source; + const target = e.target; + + // Check if this connection would create a cycle + const targetNode = nodes.find((n) => n.id === target); + + const hasCycle = (node, visited = new Set()): boolean => { + if (visited.has(node.id)) return false; + + visited.add(node.id); + + for (const outgoer of getOutgoers(node, nodes, edges)) { + if (outgoer.id === source) return true; + if (hasCycle(outgoer, visited)) return true; + } + return false; + }; + + if (targetNode?.id === source) return false; + return hasCycle(targetNode); +} + export function updateEdges(edges: EdgeType[]) { if (edges) edges.forEach((edge) => { diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index b53fe4e04..b82ea6903 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -109,6 +109,7 @@ import { HelpCircle, Home, Image, + Infinity, Info, InstagramIcon, Key, @@ -866,6 +867,7 @@ export const nodeIconsLucide: iconsType = { Share2, Share, GitBranchPlus, + Infinity, Loader2, BookmarkPlus, Heart, diff --git a/src/frontend/tests/extended/features/loop-component.spec.ts b/src/frontend/tests/extended/features/loop-component.spec.ts new file mode 100644 index 000000000..b582a48b3 --- /dev/null +++ b/src/frontend/tests/extended/features/loop-component.spec.ts @@ -0,0 +1,243 @@ +import { expect, test } from "@playwright/test"; +import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { zoomOut } from "../../utils/zoom-out"; + +test( + "should process loop with update data correctly", + { tag: ["@release", "@workspace", "@components"] }, + async ({ page }) => { + await awaitBootstrapTest(page); + await page.getByTestId("blank-flow").click(); + + await page.waitForSelector( + '[data-testid="sidebar-custom-component-button"]', + { + timeout: 3000, + }, + ); + + // Add URL component + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("url"); + await page.waitForSelector('[data-testid="dataURL"]', { + timeout: 1000, + }); + + await zoomOut(page, 3); + + await page + .getByTestId("dataURL") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 100, y: 100 }, + }); + + // Add Loop component + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("loop"); + await page.waitForSelector('[data-testid="logicLoop"]', { + timeout: 1000, + }); + + await page + .getByTestId("logicLoop") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 300, y: 100 }, + }); + + // Add Update Data component + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("update data"); + await page.waitForSelector('[data-testid="processingUpdate Data"]', { + timeout: 1000, + }); + + await page + .getByTestId("processingUpdate Data") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 500, y: 100 }, + }); + + // Add Parse Data component + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("parse data"); + await page.waitForSelector('[data-testid="processingParse Data"]', { + timeout: 1000, + }); + + await page + .getByTestId("processingParse Data") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 700, y: 100 }, + }); + + //This one is for testing the wrong loop message + await page + .getByTestId("processingParse Data") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 700, y: 400 }, + }); + + const secondParseDataOutput = await page + .getByTestId("handle-parsedata-shownode-data list-right") + .nth(2); + + const loopItemInput = await page + .getByTestId("handle-loopcomponent-shownode-item-left") + .first(); + + // Connecting the second parse data to the loop item to test the wrong loop message + + await secondParseDataOutput.hover(); + await page.mouse.down(); + await loopItemInput.hover(); + await page.mouse.up(); + + // Add Chat Output component + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.waitForSelector('[data-testid="outputsChat Output"]', { + timeout: 1000, + }); + + await page + .getByTestId("outputsChat Output") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 900, y: 100 }, + }); + + await page.getByTestId("fit_view").click(); + + await zoomOut(page, 2); + + // Loop Item -> Update Data + + const loopItemHandle = await page + .getByTestId("handle-loopcomponent-shownode-item-right") + .first(); + const updateDataInput = await page + .getByTestId("handle-updatedata-shownode-data-left") + .first(); + + await loopItemHandle.hover(); + await page.mouse.down(); + await updateDataInput.hover(); + await page.mouse.up(); + + // URL -> Loop Data + const urlOutput = await page + .getByTestId("handle-url-shownode-data-right") + .first(); + const loopInput = await page + .getByTestId("handle-loopcomponent-shownode-data-left") + .first(); + + await urlOutput.hover(); + await page.mouse.down(); + await loopInput.hover(); + await page.mouse.up(); + + // Loop Done -> Parse Data + const loopDoneHandle = await page + .getByTestId("handle-loopcomponent-shownode-done-right") + .first(); + const parseDataInput = await page + .getByTestId("handle-parsedata-shownode-data-left") + .first(); + + await loopDoneHandle.hover(); + await page.mouse.down(); + await parseDataInput.hover(); + await page.mouse.up(); + + await page.getByTestId("div-generic-node").nth(5).click(); + + await page.getByTestId("more-options-modal").click(); + + await page.getByTestId("expand-button-modal").click(); + + // Parse Data -> Chat Output + const parseDataOutput = await page + .getByTestId("handle-parsedata-shownode-message-right") + .first(); + + const chatOutputInput = await page + .getByTestId("handle-chatoutput-shownode-text-left") + .first(); + + await parseDataOutput.hover(); + await page.mouse.down(); + await chatOutputInput.hover(); + await page.mouse.up(); + + await page.getByTestId("input-list-plus-btn_urls-0").click(); + + // Configure components + await page + .getByTestId("inputlist_str_urls_0") + .fill("https://en.wikipedia.org/wiki/Artificial_intelligence"); + await page + .getByTestId("inputlist_str_urls_1") + .fill("https://en.wikipedia.org/wiki/Artificial_intelligence"); + + await page.getByTestId("div-generic-node").nth(2).click(); + await page.getByTestId("int_int_number_of_fields").fill("1"); + await page.getByTestId("div-generic-node").nth(2).click(); + + await page.getByTestId("keypair0").fill("text"); + await page.getByTestId("keypair100").fill("modified_value"); + + // Build and run, expect the wrong loop message + await page.getByTestId("button_run_chat output").click(); + await page.waitForSelector("text=The flow has an incomplete loop.", { + timeout: 30000, + }); + await page.getByText("The flow has an incomplete loop.").last().click({ + timeout: 15000, + }); + + // Delete the second parse data used to test + + await page.getByTestId("div-generic-node").nth(4).click(); + + await page.getByTestId("more-options-modal").click(); + + await page.getByText("Delete").first().click(); + + // Update Data -> Loop Item (left side) + const updateDataOutput = await page + .getByTestId("handle-updatedata-shownode-data-right") + .first(); + + await updateDataOutput.hover(); + await page.mouse.down(); + await loopItemInput.hover(); + await page.mouse.up(); + + // Build and run + await page.getByTestId("button_run_chat output").click(); + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); + + // Verify output + await page.waitForSelector( + '[data-testid="output-inspection-message-chatoutput"]', + { + timeout: 1000, + }, + ); + await page + .getByTestId("output-inspection-message-chatoutput") + .first() + .click(); + await page.getByRole("gridcell").nth(4).click(); + + const output = await page.getByPlaceholder("Empty").textContent(); + expect(output).toContain("modified_value"); + + // Count occurrences of modified_value in output + const matches = output?.match(/modified_value/g) || []; + expect(matches).toHaveLength(2); + }, +);