mirror of
https://github.com/ParisNeo/lollms-webui.git
synced 2025-06-21 16:39:39 +00:00
upgraded models
This commit is contained in:
1
gpt4all_api/__init__.py
Normal file
1
gpt4all_api/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
585
gpt4all_api/api.py
Normal file
585
gpt4all_api/api.py
Normal file
@ -0,0 +1,585 @@
|
||||
######
|
||||
# Project : GPT4ALL-UI
|
||||
# File : api.py
|
||||
# Author : ParisNeo with the help of the community
|
||||
# Supported by Nomic-AI
|
||||
# Licence : Apache 2.0
|
||||
# Description :
|
||||
# A simple api to communicate with gpt4all-ui and its models.
|
||||
######
|
||||
import gc
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from gpt4all_api.db import DiscussionsDB
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
from pyaipersonality import AIPersonality
|
||||
import multiprocessing as mp
|
||||
import threading
|
||||
import time
|
||||
import requests
|
||||
import urllib.request
|
||||
|
||||
__author__ = "parisneo"
|
||||
__github__ = "https://github.com/nomic-ai/gpt4all-ui"
|
||||
__copyright__ = "Copyright 2023, "
|
||||
__license__ = "Apache 2.0"
|
||||
|
||||
|
||||
|
||||
class ModelProcess:
|
||||
def __init__(self, config=None):
|
||||
self.config = config
|
||||
self.generate_queue = mp.Queue()
|
||||
self.generation_queue = mp.Queue()
|
||||
self.cancel_queue = mp.Queue(maxsize=1)
|
||||
self.clear_queue_queue = mp.Queue(maxsize=1)
|
||||
self.set_config_queue = mp.Queue(maxsize=1)
|
||||
self.started_queue = mp.Queue()
|
||||
self.process = None
|
||||
self.is_generating = mp.Value('i', 0)
|
||||
self.ready = False
|
||||
|
||||
def load_backend(self, backend_path):
|
||||
|
||||
# define the full absolute path to the module
|
||||
absolute_path = backend_path.resolve()
|
||||
|
||||
# infer the module name from the file path
|
||||
module_name = backend_path.stem
|
||||
|
||||
# use importlib to load the module from the file path
|
||||
loader = importlib.machinery.SourceFileLoader(module_name, str(absolute_path/"__init__.py"))
|
||||
backend_module = loader.load_module()
|
||||
backend_class = getattr(backend_module, backend_module.backend_name)
|
||||
return backend_class
|
||||
|
||||
def start(self):
|
||||
if self.process is None:
|
||||
self.process = mp.Process(target=self._run)
|
||||
self.process.start()
|
||||
|
||||
def stop(self):
|
||||
if self.process is not None:
|
||||
self.generate_queue.put(None)
|
||||
self.process.join()
|
||||
self.process = None
|
||||
|
||||
def set_backend(self, backend_path):
|
||||
self.backend = backend_path
|
||||
|
||||
def set_model(self, model_path):
|
||||
self.model = model_path
|
||||
|
||||
def set_config(self, config):
|
||||
self.set_config_queue.put(config)
|
||||
|
||||
|
||||
def generate(self, prompt, id, n_predict):
|
||||
self.generate_queue.put((prompt, id, n_predict))
|
||||
|
||||
def cancel_generation(self):
|
||||
self.cancel_queue.put(('cancel',))
|
||||
|
||||
def clear_queue(self):
|
||||
self.clear_queue_queue.put(('clear_queue',))
|
||||
|
||||
def rebuild_backend(self, config):
|
||||
try:
|
||||
backend = self.load_backend(Path("backends")/config["backend"])
|
||||
print("Backend loaded successfully")
|
||||
except Exception as ex:
|
||||
print("Couldn't build backend")
|
||||
print(ex)
|
||||
backend = None
|
||||
return backend
|
||||
|
||||
def _rebuild_model(self):
|
||||
try:
|
||||
print("Rebuilding model")
|
||||
self.backend = self.load_backend(Path("backends")/self.config["backend"])
|
||||
print("Backend loaded successfully")
|
||||
try:
|
||||
model_file = Path("models")/self.config["backend"]/self.config["model"]
|
||||
print(f"Loading model : {model_file}")
|
||||
self.model = self.backend(self.config)
|
||||
print("Model created successfully")
|
||||
except Exception as ex:
|
||||
print("Couldn't build model")
|
||||
print(ex)
|
||||
self.model = None
|
||||
except Exception as ex:
|
||||
print("Couldn't build backend")
|
||||
print(ex)
|
||||
self.backend = None
|
||||
self.model = None
|
||||
|
||||
def rebuild_personality(self):
|
||||
try:
|
||||
personality_path = f"personalities/{self.config['personality_language']}/{self.config['personality_category']}/{self.config['personality']}"
|
||||
personality = AIPersonality(personality_path)
|
||||
except Exception as ex:
|
||||
print("Personality file not found. Please verify that the personality you have selected exists or select another personality. Some updates may lead to change in personality name or category, so check the personality selection in settings to be sure.")
|
||||
if self.config["debug"]:
|
||||
print(ex)
|
||||
personality = AIPersonality()
|
||||
return personality
|
||||
|
||||
def _rebuild_personality(self):
|
||||
try:
|
||||
personality_path = f"personalities/{self.config['personality_language']}/{self.config['personality_category']}/{self.config['personality']}"
|
||||
self.personality = AIPersonality(personality_path)
|
||||
except Exception as ex:
|
||||
print("Personality file not found. Please verify that the personality you have selected exists or select another personality. Some updates may lead to change in personality name or category, so check the personality selection in settings to be sure.")
|
||||
if self.config["debug"]:
|
||||
print(ex)
|
||||
self.personality = AIPersonality()
|
||||
|
||||
def _run(self):
|
||||
self._rebuild_model()
|
||||
self._rebuild_personality()
|
||||
self._generate("I",0,1)
|
||||
print()
|
||||
print("Ready to receive data")
|
||||
print(f"Listening on :http://{self.config['host']}:{self.config['port']}")
|
||||
self.ready = True
|
||||
|
||||
while True:
|
||||
try:
|
||||
self._check_set_config_queue()
|
||||
self._check_cancel_queue()
|
||||
self._check_clear_queue()
|
||||
|
||||
if not self.generate_queue.empty():
|
||||
command = self.generate_queue.get()
|
||||
if command is None:
|
||||
break
|
||||
|
||||
if self.cancel_queue.empty() and self.clear_queue_queue.empty():
|
||||
self.is_generating.value = 1
|
||||
self.started_queue.put(1)
|
||||
self._generate(*command)
|
||||
while not self.generation_queue.empty():
|
||||
time.sleep(1)
|
||||
self.is_generating.value = 0
|
||||
time.sleep(1)
|
||||
except Exception as ex:
|
||||
time.sleep(1)
|
||||
print(ex)
|
||||
def _generate(self, prompt, id, n_predict):
|
||||
self.id = id
|
||||
if self.config["override_personality_model_parameters"]:
|
||||
self.model.generate(
|
||||
prompt,
|
||||
new_text_callback=self._callback,
|
||||
n_predict=n_predict,
|
||||
temp=self.config['temperature'],
|
||||
top_k=self.config['top_k'],
|
||||
top_p=self.config['top_p'],
|
||||
repeat_penalty=self.config['repeat_penalty'],
|
||||
repeat_last_n = self.config['repeat_last_n'],
|
||||
seed=self.config['seed'],
|
||||
n_threads=self.config['n_threads']
|
||||
)
|
||||
else:
|
||||
self.model.generate(
|
||||
prompt,
|
||||
new_text_callback=self._callback,
|
||||
n_predict=n_predict,
|
||||
temp=self.personality.model_temperature,
|
||||
top_k=self.personality.model_top_k,
|
||||
top_p=self.personality.model_top_p,
|
||||
repeat_penalty=self.personality.model_repeat_penalty,
|
||||
repeat_last_n = self.personality.model_repeat_last_n,
|
||||
#seed=self.config['seed'],
|
||||
n_threads=self.config['n_threads']
|
||||
)
|
||||
|
||||
def _callback(self, text):
|
||||
if not self.ready:
|
||||
print(".",end="")
|
||||
sys.stdout.flush()
|
||||
return True
|
||||
else:
|
||||
# Stream the generated text to the main process
|
||||
self.generation_queue.put((text,self.id))
|
||||
self._check_set_config_queue()
|
||||
self._check_cancel_queue()
|
||||
self._check_clear_queue()
|
||||
# if stop generation is detected then stop
|
||||
if self.is_generating.value==1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _check_cancel_queue(self):
|
||||
while not self.cancel_queue.empty():
|
||||
command = self.cancel_queue.get()
|
||||
if command is not None:
|
||||
self._cancel_generation()
|
||||
|
||||
def _check_clear_queue(self):
|
||||
while not self.clear_queue_queue.empty():
|
||||
command = self.clear_queue_queue.get()
|
||||
if command is not None:
|
||||
self._clear_queue()
|
||||
|
||||
def _check_set_config_queue(self):
|
||||
while not self.set_config_queue.empty():
|
||||
config = self.set_config_queue.get()
|
||||
if config is not None:
|
||||
self._set_config(config)
|
||||
|
||||
def _cancel_generation(self):
|
||||
self.is_generating.value = 0
|
||||
|
||||
def _clear_queue(self):
|
||||
while not self.generate_queue.empty():
|
||||
self.generate_queue.get()
|
||||
|
||||
def _set_config(self, config):
|
||||
bk_cfg = self.config
|
||||
self.config = config
|
||||
print("Changing configuration")
|
||||
# verify that the backend is the same
|
||||
if self.config["backend"]!=bk_cfg["backend"] or self.config["model"]!=bk_cfg["model"]:
|
||||
self._rebuild_model()
|
||||
|
||||
# verify that the personality is the same
|
||||
if self.config["personality"]!=bk_cfg["personality"] or self.config["personality_category"]!=bk_cfg["personality_category"] or self.config["personality_language"]!=bk_cfg["personality_language"]:
|
||||
self._rebuild_personality()
|
||||
|
||||
|
||||
class GPT4AllAPI():
|
||||
def __init__(self, config:dict, socketio, config_file_path:str) -> None:
|
||||
self.socketio = socketio
|
||||
#Create and launch the process
|
||||
self.process = ModelProcess(config)
|
||||
self.process.start()
|
||||
self.config = config
|
||||
|
||||
self.backend = self.process.rebuild_backend(self.config)
|
||||
self.personality = self.process.rebuild_personality()
|
||||
if config["debug"]:
|
||||
print(print(f"{self.personality}"))
|
||||
self.config_file_path = config_file_path
|
||||
self.cancel_gen = False
|
||||
|
||||
# Keeping track of current discussion and message
|
||||
self.current_discussion = None
|
||||
self._current_user_message_id = 0
|
||||
self._current_ai_message_id = 0
|
||||
self._message_id = 0
|
||||
|
||||
self.db_path = config["db_path"]
|
||||
|
||||
# Create database object
|
||||
self.db = DiscussionsDB(self.db_path)
|
||||
|
||||
# If the database is empty, populate it with tables
|
||||
self.db.populate()
|
||||
|
||||
# This is used to keep track of messages
|
||||
self.full_message_list = []
|
||||
|
||||
# =========================================================================================
|
||||
# Socket IO stuff
|
||||
# =========================================================================================
|
||||
@socketio.on('connect')
|
||||
def connect():
|
||||
print('Client connected')
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def disconnect():
|
||||
print('Client disconnected')
|
||||
|
||||
@socketio.on('install_model')
|
||||
def install_model(data):
|
||||
def install_model_():
|
||||
print("Install model triggered")
|
||||
model_path = data["path"]
|
||||
progress = 0
|
||||
installation_dir = Path(f'./models/{self.config["backend"]}/')
|
||||
filename = Path(model_path).name
|
||||
installation_path = installation_dir / filename
|
||||
print("Model install requested")
|
||||
print(f"Model path : {model_path}")
|
||||
|
||||
if installation_path.exists():
|
||||
print("Error: Model already exists")
|
||||
data.installing = False
|
||||
socketio.emit('install_progress',{'status': 'failed', 'error': 'model already exists'})
|
||||
|
||||
socketio.emit('install_progress',{'status': 'progress', 'progress': progress})
|
||||
|
||||
def callback(progress):
|
||||
socketio.emit('install_progress',{'status': 'progress', 'progress': progress})
|
||||
|
||||
self.download_file(model_path, installation_path, callback)
|
||||
socketio.emit('install_progress',{'status': 'succeeded', 'error': ''})
|
||||
tpe = threading.Thread(target=install_model_, args=())
|
||||
tpe.start()
|
||||
|
||||
@socketio.on('uninstall_model')
|
||||
def uninstall_model(data):
|
||||
model_path = data['path']
|
||||
installation_dir = Path(f'./models/{self.config["backend"]}/')
|
||||
filename = Path(model_path).name
|
||||
installation_path = installation_dir / filename
|
||||
|
||||
if not installation_path.exists():
|
||||
socketio.emit('install_progress',{'status': 'failed', 'error': 'The model does not exist'})
|
||||
|
||||
installation_path.unlink()
|
||||
socketio.emit('install_progress',{'status': 'succeeded', 'error': ''})
|
||||
|
||||
|
||||
|
||||
@socketio.on('generate_msg')
|
||||
def generate_msg(data):
|
||||
if self.current_discussion is None:
|
||||
if self.db.does_last_discussion_have_messages():
|
||||
self.current_discussion = self.db.create_discussion()
|
||||
else:
|
||||
self.current_discussion = self.db.load_last_discussion()
|
||||
|
||||
message = data["prompt"]
|
||||
message_id = self.current_discussion.add_message(
|
||||
"user", message, parent=self.message_id
|
||||
)
|
||||
|
||||
self.current_user_message_id = message_id
|
||||
tpe = threading.Thread(target=self.start_message_generation, args=(message, message_id))
|
||||
tpe.start()
|
||||
|
||||
@socketio.on('generate_msg_from')
|
||||
def handle_connection(data):
|
||||
message_id = int(data['id'])
|
||||
message = data["prompt"]
|
||||
self.current_user_message_id = message_id
|
||||
tpe = threading.Thread(target=self.start_message_generation, args=(message, message_id))
|
||||
tpe.start()
|
||||
# generation status
|
||||
self.generating=False
|
||||
|
||||
#properties
|
||||
@property
|
||||
def message_id(self):
|
||||
return self._message_id
|
||||
|
||||
@property
|
||||
def current_user_message_id(self):
|
||||
return self._current_user_message_id
|
||||
@current_user_message_id.setter
|
||||
def current_user_message_id(self, id):
|
||||
self._current_user_message_id=id
|
||||
self._message_id = id
|
||||
@property
|
||||
def current_ai_message_id(self):
|
||||
return self._current_ai_message_id
|
||||
@current_ai_message_id.setter
|
||||
def current_ai_message_id(self, id):
|
||||
self._current_ai_message_id=id
|
||||
self._message_id = id
|
||||
|
||||
|
||||
def download_file(self, url, installation_path, callback=None):
|
||||
"""
|
||||
Downloads a file from a URL and displays the download progress using tqdm.
|
||||
|
||||
Args:
|
||||
url (str): The URL of the file to download.
|
||||
callback (function, optional): A callback function to be called during the download
|
||||
with the progress percentage as an argument. Defaults to None.
|
||||
"""
|
||||
def report_hook(count, block_size, total_size):
|
||||
if callback is not None:
|
||||
percentage = (count * block_size / total_size) * 100
|
||||
callback(percentage)
|
||||
|
||||
urllib.request.urlretrieve(url, installation_path, reporthook=report_hook)
|
||||
|
||||
if callback is not None:
|
||||
callback(100.0)
|
||||
|
||||
def load_backend(self, backend_path):
|
||||
|
||||
# define the full absolute path to the module
|
||||
absolute_path = backend_path.resolve()
|
||||
|
||||
# infer the module name from the file path
|
||||
module_name = backend_path.stem
|
||||
|
||||
# use importlib to load the module from the file path
|
||||
loader = importlib.machinery.SourceFileLoader(module_name, str(absolute_path/"__init__.py"))
|
||||
backend_module = loader.load_module()
|
||||
backend_class = getattr(backend_module, backend_module.backend_name)
|
||||
return backend_class
|
||||
|
||||
|
||||
def condition_chatbot(self):
|
||||
if self.current_discussion is None:
|
||||
self.current_discussion = self.db.load_last_discussion()
|
||||
|
||||
if self.personality.welcome_message!="":
|
||||
message_id = self.current_discussion.add_message(
|
||||
self.personality.name, self.personality.welcome_message,
|
||||
DiscussionsDB.MSG_TYPE_NORMAL,
|
||||
0,
|
||||
-1
|
||||
)
|
||||
|
||||
self.current_ai_message_id = message_id
|
||||
return message_id
|
||||
|
||||
def prepare_reception(self):
|
||||
self.bot_says = ""
|
||||
self.full_text = ""
|
||||
self.is_bot_text_started = False
|
||||
|
||||
def create_new_discussion(self, title):
|
||||
self.current_discussion = self.db.create_discussion(title)
|
||||
# Get the current timestamp
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Chatbot conditionning
|
||||
self.condition_chatbot()
|
||||
return timestamp
|
||||
|
||||
def prepare_query(self, message_id=-1):
|
||||
messages = self.current_discussion.get_messages()
|
||||
self.full_message_list = []
|
||||
for message in messages:
|
||||
if message["id"]<= message_id or message_id==-1:
|
||||
if message["type"]==self.db.MSG_TYPE_NORMAL:
|
||||
if message["sender"]==self.personality.name:
|
||||
self.full_message_list.append(self.personality.ai_message_prefix+message["content"])
|
||||
else:
|
||||
self.full_message_list.append(self.personality.user_message_prefix + message["content"])
|
||||
|
||||
link_text = self.personality.link_text
|
||||
|
||||
if len(self.full_message_list) > self.config["nb_messages_to_remember"]:
|
||||
discussion_messages = self.personality.personality_conditioning+ link_text.join(self.full_message_list[-self.config["nb_messages_to_remember"]:])
|
||||
else:
|
||||
discussion_messages = self.personality.personality_conditioning+ link_text.join(self.full_message_list)
|
||||
|
||||
discussion_messages += link_text + self.personality.ai_message_prefix
|
||||
return discussion_messages # Removes the last return
|
||||
|
||||
def get_discussion_to(self, message_id=-1):
|
||||
messages = self.current_discussion.get_messages()
|
||||
self.full_message_list = []
|
||||
for message in messages:
|
||||
if message["id"]<= message_id or message_id==-1:
|
||||
if message["type"]!=self.db.MSG_TYPE_CONDITIONNING:
|
||||
if message["sender"]==self.personality.name:
|
||||
self.full_message_list.append(self.personality.ai_message_prefix+message["content"])
|
||||
else:
|
||||
self.full_message_list.append(self.personality.user_message_prefix + message["content"])
|
||||
|
||||
link_text = self.personality.link_text
|
||||
|
||||
if len(self.full_message_list) > self.config["nb_messages_to_remember"]:
|
||||
discussion_messages = self.personality.personality_conditioning+ link_text.join(self.full_message_list[-self.config["nb_messages_to_remember"]:])
|
||||
else:
|
||||
discussion_messages = self.personality.personality_conditioning+ link_text.join(self.full_message_list)
|
||||
|
||||
return discussion_messages # Removes the last return
|
||||
|
||||
|
||||
def remove_text_from_string(self, string, text_to_find):
|
||||
"""
|
||||
Removes everything from the first occurrence of the specified text in the string (case-insensitive).
|
||||
|
||||
Parameters:
|
||||
string (str): The original string.
|
||||
text_to_find (str): The text to find in the string.
|
||||
|
||||
Returns:
|
||||
str: The updated string.
|
||||
"""
|
||||
index = string.lower().find(text_to_find.lower())
|
||||
|
||||
if index != -1:
|
||||
string = string[:index]
|
||||
|
||||
return string
|
||||
|
||||
def process_chunk(self, chunk):
|
||||
print(chunk[0],end="")
|
||||
sys.stdout.flush()
|
||||
self.bot_says += chunk[0]
|
||||
if not self.personality.detect_antiprompt(self.bot_says):
|
||||
self.socketio.emit('message', {
|
||||
'data': self.bot_says,
|
||||
'user_message_id':self.current_user_message_id,
|
||||
'ai_message_id':self.current_ai_message_id,
|
||||
'discussion_id':self.current_discussion.discussion_id
|
||||
}
|
||||
)
|
||||
if self.cancel_gen:
|
||||
print("Generation canceled")
|
||||
self.process.cancel_generation()
|
||||
self.cancel_gen = False
|
||||
else:
|
||||
self.bot_says = self.remove_text_from_string(self.bot_says, self.personality.user_message_prefix.strip())
|
||||
self.process.cancel_generation()
|
||||
print("The model is halucinating")
|
||||
|
||||
def start_message_generation(self, message, message_id):
|
||||
bot_says = ""
|
||||
|
||||
# send the message to the bot
|
||||
print(f"Received message : {message}")
|
||||
if self.current_discussion:
|
||||
# First we need to send the new message ID to the client
|
||||
self.current_ai_message_id = self.current_discussion.add_message(
|
||||
self.personality.name, "", parent = self.current_user_message_id
|
||||
) # first the content is empty, but we'll fill it at the end
|
||||
self.socketio.emit('infos',
|
||||
{
|
||||
"type": "input_message_infos",
|
||||
"bot": self.personality.name,
|
||||
"user": self.personality.user_name,
|
||||
"message":message,#markdown.markdown(message),
|
||||
"user_message_id": self.current_user_message_id,
|
||||
"ai_message_id": self.current_ai_message_id,
|
||||
}
|
||||
)
|
||||
|
||||
# prepare query and reception
|
||||
self.discussion_messages = self.prepare_query(message_id)
|
||||
self.prepare_reception()
|
||||
self.generating = True
|
||||
print("## Generating message ##")
|
||||
self.process.generate(self.discussion_messages, message_id, n_predict = self.config['n_predict'])
|
||||
self.process.started_queue.get()
|
||||
while(self.process.is_generating.value): # Simulating other commands being issued
|
||||
while not self.process.generation_queue.empty():
|
||||
self.process_chunk(self.process.generation_queue.get())
|
||||
|
||||
print()
|
||||
print("## Done ##")
|
||||
print()
|
||||
|
||||
# Send final message
|
||||
self.socketio.emit('final', {
|
||||
'data': self.bot_says,
|
||||
'ai_message_id':self.current_ai_message_id,
|
||||
'parent':self.current_user_message_id, 'discussion_id':self.current_discussion.discussion_id
|
||||
}
|
||||
)
|
||||
|
||||
self.current_discussion.update_message(self.current_ai_message_id, self.bot_says)
|
||||
self.full_message_list.append(self.bot_says)
|
||||
self.cancel_gen = False
|
||||
return bot_says
|
||||
else:
|
||||
#No discussion available
|
||||
print("No discussion selected!!!")
|
||||
print("## Done ##")
|
||||
print()
|
||||
self.cancel_gen = False
|
||||
return ""
|
||||
|
61
gpt4all_api/backend.py
Normal file
61
gpt4all_api/backend.py
Normal file
@ -0,0 +1,61 @@
|
||||
######
|
||||
# Project : GPT4ALL-UI
|
||||
# File : backend.py
|
||||
# Author : ParisNeo with the help of the community
|
||||
# Supported by Nomic-AI
|
||||
# Licence : Apache 2.0
|
||||
# Description :
|
||||
# This is an interface class for GPT4All-ui backends.
|
||||
######
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
import inspect
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
__author__ = "parisneo"
|
||||
__github__ = "https://github.com/nomic-ai/gpt4all-ui"
|
||||
__copyright__ = "Copyright 2023, "
|
||||
__license__ = "Apache 2.0"
|
||||
|
||||
|
||||
class GPTBackend:
|
||||
file_extension='*.bin'
|
||||
backend_path = Path(__file__).parent
|
||||
def __init__(self, config:dict, inline:bool) -> None:
|
||||
self.config = config
|
||||
self.inline = inline
|
||||
|
||||
|
||||
def generate(self,
|
||||
prompt:str,
|
||||
n_predict: int = 128,
|
||||
new_text_callback: Callable[[str], None] = None,
|
||||
verbose: bool = False,
|
||||
**gpt_params ):
|
||||
"""Generates text out of a prompt
|
||||
This should ber implemented by child class
|
||||
|
||||
Args:
|
||||
prompt (str): The prompt to use for generation
|
||||
n_predict (int, optional): Number of tokens to prodict. Defaults to 128.
|
||||
new_text_callback (Callable[[str], None], optional): A callback function that is called everytime a new text element is generated. Defaults to None.
|
||||
verbose (bool, optional): If true, the code will spit many informations about the generation process. Defaults to False.
|
||||
"""
|
||||
pass
|
||||
@staticmethod
|
||||
def list_models(config:dict):
|
||||
"""Lists the models for this backend
|
||||
"""
|
||||
models_dir = Path('./models')/config["backend"] # replace with the actual path to the models folder
|
||||
return [f.name for f in models_dir.glob(GPTBackend.file_extension)]
|
||||
@staticmethod
|
||||
def get_available_models():
|
||||
# Create the file path relative to the child class's directory
|
||||
backend_path = Path(__file__).parent
|
||||
file_path = backend_path/"models.yaml"
|
||||
|
||||
with open(file_path, 'r') as file:
|
||||
yaml_data = yaml.safe_load(file)
|
||||
|
||||
return yaml_data
|
28
gpt4all_api/config.py
Normal file
28
gpt4all_api/config.py
Normal file
@ -0,0 +1,28 @@
|
||||
######
|
||||
# Project : GPT4ALL-UI
|
||||
# File : config.py
|
||||
# Author : ParisNeo with the help of the community
|
||||
# Supported by Nomic-AI
|
||||
# Licence : Apache 2.0
|
||||
# Description :
|
||||
# A front end Flask application for llamacpp models.
|
||||
# The official GPT4All Web ui
|
||||
# Made by the community for the community
|
||||
######
|
||||
import yaml
|
||||
|
||||
__author__ = "parisneo"
|
||||
__github__ = "https://github.com/nomic-ai/gpt4all-ui"
|
||||
__copyright__ = "Copyright 2023, "
|
||||
__license__ = "Apache 2.0"
|
||||
|
||||
def load_config(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as stream:
|
||||
config = yaml.safe_load(stream)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def save_config(config, filepath):
|
||||
with open(filepath, "w") as f:
|
||||
yaml.dump(config, f)
|
358
gpt4all_api/db.py
Normal file
358
gpt4all_api/db.py
Normal file
@ -0,0 +1,358 @@
|
||||
|
||||
import sqlite3
|
||||
|
||||
__author__ = "parisneo"
|
||||
__github__ = "https://github.com/nomic-ai/gpt4all-ui"
|
||||
__copyright__ = "Copyright 2023, "
|
||||
__license__ = "Apache 2.0"
|
||||
|
||||
|
||||
# =================================== Database ==================================================================
|
||||
class DiscussionsDB:
|
||||
MSG_TYPE_NORMAL = 0
|
||||
MSG_TYPE_CONDITIONNING = 1
|
||||
|
||||
def __init__(self, db_path="database.db"):
|
||||
self.db_path = db_path
|
||||
|
||||
def populate(self):
|
||||
"""
|
||||
create database schema
|
||||
"""
|
||||
db_version = 3
|
||||
# Verify encoding and change it if it is not complient
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
# Execute a PRAGMA statement to get the current encoding of the database
|
||||
cur = conn.execute('PRAGMA encoding')
|
||||
current_encoding = cur.fetchone()[0]
|
||||
|
||||
if current_encoding != 'UTF-8':
|
||||
# The current encoding is not UTF-8, so we need to change it
|
||||
print(f"The current encoding is {current_encoding}, changing to UTF-8...")
|
||||
conn.execute('PRAGMA encoding = "UTF-8"')
|
||||
conn.commit()
|
||||
|
||||
print("Checking discussions database...")
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
discussion_table_exist=False
|
||||
message_table_exist=False
|
||||
schema_table_exist=False
|
||||
|
||||
# Check if the 'schema_version' table exists
|
||||
try:
|
||||
cursor.execute("""
|
||||
CREATE TABLE schema_version (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version INTEGER NOT NULL
|
||||
)
|
||||
""")
|
||||
except:
|
||||
schema_table_exist = True
|
||||
try:
|
||||
cursor.execute("""
|
||||
CREATE TABLE discussion (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
except Exception:
|
||||
discussion_table_exist=True
|
||||
try:
|
||||
cursor.execute("""
|
||||
CREATE TABLE message (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
type INT NOT NULL,
|
||||
rank INT NOT NULL,
|
||||
parent INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
discussion_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (discussion_id) REFERENCES discussion(id),
|
||||
FOREIGN KEY (parent) REFERENCES message(id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
except :
|
||||
message_table_exist=True
|
||||
|
||||
# Get the current version from the schema_version table
|
||||
cursor.execute("SELECT version FROM schema_version WHERE id = 1")
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
# If the table is empty, assume version 0
|
||||
version = 0
|
||||
else:
|
||||
# Otherwise, use the version from the table
|
||||
version = row[0]
|
||||
|
||||
# Upgrade the schema to version 1
|
||||
if version < 1:
|
||||
print(f"Upgrading schema to version {db_version}...")
|
||||
# Add the 'created_at' column to the 'message' table
|
||||
if message_table_exist:
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN type INT DEFAULT 0") # Added in V1
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN rank INT DEFAULT 0") # Added in V1
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN parent INT DEFAULT 0") # Added in V2
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP") # Added in V3
|
||||
cursor.execute("ALTER TABLE discussion ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP") # Added in V3
|
||||
|
||||
# Upgrade the schema to version 1
|
||||
elif version < 2:
|
||||
print(f"Upgrading schema to version {db_version}...")
|
||||
# Add the 'created_at' column to the 'message' table
|
||||
if message_table_exist:
|
||||
try:
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN parent INT DEFAULT 0") # Added in V2
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP") # Added in V3
|
||||
cursor.execute("ALTER TABLE discussion ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP") # Added in V3
|
||||
except :
|
||||
pass
|
||||
# Upgrade the schema to version 1
|
||||
elif version < 3:
|
||||
print(f"Upgrading schema to version {db_version}...")
|
||||
# Add the 'created_at' column to the 'message' table
|
||||
if message_table_exist:
|
||||
try:
|
||||
cursor.execute("ALTER TABLE message ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP") # Added in V3
|
||||
cursor.execute("ALTER TABLE discussion ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP") # Added in V3
|
||||
except :
|
||||
pass
|
||||
# Update the schema version
|
||||
if not schema_table_exist:
|
||||
cursor.execute(f"INSERT INTO schema_version (id, version) VALUES (1, {db_version})")
|
||||
else:
|
||||
cursor.execute(f"UPDATE schema_version SET version=? WHERE id=?",(db_version,1))
|
||||
|
||||
conn.commit()
|
||||
|
||||
def select(self, query, params=None, fetch_all=True):
|
||||
"""
|
||||
Execute the specified SQL select query on the database,
|
||||
with optional parameters.
|
||||
Returns the cursor object for further processing.
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
if params is None:
|
||||
cursor = conn.execute(query)
|
||||
else:
|
||||
cursor = conn.execute(query, params)
|
||||
if fetch_all:
|
||||
return cursor.fetchall()
|
||||
else:
|
||||
return cursor.fetchone()
|
||||
|
||||
|
||||
def delete(self, query, params=None):
|
||||
"""
|
||||
Execute the specified SQL delete query on the database,
|
||||
with optional parameters.
|
||||
Returns the cursor object for further processing.
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
if params is None:
|
||||
cursor.execute(query)
|
||||
else:
|
||||
cursor.execute(query, params)
|
||||
conn.commit()
|
||||
|
||||
def insert(self, query, params=None):
|
||||
"""
|
||||
Execute the specified INSERT SQL query on the database,
|
||||
with optional parameters.
|
||||
Returns the ID of the newly inserted row.
|
||||
"""
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.execute(query, params)
|
||||
rowid = cursor.lastrowid
|
||||
conn.commit()
|
||||
self.conn = None
|
||||
return rowid
|
||||
|
||||
def update(self, query, params=None):
|
||||
"""
|
||||
Execute the specified Update SQL query on the database,
|
||||
with optional parameters.
|
||||
Returns the ID of the newly inserted row.
|
||||
"""
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute(query, params)
|
||||
conn.commit()
|
||||
|
||||
def load_last_discussion(self):
|
||||
last_discussion_id = self.select("SELECT id FROM discussion ORDER BY id DESC LIMIT 1", fetch_all=False)
|
||||
if last_discussion_id is None:
|
||||
last_discussion = self.create_discussion()
|
||||
last_discussion_id = last_discussion.discussion_id
|
||||
else:
|
||||
last_discussion_id = last_discussion_id[0]
|
||||
self.current_message_id = self.select("SELECT id FROM message WHERE discussion_id=? ORDER BY id DESC LIMIT 1", (last_discussion_id,), fetch_all=False)
|
||||
return Discussion(last_discussion_id, self)
|
||||
|
||||
def create_discussion(self, title="untitled"):
|
||||
"""Creates a new discussion
|
||||
|
||||
Args:
|
||||
title (str, optional): The title of the discussion. Defaults to "untitled".
|
||||
|
||||
Returns:
|
||||
Discussion: A Discussion instance
|
||||
"""
|
||||
discussion_id = self.insert(f"INSERT INTO discussion (title) VALUES (?)",(title,))
|
||||
return Discussion(discussion_id, self)
|
||||
|
||||
def build_discussion(self, discussion_id=0):
|
||||
return Discussion(discussion_id, self)
|
||||
|
||||
def get_discussions(self):
|
||||
rows = self.select("SELECT * FROM discussion")
|
||||
return [{"id": row[0], "title": row[1]} for row in rows]
|
||||
|
||||
def does_last_discussion_have_messages(self):
|
||||
last_discussion_id = self.select("SELECT id FROM discussion ORDER BY id DESC LIMIT 1", fetch_all=False)
|
||||
if last_discussion_id is None:
|
||||
last_discussion = self.create_discussion()
|
||||
last_discussion_id = last_discussion.discussion_id
|
||||
else:
|
||||
last_discussion_id = last_discussion_id[0]
|
||||
last_message = self.select("SELECT * FROM message WHERE discussion_id=?", (last_discussion_id,), fetch_all=False)
|
||||
return last_message is not None
|
||||
|
||||
def remove_discussions(self):
|
||||
self.delete("DELETE FROM message")
|
||||
self.delete("DELETE FROM discussion")
|
||||
|
||||
|
||||
def export_to_json(self):
|
||||
db_discussions = self.select("SELECT * FROM discussion")
|
||||
discussions = []
|
||||
for row in db_discussions:
|
||||
discussion_id = row[0]
|
||||
discussion_title = row[1]
|
||||
discussion = {"id": discussion_id, "title":discussion_title, "messages": []}
|
||||
rows = self.select(f"SELECT * FROM message WHERE discussion_id=?",(discussion_id,))
|
||||
for message_row in rows:
|
||||
sender = message_row[1]
|
||||
content = message_row[2]
|
||||
content_type = message_row[3]
|
||||
rank = message_row[4]
|
||||
parent = message_row[5]
|
||||
discussion["messages"].append(
|
||||
{"sender": sender, "content": content, "type": content_type, "rank": rank, "parent": parent}
|
||||
)
|
||||
discussions.append(discussion)
|
||||
return discussions
|
||||
|
||||
|
||||
class Discussion:
|
||||
def __init__(self, discussion_id, discussions_db:DiscussionsDB):
|
||||
self.discussion_id = discussion_id
|
||||
self.discussions_db = discussions_db
|
||||
|
||||
def add_message(self, sender, content, message_type=0, rank=0, parent=0):
|
||||
"""Adds a new message to the discussion
|
||||
|
||||
Args:
|
||||
sender (str): The sender name
|
||||
content (str): The text sent by the sender
|
||||
|
||||
Returns:
|
||||
int: The added message id
|
||||
"""
|
||||
message_id = self.discussions_db.insert(
|
||||
"INSERT INTO message (sender, content, type, rank, parent, discussion_id) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(sender, content, message_type, rank, parent, self.discussion_id)
|
||||
)
|
||||
return message_id
|
||||
|
||||
def rename(self, new_title):
|
||||
"""Renames the discussion
|
||||
|
||||
Args:
|
||||
new_title (str): The nex discussion name
|
||||
"""
|
||||
self.discussions_db.update(
|
||||
f"UPDATE discussion SET title=? WHERE id=?",(new_title,self.discussion_id)
|
||||
)
|
||||
|
||||
def delete_discussion(self):
|
||||
"""Deletes the discussion
|
||||
"""
|
||||
self.discussions_db.delete(
|
||||
f"DELETE FROM message WHERE discussion_id={self.discussion_id}"
|
||||
)
|
||||
self.discussions_db.delete(
|
||||
f"DELETE FROM discussion WHERE id={self.discussion_id}"
|
||||
)
|
||||
|
||||
def get_messages(self):
|
||||
"""Gets a list of messages information
|
||||
|
||||
Returns:
|
||||
list: List of entries in the format {"id":message id, "sender":sender name, "content":message content, "type":message type, "rank": message rank}
|
||||
"""
|
||||
rows = self.discussions_db.select(
|
||||
"SELECT * FROM message WHERE discussion_id=?", (self.discussion_id,)
|
||||
)
|
||||
|
||||
return [{"id": row[0], "sender": row[1], "content": row[2], "type": row[3], "rank": row[4], "parent": row[5]} for row in rows]
|
||||
|
||||
def update_message(self, message_id, new_content):
|
||||
"""Updates the content of a message
|
||||
|
||||
Args:
|
||||
message_id (int): The id of the message to be changed
|
||||
new_content (str): The nex message content
|
||||
"""
|
||||
self.discussions_db.update(
|
||||
f"UPDATE message SET content = ? WHERE id = ?",(new_content,message_id)
|
||||
)
|
||||
|
||||
def message_rank_up(self, message_id):
|
||||
"""Increments the rank of the message
|
||||
|
||||
Args:
|
||||
message_id (int): The id of the message to be changed
|
||||
"""
|
||||
# Retrieve current rank value for message_id
|
||||
current_rank = self.discussions_db.select("SELECT rank FROM message WHERE id=?", (message_id,),False)[0]
|
||||
|
||||
# Increment current rank value by 1
|
||||
new_rank = current_rank + 1
|
||||
self.discussions_db.update(
|
||||
f"UPDATE message SET rank = ? WHERE id = ?",(new_rank,message_id)
|
||||
)
|
||||
return new_rank
|
||||
|
||||
def message_rank_down(self, message_id):
|
||||
"""Increments the rank of the message
|
||||
|
||||
Args:
|
||||
message_id (int): The id of the message to be changed
|
||||
"""
|
||||
# Retrieve current rank value for message_id
|
||||
current_rank = self.discussions_db.select("SELECT rank FROM message WHERE id=?", (message_id,),False)[0]
|
||||
|
||||
# Increment current rank value by 1
|
||||
new_rank = current_rank - 1
|
||||
self.discussions_db.update(
|
||||
f"UPDATE message SET rank = ? WHERE id = ?",(new_rank,message_id)
|
||||
)
|
||||
return new_rank
|
||||
|
||||
def delete_message(self, message_id):
|
||||
"""Delete the message
|
||||
|
||||
Args:
|
||||
message_id (int): The id of the message to be deleted
|
||||
"""
|
||||
# Retrieve current rank value for message_id
|
||||
self.discussions_db.delete("DELETE FROM message WHERE id=?", (message_id,))
|
||||
|
||||
# ========================================================================================================================
|
18
gpt4all_api/extension.py
Normal file
18
gpt4all_api/extension.py
Normal file
@ -0,0 +1,18 @@
|
||||
# File : extension.py
|
||||
# Author : ParisNeo
|
||||
# Description :
|
||||
# This is the main class to be imported by the extension
|
||||
# it gives your code access to the model, the callback functions, the model conditionning etc
|
||||
from config import load_config, save_config
|
||||
|
||||
__author__ = "parisneo"
|
||||
__github__ = "https://github.com/nomic-ai/gpt4all-ui"
|
||||
__copyright__ = "Copyright 2023, "
|
||||
__license__ = "Apache 2.0"
|
||||
|
||||
class Extension():
|
||||
def __init__(self, metadata_file_path:str, app) -> None:
|
||||
self.app = app
|
||||
self.metadata_file_path = metadata_file_path
|
||||
self.config = load_config(metadata_file_path)
|
||||
|
Reference in New Issue
Block a user