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,