enh: bypass embedding and retrieval

This commit is contained in:
Timothy Jaeryang Baek 2025-02-26 15:42:19 -08:00
parent 1c2e36f1b7
commit 57010901e6
10 changed files with 486 additions and 370 deletions

View File

@ -1502,13 +1502,16 @@ VECTOR_DB = os.environ.get("VECTOR_DB", "chroma")
# Chroma # Chroma
if VECTOR_DB == "chroma": if VECTOR_DB == "chroma":
import chromadb import chromadb
CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db" CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
CHROMA_TENANT = os.environ.get("CHROMA_TENANT", chromadb.DEFAULT_TENANT) CHROMA_TENANT = os.environ.get("CHROMA_TENANT", chromadb.DEFAULT_TENANT)
CHROMA_DATABASE = os.environ.get("CHROMA_DATABASE", chromadb.DEFAULT_DATABASE) CHROMA_DATABASE = os.environ.get("CHROMA_DATABASE", chromadb.DEFAULT_DATABASE)
CHROMA_HTTP_HOST = os.environ.get("CHROMA_HTTP_HOST", "") CHROMA_HTTP_HOST = os.environ.get("CHROMA_HTTP_HOST", "")
CHROMA_HTTP_PORT = int(os.environ.get("CHROMA_HTTP_PORT", "8000")) CHROMA_HTTP_PORT = int(os.environ.get("CHROMA_HTTP_PORT", "8000"))
CHROMA_CLIENT_AUTH_PROVIDER = os.environ.get("CHROMA_CLIENT_AUTH_PROVIDER", "") CHROMA_CLIENT_AUTH_PROVIDER = os.environ.get("CHROMA_CLIENT_AUTH_PROVIDER", "")
CHROMA_CLIENT_AUTH_CREDENTIALS = os.environ.get("CHROMA_CLIENT_AUTH_CREDENTIALS", "") CHROMA_CLIENT_AUTH_CREDENTIALS = os.environ.get(
"CHROMA_CLIENT_AUTH_CREDENTIALS", ""
)
# Comma-separated list of header=value pairs # Comma-separated list of header=value pairs
CHROMA_HTTP_HEADERS = os.environ.get("CHROMA_HTTP_HEADERS", "") CHROMA_HTTP_HEADERS = os.environ.get("CHROMA_HTTP_HEADERS", "")
if CHROMA_HTTP_HEADERS: if CHROMA_HTTP_HEADERS:
@ -1608,6 +1611,14 @@ DOCUMENT_INTELLIGENCE_KEY = PersistentConfig(
os.getenv("DOCUMENT_INTELLIGENCE_KEY", ""), os.getenv("DOCUMENT_INTELLIGENCE_KEY", ""),
) )
BYPASS_EMBEDDING_AND_RETRIEVAL = PersistentConfig(
"BYPASS_EMBEDDING_AND_RETRIEVAL",
"rag.bypass_embedding_and_retrieval",
os.environ.get("BYPASS_EMBEDDING_AND_RETRIEVAL", "False").lower() == "true",
)
RAG_TOP_K = PersistentConfig( RAG_TOP_K = PersistentConfig(
"RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "3")) "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "3"))
) )
@ -1824,10 +1835,10 @@ RAG_WEB_SEARCH_ENGINE = PersistentConfig(
os.getenv("RAG_WEB_SEARCH_ENGINE", ""), os.getenv("RAG_WEB_SEARCH_ENGINE", ""),
) )
RAG_WEB_SEARCH_FULL_CONTEXT = PersistentConfig( BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = PersistentConfig(
"RAG_WEB_SEARCH_FULL_CONTEXT", "BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL",
"rag.web.search.full_context", "rag.web.search.bypass_embedding_and_retrieval",
os.getenv("RAG_WEB_SEARCH_FULL_CONTEXT", "False").lower() == "true", os.getenv("BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL", "False").lower() == "true",
) )
# You can provide a list of your own websites to filter after performing a web search. # You can provide a list of your own websites to filter after performing a web search.

View File

