This commit is contained in:
Timothy Jaeryang Baek committed 2026-08-11 17:35:05 -06:00
1 parent 65c3539666
commit 4f9a0ebf71
5 files changed
+192 -44

No files matched your search

+4
View File
@@ -438,6 +438,10 @@ async def reindex_knowledge_base_metadata_embeddings(
""" """
knowledge_bases = await Knowledges.get_knowledge_bases() knowledge_bases = await Knowledges.get_knowledge_bases()
log.info('Reindexing embeddings for %s knowledge bases', len(knowledge_bases)) log.info('Reindexing embeddings for %s knowledge bases', len(knowledge_bases))
try:
await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name=KNOWLEDGE_BASES_COLLECTION)
except Exception as e:
log.debug(e)
success_count = 0 success_count = 0
for kb in knowledge_bases: for kb in knowledge_bases:
+103 -36
View File
@@ -11,9 +11,10 @@ from open_webui.events import EVENTS, publish_event
from open_webui.internal.db import get_async_session from open_webui.internal.db import get_async_session
from open_webui.models.config import Config from open_webui.models.config import Config
from open_webui.models.memories import Memories, MemoryModel from open_webui.models.memories import Memories, MemoryModel
from open_webui.models.users import Users
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.utils.access_control import has_permission from open_webui.utils.access_control import has_permission
from open_webui.utils.auth import get_verified_user from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.memory import ( from open_webui.utils.memory import (
clean_memory_content, clean_memory_content,
clean_memory_path, clean_memory_path,
@@ -124,6 +125,61 @@ def _memory_metadata(memory: MemoryModel) -> dict:
} }
async def reindex_memory_vectors_for_user(
request: Request,
user_id: str,
memories: list[MemoryModel] | None = None,
user=None,
) -> int:
collection_name = f'user-memory-{user_id}'
try:
await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name)
except Exception as e:
log.debug(e)
memories = memories if memories is not None else await Memories.get_memories_by_user_id(user_id)
memories = memories or []
if not memories:
return 0
vectors = await asyncio.gather(
*[
request.app.state.EMBEDDING_FUNCTION(
memory_vector_text(memory.content, memory.path),
prefix=RAG_EMBEDDING_CONTENT_PREFIX,
user=user,
)
for memory in memories
]
)
await ASYNC_VECTOR_DB_CLIENT.upsert(
collection_name=collection_name,
items=[
{
'id': memory.id,
'text': memory_vector_text(memory.content, memory.path),
'vector': vectors[idx],
'metadata': _memory_metadata(memory),
}
for idx, memory in enumerate(memories)
],
)
return len(memories)
async def upsert_memory_vectors_or_reindex(request: Request, user, items: list[dict]) -> None:
try:
await ASYNC_VECTOR_DB_CLIENT.upsert(collection_name=f'user-memory-{user.id}', items=items)
except Exception as e:
message = str(e).lower()
if 'dimension' not in message or 'embedding' not in message:
raise
log.warning('Memory vector dimension mismatch for user %s; reindexing memory vectors.', user.id)
await reindex_memory_vectors_for_user(request, user.id, user=user)
@router.post('/add', response_model=MemoryModel | None) @router.post('/add', response_model=MemoryModel | None)
async def add_memory( async def add_memory(
request: Request, request: Request,
@@ -152,9 +208,10 @@ async def add_memory(
memory_vector_text(memory.content, memory.path), prefix=RAG_EMBEDDING_CONTENT_PREFIX, user=user memory_vector_text(memory.content, memory.path), prefix=RAG_EMBEDDING_CONTENT_PREFIX, user=user
) )
await ASYNC_VECTOR_DB_CLIENT.upsert( await upsert_memory_vectors_or_reindex(
collection_name=f'user-memory-{user.id}', request,
items=[ user,
[
{ {
'id': memory.id, 'id': memory.id,
'text': memory_vector_text(memory.content, memory.path), 'text': memory_vector_text(memory.content, memory.path),
@@ -226,7 +283,7 @@ async def update_memories(
response.append(result) response.append(result)
if upsert_items: if upsert_items:
await ASYNC_VECTOR_DB_CLIENT.upsert(collection_name=f'user-memory-{user.id}', items=upsert_items) await upsert_memory_vectors_or_reindex(request, user, upsert_items)
if delete_ids: if delete_ids:
await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=delete_ids) await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=delete_ids)
@@ -386,8 +443,42 @@ async def read_memory_path(
############################ ############################
# ResetMemoryFromVectorDB # ReindexMemoryVectorDB
############################ ############################
@router.post('/reindex')
async def reindex_memories_from_vector_db(
request: Request,
user=Depends(get_admin_user),
):
memories = await Memories.get_memories()
memories = memories or []
memories_by_user_id = {}
for memory in memories:
memories_by_user_id.setdefault(memory.user_id, []).append(memory)
users_result = await Users.get_users()
users = users_result.get('users', []) if users_result else []
total_memories = 0
for memory_user in users:
total_memories += await reindex_memory_vectors_for_user(
request,
memory_user.id,
memories=memories_by_user_id.get(memory_user.id, []),
user=memory_user,
)
await publish_event(
request,
EVENTS.MEMORY_RESET,
actor=user,
subject_id='all',
subject_type='user',
data={'count': total_memories, 'user_count': len(users), 'reindex': True},
)
return {'status': True, 'total_users': len(users), 'total_memories': total_memories}
@router.post('/reset', response_model=bool) @router.post('/reset', response_model=bool)
async def reset_memory_from_vector_db( async def reset_memory_from_vector_db(
request: Request, request: Request,
@@ -403,32 +494,7 @@ async def reset_memory_from_vector_db(
""" """
await check_memories_permission(user) await check_memories_permission(user)
await ASYNC_VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}') count = await reindex_memory_vectors_for_user(request, user.id, user=user)
memories = await Memories.get_memories_by_user_id(user.id)
# Generate vectors in parallel
vectors = await asyncio.gather(
*[
request.app.state.EMBEDDING_FUNCTION(
memory_vector_text(memory.content, memory.path), prefix=RAG_EMBEDDING_CONTENT_PREFIX, user=user
)
for memory in memories
]
)
await ASYNC_VECTOR_DB_CLIENT.upsert(
collection_name=f'user-memory-{user.id}',
items=[
{
'id': memory.id,
'text': memory_vector_text(memory.content, memory.path),
'vector': vectors[idx],
'metadata': _memory_metadata(memory),
}
for idx, memory in enumerate(memories)
],
)
await publish_event( await publish_event(
request, request,
@@ -436,7 +502,7 @@ async def reset_memory_from_vector_db(
actor=user, actor=user,
subject_id=user.id, subject_id=user.id,
subject_type='user', subject_type='user',
data={'count': len(memories)}, data={'count': count, 'reindex': True},
) )
return True return True
@@ -512,9 +578,10 @@ async def update_memory_by_id(
memory_vector_text(memory.content, memory.path), prefix=RAG_EMBEDDING_CONTENT_PREFIX, user=user memory_vector_text(memory.content, memory.path), prefix=RAG_EMBEDDING_CONTENT_PREFIX, user=user
) )
await ASYNC_VECTOR_DB_CLIENT.upsert( await upsert_memory_vectors_or_reindex(
collection_name=f'user-memory-{user.id}', request,
items=[ user,
[
{ {
'id': memory.id, 'id': memory.id,
'text': memory_vector_text(memory.content, memory.path), 'text': memory_vector_text(memory.content, memory.path),
+28
View File
@@ -955,6 +955,34 @@ export const reindexKnowledgeFiles = async (token: string) => {
return res; return res;
}; };
export const reindexKnowledgeMetadata = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/metadata/reindex`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.error(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const exportKnowledgeById = async (token: string, id: string) => { export const exportKnowledgeById = async (token: string, id: string) => {
let error = null; let error = null;
+28
View File
@@ -128,6 +128,34 @@ export const queryMemory = async (token: string, content: string) => {
return res; return res;
}; };
export const reindexMemoryVectors = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/reindex`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.error(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteMemoryById = async (token: string, id: string) => { export const deleteMemoryById = async (token: string, id: string) => {
let error = null; let error = null;
@@ -17,12 +17,13 @@
updateRAGConfig updateRAGConfig
} from '$lib/apis/retrieval'; } from '$lib/apis/retrieval';
import { reindexKnowledgeFiles } from '$lib/apis/knowledge'; import { reindexKnowledgeFiles, reindexKnowledgeMetadata } from '$lib/apis/knowledge';
import { reindexMemoryVectors } from '$lib/apis/memories';
import { deleteAllFiles } from '$lib/apis/files'; import { deleteAllFiles } from '$lib/apis/files';
import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import ReindexKnowledgeFilesConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import ReindexEmbeddingDataConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte'; import Switch from '$lib/components/common/Switch.svelte';
@@ -371,15 +372,35 @@
}} }}
/> />
<ReindexKnowledgeFilesConfirmDialog <ReindexEmbeddingDataConfirmDialog
bind:show={showReindexConfirm} bind:show={showReindexConfirm}
title={$i18n.t('Reindex Embedding Data')}
message={$i18n.t(
'Rebuild knowledge file, knowledge search, and memory vectors using the current embedding model.'
)}
on:confirm={async () => { on:confirm={async () => {
const res = await reindexKnowledgeFiles(localStorage.token).catch((error) => { const knowledgeRes = await reindexKnowledgeFiles(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (!knowledgeRes) {
return;
}
const knowledgeMetadataRes = await reindexKnowledgeMetadata(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (!knowledgeMetadataRes) {
return;
}
const memoryRes = await reindexMemoryVectors(localStorage.token).catch((error) => {
toast.error(`${error}`); toast.error(`${error}`);
return null; return null;
}); });
if (res) { if (memoryRes) {
toast.success($i18n.t('Success')); toast.success($i18n.t('Success'));
} }
}} }}
@@ -1116,7 +1137,7 @@
</div> </div>
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600"> <div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t( {$i18n.t(
'After changing the embedding model, reindex the knowledge base for changes to take effect.' 'After changing the embedding model, reindex knowledge, knowledge search, and memory vectors for changes to take effect.'
)} )}
</div> </div>
</AdminSettingField> </AdminSettingField>
@@ -1526,8 +1547,8 @@
</button> </button>
</AdminSettingRow> </AdminSettingRow>
<AdminSettingRow <AdminSettingRow
label={$i18n.t('Reindex Knowledge Base Vectors')} label={$i18n.t('Reindex Knowledge and Memory Vectors')}
description={$i18n.t('Rebuild vectors for existing knowledge files.')} description={$i18n.t('Rebuild vectors for existing knowledge files, knowledge search, and memories.')}
> >
<button <button
class={actionButtonClass} class={actionButtonClass}