import unittest from unittest.mock import MagicMock, patch import numpy as np import sys import os # Ensure the package is in the path sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from semantica.embeddings.text_embedder import TextEmbedder from semantica.embeddings.embedding_generator import EmbeddingGenerator class TestModelSwitching(unittest.TestCase): """Test suite for dynamic model switching in TextEmbedder and EmbeddingGenerator.""" def setUp(self): # Mock dependencies self.mock_st_patcher = patch('semantica.embeddings.text_embedder.SentenceTransformer') self.mock_st_class = self.mock_st_patcher.start() self.mock_fe_patcher = patch('semantica.embeddings.text_embedder.TextEmbedding') self.mock_fe_class = self.mock_fe_patcher.start() # Patch availability flags self.st_avail_patcher = patch('semantica.embeddings.text_embedder.SENTENCE_TRANSFORMERS_AVAILABLE', True) self.st_avail_patcher.start() self.fe_avail_patcher = patch('semantica.embeddings.text_embedder.FASTEMBED_AVAILABLE', True) self.fe_avail_patcher.start() def tearDown(self): self.mock_st_patcher.stop() self.mock_fe_patcher.stop() self.st_avail_patcher.stop() self.fe_avail_patcher.stop() def test_text_embedder_dynamic_switching(self): """Test that TextEmbedder correctly switches between methods and clears state.""" # 1. Start with fastembed embedder = TextEmbedder(method="fastembed", model_name="fast-model") self.assertEqual(embedder.get_method(), "fastembed") self.assertIsNotNone(embedder.fastembed_model) self.assertIsNone(embedder.model) # 2. Switch to sentence_transformers embedder.set_model(method="sentence_transformers", model_name="st-model") # Verify method and model state self.assertEqual(embedder.get_method(), "sentence_transformers") self.assertIsNone(embedder.fastembed_model, "fastembed_model should be cleared after switching") self.assertIsNotNone(embedder.model, "sentence_transformer model should be initialized") self.assertEqual(embedder.model_name, "st-model") # 3. Switch back to fastembed embedder.set_model(method="fastembed", model_name="fast-model-v2") self.assertEqual(embedder.get_method(), "fastembed") self.assertIsNotNone(embedder.fastembed_model, "fastembed_model should be re-initialized") self.assertIsNone(embedder.model, "sentence_transformer model should be cleared") self.assertEqual(embedder.model_name, "fast-model-v2") def test_embedding_generator_dynamic_switching(self): """Test that EmbeddingGenerator correctly propagates model switches to TextEmbedder.""" gen = EmbeddingGenerator() # Default should be fastembed self.assertEqual(gen.get_text_method(), "fastembed") # Switch via EmbeddingGenerator gen.set_text_model(method="sentence_transformers", model_name="new-st-model") # Verify through Generator methods self.assertEqual(gen.get_text_method(), "sentence_transformers") info = gen.get_methods_info() self.assertEqual(info['text']['method'], "sentence_transformers") self.assertEqual(info['text']['model_name'], "new-st-model") self.assertTrue(info['text']['model_loaded']) def test_fallback_on_switch_failure(self): """Test that switching to an unavailable method falls back to 'fallback'.""" embedder = TextEmbedder(method="sentence_transformers") # Mock availability to False for the next switch with patch('semantica.embeddings.text_embedder.FASTEMBED_AVAILABLE', False): embedder.set_model(method="fastembed", model_name="some-model") self.assertEqual(embedder.get_method(), "fallback") self.assertIsNone(embedder.fastembed_model) self.assertIsNone(embedder.model) def test_dimension_update_on_switch(self): """Test that embedding_dimension is correctly updated when switching models.""" # 1. Setup mocks to return specific dimensions # FastEmbed mock mock_fe_instance = MagicMock() # FastEmbed doesn't have a direct dimension attribute in our implementation, # it calls _embed_with_fastembed which calls self.fastembed_model.embed mock_fe_instance.embed.return_value = [np.zeros(384)] self.mock_fe_class.return_value = mock_fe_instance # SentenceTransformer mock mock_st_instance = MagicMock() mock_st_instance.get_sentence_embedding_dimension.return_value = 768 self.mock_st_class.return_value = mock_st_instance # 2. Start with FastEmbed (dim 384) embedder = TextEmbedder(method="fastembed", model_name="fast-model") self.assertEqual(embedder.get_embedding_dimension(), 384) # 3. Switch to SentenceTransformer (dim 768) embedder.set_model(method="sentence_transformers", model_name="st-model-large") self.assertEqual(embedder.get_embedding_dimension(), 768) # 4. Switch to another SentenceTransformer with different dimension mock_st_instance_small = MagicMock() mock_st_instance_small.get_sentence_embedding_dimension.return_value = 512 self.mock_st_class.return_value = mock_st_instance_small embedder.set_model(method="sentence_transformers", model_name="st-model-small") self.assertEqual(embedder.get_embedding_dimension(), 512) if __name__ == '__main__': unittest.main()