From 5ef84f79116710581c1b4d96d95b70b156c38327 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 15 Jul 2024 20:56:47 -0300 Subject: [PATCH] feat: add CrewAI components and refactor codebase (#2647) * feat: Add CrewAI icon component This commit adds the CrewAI icon component to the project. The component is imported and used in the styleUtils file, allowing it to be used throughout the application. This addition enhances the visual representation of the CrewAI feature in the user interface. * feat: Add CrewAI[tools] dependency to project This commit adds the CrewAI dependency to the project by including it in the pyproject.toml file. The version specified is "^0.36.0" and the "tools" extras are included. This addition allows the project to utilize the features provided by CrewAI. * fix: update crewai icon size * feat: Add CrewAIAgent component This commit adds the CrewAIAgent component to the project. The component represents an agent of CrewAI and includes various inputs and outputs for role, goal, backstory, tools, language model, memory, verbosity, and delegation. This addition enhances the functionality of the project by integrating with CrewAI and allows for more advanced agent-based interactions. * feat: Add CrewAICrew component This commit adds the CrewAICrew component to the project. The component represents a group of agents and defines how they should collaborate and the tasks they should perform. It includes various inputs and outputs for tasks, agents, topic, verbosity, memory, cache, max RPM, process, and more. This addition enhances the functionality of the project by integrating with CrewAI and allows for more advanced agent-based interactions. * feat: Add CrewAITask component This commit adds the CrewAITask component to the project. The component represents a task in CrewAI and includes inputs for description, expected output, tools, agent, and async execution. It also provides an output for the task itself. This addition enhances the functionality of the project by integrating with CrewAI and allows for the creation and execution of tasks within the system. * refactor: Update build configuration in custom_component_update endpoint This commit updates the build configuration in the custom_component_update endpoint of the endpoints.py file. The previous implementation was assigning the result of code_request.get_template() directly to the build_config parameter, which caused an issue. The updated code now assigns the result to the updated_build_config variable before passing it to the cc_instance.update_build_config() method. This change ensures that the correct build configuration is used and improves the functionality of the custom_component_update endpoint. * feat: Add SequentialTask and HierarchicalTask classes This commit adds the SequentialTask and HierarchicalTask classes to the tasks.py file in the base/agents/crewai directory. These classes represent different types of tasks in the CrewAI system. The SequentialTask class is used for sequential tasks, while the HierarchicalTask class is used for hierarchical tasks. This addition enhances the functionality of the project by providing the necessary classes for implementing different types of tasks in CrewAI. * refactor: Update CrewAIAgentComponent class name This commit updates the class name from "CrewAIAgent" to "CrewAIAgentComponent" in the CrewAIAgent.py file. The new name better reflects the nature of the class as a component and improves the clarity of the codebase. * refactor: Update CrewAICrewSequential component This commit adds the CrewAICrewSequential component to the project. The component represents a group of agents and defines how they should collaborate and the tasks they should perform in a sequential manner. It includes various inputs and outputs for tasks, verbosity, memory, cache, max RPM, and more. This addition enhances the functionality of the project by providing a specific component for sequential tasks in the CrewAI system. * feat: Add CrewAICrewHierarchical component This commit adds the CrewAICrewHierarchical component to the project. The component represents a group of agents and defines how they should collaborate and the tasks they should perform in a hierarchical manner. It includes various inputs and outputs for tasks, verbosity, memory, cache, max RPM, and more. This addition enhances the functionality of the project by providing a specific component for hierarchical tasks in the CrewAI system. * refactor: Rename CrewAITask.py to CrewAITaskHierarchical.py This commit renames the file "CrewAITask.py" to "CrewAITaskHierarchical.py" in the "helpers" directory of the "langflow/components/helpers" package. The new name better reflects the purpose of the file, which is to define the "CrewAITaskHierarchical" component. This change improves the clarity and organization of the codebase. * feat: Add CrewAITaskSequential component This commit adds the CrewAITaskSequential component to the project. The component represents a sequential task in CrewAI and includes inputs for task description, expected output, tools, agent, and async execution. It also provides an output for the task itself. This addition enhances the functionality of the project by integrating with CrewAI and allows for the creation and execution of sequential tasks within the system. * feat: Add BaseCrewComponent class This commit adds the BaseCrewComponent class to the project. The class represents a group of agents, defining how they should collaborate and the tasks they should perform. It includes various inputs and outputs for tasks, verbosity, memory, cache, max RPM, and more. This addition enhances the functionality of the project by providing a base component for creating and managing crews in the CrewAI system. * refactor: Update display name of CrewAIAgentComponent to "CrewAI Agent" * refactor: Remove CrewAICrewHierarchical component This commit removes the CrewAICrewHierarchical component from the project. The component represented a group of agents and defined how they should collaborate and the tasks they should perform in a hierarchical manner. However, it is no longer needed and has been deemed unnecessary for the current project requirements. This removal streamlines the codebase and improves the clarity of the project. * refactor: make Crew use BaseCrew class * refactor: Replace CrewAICrewSequential with SequentialCrew This commit replaces the deprecated CrewAICrewSequential component with the new SequentialCrew component. The SequentialCrew component represents a group of agents and defines how they should collaborate and the tasks they should perform in a sequential manner. This change improves the clarity and organization of the codebase by using a more descriptive and consistent naming convention for the component. It also aligns with the recent refactorings in the project, such as the removal of the CrewAICrewHierarchical component and the addition of the BaseCrewComponent class. Overall, this update enhances the functionality and maintainability of the project. * refactor: Rename CrewAITaskHierarchical.py to HierarchicalTask.py This commit renames the file "CrewAITaskHierarchical.py" to "HierarchicalTask.py" in the "helpers" directory of the "langflow/components/helpers" package. The new name better reflects the purpose of the file, which is to define the "HierarchicalTask" component. This change improves the clarity and organization of the codebase. * refactor: Rename CrewAITaskSequential.py to SequentialTask.py This commit renames the file "CrewAITaskSequential.py" to "SequentialTask.py" in the "helpers" directory of the "langflow/components/helpers" package. The new name better reflects the purpose of the file, which is to define the "SequentialTaskComponent" component. This change improves the clarity and organization of the codebase. * style: reorder imports * chore: update lock * refactor: Refactor build_crew method in BaseCrewComponent This commit refactors the build_crew method in the BaseCrewComponent class. The method was previously taking tasks and agents as arguments, but it was not using them correctly. This update removes the unnecessary arguments and fixes the method to correctly build a Crew object. This refactor improves the clarity and functionality of the codebase. * refactor: Update status assignment in CrewAIAgentComponent This commit updates the status assignment in the CrewAIAgentComponent class. Previously, the status was set using the `model_dump()` method of the agent, but it has been changed to use the `repr()` method instead. This change improves the clarity and consistency of the codebase. * refactor: Add agents input to HierarchicalCrewComponent This commit adds the "agents" input to the HierarchicalCrewComponent class in the "HierarchicalCrew.py" file. The "agents" input is a list of "Agent" objects and allows for better management and collaboration between agents within the crew. This addition enhances the functionality and flexibility of the HierarchicalCrewComponent, improving the overall codebase. * refactor: Fix post_process_raw function in artifact.py This commit fixes the post_process_raw function in the artifact.py file. Previously, the function was not correctly handling the case when the raw data was a BaseModel or a dictionary. This update ensures that the raw data is properly encoded using jsonable_encoder and updates the artifact_type accordingly. This fix improves the functionality and reliability of the codebase. * refactor: Update CustomComponent's repr_value handling This commit updates the repr_value handling in the CustomComponent class. Previously, the repr_value was being modified directly within the class, which could lead to unexpected behavior. This update ensures that the repr_value is returned instead of modifying it directly. Additionally, it adds proper handling for different types of repr_value, such as dictionaries and BaseModel objects. This refactor improves the clarity and maintainability of the codebase. * update lock * refactor: Add task input to SequentialTaskComponent This commit adds the "task" input to the SequentialTaskComponent class in the "SequentialTask.py" file. The "task" input is a SequentialTask object that will perform the task. This addition enhances the functionality and flexibility of the SequentialTaskComponent, improving the overall codebase. * refactor: Change log level of file retrieval message in LocalStorageService * refactor(crew.py): update import statement to include InputTypes from langflow.inputs.inputs module to enhance code readability and maintainability * chore: Update cachetools dependency to version 5.4.0 * feat(crew.py): add new methods get_task_callback and get_step_callback to handle task and step callbacks respectively * refactor: Add allow_code_execution input to CrewAIAgentComponent This commit adds the "allow_code_execution" input to the CrewAIAgentComponent class in the "CrewAIAgent.py" file. The "allow_code_execution" input is a boolean value that determines whether the agent is allowed to execute code. This addition enhances the functionality and flexibility of the CrewAIAgentComponent, improving the overall codebase. * Add step_callback and task_callback inputs to HierarchicalCrewComponent and SequentialCrewComponent * style(SequentialTask.py): remove unnecessary 'required' attribute from input definition in SequentialTaskComponent class * chore: fix lint issues --- poetry.lock | 275 +++++++++++++++++- pyproject.toml | 4 +- src/backend/base/langflow/api/v1/endpoints.py | 5 +- .../langflow/base/agents/crewai/__init__.py | 0 .../base/langflow/base/agents/crewai/crew.py | 80 +++++ .../base/langflow/base/agents/crewai/tasks.py | 9 + .../langflow/components/agents/CrewAIAgent.py | 85 ++++++ .../components/agents/HierarchicalCrew.py | 37 +++ .../components/agents/SequentialCrew.py | 38 +++ .../components/helpers/HierarchicalTask.py | 43 +++ .../components/helpers/SequentialTask.py | 72 +++++ .../custom/custom_component/component.py | 2 +- .../custom_component/custom_component.py | 8 +- .../base/langflow/inputs/input_mixin.py | 3 +- src/backend/base/langflow/schema/artifact.py | 15 +- .../base/langflow/services/storage/local.py | 2 +- src/frontend/src/icons/CrewAI/CrewAiIcon.jsx | 58 ++++ src/frontend/src/icons/CrewAI/crewai.svg | 12 + src/frontend/src/icons/CrewAI/index.tsx | 9 + src/frontend/src/utils/styleUtils.ts | 2 + 20 files changed, 743 insertions(+), 16 deletions(-) create mode 100644 src/backend/base/langflow/base/agents/crewai/__init__.py create mode 100644 src/backend/base/langflow/base/agents/crewai/crew.py create mode 100644 src/backend/base/langflow/base/agents/crewai/tasks.py create mode 100644 src/backend/base/langflow/components/agents/CrewAIAgent.py create mode 100644 src/backend/base/langflow/components/agents/HierarchicalCrew.py create mode 100644 src/backend/base/langflow/components/agents/SequentialCrew.py create mode 100644 src/backend/base/langflow/components/helpers/HierarchicalTask.py create mode 100644 src/backend/base/langflow/components/helpers/SequentialTask.py create mode 100644 src/frontend/src/icons/CrewAI/CrewAiIcon.jsx create mode 100644 src/frontend/src/icons/CrewAI/crewai.svg create mode 100644 src/frontend/src/icons/CrewAI/index.tsx diff --git a/poetry.lock b/poetry.lock index 97f1d4015..0bf920865 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1456,6 +1456,7 @@ files = [ [package.dependencies] appdirs = ">=1.4.4,<2.0.0" click = ">=8.1.7,<9.0.0" +crewai-tools = {version = ">=0.4.7,<0.5.0", optional = true, markers = "extra == \"tools\""} embedchain = ">=0.1.114,<0.2.0" instructor = "1.3.3" jsonref = ">=1.1.0,<2.0.0" @@ -1472,6 +1473,33 @@ regex = ">=2023.12.25,<2024.0.0" agentops = ["agentops (>=0.1.9,<0.2.0)"] tools = ["crewai-tools (>=0.4.7,<0.5.0)"] +[[package]] +name = "crewai-tools" +version = "0.4.8" +description = "Set of tools for the crewAI framework" +optional = false +python-versions = "<=3.13,>=3.10" +files = [ + {file = "crewai_tools-0.4.8-py3-none-any.whl", hash = "sha256:628b08515ee0e06c751da1dd66b0cff70c9b2644775891c8f59883cb5debfef4"}, + {file = "crewai_tools-0.4.8.tar.gz", hash = "sha256:ae190bd187f980163523c86ee7e1eb2ed78896f935d6caff98908dd7ab6c982b"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.12.3,<5.0.0" +chromadb = ">=0.4.22,<0.5.0" +docker = ">=7.1.0,<8.0.0" +docx2txt = ">=0.8,<0.9" +embedchain = ">=0.1.114,<0.2.0" +lancedb = ">=0.5.4,<0.6.0" +langchain = ">0.2,<=0.3" +openai = ">=1.12.0,<2.0.0" +pydantic = ">=2.6.1,<3.0.0" +pyright = ">=1.1.350,<2.0.0" +pytest = ">=8.0.0,<9.0.0" +pytube = ">=15.0.0,<16.0.0" +requests = ">=2.31.0,<3.0.0" +selenium = ">=4.18.1,<5.0.0" + [[package]] name = "cryptography" version = "42.0.8" @@ -1822,6 +1850,16 @@ files = [ {file = "docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e"}, ] +[[package]] +name = "docx2txt" +version = "0.8" +description = "A pure python-based utility to extract text and images from docx files." +optional = false +python-versions = "*" +files = [ + {file = "docx2txt-0.8.tar.gz", hash = "sha256:2c06d98d7cfe2d3947e5760a57d924e3ff07745b379c8737723922e7009236e5"}, +] + [[package]] name = "dspy-ai" version = "2.4.12" @@ -4453,6 +4491,39 @@ websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" [package.extras] adal = ["adal (>=1.0.2)"] +[[package]] +name = "lancedb" +version = "0.5.7" +description = "lancedb" +optional = false +python-versions = ">=3.8" +files = [ + {file = "lancedb-0.5.7-py3-none-any.whl", hash = "sha256:6169966f715ef530be545950e1aaf9f3f160967e4ba7456cd67c9f30f678095d"}, + {file = "lancedb-0.5.7.tar.gz", hash = "sha256:878914b493f91d09a77b14f1528104741f273234cbdd6671be705f447701fd51"}, +] + +[package.dependencies] +attrs = ">=21.3.0" +cachetools = "*" +click = ">=8.1.7" +deprecation = "*" +overrides = ">=0.7" +pydantic = ">=1.10" +pylance = "0.9.18" +pyyaml = ">=6.0" +ratelimiter = ">=1.0,<2.0" +requests = ">=2.31.0" +retry = ">=0.9.2" +semver = ">=3.0" +tqdm = ">=4.27.0" + +[package.extras] +clip = ["open-clip", "pillow", "torch"] +dev = ["pre-commit", "ruff"] +docs = ["mkdocs", "mkdocs-jupyter", "mkdocs-material", "mkdocs-ultralytics-plugin (==0.0.44)", "mkdocstrings[python]"] +embeddings = ["InstructorEmbedding", "awscli (>=1.29.57)", "boto3 (>=1.28.57)", "botocore (>=1.31.57)", "cohere", "google.generativeai", "huggingface-hub", "open-clip-torch", "openai (>=1.6.1)", "pillow", "sentence-transformers", "torch"] +tests = ["aiohttp", "duckdb", "pandas (>=1.4)", "polars (>=0.19)", "pytest", "pytest-asyncio", "pytest-mock", "pytz"] + [[package]] name = "langchain" version = "0.2.8" @@ -6519,6 +6590,20 @@ files = [ {file = "orjson-3.10.0.tar.gz", hash = "sha256:ba4d8cac5f2e2cff36bea6b6481cdb92b38c202bcec603d6f5ff91960595a1ed"}, ] +[[package]] +name = "outcome" +version = "1.3.0.post0" +description = "Capture the outcome of Python function calls." +optional = false +python-versions = ">=3.7" +files = [ + {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, + {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + [[package]] name = "overrides" version = "7.7.0" @@ -7278,6 +7363,17 @@ files = [ [package.extras] tests = ["pytest"] +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + [[package]] name = "py-cpuinfo" version = "9.0.0" @@ -7615,6 +7711,30 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pylance" +version = "0.9.18" +description = "python wrapper for Lance columnar format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pylance-0.9.18-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:fe2445d922c594d90e89111385106f6b152caab27996217db7bb4b8947eb0bea"}, + {file = "pylance-0.9.18-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:a2c424c50f5186edbbcc5a26f34063ed09d9a7390e28033395728ce02b5658f0"}, + {file = "pylance-0.9.18-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10af06edfde3e8451bf2251381d3980a0a164eab9d4c3d4dc8b6318969e958a6"}, + {file = "pylance-0.9.18-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d8bb9045d7163cc966b9fe34a917044192be37a90915475b77461e5b7d89e442"}, + {file = "pylance-0.9.18-cp38-abi3-win_amd64.whl", hash = "sha256:5ea80b7bf70d992f3fe63bce2d2f064f742124c04eaedeb76baca408ded85a2c"}, +] + +[package.dependencies] +numpy = ">=1.22" +pyarrow = ">=12" + +[package.extras] +benchmarks = ["pytest-benchmark"] +dev = ["ruff (==0.2.2)"] +tests = ["datasets", "duckdb", "ml_dtypes", "pandas", "pillow", "polars[pandas,pyarrow]", "pytest", "tensorflow", "tqdm"] +torch = ["torch"] + [[package]] name = "pymilvus" version = "2.4.4" @@ -7789,6 +7909,24 @@ files = [ {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, ] +[[package]] +name = "pyright" +version = "1.1.371" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.371-py3-none-any.whl", hash = "sha256:cce52e42ff73943243e7e5e24f2a59dee81b97d99f4e3cf97370b27e8a1858cd"}, + {file = "pyright-1.1.371.tar.gz", hash = "sha256:777b508b92dda2db476214c400ce043aad8d8f3dd0e10d284c96e79f298308b5"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pysbd" version = "0.3.4" @@ -7799,6 +7937,18 @@ files = [ {file = "pysbd-0.3.4-py3-none-any.whl", hash = "sha256:cd838939b7b0b185fcf86b0baf6636667dfb6e474743beeff878e9f42e022953"}, ] +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + [[package]] name = "pytest" version = "8.2.2" @@ -8534,6 +8684,20 @@ files = [ [package.extras] full = ["numpy"] +[[package]] +name = "ratelimiter" +version = "1.2.0.post0" +description = "Simple python rate limiting object" +optional = false +python-versions = "*" +files = [ + {file = "ratelimiter-1.2.0.post0-py3-none-any.whl", hash = "sha256:a52be07bc0bb0b3674b4b304550f10c769bbb00fead3072e035904474259809f"}, + {file = "ratelimiter-1.2.0.post0.tar.gz", hash = "sha256:5c395dcabdbbde2e5178ef3f89b568a3066454a6ddc223b76473dac22f89b4f7"}, +] + +[package.extras] +test = ["pytest (>=3.0)", "pytest-asyncio"] + [[package]] name = "realtime" version = "1.0.6" @@ -8752,6 +8916,21 @@ files = [ [package.dependencies] httpx = ">=0.21.0" +[[package]] +name = "retry" +version = "0.9.2" +description = "Easy to use retry decorator." +optional = false +python-versions = "*" +files = [ + {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"}, + {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"}, +] + +[package.dependencies] +decorator = ">=3.4.2" +py = ">=1.4.26,<2.0.0" + [[package]] name = "rich" version = "13.7.1" @@ -9158,6 +9337,36 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "selenium" +version = "4.22.0" +description = "Official Python bindings for Selenium WebDriver" +optional = false +python-versions = ">=3.8" +files = [ + {file = "selenium-4.22.0-py3-none-any.whl", hash = "sha256:e424991196e9857e19bf04fe5c1c0a4aac076794ff5e74615b1124e729d93104"}, + {file = "selenium-4.22.0.tar.gz", hash = "sha256:903c8c9d61b3eea6fcc9809dc7d9377e04e2ac87709876542cc8f863e482c4ce"}, +] + +[package.dependencies] +certifi = ">=2021.10.8" +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +typing_extensions = ">=4.9.0" +urllib3 = {version = ">=1.26,<3", extras = ["socks"]} +websocket-client = ">=1.8.0" + +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, +] + [[package]] name = "sentence-transformers" version = "2.7.0" @@ -9345,6 +9554,17 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + [[package]] name = "soupsieve" version = "2.5" @@ -10028,6 +10248,42 @@ torchhub = ["filelock", "huggingface-hub (>=0.19.3,<1.0)", "importlib-metadata", video = ["av (==9.2.0)", "decord (==0.6.0)"] vision = ["Pillow (>=10.0.1,<=15.0)"] +[[package]] +name = "trio" +version = "0.26.0" +description = "A friendly Python library for async concurrency and I/O" +optional = false +python-versions = ">=3.8" +files = [ + {file = "trio-0.26.0-py3-none-any.whl", hash = "sha256:bb9c1b259591af941fccfbabbdc65bc7ed764bd2db76428454c894cd5e3d2032"}, + {file = "trio-0.26.0.tar.gz", hash = "sha256:67c5ec3265dd4abc7b1d1ab9ca4fe4c25b896f9c93dac73713778adab487f9c4"}, +] + +[package.dependencies] +attrs = ">=23.2.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = "*" +outcome = "*" +sniffio = ">=1.3.0" +sortedcontainers = "*" + +[[package]] +name = "trio-websocket" +version = "0.11.1" +description = "WebSocket library for Trio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, + {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +trio = ">=0.11" +wsproto = ">=0.14" + [[package]] name = "triton" version = "2.3.1" @@ -10574,6 +10830,9 @@ files = [ {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] +[package.dependencies] +pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} + [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] @@ -11057,6 +11316,20 @@ files = [ {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "xlsxwriter" version = "3.2.0" @@ -11412,4 +11685,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "52ad29fb36dd6a302435cf69fe58d557c9fec119a07ef575ca9b5be5e1e66ee6" +content-hash = "3561ffbe8cc249b7ca946135d550991879ce9767e607f4a474f80119d2ffd5b2" diff --git a/pyproject.toml b/pyproject.toml index 65fc6b38e..dfe0b9af8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,13 +94,11 @@ langchain-aws = "^0.1.6" langchain-mongodb = "^0.1.6" kubernetes = "^30.1.0" firecrawl-py = "^0.0.16" - json-repair = "^0.25.2" - langchain-nvidia-ai-endpoints = "^0.1.2" langchain-google-calendar-tools = "^0.0.1" langchain-milvus = "^0.1.1" - +crewai = {extras = ["tools"], version = "^0.36.0"} [tool.poetry.group.dev.dependencies] diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index 48fb5a06e..84f047d77 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -598,8 +598,9 @@ async def custom_component_update( ] params = update_params_with_load_from_db_fields(cc_instance, params, load_from_db_fields) cc_instance.set_attributes(params) - updated_build_config = cc_instance.update_build_config( - build_config=code_request.get_template(), + updated_build_config = code_request.get_template() + cc_instance.update_build_config( + build_config=updated_build_config, field_value=code_request.field_value, field_name=code_request.field, ) diff --git a/src/backend/base/langflow/base/agents/crewai/__init__.py b/src/backend/base/langflow/base/agents/crewai/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/base/langflow/base/agents/crewai/crew.py b/src/backend/base/langflow/base/agents/crewai/crew.py new file mode 100644 index 000000000..a4c70a51c --- /dev/null +++ b/src/backend/base/langflow/base/agents/crewai/crew.py @@ -0,0 +1,80 @@ +from typing import Callable, List, Tuple, Union, cast + +from crewai import Agent, Crew, Process, Task # type: ignore +from crewai.task import TaskOutput # type: ignore +from langchain_core.agents import AgentAction, AgentFinish + +from langflow.custom import Component +from langflow.inputs.inputs import HandleInput, InputTypes +from langflow.io import BoolInput, IntInput, Output +from langflow.schema.data import Data +from langflow.schema.message import Message + + +class BaseCrewComponent(Component): + description: str = ( + "Represents a group of agents, defining how they should collaborate and the tasks they should perform." + ) + icon = "CrewAI" + + _base_inputs: list[InputTypes] = [ + IntInput(name="verbose", display_name="Verbose", value=0, advanced=True), + BoolInput(name="memory", display_name="Memory", value=False, advanced=True), + BoolInput(name="use_cache", display_name="Cache", value=True, advanced=True), + IntInput(name="max_rpm", display_name="Max RPM", value=100, advanced=True), + BoolInput(name="share_crew", display_name="Share Crew", value=False, advanced=True), + HandleInput( + name="function_calling_llm", + display_name="Function Calling LLM", + input_types=["LanguageModel"], + required=False, + advanced=True, + ), + ] + + outputs = [ + Output(display_name="Output", name="output", method="build_output"), + ] + + def task_is_valid(self, task_data: Data, crew_type: Process) -> Task: + return "task_type" in task_data and task_data.task_type == crew_type + + def get_tasks_and_agents(self) -> tuple[list[Task], list[Agent]]: + return self.tasks, self.agents + + def build_crew(self) -> Crew: + raise NotImplementedError("build_crew must be implemented in subclasses") + + def get_task_callback( + self, + ) -> Callable: + def task_callback(task_output: TaskOutput): + if self.vertex: + vertex_id = self.vertex.id + else: + vertex_id = self.display_name or self.__class__.__name__ + self.log(task_output.model_dump(), name=f"Task (Agent: {task_output.agent}) - {vertex_id}") + + return task_callback + + def get_step_callback( + self, + ) -> Callable: + def step_callback(agent_output: Union[AgentFinish, List[Tuple[AgentAction, str]]]): + _id = self.vertex.id if self.vertex else self.display_name + if isinstance(agent_output, AgentFinish): + messages = agent_output.messages + self.log(cast(dict, messages[0].to_json()), name=f"Finish (Agent: {_id})") + elif isinstance(agent_output, list): + _messages_dict = {f"Action {i}": action.messages for i, (action, _) in enumerate(agent_output)} + messages_dict = {k: v[0] if len(v) == 1 else v for k, v in _messages_dict.items()} + self.log(messages_dict, name=f"Step (Agent: {_id})") + + return step_callback + + async def build_output(self) -> Message: + crew = self.build_crew() + result = await crew.kickoff_async() + message = Message(text=result, sender="Machine") + self.status = "\n\n".join([result] + [str(message) for message in self._logs]) + return message diff --git a/src/backend/base/langflow/base/agents/crewai/tasks.py b/src/backend/base/langflow/base/agents/crewai/tasks.py new file mode 100644 index 000000000..8e6f6cb39 --- /dev/null +++ b/src/backend/base/langflow/base/agents/crewai/tasks.py @@ -0,0 +1,9 @@ +from crewai import Task # type: ignore + + +class SequentialTask(Task): + pass + + +class HierarchicalTask(Task): + pass diff --git a/src/backend/base/langflow/components/agents/CrewAIAgent.py b/src/backend/base/langflow/components/agents/CrewAIAgent.py new file mode 100644 index 000000000..5510a5823 --- /dev/null +++ b/src/backend/base/langflow/components/agents/CrewAIAgent.py @@ -0,0 +1,85 @@ +from crewai import Agent # type: ignore + +from langflow.custom import Component +from langflow.io import BoolInput, DictInput, HandleInput, MessageTextInput, Output + + +class CrewAIAgentComponent(Component): + display_name = "CrewAI Agent" + description = "Represents an agent of CrewAI." + documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/" + icon = "CrewAI" + + inputs = [ + MessageTextInput(name="role", display_name="Role", info="The role of the agent."), + MessageTextInput(name="goal", display_name="Goal", info="The objective of the agent."), + MessageTextInput(name="backstory", display_name="Backstory", info="The backstory of the agent."), + HandleInput( + name="tools", + display_name="Tools", + input_types=["Tool"], + is_list=True, + info="Tools at agents disposal", + value=[], + ), + HandleInput( + name="llm", + display_name="Language Model", + info="Language model that will run the agent.", + input_types=["LanguageModel"], + ), + BoolInput( + name="memory", + display_name="Memory", + info="Whether the agent should have memory or not", + advanced=True, + value=True, + ), + BoolInput( + name="verbose", + display_name="Verbose", + advanced=True, + value=False, + ), + BoolInput( + name="allow_delegation", + display_name="Allow Delegation", + info="Whether the agent is allowed to delegate tasks to other agents.", + value=True, + ), + BoolInput( + name="allow_code_execution", + display_name="Allow Code Execution", + info="Whether the agent is allowed to execute code.", + value=False, + advanced=True, + ), + DictInput( + name="kwargs", + display_name="kwargs", + info="kwargs of agent.", + is_list=True, + advanced=True, + ), + ] + + outputs = [ + Output(display_name="Agent", name="output", method="build_output"), + ] + + def build_output(self) -> Agent: + kwargs = self.kwargs if self.kwargs else {} + agent = Agent( + role=self.role, + goal=self.goal, + backstory=self.backstory, + llm=self.llm, + verbose=self.verbose, + memory=self.memory, + tools=self.tools if self.tools else [], + allow_delegation=self.allow_delegation, + allow_code_execution=self.allow_code_execution, + **kwargs, + ) + self.status = repr(agent) + return agent diff --git a/src/backend/base/langflow/components/agents/HierarchicalCrew.py b/src/backend/base/langflow/components/agents/HierarchicalCrew.py new file mode 100644 index 000000000..b5160b2e5 --- /dev/null +++ b/src/backend/base/langflow/components/agents/HierarchicalCrew.py @@ -0,0 +1,37 @@ +from crewai import Crew, Process # type: ignore + +from langflow.base.agents.crewai.crew import BaseCrewComponent +from langflow.io import HandleInput + + +class HierarchicalCrewComponent(BaseCrewComponent): + display_name: str = "Hierarchical Crew" + description: str = ( + "Represents a group of agents, defining how they should collaborate and the tasks they should perform." + ) + + inputs = BaseCrewComponent._base_inputs + [ + HandleInput(name="agents", display_name="Agents", input_types=["Agent"], is_list=True), + HandleInput(name="tasks", display_name="Tasks", input_types=["HierarchicalTask"], is_list=True), + HandleInput(name="manager_llm", display_name="Manager LLM", input_types=["LanguageModel"], required=False), + HandleInput(name="manager_agent", display_name="Manager Agent", input_types=["Agent"], required=False), + ] + + def build_crew(self) -> Crew: + tasks, agents = self.get_tasks_and_agents() + crew = Crew( + agents=agents, + tasks=tasks, + process=Process.hierarchical, + verbose=self.verbose, + memory=self.memory, + cache=self.use_cache, + max_rpm=self.max_rpm, + share_crew=self.share_crew, + function_calling_llm=self.function_calling_llm, + manager_agent=self.manager_agent, + manager_llm=self.manager_llm, + step_callback=self.get_step_callback(), + task_callback=self.get_task_callback(), + ) + return crew diff --git a/src/backend/base/langflow/components/agents/SequentialCrew.py b/src/backend/base/langflow/components/agents/SequentialCrew.py new file mode 100644 index 000000000..600d66883 --- /dev/null +++ b/src/backend/base/langflow/components/agents/SequentialCrew.py @@ -0,0 +1,38 @@ +from crewai import Agent, Crew, Process, Task # type: ignore + +from langflow.base.agents.crewai.crew import BaseCrewComponent +from langflow.io import HandleInput +from langflow.schema.message import Message + + +class SequentialCrewComponent(BaseCrewComponent): + display_name: str = "Sequential Crew" + description: str = ( + "Represents a group of agents, defining how they should collaborate and the tasks they should perform." + ) + documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/" + icon = "CrewAI" + + inputs = BaseCrewComponent._base_inputs + [ + HandleInput(name="tasks", display_name="Tasks", input_types=["SequentialTask"], is_list=True), + ] + + def get_tasks_and_agents(self) -> tuple[list[Task], list[Agent]]: + return self.tasks, [task.agent for task in self.tasks] + + def build_crew(self) -> Message: + tasks, agents = self.get_tasks_and_agents() + crew = Crew( + agents=agents, + tasks=tasks, + process=Process.sequential, + verbose=self.verbose, + memory=self.memory, + cache=self.use_cache, + max_rpm=self.max_rpm, + share_crew=self.share_crew, + function_calling_llm=self.function_calling_llm, + step_callback=self.get_step_callback(), + task_callback=self.get_task_callback(), + ) + return crew diff --git a/src/backend/base/langflow/components/helpers/HierarchicalTask.py b/src/backend/base/langflow/components/helpers/HierarchicalTask.py new file mode 100644 index 000000000..993126d36 --- /dev/null +++ b/src/backend/base/langflow/components/helpers/HierarchicalTask.py @@ -0,0 +1,43 @@ +from langflow.base.agents.crewai.tasks import HierarchicalTask +from langflow.custom import Component +from langflow.io import HandleInput, MessageTextInput, Output + + +class HierarchicalTaskComponent(Component): + display_name: str = "Hierarchical Task" + description: str = "Each task must have a description, an expected output and an agent responsible for execution." + icon = "CrewAI" + inputs = [ + MessageTextInput( + name="task_description", + display_name="Description", + info="Descriptive text detailing task's purpose and execution.", + ), + MessageTextInput( + name="expected_output", + display_name="Expected Output", + info="Clear definition of expected task outcome.", + ), + HandleInput( + name="tools", + display_name="Tools", + input_types=["Tool"], + is_list=True, + info="List of tools/resources limited for task execution. Uses the Agent tools by default.", + required=False, + advanced=True, + ), + ] + + outputs = [ + Output(display_name="Task", name="task_output", method="build_task"), + ] + + def build_task(self) -> HierarchicalTask: + task = HierarchicalTask( + description=self.task_description, + expected_output=self.expected_output, + tools=self.tools or [], + ) + self.status = task + return task diff --git a/src/backend/base/langflow/components/helpers/SequentialTask.py b/src/backend/base/langflow/components/helpers/SequentialTask.py new file mode 100644 index 000000000..5cf3f54e9 --- /dev/null +++ b/src/backend/base/langflow/components/helpers/SequentialTask.py @@ -0,0 +1,72 @@ +from langflow.base.agents.crewai.tasks import SequentialTask +from langflow.custom import Component +from langflow.io import BoolInput, HandleInput, MessageTextInput, Output + + +class SequentialTaskComponent(Component): + display_name: str = "Sequential Task" + description: str = "Each task must have a description, an expected output and an agent responsible for execution." + icon = "CrewAI" + inputs = [ + MessageTextInput( + name="task_description", + display_name="Description", + info="Descriptive text detailing task's purpose and execution.", + ), + MessageTextInput( + name="expected_output", + display_name="Expected Output", + info="Clear definition of expected task outcome.", + ), + HandleInput( + name="tools", + display_name="Tools", + input_types=["Tool"], + is_list=True, + info="List of tools/resources limited for task execution. Uses the Agent tools by default.", + required=False, + advanced=True, + ), + HandleInput( + name="agent", + display_name="Agent", + input_types=["Agent"], + info="CrewAI Agent that will perform the task", + required=True, + ), + HandleInput( + name="task", + display_name="Task", + input_types=["SequentialTask"], + info="CrewAI Task that will perform the task", + ), + BoolInput( + name="async_execution", + display_name="Async Execution", + value=True, + advanced=True, + info="Boolean flag indicating asynchronous task execution.", + ), + ] + + outputs = [ + Output(display_name="Task", name="task_output", method="build_task"), + ] + + def build_task(self) -> list[SequentialTask]: + tasks = [] + task = SequentialTask( + description=self.task_description, + expected_output=self.expected_output, + tools=self.agent.tools, + async_execution=False, + agent=self.agent, + ) + tasks.append(task) + self.status = task + if self.task: + if isinstance(self.task, list): + tasks.extend(self.task) + else: + tasks.append(self.task) + return tasks diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index aeddc6093..254c9fe7e 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -187,7 +187,7 @@ class Component(CustomComponent): if raw is None and isinstance(result, (dict, Data, str)): raw = result.data if isinstance(result, Data) else result artifact_type = get_artifact_type(artifact_value, result) - raw = post_process_raw(raw, artifact_type) + raw, artifact_type = post_process_raw(raw, artifact_type) artifact = {"repr": custom_repr, "raw": raw, "type": artifact_type} _artifacts[output.name] = artifact self._artifacts = _artifacts 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 a3b4c57c7..2527cbea0 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -192,11 +192,11 @@ class CustomComponent(BaseComponent): if self.repr_value == "": self.repr_value = self.status if isinstance(self.repr_value, dict): - self.repr_value = yaml.dump(self.repr_value) + return yaml.dump(self.repr_value) + if isinstance(self.repr_value, str): + return self.repr_value if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Data): - self.repr_value = str(self.repr_value) - elif hasattr(self.repr_value, "to_json") and not isinstance(self.repr_value, Data): - self.repr_value = self.repr_value.to_json() + return str(self.repr_value) return self.repr_value def build_config(self): diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 411c98190..09451bf3d 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -1,9 +1,10 @@ from enum import Enum from typing import Annotated, Any, Optional +from pydantic import BaseModel, ConfigDict, Field, PlainSerializer, field_validator, model_serializer + from langflow.field_typing.range_spec import RangeSpec from langflow.inputs.validators import CoalesceBool -from pydantic import BaseModel, ConfigDict, Field, PlainSerializer, field_validator, model_serializer class FieldTypes(str, Enum): diff --git a/src/backend/base/langflow/schema/artifact.py b/src/backend/base/langflow/schema/artifact.py index dce70f790..a238838ff 100644 --- a/src/backend/base/langflow/schema/artifact.py +++ b/src/backend/base/langflow/schema/artifact.py @@ -1,6 +1,9 @@ from enum import Enum from typing import Generator +from fastapi.encoders import jsonable_encoder +from pydantic import BaseModel + from langflow.schema import Data from langflow.schema.message import Message @@ -50,6 +53,12 @@ def post_process_raw(raw, artifact_type: str): if artifact_type == ArtifactType.STREAM.value: raw = "" elif artifact_type == ArtifactType.UNKNOWN.value and raw is not None: - raw = "Built Successfully ✨" - - return raw + if isinstance(raw, (BaseModel, dict)): + try: + raw = jsonable_encoder(raw) + artifact_type = ArtifactType.OBJECT.value + except Exception: + raw = "Built Successfully ✨" + else: + raw = "Built Successfully ✨" + return raw, artifact_type diff --git a/src/backend/base/langflow/services/storage/local.py b/src/backend/base/langflow/services/storage/local.py index 9ad9feafb..b4eb81e22 100644 --- a/src/backend/base/langflow/services/storage/local.py +++ b/src/backend/base/langflow/services/storage/local.py @@ -56,7 +56,7 @@ class LocalStorageService(StorageService): raise FileNotFoundError(f"File {file_name} not found in flow {flow_id}") with open(file_path, "rb") as f: - logger.info(f"File {file_name} retrieved successfully from flow {flow_id}.") + logger.debug(f"File {file_name} retrieved successfully from flow {flow_id}.") return f.read() async def list_files(self, flow_id: str): diff --git a/src/frontend/src/icons/CrewAI/CrewAiIcon.jsx b/src/frontend/src/icons/CrewAI/CrewAiIcon.jsx new file mode 100644 index 000000000..d2a02f8f3 --- /dev/null +++ b/src/frontend/src/icons/CrewAI/CrewAiIcon.jsx @@ -0,0 +1,58 @@ +const SvgCrewAiIcon = (props) => ( + + + + + + + + + + + +); +export default SvgCrewAiIcon; diff --git a/src/frontend/src/icons/CrewAI/crewai.svg b/src/frontend/src/icons/CrewAI/crewai.svg new file mode 100644 index 000000000..3b3192c3c --- /dev/null +++ b/src/frontend/src/icons/CrewAI/crewai.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/frontend/src/icons/CrewAI/index.tsx b/src/frontend/src/icons/CrewAI/index.tsx new file mode 100644 index 000000000..b3afaf3fc --- /dev/null +++ b/src/frontend/src/icons/CrewAI/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import SvgCrewAiIcon from "./CrewAiIcon"; + +export const CrewAiIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 198f9eeb0..5990cc4f1 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -168,6 +168,7 @@ import { CassandraIcon } from "../icons/Cassandra"; import { ChromaIcon } from "../icons/ChromaIcon"; import { CohereIcon } from "../icons/Cohere"; import { CouchbaseIcon } from "../icons/Couchbase"; +import { CrewAiIcon } from "../icons/CrewAI"; import { ElasticsearchIcon } from "../icons/ElasticsearchStore"; import { EvernoteIcon } from "../icons/Evernote"; import { FBIcon } from "../icons/FacebookMessenger"; @@ -381,6 +382,7 @@ export const nodeIconsLucide: iconsType = { HuggingFace: HuggingFaceIcon, HuggingFaceEmbeddings: HuggingFaceIcon, IFixitLoader: IFixIcon, + CrewAI: CrewAiIcon, Meta: MetaIcon, Midjorney: MidjourneyIcon, MongoDBAtlasVectorSearch: MongoDBIcon,