From 25334549ec740f5ad332160a8738f3d63c477c50 Mon Sep 17 00:00:00 2001 From: Saifeddine ALOUI Date: Wed, 10 Jan 2024 21:21:53 +0100 Subject: [PATCH] Added new services --- lollms/app.py | 29 +++--- lollms/personality.py | 69 +++++++++++-- lollms/server/elf_server.py | 1 + .../endpoints/lollms_personalities_infos.py | 10 +- .../events/lollms_personality_events.py | 6 +- lollms/services/ollama/install.sh | 68 +++++++++++++ lollms/services/ollama/lollms_ollama.py | 98 +++++++++++++++++++ lollms/services/ollama/run_ollama.sh | 2 + .../sd}/lollms_sd.py | 2 +- .../xtts}/lollms_xtts.py | 2 +- setup.py | 4 +- 11 files changed, 254 insertions(+), 37 deletions(-) create mode 100644 lollms/services/ollama/install.sh create mode 100644 lollms/services/ollama/lollms_ollama.py create mode 100644 lollms/services/ollama/run_ollama.sh rename lollms/{image_gen_modules => services/sd}/lollms_sd.py (99%) rename lollms/{audio_gen_modules => services/xtts}/lollms_xtts.py (98%) diff --git a/lollms/app.py b/lollms/app.py index 631a616..989f127 100644 --- a/lollms/app.py +++ b/lollms/app.py @@ -1,6 +1,6 @@ from lollms.main_config import LOLLMSConfig from lollms.paths import LollmsPaths -from lollms.personality import PersonalityBuilder +from lollms.personality import PersonalityBuilder, AIPersonality from lollms.binding import LLMBinding, BindingBuilder, ModelBuilder from lollms.extension import LOLLMSExtension, ExtensionBuilder from lollms.config import InstallOption @@ -39,31 +39,32 @@ class LollmsApplication(LoLLMsCom): Creates a LOLLMS Application """ super().__init__(socketio) - self.app_name = app_name - self.config = config - self.lollms_paths = lollms_paths + self.app_name = app_name + self.config = config + self.lollms_paths = lollms_paths - self.menu = MainMenu(self, callback) - self.mounted_personalities = [] - self.personality = None + self.menu = MainMenu(self, callback) + self.mounted_personalities = [] + self.personality:AIPersonality = None - self.mounted_extensions = [] - self.binding = None - self.model:LLMBinding = None - self.long_term_memory = None + self.mounted_extensions = [] + self.binding = None + self.model:LLMBinding = None + self.long_term_memory = None + + self.tts = None - self.tts = None if not free_mode: if self.config.enable_voice_service and load_voice_service: try: - from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS + from lollms.services.xtts.lollms_xtts import LollmsXTTS self.tts = LollmsXTTS(self, voice_samples_path=lollms_paths.custom_voices_path, xtts_base_url=self.config.xtts_base_url) except: self.warning(f"Couldn't load XTTS") if self.config.enable_sd_service and load_sd_service: try: - from lollms.image_gen_modules.lollms_sd import LollmsSD + from lollms.services.sd.lollms_sd import LollmsSD self.tts = LollmsSD(self, auto_sd_base_url=self.config.sd_base_url) except: self.warning(f"Couldn't load SD") diff --git a/lollms/personality.py b/lollms/personality.py index 6909862..a7aa03c 100644 --- a/lollms/personality.py +++ b/lollms/personality.py @@ -679,6 +679,12 @@ Date: {{date}} self._assets_list = contents return config + def settings_updated(self): + """ + To be implemented by the bindings when the settings have changed + """ + pass + def remove_file(self, path, callback=None): try: if path in self.text_files: @@ -1780,21 +1786,31 @@ class APScript(StateMachine): def generate(self, prompt, max_size, temperature = None, top_k = None, top_p=None, repeat_penalty=None, repeat_last_n=None, callback=None, debug=False ): return self.personality.generate(prompt, max_size, temperature, top_k, top_p, repeat_penalty, repeat_last_n, callback, debug=debug) - def run_workflow(self, prompt:str, previous_discussion_text:str="", callback: Callable[[str, MSG_TYPE, dict, list], bool]=None): + def run_workflow(self, prompt:str, previous_discussion_text:str="", callback: Callable[[str, MSG_TYPE, dict, list], bool]=None, context_details=None): """ - Runs the workflow for processing the model input and output. - - This method should be called to execute the processing workflow. + This function generates code based on the given parameters. Args: - generate_fn (function): A function that generates model output based on the input prompt. - The function should take a single argument (prompt) and return the generated text. - prompt (str): The input prompt for the model. - previous_discussion_text (str, optional): The text of the previous discussion. Default is an empty string. + full_prompt (str): The full prompt for code generation. + prompt (str): The prompt for code generation. + context_details (dict): A dictionary containing the following context details for code generation: + - conditionning (str): The conditioning information. + - documentation (str): The documentation information. + - knowledge (str): The knowledge information. + - user_description (str): The user description information. + - discussion_messages (str): The discussion messages information. + - positive_boost (str): The positive boost information. + - negative_boost (str): The negative boost information. + - force_language (str): The force language information. + - ai_prefix (str): The AI prefix information. + n_predict (int): The number of predictions to generate. + client_id: The client ID for code generation. + callback (function, optional): The callback function for code generation. Returns: None """ + return None @@ -1849,6 +1865,43 @@ class APScript(StateMachine): self.step_end(f"Processing chunk : {i+1}/{len(chunks)}") return "\n".join(summeries) + + def build_prompt(self, prompt_parts:List[str], sacrifice_id:int=-1, context_size:int=None, minimum_spare_context_size:int=None): + """ + Builds the prompt for code generation. + + Args: + prompt_parts (List[str]): A list of strings representing the parts of the prompt. + sacrifice_id (int, optional): The ID of the part to sacrifice. + context_size (int, optional): The size of the context. + minimum_spare_context_size (int, optional): The minimum spare context size. + + Returns: + str: The built prompt. + """ + if context_size is None: + context_size = self.personality.config.ctx_size + if minimum_spare_context_size is None: + minimum_spare_context_size = self.personality.config.min_n_predict + + if sacrifice_id == -1 or len(prompt_parts[sacrifice_id])<50: + return "\n".join([s for s in prompt_parts if s!=""]) + else: + part_tokens=[] + nb_tokens=0 + for i,part in enumerate(prompt_parts): + tk = self.personality.model.tokenize(part) + part_tokens.append(tk) + if i != sacrifice_id: + nb_tokens += len(tk) + if len(part_tokens[sacrifice_id])>0: + sacrifice_tk = part_tokens[sacrifice_id] + sacrifice_tk= sacrifice_tk[-(context_size-nb_tokens-minimum_spare_context_size):] + sacrifice_text = self.personality.model.detokenize(sacrifice_tk) + else: + sacrifice_text = "" + prompt_parts[sacrifice_id] = sacrifice_text + return "\n".join([s for s in prompt_parts if s!=""]) # ================================================= Sending commands to ui =========================================== def step_start(self, step_text, callback: Callable[[str, MSG_TYPE, dict, list], bool]=None): diff --git a/lollms/server/elf_server.py b/lollms/server/elf_server.py index bea05d0..ff91f00 100644 --- a/lollms/server/elf_server.py +++ b/lollms/server/elf_server.py @@ -9,6 +9,7 @@ This class provides a singleton instance of the LoLLMS web UI, allowing access t from lollms.app import LollmsApplication from lollms.main_config import LOLLMSConfig from lollms.paths import LollmsPaths +from lollms.personality import AIPersonality from pathlib import Path class LOLLMSElfServer(LollmsApplication): diff --git a/lollms/server/endpoints/lollms_personalities_infos.py b/lollms/server/endpoints/lollms_personalities_infos.py index 8c9ba3a..c32359e 100644 --- a/lollms/server/endpoints/lollms_personalities_infos.py +++ b/lollms/server/endpoints/lollms_personalities_infos.py @@ -464,7 +464,7 @@ def get_active_personality_settings(): else: return {} -@router.post("/get_active_personality_settings") +@router.post("/set_active_personality_settings") def set_active_personality_settings(data): print("- Setting personality settings") @@ -475,6 +475,7 @@ def set_active_personality_settings(data): if lollmsElfServer.config.auto_save: ASCIIColors.info("Saving configuration") lollmsElfServer.config.save_config() + lollmsElfServer.personality.settings_updated() return {'status':True} else: return {'status':False} @@ -492,10 +493,3 @@ def post_to_personality(data): else: return {} - -@router.get("/get_current_personality_files_list") -def get_current_personality_files_list(): - if lollmsElfServer.personality is None: - return {"state":False, "error":"No personality selected"} - return {"state":True, "files":[{"name":Path(f).name, "size":Path(f).stat().st_size} for f in lollmsElfServer.personality.text_files]+[{"name":Path(f).name, "size":Path(f).stat().st_size} for f in lollmsElfServer.personality.image_files]} - diff --git a/lollms/server/events/lollms_personality_events.py b/lollms/server/events/lollms_personality_events.py index f35438b..f3030ab 100644 --- a/lollms/server/events/lollms_personality_events.py +++ b/lollms/server/events/lollms_personality_events.py @@ -15,7 +15,7 @@ from fastapi.responses import FileResponse from lollms.binding import BindingBuilder, InstallOption from ascii_colors import ASCIIColors from lollms.personality import MSG_TYPE, AIPersonality -from lollms.utilities import load_config, trace_exception, gc, terminate_thread +from lollms.utilities import load_config, trace_exception, gc, terminate_thread, run_async from pathlib import Path from typing import List import socketio @@ -68,10 +68,10 @@ def add_events(sio:socketio): else: result = lollmsElfServer.personality.add_file(file_path, partial(lollmsElfServer.process_chunk, client_id=client_id)) - lollmsElfServer.run_async(partial(sio.emit,'file_received', {'status': True, 'filename': filename})) + run_async(partial(sio.emit,'file_received', {'status': True, 'filename': filename})) else: # Request the next chunk from the client - lollmsElfServer.run_async(partial(sio.emit,'request_next_chunk', {'offset': offset + len(chunk)})) + run_async(partial(sio.emit,'request_next_chunk', {'offset': offset + len(chunk)})) @sio.on('execute_command') def execute_command(sid, data): diff --git a/lollms/services/ollama/install.sh b/lollms/services/ollama/install.sh new file mode 100644 index 0000000..9313f2f --- /dev/null +++ b/lollms/services/ollama/install.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# This script installs Ollama on Linux. +# It detects the current operating system architecture and installs the appropriate version of Ollama. + +set -eu + +status() { echo ">>> $*" >&2; } +error() { echo "ERROR $*"; exit 1; } +warning() { echo "WARNING: $*"; } + +OLLAMA_DIR=~/ollama + +if [ ! -d $OLLAMA_DIR ]; then + mkdir $OLLAMA_DIR + echo "Folder $OLLAMA_DIR created successfully!" +else + echo "Folder $OLLAMA_DIR already exists." +fi + +available() { command -v $1 >/dev/null; } +require() { + local MISSING='' + for TOOL in $*; do + if ! available $TOOL; then + MISSING="$MISSING $TOOL" + fi + done + + echo $MISSING +} + +[ "$(uname -s)" = "Linux" ] || error 'This script is intended to run on Linux only.' + +ARCH=$(uname -m) +case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) error "Unsupported architecture: $ARCH" ;; +esac + +KERN=$(uname -r) +case "$KERN" in + *icrosoft*WSL2 | *icrosoft*wsl2) ;; + *icrosoft) error "Microsoft WSL1 is not currently supported. Please upgrade to WSL2 with 'wsl --set-version 2'" ;; + *) ;; +esac + + +NEEDS=$(require curl awk grep sed tee xargs) +if [ -n "$NEEDS" ]; then + status "ERROR: The following tools are required but missing:" + for NEED in $NEEDS; do + echo " - $NEED" + done + exit 1 +fi + +status "Downloading ollama..." +curl --fail --show-error --location --progress-bar -o $OLLAMA_DIR "https://ollama.ai/download/ollama-linux-$ARCH" + +status "Installing ollama to OLLAMA_DIR..." + +install_success() { + status 'The Ollama API is now available at 0.0.0.0:11434.' + status 'Install complete. Run "ollama" from the command line.' +} +trap install_success EXIT + diff --git a/lollms/services/ollama/lollms_ollama.py b/lollms/services/ollama/lollms_ollama.py new file mode 100644 index 0000000..09fcdea --- /dev/null +++ b/lollms/services/ollama/lollms_ollama.py @@ -0,0 +1,98 @@ +# Title Ollama service +# Licence: MIT +# Author : Paris Neo +# This is a service launcher for the ollama server by Jeffrey Morgan (jmorganca) +# check it out : https://github.com/jmorganca/ollama +# Here is a copy of the LICENCE https://github.com/jmorganca/ollama/blob/main/LICENSE +# All rights are reserved + +from pathlib import Path +import os +import sys +from lollms.app import LollmsApplication +from lollms.paths import LollmsPaths +from lollms.config import TypedConfig, ConfigTemplate, BaseConfig +import time +import io +import sys +import requests +import os +import base64 +import subprocess +import time +import json +import platform +from dataclasses import dataclass +from PIL import Image, PngImagePlugin +from enum import Enum +from typing import List, Dict, Any + +from ascii_colors import ASCIIColors, trace_exception +from lollms.paths import LollmsPaths +from lollms.utilities import git_pull +import subprocess +import platform + + +def verify_ollama(lollms_paths:LollmsPaths): + # Clone repository + + root_dir = lollms_paths.personal_path + shared_folder = root_dir/"shared" + sd_folder = shared_folder / "auto_sd" + return sd_folder.exists() + +def install_ollama(): + if platform.system() == 'Windows': + if os.path.exists('C:\\Windows\\System32\\wsl.exe'): + subprocess.run(['wsl', 'bash', str(Path(__file__).parent / 'install.sh')]) + else: + subprocess.run(['wsl', '--install', 'Ubuntu']) + subprocess.run(['wsl', 'bash', str(Path(__file__).parent / 'install.sh')]) + else: + subprocess.run(['bash', str(Path(__file__).parent / 'install.sh')]) + +def get_sd(lollms_paths:LollmsPaths): + root_dir = lollms_paths.personal_path + shared_folder = root_dir/"shared" + sd_folder = shared_folder / "auto_sd" + sd_script_path = sd_folder / "lollms_sd.py" + git_pull(sd_folder) + + if sd_script_path.exists(): + ASCIIColors.success("lollms_sd found.") + ASCIIColors.success("Loading source file...",end="") + # use importlib to load the module from the file path + from lollms.services.sd.lollms_sd import LollmsSD + ASCIIColors.success("ok") + return LollmsSD + +class Service: + def __init__( + self, + app:LollmsApplication, + base_url="http://127.0.0.1:11434", + wait_max_retries = 5 + ): + if base_url=="" or base_url=="http://127.0.0.1:7860": + base_url = None + # Get the current directory + lollms_paths = app.lollms_paths + self.app = app + root_dir = lollms_paths.personal_path + + ASCIIColors.red(" __ _____ __ __ _____ _____ _____ __ __ _____ _____ _____ ") + ASCIIColors.red("| | | | | | | | | __| | | | | | | _ | | _ |") + ASCIIColors.red("| |__| | | |__| |__| | | |__ | | | | |__| |__| | | | | |") + ASCIIColors.red("|_____|_____|_____|_____|_|_|_|_____|_____|_____|_____|_____|__|__|_|_|_|__|__|") + ASCIIColors.red(" |_____| ") + + ASCIIColors.red(" Launching ollama service by Jeffrey Morgan (jmorganca)") + ASCIIColors.red(" Integration in lollms by ParisNeo") + + if not self.wait_for_service(1,False) and base_url is None: + ASCIIColors.info("Loading ollama service") + + # Wait until the service is available at http://127.0.0.1:7860/ + self.wait_for_service(max_retries=wait_max_retries) + diff --git a/lollms/services/ollama/run_ollama.sh b/lollms/services/ollama/run_ollama.sh new file mode 100644 index 0000000..14b84fb --- /dev/null +++ b/lollms/services/ollama/run_ollama.sh @@ -0,0 +1,2 @@ +ollama serve& +ollama run mistral \ No newline at end of file diff --git a/lollms/image_gen_modules/lollms_sd.py b/lollms/services/sd/lollms_sd.py similarity index 99% rename from lollms/image_gen_modules/lollms_sd.py rename to lollms/services/sd/lollms_sd.py index 18a2db4..6228609 100644 --- a/lollms/image_gen_modules/lollms_sd.py +++ b/lollms/services/sd/lollms_sd.py @@ -61,7 +61,7 @@ def get_sd(lollms_paths:LollmsPaths): ASCIIColors.success("lollms_sd found.") ASCIIColors.success("Loading source file...",end="") # use importlib to load the module from the file path - from lollms.image_gen_modules.lollms_sd import LollmsSD + from lollms.services.sd.lollms_sd import LollmsSD ASCIIColors.success("ok") return LollmsSD diff --git a/lollms/audio_gen_modules/lollms_xtts.py b/lollms/services/xtts/lollms_xtts.py similarity index 98% rename from lollms/audio_gen_modules/lollms_xtts.py rename to lollms/services/xtts/lollms_xtts.py index 38d520c..c418579 100644 --- a/lollms/audio_gen_modules/lollms_xtts.py +++ b/lollms/services/xtts/lollms_xtts.py @@ -63,7 +63,7 @@ def get_xtts(lollms_paths:LollmsPaths): ASCIIColors.success("lollms_xtts found.") ASCIIColors.success("Loading source file...",end="") # use importlib to load the module from the file path - from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS + from lollms.services.xtts.lollms_xtts import LollmsXTTS ASCIIColors.success("ok") return LollmsXTTS diff --git a/setup.py b/setup.py index f190dbb..1798d0e 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def get_all_files(path): setuptools.setup( name="lollms", - version="7.0.0", + version="7.1.0", author="Saifeddine ALOUI", author_email="aloui.saifeddine@gmail.com", description="A python library for AI personality definition", @@ -39,7 +39,7 @@ setuptools.setup( entry_points={ 'console_scripts': [ 'lollms-elf = lollms.apps.elf:main', - 'lollms-server = lollms.apps.server:main', + 'lollms-server = lollms.server.server:main', 'lollms-console = lollms.apps.console:main', 'lollms-settings = lollms.apps.settings:main', 'lollms-discord = lollms.apps.discord_bot:main',