@ -162,6 +162,7 @@ from open_webui.config import (
RAG_TEMPLATE, RAG_TEMPLATE,
DEFAULT_RAG_TEMPLATE, DEFAULT_RAG_TEMPLATE,
RAG_FULL_CONTEXT, RAG_FULL_CONTEXT,
BYPASS_EMBEDDING_AND_RETRIEVAL,
RAG_EMBEDDING_MODEL, RAG_EMBEDDING_MODEL,
RAG_EMBEDDING_MODEL_AUTO_UPDATE, RAG_EMBEDDING_MODEL_AUTO_UPDATE,
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE, RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
@ -191,7 +192,7 @@ from open_webui.config import (
YOUTUBE_LOADER_PROXY_URL, YOUTUBE_LOADER_PROXY_URL,
# Retrieval (Web Search) # Retrieval (Web Search)
RAG_WEB_SEARCH_ENGINE, RAG_WEB_SEARCH_ENGINE,
RAG_WEB_SEARCH_FULL_CONTEXT, BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL,
RAG_WEB_SEARCH_RESULT_COUNT, RAG_WEB_SEARCH_RESULT_COUNT,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS, RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_WEB_SEARCH_TRUST_ENV, RAG_WEB_SEARCH_TRUST_ENV,
@ -531,6 +532,7 @@ app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT
app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL = BYPASS_EMBEDDING_AND_RETRIEVAL
app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = ( app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
@ -567,7 +569,9 @@ app.state.config.YOUTUBE_LOADER_PROXY_URL = YOUTUBE_LOADER_PROXY_URL
app.state.config.ENABLE_RAG_WEB_SEARCH = ENABLE_RAG_WEB_SEARCH app.state.config.ENABLE_RAG_WEB_SEARCH = ENABLE_RAG_WEB_SEARCH
app.state.config.RAG_WEB_SEARCH_ENGINE = RAG_WEB_SEARCH_ENGINE app.state.config.RAG_WEB_SEARCH_ENGINE = RAG_WEB_SEARCH_ENGINE
app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT = RAG_WEB_SEARCH_FULL_CONTEXT app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
)
app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = RAG_WEB_SEARCH_DOMAIN_FILTER_LIST app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = RAG_WEB_SEARCH_DOMAIN_FILTER_LIST
app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION

View File

@ -17,6 +17,7 @@ from open_webui.retrieval.vector.connector import VECTOR_DB_CLIENT
from open_webui.utils.misc import get_last_user_message, calculate_sha256_string from open_webui.utils.misc import get_last_user_message, calculate_sha256_string
from open_webui.models.users import UserModel from open_webui.models.users import UserModel
from open_webui.models.files import Files
from open_webui.env import ( from open_webui.env import (
SRC_LOG_LEVELS, SRC_LOG_LEVELS,
@ -342,6 +343,7 @@ def get_embedding_function(
def get_sources_from_files( def get_sources_from_files(
request,
files, files,
queries, queries,
embedding_function, embedding_function,
@ -359,19 +361,64 @@ def get_sources_from_files(
relevant_contexts = [] relevant_contexts = []
for file in files: for file in files:
context = None
if file.get("docs"): if file.get("docs"):
# BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
context = { context = {
"documents": [[doc.get("content") for doc in file.get("docs")]], "documents": [[doc.get("content") for doc in file.get("docs")]],
"metadatas": [[doc.get("metadata") for doc in file.get("docs")]], "metadatas": [[doc.get("metadata") for doc in file.get("docs")]],
} }
elif file.get("context") == "full": elif file.get("context") == "full":
# Manual Full Mode Toggle
context = { context = {
"documents": [[file.get("file").get("data", {}).get("content")]], "documents": [[file.get("file").get("data", {}).get("content")]],
"metadatas": [[{"file_id": file.get("id"), "name": file.get("name")}]], "metadatas": [[{"file_id": file.get("id"), "name": file.get("name")}]],
} }
else: elif (
context = None file.get("type") != "web_search"
and request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL
):
# BYPASS_EMBEDDING_AND_RETRIEVAL
if file.get("type") == "collection":
file_ids = file.get("data", {}).get("file_ids", [])
documents = []
metadatas = []
for file_id in file_ids:
file_object = Files.get_file_by_id(file_id)
if file_object:
documents.append(file_object.data.get("content", ""))
metadatas.append(
{
"file_id": file_id,
"name": file_object.filename,
"source": file_object.filename,
}
)
context = {
"documents": [documents],
"metadatas": [metadatas],
}
elif file.get("id"):
file_object = Files.get_file_by_id(file.get("id"))
if file_object:
context = {
"documents": [[file_object.data.get("content", "")]],
"metadatas": [
[
{
"file_id": file.get("id"),
"name": file_object.filename,
"source": file_object.filename,
}
]
],
}
else:
collection_names = [] collection_names = []
if file.get("type") == "collection": if file.get("type") == "collection":
if file.get("legacy"): if file.get("legacy"):
@ -434,6 +481,7 @@ def get_sources_from_files(
if context: if context:
if "data" in file: if "data" in file:
del file["data"] del file["data"]
relevant_contexts.append({**context, "file": file}) relevant_contexts.append({**context, "file": file})
sources = [] sources = []

View File

@ -107,8 +107,7 @@ class ChromaClient:
} }
) )
return None return None
except Exception as e: except:
log.exception(f"{e}")
return None return None
def get(self, collection_name: str) -> Optional[GetResult]: def get(self, collection_name: str) -> Optional[GetResult]:

View File

@ -352,6 +352,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
"status": True, "status": True,
"pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES, "pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES,
"RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT, "RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT,
"BYPASS_EMBEDDING_AND_RETRIEVAL": request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL,
"enable_google_drive_integration": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION, "enable_google_drive_integration": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
"enable_onedrive_integration": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION, "enable_onedrive_integration": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
"content_extraction": { "content_extraction": {
@ -378,7 +379,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
}, },
"web": { "web": {
"ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION": request.app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION, "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION": request.app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
"RAG_WEB_SEARCH_FULL_CONTEXT": request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT, "BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL": request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL,
"search": { "search": {
"enabled": request.app.state.config.ENABLE_RAG_WEB_SEARCH, "enabled": request.app.state.config.ENABLE_RAG_WEB_SEARCH,
"drive": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION, "drive": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
@ -473,11 +474,12 @@ class WebSearchConfig(BaseModel):
class WebConfig(BaseModel): class WebConfig(BaseModel):
search: WebSearchConfig search: WebSearchConfig
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION: Optional[bool] = None ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION: Optional[bool] = None
RAG_WEB_SEARCH_FULL_CONTEXT: Optional[bool] = None BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL: Optional[bool] = None
class ConfigUpdateForm(BaseModel): class ConfigUpdateForm(BaseModel):
RAG_FULL_CONTEXT: Optional[bool] = None RAG_FULL_CONTEXT: Optional[bool] = None
BYPASS_EMBEDDING_AND_RETRIEVAL: Optional[bool] = None
pdf_extract_images: Optional[bool] = None pdf_extract_images: Optional[bool] = None
enable_google_drive_integration: Optional[bool] = None enable_google_drive_integration: Optional[bool] = None
enable_onedrive_integration: Optional[bool] = None enable_onedrive_integration: Optional[bool] = None
@ -504,6 +506,12 @@ async def update_rag_config(
else request.app.state.config.RAG_FULL_CONTEXT else request.app.state.config.RAG_FULL_CONTEXT
) )
request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL = (
form_data.BYPASS_EMBEDDING_AND_RETRIEVAL
if form_data.BYPASS_EMBEDDING_AND_RETRIEVAL is not None
else request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL
)
request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ( request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = (
form_data.enable_google_drive_integration form_data.enable_google_drive_integration
if form_data.enable_google_drive_integration is not None if form_data.enable_google_drive_integration is not None
@ -557,8 +565,8 @@ async def update_rag_config(
request.app.state.config.ENABLE_RAG_WEB_SEARCH = form_data.web.search.enabled request.app.state.config.ENABLE_RAG_WEB_SEARCH = form_data.web.search.enabled
request.app.state.config.RAG_WEB_SEARCH_ENGINE = form_data.web.search.engine request.app.state.config.RAG_WEB_SEARCH_ENGINE = form_data.web.search.engine
request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT = ( request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
form_data.web.RAG_WEB_SEARCH_FULL_CONTEXT form_data.web.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
) )
request.app.state.config.SEARXNG_QUERY_URL = ( request.app.state.config.SEARXNG_QUERY_URL = (
@ -626,6 +634,7 @@ async def update_rag_config(
"status": True, "status": True,
"pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES, "pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES,
"RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT, "RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT,
"BYPASS_EMBEDDING_AND_RETRIEVAL": request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL,
"file": { "file": {
"max_size": request.app.state.config.FILE_MAX_SIZE, "max_size": request.app.state.config.FILE_MAX_SIZE,
"max_count": request.app.state.config.FILE_MAX_COUNT, "max_count": request.app.state.config.FILE_MAX_COUNT,
@ -650,7 +659,7 @@ async def update_rag_config(
}, },
"web": { "web": {
"ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION": request.app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION, "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION": request.app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
"RAG_WEB_SEARCH_FULL_CONTEXT": request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT, "BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL": request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL,
"search": { "search": {
"enabled": request.app.state.config.ENABLE_RAG_WEB_SEARCH, "enabled": request.app.state.config.ENABLE_RAG_WEB_SEARCH,
"engine": request.app.state.config.RAG_WEB_SEARCH_ENGINE, "engine": request.app.state.config.RAG_WEB_SEARCH_ENGINE,
@ -1019,36 +1028,45 @@ def process_file(
hash = calculate_sha256_string(text_content) hash = calculate_sha256_string(text_content)
Files.update_file_hash_by_id(file.id, hash) Files.update_file_hash_by_id(file.id, hash)
try: if not request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL:
result = save_docs_to_vector_db( try:
request, result = save_docs_to_vector_db(
docs=docs, request,
collection_name=collection_name, docs=docs,
metadata={ collection_name=collection_name,
"file_id": file.id, metadata={
"name": file.filename, "file_id": file.id,
"hash": hash, "name": file.filename,
}, "hash": hash,
add=(True if form_data.collection_name else False),
user=user,
)
if result:
Files.update_file_metadata_by_id(
file.id,
{
"collection_name": collection_name,
}, },
add=(True if form_data.collection_name else False),
user=user,
) )
return { if result:
"status": True, Files.update_file_metadata_by_id(
"collection_name": collection_name, file.id,
"filename": file.filename, {
"content": text_content, "collection_name": collection_name,
} },
except Exception as e: )
raise e
return {
"status": True,
"collection_name": collection_name,
"filename": file.filename,
"content": text_content,
}
except Exception as e:
raise e
else:
return {
"status": True,
"collection_name": None,
"filename": file.filename,
"content": text_content,
}
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
if "No pandoc was found" in str(e): if "No pandoc was found" in str(e):
@ -1408,9 +1426,11 @@ async def process_web_search(
) )
docs = await loader.aload() docs = await loader.aload()
if request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT: if request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL:
return { return {
"status": True, "status": True,
"collection_name": None,
"filenames": urls,
"docs": [ "docs": [
{ {
"content": doc.page_content, "content": doc.page_content,
@ -1418,7 +1438,6 @@ async def process_web_search(
} }
for doc in docs for doc in docs
], ],
"filenames": urls,
"loaded_count": len(docs), "loaded_count": len(docs),
} }
else: else:

View File

@ -351,24 +351,25 @@ async def chat_web_search_handler(
all_results.append(results) all_results.append(results)
files = form_data.get("files", []) files = form_data.get("files", [])
if request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT: if results.get("collection_name"):
files.append(
{
"docs": results.get("docs", []),
"name": searchQuery,
"type": "web_search_docs",
"urls": results["filenames"],
}
)
else:
files.append( files.append(
{ {
"collection_name": results["collection_name"], "collection_name": results["collection_name"],
"name": searchQuery, "name": searchQuery,
"type": "web_search_results", "type": "web_search",
"urls": results["filenames"], "urls": results["filenames"],
} }
) )
elif results.get("docs"):
files.append(
{
"docs": results.get("docs", []),
"name": searchQuery,
"type": "web_search",
"urls": results["filenames"],
}
)
form_data["files"] = files form_data["files"] = files
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
@ -518,6 +519,7 @@ async def chat_completion_files_handler(
sources = [] sources = []
if files := body.get("metadata", {}).get("files", None): if files := body.get("metadata", {}).get("files", None):
queries = []
try: try:
queries_response = await generate_queries( queries_response = await generate_queries(
request, request,
@ -543,8 +545,8 @@ async def chat_completion_files_handler(
queries_response = {"queries": [queries_response]} queries_response = {"queries": [queries_response]}
queries = queries_response.get("queries", []) queries = queries_response.get("queries", [])
except Exception as e: except:
queries = [] pass
if len(queries) == 0: if len(queries) == 0:
queries = [get_last_user_message(body["messages"])] queries = [get_last_user_message(body["messages"])]
@ -556,6 +558,7 @@ async def chat_completion_files_handler(
sources = await loop.run_in_executor( sources = await loop.run_in_executor(
executor, executor,
lambda: get_sources_from_files( lambda: get_sources_from_files(
request=request,
files=files, files=files,
queries=queries, queries=queries,
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION( embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
@ -738,6 +741,7 @@ async def process_chat_payload(request, form_data, metadata, user, model):
tool_ids = form_data.pop("tool_ids", None) tool_ids = form_data.pop("tool_ids", None)
files = form_data.pop("files", None) files = form_data.pop("files", None)
# Remove files duplicates # Remove files duplicates
if files: if files:
files = list({json.dumps(f, sort_keys=True): f for f in files}.values()) files = list({json.dumps(f, sort_keys=True): f for f in files}.values())
@ -795,8 +799,6 @@ async def process_chat_payload(request, form_data, metadata, user, model):
if len(sources) > 0: if len(sources) > 0:
context_string = "" context_string = ""
for source_idx, source in enumerate(sources): for source_idx, source in enumerate(sources):
source_id = source.get("source", {}).get("name", "")
if "document" in source: if "document" in source:
for doc_idx, doc_context in enumerate(source["document"]): for doc_idx, doc_context in enumerate(source["document"]):
context_string += f"<source><source_id>{source_idx}</source_id><source_context>{doc_context}</source_context></source>\n" context_string += f"<source><source_id>{source_idx}</source_id><source_context>{doc_context}</source_context></source>\n"
@ -1913,7 +1915,9 @@ async def process_chat_response(
) )
log.info(f"content_blocks={content_blocks}") log.info(f"content_blocks={content_blocks}")
log.info(f"serialize_content_blocks={serialize_content_blocks(content_blocks)}") log.info(
f"serialize_content_blocks={serialize_content_blocks(content_blocks)}"
)
try: try:
res = await generate_chat_completion( res = await generate_chat_completion(

View File

@ -59,6 +59,7 @@
let pdfExtractImages = true; let pdfExtractImages = true;
let RAG_FULL_CONTEXT = false; let RAG_FULL_CONTEXT = false;
let BYPASS_EMBEDDING_AND_RETRIEVAL = false;
let enableGoogleDriveIntegration = false; let enableGoogleDriveIntegration = false;
let enableOneDriveIntegration = false; let enableOneDriveIntegration = false;
@ -170,12 +171,6 @@
}; };
const submitHandler = async () => { const submitHandler = async () => {
await embeddingModelUpdateHandler();
if (querySettings.hybrid) {
await rerankingModelUpdateHandler();
}
if (contentExtractionEngine === 'tika' && tikaServerUrl === '') { if (contentExtractionEngine === 'tika' && tikaServerUrl === '') {
toast.error($i18n.t('Tika Server URL required.')); toast.error($i18n.t('Tika Server URL required.'));
return; return;
@ -187,6 +182,15 @@
toast.error($i18n.t('Document Intelligence endpoint and key required.')); toast.error($i18n.t('Document Intelligence endpoint and key required.'));
return; return;
} }
if (!BYPASS_EMBEDDING_AND_RETRIEVAL) {
await embeddingModelUpdateHandler();
if (querySettings.hybrid) {
await rerankingModelUpdateHandler();
}
}
const res = await updateRAGConfig(localStorage.token, { const res = await updateRAGConfig(localStorage.token, {
pdf_extract_images: pdfExtractImages, pdf_extract_images: pdfExtractImages,
enable_google_drive_integration: enableGoogleDriveIntegration, enable_google_drive_integration: enableGoogleDriveIntegration,
@ -196,6 +200,7 @@
max_count: fileMaxCount === '' ? null : fileMaxCount max_count: fileMaxCount === '' ? null : fileMaxCount
}, },
RAG_FULL_CONTEXT: RAG_FULL_CONTEXT, RAG_FULL_CONTEXT: RAG_FULL_CONTEXT,
BYPASS_EMBEDDING_AND_RETRIEVAL: BYPASS_EMBEDDING_AND_RETRIEVAL,
chunk: { chunk: {
text_splitter: textSplitter, text_splitter: textSplitter,
chunk_overlap: chunkOverlap, chunk_overlap: chunkOverlap,
@ -260,6 +265,7 @@
chunkOverlap = res.chunk.chunk_overlap; chunkOverlap = res.chunk.chunk_overlap;
RAG_FULL_CONTEXT = res.RAG_FULL_CONTEXT; RAG_FULL_CONTEXT = res.RAG_FULL_CONTEXT;
BYPASS_EMBEDDING_AND_RETRIEVAL = res.BYPASS_EMBEDDING_AND_RETRIEVAL;
contentExtractionEngine = res.content_extraction.engine; contentExtractionEngine = res.content_extraction.engine;
tikaServerUrl = res.content_extraction.tika_server_url; tikaServerUrl = res.content_extraction.tika_server_url;
@ -328,9 +334,6 @@
<select <select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right" class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
bind:value={contentExtractionEngine} bind:value={contentExtractionEngine}
on:change={(e) => {
showDocumentIntelligenceConfig = e.target.value === 'document_intelligence';
}}
> >
<option value="">{$i18n.t('Default')} </option> <option value="">{$i18n.t('Default')} </option>
<option value="tika">{$i18n.t('Tika')}</option> <option value="tika">{$i18n.t('Tika')}</option>
@ -376,151 +379,295 @@
{/if} {/if}
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div> <div class=" self-center text-xs font-medium">
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
{$i18n.t('Bypass Embedding and Retrieval')}
</Tooltip>
</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <Tooltip
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right" content={BYPASS_EMBEDDING_AND_RETRIEVAL
bind:value={textSplitter} ? 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
> >
<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option> <Switch bind:state={BYPASS_EMBEDDING_AND_RETRIEVAL} />
<option value="token">{$i18n.t('Token')} ({$i18n.t('Tiktoken')})</option> </Tooltip>
</select>
</div> </div>
</div> </div>
<div class=" mb-2.5 flex w-full justify-between"> {#if !BYPASS_EMBEDDING_AND_RETRIEVAL}
<div class=" flex gap-1.5 w-full"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" w-full justify-between"> <div class=" self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div>
<div class="self-center text-xs font-medium min-w-fit mb-1">
{$i18n.t('Chunk Size')}
</div>
<div class="self-center">
<input
class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="number"
placeholder={$i18n.t('Enter Chunk Size')}
bind:value={chunkSize}
autocomplete="off"
min="0"
/>
</div>
</div>
<div class="w-full">
<div class=" self-center text-xs font-medium min-w-fit mb-1">
{$i18n.t('Chunk Overlap')}
</div>
<div class="self-center">
<input
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="number"
placeholder={$i18n.t('Enter Chunk Overlap')}
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div>
</div>
</div>
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Embedding')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right" class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
bind:value={embeddingEngine} bind:value={textSplitter}
placeholder="Select an embedding model engine"
on:change={(e) => {
if (e.target.value === 'ollama') {
embeddingModel = '';
} else if (e.target.value === 'openai') {
embeddingModel = 'text-embedding-3-small';
} else if (e.target.value === '') {
embeddingModel = 'sentence-transformers/all-MiniLM-L6-v2';
}
}}
> >
<option value="">{$i18n.t('Default (SentenceTransformers)')}</option> <option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
<option value="ollama">{$i18n.t('Ollama')}</option> <option value="token">{$i18n.t('Token')} ({$i18n.t('Tiktoken')})</option>
<option value="openai">{$i18n.t('OpenAI')}</option>
</select> </select>
</div> </div>
</div> </div>
{#if embeddingEngine === 'openai'} <div class=" mb-2.5 flex w-full justify-between">
<div class="my-0.5 flex gap-2 pr-2"> <div class=" flex gap-1.5 w-full">
<input <div class=" w-full justify-between">
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" <div class="self-center text-xs font-medium min-w-fit mb-1">
placeholder={$i18n.t('API Base URL')} {$i18n.t('Chunk Size')}
bind:value={OpenAIUrl} </div>
required <div class="self-center">
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={OpenAIKey} />
</div>
{:else if embeddingEngine === 'ollama'}
<div class="my-0.5 flex gap-2 pr-2">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('API Base URL')}
bind:value={OllamaUrl}
required
/>
<SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={OllamaKey}
required={false}
/>
</div>
{/if}
</div>
<div class=" mb-2.5 flex flex-col w-full">
<div class=" mb-1 text-xs font-medium">{$i18n.t('Embedding Model')}</div>
<div class="">
{#if embeddingEngine === 'ollama'}
<div class="flex w-full">
<div class="flex-1 mr-2">
<input <input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={embeddingModel} type="number"
placeholder={$i18n.t('Set embedding model')} placeholder={$i18n.t('Enter Chunk Size')}
required bind:value={chunkSize}
autocomplete="off"
min="0"
/> />
</div> </div>
</div> </div>
{:else}
<div class="flex w-full"> <div class="w-full">
<div class="flex-1 mr-2"> <div class=" self-center text-xs font-medium min-w-fit mb-1">
<input {$i18n.t('Chunk Overlap')}
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
model: embeddingModel.slice(-40)
})}
bind:value={embeddingModel}
/>
</div> </div>
{#if embeddingEngine === ''} <div class="self-center">
<input
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="number"
placeholder={$i18n.t('Enter Chunk Overlap')}
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div>
</div>
</div>
{/if}
</div>
{#if !BYPASS_EMBEDDING_AND_RETRIEVAL}
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Embedding')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Embedding Model Engine')}
</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={embeddingEngine}
placeholder="Select an embedding model engine"
on:change={(e) => {
if (e.target.value === 'ollama') {
embeddingModel = '';
} else if (e.target.value === 'openai') {
embeddingModel = 'text-embedding-3-small';
} else if (e.target.value === '') {
embeddingModel = 'sentence-transformers/all-MiniLM-L6-v2';
}
}}
>
<option value="">{$i18n.t('Default (SentenceTransformers)')}</option>
<option value="ollama">{$i18n.t('Ollama')}</option>
<option value="openai">{$i18n.t('OpenAI')}</option>
</select>
</div>
</div>
{#if embeddingEngine === 'openai'}
<div class="my-0.5 flex gap-2 pr-2">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('API Base URL')}
bind:value={OpenAIUrl}
required
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={OpenAIKey} />
</div>
{:else if embeddingEngine === 'ollama'}
<div class="my-0.5 flex gap-2 pr-2">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('API Base URL')}
bind:value={OllamaUrl}
required
/>
<SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={OllamaKey}
required={false}
/>
</div>
{/if}
</div>
<div class=" mb-2.5 flex flex-col w-full">
<div class=" mb-1 text-xs font-medium">{$i18n.t('Embedding Model')}</div>
<div class="">
{#if embeddingEngine === 'ollama'}
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
bind:value={embeddingModel}
placeholder={$i18n.t('Set embedding model')}
required
/>
</div>
</div>
{:else}
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
model: embeddingModel.slice(-40)
})}
bind:value={embeddingModel}
/>
</div>
{#if embeddingEngine === ''}
<button
class="px-2.5 bg-transparent text-gray-800 dark:bg-transparent dark:text-gray-100 rounded-lg transition"
on:click={() => {
embeddingModelUpdateHandler();
}}
disabled={updateEmbeddingModelLoading}
>
{#if updateEmbeddingModelLoading}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style>
<path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/>
<path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/>
</svg>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
{/if}
</div>
{/if}
</div>
<div class="mt-1 mb-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t(
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
)}
</div>
</div>
{#if embeddingEngine === 'ollama' || embeddingEngine === 'openai'}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Batch Size')}</div>
<div class="">
<input
bind:value={embeddingBatchSize}
type="number"
class=" bg-transparent text-center w-14 outline-none"
min="-2"
max="16000"
step="1"
/>
</div>
</div>
{/if}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Full Context Mode')}</div>
<div class="flex items-center relative">
<Tooltip
content={RAG_FULL_CONTEXT
? 'Inject entire contents as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
>
<Switch bind:state={RAG_FULL_CONTEXT} />
</Tooltip>
</div>
</div>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div>
<div class="flex items-center relative">
<Switch
bind:state={querySettings.hybrid}
on:change={() => {
toggleHybridSearch();
}}
/>
</div>
</div>
{#if querySettings.hybrid === true}
<div class=" mb-2.5 flex flex-col w-full">
<div class=" mb-1 text-xs font-medium">{$i18n.t('Reranking Model')}</div>
<div class="">
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
model: 'BAAI/bge-reranker-v2-m3'
})}
bind:value={rerankingModel}
/>
</div>
<button <button
class="px-2.5 bg-transparent text-gray-800 dark:bg-transparent dark:text-gray-100 rounded-lg transition" class="px-2.5 bg-transparent text-gray-800 dark:bg-transparent dark:text-gray-100 rounded-lg transition"
on:click={() => { on:click={() => {
embeddingModelUpdateHandler(); rerankingModelUpdateHandler();
}} }}
disabled={updateEmbeddingModelLoading} disabled={updateRerankingModelLoading}
> >
{#if updateEmbeddingModelLoading} {#if updateRerankingModelLoading}
<div class="self-center"> <div class="self-center">
<svg <svg
class=" w-4 h-4" class=" w-4 h-4"
@ -566,196 +713,75 @@
</svg> </svg>
{/if} {/if}
</button> </button>
{/if} </div>
</div> </div>
{/if} </div>
</div> {/if}
<div class="mt-1 mb-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t(
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
)}
</div>
</div> </div>
{#if embeddingEngine === 'ollama' || embeddingEngine === 'openai'} <div class="mb-3">
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 text-base font-medium">{$i18n.t('Retrieval')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Batch Size')}</div>
<div class=""> <hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Top K')}</div>
<div class="flex items-center relative">
<input <input
bind:value={embeddingBatchSize} class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number" type="number"
class=" bg-transparent text-center w-14 outline-none" placeholder={$i18n.t('Enter Top K')}
min="-2" bind:value={querySettings.k}
max="16000" autocomplete="off"
step="1" min="0"
/> />
</div> </div>
</div> </div>
{/if}
<div class=" mb-2.5 flex w-full justify-between"> {#if querySettings.hybrid === true}
<div class=" self-center text-xs font-medium">{$i18n.t('Full Context Mode')}</div> <div class=" mb-2.5 flex flex-col w-full justify-between">
<div class="flex items-center relative"> <div class=" flex w-full justify-between">
<Tooltip <div class=" self-center text-xs font-medium">{$i18n.t('Minimum Score')}</div>
content={RAG_FULL_CONTEXT <div class="flex items-center relative">
? 'Inject entire contents as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
>
<Switch bind:state={RAG_FULL_CONTEXT} />
</Tooltip>
</div>
</div>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div>
<div class="flex items-center relative">
<Switch
bind:state={querySettings.hybrid}
on:change={() => {
toggleHybridSearch();
}}
/>
</div>
</div>
{#if querySettings.hybrid === true}
<div class=" mb-2.5 flex flex-col w-full">
<div class=" mb-1 text-xs font-medium">{$i18n.t('Reranking Model')}</div>
<div class="">
<div class="flex w-full">
<div class="flex-1 mr-2">
<input <input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Set reranking model (e.g. {{model}})', { type="number"
model: 'BAAI/bge-reranker-v2-m3' step="0.01"
})} placeholder={$i18n.t('Enter Score')}
bind:value={rerankingModel} bind:value={querySettings.r}
autocomplete="off"
min="0.0"
title={$i18n.t('The score should be a value between 0.0 (0%) and 1.0 (100%).')}
/> />
</div> </div>
<button
class="px-2.5 bg-transparent text-gray-800 dark:bg-transparent dark:text-gray-100 rounded-lg transition"
on:click={() => {
rerankingModelUpdateHandler();
}}
disabled={updateRerankingModelLoading}
>
{#if updateRerankingModelLoading}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style>
<path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/>
<path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/>
</svg>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
</div> </div>
</div> <div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
</div> {$i18n.t(
{/if} 'Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.'
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Query')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Top K')}</div>
<div class="flex items-center relative">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number"
placeholder={$i18n.t('Enter Top K')}
bind:value={querySettings.k}
autocomplete="off"
min="0"
/>
</div>
</div>
{#if querySettings.hybrid === true}
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Minimum Score')}</div>
<div class="flex items-center relative">
<input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number"
step="0.01"
placeholder={$i18n.t('Enter Score')}
bind:value={querySettings.r}
autocomplete="off"
min="0.0"
title={$i18n.t('The score should be a value between 0.0 (0%) and 1.0 (100%).')}
/>
</div>
</div>
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t(
'Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.'
)}
</div>
</div>
{/if}
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class=" mb-1 text-xs font-medium">{$i18n.t('RAG Template')}</div>
<div class="flex w-full items-center relative">
<Tooltip
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
placement="top-start"
className="w-full"
>
<Textarea
bind:value={querySettings.template}
placeholder={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt'
)} )}
/> </div>
</Tooltip> </div>
{/if}
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class=" mb-1 text-xs font-medium">{$i18n.t('RAG Template')}</div>
<div class="flex w-full items-center relative">
<Tooltip
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
placement="top-start"
className="w-full"
>
<Textarea
bind:value={querySettings.template}
placeholder={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt'
)}
/>
</Tooltip>
</div>
</div> </div>
</div> </div>
</div> {/if}
<div class="mb-3"> <div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Files')}</div> <div class=" mb-2.5 text-base font-medium">{$i18n.t('Files')}</div>

View File

@ -118,14 +118,18 @@
</div> </div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Full Context Mode')}</div> <div class=" self-center text-xs font-medium">
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
{$i18n.t('Bypass Embedding and Retrieval')}
</Tooltip>
</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Tooltip <Tooltip
content={webConfig.RAG_WEB_SEARCH_FULL_CONTEXT content={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
? 'Inject the entire web results as context for comprehensive processing, this is recommended for complex queries.' ? 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'} : 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
> >
<Switch bind:state={webConfig.RAG_WEB_SEARCH_FULL_CONTEXT} /> <Switch bind:state={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL} />
</Tooltip> </Tooltip>
</div> </div>
</div> </div>

View File

@ -43,6 +43,7 @@
} }
$: { $: {
console.log('sources', sources);
citations = sources.reduce((acc, source) => { citations = sources.reduce((acc, source) => {
if (Object.keys(source).length === 0) { if (Object.keys(source).length === 0) {
return acc; return acc;
@ -53,7 +54,7 @@
const distance = source.distances?.[index]; const distance = source.distances?.[index];
// Within the same citation there could be multiple documents // Within the same citation there could be multiple documents
const id = metadata?.source ?? 'N/A'; const id = metadata?.source ?? source?.source?.id ?? 'N/A';
let _source = source?.source; let _source = source?.source;
if (metadata?.name) { if (metadata?.name) {

View File

@ -87,7 +87,7 @@
<div> <div>
<Tooltip <Tooltip
content={enableFullContent content={enableFullContent
? 'Inject the entire document as context for comprehensive processing, this is recommended for complex queries.' ? 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'} : 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
> >
<div class="flex items-center gap-1.5 text-xs"> <div class="flex items-center gap-1.5 text-xs">