fix: Remove OpenAI dependency for memory reset when using alternative LLMs

- Add environment variables for default embedding provider
- Support Ollama as default embedding provider
- Add tests for memory reset with different providers
- Update documentation

Fixes #2023

Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
Devin AI
2025-02-05 11:09:36 +00:00
parent 649414805d
commit 823f22a601
4 changed files with 112 additions and 24 deletions

View File

@@ -34,3 +34,43 @@ crewai reset-memories -a
```
The memory system will use the configured embedding provider for all operations, including memory reset.
## Troubleshooting
### Common Issues
1. OpenAI API Key Missing
```bash
# Error: EmbeddingConfigurationError: Invalid configuration for OpenAI provider
# Solution: Set your OpenAI API key
export OPENAI_API_KEY=your_key
```
2. Ollama Connection Issues
```bash
# Error: Failed to connect to Ollama server
# Solution: Ensure Ollama is running and accessible
curl http://localhost:11434/api/embeddings
```
3. Memory Reset Permission Issues
```bash
# Error: Failed to reset memory (readonly database)
# Solution: Check file permissions or use custom path
export CREWAI_MEMORY_PATH=/path/with/write/access
```
### Custom Storage Path
You can configure a custom storage path for memory files:
```python
from crewai.memory import ShortTermMemory
memory = ShortTermMemory(path="/custom/storage/path")
```
This is useful when:
- Default storage location has permission issues
- You want to isolate memory storage for different crews
- You need to manage memory persistence manually

View File

@@ -85,11 +85,10 @@ class RAGStorage(BaseRAGStorage):
self._set_embedder_config()
try:
chroma_client = chromadb.PersistentClient(
self.app = chromadb.PersistentClient(
path=self.path if self.path else self.storage_file_name,
settings=Settings(allow_reset=self.allow_reset),
)
self.app = chroma_client
if not self.app:
raise RuntimeError("Failed to initialize ChromaDB client")
@@ -104,19 +103,6 @@ class RAGStorage(BaseRAGStorage):
except Exception as e:
raise RuntimeError(f"Failed to initialize ChromaDB: {str(e)}")
self.app = chroma_client
if not self.app:
raise RuntimeError("Failed to initialize ChromaDB client")
try:
self.collection = self.app.get_collection(
name=self.type, embedding_function=self.embedder_config
)
except Exception:
self.collection = self.app.create_collection(
name=self.type, embedding_function=self.embedder_config
)
def _sanitize_role(self, role: str) -> str:
"""
Sanitizes agent roles to ensure valid directory names.
@@ -138,12 +124,21 @@ class RAGStorage(BaseRAGStorage):
return f"{base_path}/{file_name}"
def save(self, value: Any, metadata: Dict[str, Any]) -> None:
"""Save a value with metadata to the memory storage.
Args:
value: The text content to store
metadata: Additional metadata for the stored content
Raises:
EmbeddingInitializationError: If embedding generation fails
"""
if not hasattr(self, "app") or not hasattr(self, "collection"):
self._initialize_app()
try:
self._generate_embedding(value, metadata)
except Exception as e:
logging.error(f"Error during {self.type} save: {str(e)}")
raise EmbeddingInitializationError(self.type, str(e))
def search(
self,
@@ -151,7 +146,18 @@ class RAGStorage(BaseRAGStorage):
limit: int = 3,
filter: Optional[dict] = None,
score_threshold: float = 0.35,
) -> List[Any]:
) -> List[Dict[str, Any]]:
"""Search for similar content in memory.
Args:
query: The search query text
limit: Maximum number of results to return
filter: Optional filter criteria
score_threshold: Minimum similarity score threshold
Returns:
List of matching results with metadata and scores
"""
if not hasattr(self, "app"):
self._initialize_app()
@@ -175,15 +181,24 @@ class RAGStorage(BaseRAGStorage):
logging.error(f"Error during {self.type} search: {str(e)}")
return []
def _generate_embedding(self, text: str, metadata: Dict[str, Any]) -> None: # type: ignore
def _generate_embedding(self, text: str, metadata: Dict[str, Any]) -> None:
"""Generate and store embeddings for the given text.
Args:
text: The text to generate embeddings for
metadata: Additional metadata to store with the embeddings
"""
if not hasattr(self, "app") or not hasattr(self, "collection"):
self._initialize_app()
self.collection.add(
documents=[text],
metadatas=[metadata or {}],
ids=[str(uuid.uuid4())],
)
try:
self.collection.add(
documents=[text],
metadatas=[metadata or {}],
ids=[str(uuid.uuid4())],
)
except Exception as e:
raise EmbeddingInitializationError(self.type, f"Failed to generate embedding: {str(e)}")
def reset(self) -> None:
"""Reset the memory storage by clearing the database and removing files.

View File

@@ -71,8 +71,11 @@ class EmbeddingConfigurator:
model = os.getenv("CREWAI_EMBEDDING_MODEL", DEFAULT_EMBEDDING_MODEL)
if provider == "openai":
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise EmbeddingConfigurationError("OpenAI API key is required but not provided")
from chromadb.utils.embedding_functions.openai_embedding_function import OpenAIEmbeddingFunction
return OpenAIEmbeddingFunction(api_key=os.getenv("OPENAI_API_KEY"), model_name=model)
return OpenAIEmbeddingFunction(api_key=api_key, model_name=model)
elif provider == "ollama":
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction
url = os.getenv("CREWAI_OLLAMA_URL", "http://localhost:11434/api/embeddings")

View File

@@ -48,6 +48,19 @@ def test_memory_reset_with_invalid_provider(temp_db_dir):
for memory in memories:
memory.reset()
def test_memory_reset_with_invalid_configuration(temp_db_dir):
os.environ["CREWAI_EMBEDDING_PROVIDER"] = "openai"
os.environ.pop("OPENAI_API_KEY", None)
with pytest.raises(EmbeddingConfigurationError):
memories = [
ShortTermMemory(path=temp_db_dir),
LongTermMemory(path=temp_db_dir),
EntityMemory(path=temp_db_dir)
]
for memory in memories:
memory.reset()
def test_memory_reset_with_missing_ollama_url(temp_db_dir):
os.environ["CREWAI_EMBEDDING_PROVIDER"] = "ollama"
os.environ.pop("CREWAI_OLLAMA_URL", None)
@@ -59,3 +72,20 @@ def test_memory_reset_with_missing_ollama_url(temp_db_dir):
]
for memory in memories:
memory.reset()
def test_memory_reset_with_custom_path(temp_db_dir):
os.environ["CREWAI_EMBEDDING_PROVIDER"] = "ollama"
custom_path = os.path.join(temp_db_dir, "custom")
os.makedirs(custom_path, exist_ok=True)
memories = [
ShortTermMemory(path=custom_path),
LongTermMemory(path=custom_path),
EntityMemory(path=custom_path)
]
for memory in memories:
memory.reset()
assert not os.path.exists(os.path.join(custom_path, "short_term"))
assert not os.path.exists(os.path.join(custom_path, "long_term"))
assert not os.path.exists(os.path.join(custom_path, "entity"))