mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-11 09:08:31 +00:00
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:
193
tests/test_lite_installation.py
Normal file
193
tests/test_lite_installation.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""Test that crewAI lite installation works with minimal dependencies."""
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
|
||||
def test_core_imports_work_without_optional_deps():
|
||||
"""Test that core crewAI functionality can be imported without optional dependencies."""
|
||||
|
||||
try:
|
||||
from crewai import Agent, Crew, Task, LLM
|
||||
from crewai.lite_agent import LiteAgent
|
||||
from crewai.process import Process
|
||||
assert Agent is not None
|
||||
assert Crew is not None
|
||||
assert Task is not None
|
||||
assert LLM is not None
|
||||
assert LiteAgent is not None
|
||||
assert Process is not None
|
||||
except ImportError as e:
|
||||
pytest.fail(f"Core imports should work without optional dependencies: {e}")
|
||||
|
||||
|
||||
def test_optional_memory_import_error():
|
||||
"""Test that memory functionality raises helpful error without chromadb."""
|
||||
with patch.dict('sys.modules', {'chromadb': None}):
|
||||
with patch('crewai.memory.storage.rag_storage.CHROMADB_AVAILABLE', False):
|
||||
from crewai.memory.storage.rag_storage import RAGStorage
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
RAGStorage("test")
|
||||
|
||||
assert "ChromaDB is required" in str(exc_info.value)
|
||||
assert "crewai[memory]" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_optional_knowledge_import_error():
|
||||
"""Test that knowledge functionality raises helpful error without dependencies."""
|
||||
with patch.dict('sys.modules', {'chromadb': None}):
|
||||
with patch('crewai.knowledge.storage.knowledge_storage.CHROMADB_AVAILABLE', False):
|
||||
from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
KnowledgeStorage()
|
||||
|
||||
assert "ChromaDB is required" in str(exc_info.value)
|
||||
assert "crewai[knowledge]" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_optional_pdf_import_error():
|
||||
"""Test that PDF knowledge source raises helpful error without pdfplumber."""
|
||||
with patch.dict('sys.modules', {'pdfplumber': None}):
|
||||
with patch('crewai.knowledge.source.pdf_knowledge_source.PDFPLUMBER_AVAILABLE', False):
|
||||
from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource
|
||||
|
||||
knowledge_dir = Path("knowledge/tmp")
|
||||
knowledge_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
test_file = knowledge_dir / "test.pdf"
|
||||
test_file.touch()
|
||||
|
||||
try:
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
PDFKnowledgeSource(file_paths=["tmp/test.pdf"])
|
||||
|
||||
assert "pdfplumber is required" in str(exc_info.value)
|
||||
assert "crewai[knowledge]" in str(exc_info.value)
|
||||
finally:
|
||||
if test_file.exists():
|
||||
test_file.unlink()
|
||||
if knowledge_dir.exists() and not any(knowledge_dir.iterdir()):
|
||||
knowledge_dir.rmdir()
|
||||
|
||||
|
||||
def test_optional_visualization_import_error():
|
||||
"""Test that flow visualization raises helpful error without pyvis."""
|
||||
with patch.dict('sys.modules', {'pyvis': None}):
|
||||
with patch('crewai.flow.flow_visualizer.PYVIS_AVAILABLE', False):
|
||||
from crewai.flow.flow_visualizer import plot_flow
|
||||
|
||||
mock_flow = Mock()
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
plot_flow(mock_flow)
|
||||
|
||||
assert "Pyvis is required" in str(exc_info.value)
|
||||
assert "crewai[visualization]" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_telemetry_disabled_without_opentelemetry():
|
||||
"""Test that telemetry is disabled gracefully without opentelemetry."""
|
||||
from crewai.telemetry.telemetry import Telemetry
|
||||
|
||||
telemetry = Telemetry()
|
||||
|
||||
assert isinstance(telemetry.ready, bool)
|
||||
assert isinstance(telemetry._is_telemetry_disabled(), bool)
|
||||
|
||||
|
||||
def test_lite_agent_works_without_optional_deps():
|
||||
"""Test that LiteAgent can be created and used without optional dependencies."""
|
||||
from crewai.lite_agent import LiteAgent
|
||||
from crewai import LLM
|
||||
from unittest.mock import Mock
|
||||
|
||||
mock_llm = Mock(spec=LLM)
|
||||
mock_llm.call.return_value = "Test response"
|
||||
mock_llm.model = "test-model"
|
||||
|
||||
agent = LiteAgent(
|
||||
role="Test Agent",
|
||||
goal="Test Goal",
|
||||
backstory="Test Backstory",
|
||||
llm=mock_llm,
|
||||
verbose=False
|
||||
)
|
||||
|
||||
assert agent.role == "Test Agent"
|
||||
assert agent.goal == "Test Goal"
|
||||
assert agent.backstory == "Test Backstory"
|
||||
|
||||
|
||||
def test_basic_crew_creation_without_optional_deps():
|
||||
"""Test that basic Crew can be created without optional dependencies."""
|
||||
from crewai import Agent, Crew, Task, LLM
|
||||
from unittest.mock import Mock
|
||||
|
||||
mock_llm = Mock(spec=LLM)
|
||||
mock_llm.call.return_value = "Test response"
|
||||
mock_llm.model = "test-model"
|
||||
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test Goal",
|
||||
backstory="Test Backstory",
|
||||
llm=mock_llm,
|
||||
verbose=False
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Test task",
|
||||
agent=agent,
|
||||
expected_output="Test output"
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
verbose=False
|
||||
)
|
||||
|
||||
assert crew.agents[0].role == "Test Agent"
|
||||
assert crew.tasks[0].description == "Test task"
|
||||
|
||||
|
||||
def test_core_functionality_without_optional_deps():
|
||||
"""Test that core crewAI functionality works without optional dependencies."""
|
||||
from crewai import Agent, Task, Crew, LLM
|
||||
from unittest.mock import Mock
|
||||
|
||||
mock_llm = Mock(spec=LLM)
|
||||
mock_llm.call.return_value = "Test response"
|
||||
mock_llm.model = "test-model"
|
||||
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test Goal",
|
||||
backstory="Test Backstory",
|
||||
llm=mock_llm,
|
||||
verbose=False
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Test task description",
|
||||
agent=agent,
|
||||
expected_output="Test expected output"
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
verbose=False
|
||||
)
|
||||
|
||||
assert agent.role == "Test Agent"
|
||||
assert task.description == "Test task description"
|
||||
assert len(crew.agents) == 1
|
||||
assert len(crew.tasks) == 1
|
||||
122
tests/test_optional_dependencies.py
Normal file
122
tests/test_optional_dependencies.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Test optional dependency handling for crewAI lite version."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, Mock
|
||||
import sys
|
||||
|
||||
|
||||
class TestOptionalDependencies:
|
||||
"""Test that optional dependencies are handled gracefully."""
|
||||
|
||||
def test_chromadb_import_error_memory(self):
|
||||
"""Test that memory functionality raises helpful error without chromadb."""
|
||||
with patch.dict('sys.modules', {'chromadb': None}):
|
||||
with patch('crewai.memory.storage.rag_storage.CHROMADB_AVAILABLE', False):
|
||||
from crewai.memory.storage.rag_storage import RAGStorage
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
RAGStorage("test")
|
||||
|
||||
assert "ChromaDB is required" in str(exc_info.value)
|
||||
assert "crewai[memory]" in str(exc_info.value)
|
||||
|
||||
def test_chromadb_import_error_knowledge(self):
|
||||
"""Test that knowledge functionality raises helpful error without chromadb."""
|
||||
with patch.dict('sys.modules', {'chromadb': None}):
|
||||
with patch('crewai.knowledge.storage.knowledge_storage.CHROMADB_AVAILABLE', False):
|
||||
from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
KnowledgeStorage()
|
||||
|
||||
assert "ChromaDB is required" in str(exc_info.value)
|
||||
assert "crewai[knowledge]" in str(exc_info.value)
|
||||
|
||||
def test_pdfplumber_import_error(self):
|
||||
"""Test that PDF knowledge source raises helpful error without pdfplumber."""
|
||||
with patch.dict('sys.modules', {'pdfplumber': None}):
|
||||
from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource
|
||||
|
||||
pdf_source = PDFKnowledgeSource(file_paths=["test.pdf"])
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
pdf_source._import_pdfplumber()
|
||||
|
||||
assert "pdfplumber is required" in str(exc_info.value)
|
||||
assert "crewai[knowledge]" in str(exc_info.value)
|
||||
|
||||
def test_pyvis_import_error(self):
|
||||
"""Test that flow visualization raises helpful error without pyvis."""
|
||||
with patch.dict('sys.modules', {'pyvis': None}):
|
||||
with patch('crewai.flow.flow_visualizer.PYVIS_AVAILABLE', False):
|
||||
from crewai.flow.flow_visualizer import plot_flow
|
||||
|
||||
mock_flow = Mock()
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
plot_flow(mock_flow)
|
||||
|
||||
assert "Pyvis is required" in str(exc_info.value)
|
||||
assert "crewai[visualization]" in str(exc_info.value)
|
||||
|
||||
def test_auth0_import_error(self):
|
||||
"""Test that authentication raises helpful error without auth0."""
|
||||
with patch.dict('sys.modules', {'auth0': None}):
|
||||
with patch('crewai.cli.authentication.utils.AUTH0_AVAILABLE', False):
|
||||
from crewai.cli.authentication.utils import validate_token
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
validate_token("fake_token")
|
||||
|
||||
assert "Auth0 is required" in str(exc_info.value)
|
||||
assert "crewai[auth]" in str(exc_info.value)
|
||||
|
||||
def test_aisuite_import_error(self):
|
||||
"""Test that AISuite LLM raises helpful error without aisuite."""
|
||||
with patch.dict('sys.modules', {'aisuite': None}):
|
||||
with patch('crewai.llms.third_party.ai_suite.AISUITE_AVAILABLE', False):
|
||||
from crewai.llms.third_party.ai_suite import AISuiteLLM
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
AISuiteLLM("test-model")
|
||||
|
||||
assert "AISuite is required" in str(exc_info.value)
|
||||
assert "crewai[llm-integrations]" in str(exc_info.value)
|
||||
|
||||
def test_opentelemetry_graceful_degradation(self):
|
||||
"""Test that telemetry degrades gracefully without opentelemetry."""
|
||||
with patch.dict('sys.modules', {'opentelemetry': None}):
|
||||
with patch('crewai.telemetry.telemetry.OPENTELEMETRY_AVAILABLE', False):
|
||||
from crewai.telemetry.telemetry import Telemetry
|
||||
|
||||
telemetry = Telemetry()
|
||||
|
||||
assert not telemetry.ready
|
||||
assert telemetry._is_telemetry_disabled()
|
||||
assert not telemetry._should_execute_telemetry()
|
||||
|
||||
def test_embedding_configurator_import_error(self):
|
||||
"""Test that embedding configurator raises helpful error without chromadb."""
|
||||
with patch.dict('sys.modules', {'chromadb': None}):
|
||||
with patch('crewai.utilities.embedding_configurator.CHROMADB_AVAILABLE', False):
|
||||
from crewai.utilities.embedding_configurator import EmbeddingConfigurator
|
||||
|
||||
configurator = EmbeddingConfigurator()
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
configurator.configure_embedder(None)
|
||||
|
||||
assert "ChromaDB is required" in str(exc_info.value)
|
||||
assert "crewai[memory]" in str(exc_info.value) or "crewai[knowledge]" in str(exc_info.value)
|
||||
|
||||
def test_docling_import_error(self):
|
||||
"""Test that docling knowledge source raises helpful error without docling."""
|
||||
with patch.dict('sys.modules', {'docling': None}):
|
||||
with patch('crewai.knowledge.source.crew_docling_source.DOCLING_AVAILABLE', False):
|
||||
from crewai.knowledge.source.crew_docling_source import CrewDoclingSource
|
||||
|
||||
with pytest.raises(ImportError) as exc_info:
|
||||
CrewDoclingSource()
|
||||
|
||||
assert "docling package is required" in str(exc_info.value)
|
||||
assert "uv add docling" in str(exc_info.value)
|
||||
Reference in New Issue
Block a user