more overhaul
All checks were successful
Gitea Docker Redeploy / Redploy-App-on-self-via-SSH (push) Successful in 1m40s

This commit is contained in:
Tobias Weise 2024-08-27 22:39:29 +02:00
parent 955b71459c
commit 2d227d438c
13 changed files with 530 additions and 624 deletions

View File

@ -13,12 +13,6 @@ docker-compose build
docker-compose up -d docker-compose up -d
``` ```
After deploy:
### WebUI for Ollama:
* http://localhost:8888
* use to install model llama3 (or more https://ollama.com/library)
---- ----
## Usage ## Usage
@ -43,7 +37,6 @@ After deploy:
### Backend ### Backend
* FastAPI * FastAPI
* RabbitMQ/Kafka? * RabbitMQ/Kafka?
* OpenSearch

View File

@ -13,10 +13,16 @@ WORKDIR /code
COPY requirements.txt /code/requirements.txt COPY requirements.txt /code/requirements.txt
#RUN pip3 install --no-cache-dir --upgrade -r 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
RUN pip3 freeze > current_requirements.txt
COPY . . COPY . .
ENTRYPOINT ["python3", "/code/app.py"] ENTRYPOINT ["python3", "/code/app.py"]
#ENTRYPOINT ["fastapi", "run", "main.py", "--port", "8000"] #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

View File

@ -1,10 +1,7 @@
""" """
OpenAPI access via http://localhost:5000/openapi/ on local docker-compose deployment OpenAPI access via http://localhost:5000/openapi/ on local docker-compose deployment
""" """
#import warnings
#warnings.filterwarnings("ignore")
#------std lib modules:------- #------std lib modules:-------
import os, sys, json, time import os, sys, json, time
@ -17,50 +14,26 @@ from functools import wraps
import base64 import base64
#-------ext libs-------------- #-------ext libs--------------
#llm
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
#import tiktoken
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain.callbacks.base import BaseCallbackHandler, BaseCallbackManager
from langchain.prompts import PromptTemplate
from langchain_community.llms import Ollama
from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader
from langchain_community.embeddings import OllamaEmbeddings
#from langchain_community.vectorstores.elasticsearch import ElasticsearchStore #deprecated
from langchain_elasticsearch import ElasticsearchStore
from uuid import uuid4
from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors 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_dsl import Search, A, Document, Date, Integer, Keyword, Float, Long, Text, connections
from elasticsearch.exceptions import ConnectionError
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
import logging_loki
import jwt as pyjwt import jwt as pyjwt
#flask, openapi #flask, openapi
from flask import Flask, send_from_directory, send_file, Response, request, jsonify from flask import Flask, send_from_directory, send_file, Response, request, jsonify
from flask_cors import CORS, cross_origin from flask_openapi3 import Info, Tag, OpenAPI, Server #FileStorage
from werkzeug.utils import secure_filename
from flask_openapi3 import Info, Tag, OpenAPI, Server, FileStorage
from flask_socketio import SocketIO, join_room, leave_room, rooms, send from flask_socketio import SocketIO, join_room, leave_room, rooms, send
#from werkzeug.utils import secure_filename
from cryptography.fernet import Fernet import asyncio
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
#----------home grown-------------- #----------home grown--------------
from lib.funcs import group_by from lib.funcs import group_by
from lib.elastictools import get_by_id, update_by_id, delete_by_id, wait_for_elasticsearch from lib.elastictools import get_by_id, wait_for_elasticsearch
from lib.models import init_indicies, QueryLog, Chatbot, User, Text from lib.models import init_indicies, Chatbot, User, Text
from lib.chatbot import ask_bot, train_text, download_llm from lib.chatbot import ask_bot, ask_bot2, train_text, download_llm
from lib.speech import text_to_speech from lib.speech import text_to_speech
from lib.mail import send_mail from lib.mail import send_mail
from lib.user import hash_password, create_user, create_default_users from lib.user import hash_password, create_user, create_default_users
@ -69,6 +42,15 @@ from lib.user import hash_password, create_user, create_default_users
BOT_ROOT_PATH = os.getenv("BOT_ROOT_PATH") BOT_ROOT_PATH = os.getenv("BOT_ROOT_PATH")
assert 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
# JWT Bearer Sample # JWT Bearer Sample
jwt = { jwt = {
@ -80,11 +62,24 @@ security_schemes = {"jwt": jwt}
security = [{"jwt": []}] security = [{"jwt": []}]
def get_module_versions():
with open("requirements.txt", "r") as f:
modules = {line.split("#")[0] for line in f.read().split("\n") if line.split("#")[0] != ""}
with open("current_requirements.txt", "r") as f:
d = {k: v for (k, v) in [line.split("#")[0].split("==") for line in f.read().split("\n") if line.split("#")[0] != ""]}
return {k: v for k, v in d.items() if k in modules}
import multiprocessing
cpus = multiprocessing.cpu_count()
info = Info( info = Info(
title="Chatbot-API", title="CreativeBots-API",
version="1.0.0", version="1.0.0",
summary="The REST-API", summary="The REST-API to manage bots, users and more!",
description="Default model: ..." description="CPUs: " + str(cpus) + "<br>" + json.dumps(get_module_versions(), indent=4).replace("\n", "<br>")
) )
servers = [ servers = [
Server(url=BOT_ROOT_PATH ) Server(url=BOT_ROOT_PATH )
@ -108,6 +103,9 @@ def uses_jwt(required=True):
Wraps routes in a jwt-required logic and passes decoded jwt and user from elasticsearch to the route as keyword Wraps routes in a jwt-required logic and passes decoded jwt and user from elasticsearch to the route as keyword
""" """
jwt_secret = os.getenv("SECRET")
assert jwt_secret
def non_param_deco(f): def non_param_deco(f):
@wraps(f) @wraps(f)
def decorated_route(*args, **kwargs): def decorated_route(*args, **kwargs):
@ -115,7 +113,6 @@ def uses_jwt(required=True):
if "Authorization" in request.headers: if "Authorization" in request.headers:
token = request.headers["Authorization"].split(" ")[1] token = request.headers["Authorization"].split(" ")[1]
if not token: if not token:
if required: if required:
return jsonify({ return jsonify({
@ -130,7 +127,7 @@ def uses_jwt(required=True):
try: try:
data = pyjwt.decode(token, app.config["jwt_secret"], algorithms=["HS256"]) data = pyjwt.decode(token, jwt_secret, algorithms=["HS256"])
except Exception as e: except Exception as e:
return jsonify({ return jsonify({
'status': 'error', 'status': 'error',
@ -160,24 +157,6 @@ def uses_jwt(required=True):
env_to_conf = {
"ELASTIC_URI": "elastic_uri",
"SECRET": "jwt_secret"
}
#import values from env into flask config and do existence check
for env_key, conf_key in env_to_conf.items():
x = os.getenv(env_key)
if not x:
msg = "Environment variable '%s' not set!" % env_key
app.logger.fatal(msg)
sys.exit(1)
else:
app.config[conf_key] = x
socket = SocketIO(app, cors_allowed_origins="*") socket = SocketIO(app, cors_allowed_origins="*")
@socket.on('connect') @socket.on('connect')
@ -192,24 +171,60 @@ def sockcon(data):
socket.emit('backend response', {'msg': f'Connected to room {room} !', "room": room}) # looks like iOS needs an answer socket.emit('backend response', {'msg': f'Connected to room {room} !', "room": room}) # looks like iOS needs an answer
#TODO: pydantic message type validation 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")
#TODO: pydantic message type validation
@socket.on('client message') @socket.on('client message')
def handle_message(message): def handle_message(message):
SocketMessage.model_validate(message)
#try: #try:
room = message["room"] room = message["room"]
question = message["question"] question = message["question"]
system_prompt = message["system_prompt"] system_prompt = message["system_prompt"]
bot_id = message["bot_id"] bot_id = message["bot_id"]
#except: start = datetime.now().timestamp()
# return d = ask_bot2(system_prompt + " " + question, bot_id)
for chunk in ask_bot(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) socket.emit('backend token', {'data': chunk, "done": False}, to=room)
socket.emit('backend token', {'done': True}, to=room) stream_duration = round(datetime.now().timestamp() - start_stream, 2)
print("Stream duration: ", stream_duration, flush=True)
async def f():
ls = await asyncio.gather(
asyncio.to_thread(get_scores, 1,2,3),
asyncio.to_thread(do_streaming, 1,2,3)
)
return ls
[score_docs, _] = asyncio.run(f())
socket.emit('backend token', {
'done': True,
"score_docs": score_docs
}, to=room)
duration = round(datetime.now().timestamp() - start, 2)
print("Total duration: ", duration, flush=True)
#======================= TAGS ============================= #======================= TAGS =============================
@ -240,8 +255,7 @@ def login(form: LoginRequest):
'message': msg 'message': msg
}), 400 }), 400
client = Elasticsearch(app.config['elastic_uri']) match get_by_id(index="user", id_field_name="email", id_value=form.email):
match get_by_id(client, index="user", id_field_name="email", id_value=form.email):
case []: case []:
msg = "User with email '%s' doesn't exist!" % form.email msg = "User with email '%s' doesn't exist!" % form.email
app.logger.error(msg) app.logger.error(msg)
@ -252,7 +266,7 @@ def login(form: LoginRequest):
case [user]: case [user]:
if user["password_hash"] == hash_password(form.password + form.email): if user["password_hash"] == hash_password(form.password + form.email):
token = pyjwt.encode({"email": form.email}, app.config['jwt_secret'], algorithm="HS256") token = pyjwt.encode({"email": form.email}, jwt_secret, algorithm="HS256")
#app.logger.info(token) #app.logger.info(token)
return jsonify({ return jsonify({
'status': 'success', 'status': 'success',
@ -331,14 +345,50 @@ class GetSpeechRequest(BaseModel):
@app.post('/text2speech', summary="", tags=[], security=security) @app.post('/text2speech', summary="", tags=[], security=security)
def text2speech(form: GetSpeechRequest): def text2speech(form: GetSpeechRequest):
file_name = text_to_speech(form.text) file_name = text_to_speech(form.text)
#return send_file(file_path, mimetype='audio/mpeg') #, attachment_filename= 'Audiofiles.zip', as_attachment = True)
return jsonify({ return jsonify({
"status": "success", "status": "success",
"file": "/" + file_name "file": "/" + file_name
}) })
#============ Bot CRUD ===============
class CreateBotRequest(BaseModel):
name: str = Field(None, description="The bot's name")
visibility: str = Field('private', description="The bot's visibility to other users ('private', 'public')")
description: str = Field('', description="The bot's description of purpose and being")
system_prompt: str = Field('', description="The bot's defining system prompt")
llm_model: str = Field("llama3", description="The bot's used LLM")
#status = Keyword()
#temperature = Float()
@app.post('/bot', summary="", tags=[bot_tag], security=security)
@uses_jwt()
def create_bot(form: CreateBotRequest, decoded_jwt, user):
"""
Creates a chatbot for the JWT associated user.
"""
CreateBotRequest.model_validate(form)
bot = Chatbot()
bot.name = form.name
bot.visibility = form.visibility
bot.description = form.description
bot.system_prompt = form.system_prompt
bot.llm_model = form.llm_model
#add meta data
bot.creation_date = datetime.now()
bot.creator_id = user.meta.id
bot.save()
return jsonify({
"bot_id": bot.meta.id
})
class GetBotRequest(BaseModel): class GetBotRequest(BaseModel):
id: str = Field(None, description="The bot's id") id: str = Field(None, description="The bot's id")
@ -395,25 +445,23 @@ def get_bots(query: GetBotRequest, decoded_jwt, user):
class UpdateBotRequest(BaseModel):
class CreateBotRequest(BaseModel): id: str = Field(None, description="The bot's id")
name: str = Field(None, description="The bot's name") name: str = Field(None, description="The bot's name")
visibility: str = Field('private', description="The bot's visibility to other users ('private', 'public')") visibility: str = Field(None, description="The bot's visibility to other users ('private', 'public')")
description: str = Field('', description="The bot's description of purpose and being") description: str = Field(None, description="The bot's description of purpose and being")
system_prompt: str = Field('', description="The bot's defining system prompt") system_prompt: str = Field(None, description="The bot's defining system prompt")
llm_model: str = Field("llama3", description="The bot's used LLM") llm_model: str = Field(None, description="The bot's used LLM")
#status = Keyword()
#temperature = Float()
@app.post('/bot', summary="", tags=[bot_tag], security=security) @app.put('/bot', summary="", tags=[bot_tag], security=security)
@uses_jwt() @uses_jwt()
def create_bot(form: CreateBotRequest, decoded_jwt, user): def update_bot(form: UpdateBotRequest, decoded_jwt, user):
""" """
Creates a chatbot for the JWT associated user. Change a chatbot via it's id
""" """
bot = Chatbot() bot = Chatbot.get(id=form.id)
bot.name = form.name bot.name = form.name
bot.visibility = form.visibility bot.visibility = form.visibility
bot.description = form.description bot.description = form.description
@ -421,16 +469,16 @@ def create_bot(form: CreateBotRequest, decoded_jwt, user):
bot.llm_model = form.llm_model bot.llm_model = form.llm_model
#add meta data #add meta data
bot.creation_date = datetime.now() bot.changed_date = datetime.now()
bot.creator_id = user.meta.id
bot.save() bot.save()
return jsonify({ return jsonify({
"bot_id": bot.meta.id "status": "success"
}) })
class DeleteBotRequest(BaseModel): class DeleteBotRequest(BaseModel):
id: str = Field(None, description="The bot's id") id: str = Field(None, description="The bot's id")
@ -442,26 +490,13 @@ def delete_bot(form: DeleteBotRequest, decoded_jwt, user):
""" """
bot = Chatbot.get(id=form.id) bot = Chatbot.get(id=form.id)
bot.delete() bot.delete()
return jsonify({ return jsonify({
"status": "success" "status": "success"
}) })
class UpdateBotRequest(BaseModel):
id: str = Field(None, description="The bot's id")
@app.put('/bot', summary="", tags=[bot_tag], security=security)
@uses_jwt()
def update_bot(form: UpdateBotRequest, decoded_jwt, user):
"""
Changes a chatbot
"""
return jsonify({
"status": "success"
})
#============================================================================
class AskBotRequest(BaseModel): class AskBotRequest(BaseModel):
@ -469,94 +504,53 @@ class AskBotRequest(BaseModel):
question: str = Field(None, description="The question the bot should answer") question: str = Field(None, description="The question the bot should answer")
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
@app.get('/bot/ask', summary="", tags=[bot_tag], security=security) @app.get('/bot/ask', summary="", tags=[bot_tag], security=security)
@uses_jwt() @uses_jwt()
def query_bot(query: AskBotRequest, decoded_jwt, user): def query_bot(query: AskBotRequest, decoded_jwt, user):
""" """
Asks a chatbot Asks a chatbot
""" """
start = datetime.now().timestamp()
bot_id = query.bot_id bot_id = query.bot_id
prompt = query.question question = query.question
system_prompt = ( start = datetime.now().timestamp()
"Antworte freundlich, mit einer ausführlichen Erklärung, sofern vorhanden auf Basis der folgenden Informationen. Please answer in the language of the question."
"\n\n" d = ask_bot2(system_prompt + " " + question, bot_id)
"{context}"
def get_scores(*args):
score_docs = d["get_score_docs"]()
return score_docs
def do_streaming(*args):
start_stream = datetime.now().timestamp()
answer = ""
for chunk in d["answer_generator"]():
answer += chunk
stream_duration = round(datetime.now().timestamp() - start_stream, 2)
print("Stream duration: ", stream_duration, flush=True)
return answer
async def f():
ls = await asyncio.gather(
asyncio.to_thread(get_scores, 1,2,3),
asyncio.to_thread(do_streaming, 1,2,3)
) )
return ls
[score_docs, answer] = asyncio.run(f())
ch_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
("human", "{input}"),
]
)
ollama_url = os.getenv("OLLAMA_URI")
embeddings = OllamaEmbeddings(model="llama3", base_url=ollama_url)
vector_store = ElasticsearchStore(
es_url=app.config['elastic_uri'],
index_name= "chatbot_" + bot_id.lower(),
distance_strategy="COSINE",
embedding=embeddings
)
bot = Chatbot.get(id=bot_id)
llm = Ollama(
model=bot.llm_model,
base_url=ollama_url
)
k = 4
scoredocs = vector_store.similarity_search_with_score(prompt, k=k)
retriever = vector_store.as_retriever()
question_answer_chain = create_stuff_documents_chain(llm, ch_prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
r = ""
#for chunk in rag_chain.stream({"input": "What is Task Decomposition?"}):
for chunk in rag_chain.stream({"input": prompt}):
print(chunk, flush=True)
if "answer" in chunk:
r += chunk["answer"]
#for chunk in ask_bot(question=query.question, bot_id=query.bot_id):
# r += chunk
xs = []
for doc, score in scoredocs:
#print(doc.__dict__, flush=True)
#print(doc, flush=True)
xs.append([dict(doc), score])
duration = round(datetime.now().timestamp() - start, 2) duration = round(datetime.now().timestamp() - start, 2)
print("Total duration: ", duration, flush=True)
app.logger.info(duration) app.logger.info(duration)
return jsonify({ return jsonify({
"answer": r, "answer": answer,
"duration": str(duration), "duration": str(duration),
#"docs": ls#, #"docs": ls#,
"score_docs": xs "score_docs": xs
@ -608,12 +602,14 @@ def index():
@app.route('/<path:path>') #generische Route (auch Unterordner) @app.route('/<path:path>') #generische Route (auch Unterordner)
def catchAll(path): def catchAll(path):
#return send_from_directory('.', path)
return send_from_directory('./public', path) return send_from_directory('./public', path)
#import logging_loki
def main():
def create_app():
LOG_LEVEL = os.getenv("LOG_LEVEL") LOG_LEVEL = os.getenv("LOG_LEVEL")
if LOG_LEVEL: if LOG_LEVEL:
logging.basicConfig(level=eval("logging." + LOG_LEVEL)) logging.basicConfig(level=eval("logging." + LOG_LEVEL))
@ -621,32 +617,16 @@ def main():
logging.basicConfig(level=logging.WARN) logging.basicConfig(level=logging.WARN)
#TODO: implement some kind of logging mechanism #TODO: implement some kind of logging mechanism
download_llm("llama3")
""" connections.create_connection(hosts=elastic_url, request_timeout=60)
USE_LOKI_LOGGER = os.getenv("USE_LOKI_LOGGER")
if USE_LOKI_LOGGER:
handler = logging_loki.LokiHandler(
url="http://loki:3100/loki/api/v1/push",
tags={"application": "CreativeBots"},
#auth=("username", "password"),
version="1",
)
app.logger.addHandler(handler)
"""
#wait_for_elasticsearch()
download_llm()
connections.create_connection(hosts=app.config['elastic_uri'], request_timeout=60)
wait_for_elasticsearch() wait_for_elasticsearch()
init_indicies() init_indicies()
create_default_users() create_default_users()
app.run(debug=False, threaded=True, host='0.0.0.0')
return app
if __name__ == '__main__': if __name__ == '__main__':
main() app = create_app()
app.run(debug=False, host='0.0.0.0')

View File

@ -6,18 +6,9 @@ from uuid import uuid4
from collections import namedtuple from collections import namedtuple
import os, hashlib, traceback, logging import os, hashlib, traceback, logging
from datetime import datetime, date from datetime import datetime, date
#from elasticsearch_dsl import Document, Date, Integer, Keyword, Text, connections
from elasticsearch_dsl import connections from elasticsearch_dsl import connections
#from langchain.callbacks.manager import CallbackManager
#from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
#import tiktoken
from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.text_splitter import RecursiveCharacterTextSplitter
#from langchain.chains import RetrievalQA
#from langchain.callbacks.base import BaseCallbackHandler, BaseCallbackManager
#from langchain.prompts import PromptTemplate
from langchain_community.llms import Ollama from langchain_community.llms import Ollama
from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader
@ -29,14 +20,9 @@ from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate from langchain_core.prompts import ChatPromptTemplate
from lib.models import Chatbot, Text, User from lib.models import Chatbot, Text, User
ollama_url = os.getenv("OLLAMA_URI") ollama_url = os.getenv("OLLAMA_URI")
elastic_url = os.getenv("ELASTIC_URI") elastic_url = os.getenv("ELASTIC_URI")
@ -51,13 +37,19 @@ def train_text(bot_id, text):
""" """
Caution: Long running request! Caution: Long running request!
""" """
txt_md5 = hashlib.md5(text.encode()).hexdigest()
t = Text.get(id=txt_md5, ignore=404)
if t is not None:
return True
else:
bot = Chatbot.get(id=bot_id) bot = Chatbot.get(id=bot_id)
user = User.get(id=bot.creator_id) user = User.get(id=bot.creator_id)
t = Text(meta={'id': txt_md5})
t = Text() t = Text()
t.text = text t.text = text
t.md5 = hashlib.md5(text.encode()).hexdigest() t.md5 = txt_md5
#add meta data #add meta data
t.creation_date = datetime.now() t.creation_date = datetime.now()
@ -110,13 +102,72 @@ def ask_bot(question, bot_id):
#connections.get_connection() #connections.get_connection()
#if es.indices.exists(index="index"): #if es.indices.exists(index="index"):
def ask_bot2(question, bot_id): def ask_bot2(question, bot_id):
"""
Asks a chatbot
"""
bot = Chatbot.get(id=bot_id)
prompt = question
system_prompt = bot.system_prompt + "\n\n{context}"
rag_index = "chatbot_" + bot_id.lower()
if connections.get_connection().indices.exists(index=rag_index):
vector_store = ElasticsearchStore(
es_url=elastic_url,
index_name=rag_index,
distance_strategy="COSINE",
embedding=OllamaEmbeddings(model=bot.llm_model, base_url=ollama_url)
)
def gen_func():
llm = Ollama(
model=bot.llm_model,
base_url=ollama_url
)
ch_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
("human", "{input}"),
]
)
retriever = vector_store.as_retriever()
question_answer_chain = create_stuff_documents_chain(llm, ch_prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
for chunk in rag_chain.stream({"input": prompt}):
print(chunk, flush=True)
if "answer" in chunk:
yield chunk["answer"]
def get_score_docs():
k = 4
start_vec_search = datetime.now().timestamp()
scoredocs = vector_store.similarity_search_with_score(prompt, k=k)
vec_search_duration = round(datetime.now().timestamp() - start_vec_search, 2)
print("Vec search duration: ", vec_search_duration, flush=True)
xs = []
for doc, score in scoredocs:
#print(doc.__dict__, flush=True)
#print(doc, flush=True)
xs.append([score, dict(doc)])
return xs
return {
"answer_generator": gen_func,
"get_score_docs": get_score_docs
}
else:
def gen_func():
bot = Chatbot.get(id=bot_id) bot = Chatbot.get(id=bot_id)
llm = Ollama( llm = Ollama(
model=bot.llm_model, model=bot.llm_model,
@ -126,93 +177,17 @@ def ask_bot2(question, bot_id):
for chunk in llm.stream(query): for chunk in llm.stream(query):
yield chunk yield chunk
return {
"answer_generator": gen_func,
"get_score_docs": lambda: []
}
#def query_bot(query: AskBotRequest, decoded_jwt, user):
"""
Asks a chatbot
"""
start = datetime.now().timestamp()
bot_id = query.bot_id
prompt = query.question
system_prompt = (
"Antworte freundlich, mit einer ausführlichen Erklärung, sofern vorhanden auf Basis der folgenden Informationen. Please answer in the language of the question."
"\n\n"
"{context}"
)
ch_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
("human", "{input}"),
]
)
embeddings = OllamaEmbeddings(model="llama3", base_url=ollama_url)
vector_store = ElasticsearchStore(
es_url=app.config['elastic_uri'],
index_name= "chatbot_" + bot_id.lower(),
distance_strategy="COSINE",
embedding=embeddings
)
bot = Chatbot.get(id=bot_id)
llm = Ollama(
model=bot.llm_model,
base_url=ollama_url
)
k = 4
scoredocs = vector_store.similarity_search_with_score(prompt, k=k)
retriever = vector_store.as_retriever()
question_answer_chain = create_stuff_documents_chain(llm, ch_prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
r = ""
#for chunk in rag_chain.stream({"input": "What is Task Decomposition?"}):
for chunk in rag_chain.stream({"input": prompt}):
print(chunk, flush=True)
if "answer" in chunk:
r += chunk["answer"]
#for chunk in ask_bot(question=query.question, bot_id=query.bot_id):
# r += chunk
xs = []
for doc, score in scoredocs:
#print(doc.__dict__, flush=True)
#print(doc, flush=True)
xs.append([dict(doc), score])
duration = round(datetime.now().timestamp() - start, 2)
#app.logger.info(duration)
#return jsonify({
# "answer": r,
# "duration": str(duration),
# #"docs": ls#,
# "score_docs": xs
#})
from ollama import Client as OllamaClient from ollama import Client as OllamaClient
def download_llm(model="llama3"): def download_llm(model):
#print(ollama_url, flush=True) #print(ollama_url, flush=True)
#ollama_client = OllamaClient(host=ollama_url) #ollama_client = OllamaClient(host=ollama_url)
@ -224,7 +199,3 @@ def download_llm(model="llama3"):
s = """curl %s/api/pull -d '{ "name": "%s" }' """ % (ollama_url, model) s = """curl %s/api/pull -d '{ "name": "%s" }' """ % (ollama_url, model)
print( os.system(s.strip()) ,flush=True) print( os.system(s.strip()) ,flush=True)

View File

@ -5,34 +5,22 @@ import time, json, os
from typing import Any, Tuple, List, Dict, Any, Callable, Optional from typing import Any, Tuple, List, Dict, Any, Callable, Optional
from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors
from elasticsearch_dsl import Search, A, UpdateByQuery, Document, Date, Integer, Keyword, Float, Long, Text, connections from elasticsearch_dsl import Search, A, UpdateByQuery, Document, Date, Integer, Keyword, Float, Long, Text, connections
from elasticsearch.exceptions import ConnectionError
def get_by_id(client: Elasticsearch, index: str, id_field_name: str, id_value: str): def get_by_id(index: str, id_field_name: str, id_value: str):
client = connections.get_connection()
response = Search(using=client, index=index).filter("term", **{id_field_name: id_value})[0:10000].execute() response = Search(using=client, index=index).filter("term", **{id_field_name: id_value})[0:10000].execute()
return [hit.to_dict() for hit in response] return [hit.to_dict() for hit in response]
def update_by_id(client: Elasticsearch, index: str, id_field_name: str, id_value: str, values_to_set: Dict[str, Any]) -> None: def update_by_id(index: str, id_field_name: str, id_value: str, values_to_set: Dict[str, Any]) -> None:
client = connections.get_connection()
#create painless insert script #create painless insert script
source = "" source = ""
for k, v in values_to_set.items(): for k, v in values_to_set.items():
source += f"ctx._source.{k} = {json.dumps(v)};" source += f"ctx._source.{k} = {json.dumps(v)};"
"""
body = {
"query": {
"term": {
id_field_name: id_value
}
},
"script": {
"source": source,
"lang": "painless"
}
}
client.update_by_query(index=index, body=body)
"""
ubq = UpdateByQuery(using=client, index=index) \ ubq = UpdateByQuery(using=client, index=index) \
.query("term", **{id_field_name: id_value}) \ .query("term", **{id_field_name: id_value}) \
.script(source=source, lang="painless") .script(source=source, lang="painless")
@ -41,8 +29,8 @@ def update_by_id(client: Elasticsearch, index: str, id_field_name: str, id_value
return response.success() return response.success()
def delete_by_id(index: str, id_field_name: str, id_value: str):
def delete_by_id(client: Elasticsearch, index: str, id_field_name: str, id_value: str): client = connections.get_connection()
s = Search(using=client, index=index).filter("term", **{id_field_name: id_value}) s = Search(using=client, index=index).filter("term", **{id_field_name: id_value})
response = s.delete() response = s.delete()
#if not response.success(): #if not response.success():
@ -68,7 +56,8 @@ def simplify_properties(d):
return new_d return new_d
def get_type_schema(client: Elasticsearch): def get_type_schema():
client = connections.get_connection()
d = client.indices.get(index="*").body d = client.indices.get(index="*").body
new_d = {} new_d = {}
for index, d2 in d.items(): for index, d2 in d.items():
@ -80,22 +69,20 @@ def get_type_schema(client: Elasticsearch):
def wait_for_elasticsearch(): def wait_for_elasticsearch():
#TODO: find a clean way to wait without exceptions! #TODO: find a clean way to wait without exceptions!
#Wait for elasticsearch to start up! #Wait for elasticsearch to start up!
#elastic_url = os.getenv("ELASTIC_URI")
elastic_url = os.getenv("ELASTIC_URI") #assert elastic_url
assert elastic_url
i = 1 i = 1
while True: while True:
try: try:
client = Elasticsearch(hosts=elastic_url) #client = Elasticsearch(hosts=elastic_url)
client = connections.get_connection()
client.indices.get_alias(index="*") client.indices.get_alias(index="*")
#connections.create_connection(hosts=app.config['elastic_uri']) #connections.create_connection(hosts=app.config['elastic_uri'])
#connections.get_connection().cluster.health(wait_for_status='yellow') #connections.get_connection().cluster.health(wait_for_status='yellow')
#init_indicies() #init_indicies()
print("Elasticsearch found! Run Flask-app!", flush=True) print("Elasticsearch found! Run Flask-app!", flush=True)
break return
except: except ConnectionError:
#except ConnectionError:
i *= 2 #1.5 i *= 2 #1.5
time.sleep(i) time.sleep(i)
print("Elasticsearch not found! Wait %s seconds!" % i, flush=True) print("Elasticsearch not found! Wait %s seconds!" % i, flush=True)

View File

@ -1,50 +1,8 @@
from smtplib import * from smtplib import *
from email.mime.text import MIMEText from email.mime.text import MIMEText
""" def send_mail(target_mail, subject, sender_mail, msg):
import threading
import smtpd
import asyncore
import smtplib
server = smtpd.SMTPServer(('localhost', 1025), None)
loop_thread = threading.Thread(target=asyncore.loop, name="Asyncore Loop")
# If you want to make the thread a daemon
# loop_thread.daemon = True
loop_thread.start()
"""
import asyncio
from aiosmtpd.controller import Controller
class CustomHandler:
async def handle_DATA(self, server, session, envelope):
peer = session.peer
mail_from = envelope.mail_from
rcpt_tos = envelope.rcpt_tos
data = envelope.content # type: bytes
# Process message data...
#if error_occurred:
# return '500 Could not process your message'
return '250 OK'
def send_mail2(target_mail, subject, sender_mail, msg):
#handler = CustomHandler()
#controller = Controller(handler, hostname='127.0.0.1', port=1025)
# Run the event loop in a separate thread.
#controller.start()
msg = MIMEText(msg) msg = MIMEText(msg)
msg['Subject'] = subject msg['Subject'] = subject
@ -55,93 +13,3 @@ def send_mail2(target_mail, subject, sender_mail, msg):
smtp.sendmail("Creative Bots", [target_mail], msg.as_string()) smtp.sendmail("Creative Bots", [target_mail], msg.as_string())
smtp.quit() smtp.quit()
#controller.stop()
import smtplib, dns.resolver
def send_mail3(target_mail, subject, sender_mail, msg):
msg = MIMEText(msg)
msg['Subject'] = subject
msg['From'] = sender_mail
msg['To'] = target_mail
#smtp = SMTP('mailserver', port=10025)
#smtp.sendmail("Creative Bots", [target_mail], msg.as_string())
#smtp.quit()
[nick, domain] = target_mail.split("@")
#domain = 'example.com'
records = dns.resolver.resolve(domain, 'MX')
mx_record = records[0].exchange
server = smtplib.SMTP(mx_record, 25)
#server.sendmail('your_email@example.com', 'recipient_email@example.com', 'Hello, this is a test email.')
server.sendmail(sender_mail, target_mail, msg.as_string())
server.quit()
import sys
import chilkat
def send_mail(target_mail, subject, sender_mail, msg):
msg = MIMEText(msg)
msg['Subject'] = subject
msg['From'] = sender_mail
msg['To'] = target_mail
# The mailman object is used for sending and receiving email.
mailman = chilkat.CkMailMan()
recipient = target_mail
# Do a DNS MX lookup for the recipient's mail server.
smtpHostname = mailman.mxLookup(recipient)
if (mailman.get_LastMethodSuccess() != True):
print(mailman.lastErrorText(), flush=True)
#sys.exit()
return False
print(smtpHostname)
# Set the SMTP server.
mailman.put_SmtpHost(smtpHostname)
# Create a new email object
email = chilkat.CkEmail()
email.put_Subject(subject)
email.put_Body(msg.as_string())
email.put_From(sender_mail)
email.AddTo("", recipient)
success = mailman.SendEmail(email)
if (success != True):
print(mailman.lastErrorText(), flush=True)
return False
else:
print("Mail Sent!", flush=True)
return True

View File

@ -32,6 +32,7 @@ class User(Document):
class Chatbot(Document): class Chatbot(Document):
creation_date = Date() creation_date = Date()
changed_date = Date()
name = Text() name = Text()
creator_id = Keyword() creator_id = Keyword()
description = Text() description = Text()

View File

@ -5,11 +5,10 @@ import os, json, hashlib, traceback, logging
from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors from elasticsearch import NotFoundError, Elasticsearch # for normal read/write without vectors
from lib.models import User from lib.models import User
from lib.elastictools import get_by_id, update_by_id, delete_by_id, wait_for_elasticsearch from lib.elastictools import get_by_id, update_by_id, wait_for_elasticsearch
elastic_url = os.getenv("ELASTIC_URI") elastic_url = os.getenv("ELASTIC_URI")
assert elastic_url assert elastic_url
@ -27,11 +26,10 @@ def create_user(email, password, role="user", verified=False):
def create_default_users(): def create_default_users():
#create default users #create default users
client = Elasticsearch(elastic_url)
default_users = os.getenv("DEFAULT_USERS") default_users = os.getenv("DEFAULT_USERS")
if default_users: if default_users:
for (email, pwd, role) in json.loads(default_users): for (email, pwd, role) in json.loads(default_users):
if len(get_by_id(client, index="user", id_field_name="email", id_value=email)) == 0: if len(get_by_id(index="user", id_field_name="email", id_value=email)) == 0:
create_user(email, pwd, role=role, verified=True) create_user(email, pwd, role=role, verified=True)

View File

@ -28,6 +28,28 @@
<script async="async" src="jsnetworkx.js"></script> <script async="async" src="jsnetworkx.js"></script>
<script async="async" src="widget.js"></script> <script async="async" src="widget.js"></script>
<style>
#fun222 {
background-image: linear-gradient(
45deg,
red,
blue
);
opacity: 0.75;
}
#fun22 {
background-image: linear-gradient(
135deg,
red,
blue
);
opacity: 0.75;
}
</style>
</head> </head>
<body> <body>
@ -68,7 +90,8 @@
let ele = document.getElementById("register_password"); let ele = document.getElementById("register_password");
if(ele.type === "password"){ if(ele.type === "password"){
ele.type = "text"; ele.type = "text";
} else { }
else {
ele.type = "password"; ele.type = "password";
} }
} }
@ -121,7 +144,8 @@
let ele = document.getElementById("pass"); let ele = document.getElementById("pass");
if(ele.type === "password"){ if(ele.type === "password"){
ele.type = "text"; ele.type = "text";
} else { }
else {
ele.type = "password"; ele.type = "password";
} }
} }
@ -140,7 +164,7 @@
<div class="container-fluid p-3 bg-primary text-white text-center"> <div id="fun" class="container-fluid p-3 bg-primary text-white text-center">
<h1>Creative Bots</h1> <h1>Creative Bots</h1>
<p>Create and talk to chatbots!</p> <p>Create and talk to chatbots!</p>
</div> </div>
@ -161,6 +185,7 @@
<button id="login_btn" type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#myModal">Login</button> <button id="login_btn" type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#myModal">Login</button>
<button id="logout_btn" type="button" class="btn btn-danger text-white">Logout</button> <button id="logout_btn" type="button" class="btn btn-danger text-white">Logout</button>
<br>
<br> <br>
<label for="system_prompt">System prompt:</label> <label for="system_prompt">System prompt:</label>
@ -168,6 +193,29 @@
<br> <br>
<button onclick="add_geo_location()" type="button" class="btn btn-secondary text-white">Add Geo-Location</button>
<script>
function add_geo_location(){
function callback(position) {
let s = "My current position is: Latitude: " + position.coords.latitude + " Longitude: " + position.coords.longitude + " !";
console.log(s);
document.getElementById("system_prompt").innerHTML += " " + s;
}
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(callback);
}
else {
console.log("Geolocation is not supported by this browser.");
}
}
</script>
<br>
<br>
<label for="views">Choose a view:</label> <label for="views">Choose a view:</label>
<select name="views" id="view_select" class="form-select"> <select name="views" id="view_select" class="form-select">
<option value="md">Markdown</option> <option value="md">Markdown</option>
@ -220,7 +268,7 @@
<option value="Is a monad a burito?"> <option value="Is a monad a burito?">
<option value="Give the JSON of a graph linking Germanys 9 biggest cities"> <option value="Give the JSON of a graph linking Germanys 9 biggest cities">
</datalist> </datalist>
<button id="submit_btn" class="btn btn-success" type="submit">Send</button> <button id="submit_btn" class="btn btn-success" type="button">Send</button>
</div> </div>
<!-- Button to open the offcanvas sidebar --> <!-- Button to open the offcanvas sidebar -->
@ -269,18 +317,6 @@
<label for="bot_system_prompt">System prompt:</label> <label for="bot_system_prompt">System prompt:</label>
<textarea id="bot_system_prompt" class="form-control" rows="8" name="text" placeholder="The prompt that defines the bot's main behaviour."></textarea> <textarea id="bot_system_prompt" class="form-control" rows="8" name="text" placeholder="The prompt that defines the bot's main behaviour."></textarea>
<br>
<label for="avatar">Text documents:</label>
<input disabled class="form-control" type="file" id="avatar" name="avatar" multiple accept=".pdf,.xml,.txt,.md,.doc,.docx,.odt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
<br>
<label for="url">Links:</label>
<input disabled class="form-control" type="url" name="url" id="url" placeholder="https://example.com" pattern="https://.*" size="30" required />
<hr> <hr>
<div class="row"> <div class="row">
@ -299,19 +335,6 @@
<div id="alert_spawn"></div> <div id="alert_spawn"></div>
<!--
<div id="alert_bot_created" style="display: none;" class="alert alert-success alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Success!</strong> Bot created!
</div>
<div id="alert_not_bot_created" style="display: none;" class="alert alert-danger alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Couldn't create bot!</strong> Something killed that bot!
</div>
-->
</form> </form>
</div> </div>
@ -337,45 +360,60 @@
<br> <br>
<label for="bot_name" class="form-label">Name:</label> <label for="bot_name" class="form-label">Name:</label>
<input type="bot_name" class="form-control" id="bot_name" placeholder="The displayed name of the bot."> <input type="bot_name" class="form-control" id="change_bot_name" placeholder="The displayed name of the bot.">
<br> <br>
<label for="bot_visibility">Visibility:</label> <label for="bot_visibility">Visibility:</label>
<select name="bot_visibility" id="bot_visibility_select" class="form-select"> <select name="bot_visibility" id="change_bot_visibility_select" class="form-select">
<option value="public">Public to All</option> <option value="public">Public to All</option>
<option value="private">Private to User</option> <option value="private">Private to User</option>
</select> </select>
<br> <br>
<label for="bot_description">Description:</label> <label for="bot_description">Description:</label>
<textarea id="bot_description" class="form-control" rows="8" name="text" placeholder="A description of the bot and it's purpose."></textarea> <textarea id="change_bot_description" class="form-control" rows="8" name="text" placeholder="A description of the bot and it's purpose."></textarea>
<br> <br>
<label for="bot_llm">Language model:</label> <label for="bot_llm">Language model:</label>
<select name="bot_llm" id="bot_llm_select" class="form-select"> <select name="bot_llm" id="change_bot_llm_select" class="form-select">
<option value="llama3">Llama3</option> <option value="llama3">Llama3</option>
</select> </select>
<br> <br>
<label for="bot_system_prompt">System prompt:</label> <label for="bot_system_prompt">System prompt(behavior):</label>
<textarea id="bot_system_prompt" class="form-control" rows="8" name="text" placeholder="The prompt that defines the bot's main behaviour."></textarea> <textarea id="change_bot_system_prompt" class="form-control" rows="8" name="text" placeholder="The prompt that defines the bot's main behaviour."></textarea>
<br>
<h4>Knowledge resources:</h4>
<br>
<label for="change_bot_rag_text_name">Text:</label>
<textarea id="change_bot_rag_text" class="form-control" rows="16" name="change_bot_rag_text_name" placeholder="A text that contains information used for the bot's answers.'"></textarea>
<br> <br>
<label for="avatar">Text documents:</label> <label for="avatar">Text documents:</label>
<input disabled class="form-control" type="file" id="avatar" name="avatar" multiple accept=".pdf,.xml,.txt,.md,.doc,.docx,.odt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" /> <input disabled class="form-control" type="file" id="avatar" name="avatar" multiple accept=".pdf,.xml,.txt,.md,.doc,.docx,.odt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
<br> <br>
<label for="url">Links:</label> <label for="rag_urls">Links:</label>
<input disabled class="form-control" type="url" name="url" id="url" placeholder="https://example.com" pattern="https://.*" size="30" required /> <div class="input-group">
<input disabled class="form-control" type="url" name="rag_urls" id="url" placeholder="https://example.com" pattern="https://.*" size="30" />
<button disabled id="add_url_btn" class="btn btn-success" type="button">Add</button>
</div>
<!--
<ol>
<li>https://playwright.dev/docs/ci-intro</li>
</ol>
-->
<hr> <hr>
@ -412,18 +450,13 @@
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<footer> <footer>
<div class="container-fluid p-3 bg-primary text-white mt-5"> <div id="fun2" class="container-fluid p-3 bg-primary text-white mt-5">
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col-sm-4">
<h3>A simple UI</h3> <h3>A simple UI</h3>
@ -440,14 +473,11 @@
<div class="col-sm-4"> <div class="col-sm-4">
<h3>Who? Why?</h3> <h3>Who? Why?</h3>
<p>The guy on this site: <a class="text-white" href="https://tobiasweise.dev">tobiasweise.dev</a></p> <p>The guy on this site: <a class="text-white" href="https://tobiasweise.dev">tobiasweise.dev</a></p>
<p>For fun and learning...</p> <p>For fun and learning.</p>
<p>...and maybe getting a job that employs the used skills.</p>
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>
<script src="main.js"></script> <script src="main.js"></script>
</body> </body>
</html> </html>

View File

@ -2,8 +2,6 @@
"use strict"; "use strict";
//idea: generate proxy opject via openapi.json api(url).login_now() //idea: generate proxy opject via openapi.json api(url).login_now()
function API(jwt){ function API(jwt){
@ -109,7 +107,8 @@ async function create_bot(jwt, name, visibility, description, llm, sys_prompt){
return response.json(); return response.json();
} }
async function get_bots(jwt){ async function get_bots(jwt, bot_id){
if(!bot_id){
if(jwt){ if(jwt){
const response = await fetch("/bot", { const response = await fetch("/bot", {
method: "GET", method: "GET",
@ -129,10 +128,26 @@ async function get_bots(jwt){
}); });
return response.json(); return response.json();
} }
}
else{
if(jwt){
const response = await fetch("/bot?id=" + bot_id, {
method: "GET",
headers: {
'accept': '*/*',
'Authorization': 'Bearer ' + jwt
}
});
return response.json();
}
}
} }
async function change_bot(jwt, name, visibility, description, llm, sys_prompt){
async function change_bot(jwt, id, name, visibility, description, llm, sys_prompt){
const formData = new FormData(); const formData = new FormData();
formData.append("id", id);
formData.append("name", name); formData.append("name", name);
formData.append("visibility", visibility); formData.append("visibility", visibility);
formData.append("description", description); formData.append("description", description);
@ -165,6 +180,21 @@ async function delete_bot(jwt, bot_id){
return response.json(); return response.json();
} }
async function bot_train_text(jwt, bot_id, text){
const formData = new FormData();
formData.append("bot_id", bot_id);
formData.append("text", text);
const response = await fetch("/bot/train/text", {
method: "POST",
headers: {
'accept': '*/*',
'Authorization': 'Bearer ' + jwt
},
body: formData
});
return response.json();
}
async function* ask_question(bot_id, question, system_prompt=""){ async function* ask_question(bot_id, question, system_prompt=""){
@ -207,7 +237,7 @@ async function* ask_question(bot_id, question, system_prompt=""){
else{ else{
done = true; done = true;
socket.off('backend token'); socket.off('backend token');
dom_ele.dispatchEvent(new CustomEvent(evt_name, { detail: "" })); dom_ele.dispatchEvent(new CustomEvent(evt_name, { detail: obj }));
} }
}); });
socket.emit('client message', {question, system_prompt, bot_id, room}); socket.emit('client message', {question, system_prompt, bot_id, room});
@ -343,6 +373,7 @@ window.onload = async ()=>{
let change_bot_description = document.getElementById("change_bot_description"); let change_bot_description = document.getElementById("change_bot_description");
let change_bot_llm_select = document.getElementById("change_bot_llm_select"); let change_bot_llm_select = document.getElementById("change_bot_llm_select");
let change_bot_system_prompt = document.getElementById("change_bot_system_prompt"); let change_bot_system_prompt = document.getElementById("change_bot_system_prompt");
let change_bot_rag_text = document.getElementById("change_bot_rag_text");
let delete_bot_btn = document.getElementById("delete_bot_btn"); let delete_bot_btn = document.getElementById("delete_bot_btn");
@ -367,14 +398,44 @@ window.onload = async ()=>{
return bot_select.options[i].text; return bot_select.options[i].text;
} }
function clean_bot_create_form(){ function clean_bot_create_form(){
bot_name.value = ""; bot_name.value = "";
bot_description.value = ""; bot_description.value = "";
bot_system_prompt.value = ""; bot_system_prompt.value = "";
} }
async function fill_bot_change_form(bot_id){
let jwt = localStorage.getItem("jwt");
if(jwt){
let bot = await get_bots(jwt, bot_id);
change_bot_name.value = bot.name;
change_bot_visibility_select.value = bot.visibility;
change_bot_description.value = bot.description;
change_bot_llm_select.value = bot.llm_model;
change_bot_system_prompt.value = bot.system_prompt;
/*
{
"creation_date": "Fri, 26 Jul 2024 19:13:18 GMT",
"creator_id": "cWG-75ABLLZSH2M7pxp8",
"description": "basic bot",
"id": "uqJ28JABAAhLOtEyOj2_",
"llm_model": "llama3",
"name": "Testbot",
"system_prompt": "",
"visibility": "public"
}
*/
}
}
function set_ui_loggedin(b){ function set_ui_loggedin(b){
if(b){ if(b){
console.log("User logged in!"); console.log("User logged in!");
@ -429,6 +490,11 @@ window.onload = async ()=>{
set_bot_list(bot_select, bots); set_bot_list(bot_select, bots);
set_bot_list(change_bot_select, bots); set_bot_list(change_bot_select, bots);
set_ui_loggedin(true); set_ui_loggedin(true);
let bot_id = change_bot_select.value;
await fill_bot_change_form(bot_id);
} }
} }
@ -491,10 +557,21 @@ window.onload = async ()=>{
} }
}; };
change_bot_select.onchange = async ()=>{
let jwt = localStorage.getItem("jwt");
if(jwt){
let bot_id = change_bot_select.value;
await fill_bot_change_form(bot_id);
}
};
change_bot_btn.onclick = async ()=>{ change_bot_btn.onclick = async ()=>{
let jwt = localStorage.getItem("jwt"); let jwt = localStorage.getItem("jwt");
if(jwt){ if(jwt){
let bot_id = change_bot_select.value
let name = change_bot_name.value; let name = change_bot_name.value;
let visibility = change_bot_visibility_select.value; let visibility = change_bot_visibility_select.value;
let description = change_bot_description.value; let description = change_bot_description.value;
@ -512,7 +589,18 @@ window.onload = async ()=>{
} }
try{ try{
let {bot_id} = await change_bot(jwt, name, visibility, description, llm, sys_prompt); await change_bot(jwt, bot_id, name, visibility, description, llm, sys_prompt);
let text = change_bot_rag_text.value;
if(text.trim() !== ""){
await bot_train_text(jwt, bot_id, text);
//TODO: kill bot on partially failed creation?
}
alert_bot_change(true); alert_bot_change(true);
await update_ui(); await update_ui();
} }
@ -724,8 +812,10 @@ window.onload = async ()=>{
let acc_text = ""; let acc_text = "";
for await (let token of ask_question(bot_select.value, input_string, system_prompt.value)){ for await (let token of ask_question(bot_select.value, input_string, system_prompt.value)){
//console.log(token); console.log(token);
acc_text += "" + token;
if(typeof token === "string"){
acc_text += token;
switch(view_select.value){ switch(view_select.value){
case "md": case "md":
table_cell.innerHTML = ""; table_cell.innerHTML = "";
@ -739,6 +829,7 @@ window.onload = async ()=>{
} }
scroll_down(); scroll_down();
} }
}
/* /*
function play() { function play() {
@ -783,17 +874,3 @@ window.onload = async ()=>{
}; };

View File

@ -7,19 +7,20 @@ bs4
elasticsearch elasticsearch
elasticsearch-dsl elasticsearch-dsl
ollama
langchain langchain
langchain-community langchain-community
tiktoken
langchain_ollama langchain_ollama
langchain-elasticsearch langchain-elasticsearch
pydantic pydantic
fastapi fastapi
gunicorn
Werkzeug Werkzeug
flask flask
Flask-Cors
Flask-SocketIO Flask-SocketIO
flask-openapi3 flask-openapi3
@ -34,8 +35,3 @@ neo4j
pyttsx3 pyttsx3
aiosmtpd
dnspython
chilkat

View File

@ -18,7 +18,6 @@ services:
minio: minio:
container_name: ${APP_PREFIX}_minio container_name: ${APP_PREFIX}_minio
#image: docker.io/bitnami/minio #:2022
image: minio/minio image: minio/minio
ports: ports:
- "29000:9000" - "29000:9000"
@ -36,7 +35,6 @@ services:
elasticsearch: elasticsearch:
container_name: ${APP_PREFIX}_elasticsearch container_name: ${APP_PREFIX}_elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0 image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
#image: opensearchproject/opensearch
restart: always restart: always
mem_limit: 4024m mem_limit: 4024m
ports: ports:
@ -48,9 +46,6 @@ services:
- xpack.security.enabled=false - xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false - xpack.security.http.ssl.enabled=false
#- OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD} # Sets the demo admin user password when using demo configuration, required for OpenSearch 2.12 and later
#- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # Set min and max JVM heap sizes to at least 50% of system RAM
#- ES_JAVA_OPTS="-Xms2g -Xmx2g"
volumes: volumes:
- esdata:/usr/share/elasticsearch/data - esdata:/usr/share/elasticsearch/data
ulimits: ulimits:
@ -76,22 +71,22 @@ services:
- llm_network - llm_network
#command: "ollama pull llama2" #command: "ollama pull llama2"
ollama-webui: #ollama-webui:
container_name: ${APP_PREFIX}_ollama-webui # container_name: ${APP_PREFIX}_ollama-webui
image: ghcr.io/ollama-webui/ollama-webui:main # image: ghcr.io/ollama-webui/ollama-webui:main
volumes: # volumes:
- ../ollama/ollama-webui:/app/backend/data # - ../ollama/ollama-webui:/app/backend/data
depends_on: # depends_on:
- ollama # - ollama
ports: # ports:
- 8888:8080 # - 8888:8080
environment: # environment:
- "/ollama/api=http://ollama:11434/api" # - "/ollama/api=http://ollama:11434/api"
extra_hosts: # extra_hosts:
- host.docker.internal:host-gateway # - host.docker.internal:host-gateway
restart: unless-stopped # restart: unless-stopped
networks: # networks:
- llm_network # - llm_network
#frontend: #frontend:
# container_name: ${APP_PREFIX}_frontend # container_name: ${APP_PREFIX}_frontend

View File

@ -19,13 +19,17 @@ https://favtutor.com/articles/meta-llama-3-jailbreak/
* https://medium.com/@lucgagan/understanding-chatgpt-functions-and-how-to-use-them-6643a7d3c01a * https://medium.com/@lucgagan/understanding-chatgpt-functions-and-how-to-use-them-6643a7d3c01a
## Use drivers for GPU!
"ollama recommends running the https://www.amd.com/en/support/linux-drivers: amdgpu version file missing: /sys/module/amdgpu/version stat /sys/module/amdgpu/version: no such file or directory"
"detected amdgpu versions []"
"all detected amdgpus are skipped, falling back to CPU"
"no GPU detected"
## Use PyPy for more speed
...