From bd9e5c013f44ec958938c153b7217330670aeeb8 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 10:41:17 -0800 Subject: [PATCH 01/13] feat: start command for windows --- backend/start_windows.bat | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 backend/start_windows.bat diff --git a/backend/start_windows.bat b/backend/start_windows.bat new file mode 100644 index 00000000..9d10ef9f --- /dev/null +++ b/backend/start_windows.bat @@ -0,0 +1,31 @@ +@echo off +SETLOCAL ENABLEDELAYEDEXPANSION + +:: Get the directory of the current script +SET "SCRIPT_DIR=%~dp0" +cd /d "%SCRIPT_DIR%" || exit /b + +SET "KEY_FILE=.webui_secret_key" +SET "PORT=%PORT:8080%" +SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%" +SET "WEBUI_JWT_SECRET_KEY=%WEBUI_JWT_SECRET_KEY%" + +:: Check if WEBUI_SECRET_KEY and WEBUI_JWT_SECRET_KEY are not set +IF "%WEBUI_SECRET_KEY%%WEBUI_JWT_SECRET_KEY%" == " " ( + echo No WEBUI_SECRET_KEY provided + + IF NOT EXIST "%KEY_FILE%" ( + echo Generating WEBUI_SECRET_KEY + :: Generate a random value to use as a WEBUI_SECRET_KEY in case the user didn't provide one + SET /p WEBUI_SECRET_KEY=>%KEY_FILE% + echo WEBUI_SECRET_KEY generated + ) + + echo Loading WEBUI_SECRET_KEY from %KEY_FILE% + SET /p WEBUI_SECRET_KEY=<%KEY_FILE% +) + +:: Execute uvicorn +SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%" +uvicorn main:app --host 0.0.0.0 --port "%PORT%" --forwarded-allow-ips '*' From 22d2c1371375a4df302bc9f87fd9a6479b4c0e69 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 10:43:50 -0800 Subject: [PATCH 02/13] Update start_windows.bat --- backend/start_windows.bat | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/start_windows.bat b/backend/start_windows.bat index 9d10ef9f..45634ef8 100644 --- a/backend/start_windows.bat +++ b/backend/start_windows.bat @@ -1,3 +1,5 @@ +REM This method is not recommended, and we recommend you use the `start.sh` file with WSL instead. + @echo off SETLOCAL ENABLEDELAYEDEXPANSION From d1e5274fc91ac51d60fb3250e76bf21cfe711f9b Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 11:25:11 -0800 Subject: [PATCH 03/13] Update start_windows.bat --- backend/start_windows.bat | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/start_windows.bat b/backend/start_windows.bat index 45634ef8..b2c37017 100644 --- a/backend/start_windows.bat +++ b/backend/start_windows.bat @@ -1,5 +1,4 @@ -REM This method is not recommended, and we recommend you use the `start.sh` file with WSL instead. - +:: This method is not recommended, and we recommend you use the `start.sh` file with WSL instead. @echo off SETLOCAL ENABLEDELAYEDEXPANSION From aa39305dec83a6c58472b0205b66fff7b92497ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Garc=C3=ADa?= Date: Sun, 18 Feb 2024 04:08:58 +0100 Subject: [PATCH 04/13] Update README.md Added detailed instructions to migrate from ollama-webui to open-webui, preserving and not preserving existing data --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1d089e7..466e3af6 100644 --- a/README.md +++ b/README.md @@ -219,9 +219,48 @@ docker rm -f open-webui docker pull ghcr.io/open-webui/open-webui:main [insert command you used to install] ``` - In the last line, you need to use the very same command you used to install (local install, remote server, etc.) +### Moving from Ollama WebUI to Open WebUI + +Given recent name changes, the docker image has been renamed. Additional steps are required to update for those people that used Ollama WebUI previously and want to start using the new images. + +#### Updating to Open WebUI without keeping your data + +If you want to update to the new image but don't want to keep any previous data like conversations, prompts, documents, etc. you can perform the following steps: + +```bash +docker rm -f ollama-webui +docker pull ghcr.io/open-webui/open-webui:main +[insert the equivalent command that you used to install with the new Docker image name] +docker volume rm ollama-webui +``` + +For example, for local installation it would be `docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main`. For other installation commands, check the relevant parts of this README document. + +#### Migrating your contents from Ollama WebUI to Open WebUI + +If you want to update to the new image migrating all your previous settings like conversations, prompts, documents, etc. you can perform the following steps: + +```bash +docker rm -f ollama-webui +docker pull ghcr.io/open-webui/open-webui:main +# Creates a new volume and uses a temporary container to copy from one volume to another as per https://github.com/moby/moby/issues/31154#issuecomment-360531460 +docker volume create --name open-webui +docker run --rm -v ollama-webui:/from -v open-webui:/to alpine ash -c "cd /from ; cp -av . /to" +[insert the equivalent command that you used to install with the new Docker image name] +``` + +Once you verify that all the data has been migrated you can erase the old volumen using the following command: + +```bash +docker volume rm ollama-webui +``` + + + + + ## How to Install Without Docker While we strongly recommend using our convenient Docker container installation for optimal support, we understand that some situations may require a non-Docker setup, especially for development purposes. Please note that non-Docker installations are not officially supported, and you might need to troubleshoot on your own. From e07001e5f6c0570b7a4dbf594b30b1d5f1ccbd54 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 21:06:08 -0800 Subject: [PATCH 05/13] feat: rag folder scan support --- backend/apps/rag/main.py | 99 ++++++++++++++++--- backend/apps/web/routers/documents.py | 4 + backend/config.py | 8 ++ backend/utils/misc.py | 38 +++++++ src/lib/apis/rag/index.ts | 26 +++++ .../chat/Messages/ResponseMessage.svelte | 2 +- .../documents/Settings/General.svelte | 68 +++++++++++++ .../components/documents/SettingsModal.svelte | 86 ++++++++++++++++ src/routes/(app)/documents/+page.svelte | 31 ++++++ 9 files changed, 350 insertions(+), 12 deletions(-) create mode 100644 src/lib/components/documents/Settings/General.svelte create mode 100644 src/lib/components/documents/SettingsModal.svelte diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 07a30ade..ec9e0a8b 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -10,6 +10,8 @@ from fastapi import ( ) from fastapi.middleware.cors import CORSMiddleware import os, shutil + +from pathlib import Path from typing import List # from chromadb.utils import embedding_functions @@ -28,19 +30,39 @@ from langchain_community.document_loaders import ( UnstructuredExcelLoader, ) from langchain.text_splitter import RecursiveCharacterTextSplitter -from langchain_community.vectorstores import Chroma from langchain.chains import RetrievalQA +from langchain_community.vectorstores import Chroma from pydantic import BaseModel from typing import Optional - +import mimetypes import uuid +import json import time -from utils.misc import calculate_sha256, calculate_sha256_string + +from apps.web.models.documents import ( + Documents, + DocumentForm, + DocumentResponse, +) + +from utils.misc import ( + calculate_sha256, + calculate_sha256_string, + sanitize_filename, + extract_folders_after_data_docs, +) from utils.utils import get_current_user, get_admin_user -from config import UPLOAD_DIR, EMBED_MODEL, CHROMA_CLIENT, CHUNK_SIZE, CHUNK_OVERLAP +from config import ( + UPLOAD_DIR, + DOCS_DIR, + EMBED_MODEL, + CHROMA_CLIENT, + CHUNK_SIZE, + CHUNK_OVERLAP, +) from constants import ERROR_MESSAGES # EMBEDDING_FUNC = embedding_functions.SentenceTransformerEmbeddingFunction( @@ -220,8 +242,8 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)): ) -def get_loader(file, file_path): - file_ext = file.filename.split(".")[-1].lower() +def get_loader(filename: str, file_content_type: str, file_path: str): + file_ext = filename.split(".")[-1].lower() known_type = True known_source_ext = [ @@ -279,20 +301,20 @@ def get_loader(file, file_path): loader = UnstructuredXMLLoader(file_path) elif file_ext == "md": loader = UnstructuredMarkdownLoader(file_path) - elif file.content_type == "application/epub+zip": + elif file_content_type == "application/epub+zip": loader = UnstructuredEPubLoader(file_path) elif ( - file.content_type + file_content_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" or file_ext in ["doc", "docx"] ): loader = Docx2txtLoader(file_path) - elif file.content_type in [ + elif file_content_type in [ "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ] or file_ext in ["xls", "xlsx"]: loader = UnstructuredExcelLoader(file_path) - elif file_ext in known_source_ext or file.content_type.find("text/") >= 0: + elif file_ext in known_source_ext or file_content_type.find("text/") >= 0: loader = TextLoader(file_path) else: loader = TextLoader(file_path) @@ -323,7 +345,7 @@ def store_doc( collection_name = calculate_sha256(f)[:63] f.close() - loader, known_type = get_loader(file, file_path) + loader, known_type = get_loader(file.filename, file.content_type, file_path) data = loader.load() result = store_data_in_vector_db(data, collection_name) @@ -353,6 +375,61 @@ def store_doc( ) +@app.get("/scan") +def scan_docs_dir(user=Depends(get_admin_user)): + try: + for path in Path(DOCS_DIR).rglob("./**/*"): + if path.is_file() and not path.name.startswith("."): + tags = extract_folders_after_data_docs(path) + filename = path.name + file_content_type = mimetypes.guess_type(path) + + f = open(path, "rb") + collection_name = calculate_sha256(f)[:63] + f.close() + + loader, known_type = get_loader(filename, file_content_type, str(path)) + data = loader.load() + + result = store_data_in_vector_db(data, collection_name) + + if result: + sanitized_filename = sanitize_filename(filename) + doc = Documents.get_doc_by_name(sanitized_filename) + + if doc == None: + doc = Documents.insert_new_doc( + user.id, + DocumentForm( + **{ + "name": sanitized_filename, + "title": filename, + "collection_name": collection_name, + "filename": filename, + "content": ( + json.dumps( + { + "tags": list( + map( + lambda name: {"name": name}, + tags, + ) + ) + } + ) + if len(tags) + else "{}" + ), + } + ), + ) + + except Exception as e: + print(e) + + return True + + @app.get("/reset/db") def reset_vector_db(user=Depends(get_admin_user)): CHROMA_CLIENT.reset() diff --git a/backend/apps/web/routers/documents.py b/backend/apps/web/routers/documents.py index 5bc473fa..7c69514f 100644 --- a/backend/apps/web/routers/documents.py +++ b/backend/apps/web/routers/documents.py @@ -96,6 +96,10 @@ async def get_doc_by_name(name: str, user=Depends(get_current_user)): ############################ +class TagItem(BaseModel): + name: str + + class TagDocumentForm(BaseModel): name: str tags: List[dict] diff --git a/backend/config.py b/backend/config.py index d7c89b3b..f5acf06b 100644 --- a/backend/config.py +++ b/backend/config.py @@ -43,6 +43,14 @@ Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True) CACHE_DIR = f"{DATA_DIR}/cache" Path(CACHE_DIR).mkdir(parents=True, exist_ok=True) + +#################################### +# Docs DIR +#################################### + +DOCS_DIR = f"{DATA_DIR}/docs" +Path(DOCS_DIR).mkdir(parents=True, exist_ok=True) + #################################### # OLLAMA_API_BASE_URL #################################### diff --git a/backend/utils/misc.py b/backend/utils/misc.py index 385a2c41..5e9d5876 100644 --- a/backend/utils/misc.py +++ b/backend/utils/misc.py @@ -1,3 +1,4 @@ +from pathlib import Path import hashlib import re @@ -38,3 +39,40 @@ def validate_email_format(email: str) -> bool: if not re.match(r"[^@]+@[^@]+\.[^@]+", email): return False return True + + +def sanitize_filename(file_name): + # Convert to lowercase + lower_case_file_name = file_name.lower() + + # Remove special characters using regular expression + sanitized_file_name = re.sub(r"[^\w\s]", "", lower_case_file_name) + + # Replace spaces with dashes + final_file_name = re.sub(r"\s+", "-", sanitized_file_name) + + return final_file_name + + +def extract_folders_after_data_docs(path): + # Convert the path to a Path object if it's not already + path = Path(path) + + # Extract parts of the path + parts = path.parts + + # Find the index of '/data/docs' in the path + try: + index_data_docs = parts.index("data") + 1 + index_docs = parts.index("docs", index_data_docs) + 1 + except ValueError: + return [] + + # Exclude the filename and accumulate folder names + tags = [] + + folders = parts[index_docs:-1] + for idx, part in enumerate(folders): + tags.append("/".join(folders[: idx + 1])) + + return tags diff --git a/src/lib/apis/rag/index.ts b/src/lib/apis/rag/index.ts index 3f4f30bf..fc3571aa 100644 --- a/src/lib/apis/rag/index.ts +++ b/src/lib/apis/rag/index.ts @@ -138,6 +138,32 @@ export const queryCollection = async ( return res; }; +export const scanDocs = async (token: string) => { + let error = null; + + const res = await fetch(`${RAG_API_BASE_URL}/scan`, { + method: 'GET', + headers: { + Accept: 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const resetVectorDB = async (token: string) => { let error = null; diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index a0ffc83c..cc42d0b9 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -366,7 +366,7 @@ {#if message.done}
{#if siblings.length > 1}
diff --git a/src/lib/components/documents/Settings/General.svelte b/src/lib/components/documents/Settings/General.svelte new file mode 100644 index 00000000..19a6493b --- /dev/null +++ b/src/lib/components/documents/Settings/General.svelte @@ -0,0 +1,68 @@ + + +
{ + // console.log('submit'); + saveHandler(); + }} +> +
+
+
General Settings
+ +
+
Scan for documents from '/data/docs'
+ + +
+
+
+ + +
diff --git a/src/lib/components/documents/SettingsModal.svelte b/src/lib/components/documents/SettingsModal.svelte new file mode 100644 index 00000000..332bf9d5 --- /dev/null +++ b/src/lib/components/documents/SettingsModal.svelte @@ -0,0 +1,86 @@ + + + +
+
+
Document Settings
+ +
+
+ +
+
+ +
+
+ {#if selectedTab === 'general'} + { + show = false; + }} + /> + + + {/if} +
+
+
+
diff --git a/src/routes/(app)/documents/+page.svelte b/src/routes/(app)/documents/+page.svelte index a5653483..ab3d6553 100644 --- a/src/routes/(app)/documents/+page.svelte +++ b/src/routes/(app)/documents/+page.svelte @@ -13,6 +13,7 @@ import EditDocModal from '$lib/components/documents/EditDocModal.svelte'; import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte'; + import SettingsModal from '$lib/components/documents/SettingsModal.svelte'; let importFiles = ''; let inputFiles = ''; @@ -20,6 +21,7 @@ let tags = []; + let showSettingsModal = false; let showEditDocModal = false; let selectedDoc; let selectedTag = ''; @@ -179,11 +181,38 @@ }} /> + +
My Documents
+ +
+ +
@@ -419,6 +448,8 @@
+ +
{/each} {#if $documents.length > 0} From 79d0932a3ab12ccacf7e4c82c3fc658544364008 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 21:14:42 -0800 Subject: [PATCH 06/13] refac: styling --- .../documents/Settings/General.svelte | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/lib/components/documents/Settings/General.svelte b/src/lib/components/documents/Settings/General.svelte index 19a6493b..c3c7df5b 100644 --- a/src/lib/components/documents/Settings/General.svelte +++ b/src/lib/components/documents/Settings/General.svelte @@ -3,13 +3,20 @@ import { scanDocs } from '$lib/apis/rag'; import { documents } from '$lib/stores'; import { onMount } from 'svelte'; + import toast from 'svelte-french-toast'; export let saveHandler: Function; + + let loading = false; + const scanHandler = async () => { + loading = true; const res = await scanDocs(localStorage.token); + loading = false; if (res) { await documents.set(await getDocs(localStorage.token)); + toast.success('Scan complete!'); } }; @@ -31,16 +38,19 @@
Scan for documents from '/data/docs'
From a94e4161f778e646542037ef372533e2ab1061f9 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 21:31:46 -0800 Subject: [PATCH 07/13] fix: file content type issue --- backend/apps/rag/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index ec9e0a8b..f24b6b90 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -388,7 +388,9 @@ def scan_docs_dir(user=Depends(get_admin_user)): collection_name = calculate_sha256(f)[:63] f.close() - loader, known_type = get_loader(filename, file_content_type, str(path)) + loader, known_type = get_loader( + filename, file_content_type[0], str(path) + ) data = loader.load() result = store_data_in_vector_db(data, collection_name) From ccf08fb91e3960b1b8457306ac231d18dc7f21e2 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 22:29:52 -0800 Subject: [PATCH 08/13] feat: editable chunk params --- backend/apps/rag/main.py | 39 +++++++++++- src/lib/apis/rag/index.ts | 58 +++++++++++++++++ .../documents/Settings/General.svelte | 62 +++++++++++++++++-- 3 files changed, 152 insertions(+), 7 deletions(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index f24b6b90..79981680 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -71,6 +71,9 @@ from constants import ERROR_MESSAGES app = FastAPI() +app.state.CHUNK_SIZE = CHUNK_SIZE +app.state.CHUNK_OVERLAP = CHUNK_OVERLAP + origins = ["*"] app.add_middleware( @@ -92,7 +95,7 @@ class StoreWebForm(CollectionNameForm): def store_data_in_vector_db(data, collection_name) -> bool: text_splitter = RecursiveCharacterTextSplitter( - chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP + chunk_size=app.state.CHUNK_SIZE, chunk_overlap=app.state.CHUNK_OVERLAP ) docs = text_splitter.split_documents(data) @@ -116,7 +119,39 @@ def store_data_in_vector_db(data, collection_name) -> bool: @app.get("/") async def get_status(): - return {"status": True} + return { + "status": True, + "chunk_size": app.state.CHUNK_SIZE, + "chunk_overlap": app.state.CHUNK_OVERLAP, + } + + +@app.get("/chunk") +async def get_chunk_params(user=Depends(get_admin_user)): + return { + "status": True, + "chunk_size": app.state.CHUNK_SIZE, + "chunk_overlap": app.state.CHUNK_OVERLAP, + } + + +class ChunkParamUpdateForm(BaseModel): + chunk_size: int + chunk_overlap: int + + +@app.post("/chunk/update") +async def update_chunk_params( + form_data: ChunkParamUpdateForm, user=Depends(get_admin_user) +): + app.state.CHUNK_SIZE = form_data.chunk_size + app.state.CHUNK_OVERLAP = form_data.chunk_overlap + + return { + "status": True, + "chunk_size": app.state.CHUNK_SIZE, + "chunk_overlap": app.state.CHUNK_OVERLAP, + } class QueryDocForm(BaseModel): diff --git a/src/lib/apis/rag/index.ts b/src/lib/apis/rag/index.ts index fc3571aa..5819badb 100644 --- a/src/lib/apis/rag/index.ts +++ b/src/lib/apis/rag/index.ts @@ -1,5 +1,63 @@ import { RAG_API_BASE_URL } from '$lib/constants'; +export const getChunkParams = async (token: string) => { + let error = null; + + const res = await fetch(`${RAG_API_BASE_URL}/chunk`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateChunkParams = async (token: string, size: number, overlap: number) => { + let error = null; + + const res = await fetch(`${RAG_API_BASE_URL}/chunk/update`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + chunk_size: size, + chunk_overlap: overlap + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const uploadDocToVectorDB = async (token: string, collection_name: string, file: File) => { const data = new FormData(); data.append('file', file); diff --git a/src/lib/components/documents/Settings/General.svelte b/src/lib/components/documents/Settings/General.svelte index c3c7df5b..038fa83b 100644 --- a/src/lib/components/documents/Settings/General.svelte +++ b/src/lib/components/documents/Settings/General.svelte @@ -1,6 +1,6 @@
{ - // console.log('submit'); + submitHandler(); saveHandler(); }} > @@ -93,14 +107,52 @@
+ +
+ +
+
Chunk Params
+ +
+
+
Chunk Size
+ +
+ +
+
+ +
+
Chunk Overlap
+ +
+ +
+
+
+
- + From 5270efa9e5cf5d39de326930344ac94611622935 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 22:41:03 -0800 Subject: [PATCH 09/13] feat: editable rag template --- backend/apps/rag/main.py | 22 +++++++++++ backend/config.py | 15 ++++++++ src/lib/apis/rag/index.ts | 57 ++++++++++++++++++++++++++++ src/lib/utils/rag/index.ts | 32 +++++++++------- src/routes/(app)/+page.svelte | 6 ++- src/routes/(app)/c/[id]/+page.svelte | 6 ++- 6 files changed, 122 insertions(+), 16 deletions(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 79981680..fbfba258 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -62,6 +62,7 @@ from config import ( CHROMA_CLIENT, CHUNK_SIZE, CHUNK_OVERLAP, + RAG_TEMPLATE, ) from constants import ERROR_MESSAGES @@ -73,6 +74,8 @@ app = FastAPI() app.state.CHUNK_SIZE = CHUNK_SIZE app.state.CHUNK_OVERLAP = CHUNK_OVERLAP +app.state.RAG_TEMPLATE = RAG_TEMPLATE + origins = ["*"] @@ -154,6 +157,25 @@ async def update_chunk_params( } +@app.get("/template") +async def get_rag_template(user=Depends(get_current_user)): + return { + "status": True, + "template": app.state.RAG_TEMPLATE, + } + + +class RAGTemplateForm(BaseModel): + template: str + + +@app.post("/template/update") +async def update_rag_template(form_data: RAGTemplateForm, user=Depends(get_admin_user)): + # TODO: check template requirements + app.state.RAG_TEMPLATE = form_data.template + return {"status": True, "template": app.state.RAG_TEMPLATE} + + class QueryDocForm(BaseModel): collection_name: str query: str diff --git a/backend/config.py b/backend/config.py index f5acf06b..440256c4 100644 --- a/backend/config.py +++ b/backend/config.py @@ -144,6 +144,21 @@ CHROMA_CLIENT = chromadb.PersistentClient( CHUNK_SIZE = 1500 CHUNK_OVERLAP = 100 + +RAG_TEMPLATE = """Use the following context as your learned knowledge, inside XML tags. + + [context] + + +When answer to user: +- If you don't know, just say that you don't know. +- If you don't know when you are not sure, ask for clarification. +Avoid mentioning that you obtained the information from the context. +And answer according to the language of the user's question. + +Given the context information, answer the query. +Query: [query]""" + #################################### # Transcribe #################################### diff --git a/src/lib/apis/rag/index.ts b/src/lib/apis/rag/index.ts index 5819badb..78c220b6 100644 --- a/src/lib/apis/rag/index.ts +++ b/src/lib/apis/rag/index.ts @@ -58,6 +58,63 @@ export const updateChunkParams = async (token: string, size: number, overlap: nu return res; }; +export const getRAGTemplate = async (token: string) => { + let error = null; + + const res = await fetch(`${RAG_API_BASE_URL}/template`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateRAGTemplate = async (token: string, template: string) => { + let error = null; + + const res = await fetch(`${RAG_API_BASE_URL}/template/update`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + template: template + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const uploadDocToVectorDB = async (token: string, collection_name: string, file: File) => { const data = new FormData(); data.append('file', file); diff --git a/src/lib/utils/rag/index.ts b/src/lib/utils/rag/index.ts index 6b219ef2..ba1f29f8 100644 --- a/src/lib/utils/rag/index.ts +++ b/src/lib/utils/rag/index.ts @@ -1,17 +1,21 @@ -export const RAGTemplate = (context: string, query: string) => { - let template = `Use the following context as your learned knowledge, inside XML tags. - - [context] - - - When answer to user: - - If you don't know, just say that you don't know. - - If you don't know when you are not sure, ask for clarification. - Avoid mentioning that you obtained the information from the context. - And answer according to the language of the user's question. - - Given the context information, answer the query. - Query: [query]`; +import { getRAGTemplate } from '$lib/apis/rag'; + +export const RAGTemplate = async (token: string, context: string, query: string) => { + let template = await getRAGTemplate(token).catch(() => { + return `Use the following context as your learned knowledge, inside XML tags. + + [context] + + + When answer to user: + - If you don't know, just say that you don't know. + - If you don't know when you are not sure, ask for clarification. + Avoid mentioning that you obtained the information from the context. + And answer according to the language of the user's question. + + Given the context information, answer the query. + Query: [query]`; + }); template = template.replace(/\[context\]/g, context); template = template.replace(/\[query\]/g, query); diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 604cb544..1d91a614 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -266,7 +266,11 @@ console.log(contextString); - history.messages[parentId].raContent = RAGTemplate(contextString, query); + history.messages[parentId].raContent = await RAGTemplate( + localStorage.token, + contextString, + query + ); history.messages[parentId].contexts = relevantContexts; await tick(); processing = ''; diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index aab03d74..b719ebf2 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -280,7 +280,11 @@ console.log(contextString); - history.messages[parentId].raContent = RAGTemplate(contextString, query); + history.messages[parentId].raContent = await RAGTemplate( + localStorage.token, + contextString, + query + ); history.messages[parentId].contexts = relevantContexts; await tick(); processing = ''; From 082d1d15c39bc37687afada7213875e367549055 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 22:42:36 -0800 Subject: [PATCH 10/13] fix: template load issue --- src/lib/apis/rag/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/apis/rag/index.ts b/src/lib/apis/rag/index.ts index 78c220b6..ed36f014 100644 --- a/src/lib/apis/rag/index.ts +++ b/src/lib/apis/rag/index.ts @@ -82,7 +82,7 @@ export const getRAGTemplate = async (token: string) => { throw error; } - return res; + return res?.template ?? ''; }; export const updateRAGTemplate = async (token: string, template: string) => { From a31feccd64e53ef0934b3d59de03e9f3e50ada9e Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 17 Feb 2024 22:47:58 -0800 Subject: [PATCH 11/13] feat: editable rag template frontend --- .../documents/Settings/General.svelte | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib/components/documents/Settings/General.svelte b/src/lib/components/documents/Settings/General.svelte index 038fa83b..503cbc84 100644 --- a/src/lib/components/documents/Settings/General.svelte +++ b/src/lib/components/documents/Settings/General.svelte @@ -1,6 +1,12 @@ @@ -144,6 +155,15 @@ + +
+
RAG Template
+