feat: implement crewAI lite version with optional dependencies

- Restructure pyproject.toml to move non-essential dependencies to optional extras
- Add graceful handling for missing optional dependencies in core modules
- Create memory, knowledge, telemetry, visualization, auth, and llm-integrations extras
- Implement helpful ImportError messages directing users to install specific extras
- Add comprehensive test suite for lite installation scenarios
- Maintain backward compatibility with existing installations
- Support minimal core installation with Agent/Crew/Task functionality

Addresses GitHub issue #3026 for lightweight crewAI installation

Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
Devin AI
2025-06-18 10:13:42 +00:00
parent db1e9e9b9a
commit bccb27ab2e
12 changed files with 692 additions and 52 deletions

View File

@@ -5,16 +5,27 @@ from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional
from auth0.authentication.token_verifier import (
AsymmetricSignatureVerifier,
TokenVerifier,
)
try:
from auth0.authentication.token_verifier import (
AsymmetricSignatureVerifier,
TokenVerifier,
)
AUTH0_AVAILABLE = True
except ImportError:
AUTH0_AVAILABLE = False
AsymmetricSignatureVerifier = None
TokenVerifier = None
from cryptography.fernet import Fernet
from .constants import AUTH0_CLIENT_ID, AUTH0_DOMAIN
def validate_token(id_token: str) -> None:
if not AUTH0_AVAILABLE:
raise ImportError(
"Auth0 is required for authentication functionality. "
"Please install it with: pip install 'crewai[auth]'"
)
"""
Verify the token and its precedence

View File

@@ -3,7 +3,12 @@
import os
from pathlib import Path
from pyvis.network import Network
try:
from pyvis.network import Network
PYVIS_AVAILABLE = True
except ImportError:
PYVIS_AVAILABLE = False
Network = None
from crewai.flow.config import COLORS, NODE_STYLES
from crewai.flow.html_template_handler import HTMLTemplateHandler
@@ -63,6 +68,12 @@ class FlowPlot:
RuntimeError
If network visualization generation fails.
"""
if not PYVIS_AVAILABLE:
raise ImportError(
"Pyvis is required for flow visualization. "
"Please install it with: pip install 'crewai[visualization]'"
)
if not filename or not isinstance(filename, str):
raise ValueError("Filename must be a non-empty string")
@@ -222,5 +233,11 @@ def plot_flow(flow, filename="flow_plot"):
IOError
If file operations fail.
"""
if not PYVIS_AVAILABLE:
raise ImportError(
"Pyvis is required for flow visualization. "
"Please install it with: pip install 'crewai[visualization]'"
)
visualizer = FlowPlot(flow)
visualizer.plot(filename)

View File

@@ -1,6 +1,13 @@
from pathlib import Path
from typing import Dict, List
try:
import pdfplumber
PDFPLUMBER_AVAILABLE = True
except ImportError:
PDFPLUMBER_AVAILABLE = False
pdfplumber = None
from crewai.knowledge.source.base_file_knowledge_source import BaseFileKnowledgeSource
@@ -26,14 +33,12 @@ class PDFKnowledgeSource(BaseFileKnowledgeSource):
def _import_pdfplumber(self):
"""Dynamically import pdfplumber."""
try:
import pdfplumber
return pdfplumber
except ImportError:
if not PDFPLUMBER_AVAILABLE:
raise ImportError(
"pdfplumber is not installed. Please install it with: pip install pdfplumber"
"pdfplumber is required for PDF knowledge sources. "
"Please install it with: pip install 'crewai[knowledge]'"
)
return pdfplumber
def add(self) -> None:
"""

View File

