diff --git a/backend/Dockerfile b/backend/Dockerfile index 5525a4f..25081a5 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,28 +1,88 @@ -FROM python:3.12 +#FROM python:3.12 +FROM ubuntu RUN apt-get update -RUN apt-get install -y firefox-esr +RUN apt-get install -y python3 +RUN apt-get install -y python3-pip + + +#on debian: +#RUN apt-get install -y firefox-esr +RUN apt-get install -y firefox + RUN apt-get install -y ffmpeg RUN apt-get install -y espeak RUN apt-get install -y flite +#COPY "amdgpu-install_6.1.60103-1_all.deb" "amdgpu-install_6.1.60103-1_all.deb" +#RUN dpkg -i "amdgpu-install_6.1.60103-1_all.deb" +#RUN amdgpu-install -y +#RUN apt-get -y install rocm-device-libs + + +#install "apt-add-repository" command: +RUN apt-get -y install software-properties-common dirmngr apt-transport-https lsb-release ca-certificates +RUN apt-add-repository -r ppa:graphics-drivers/ppa +RUN add-apt-repository ppa:oibaf/graphics-drivers + + +#RUN apt install hipsolver rocm-gdb -y + +#RUN apt-get -y install wget +#RUN apt-get update -y --allow-unauthenticated +#RUN wget https://repo.radeon.com/amdgpu-install/6.1.1/ubuntu/jammy/amdgpu-install_6.1.60101-1_all.deb +#RUN apt-get install ./amdgpu-install_6.1.60101-1_all.deb -y +#RUN amdgpu-install --usecase=rocm + + +#RUN wget https://repo.radeon.com/amdgpu-install/6.1.1/ubuntu/jammy/amdgpu-install_6.1.60101-1_all.deb +#RUN apt install ./amdgpu-install_6.1.60101-1_all.deb -y +#RUN amdgpu-install --usecase=graphics,rocm -y +#RUN usermod -a -G render,video $LOGNAME + + +#RUN wget https://repo.radeon.com/amdgpu-install/6.1.1/ubuntu/jammy/amdgpu-install_6.1.60101-1_all.deb +#RUN apt install ./amdgpu-install_6.1.60101-1_all.deb -y +#RUN amdgpu-install --usecase=graphics,rocm -y + + +#RUN echo "deb http://deb.debian.org/debian/ bookworm main contrib non-free-firmware" > /etc/apt/sources.list +#RUN echo "deb http://deb.debian.org/debian/ bookworm main contrib non-free" > /etc/apt/sources.list + +#RUN apt-add-repository contrib +#RUN apt-add-repository non-free +#RUN apt update -y + +#RUN apt install nvidia-driver -y +#RUN apt-get install firmware-amd-graphics libgl1-mesa-dri libglx-mesa0 mesa-vulkan-drivers xserver-xorg-video-all -y + +RUN apt-get update -y --allow-unauthenticated +RUN apt-get upgrade -y --allow-unauthenticated +RUN apt-get autoremove -y +RUN apt-get autoclean -y + #RUN curl https://ollama.ai/install.sh | sh #RUN ollama run llama2 WORKDIR /code COPY requirements.txt /code/requirements.txt #RUN pip3 install --no-cache-dir --upgrade -r requirements.txt -RUN pip3 install --no-cache-dir -r requirements.txt + +RUN pip3 install --no-cache-dir -r requirements.txt --break-system-packages RUN pip3 freeze > current_requirements.txt COPY . . ENTRYPOINT ["python3", "/code/app.py"] -#ENTRYPOINT ["fastapi", "run", "main.py", "--port", "8000"] #gunicorn -w 4 -b 0.0.0.0 'hello:create_app()' #ENTRYPOINT ["gunicorn", "-w", "1", "-b", "0.0.0.0", "app:create_app()"] - #ENTRYPOINT ["gunicorn", "-w", "1", "-b", "0.0.0.0:5000", "app:create_app()"] #gunicorn app:app --worker-class eventlet -w 1 --bind 0.0.0.0:5000 --reload + +#ENTRYPOINT ["fastapi", "run", "main.py", "--port", "8000"] + +#ENTRYPOINT ["uvicorn", "main:app", "--port", "8000", "--host", "0.0.0.0"] + + diff --git a/backend/amdgpu-install_6.1.60103-1_all.deb b/backend/amdgpu-install_6.1.60103-1_all.deb new file mode 100644 index 0000000..7c0b124 Binary files /dev/null and b/backend/amdgpu-install_6.1.60103-1_all.deb differ diff --git a/backend/app.py b/backend/app.py index c67df6a..0fed00d 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,17 +6,16 @@ OpenAPI access via http://localhost:5000/openapi/ on local docker-compose deploy #------std lib modules:------- import os, sys, json, time import os.path -from typing import Any, Tuple, List, Dict, Any, Callable, Optional +from typing import Any, Tuple, List, Dict, Any, Callable, Optional, Union from datetime import datetime, date -#from collections import namedtuple -import hashlib, traceback, logging +import logging from functools import wraps -import base64 #-------ext libs-------------- -from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors -from elasticsearch_dsl import Search, A, Document, Date, Integer, Keyword, Float, Long, Text, connections +#from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors +#from elasticsearch_dsl import A, Document, Date, Integer, Keyword, Float, Long, Text, connections +from elasticsearch_dsl import connections from pydantic import BaseModel, Field import jwt as pyjwt @@ -172,10 +171,10 @@ def sockcon(data): class SocketMessage(BaseModel): - room: str = Field(None, description="Status Code") question: str = Field(None, description="Status Code") system_prompt: str = Field(None, description="Status Code") bot_id: str = Field(None, description="Status Code") + room: Union[str, None] # = Field(None, description="Status Code") #TODO: pydantic message type validation diff --git a/backend/lib/elastictools.py b/backend/lib/elastictools.py index 86850a1..d4c6399 100644 --- a/backend/lib/elastictools.py +++ b/backend/lib/elastictools.py @@ -67,19 +67,11 @@ def get_type_schema(): def wait_for_elasticsearch(): - #TODO: find a clean way to wait without exceptions! - #Wait for elasticsearch to start up! - #elastic_url = os.getenv("ELASTIC_URI") - #assert elastic_url i = 1 while True: try: - #client = Elasticsearch(hosts=elastic_url) client = connections.get_connection() client.indices.get_alias(index="*") - #connections.create_connection(hosts=app.config['elastic_uri']) - #connections.get_connection().cluster.health(wait_for_status='yellow') - #init_indicies() print("Elasticsearch found! Run Flask-app!", flush=True) return except ConnectionError: diff --git a/backend/lib/mail.py b/backend/lib/mail.py index 75ca1fa..bab70d1 100644 --- a/backend/lib/mail.py +++ b/backend/lib/mail.py @@ -1,7 +1,6 @@ from smtplib import * from email.mime.text import MIMEText - def send_mail(target_mail, subject, sender_mail, msg): msg = MIMEText(msg) diff --git a/backend/lib/webbot.py b/backend/lib/webbot.py index e0a3c11..42f5f4b 100644 --- a/backend/lib/webbot.py +++ b/backend/lib/webbot.py @@ -7,11 +7,9 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC #from selenium.webdriver.chrome.options import Options - from webdriver_manager.firefox import GeckoDriverManager from selenium.webdriver.firefox.service import Service as FirefoxService - from tempfile import mkdtemp from time import sleep from bs4 import BeautifulSoup diff --git a/backend/main.py b/backend/main.py index 96b8355..18caf5b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,119 +1,151 @@ + + +""" +OpenAPI access via http://localhost:5000/openapi/ on local docker-compose deployment +""" + +#------std lib modules:------- +import os, sys, json, time +import os.path +from typing import Any, Tuple, List, Dict, Any, Callable, Optional +from datetime import datetime, date +import logging +from functools import wraps + +#-------ext libs-------------- + +from elasticsearch_dsl import connections + +from pydantic import BaseModel, Field +import jwt as pyjwt + +import asyncio + +#----------home grown-------------- +from lib.funcs import group_by +from lib.elastictools import get_by_id, wait_for_elasticsearch +from lib.models import init_indicies, Chatbot, User, Text +from lib.chatbot import ask_bot, ask_bot2, train_text, download_llm +from lib.speech import text_to_speech +from lib.mail import send_mail +from lib.user import hash_password, create_user, create_default_users + from fastapi import FastAPI from fastapi.responses import HTMLResponse +from fastapi_socketio import SocketManager + from jinja2 import Environment, FileSystemLoader -from pydantic import BaseModel - -from neo4j import GraphDatabase - -import os, sys -from multiprocessing import Pool -from bs4 import BeautifulSoup -import requests -from webbot import * #Bot, innerHTML -from xing import * -env = Environment(loader=FileSystemLoader('templates')) +BOT_ROOT_PATH = os.getenv("BOT_ROOT_PATH") +assert BOT_ROOT_PATH + +ollama_url = os.getenv("OLLAMA_URI") +assert ollama_url + +elastic_url = os.getenv("ELASTIC_URI") +assert elastic_url + +jwt_secret = os.getenv("SECRET") +assert jwt_secret + + + app = FastAPI() +socket_manager = SocketManager(app=app) + + + +@app.sio.on('connect') +async def sockcon(sid, data): + """ + put every connection into it's own room + to avoid broadcasting messages + answer in callback only to room with sid + """ + room = request.sid + request.remote_addr + join_room(room) + await app.sio.emit('backend response', {'msg': f'Connected to room {room} !', "room": room}) # looks like iOS needs an answer + + + +@app.sio.on('client message') +async def handle_message(sid, message): + + #try: + room = message["room"] + question = message["question"] + system_prompt = message["system_prompt"] + bot_id = message["bot_id"] + + start = datetime.now().timestamp() + d = ask_bot2(system_prompt + " " + question, bot_id) + + def get_scores(*args): + score_docs = d["get_score_docs"]() + return score_docs + + + def do_streaming(*args): + start_stream = datetime.now().timestamp() + for chunk in d["answer_generator"](): + socket.emit('backend token', {'data': chunk, "done": False}, to=room) + stream_duration = round(datetime.now().timestamp() - start_stream, 2) + print("Stream duration: ", stream_duration, flush=True) + + + + [score_docs, _] = await asyncio.gather( + asyncio.to_thread(get_scores, 1,2,3), + asyncio.to_thread(do_streaming, 1,2,3) + ) + + + await app.sio.emit.emit('backend token', { + 'done': True, + "score_docs": score_docs + }, to=room) + + duration = round(datetime.now().timestamp() - start, 2) + print("Total duration: ", duration, flush=True) + + + + + +#@app.sio.on('join') +#async def handle_join(sid, *args, **kwargs): +# await app.sio.emit('lobby', 'User joined') + + +#@sm.on('leave') +#async def handle_leave(sid, *args, **kwargs): +# await sm.emit('lobby', 'User left') + + + + + + + + class JobSearch(BaseModel): location: str language: str - -def xing_job_search(location: str, radius: int) -> list: - with Bot() as bot: - vars_ = { - "page": 1, - "filter.industry%5B%5D": 90000, - "filter.type%5B%5D": "FULL_TIME", - "filter.level%5B%5D": 2, - "location": location, - "radius": radius - } - start_url = "https://www.xing.com/jobs/search?" + "&".join([k + "=" + str(v) for k, v in vars_.items()]) - - - def kill_cookie_questions(): - bot.click_id("consent-accept-button") - - - def next_page(): - nav = bot.get_elements_by_tag_name("nav")[1] - next_site_link = get_elements_by_tag_name(nav, "a")[-1] - bot.click(next_site_link) - - def get_nr_pages(): - nav = bot.get_elements_by_tag_name("nav")[1] - return int(get_elements_by_tag_name(nav, "a")[-2].text) - - def get_items(): - rs = [] - for article in bot.get_elements_by_tag_name("article"): - rs.append( get_children(article)[0].get_attribute("href") ) - return rs - - return collect_pagination_items(bot, start_url, next_page, get_nr_pages, get_items, kill_cookie_questions) - - - - - -""" -pwd = "neo4j2" -proto = "bolt" -host = "192.168.99.101" - -driver = GraphDatabase.driver("%s://%s:7687" % (proto, host), auth=("neo4j", pwd), encrypted=False) - -def add_friend(tx, name, friend_name): - tx.run("MERGE (a:Person {name: $name}) " - "MERGE (a)-[:KNOWS]->(friend:Person {name: $friend_name})", - name=name, friend_name=friend_name) - -def print_friends(tx, name): - for record in tx.run("MATCH (a:Person)-[:KNOWS]->(friend) WHERE a.name = $name " - "RETURN friend.name ORDER BY friend.name", name=name): - print(record["friend.name"]) - -with driver.session() as session: - session.write_transaction(add_friend, "Arthur", "Guinevere") - session.write_transaction(add_friend, "Arthur", "Lancelot") - session.write_transaction(add_friend, "Arthur", "Merlin") - session.read_transaction(print_friends, "Arthur") - -driver.close() -""" - - @app.post("/search") def job_search(js: JobSearch): - #https://berlinstartupjobs.com/?s=python&page=3 location = "Berlin" radius = 50 - with Bot() as bot: - vars_ = { - "page": 1, - "filter.industry%5B%5D": 90000, - "filter.type%5B%5D": "FULL_TIME", - "filter.level%5B%5D": 2, - "location": location, - "radius": radius - } - start_url = "https://www.xing.com/jobs/search?" + "&".join([k + "=" + str(v) for k, v in vars_.items()]) - - bot.set_url(start_url) - return bot.get_page_content() - - @app.get("/") diff --git a/backend/public/index.html b/backend/public/index.html index fb7b440..32dbe0a 100644 --- a/backend/public/index.html +++ b/backend/public/index.html @@ -182,6 +182,8 @@ +
+
@@ -270,11 +272,13 @@ +
+ + +
- -
diff --git a/backend/requirements.txt b/backend/requirements.txt index 60714c6..45091a1 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,37 +1,39 @@ webdriver_manager -requests -selenium +requests==2.32.3 +selenium==4.23.1 bs4 -elasticsearch -elasticsearch-dsl +elasticsearch==8.15.0 +elasticsearch-dsl==8.15.1 -ollama -langchain -langchain-community +ollama==0.3.1 +langchain==0.2.14 +langchain-community==0.2.12 langchain_ollama -langchain-elasticsearch +langchain-elasticsearch==0.2.2 -pydantic +pydantic==2.8.2 + +uvicorn +fastapi==0.112.2 +fastapi-socketio -fastapi - -gunicorn -Werkzeug +gunicorn==23.0.0 +Werkzeug==3.0.4 flask -Flask-SocketIO -flask-openapi3 +Flask-SocketIO==5.3.6 +flask-openapi3==3.1.3 -minio +minio==7.2.8 -python-logging-loki +python-logging-loki==0.3.1 pyjwt -cryptography +#cryptography==43.0.0 -neo4j +neo4j==5.23.1 -pyttsx3 +pyttsx3==2.91 diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index faa512b..09a14e9 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -70,6 +70,11 @@ services: networks: - llm_network #command: "ollama pull llama2" + devices: + #- /dev/dri/renderD128:/dev/dri/renderD128 + #- /dev/fdk + - /dev/dri + #ollama-webui: # container_name: ${APP_PREFIX}_ollama-webui