Moved to the new configuration system

This commit is contained in:
Saifeddine ALOUI 2023-06-21 00:56:00 +02:00
parent 2ed99531eb
commit f7348b0084
11 changed files with 785 additions and 327 deletions

View File

@ -6,18 +6,13 @@ __copyright__ = "Copyright 2023, "
__license__ = "Apache 2.0"
from lollms.binding import LLMBinding, LOLLMSConfig
from lollms.binding import LLMBinding, LOLLMSConfig
from lollms.personality import AIPersonality, MSG_TYPE
from lollms.helpers import ASCIIColors
from lollms.paths import LollmsPaths
from lollms.helpers import ASCIIColors
from lollms.paths import LollmsPaths
#from lollms.binding import LLMBinding
import importlib
from pathlib import Path
from pathlib import Path
from pathlib import Path
def reset_all_installs():
@ -33,20 +28,9 @@ def reset_all_installs():
class BindingBuilder:
def build_binding(self, bindings_path: Path, cfg: LOLLMSConfig, force_reinstall=False)->LLMBinding:
binding_path = Path(bindings_path) / cfg["binding_name"]
# first find out if there is a requirements.txt file
install_file_name = "install.py"
install_script_path = binding_path / install_file_name
if install_script_path.exists():
module_name = install_file_name[:-3] # Remove the ".py" extension
module_spec = importlib.util.spec_from_file_location(module_name, str(install_script_path))
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
if hasattr(module, "Install"):
module.Install(cfg, force_reinstall=force_reinstall)
# define the full absolute path to the module
absolute_path = binding_path.resolve()
# infer the module name from the file path

View File

