update to ollama & openwebui

This commit is contained in:
Grégory Lebreton 2024-10-18 14:09:16 +02:00
parent 944ce3eae8
commit ca15f31551
18 changed files with 58 additions and 597 deletions

View File

@ -1,9 +0,0 @@
docker-compose.yml
models/
images/
app/bin
app/lib
app/lib64
app/__pycache__
app/pyvenv.cfg
app/include

19
.env
View File

@ -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"}]

View File

@ -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"]

View File

@ -2,10 +2,7 @@
![CHATGPT](https://media2.giphy.com/media/qAtZM2gvjWhPjmclZE/giphy.gif?cid=ecf05e47pji6o2vjk5sa2thp1f8yqjtywlt7vm9m45nqykzx&ep=v1_gifs_search&rid=giphy.gif&ct=g) ![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/) 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)
utilisant une simple flaskapp comme frontend.
:warning: seulement le model ggml-gpt4all-j (gpt-3.5-turbo) est pris en charge pour le moment
## PREREQUIS :paperclip: ## PREREQUIS :paperclip:
@ -13,87 +10,28 @@ utilisant une simple flaskapp comme frontend.
## MODELS :moyai: ## MODELS :moyai:
- compatibles: [LocalAI](https://github.com/go-skynet/model-gallery) - compatibles: [Ollama modèles librairie](https://ollama.com/library)
- tous: [HugginFace](https://huggingface.co/models?search=ggml)
## CONFIGURATION :wrench: ## CONFIGURATION :wrench:
La configuration se fait dans le .env.local: ### BASE
- THREADS -> nombre de cores du CPU utilisés (privilègier le nombre de cores physiques au max)
<img src="https://indipest.files.wordpress.com/2021/03/bw6d5zz.gif" width="200px">
- 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
: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: ## UTILISATION :checkered_flag:
- Premier lancement: - Démarrer la stack:
```bash ```bash
docker compose up -d 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: ![ill-2](docs/workspace.png)
```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

View File

@ -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"}]

View File

@ -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 = '<md>' + response.json()['choices'][0]['message']['content'] + '</md>'
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 = '<img src=' + image_url + ' >'
return render_template("image.html", result=result, time=temps)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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); }
} */

View File

@ -1,92 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}" />
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>Garage AI</title>
</head>
<body>
<div id="loading-spinner">
<img src="{{ url_for('static', filename='loader.gif') }}" class="spinner">
<p>Loading ...</p>
</div>
<h3>Garage AI</h3>
<div class="container">
<nav class="navbar navbar-expand-lg navbar-light bg-light jumbotron">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
{% if request.path != '/' %}
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('index') }}">CHAT <span class="sr-only">(current)</span></a>
</li>
{% elif request.path != '/image' %}
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('image') }}">IMAGE <span class="sr-only">(current)</span></a>
</li>
{% endif %}
</ul>
</div>
</nav>
</div>
<div class="container jumbotron">
{% block content %}
{% endblock %}
<button id="copyButton" style="display: none;" onclick="copyCodeToClipboard()">Copier le code</button>
</div>
<div class="footer">
<div class="temps">{{ time }}</div>
<div>
<img src="{{ url_for('static', filename='logo.png') }}" style="max-width: 50%;">
</div>
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/MarketingPipeline/Markdown-Tag/markdown-tag-GitHub.js"></script>
<script>
const myForm = document.getElementById("form");
const loadingSpinner = document.getElementById("loading-spinner");
myForm.addEventListener("submit", function (event) {
loadingSpinner.style.display = "block";
});
function onPageLoaded() {
loadingSpinner.style.display = "none";
if (document.getElementsByTagName("code").length > 0)
{
var copyButton = document.getElementById("copyButton").style.display = "block"; // Affiche le bouton
}
}
function copyCodeToClipboard() {
var codeToCopy = document.querySelector("code");
var textArea = document.createElement("textarea");
textArea.value = codeToCopy.textContent;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
window.addEventListener("load", onPageLoaded);
</script>
</body>
</html>

View File

@ -1,14 +0,0 @@
{% extends "base.html" %}
{% block content %}
<form action="/image" method="post" id="form">
<input type="text" name="image" placeholder="Description de l'image ici!" required />
<input type="submit" value="Go!" />
</form>
{% if result %}
<div class="result">{{ result|safe }}</div>
{% endif %}
{% endblock %}

View File

@ -1,23 +0,0 @@
{% extends "base.html" %}
{% block content %}
<form action="/" method="post" id="form">
<div class="settings">
<br>
<label for="valeur">Sélectionnez la température :</label>
<input type="range" id="valeur" name="temperature" min="0" max="1" step=".1">
<br>
<input type="text" name="question" placeholder="Ecrire la question ici!" required />
<br>
</div>
<br>
<input type="submit" value="Go!" />
<br>
</form>
{% if result %}
<div class="result">{{ result|safe }}</div>
{% endif %}
{% endblock %}

View File

@ -1,3 +0,0 @@
from app import app # Remplacez "votre_application" par le nom de votre application Flask
if __name__ == "__main__":
app.run()

View File

@ -1,33 +1,54 @@
version: '3.6'
services: services:
local-ai: web-ui:
image: quay.io/go-skynet/local-ai:latest image: ghcr.io/open-webui/open-webui:main
container_name: local-ai container_name: open-webui
restart: always restart: always
ports: ports:
- 8080:8080 - 3100:8080
env_file:
- .env
volumes: volumes:
- ./models:/models:cached - ./open-webui:/app/backend/data
- ./images:/tmp/generated/images environment:
command: ["chmod -R 777 /tmp", "/usr/bin/local-ai"] - "OLLAMA_BASE_URL=http://ollama:11434"
healthcheck: - "ENV=prod"
test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"] - "DEFAULT_MODELS=fr-FR"
interval: 1m - "ENABLE_OAUTH_SIGNUP=true"
timeout: 20m - "OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true"
retries: 20 - "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: ollama:
build: image: ollama/ollama
context: . container_name: ollama
image: local/flask-ui:1.0
container_name: flask-ui
restart: always restart: always
ports: ports:
- 5000:5000 - 11434:11434
depends_on: volumes:
local-ai: - ./ollama:/root/.ollama
condition: service_healthy
# GPU USE
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]

BIN
docs/modèles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/workspace.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -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