"""
The FRANKENSTEIN is an architecture for language models that combines various submodels of different architectures
so that they can work together in order to deliver responses that are more accurate and better suited to the user's needs. Both local models and external models via API can be combined,
or even a combination of the two. These capabilities allow FRANKENSTEIN to infer both models with the proprietary architectures of Sapiens Technology®️,
as well as models with third-party architectures if there is such a need. The submodels are grouped into a classes structure capable of selecting one or more of them depending on the needs of the prompt.
When compiling the FRANKENSTEIN structure, a single model will be created through the result of the weights of all the local submodels contained in the architecture,
enabling packaging and distribution with a single file. Unlike what happens in a conventional MoE (Mixture of Experts),
FRANKENSTEIN does not need to load all submodels at once into memory and does not depend on all submodels belonging to the same architecture,
making it lighter and more flexible than a MoE and allowing it to run on more modest hardware.

Any alteration, copying, sharing, posting, or public comment about the technologies presented here without the prior authorization of Sapiens Technology®️ is strictly prohibited,
and failure to comply with these rules will result in legal proceedings initiated by our team of lawyers.
"""
# --------------------------> A SAPIENS TECHNOLOGY®️ PRODUCTION) <--------------------------
class FRANKENSTEIN:
	def __init__(self, show_errors=True, display_error_point=False, progress=False):
		try:
			self.__show_errors = bool(show_errors) if type(show_errors) in (bool, int, float) else True
			self.__display_error_point = bool(display_error_point) if type(display_error_point) in (bool, int, float) else False
			self.__progress = bool(progress) if type(progress) in (bool, int, float) else False
			try:
				from warnings import filterwarnings
				from logging import getLogger, ERROR, disable, CRITICAL
				from os import environ
				from dotenv import load_dotenv
				filterwarnings('ignore')
				filterwarnings('ignore', category=UserWarning, module='torch.distributed')
				getLogger('torch.distributed.elastic.multiprocessing.redirects').setLevel(ERROR)
				environ['DISABLE_MODEL_SOURCE_CHECK'] = 'True'
				load_dotenv()
				disable(CRITICAL)
			except: pass
			from traceback import print_exc
			self.__print_exc = print_exc
			from sapiens_frankenstein import SapiensFrankenstein
			self.__FrankensteinClassesStructure = SapiensFrankenstein(show_errors=self.__show_errors, display_error_point=self.__display_error_point, progress=self.__progress)
			self.__model_path, self.__temporary_paths = '', []
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.__init__: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
	def __closeFiles(self):
		try:
			if self.__temporary_paths:
				from utilities_nlp import UtilitiesNLP as SapiensUtilities
				sapiens_utilities = SapiensUtilities(show_errors=self.__show_errors, display_error_point=self.__display_error_point)
				for temporary_path in self.__temporary_paths:
					if temporary_path and not sapiens_utilities.isURLAddress(file_path=temporary_path): sapiens_utilities.deleteFile(file_path=temporary_path)
			return True
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.__closeFiles: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
	def downloadSubmodel(self, model_path='', destination_path=''):
		try:
			downloaded_submodel = False
			model_path = str(model_path).strip()
			destination_path = str(destination_path).strip()
			from warnings import simplefilter
			simplefilter('ignore')
			from sapiens_transformers.download import DownloadHF as SapiensDownload
			download = SapiensDownload(model_path=model_path)
			downloaded_submodel = download.native_snapshot_download(destination_path=destination_path)
			return downloaded_submodel
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.downloadSubmodel: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
	def createFrankenstein(self, model_path='', configuration={}):
		try: return self.__FrankensteinClassesStructure.createFrankenstein(model_path=model_path, configuration=configuration)
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.createFrankenstein: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
	def loadFrankenstein(self, model_path=''):
		try:
			loaded_frankenstein = False
			model_path = str(model_path).strip()
			loaded_frankenstein = self.__FrankensteinClassesStructure.loadFrankenstein(model_path=model_path)
			if loaded_frankenstein: self.__model_path = model_path
			return loaded_frankenstein
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.loadFrankenstein: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
	def compileFrankenstein(self, model_path='', progress=True, sapi=False):
		try:
			compiled_frankenstein = False
			model_path = str(model_path).strip()
			progress = bool(progress) if type(progress) in (bool, int, float) else True
			sapi = bool(sapi) if type(sapi) in (bool, int, float) else False
			if not model_path: return compiled_frankenstein
			def _copy_directory_with_new_name(directory_path=''):
				from pathlib import Path
				from shutil import copytree
				try:
					new_model_path = ''
					source_path = Path(directory_path)
					if not source_path.exists() or not source_path.is_dir(): return False
					new_name = 'opened_model_' + source_path.name
					destination_path = source_path.parent / new_name
					new_model_path = str(destination_path)
					if destination_path.exists(): return False
					copytree(source_path, destination_path)
					if destination_path.exists() and destination_path.is_dir(): return new_model_path
				except: return False
			def _normalize_model_path_in_json(json_file_path=''):
			    try:
			        from json import load, dump
			        with open(json_file_path, 'r', encoding='utf-8') as file: data = load(file)
			        def __process_structure(structure={}):
			            from os import path, sep
			            if isinstance(structure, dict):
			                for key, value in structure.items():
			                    if key == 'model_path' and isinstance(value, str) and value.strip():
			                        if not path.isabs(value) and path.dirname(value) == '': structure[key] = '.'+sep+value
			                    else: __process_structure(value)
			            elif isinstance(structure, list):
			                for item in structure: __process_structure(item)
			        __process_structure(data)
			        with open(json_file_path, 'w', encoding='utf-8') as file: dump(data, file, ensure_ascii=False, indent=4)
			        return True
			    except: return False
			new_model_path = _copy_directory_with_new_name(directory_path=model_path)
			from os.path import join, exists
			json_file_path = join(new_model_path, 'frankenstein.json')
			normalize_model_path_in_json = _normalize_model_path_in_json(json_file_path=json_file_path)
			if not normalize_model_path_in_json and not exists(json_file_path): new_model_path = ''
			if new_model_path:
				if self.loadFrankenstein(model_path=new_model_path):
					configuration = self.__FrankensteinClassesStructure._SapiensFrankenstein__configuration
					def _update_model_path(configuration={}, default_key='instruction_path'):
						try:
							from os import path
							if not isinstance(configuration, dict): return configuration
							default_key = str(default_key).strip()
							found_instruction_path = False
							updated_any_value = False
							def __process_dictionary(current_dictionary=None):
								nonlocal found_instruction_path, updated_any_value
								for key, value in current_dictionary.items():
									if key == default_key:
										found_instruction_path = True
										if isinstance(value, str) and value.strip():
											file_name = path.basename(value)
											if file_name:
												current_dictionary[key] = file_name
												updated_any_value = True
									elif isinstance(value, dict): __process_dictionary(current_dictionary=value)
									elif isinstance(value, list):
										for item in value:
											if isinstance(item, dict): __process_dictionary(current_dictionary=item)
							__process_dictionary(current_dictionary=configuration)
							if not found_instruction_path or not updated_any_value: return configuration
							return configuration
						except: return configuration
					self.__FrankensteinClassesStructure._SapiensFrankenstein__configuration = _update_model_path(configuration=configuration, default_key='model_path')
					self.__FrankensteinClassesStructure._SapiensFrankenstein__configuration = _update_model_path(configuration=configuration, default_key='instruction_path')
					if self.__FrankensteinClassesStructure.assembleFrankenstein(progress=progress):
						if self.__FrankensteinClassesStructure.packFrankenstein(model_path=new_model_path, progress=progress, sapi=sapi):
							def _rename_opened_model_file(directory_path=''):
								from pathlib import Path
								try:
									source_path = Path(directory_path)
									if not source_path.exists() or not source_path.is_dir(): return False
									parent_path = source_path.parent
									for file_path in parent_path.iterdir():
										if file_path.is_file() and file_path.name.startswith('opened_model_') and file_path.suffix in ('.SAPI', '.sapiens'):
											new_name = 'closed_model_' + file_path.name[len('opened_model_'):]
											new_path = parent_path / new_name
											file_path.rename(new_path)
											if new_path.exists(): return True
											return False
									return False
								except: return False
							compiled_frankenstein = _rename_opened_model_file(directory_path=new_model_path)
			return compiled_frankenstein
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.compileFrankenstein: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
	def inference(self, messages=[], max_tokens=None, stream=True, task_names=[], language='', temperature=0.5, creativity=0.5, humor=0.5, formality=0.5, political_spectrum=0.5, reasoning=0.0, reflection=False, tools=[]):
		try:
			inference_result = {'answer': '', 'answer_for_files': '', 'files': [], 'sources': [], 'javascript_charts': [], 'artifacts': [], 'next_token': '', 'str_cost': '0.0000000000', 'tool_call': []}
			messages = list(messages) if type(messages) in (tuple, list) else []
			if max_tokens is not None: max_tokens = int(max_tokens) if type(max_tokens) in (bool, int, float) else None
			stream = bool(stream) if type(stream) in (bool, int, float) else True
			task_names = list(task_names) if type(task_names) in (tuple, list) else []
			language = str(language).strip()
			temperature = float(temperature) if type(temperature) in (bool, int, float) else 0.5
			creativity = float(creativity) if type(creativity) in (bool, int, float) else 0.5
			humor = float(humor) if type(humor) in (bool, int, float) else 0.5
			formality = float(formality) if type(formality) in (bool, int, float) else 0.5
			political_spectrum = float(political_spectrum) if type(political_spectrum) in (bool, int, float) else 0.5
			reasoning = float(reasoning) if type(reasoning) in (bool, int, float) else 0.5
			reflection = bool(reflection) if type(reflection) in (bool, int, float) else False
			tools = list(tools) if type(tools) in (tuple, list) else []
			temporary_paths = []
			from utilities_nlp import UtilitiesNLP as SapiensUtilities
			sapiens_utilities = SapiensUtilities(show_errors=self.__show_errors, display_error_point=self.__display_error_point)
			def _get_temporary_file_path(file_name=''):
				from tempfile import gettempdir
				from os.path import join
				temporary_directory = gettempdir()
				return join(temporary_directory, file_name)
			for index, message in enumerate(messages):
				role = str(message.get('role', '')).lower().strip()
				if role == 'user':
					file_paths, base64 = [], []
					base64 = message.get('base64', [])
					base64 = list(base64) if type(base64) in (tuple, list) else [dict(base64)]
					if base64:
						file_paths = message.get('file_paths', [])
						file_paths = list(file_paths) if type(file_paths) in (tuple, list) else [str(file_paths).strip()]
						if not file_paths:
							file_path = message.get('file_path', [])
							file_paths = list(file_path) if type(file_path) in (tuple, list) else [str(file_path).strip()]
						for file_dictionary in base64:
							if file_dictionary and type(file_dictionary) == dict:
								_type = str(file_dictionary.get('type', 'png')).strip()
								if not _type: _type = 'png'
								file_name = f'{sapiens_utilities.getHashCode()}.{_type}'.strip()
								file_name = _get_temporary_file_path(file_name=file_name)
								if sapiens_utilities.base64ToFile(file_dictionary=file_dictionary, file_path=file_name):
									file_paths.append(file_name)
									temporary_paths.append(file_name)
									messages[index]['file_paths'] = file_paths
						del messages[index]['base64']
			inference_result = self.__FrankensteinClassesStructure.inference(messages=messages, max_tokens=max_tokens, stream=stream, task_names=task_names, language=language, temperature=temperature, creativity=creativity, humor=humor, formality=formality, political_spectrum=political_spectrum, reasoning=reasoning, reflection=reflection, tools=tools)
			self.__temporary_paths = temporary_paths
			return inference_result
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.inference: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return {'answer': '', 'answer_for_files': '', 'files': [], 'sources': [], 'javascript_charts': [], 'artifacts': [], 'next_token': '', 'str_cost': '0.0000000000', 'tool_call': []}
	def close(self):
		try:
			closed = False
			if self.__model_path:
				from sapiens_loader import SapiensLoader, DataLoader
				sapiens_loader = SapiensLoader(self.__model_path)
				data_loader = DataLoader(sapiens_loader)
				data_loader.open(False)
				closed = self.__closeFiles()
				return data_loader.close() and closed
			else: return self.__closeFiles()
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.close: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
	def delete(self, model_path=''):
		try:
			model_path = str(model_path).strip()
			from pathlib import Path
			if not Path(model_path).exists(): return True
			from SUBMODELS import SUBMODELS
			return SUBMODELS(show_errors=self.__show_errors, display_error_point=self.__display_error_point).deleteModel(model_path)
		except Exception as error:
			try:
				if self.__show_errors:
					error_message = 'ERROR in FRANKENSTEIN.delete: '+str(error)
					print(error_message)
					try: self.__print_exc() if self.__display_error_point else None
					except: pass
			except: pass
			return False
"""
The FRANKENSTEIN is an architecture for language models that combines various submodels of different architectures
so that they can work together in order to deliver responses that are more accurate and better suited to the user's needs. Both local models and external models via API can be combined,
or even a combination of the two. These capabilities allow FRANKENSTEIN to infer both models with the proprietary architectures of Sapiens Technology®️,
as well as models with third-party architectures if there is such a need. The submodels are grouped into a classes structure capable of selecting one or more of them depending on the needs of the prompt.
When compiling the FRANKENSTEIN structure, a single model will be created through the result of the weights of all the local submodels contained in the architecture,
enabling packaging and distribution with a single file. Unlike what happens in a conventional MoE (Mixture of Experts),
FRANKENSTEIN does not need to load all submodels at once into memory and does not depend on all submodels belonging to the same architecture,
making it lighter and more flexible than a MoE and allowing it to run on more modest hardware.

Any alteration, copying, sharing, posting, or public comment about the technologies presented here without the prior authorization of Sapiens Technology®️ is strictly prohibited,
and failure to comply with these rules will result in legal proceedings initiated by our team of lawyers.
"""
# --------------------------> A SAPIENS TECHNOLOGY®️ PRODUCTION) <--------------------------