@ -9,17 +9,15 @@
######
from pathlib import Path
from typing import Callable
from lollms.helpers import BaseConfig, ASCIIColors
from lollms.paths import LollmsPaths
from lollms.helpers import ASCIIColors
import inspect
import yaml
import sys
from tqdm import tqdm
import urllib.request
import importlib
import shutil
import subprocess
from lollms.config import TypedConfig
from lollms.main_config import LOLLMSConfig
__author__ = "parisneo"
@ -30,174 +28,6 @@ __license__ = "Apache 2.0"
import yaml
DEFAULT_CONFIG = {
# =================== Lord Of Large Language Models Configuration file ===========================
"version": 5,
"binding_name": "llama_cpp_official",
"model_name": "Wizard-Vicuna-7B-Uncensored.ggmlv3.q4_0.bin",
# Host information
"host": "localhost",
"port": 9600,
# Genreration parameters
"seed": -1,
"n_predict": 1024,
"ctx_size": 2048,
"temperature": 0.9,
"top_k": 50,
"top_p": 0.95,
"repeat_last_n": 40,
"repeat_penalty": 1.2,
"n_threads": 8,
#Personality parameters
"personalities": ["english/generic/lollms"],
"active_personality_id": 0,
"override_personality_model_parameters": False, #if true the personality parameters are overriden by those of the configuration (may affect personality behaviour)
"user_name": "user",
}
class LOLLMSConfig(BaseConfig):
def __init__(self, file_path=None, lollms_paths:LollmsPaths = None):
super().__init__(["file_path", "config", "lollms_paths"])
if file_path:
self.file_path = Path(file_path)
else:
self.file_path = None
if file_path is not None:
self.load_config(file_path)
else:
self.config = DEFAULT_CONFIG.copy()
if lollms_paths is None:
self.lollms_paths = LollmsPaths()
else:
self.lollms_paths = lollms_paths
@staticmethod
def autoload(lollms_paths, config_path:str=None):
# Configuration loading part
original_cfg_path = lollms_paths.default_cfg_path
if config_path is None:
local = lollms_paths.personal_configuration_path / "local_config.yaml"
if not local.exists():
shutil.copy(original_cfg_path, local)
cfg_path = local
else:
cfg_path = config_path
if cfg_path.exists():
original_config = LOLLMSConfig(original_cfg_path, lollms_paths)
config = LOLLMSConfig(cfg_path, lollms_paths)
if "version" not in config or int(config["version"])<int(original_config["version"]):
#Upgrade old configuration files to new format
ASCIIColors.error("Configuration file is very old.\nReplacing with default configuration")
_, added, removed = config.sync_cfg(original_config)
print(f"Added entries : {added}, removed entries:{removed}")
config.save_config(cfg_path)
else:
config = LOLLMSConfig(lollms_paths=lollms_paths)
return config
def sync_cfg(self, default_config):
"""Syncs a configuration with the default configuration
Args:
default_config (_type_): _description_
config (_type_): _description_
Returns:
_type_: _description_
"""
added_entries = []
removed_entries = []
# Ensure all fields from default_config exist in config
for key, value in default_config.config.items():
if key not in self:
self[key] = value
added_entries.append(key)
# Remove fields from config that don't exist in default_config
for key in list(self.config.keys()):
if key not in default_config.config:
del self.config[key]
removed_entries.append(key)
self["version"]=default_config["version"]
return self, added_entries, removed_entries
def get_model_path_infos(self):
return f"personal_models_path: {self.lollms_paths.personal_models_path}\nBinding name:{self.binding_name}\nModel name:{self.model_name}"
def get_personality_path_infos(self):
return f"personalities_zoo_path: {self.lollms_paths.personalities_zoo_path}\nPersonalities:{self.personalities}\nActive personality id:{self.active_personality_id}"
def get_model_full_path(self):
try:
return self.lollms_paths.personal_models_path/self.binding_name/self.model_name
except:
return None
def check_model_existance(self):
try:
model_path = self.lollms_paths.personal_models_path/self.binding_name/self.model_name
return model_path.exists()
except Exception as ex:
print(f"Exception in checking model existance: {ex}")
return False
def download_model(self, url, binding, callback = None):
folder_path = self.lollms_paths.personal_models_path/self.binding_name
model_name = url.split("/")[-1]
model_full_path = (folder_path / model_name)
if binding is not None and hasattr(binding,'download_model'):
binding.download_model(url, model_full_path, callback)
else:
# Check if file already exists in folder
if model_full_path.exists():
print("File already exists in folder")
else:
# Create folder if it doesn't exist
folder_path.mkdir(parents=True, exist_ok=True)
progress_bar = tqdm(total=None, unit="B", unit_scale=True, desc=f"Downloading {url.split('/')[-1]}")
# Define callback function for urlretrieve
def report_progress(block_num, block_size, total_size):
progress_bar.total=total_size
progress_bar.update(block_size)
# Download file from URL to folder
try:
urllib.request.urlretrieve(url, folder_path / url.split("/")[-1], reporthook=report_progress if callback is None else callback)
print("File downloaded successfully!")
except Exception as e:
print("Error downloading file:", e)
sys.exit(1)
def reference_model(self, path):
path = str(path).replace("\\","/")
folder_path = self.lollms_paths.personal_models_path/self.binding_name
model_name = path.split("/")[-1]+".reference"
model_full_path = (folder_path / model_name)
# Check if file already exists in folder
if model_full_path.exists():
print("File already exists in folder")
else:
# Create folder if it doesn't exist
folder_path.mkdir(parents=True, exist_ok=True)
with open(model_full_path,"w") as f:
f.write(path)
print("Reference created, please make sure you don't delete the file or you will have broken link")
class BindingInstaller:
@ -219,11 +49,62 @@ class LLMBinding:
file_extension='*.bin'
binding_path = Path(__file__).parent
def __init__(self, config:LOLLMSConfig, inline:bool) -> None:
self.config = config
self.inline = inline
def __init__(
self,
binding_dir:Path,
lollms_paths:LollmsPaths,
config:LOLLMSConfig,
binding_config:TypedConfig,
force_install:bool=False
) -> None:
self.binding_dir = binding_dir
self.binding_folder_name = binding_dir.stem
self.lollms_paths = lollms_paths
self.config = config
self.binding_config = binding_config
def load_config_file(self, path):
self.configuration_file_path = lollms_paths.personal_configuration_path/f"binding_{self.binding_folder_name}.yaml"
self.binding_config.config.file_path = self.configuration_file_path
if not self.configuration_file_path.exists() or force_install:
self.install()
self.binding_config.config.save_config()
else:
self.load_binding_config()
self.models_folder = config.lollms_paths.personal_models_path / self.binding_folder_name
self.models_folder.mkdir(parents=True, exist_ok=True)
def install(self):
"""
Installation procedure (to be implemented)
"""
ASCIIColors.blue("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*")
ASCIIColors.red(f"Installing {self.binding_folder_name}")
ASCIIColors.blue("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*")
def get_model_path(self):
"""
Retrieves the path of the model based on the configuration.
If the model name ends with ".reference", it reads the model path from a file.
Otherwise, it constructs the model path based on the configuration.
Returns:
str: The path of the model.
"""
if self.config.model_name.endswith(".reference"):
with open(str(self.lollms_paths.personal_models_path / f"{self.binding_folder_name}/{self.config.model_name}"), 'r') as f:
model_path = Path(f.read())
else:
model_path = Path(self.lollms_paths.personal_models_path / f"{self.binding_folder_name}/{self.config.model_name}")
return model_path
def load_binding_config(self):
"""
Load the content of local_config.yaml file.
@ -235,9 +116,24 @@ class LLMBinding:
Returns:
dict: A dictionary containing the loaded data from the local_config.yaml file.
"""
with open(path, 'r') as file:
data = yaml.safe_load(file)
return data
self.binding_config.config.load_config()
self.binding_config.sync()
def save_config_file(self, path):
"""
Load the content of local_config.yaml file.
The function reads the content of the local_config.yaml file and returns it as a Python dictionary.
Args:
None
Returns:
dict: A dictionary containing the loaded data from the local_config.yaml file.
"""
self.binding_config.config.save_config(self.configuration_file_path)
def generate(self,

@ -1 +1 @@
Subproject commit fb0b73a12f02ba033c6860f7bb8f1cc7c4fba083
Subproject commit 854400ff86978e212dbb768d5ff575a4521f2272

466
lollms/config.py Normal file
View File

@ -0,0 +1,466 @@
from pathlib import Path
import yaml
class BaseConfig:
"""
A base class for managing configuration data.
The `BaseConfig` class provides basic functionality to load, save, and access configuration data.
Attributes:
exceptional_keys (list): A list of exceptional keys that can be accessed directly as attributes.
config (dict): The configuration data stored as a dictionary.
Methods:
to_dict():
Returns the configuration data as a dictionary.
__getitem__(key):
Retrieves the configuration value associated with the specified key.
__getattr__(key):
Retrieves the configuration value associated with the specified key as an attribute.
__setattr__(key, value):
Sets the value of the configuration key.
__setitem__(key, value):
Sets the value of the configuration key.
__contains__(item):
Checks if the configuration contains the specified key.
load_config(file_path):
Loads the configuration from a YAML file.
save_config(file_path):
Saves the configuration to a YAML file.
"""
def __init__(self, exceptional_keys: list = [], config: dict = None, file_path:Path|str=None):
"""
Initializes a new instance of the `BaseConfig` class.
Args:
exceptional_keys (list, optional): A list of exceptional keys that can be accessed directly as attributes.
Defaults to an empty list.
config (dict, optional): The configuration data stored as a dictionary. Defaults to None.
"""
self.exceptional_keys = exceptional_keys
self.config = config
self.file_path = file_path
def to_dict(self):
"""
Returns the configuration data as a dictionary.
Returns:
dict: The configuration data as a dictionary.
"""
return self.config
def __getitem__(self, key):
"""
Retrieves the configuration value associated with the specified key.
Args:
key (Any): The key to retrieve the configuration value.
Returns:
Any: The configuration value associated with the key.
Raises:
ValueError: If no configuration is loaded.
KeyError: If the specified key is not found in the configuration.
"""
if self.config is None:
raise ValueError("No configuration loaded.")
return self.config[key]
def __getattr__(self, key):
"""
Retrieves the configuration value associated with the specified key as an attribute.
Args:
key (str): The key to retrieve the configuration value.
Returns:
Any: The configuration value associated with the key.
Raises:
ValueError: If no configuration is loaded.
AttributeError: If the specified key is not found in the configuration.
"""
if key == "exceptional_keys":
return super().__getattribute__(key)
if key in self.exceptional_keys + ["config","file_path"] or key.startswith("__"):
return super().__getattribute__(key)
else:
if self.config is None:
raise ValueError("No configuration loaded.")
return self.config[key]
def __setattr__(self, key, value):
"""
Sets the value of the configuration key.
Args:
key (str): The key of the configuration.
value (Any): The new value for the configuration key.
Raises:
ValueError: If no configuration is loaded.
"""
if key == "exceptional_keys":
return super().__setattr__(key, value)
if key in self.exceptional_keys + ["config","file_path"] or key.startswith("__"):
super().__setattr__(key, value)
else:
if self.config is None:
raise ValueError("No configuration loaded.")
self.config[key] = value
def __setitem__(self, key, value):
"""
Sets the value of the configuration key.
Args:
key (str): The key of the configuration.
value (Any): The new value for the configuration key.
Raises:
ValueError: If no configuration is loaded.
"""
if self.config is None:
raise ValueError("No configuration loaded.")
self.config[key] = value
def __contains__(self, item):
"""
Checks if the configuration contains the specified key.
Args:
item (str): The key to check.
Returns:
bool: True if the key is present in the configuration, False otherwise.
Raises:
ValueError: If no configuration is loaded.
"""
if self.config is None:
raise ValueError("No configuration loaded.")
return item in self.config
def load_config(self, file_path: Path | str = None):
"""
Loads the configuration from a YAML file.
Args:
file_path (str or Path, optional): The path to the YAML file. If not provided, uses the previously set file path.
Raises:
ValueError: If no configuration file path is specified.
FileNotFoundError: If the specified file path does not exist.
yaml.YAMLError: If there is an error parsing the YAML file.
"""
if file_path is None:
if self.file_path is None:
raise ValueError("No configuration file path specified.")
file_path = self.file_path
file_path = Path(file_path)
if not file_path.exists():
raise FileNotFoundError(f"Configuration file not found: {file_path}")
with open(file_path, 'r', encoding='utf-8') as stream:
self.config = yaml.safe_load(stream)
def save_config(self, file_path=None):
"""
Saves the configuration to a YAML file.
Args:
file_path (str or Path, optional): The path to the YAML file. If not provided, uses the previously set file path.
Raises:
ValueError: If no configuration is loaded.
ValueError: If no configuration file path is specified.
PermissionError: If the user does not have permission to write to the specified file path.
yaml.YAMLError: If there is an error serializing the configuration to YAML.
"""
if file_path is None:
if self.file_path is None:
raise ValueError("No configuration file path specified.")
file_path = self.file_path
if self.config is None:
raise ValueError("No configuration loaded.")
file_path = Path(file_path)
with open(file_path, "w") as f:
yaml.dump(self.config, f)
class ConfigTemplate:
"""
A class representing a configuration template.
The `ConfigTemplate` class provides functionality to define and manage configuration entries in the form of a template.
Attributes:
template (list): A list of dictionaries representing configuration entries.
Methods:
add_entry(entry_name, entry_value, entry_type, entry_min=None, entry_max=None):
Adds a new entry to the configuration template.
__getitem__(key):
Retrieves the configuration entry with the specified key.
__getattr__(key):
Retrieves the configuration entry with the specified key as an attribute.
__setattr__(key, value):
Sets the value of the configuration entry with the specified key.
__setitem__(key, value):
Sets the value of the configuration entry with the specified key.
__contains__(item):
Checks if a configuration entry with the specified name exists in the template.
"""
def __init__(self, template: list = None) -> None:
"""
Initializes a new instance of the `ConfigTemplate` class.
Args:
template (list, optional): A list of dictionaries representing configuration entries. Defaults to an empty list.
Raises:
ValueError: If the `template` parameter is not a list of dictionaries or if any entry is missing required fields.
"""
if template is None:
template = []
elif not isinstance(template, list):
raise ValueError("Template must be a list of dictionaries.")
else:
for entry in template:
if not isinstance(entry, dict):
raise ValueError("Each entry in the template must be a dictionary.")
required_fields = ["name", "value", "type"]
missing_fields = [field for field in required_fields if field not in entry]
if missing_fields:
raise ValueError(f"Missing fields {', '.join(missing_fields)} in template entry.")
self.template = template
def add_entry(self, entry_name, entry_value, entry_type, entry_min=None, entry_max=None, entry_help=""):
"""
Adds a new entry to the configuration template.
Args:
entry_name (str): The name of the configuration entry.
entry_value (Any): The value of the configuration entry.
entry_type (str): The type of the configuration entry.
entry_min (Any, optional): The minimum allowed value for the configuration entry. Defaults to None.
entry_max (Any, optional): The maximum allowed value for the configuration entry. Defaults to None.
entry_help (str, optional): the help string to describe the entry
"""
self.template.append({
"name": entry_name,
"value": entry_value,
"type": entry_type,
"min": entry_min,
"max": entry_max,
"help": entry_help
})
def __getitem__(self, key):
"""
Retrieves the configuration entry with the specified key.
Args:
key (str): The name of the configuration entry.
Returns:
dict: The configuration entry with the specified key, or None if not found.
Raises:
ValueError: If no configuration is loaded.
"""
if self.template is None:
raise ValueError("No configuration loaded.")
for entry in self.template:
if entry["name"] == key:
return entry
return None
def __getattr__(self, key):
"""
Retrieves the configuration entry with the specified key as an attribute.
Args:
key (str): The name of the configuration entry.
Returns:
dict: The configuration entry with the specified key, or None if not found.
Raises:
ValueError: If no configuration is loaded.
"""
if key == "exceptional_keys":
return super().__getattribute__(key)
if key in ["template"] or key.startswith("__"):
return super().__getattribute__(key)
else:
if self.template is None:
raise ValueError("No configuration loaded.")
for entry in self.template:
if entry["name"] == key:
return entry
return None
def __setattr__(self, key, value):
"""
Sets the value of the configuration entry with the specified key.
Args:
key (str): The name of the configuration entry.
value (Any): The new value for the configuration entry.
Raises:
ValueError: If no configuration is loaded or if the specified key is not found.
"""
if key == "exceptional_keys":
return super().__setattr__(key, value)
if key in ["template"] or key.startswith("__"):
super().__setattr__(key, value)
else:
if self.template is None:
raise ValueError("No configuration loaded.")
for entry in self.template:
if entry["name"] == key:
entry["value"] = value
return
raise ValueError(f"Configuration entry '{key}' not found.")
def __setitem__(self, key, value):
"""
Sets the value of the configuration entry with the specified key.
Args:
key (str): The name of the configuration entry.
value (Any): The new value for the configuration entry.
Raises:
ValueError: If no configuration is loaded or if the specified key is not found.
"""
if self.template is None:
raise ValueError("No configuration loaded.")
for entry in self.template:
if entry["name"] == key:
entry["value"] = value
return
raise ValueError(f"Configuration entry '{key}' not found.")
def __contains__(self, item):
"""
Checks if a configuration entry with the specified name exists in the template.
Args:
item (str): The name of the configuration entry.
Returns:
bool: True if the configuration entry exists, False otherwise.
Raises:
ValueError: If no configuration is loaded.
"""
if self.template is None:
raise ValueError("No configuration loaded.")
for entry in self.template:
if entry["name"] == item:
return True
return False
class TypedConfig:
"""
This type of configuration contains a template of descriptions for the fields of the configuration.
Field types: int, float, str.
"""
def __init__(self, config_template: ConfigTemplate, config: BaseConfig):
"""
Initializes a new instance of the `TypedConfig` class.
Args:
config_template (ConfigTemplate): The template of descriptions for the fields of the configuration.
config (BaseConfig): The base configuration object containing the configuration values.
"""
self.config = config
self.config_template = config_template
# Fill the template values from the config values
self.sync()
def sync(self):
"""
Fills the template values from the config values.
"""
if self.config_template is None:
raise ValueError("No configuration template loaded.")
if self.config is None:
raise ValueError("No configuration loaded.")
for entry in self.config_template.template:
entry_name = entry["name"]
if entry_name in self.config:
entry_value = self.config[entry_name]
entry_type = entry["type"]
# Validate and convert the entry value based on its type
if entry_type == "int":
entry_value = int(entry_value)
elif entry_type == "float":
entry_value = float(entry_value)
elif entry_type == "str":
entry_value = str(entry_value)
elif entry_type == "bool":
entry_value = bool(entry_value)
elif entry_type == "list":
entry_value = list(entry_value)
else:
raise ValueError(f"Invalid field type '{entry_type}' for entry '{entry_name}'.")
# Skip checking min and max if the entry type is not numeric
if entry_type == "int" or entry_type == "float":
entry_min = entry.get("min")
entry_max = entry.get("max")
# Check if the value is within the allowed range (if specified)
if entry_min is not None and entry_max is not None:
if entry_value < entry_min:
entry_value = entry_min
elif entry_value > entry_max:
entry_value = entry_max
elif entry_min is not None:
if entry_value < entry_min:
entry_value = entry_min
elif entry_max is not None:
if entry_value > entry_max:
entry_value = entry_max
# Update the template entry with the converted value
entry["value"] = entry_value
else:
self.config[entry_name] = entry["value"]
def set_config(self, config: BaseConfig):
"""
Sets the configuration and updates the values of the template.
Args:
config (BaseConfig): The base configuration object containing the configuration values.
"""
self.config = config
self.sync()
def to_dict(self, use_template=False):
if not use_template:
return self.config
else:
return self.config_template

View File

@ -1,9 +1,8 @@
from lollms.personality import AIPersonality, MSG_TYPE
from lollms.binding import LOLLMSConfig, LLMBinding
from lollms.personality import MSG_TYPE
from lollms.main_config import LOLLMSConfig
from lollms.helpers import ASCIIColors
from lollms.paths import LollmsPaths
from lollms import reset_all_installs
import shutil
import yaml
from pathlib import Path
import sys
@ -14,9 +13,9 @@ from lollms import BindingBuilder, ModelBuilder, PersonalityBuilder
class MainMenu:
def __init__(self, conversation):
def __init__(self, lollms_app):
self.binding_infs = []
self.conversation = conversation
self.lollms_app = lollms_app
def show_logo(self):
print(f"{ASCIIColors.color_bright_yellow}")
@ -53,9 +52,9 @@ class MainMenu:
print(f" {ASCIIColors.color_red}{ASCIIColors.color_reset} send_file: uploads a file to the AI")
print(f" {ASCIIColors.color_red}{ASCIIColors.color_reset} exit: exists the console")
if self.conversation.personality.help !="":
if self.lollms_app.personality.help !="":
print(f"Personality help:")
print(f"{self.conversation.personality.help}")
print(f"{self.lollms_app.personality.help}")
@ -69,28 +68,31 @@ class MainMenu:
def select_binding(self):
bindings_list = []
print()
print(f"{ASCIIColors.color_green}Current binding: {ASCIIColors.color_reset}{self.conversation.config['binding_name']}")
for p in self.conversation.lollms_paths.bindings_zoo_path.iterdir():
print(f"{ASCIIColors.color_green}Current binding: {ASCIIColors.color_reset}{self.lollms_app.config['binding_name']}")
for p in self.lollms_app.lollms_paths.bindings_zoo_path.iterdir():
if p.is_dir():
with open(p/"binding_card.yaml", "r") as f:
card = yaml.safe_load(f)
with open(p/"models.yaml", "r") as f:
models = yaml.safe_load(f)
entry=f"{card['name']} (by {card['author']})"
is_installed = (self.lollms_app.lollms_paths.personal_configuration_path/f"binding_{p.name}.yaml").exists()
entry=f"{ASCIIColors.color_green if is_installed else ''}{card['name']} (by {card['author']})"
bindings_list.append(entry)
entry={
"name":p.name,
"card":card,
"models":models
"models":models,
"installed": is_installed
}
self.binding_infs.append(entry)
bindings_list += ["Back"]
choice = self.show_menu(bindings_list)
if 1 <= choice <= len(bindings_list)-1:
print(f"You selected binding: {ASCIIColors.color_green}{self.binding_infs[choice - 1]['name']}{ASCIIColors.color_reset}")
self.conversation.config['binding_name']=self.binding_infs[choice - 1]['name']
self.conversation.load_binding()
self.conversation.config.save_config()
self.lollms_app.config['binding_name']=self.binding_infs[choice - 1]['name']
self.lollms_app.load_binding()
self.lollms_app.config['model_name']=None
self.lollms_app.config.save_config()
elif choice <= len(bindings_list):
return
else:
@ -98,16 +100,19 @@ class MainMenu:
def select_model(self):
print()
print(f"{ASCIIColors.color_green}Current model: {ASCIIColors.color_reset}{self.conversation.config['model_name']}")
models_dir:Path = (self.conversation.lollms_paths.personal_models_path/self.conversation.config['binding_name'])
print(f"{ASCIIColors.color_green}Current model: {ASCIIColors.color_reset}{self.lollms_app.config['model_name']}")
models_dir:Path = (self.lollms_app.lollms_paths.personal_models_path/self.lollms_app.config['binding_name'])
models_dir.mkdir(parents=True, exist_ok=True)
models_list = [m.name for m in models_dir.iterdir() if m.name.lower() not in [".ds_dtore","thumb.db"]] + ["Install model", "Change binding", "Back"]
if hasattr(self.lollms_app,"binding") and hasattr(self.lollms_app.binding,"list_models"):
models_list = [f'{v["filename"]} (by {v["owner"]})' for v in self.lollms_app.binding.list_models(self.lollms_app.config)] + ["Install model", "Change binding", "Back"]
else:
models_list = [m.name for m in models_dir.iterdir() if m.name.lower() not in [".ds_dtore","thumb.db"]] + ["Install model", "Change binding", "Back"]
choice = self.show_menu(models_list)
if 1 <= choice <= len(models_list)-3:
print(f"You selected model: {ASCIIColors.color_green}{models_list[choice - 1]}{ASCIIColors.color_reset}")
self.conversation.config['model_name']=models_list[choice - 1]
self.conversation.config.save_config()
self.conversation.load_model()
self.lollms_app.config['model_name']=models_list[choice - 1]
self.lollms_app.config.save_config()
self.lollms_app.load_model()
elif choice <= len(models_list)-2:
self.install_model()
elif choice <= len(models_list)-1:
@ -129,12 +134,12 @@ class MainMenu:
# Usage example
with tqdm(total=100, unit="%", desc="Download Progress", ncols=80) as tqdm_bar:
self.conversation.config.download_model(url,self.conversation.binding, progress_callback)
self.lollms_app.config.download_model(url,self.lollms_app.binding, progress_callback)
self.select_model()
elif choice <= len(models_list)-1:
path = Path(input("Give a path to the model to be used on your PC:"))
if path.exists():
self.conversation.config.reference_model(path)
self.lollms_app.config.reference_model(path)
self.select_model()
elif choice <= len(models_list):
return
@ -143,31 +148,31 @@ class MainMenu:
def select_personality(self):
print()
print(f"{ASCIIColors.color_green}Current personality: {ASCIIColors.color_reset}{self.conversation.config['personalities'][self.conversation.config['active_personality_id']]}")
personality_languages = [p.stem for p in self.conversation.lollms_paths.personalities_zoo_path.iterdir() if p.is_dir()] + ["Back"]
print(f"{ASCIIColors.color_green}Current personality: {ASCIIColors.color_reset}{self.lollms_app.config['personalities'][self.lollms_app.config['active_personality_id']]}")
personality_languages = [p.stem for p in self.lollms_app.lollms_paths.personalities_zoo_path.iterdir() if p.is_dir()] + ["Back"]
print("Select language")
choice = self.show_menu(personality_languages)
if 1 <= choice <= len(personality_languages)-1:
language = personality_languages[choice - 1]
print(f"You selected language: {ASCIIColors.color_green}{language}{ASCIIColors.color_reset}")
personality_categories = [p.stem for p in (self.conversation.lollms_paths.personalities_zoo_path/language).iterdir() if p.is_dir()]+["Custom","Back"]
personality_categories = [p.stem for p in (self.lollms_app.lollms_paths.personalities_zoo_path/language).iterdir() if p.is_dir()]+["Custom","Back"]
print("Select category")
choice = self.show_menu(personality_categories)
if 1 <= choice <= len(personality_categories)-1:
category = personality_categories[choice - 1]
print(f"You selected category: {ASCIIColors.color_green}{category}{ASCIIColors.color_reset}")
if category=="Custom":
personality_names = [p.stem for p in self.conversation.lollms_paths.personal_personalities_path.iterdir() if p.is_dir()]+["Back"]
personality_names = [p.stem for p in self.lollms_app.lollms_paths.personal_personalities_path.iterdir() if p.is_dir()]+["Back"]
else:
personality_names = [p.stem for p in (self.conversation.lollms_paths.personalities_zoo_path/language/category).iterdir() if p.is_dir()]+["Back"]
personality_names = [p.stem for p in (self.lollms_app.lollms_paths.personalities_zoo_path/language/category).iterdir() if p.is_dir()]+["Back"]
print("Select personality")
choice = self.show_menu(personality_names)
if 1 <= choice <= len(personality_names)-1:
name = personality_names[choice - 1]
print(f"You selected personality: {ASCIIColors.color_green}{name}{ASCIIColors.color_reset}")
self.conversation.config["personalities"]=[f"{language}/{category}/{name}"]
self.conversation.load_personality()
self.conversation.config.save_config()
self.lollms_app.config["personalities"]=[f"{language}/{category}/{name}"]
self.lollms_app.load_personality()
self.lollms_app.config.save_config()
print("Personality saved successfully!")
elif 1 <= choice <= len(personality_names):
return
@ -183,22 +188,22 @@ class MainMenu:
print("Invalid choice!")
def reinstall_binding(self):
conversation = self.conversation
lollms_app = self.lollms_app
try:
conversation.binding = BindingBuilder().build_binding(conversation.lollms_paths.bindings_zoo_path, conversation.config, force_reinstall=True)
lollms_app.binding = BindingBuilder().build_binding(lollms_app.lollms_paths.bindings_zoo_path, lollms_app.config, force_reinstall=True)
except Exception as ex:
print(ex)
print(f"Couldn't find binding. Please verify your configuration file at {conversation.config.file_path} or use the next menu to select a valid binding")
print(f"Couldn't find binding. Please verify your configuration file at {lollms_app.config.file_path} or use the next menu to select a valid binding")
self.select_binding()
def reinstall_personality(self):
conversation = self.conversation
lollms_app = self.lollms_app
try:
conversation.personality = PersonalityBuilder(conversation.lollms_paths, conversation.config, conversation.model).build_personality(force_reinstall=True)
lollms_app.personality = PersonalityBuilder(lollms_app.lollms_paths, lollms_app.config, lollms_app.model).build_personality(force_reinstall=True)
except Exception as ex:
ASCIIColors.error(f"Couldn't load personality. Please verify your configuration file at {conversation.configuration_path} or use the next menu to select a valid personality")
ASCIIColors.error(f"Couldn't load personality. Please verify your configuration file at {lollms_app.configuration_path} or use the next menu to select a valid personality")
ASCIIColors.error(f"Binding returned this exception : {ex}")
ASCIIColors.error(f"{conversation.config.get_personality_path_infos()}")
ASCIIColors.error(f"{lollms_app.config.get_personality_path_infos()}")
print("Please select a valid model or install a new one from a url")
self.select_model()
@ -595,8 +600,8 @@ def main():
configuration_path = args.configuration_path
conversation = Conversation(configuration_path=configuration_path, show_commands_list=True)
conversation.start_conversation()
lollms_app = Conversation(configuration_path=configuration_path, show_commands_list=True)
lollms_app.start_conversation()
if __name__ == "__main__":

View File

@ -1,6 +1,3 @@
from pathlib import Path
import yaml
class ASCIIColors:
@ -74,61 +71,3 @@ class ASCIIColors:
class BaseConfig():
def __init__(self, exceptional_keys=[], config = None):
self.exceptional_keys = exceptional_keys
self.config = config
def to_dict(self):
return self.config
def __getitem__(self, key):
if self.config is None:
raise ValueError("No configuration loaded.")
return self.config[key]
def __getattr__(self, key):
if key == "exceptional_keys":
return super().__getattribute__(key)
if key in self.exceptional_keys+ ["config"] or key.startswith("__"):
return super().__getattribute__(key)
else:
if self.config is None:
raise ValueError("No configuration loaded.")
return self.config[key]
def __setattr__(self, key, value):
if key == "exceptional_keys":
return super().__setattr__(key, value)
if key in self.exceptional_keys+ ["config"] or key.startswith("__"):
super().__setattr__(key, value)
else:
if self.config is None:
raise ValueError("No configuration loaded.")
self.config[key] = value
def __setitem__(self, key, value):
if self.config is None:
raise ValueError("No configuration loaded.")
self.config[key] = value
def __contains__(self, item):
if self.config is None:
raise ValueError("No configuration loaded.")
return item in self.config
def load_config(self, file_path:Path=None):
if file_path is None:
file_path = self.file_path
with open(file_path, 'r', encoding='utf-8') as stream:
self.config = yaml.safe_load(stream)
def save_config(self, file_path:Path=None):
if file_path is None:
file_path = self.file_path
if self.config is None:
raise ValueError("No configuration loaded.")
with open(file_path, "w") as f:
yaml.dump(self.config, f)

184
lollms/main_config.py Normal file
View File

@ -0,0 +1,184 @@
__author__ = "ParisNeo"
__github__ = "https://github.com/ParisNeo/lollms"
__copyright__ = "Copyright 2023, "
__license__ = "Apache 2.0"
from lollms.helpers import ASCIIColors
from lollms.paths import LollmsPaths
from lollms.config import BaseConfig
#from lollms.binding import LLMBinding
import shutil
import urllib
from tqdm import tqdm
from pathlib import Path
import sys
DEFAULT_CONFIG = {
# =================== Lord Of Large Language Models Configuration file ===========================
"version": 5,
"binding_name": "llama_cpp_official",
"model_name": "Wizard-Vicuna-7B-Uncensored.ggmlv3.q4_0.bin",
# Host information
"host": "localhost",
"port": 9600,
# Genreration parameters
"seed": -1,
"n_predict": 1024,
"ctx_size": 2048,
"temperature": 0.9,
"top_k": 50,
"top_p": 0.95,
"repeat_last_n": 40,
"repeat_penalty": 1.2,
"n_threads": 8,
#Personality parameters
"personalities": ["english/generic/lollms"],
"active_personality_id": 0,
"override_personality_model_parameters": False, #if true the personality parameters are overriden by those of the configuration (may affect personality behaviour)
"user_name": "user",
}
class LOLLMSConfig(BaseConfig):
def __init__(self, file_path=None, lollms_paths:LollmsPaths = None):
super().__init__(["file_path", "config", "lollms_paths"])
if file_path:
self.file_path = Path(file_path)
else:
self.file_path = None
if file_path is not None:
self.load_config(file_path)
else:
self.config = DEFAULT_CONFIG.copy()
if lollms_paths is None:
self.lollms_paths = LollmsPaths()
else:
self.lollms_paths = lollms_paths
@staticmethod
def autoload(lollms_paths, config_path:str=None):
# Configuration loading part
original_cfg_path = lollms_paths.default_cfg_path
if config_path is None:
local = lollms_paths.personal_configuration_path / "local_config.yaml"
if not local.exists():
shutil.copy(original_cfg_path, local)
cfg_path = local
else:
cfg_path = config_path
if cfg_path.exists():
original_config = LOLLMSConfig(original_cfg_path, lollms_paths)
config = LOLLMSConfig(cfg_path, lollms_paths)
if "version" not in config or int(config["version"])<int(original_config["version"]):
#Upgrade old configuration files to new format
ASCIIColors.error("Configuration file is very old.\nReplacing with default configuration")
_, added, removed = config.sync_cfg(original_config)
print(f"Added entries : {added}, removed entries:{removed}")
config.save_config(cfg_path)
else:
config = LOLLMSConfig(lollms_paths=lollms_paths)
return config
def sync_cfg(self, default_config):
"""Syncs a configuration with the default configuration
Args:
default_config (_type_): _description_
config (_type_): _description_
Returns:
_type_: _description_
"""
added_entries = []
removed_entries = []
# Ensure all fields from default_config exist in config
for key, value in default_config.config.items():
if key not in self:
self[key] = value
added_entries.append(key)
# Remove fields from config that don't exist in default_config
for key in list(self.config.keys()):
if key not in default_config.config:
del self.config[key]
removed_entries.append(key)
self["version"]=default_config["version"]
return self, added_entries, removed_entries
def get_model_path_infos(self):
return f"personal_models_path: {self.lollms_paths.personal_models_path}\nBinding name:{self.binding_name}\nModel name:{self.model_name}"
def get_personality_path_infos(self):
return f"personalities_zoo_path: {self.lollms_paths.personalities_zoo_path}\nPersonalities:{self.personalities}\nActive personality id:{self.active_personality_id}"
def get_model_full_path(self):
try:
return self.lollms_paths.personal_models_path/self.binding_name/self.model_name
except:
return None
def check_model_existance(self):
try:
model_path = self.lollms_paths.personal_models_path/self.binding_name/self.model_name
return model_path.exists()
except Exception as ex:
print(f"Exception in checking model existance: {ex}")
return False
def download_model(self, url, binding, callback = None):
folder_path = self.lollms_paths.personal_models_path/self.binding_name
model_name = url.split("/")[-1]
model_full_path = (folder_path / model_name)
if binding is not None and hasattr(binding,'download_model'):
binding.download_model(url, model_full_path, callback)
else:
# Check if file already exists in folder
if model_full_path.exists():
print("File already exists in folder")
else:
# Create folder if it doesn't exist
folder_path.mkdir(parents=True, exist_ok=True)
progress_bar = tqdm(total=None, unit="B", unit_scale=True, desc=f"Downloading {url.split('/')[-1]}")
# Define callback function for urlretrieve
def report_progress(block_num, block_size, total_size):
progress_bar.total=total_size
progress_bar.update(block_size)
# Download file from URL to folder
try:
urllib.request.urlretrieve(url, folder_path / url.split("/")[-1], reporthook=report_progress if callback is None else callback)
print("File downloaded successfully!")
except Exception as e:
print("Error downloading file:", e)
sys.exit(1)
def reference_model(self, path):
path = str(path).replace("\\","/")
folder_path = self.lollms_paths.personal_models_path/self.binding_name
model_name = path.split("/")[-1]+".reference"
model_full_path = (folder_path / model_name)
# Check if file already exists in folder
if model_full_path.exists():
print("File already exists in folder")
else:
# Create folder if it doesn't exist
folder_path.mkdir(parents=True, exist_ok=True)
with open(model_full_path,"w") as f:
f.write(path)
print("Reference created, please make sure you don't delete the file or you will have broken link")

View File

@ -1,7 +1,7 @@
from pathlib import Path
import shutil
from lollms.helpers import ASCIIColors
from lollms.helpers import BaseConfig
from lollms.config import BaseConfig
lollms_path = Path(__file__).parent
lollms_default_cfg_path = lollms_path / "configs/config.yaml"
@ -81,15 +81,14 @@ class LollmsPaths:
# if the app is not forcing a specific path, then try to find out if the default installed library has specified a default path
global_paths_cfg = lollms_path/"global_paths_cfg.yaml"
if global_paths_cfg.exists():
cfg = BaseConfig()
cfg.load_config(global_paths_cfg)
try:
cfg = BaseConfig()
cfg.load_config(global_paths_cfg)
lollms_path = cfg.lollms_path
lollms_personal_path = cfg.lollms_personal_path
return LollmsPaths(lollms_path, lollms_personal_path, custom_default_cfg_path=custom_default_cfg_path)
except Exception as ex:
print(f"{ASCIIColors.color_red}Global paths configuration file found but seems to be corrupted{ASCIIColors.color_reset}")
print("Couldn't find your personal data path!")
cfg.lollms_path = Path(__file__).parent
cfg.lollms_personal_path = input("Please specify the folder where your configuration files, your models and your custom personalities need to be stored:")
cfg.save_config(global_paths_cfg)

@ -1 +1 @@
Subproject commit 02543f5b23ad31cd587dffd4b8b8f2623a7ea3a2
Subproject commit cc16d94f2ebaee7736891cf507ca5cc0026d58d6

View File

@ -1,8 +1,9 @@
from flask import Flask, render_template, request
from flask import Flask, request
from flask_socketio import SocketIO, emit
from flask_cors import CORS
from lollms.personality import AIPersonality, MSG_TYPE
from lollms.binding import LOLLMSConfig, LLMBinding
from lollms.main_config import LOLLMSConfig
from lollms.binding import LLMBinding
from lollms.helpers import ASCIIColors
from lollms.console import MainMenu
from lollms.paths import LollmsPaths
@ -14,10 +15,8 @@ import importlib
from pathlib import Path
import argparse
import logging
import shutil
import yaml
import copy
from threading import Thread
class LoLLMsServer:
def __init__(self):
@ -112,7 +111,7 @@ class LoLLMsServer:
self.menu.show_logo()
self.app = Flask("LoLLMsServer_Server")
self.app = Flask("LoLLMsServer")
#self.app.config['SECRET_KEY'] = 'lollmssecret'
CORS(self.app) # Enable CORS for all routes
@ -547,16 +546,6 @@ class LoLLMsServer:
def build_binding(self, bindings_path: Path, cfg: LOLLMSConfig)->LLMBinding:
binding_path = Path(bindings_path) / cfg["binding_name"]
# first find out if there is a requirements.txt file
install_file_name = "install.py"
install_script_path = binding_path / install_file_name
if install_script_path.exists():
module_name = install_file_name[:-3] # Remove the ".py" extension
module_spec = importlib.util.spec_from_file_location(module_name, str(install_script_path))
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
if hasattr(module, "Install"):
module.Install(self.config)
# define the full absolute path to the module
absolute_path = binding_path.resolve()
# infer the module name from the file path

View File

@ -1,14 +1,10 @@
from lollms.personality import AIPersonality, MSG_TYPE
from lollms.binding import LOLLMSConfig, LLMBinding
from lollms.main_config import LOLLMSConfig
from lollms.helpers import ASCIIColors
from lollms.paths import LollmsPaths
from lollms import reset_all_installs
import shutil
import yaml
import importlib
from pathlib import Path
import sys
import pkg_resources
import argparse
from tqdm import tqdm
from lollms import BindingBuilder, ModelBuilder, PersonalityBuilder