feat: implement global memories toggle and permissions (#20462)

This commit is contained in:
G30
2026-01-07 14:50:04 -05:00
committed by GitHub
parent 48f1b2d547
commit b73d30b6df
10 changed files with 152 additions and 5 deletions

View File

@@ -1470,6 +1470,10 @@ USER_PERMISSIONS_FEATURES_API_KEYS = (
os.environ.get("USER_PERMISSIONS_FEATURES_API_KEYS", "False").lower() == "true"
)
USER_PERMISSIONS_FEATURES_MEMORIES = (
os.environ.get("USER_PERMISSIONS_FEATURES_MEMORIES", "True").lower() == "true"
)
USER_PERMISSIONS_SETTINGS_INTERFACE = (
os.environ.get("USER_PERMISSIONS_SETTINGS_INTERFACE", "True").lower() == "true"
@@ -1533,6 +1537,7 @@ DEFAULT_USER_PERMISSIONS = {
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
"memories": USER_PERMISSIONS_FEATURES_MEMORIES,
},
"settings": {
"interface": USER_PERMISSIONS_SETTINGS_INTERFACE,
@@ -2083,6 +2088,12 @@ ENABLE_CODE_INTERPRETER = PersistentConfig(
os.environ.get("ENABLE_CODE_INTERPRETER", "True").lower() == "true",
)
ENABLE_MEMORIES = PersistentConfig(
"ENABLE_MEMORIES",
"memories.enable",
os.environ.get("ENABLE_MEMORIES", "True").lower() == "true",
)
CODE_INTERPRETER_ENGINE = PersistentConfig(
"CODE_INTERPRETER_ENGINE",
"code_interpreter.engine",

View File

@@ -145,6 +145,7 @@ from open_webui.config import (
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
CODE_INTERPRETER_JUPYTER_TIMEOUT,
ENABLE_MEMORIES,
# Image
AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL,
@@ -1106,6 +1107,7 @@ app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT = CODE_INTERPRETER_JUPYTER_TIM
app.state.config.IMAGE_GENERATION_ENGINE = IMAGE_GENERATION_ENGINE
app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION
app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION
app.state.config.ENABLE_MEMORIES = ENABLE_MEMORIES
app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
app.state.config.IMAGE_SIZE = IMAGE_SIZE
@@ -1935,6 +1937,7 @@ async def get_app_config(request: Request):
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
"enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
"enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
"enable_memories": app.state.config.ENABLE_MEMORIES,
**(
{
"enable_onedrive_personal": ENABLE_ONEDRIVE_PERSONAL,

View File

@@ -991,6 +991,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
"FOLDER_MAX_FILE_COUNT": request.app.state.config.FOLDER_MAX_FILE_COUNT,
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
"ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES,
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,
@@ -1015,6 +1016,7 @@ class AdminConfig(BaseModel):
ENABLE_FOLDERS: bool
FOLDER_MAX_FILE_COUNT: Optional[int | str] = None
ENABLE_CHANNELS: bool
ENABLE_MEMORIES: bool
ENABLE_NOTES: bool
ENABLE_USER_WEBHOOKS: bool
PENDING_USER_OVERLAY_TITLE: Optional[str] = None
@@ -1046,6 +1048,7 @@ async def update_admin_config(
else None
)
request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS
request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES
request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES
if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]:
@@ -1091,6 +1094,7 @@ async def update_admin_config(
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
"FOLDER_MAX_FILE_COUNT": request.app.state.config.FOLDER_MAX_FILE_COUNT,
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
"ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES,
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
import logging
import asyncio
@@ -10,11 +10,16 @@ from open_webui.utils.auth import get_verified_user
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.utils.access_control import has_permission
from open_webui.constants import ERROR_MESSAGES
log = logging.getLogger(__name__)
router = APIRouter()
@router.get("/ef")
async def get_embeddings(request: Request):
return {"result": await request.app.state.EMBEDDING_FUNCTION("hello world")}
@@ -26,7 +31,21 @@ async def get_embeddings(request: Request):
@router.get("/", response_model=list[MemoryModel])
async def get_memories(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_memories(
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
return Memories.get_memories_by_user_id(user.id, db=db)
@@ -50,6 +69,18 @@ async def add_memory(
user=Depends(get_verified_user),
db: Session = Depends(get_session),
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
memory = Memories.insert_new_memory(user.id, form_data.content, db=db)
vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user)
@@ -83,6 +114,18 @@ class QueryMemoryForm(BaseModel):
async def query_memory(
request: Request, form_data: QueryMemoryForm, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
memories = Memories.get_memories_by_user_id(user.id, db=db)
if not memories:
raise HTTPException(status_code=404, detail="No memories found for user")
@@ -105,6 +148,18 @@ async def query_memory(
async def reset_memory_from_vector_db(
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
VECTOR_DB_CLIENT.delete_collection(f"user-memory-{user.id}")
memories = Memories.get_memories_by_user_id(user.id, db=db)
@@ -142,7 +197,21 @@ async def reset_memory_from_vector_db(
@router.delete("/delete/user", response_model=bool)
async def delete_memory_by_user_id(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def delete_memory_by_user_id(
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Memories.delete_memories_by_user_id(user.id, db=db)
if result:
@@ -168,6 +237,18 @@ async def update_memory_by_id(
user=Depends(get_verified_user),
db: Session = Depends(get_session),
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
memory = Memories.update_memory_by_id_and_user_id(
memory_id, user.id, form_data.content, db=db
)
@@ -201,7 +282,21 @@ async def update_memory_by_id(
@router.delete("/{memory_id}", response_model=bool)
async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def delete_memory_by_id(
memory_id: str, request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
if result:

View File

@@ -226,6 +226,7 @@ class FeaturesPermissions(BaseModel):
web_search: bool = True
image_generation: bool = True
code_interpreter: bool = True
memories: bool = True
class SettingsPermissions(BaseModel):