diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index ddebbc0..0000000 --- a/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -docker-compose.yml -models/ -images/ -app/bin -app/lib -app/lib64 -app/__pycache__ -app/pyvenv.cfg -app/include diff --git a/.env b/.env deleted file mode 100644 index 806674a..0000000 --- a/.env +++ /dev/null @@ -1,19 +0,0 @@ -#########################LOCALAI######################### - -# local-ai quand flask dockerisé -LOCALAI_HOST=local-ai - -MODELS_PATH=/models - -DEBUG=true -REBUILD=false - -#THREADS=4 - -DEFAULT_MODEL=gpt-3.5-turbo -PRELOAD_MODELS=[{"url":"github:go-skynet/model-gallery/gpt4all-j.yaml","name":"gpt-3.5-turbo"},{"url":"github:go-skynet/model-gallery/stablediffusion.yaml","name":"stablediffusion"}] - -#DEFAULT_MODEL=wizard-lm -#PRELOAD_MODELS=[{"url":"github:go-skynet/model-gallery/openllama_7b.yaml","name":"open_llama"}] - -#GALLERIES=[{"name":"model-gallery","url":"github:go-skynet/model-gallery/index.yaml"}] diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 476ea10..0000000 --- a/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM python:3.10-slim-bullseye -ENV PYTHONDONTWRITEBYTECODE 1 -ENV PYTHONUNBUFFERED 1 -WORKDIR / -COPY . . -RUN pip3 install -r requirements.txt -WORKDIR /app -CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"] \ No newline at end of file diff --git a/README.md b/README.md index ce875f4..142b53e 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,7 @@ ![CHATGPT](https://media2.giphy.com/media/qAtZM2gvjWhPjmclZE/giphy.gif?cid=ecf05e47pji6o2vjk5sa2thp1f8yqjtywlt7vm9m45nqykzx&ep=v1_gifs_search&rid=giphy.gif&ct=g) -Projet pour héberger un Chat-GPT local s'appuyant sur le projet [LocalAI](https://localai.io/) -utilisant une simple flaskapp comme frontend. - -:warning: seulement le model ggml-gpt4all-j (gpt-3.5-turbo) est pris en charge pour le moment +Projet pour héberger un Chat-GPT local s'appuyant sur les projets [Ollama](https://github.com/ollama/ollama) et [OpenWebUI](https://github.com/open-webui/open-webui) ## PREREQUIS :paperclip: @@ -13,87 +10,28 @@ utilisant une simple flaskapp comme frontend. ## MODELS :moyai: -- compatibles: [LocalAI](https://github.com/go-skynet/model-gallery) -- tous: [HugginFace](https://huggingface.co/models?search=ggml) +- compatibles: [Ollama modèles librairie](https://ollama.com/library) ## CONFIGURATION :wrench: -La configuration se fait dans le .env.local: -- THREADS -> nombre de cores du CPU utilisés (privilègier le nombre de cores physiques au max) - -- DEFAULT_MODEL -> Le modèle chargé par défault (dans la RAM) -- PRELOAD_MODELS -> Renseigner les adresses des modèles que l'on veux télécharger via l'adresse https://github.com/go-skynet/model-gallery/model.yaml +### BASE -:warning: L'image LocalAI fait un peu plus de 12Go et les modèles 7B ou 13B font en moyenne 4 à 6Go + +### OIDC :key: + +Renseigner les variables d'environnement dans le docker-compose.yml ## UTILISATION :checkered_flag: -- Premier lancement: +- Démarrer la stack: ```bash docker compose up -d ``` -:hourglass: Attendre que la stack se build :coffee: +- Ajouter des modèles: -- L'interface est accessible à cette adresse: +![ill-1](docs/modèles.png) -> http://localhost:5000 +- Choisir les modèles disponible pour les utilisateurs: -- Après les premier lancement: - -```bash -nano .env -REBUILD=false -``` - -> A chaque modification des PRELOAD_MODELS, REBUILD=true - -#### voir les logs - -```bash -docker compose logs -f -``` - -## OIDC :key: - -> voir le [README](https://git.legaragenumerique.fr/GARAGENUM/flask-keycloak/README.md) dans le projet flask-keycloak - -## MODELS OK - -:white_check_mark: ggml-gpt4all-j.bin (= gpt-turbo-3.5) -:white_check_mark: stablediffusion (image generator) -- [ ] wizardlm-13b-v1.1-superhot-8k.ggmlv3.q4_0.bin -- [ ] open-llama-7b-q4_0.bin -> HS pour le moment -- [ ] whisper (audio to text) -- [ ] bloomz (traduction) -- [ ] wizardcode (code) -> URL model HS - -## TEST HARDWARE :computer: - -| MODEL | PROMPT | i5-8350U 16G RAM | RYZEN 7 5800X 32G RAM | TEMP -| :--------------- |:-----------------:| ---------------------:|:----------------:|:------:| -| GPT-TURBO-3.5 | WRITE JS FUNCTION | 41S | 16s | 0.5 | -| STABLEDIFFUSION | BLUE FLOWER | 90s | 20S | X | - - -## TO DO :bookmark_tabs: - -:white_check_mark: une page gpt / une page stablediffusion avec navbar dans base.html -:white_check_mark: Temperature bouton -:white_check_mark: formater code (```js ```) -:white_check_mark: Flask app frontend -:white_check_mark: authentification Keycloak -> https://git.legaragenumerique.fr/GARAGENUM/flask-keycloak -:white_check_mark: wsgi.py for prod + DNS -- [ ] ajouter config Nginx (ai.domaine.tld + image.domaine.tld) -- [ ] bouton stop generating ? -- [ ] bouton home -- [ ] conserver context (sqlite / json / session ?) -- [ ] Traduction via [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) :gb: -> :fr: -- [ ] restart container si timeout -- [ ] utiliser GPU -- [ ] entraîner avec big GPU - -### bugs :ghost: - -:white_check_mark: permissions dossier models (root != user) -:white_check_mark: image url en prod +![ill-2](docs/workspace.png) diff --git a/app/.env b/app/.env deleted file mode 100644 index 806674a..0000000 --- a/app/.env +++ /dev/null @@ -1,19 +0,0 @@ -#########################LOCALAI######################### - -# local-ai quand flask dockerisé -LOCALAI_HOST=local-ai - -MODELS_PATH=/models - -DEBUG=true -REBUILD=false - -#THREADS=4 - -DEFAULT_MODEL=gpt-3.5-turbo -PRELOAD_MODELS=[{"url":"github:go-skynet/model-gallery/gpt4all-j.yaml","name":"gpt-3.5-turbo"},{"url":"github:go-skynet/model-gallery/stablediffusion.yaml","name":"stablediffusion"}] - -#DEFAULT_MODEL=wizard-lm -#PRELOAD_MODELS=[{"url":"github:go-skynet/model-gallery/openllama_7b.yaml","name":"open_llama"}] - -#GALLERIES=[{"name":"model-gallery","url":"github:go-skynet/model-gallery/index.yaml"}] diff --git a/app/app.py b/app/app.py deleted file mode 100644 index a772e4f..0000000 --- a/app/app.py +++ /dev/null @@ -1,88 +0,0 @@ -import os, re -import requests, time -from dotenv import load_dotenv -from flask_oidc import OpenIDConnect -from flask import Flask, redirect, render_template, request, url_for - -app = Flask(__name__) -model = "ggml-gpt4all-j.bin" - -load_dotenv() -host = os.getenv("LOCALAI_HOST") - -# CHAT BOT: GPT-TURBO-3.5 -@app.route("/", methods=("GET", "POST")) -def index(): - - result = '' - temps = '' - - if request.method == "POST": - - question = request.form["question"] - temp = request.form["temperature"] - - url = "http://" + host + ":8080/v1/chat/completions" - - payload = { - # "role": "system", "content": "You are Yoda, the character of Star Wars and you answer the question like he would.", - "model": 'gpt-3.5-turbo', - "messages": [{"role": "user", "content": question}], - "temperature": float(temp) - } - - tic = time.perf_counter() - - response = requests.post(url, json=payload) - if response.status_code == 200: - result = '' + response.json()['choices'][0]['message']['content'] + '' - - else: - result = "Erreur de connection avec l'API" - - toc = time.perf_counter() - - temps = f"temps de réponse: {toc - tic:0.4f} seconds" - - return render_template("index.html", result=result, time=temps) - - -# IMAGE GENERATOR: STABLEDIFFUSION -@app.route("/image", methods=("GET", "POST")) -def image(): - - result = '' - temps = '' - - if request.method == "POST": - question = request.form["image"] - url = "http://" + host + ":8080/v1/images/generations" - - headers = { - "Content-Type": "application/json" - } - - data = { - "prompt": question, - "size": "256x256", - "directory": "/tmp" - } - - tic = time.perf_counter() - - response = requests.post(url, headers=headers, json=data) - - if response.status_code == 200: - image_url = response.json()['data'][0]['url'].replace("local-ai", "localhost") - - else: - result = "Erreur de connection avec l'API" - - toc = time.perf_counter() - - temps = f"temps de réponse: {toc - tic:0.4f} seconds" - result = '' - - - return render_template("image.html", result=result, time=temps) - diff --git a/app/static/favicon.ico b/app/static/favicon.ico deleted file mode 100644 index 1c07f6a..0000000 Binary files a/app/static/favicon.ico and /dev/null differ diff --git a/app/static/loader.gif b/app/static/loader.gif deleted file mode 100644 index fe5d429..0000000 Binary files a/app/static/loader.gif and /dev/null differ diff --git a/app/static/logo.png b/app/static/logo.png deleted file mode 100644 index f9bba13..0000000 Binary files a/app/static/logo.png and /dev/null differ diff --git a/app/static/main.css b/app/static/main.css deleted file mode 100644 index 2ed5e80..0000000 --- a/app/static/main.css +++ /dev/null @@ -1,213 +0,0 @@ -@font-face { - font-family: "ColfaxAI"; - src: url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff2) - format("woff2"), - url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff) format("woff"); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: "ColfaxAI"; - src: url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff2) format("woff2"), - url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff) format("woff"); - font-weight: bold; - font-style: normal; -} - -body, -input { - font-size: 16px; - line-height: 24px; - color: #353740; - font-family: "ColfaxAI", Helvetica, sans-serif; -} - -body { - background-color: #24262B; - /* background: url("matrix.gif"); */ - display: flex; - flex-direction: column; - align-items: center; - /* padding-top: 60px; */ - min-height: 100%; - text-align: center; -} - -.icon { - width: 34px; -} - -h3 { - font-size: 32px; - line-height: 40px; - font-weight: bold; - color: #fff; - margin: 16px 0 40px; -} - -form { - margin-top: 10px; - display: flex; - flex-direction: column; - width: 400px; - text-align: center; -} - -input[type="text"] { - padding: 12px 16px; - border: 1px solid #10a37f; - border-radius: 4px; - margin-top: 5px; - margin-bottom: 24px; -} - -::placeholder { - color: #8e8ea0; - opacity: 1; -} - -input[type="submit"] { - padding: 12px 0; - color: #fff; - background-color: #10a37f; - border: none; - border-radius: 4px; - text-align: center; - cursor: pointer; -} - -.result { - color: #10a37f; - max-width: 80%; - max-height: 80%; - border: 1px solid #10a37f; - border-radius: 4px; - cursor: pointer; - font-weight: bold; - margin-top: 40px; - margin-left: 50px; - margin-right: 50px; - margin-bottom: 30px; -} - - -label { - color: black; -} - -.spinner { - padding-top: 250px; - width: 30%; -} - -.footer { - padding: 10px 0; - /* position: fixed; */ - color: #fff; - bottom: 0; - left: 0; - width: 100%; - text-align: center; -} - -select, input { - margin-bottom: 20px; - text-align: center; -} - -option { - text-align: center; -} - -.settings { - border: 1px solid #10a37f; - border-radius: 4px; - text-align: center; - cursor: pointer; - margin-bottom: 10px; -} - -.temps { - color: #fff; - text-align: center; - font-size: 10px; -} - -div { - margin-left: 10px; - margin-top: 10px; - align-items: center; -} - -.jumbotron { - height: auto; - padding-top: 0.3em; - padding-bottom: 0.3em; - margin: 20px; - background-color: #E9ECEF; -} - -.container { - display: flex; - flex-direction: column; - justify-content: center; /* Centre verticalement */ - align-items: center; /* Centre horizontalement */ -} - -a.nav-link { - color: cornflowerblue; - font-size: 30px; -} - -pre { - background-color: #f4f4f4; - margin-left: 5px; - margin-right: 5px; - margin-top: 5px; - border: 1px solid #000; - padding: 10px; - white-space: pre; -} - -button { - padding: 12px 10px; - color: #fff; - background-color: #10a37f; - border: none; - border-radius: 4px; - text-align: center; - cursor: pointer; -} - -p { - margin-left: 5px; - margin-right: 5px; - margin-top: 5px; -} - - #loading-spinner { - display: flex; - align-items: center; - justify-content: center; - height: 100vh; /* Hauteur égale à la hauteur de la fenêtre */ - position: fixed; - width: 100%; - background-color: rgba(255, 255, 255, 0.8); /* Fond semi-transparent */ - z-index: 9999; /* S'assurer qu'il apparaît au-dessus du contenu */ - -} - -/* #loading-spinner::after { - content: ''; - display: inline-block; - width: 50px; - height: 50px; - border: 5px solid #3498db; - border-radius: 50%; - border-top: 5px solid #f3f3f3; -} */ - -/* @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} */ diff --git a/app/templates/base.html b/app/templates/base.html deleted file mode 100644 index b25bbd7..0000000 --- a/app/templates/base.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - Garage AI - - - - - -
- -

