Initial implementation of the chat application with MariaDB and ArangoDB integration, including Docker setup and web interface.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
data/arangodb/
|
||||||
|
data/mariadb/
|
||||||
6
api/Dockerfile
Normal file
6
api/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY app.py .
|
||||||
|
CMD ["python", "app.py"]
|
||||||
70
api/app.py
Normal file
70
api/app.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import mysql.connector
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
from arango import ArangoClient
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# MariaDB Verbindung
|
||||||
|
def get_db():
|
||||||
|
return mysql.connector.connect(
|
||||||
|
host=os.environ['DB_HOST'],
|
||||||
|
user=os.environ['DB_USER'],
|
||||||
|
password=os.environ['DB_PASSWORD'],
|
||||||
|
database=os.environ['DB_NAME']
|
||||||
|
)
|
||||||
|
|
||||||
|
# ArangoDB Verbindung
|
||||||
|
def get_arango():
|
||||||
|
client = ArangoClient(hosts=os.environ['ARANGO_URL'])
|
||||||
|
sys_db = client.db('_system', username=os.environ['ARANGO_USER'], password=os.environ['ARANGO_PASSWORD'])
|
||||||
|
if not sys_db.has_database('llm_facts'):
|
||||||
|
sys_db.create_database('llm_facts')
|
||||||
|
db = client.db('llm_facts', username=os.environ['ARANGO_USER'], password=os.environ['ARANGO_PASSWORD'])
|
||||||
|
if not db.has_collection('facts'):
|
||||||
|
db.create_collection('facts')
|
||||||
|
return db.collection('facts')
|
||||||
|
|
||||||
|
# Skill-Trigger
|
||||||
|
def extract_fact(text):
|
||||||
|
triggers = ["merke dir", "notiere", "speichere"]
|
||||||
|
for t in triggers:
|
||||||
|
if text.lower().startswith(t):
|
||||||
|
fact = text[len(t):].strip()
|
||||||
|
return fact
|
||||||
|
return None
|
||||||
|
|
||||||
|
@app.route("/chat", methods=["POST"])
|
||||||
|
def chat():
|
||||||
|
data = request.json
|
||||||
|
project = data.get("project")
|
||||||
|
message = data.get("message")
|
||||||
|
|
||||||
|
# Lernbefehl
|
||||||
|
fact = extract_fact(message)
|
||||||
|
if fact:
|
||||||
|
arango = get_arango()
|
||||||
|
arango.insert({
|
||||||
|
"_raw": message,
|
||||||
|
"_source": project,
|
||||||
|
"content": fact,
|
||||||
|
"tags": ["skill:merke"],
|
||||||
|
"relations": []
|
||||||
|
})
|
||||||
|
return jsonify({"reply": f"Fakt gespeichert: {fact}"})
|
||||||
|
|
||||||
|
# Chat speichern in MariaDB
|
||||||
|
db = get_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("INSERT INTO chats (project, user_input) VALUES (%s,%s)", (project, message))
|
||||||
|
db.commit()
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Anfrage an LLM
|
||||||
|
resp = requests.post(f"{os.environ['LM_API_URL']}/generate", json={"prompt": message})
|
||||||
|
return jsonify({"reply": resp.json()})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=5000)
|
||||||
4
api/requirements.txt
Normal file
4
api/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
flask
|
||||||
|
mysql-connector-python
|
||||||
|
requests
|
||||||
|
python-arango
|
||||||
96
docker-compose.yml
Normal file
96
docker-compose.yml
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
services:
|
||||||
|
myki-mariadb:
|
||||||
|
image: mariadb:11
|
||||||
|
container_name: myki-mariadb
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
|
MYSQL_DATABASE: llm_projects
|
||||||
|
MYSQL_USER: llmuser
|
||||||
|
MYSQL_PASSWORD: llmpassword
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- ./data/mariadb:/var/lib/mysql
|
||||||
|
- ./initdb:/docker-entrypoint-initdb.d # SQL-Dateien werden hier automatisch beim ersten Start ausgeführt
|
||||||
|
|
||||||
|
myki-phpmyadmin:
|
||||||
|
image: phpmyadmin/phpmyadmin
|
||||||
|
container_name: myki-phpmyadmin
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
PMA_HOST: mariadb
|
||||||
|
PMA_USER: llmuser
|
||||||
|
PMA_PASSWORD: llmpassword
|
||||||
|
ports:
|
||||||
|
- "8081:80"
|
||||||
|
depends_on:
|
||||||
|
- myki-mariadb
|
||||||
|
|
||||||
|
myki-arangodb:
|
||||||
|
image: arangodb:3.12
|
||||||
|
container_name: myki-arangodb
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
ARANGO_ROOT_PASSWORD: rootpassword
|
||||||
|
ports:
|
||||||
|
- "8529:8529"
|
||||||
|
volumes:
|
||||||
|
- ./data/arangodb:/var/lib/arangodb3
|
||||||
|
- ./initdb-arango:/docker-entrypoint-initdb.d # Initialisierung
|
||||||
|
|
||||||
|
# myki-lmstudio:
|
||||||
|
# image: ghcr.io/nomic-ai/ministral-3-14b-instruct-2512
|
||||||
|
# container_name: myki-lmstudio
|
||||||
|
# environment:
|
||||||
|
# LMSTUDIO_API: "1"
|
||||||
|
# ports:
|
||||||
|
# - "8080:8080"
|
||||||
|
# volumes:
|
||||||
|
# - ./lmstudio/models:/models
|
||||||
|
|
||||||
|
myki-api:
|
||||||
|
build: ./api
|
||||||
|
container_name: myki-api
|
||||||
|
depends_on:
|
||||||
|
- myki-mariadb
|
||||||
|
- myki-arangodb
|
||||||
|
#- myki-lmstudio
|
||||||
|
environment:
|
||||||
|
DB_HOST: mariadb
|
||||||
|
DB_USER: llmuser
|
||||||
|
DB_PASSWORD: llmpassword
|
||||||
|
DB_NAME: llm_projects
|
||||||
|
#LM_API_URL: http://lmstudio:8080
|
||||||
|
LM_API_URL: http://host.docker.internal:1234
|
||||||
|
ARANGO_URL: http://arangodb:8529
|
||||||
|
ARANGO_USER: root
|
||||||
|
ARANGO_PASSWORD: rootpassword
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
|
||||||
|
myki-worker:
|
||||||
|
build: ./worker
|
||||||
|
container_name: myki-worker
|
||||||
|
depends_on:
|
||||||
|
- myki-api
|
||||||
|
- myki-mariadb
|
||||||
|
- myki-arangodb
|
||||||
|
#- lmstudio
|
||||||
|
environment:
|
||||||
|
DB_HOST: mariadb
|
||||||
|
DB_USER: llmuser
|
||||||
|
DB_PASSWORD: llmpassword
|
||||||
|
DB_NAME: llm_projects
|
||||||
|
LM_API_URL: http://lmstudio:8080
|
||||||
|
ARANGO_URL: http://arangodb:8529
|
||||||
|
ARANGO_USER: root
|
||||||
|
ARANGO_PASSWORD: rootpassword
|
||||||
|
|
||||||
|
web:
|
||||||
|
build: ./web
|
||||||
|
container_name: llm_web
|
||||||
|
depends_on:
|
||||||
|
- myki-api
|
||||||
|
ports:
|
||||||
|
- "3001:3000"
|
||||||
15
initdb/init.sql
Normal file
15
initdb/init.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE chats (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
project VARCHAR(255),
|
||||||
|
user_input TEXT,
|
||||||
|
llm_output TEXT,
|
||||||
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE feedback (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
chat_id INT,
|
||||||
|
rating ENUM('positive','negative'),
|
||||||
|
comment TEXT,
|
||||||
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
2
web/Dockerfile
Normal file
2
web/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
COPY . /usr/share/nginx/html
|
||||||
17
web/index.html
Normal file
17
web/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Projekt-LLM Chat</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chat-container">
|
||||||
|
<input id="project" placeholder="Projektname">
|
||||||
|
<div id="messages"></div>
|
||||||
|
<input id="message" placeholder="Schreibe eine Nachricht">
|
||||||
|
<button onclick="sendMessage()">Senden</button>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
web/scripts.js
Normal file
12
web/scripts.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
async function sendMessage() {
|
||||||
|
const project = document.getElementById('project').value;
|
||||||
|
const message = document.getElementById('message').value;
|
||||||
|
const res = await fetch('http://localhost:5000/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({project, message})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const messagesDiv = document.getElementById('messages');
|
||||||
|
messagesDiv.innerHTML += `<p><b>Du:</b> ${message}</p><p><b>LLM:</b> ${data.reply}</p>`;
|
||||||
|
}
|
||||||
4
web/style.css
Normal file
4
web/style.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#chat-container { width: 500px; margin: auto; }
|
||||||
|
#messages { border: 1px solid #ccc; height: 400px; overflow-y: scroll; padding: 5px; margin-bottom: 5px; }
|
||||||
|
input { width: 80%; margin-bottom: 5px; }
|
||||||
|
button { width: 18%; }
|
||||||
6
worker/Dockerfile
Normal file
6
worker/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY worker.py .
|
||||||
|
CMD ["python", "worker.py"]
|
||||||
4
worker/requirements.txt
Normal file
4
worker/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
mysql-connector-python
|
||||||
|
requests
|
||||||
|
python-arango
|
||||||
|
schedule
|
||||||
58
worker/worker.py
Normal file
58
worker/worker.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import os
|
||||||
|
import mysql.connector
|
||||||
|
import requests
|
||||||
|
from arango import ArangoClient
|
||||||
|
import schedule
|
||||||
|
import time
|
||||||
|
|
||||||
|
# MariaDB
|
||||||
|
def get_db():
|
||||||
|
return mysql.connector.connect(
|
||||||
|
host=os.environ['DB_HOST'],
|
||||||
|
user=os.environ['DB_USER'],
|
||||||
|
password=os.environ['DB_PASSWORD'],
|
||||||
|
database=os.environ['DB_NAME']
|
||||||
|
)
|
||||||
|
|
||||||
|
# ArangoDB
|
||||||
|
def get_arango():
|
||||||
|
client = ArangoClient(hosts=os.environ['ARANGO_URL'])
|
||||||
|
db = client.db('llm_facts', username=os.environ['ARANGO_USER'], password=os.environ['ARANGO_PASSWORD'])
|
||||||
|
return db.collection('facts')
|
||||||
|
|
||||||
|
# Analysiere neue Chats und überführe sie in ArangoDB
|
||||||
|
def analyze_chats():
|
||||||
|
db = get_db()
|
||||||
|
cursor = db.cursor(dictionary=True)
|
||||||
|
cursor.execute("SELECT * FROM chats WHERE llm_output IS NULL")
|
||||||
|
chats = cursor.fetchall()
|
||||||
|
|
||||||
|
arango = get_arango()
|
||||||
|
|
||||||
|
for chat in chats:
|
||||||
|
# Anfrage an LLM zur Analyse / Fakt-Extraktion
|
||||||
|
resp = requests.post(f"{os.environ['LM_API_URL']}/generate", json={"prompt": f"Extrahiere Fakten und Kontext aus diesem Text:\n{chat['user_input']}"})
|
||||||
|
fact = resp.json()
|
||||||
|
|
||||||
|
# Speichern in ArangoDB
|
||||||
|
arango.insert({
|
||||||
|
"_raw": chat['user_input'],
|
||||||
|
"_source": chat['project'],
|
||||||
|
"content": fact,
|
||||||
|
"tags": ["auto"],
|
||||||
|
"relations": []
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update llm_output in MariaDB
|
||||||
|
cursor.execute("UPDATE chats SET llm_output=%s WHERE id=%s", (fact, chat['id']))
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Alle 30 Sekunden ausführen
|
||||||
|
schedule.every(30).seconds.do(analyze_chats)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(1)
|
||||||
Reference in New Issue
Block a user