@@ -6,11 +6,19 @@ import os
import shutil
from typing import Any, Dict, List, Optional, Union
import chromadb
import chromadb.errors
from chromadb.api import ClientAPI
from chromadb.api.types import OneOrMany
from chromadb.config import Settings
try:
import chromadb
import chromadb.errors
from chromadb.api import ClientAPI
from chromadb.api.types import OneOrMany
from chromadb.config import Settings
CHROMADB_AVAILABLE = True
except ImportError:
CHROMADB_AVAILABLE = False
chromadb = None
ClientAPI = None
OneOrMany = None
Settings = None
from crewai.knowledge.storage.base_knowledge_storage import BaseKnowledgeStorage
from crewai.utilities import EmbeddingConfigurator
@@ -43,7 +51,7 @@ class KnowledgeStorage(BaseKnowledgeStorage):
search efficiency.
"""
collection: Optional[chromadb.Collection] = None
collection: Optional[Any] = None
collection_name: Optional[str] = "knowledge"
app: Optional[ClientAPI] = None
@@ -52,6 +60,12 @@ class KnowledgeStorage(BaseKnowledgeStorage):
embedder: Optional[Dict[str, Any]] = None,
collection_name: Optional[str] = None,
):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for knowledge storage functionality. "
"Please install it with: pip install 'crewai[knowledge]'"
)
self.collection_name = collection_name
self._set_embedder_config(embedder)
@@ -181,6 +195,12 @@ class KnowledgeStorage(BaseKnowledgeStorage):
raise
def _create_default_embedding_function(self):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for embedding functionality. "
"Please install it with: pip install 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.openai_embedding_function import (
OpenAIEmbeddingFunction,
)

View File

@@ -1,12 +1,22 @@
from typing import Any, Dict, List, Optional, Union
import aisuite as ai
try:
import aisuite as ai
AISUITE_AVAILABLE = True
except ImportError:
AISUITE_AVAILABLE = False
ai = None
from crewai.llms.base_llm import BaseLLM
class AISuiteLLM(BaseLLM):
def __init__(self, model: str, temperature: Optional[float] = None, **kwargs):
if not AISUITE_AVAILABLE:
raise ImportError(
"AISuite is required for AISuiteLLM. "
"Please install it with: pip install 'crewai[llm-integrations]'"
)
super().__init__(model, temperature, **kwargs)
self.client = ai.Client()

View File

@@ -6,7 +6,12 @@ import shutil
import uuid
from typing import Any, Dict, List, Optional
from chromadb.api import ClientAPI
try:
from chromadb.api import ClientAPI
CHROMADB_AVAILABLE = True
except ImportError:
CHROMADB_AVAILABLE = False
ClientAPI = None
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
from crewai.utilities import EmbeddingConfigurator
@@ -37,11 +42,17 @@ class RAGStorage(BaseRAGStorage):
search efficiency.
"""
app: ClientAPI | None = None
app: Optional[Any] = None
def __init__(
self, type, allow_reset=True, embedder_config=None, crew=None, path=None
):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for RAG storage functionality. "
"Please install it with: pip install 'crewai[memory]'"
)
super().__init__(type, allow_reset, embedder_config, crew)
agents = crew.agents if crew else []
agents = [self._sanitize_role(agent.role) for agent in agents]
@@ -60,6 +71,12 @@ class RAGStorage(BaseRAGStorage):
self.embedder_config = configurator.configure_embedder(self.embedder_config)
def _initialize_app(self):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for RAG storage functionality. "
"Please install it with: pip install 'crewai[memory]'"
)
import chromadb
from chromadb.config import Settings
@@ -165,6 +182,12 @@ class RAGStorage(BaseRAGStorage):
)
def _create_default_embedding_function(self):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for embedding functionality. "
"Please install it with: pip install 'crewai[memory]'"
)
from chromadb.utils.embedding_functions.openai_embedding_function import (
OpenAIEmbeddingFunction,
)

View File

