Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ on:
default: true



jobs:
ci:
if: ${{ github.event.inputs.release_package_base == 'true' || github.event.inputs.release_package_main == 'true' }}
Expand All @@ -50,6 +51,8 @@ jobs:
python-versions: "['3.10', '3.11', '3.12', '3.13']"
frontend-tests-folder: "tests"
release: true
secrets: inherit


release-base:
name: Release Langflow Base
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "langflow"
version = "1.4.3"
version = "1.5.0"
description = "A Python package with a built-in web application"
requires-python = ">=3.10,<3.14"
license = "MIT"
Expand All @@ -17,7 +17,7 @@ maintainers = [
]
# Define your main dependencies here
dependencies = [
"langflow-base==0.4.3",
"langflow-base==0.5.0",
"beautifulsoup4==4.12.3",
"google-search-results>=2.4.1,<3.0.0",
"google-api-python-client==2.154.0",
Expand Down Expand Up @@ -104,11 +104,10 @@ dependencies = [
"sseclient-py==1.8.0",
"arize-phoenix-otel>=0.6.1",
"openinference-instrumentation-langchain>=0.1.29",
"crewai==0.102.0",
# "crewai>=0.126.0",
"mcp>=1.10.1",
"uv>=0.5.7",
"scipy>=1.14.1",
"ag2>=0.1.0",
"scrapegraph-py>=1.12.0",
"pydantic-ai>=0.0.19",
"smolagents>=1.8.0",
Expand Down
42 changes: 31 additions & 11 deletions src/backend/base/langflow/base/agents/crewai/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
from typing import Any, cast

import litellm
from crewai import LLM, Agent, Crew, Process, Task
from crewai.task import TaskOutput
from crewai.tools.base_tool import Tool
from langchain_core.agents import AgentAction, AgentFinish
from pydantic import SecretStr

from langflow.custom.custom_component.component import Component
Expand Down Expand Up @@ -45,7 +41,7 @@ def _find_api_key(model):
return None


def convert_llm(llm: Any, excluded_keys=None) -> LLM:
def convert_llm(llm: Any, excluded_keys=None):
"""Converts a LangChain LLM object to a CrewAI-compatible LLM object.

Args:
Expand All @@ -55,6 +51,12 @@ def convert_llm(llm: Any, excluded_keys=None) -> LLM:
Returns:
A CrewAI-compatible LLM object
"""
try:
from crewai import LLM
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e

if not llm:
return None

Expand Down Expand Up @@ -109,6 +111,12 @@ def convert_tools(tools):
Returns:
A CrewAI-compatible tools list.
"""
try:
from crewai.tools.base_tool import Tool
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e

if not tools:
return []

Expand Down Expand Up @@ -142,12 +150,12 @@ class BaseCrewComponent(Component):
]

# Model properties to exclude when creating a CrewAI LLM object
manager_llm: LLM | None
manager_llm = None

def task_is_valid(self, task_data: Data, crew_type: Process) -> Task:
def task_is_valid(self, task_data: Data, crew_type) -> bool:
return "task_type" in task_data and task_data.task_type == crew_type

def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]:
def get_tasks_and_agents(self, agents_list=None) -> tuple[list, list]:
# Allow passing a custom list of agents
if not agents_list:
agents_list = self.agents or []
Expand All @@ -160,21 +168,27 @@ def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent

return self.tasks, agents_list

def get_manager_llm(self) -> LLM | None:
def get_manager_llm(self):
if not self.manager_llm:
return None

self.manager_llm = convert_llm(self.manager_llm)

return self.manager_llm

def build_crew(self) -> Crew:
def build_crew(self):
msg = "build_crew must be implemented in subclasses"
raise NotImplementedError(msg)

def get_task_callback(
self,
) -> Callable:
try:
from crewai.task import TaskOutput
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e

def task_callback(task_output: TaskOutput) -> None:
vertex_id = self._vertex.id if self._vertex else self.display_name or self.__class__.__name__
self.log(task_output.model_dump(), name=f"Task (Agent: {task_output.agent}) - {vertex_id}")
Expand All @@ -184,7 +198,13 @@ def task_callback(task_output: TaskOutput) -> None:
def get_step_callback(
self,
) -> Callable:
def step_callback(agent_output: AgentFinish | list[tuple[AgentAction, str]]) -> None:
try:
from langchain_core.agents import AgentFinish
except ImportError as e:
msg = "langchain_core is not installed. Please install it with `uv pip install langchain-core`."
raise ImportError(msg) from e

def step_callback(agent_output) -> None:
id_ = self._vertex.id if self._vertex else self.display_name
if isinstance(agent_output, AgentFinish):
messages = agent_output.messages
Expand Down
5 changes: 4 additions & 1 deletion src/backend/base/langflow/base/agents/crewai/tasks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from crewai import Task
try:
from crewai import Task
except ImportError:
Task = object


class SequentialTask(Task):
Expand Down
6 changes: 5 additions & 1 deletion src/backend/base/langflow/base/data/docling_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ def extract_docling_documents(data_inputs: Data | list[Data] | DataFrame, doc_ke

if isinstance(data_inputs, Data):
if doc_key not in data_inputs.data:
msg = f"{doc_key} field not available in the input Data"
msg = (
f"'{doc_key}' field not available in the input Data. "
"Check that your input is a DoclingDocument. "
"You can use the Docling component to convert your input to a DoclingDocument."
)
raise TypeError(msg)
documents = [data_inputs.data[doc_key]]
else:
Expand Down
6 changes: 2 additions & 4 deletions src/backend/base/langflow/base/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,9 @@ def _validate_outputs(self) -> None:
raise ValueError(msg)

async def text_response(self) -> Message:
input_value = self.input_value
stream = self.stream
system_message = self.system_message
output = self.build_model()
result = await self.get_chat_result(
runnable=output, stream=stream, input_value=input_value, system_message=system_message
runnable=output, stream=self.stream, input_value=self.input_value, system_message=self.system_message
)
self.status = result
return result
Expand Down Expand Up @@ -176,6 +173,7 @@ async def get_chat_result(
input_value: str | Message,
system_message: str | None = None,
) -> Message:
# NVIDIA reasoning models use detailed thinking
if getattr(self, "detailed_thinking", False):
system_message = DETAILED_THINKING_PREFIX + (system_message or "")

Expand Down
25 changes: 17 additions & 8 deletions src/backend/base/langflow/base/models/openai_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
create_model_metadata(provider="OpenAI", name="gpt-3.5-turbo", icon="OpenAI", tool_calling=True),
# Reasoning Models
create_model_metadata(provider="OpenAI", name="o1", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o1-mini", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o1-pro", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o3-mini", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o3", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o3-pro", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o4-mini", icon="OpenAI", reasoning=True),
create_model_metadata(provider="OpenAI", name="o4-mini-high", icon="OpenAI", reasoning=True),
# Search Models
create_model_metadata(
provider="OpenAI",
Expand All @@ -27,7 +34,12 @@
preview=True,
),
create_model_metadata(
provider="OpenAI", name="gpt-4o-search-preview", icon="OpenAI", tool_calling=True, search=True, preview=True
provider="OpenAI",
name="gpt-4o-search-preview",
icon="OpenAI",
tool_calling=True,
search=True,
preview=True,
),
# Not Supported Models
create_model_metadata(
Expand All @@ -45,16 +57,13 @@
create_model_metadata(
provider="OpenAI", name="gpt-4o-mini-realtime-preview", icon="OpenAI", not_supported=True, preview=True
),
create_model_metadata(provider="OpenAI", name="o3-mini", icon="OpenAI", reasoning=True, not_supported=True),
create_model_metadata(provider="OpenAI", name="o1-mini", icon="OpenAI", reasoning=True, not_supported=True),
]

OPENAI_MODEL_NAMES = [
OPENAI_CHAT_MODEL_NAMES = [
metadata["name"]
for metadata in OPENAI_MODELS_DETAILED
if not metadata.get("reasoning", False)
if not metadata.get("not_supported", False)
and not metadata.get("reasoning", False)
and not metadata.get("search", False)
and not metadata.get("not_supported", False)
]

OPENAI_REASONING_MODEL_NAMES = [
Expand All @@ -78,4 +87,4 @@
]

# Backwards compatibility
MODEL_NAMES = OPENAI_MODEL_NAMES
MODEL_NAMES = OPENAI_CHAT_MODEL_NAMES
11 changes: 8 additions & 3 deletions src/backend/base/langflow/components/crewai/crewai.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from crewai import Agent

from langflow.base.agents.crewai.crew import convert_llm, convert_tools
from langflow.custom.custom_component.component import Component
from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
Expand All @@ -22,6 +20,7 @@ class CrewAIAgentComponent(Component):
description = "Represents an agent of CrewAI."
documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/"
icon = "CrewAI"
legacy = True

inputs = [
MultilineInput(name="role", display_name="Role", info="The role of the agent."),
Expand Down Expand Up @@ -80,7 +79,13 @@ class CrewAIAgentComponent(Component):
Output(display_name="Agent", name="output", method="build_output"),
]

def build_output(self) -> Agent:
def build_output(self):
try:
from crewai import Agent
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e

kwargs = self.kwargs or {}

# Define the Agent
Expand Down
11 changes: 8 additions & 3 deletions src/backend/base/langflow/components/crewai/hierarchical_crew.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from crewai import Crew, Process

from langflow.base.agents.crewai.crew import BaseCrewComponent
from langflow.io import HandleInput

Expand All @@ -11,6 +9,7 @@ class HierarchicalCrewComponent(BaseCrewComponent):
)
documentation: str = "https://docs.crewai.com/how-to/Hierarchical/"
icon = "CrewAI"
legacy = True

inputs = [
*BaseCrewComponent._base_inputs,
Expand All @@ -20,7 +19,13 @@ class HierarchicalCrewComponent(BaseCrewComponent):
HandleInput(name="manager_agent", display_name="Manager Agent", input_types=["Agent"], required=False),
]

def build_crew(self) -> Crew:
def build_crew(self):
try:
from crewai import Crew, Process
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e

tasks, agents = self.get_tasks_and_agents()
manager_llm = self.get_manager_llm()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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"
legacy = True
inputs = [
MultilineInput(
name="task_description",
Expand Down
13 changes: 9 additions & 4 deletions src/backend/base/langflow/components/crewai/sequential_crew.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from crewai import Agent, Crew, Process, Task

from langflow.base.agents.crewai.crew import BaseCrewComponent
from langflow.io import HandleInput
from langflow.schema.message import Message
Expand All @@ -10,18 +8,19 @@ class SequentialCrewComponent(BaseCrewComponent):
description: str = "Represents a group of agents with tasks that are executed sequentially."
documentation: str = "https://docs.crewai.com/how-to/Sequential/"
icon = "CrewAI"
legacy = True

inputs = [
*BaseCrewComponent._base_inputs,
HandleInput(name="tasks", display_name="Tasks", input_types=["SequentialTask"], is_list=True),
]

@property
def agents(self: "SequentialCrewComponent") -> list[Agent]:
def agents(self: "SequentialCrewComponent") -> list:
# Derive agents directly from linked tasks
return [task.agent for task in self.tasks if hasattr(task, "agent")]

def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]:
def get_tasks_and_agents(self, agents_list=None) -> tuple[list, list]:
# Use the agents property to derive agents
if not agents_list:
existing_agents = self.agents
Expand All @@ -30,6 +29,12 @@ def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent
return super().get_tasks_and_agents(agents_list=agents_list)

def build_crew(self) -> Message:
try:
from crewai import Crew, Process
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e

tasks, agents = self.get_tasks_and_agents()

return Crew(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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"
legacy = True
inputs = [
MultilineInput(
name="task_description",
Expand Down Expand Up @@ -65,7 +66,7 @@ def build_task(self) -> list[SequentialTask]:
tasks.append(task)
self.status = task
if self.task:
if isinstance(self.task, list) and all(isinstance(task, SequentialTask) for task in self.task):
if isinstance(self.task, list) and all(isinstance(task_item, SequentialTask) for task_item in self.task):
tasks = self.task + tasks
elif isinstance(self.task, SequentialTask):
tasks = [self.task, *tasks]
Expand Down
Loading
Loading