Compare commits

...

1 Commits

Author SHA1 Message Date
Devin AI
072f4aa2c2 fix: make lancedb an optional dependency to fix Windows installation (#5045)
lancedb >=0.30 dropped Windows (win_amd64) wheels, causing 'crewai install'
to fail on Windows with:
  Distribution lancedb==0.30.1 can't be installed because it doesn't have a
  source distribution or wheel for the current platform

Changes:
- Move lancedb from core dependencies to [project.optional-dependencies]
  under a new 'memory-storage' extra
- Guard the top-level 'import lancedb' in lancedb_storage.py with
  try/except ImportError, raising a clear install hint
- Guard the lazy import in Memory.model_post_init with a helpful
  ImportError message pointing to 'pip install crewai[memory-storage]'
- Add 3 tests verifying graceful handling when lancedb is absent

Co-Authored-By: João <joao@crewai.com>
2026-03-24 18:32:33 +00:00
5 changed files with 101 additions and 7 deletions

View File

@@ -43,7 +43,6 @@ dependencies = [
"uv~=0.9.13",
"aiosqlite~=0.21.0",
"pyyaml~=6.0",
"lancedb>=0.29.2",
]
[project.urls]
@@ -103,6 +102,9 @@ a2a = [
"httpx-sse~=0.4.0",
"aiocache[redis,memcached]~=0.12.3",
]
memory-storage = [
"lancedb>=0.29.2",
]
file-processing = [
"crewai-files",
]

View File

@@ -12,12 +12,28 @@ import threading
import time
from typing import Any
import lancedb # type: ignore[import-untyped]
try:
import lancedb # type: ignore[import-untyped]
except ImportError:
lancedb = None # type: ignore[assignment]
from crewai.memory.types import MemoryRecord, ScopeInfo
from crewai.utilities.lock_store import lock as store_lock
_INSTALL_HINT = (
"lancedb is required for the default memory storage backend but is not installed.\n\n"
"Install it with:\n"
" pip install crewai[memory-storage] # or: pip install lancedb\n\n"
"If lancedb does not provide wheels for your platform (e.g. Windows with "
"lancedb >=0.30), pin an older version:\n"
" pip install 'lancedb>=0.29.2,<0.30'\n\n"
"Alternatively, supply a custom StorageBackend to Memory(storage=...) to "
"bypass lancedb entirely."
)
_logger = logging.getLogger(__name__)
# Default embedding vector dimensionality (matches OpenAI text-embedding-3-small).
@@ -63,6 +79,8 @@ class LanceDBStorage:
fragment file; compaction merges them, keeping query
performance consistent. Set to 0 to disable.
"""
if lancedb is None:
raise ImportError(_INSTALL_HINT)
if path is None:
storage_dir = os.environ.get("CREWAI_STORAGE_DIR")
if storage_dir:

View File

@@ -173,7 +173,20 @@ class Memory(BaseModel):
)
if isinstance(self.storage, str):
from crewai.memory.storage.lancedb_storage import LanceDBStorage
try:
from crewai.memory.storage.lancedb_storage import LanceDBStorage
except ImportError as exc:
raise ImportError(
"lancedb is required for the default memory storage backend "
"but is not installed.\n\n"
"Install it with:\n"
" pip install crewai[memory-storage] # or: pip install lancedb\n\n"
"If lancedb does not provide wheels for your platform "
"(e.g. Windows with lancedb >=0.30), pin an older version:\n"
" pip install 'lancedb>=0.29.2,<0.30'\n\n"
"Alternatively, supply a custom StorageBackend instance to "
"Memory(storage=my_backend) to bypass lancedb entirely."
) from exc
self._storage = (
LanceDBStorage()

View File

@@ -999,3 +999,62 @@ def test_close_drains_and_shuts_down(tmp_path: Path, mock_embedder: MagicMock) -
mem.close()
# After close, records should be persisted
assert mem._storage.count() == 1
# --- lancedb optional dependency ---
def test_lancedb_storage_import_error_when_missing(monkeypatch: pytest.MonkeyPatch) -> None:
"""LanceDBStorage.__init__ raises ImportError with install hint when lancedb is absent."""
import crewai.memory.storage.lancedb_storage as mod
# Simulate lancedb not being installed
monkeypatch.setattr(mod, "lancedb", None)
with pytest.raises(ImportError, match="pip install crewai\\[memory-storage\\]"):
mod.LanceDBStorage(path="/tmp/fake")
def test_memory_raises_import_error_when_lancedb_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Memory(storage='lancedb') raises ImportError with install hint when lancedb is absent."""
import importlib
# Make the import of lancedb_storage raise ImportError
original_import = importlib.import_module
def _patched_import(name: str, *args, **kwargs): # type: ignore[no-untyped-def]
if name == "crewai.memory.storage.lancedb_storage":
raise ImportError("No module named 'lancedb'")
return original_import(name, *args, **kwargs)
monkeypatch.setattr(importlib, "import_module", _patched_import)
# Also patch the direct import that model_post_init uses
import builtins
original_builtins_import = builtins.__import__
def _patched_builtins_import(name, *args, **kwargs): # type: ignore[no-untyped-def]
if "lancedb_storage" in name:
raise ImportError("No module named 'lancedb'")
return original_builtins_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", _patched_builtins_import)
from crewai.memory.unified_memory import Memory
with pytest.raises(ImportError, match="pip install crewai\\[memory-storage\\]"):
Memory(storage="lancedb", llm=MagicMock(), embedder=MagicMock())
def test_memory_works_with_custom_storage_when_lancedb_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Memory accepts a custom StorageBackend even when lancedb is not installed."""
from crewai.memory.unified_memory import Memory
mock_storage = MagicMock()
# Should not raise even if lancedb is missing — custom storage bypasses it
mem = Memory(storage=mock_storage, llm=MagicMock(), embedder=MagicMock())
assert mem._storage is mock_storage

10
uv.lock generated
View File

@@ -1136,7 +1136,6 @@ dependencies = [
{ name = "json-repair" },
{ name = "json5" },
{ name = "jsonref" },
{ name = "lancedb" },
{ name = "mcp" },
{ name = "openai" },
{ name = "openpyxl" },
@@ -1196,6 +1195,9 @@ litellm = [
mem0 = [
{ name = "mem0ai" },
]
memory-storage = [
{ name = "lancedb" },
]
openpyxl = [
{ name = "openpyxl" },
]
@@ -1240,8 +1242,8 @@ requires-dist = [
{ name = "json-repair", specifier = "~=0.25.2" },
{ name = "json5", specifier = "~=0.10.0" },
{ name = "jsonref", specifier = "~=1.1.0" },
{ name = "lancedb", specifier = ">=0.29.2" },
{ name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.74.9,<3" },
{ name = "lancedb", marker = "extra == 'memory-storage'", specifier = ">=0.29.2" },
{ name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.74.9,<=1.82.6" },
{ name = "mcp", specifier = "~=1.26.0" },
{ name = "mem0ai", marker = "extra == 'mem0'", specifier = "~=0.1.94" },
{ name = "openai", specifier = ">=1.83.0,<3" },
@@ -1268,7 +1270,7 @@ requires-dist = [
{ name = "uv", specifier = "~=0.9.13" },
{ name = "voyageai", marker = "extra == 'voyageai'", specifier = "~=0.3.5" },
]
provides-extras = ["a2a", "anthropic", "aws", "azure-ai-inference", "bedrock", "docling", "embeddings", "file-processing", "google-genai", "litellm", "mem0", "openpyxl", "pandas", "qdrant", "tools", "voyageai", "watson"]
provides-extras = ["a2a", "anthropic", "aws", "azure-ai-inference", "bedrock", "docling", "embeddings", "file-processing", "google-genai", "litellm", "mem0", "memory-storage", "openpyxl", "pandas", "qdrant", "tools", "voyageai", "watson"]
[[package]]
name = "crewai-devtools"