@@ -11,17 +11,31 @@ from importlib.metadata import version
from typing import TYPE_CHECKING, Any, Callable, Optional
import threading
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
SpanExportResult,
)
from opentelemetry.trace import Span, Status, StatusCode
try:
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
SpanExportResult,
)
from opentelemetry.trace import Span, Status, StatusCode
OPENTELEMETRY_AVAILABLE = True
except ImportError:
OPENTELEMETRY_AVAILABLE = False
trace = None
OTLPSpanExporter = None
SERVICE_NAME = None
Resource = None
TracerProvider = None
BatchSpanProcessor = None
SpanExportResult = None
Span = None
Status = None
StatusCode = None
from crewai.telemetry.constants import (
CREWAI_TELEMETRY_BASE_URL,
@@ -43,13 +57,21 @@ if TYPE_CHECKING:
from crewai.task import Task
class SafeOTLPSpanExporter(OTLPSpanExporter):
def export(self, spans) -> SpanExportResult:
class SafeOTLPSpanExporter:
def __init__(self, *args, **kwargs):
if OPENTELEMETRY_AVAILABLE:
self._exporter = OTLPSpanExporter(*args, **kwargs)
else:
self._exporter = None
def export(self, spans):
if not OPENTELEMETRY_AVAILABLE or not self._exporter:
return None
try:
return super().export(spans)
return self._exporter.export(spans)
except Exception as e:
logger.error(e)
return SpanExportResult.FAILURE
return SpanExportResult.FAILURE if SpanExportResult else None
class Telemetry:
@@ -84,7 +106,7 @@ class Telemetry:
self.trace_set: bool = False
self._initialized: bool = True
if self._is_telemetry_disabled():
if self._is_telemetry_disabled() or not OPENTELEMETRY_AVAILABLE:
return
try:
@@ -116,11 +138,12 @@ class Telemetry:
return (
os.getenv("OTEL_SDK_DISABLED", "false").lower() == "true"
or os.getenv("CREWAI_DISABLE_TELEMETRY", "false").lower() == "true"
or not OPENTELEMETRY_AVAILABLE
)
def _should_execute_telemetry(self) -> bool:
"""Check if telemetry operations should be executed."""
return self.ready and not self._is_telemetry_disabled()
return self.ready and not self._is_telemetry_disabled() and OPENTELEMETRY_AVAILABLE
def set_tracer(self):
if self.ready and not self.trace_set:

View File

@@ -1,8 +1,15 @@
import os
from typing import Any, Dict, Optional, cast
from chromadb import Documents, EmbeddingFunction, Embeddings
from chromadb.api.types import validate_embedding_function
try:
from chromadb import Documents, EmbeddingFunction, Embeddings
from chromadb.api.types import validate_embedding_function
CHROMADB_AVAILABLE = True
except ImportError:
CHROMADB_AVAILABLE = False
Documents = None
EmbeddingFunction = None
Embeddings = None
class EmbeddingConfigurator:
@@ -25,6 +32,11 @@ class EmbeddingConfigurator:
self,
embedder_config: Optional[Dict[str, Any]] = None,
) -> EmbeddingFunction:
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
"""Configures and returns an embedding function based on the provided config."""
if embedder_config is None:
return self._create_default_embedding_function()
@@ -47,6 +59,12 @@ class EmbeddingConfigurator:
@staticmethod
def _create_default_embedding_function():
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for embedding functionality. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.openai_embedding_function import (
OpenAIEmbeddingFunction,
)
@@ -57,6 +75,12 @@ class EmbeddingConfigurator:
@staticmethod
def _configure_openai(config, model_name):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for OpenAI embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.openai_embedding_function import (
OpenAIEmbeddingFunction,
)
@@ -75,6 +99,12 @@ class EmbeddingConfigurator:
@staticmethod
def _configure_azure(config, model_name):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for Azure embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.openai_embedding_function import (
OpenAIEmbeddingFunction,
)
@@ -93,6 +123,12 @@ class EmbeddingConfigurator:
@staticmethod
def _configure_ollama(config, model_name):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for Ollama embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.ollama_embedding_function import (
OllamaEmbeddingFunction,
)
@@ -104,6 +140,12 @@ class EmbeddingConfigurator:
@staticmethod
def _configure_vertexai(config, model_name):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for VertexAI embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.google_embedding_function import (
GoogleVertexEmbeddingFunction,
)
@@ -117,6 +159,12 @@ class EmbeddingConfigurator:
@staticmethod
def _configure_google(config, model_name):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for Google embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.google_embedding_function import (
GoogleGenerativeAiEmbeddingFunction,
)
@@ -129,6 +177,12 @@ class EmbeddingConfigurator:
@staticmethod
def _configure_cohere(config, model_name):
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for Cohere embedding configuration. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
from chromadb.utils.embedding_functions.cohere_embedding_function import (
CohereEmbeddingFunction,
)
@@ -234,3 +288,11 @@ class EmbeddingConfigurator:
raise ValueError(
"Custom embedder must be an instance of `EmbeddingFunction` or a callable that creates one"
)
def validate_embedder_config(self, config: EmbeddingFunction) -> EmbeddingFunction:
if not CHROMADB_AVAILABLE:
raise ImportError(
"ChromaDB is required for embedding validation. "
"Please install it with: pip install 'crewai[memory]' or 'crewai[knowledge]'"
)
return cast(EmbeddingFunction, validate_embedding_function(config))