Loading ...

-
- -

Garage AI

- -
- -
- -
- {% block content %} - {% endblock %} - -
- - - - - - - - - - - diff --git a/app/templates/image.html b/app/templates/image.html deleted file mode 100644 index 04f386e..0000000 --- a/app/templates/image.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -
- - -
- - {% if result %} -
{{ result|safe }}
- {% endif %} - -{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html deleted file mode 100644 index 437ebf1..0000000 --- a/app/templates/index.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -
-
-
- - -
- -
-
-
- -
-
- - {% if result %} -
{{ result|safe }}
- {% endif %} - -{% endblock %} diff --git a/app/wsgi.py b/app/wsgi.py deleted file mode 100644 index 53ea90f..0000000 --- a/app/wsgi.py +++ /dev/null @@ -1,3 +0,0 @@ -from app import app # Remplacez "votre_application" par le nom de votre application Flask -if __name__ == "__main__": - app.run() \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 27158ba..5837220 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,33 +1,54 @@ -version: '3.6' - services: - local-ai: - image: quay.io/go-skynet/local-ai:latest - container_name: local-ai + web-ui: + image: ghcr.io/open-webui/open-webui:main + container_name: open-webui restart: always ports: - - 8080:8080 - env_file: - - .env + - 3100:8080 volumes: - - ./models:/models:cached - - ./images:/tmp/generated/images - command: ["chmod -R 777 /tmp", "/usr/bin/local-ai"] - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"] - interval: 1m - timeout: 20m - retries: 20 + - ./open-webui:/app/backend/data + environment: + - "OLLAMA_BASE_URL=http://ollama:11434" + - "ENV=prod" + - "DEFAULT_MODELS=fr-FR" + - "ENABLE_OAUTH_SIGNUP=true" + - "OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true" + - "ENABLE_LOGIN_FORM=false" + - "ENABLE_COMMUNITY_SHARING=false" + - "ENABLE_OLLAMA_API=true" + - "ENV=prod" + - "GLOBAL_LOG_LEVEL=INFO" + - "WEBUI_AUTH=true" + - "OAUTH_USERNAME_CLAIM=name" + - "SCARF_NO_ANALYTICS=true" + - "DO_NOT_TRACK=true" + - "ANONYMIZED_TELEMETRY=false" + - "WEBUI_SESSION_COOKIE_SECURE=true" + - "RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE=false" + # OIDC + - "ENABLE_OPENAI_API=true" + - "OPENAI_API_KEY=" + - "OAUTH_CLIENT_ID=ai" + - "OAUTH_CLIENT_SECRET=redacted" + - "OPENID_PROVIDER_URL=https://keycloak/auth/realms/realm/.well-known/openid-configuration" + - "OAUTH_PROVIDER_NAME=keycloak" + - "OAUTH_SCOPES=openid profile email" - flask-ui: - build: - context: . - image: local/flask-ui:1.0 - container_name: flask-ui + ollama: + image: ollama/ollama + container_name: ollama restart: always ports: - - 5000:5000 - depends_on: - local-ai: - condition: service_healthy + - 11434:11434 + volumes: + - ./ollama:/root/.ollama + +# GPU USE + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] \ No newline at end of file diff --git a/docs/modèles.png b/docs/modèles.png new file mode 100644 index 0000000..62c737b Binary files /dev/null and b/docs/modèles.png differ diff --git a/docs/workspace.png b/docs/workspace.png new file mode 100644 index 0000000..995cb61 Binary files /dev/null and b/docs/workspace.png differ diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d6523bb..0000000 --- a/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -Flask==2.0.2 -Jinja2==3.0.2 -MarkupSafe==2.0.1 -openpyxl==3.0.9 -requests==2.26.0 -urllib3==1.26.7 -Werkzeug==2.0.2 -gunicorn==21.2.0 -itsdangerous==2.0.1 -python-dotenv \ No newline at end of file