diff --git a/docs/memory.md b/docs/memory.md index 06a3ee54f..e2eb381e6 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -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 diff --git a/src/crewai/memory/storage/rag_storage.py b/src/crewai/memory/storage/rag_storage.py index ca57cb4c4..79c04852a 100644 --- a/src/crewai/memory/storage/rag_storage.py +++ b/src/crewai/memory/storage/rag_storage.py @@ -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. diff --git a/src/crewai/utilities/embedding_configurator.py b/src/crewai/utilities/embedding_configurator.py index e5c4480b1..89ca7b461 100644 --- a/src/crewai/utilities/embedding_configurator.py +++ b/src/crewai/utilities/embedding_configurator.py @@ -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") diff --git a/tests/memory/test_memory_reset.py b/tests/memory/test_memory_reset.py index 1e9d5e722..dfcffa2e2 100644 --- a/tests/memory/test_memory_reset.py +++ b/tests/memory/test_memory_reset.py @@ -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"))