|
31 | 31 | from open_webui.models.shared_chats import SharedChatResponse, SharedChats |
32 | 32 | from open_webui.models.tags import TagModel, Tags |
33 | 33 | from open_webui.socket.main import get_event_emitter |
34 | | -from open_webui.tasks import stop_item_tasks |
| 34 | +from open_webui.tasks import has_active_tasks, stop_item_tasks |
35 | 35 | from open_webui.utils.access_control import filter_allowed_access_grants, has_permission |
36 | 36 | from open_webui.utils.access_control.folders import has_folder_access |
37 | 37 | from open_webui.utils.auth import get_admin_user, get_verified_user |
| 38 | +from open_webui.utils.context_compaction import compact_chat_branch |
38 | 39 | from open_webui.utils.middleware import serialize_output |
39 | 40 | from open_webui.utils.misc import get_message_list |
| 41 | +from open_webui.utils.models import get_all_models |
40 | 42 | from pydantic import BaseModel |
41 | 43 | from sqlalchemy.ext.asyncio import AsyncSession |
42 | 44 |
|
43 | 45 | log = logging.getLogger(__name__) |
44 | 46 |
|
45 | 47 | router = APIRouter() |
46 | 48 |
|
| 49 | +CHAT_CONFIG_KEYS = { |
| 50 | + 'ENABLE_CONTEXT_COMPACTION': 'chat.context_compaction.enable', |
| 51 | + 'CONTEXT_COMPACTION_TOKEN_THRESHOLD': 'chat.context_compaction.token_threshold', |
| 52 | + 'CONTEXT_COMPACTION_PROMPT_TEMPLATE': 'chat.context_compaction.prompt_template', |
| 53 | +} |
| 54 | + |
| 55 | + |
| 56 | +class ChatConfigForm(BaseModel): |
| 57 | + ENABLE_CONTEXT_COMPACTION: bool |
| 58 | + CONTEXT_COMPACTION_TOKEN_THRESHOLD: int |
| 59 | + CONTEXT_COMPACTION_PROMPT_TEMPLATE: str |
| 60 | + |
| 61 | + |
| 62 | +class CompactChatForm(BaseModel): |
| 63 | + model: str | None = None |
| 64 | + |
| 65 | + |
| 66 | +async def get_chat_config_values() -> dict: |
| 67 | + values = await Config.get_many(*CHAT_CONFIG_KEYS.values()) |
| 68 | + return {field: values[storage_key] for field, storage_key in CHAT_CONFIG_KEYS.items() if storage_key in values} |
| 69 | + |
| 70 | + |
| 71 | +def chat_config_updates(data: dict) -> dict: |
| 72 | + return {CHAT_CONFIG_KEYS[field]: value for field, value in data.items() if field in CHAT_CONFIG_KEYS} |
| 73 | + |
47 | 74 |
|
48 | 75 | async def require_chat_import_permission(request: Request, user, db: AsyncSession): |
49 | 76 | if user.role != 'admin' and not await has_permission( |
@@ -612,6 +639,30 @@ async def import_chats( |
612 | 639 | raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()) |
613 | 640 |
|
614 | 641 |
|
| 642 | +############################ |
| 643 | +# ChatConfig |
| 644 | +############################ |
| 645 | + |
| 646 | + |
| 647 | +@router.get('/config', response_model=ChatConfigForm) |
| 648 | +async def get_chat_config(user=Depends(get_admin_user)): |
| 649 | + return await get_chat_config_values() |
| 650 | + |
| 651 | + |
| 652 | +@router.post('/config', response_model=ChatConfigForm) |
| 653 | +async def set_chat_config(form_data: ChatConfigForm, user=Depends(get_admin_user)): |
| 654 | + threshold = max(1, int(form_data.CONTEXT_COMPACTION_TOKEN_THRESHOLD)) |
| 655 | + await Config.upsert( |
| 656 | + chat_config_updates( |
| 657 | + { |
| 658 | + **form_data.model_dump(), |
| 659 | + 'CONTEXT_COMPACTION_TOKEN_THRESHOLD': threshold, |
| 660 | + } |
| 661 | + ) |
| 662 | + ) |
| 663 | + return await get_chat_config_values() |
| 664 | + |
| 665 | + |
615 | 666 | ############################ |
616 | 667 | # GetChats |
617 | 668 | ############################ |
@@ -976,6 +1027,49 @@ async def get_user_chat_list_by_tag_name( |
976 | 1027 | return chats |
977 | 1028 |
|
978 | 1029 |
|
| 1030 | +############################ |
| 1031 | +# CompactChat |
| 1032 | +############################ |
| 1033 | + |
| 1034 | + |
| 1035 | +@router.post('/{id}/compact') |
| 1036 | +async def compact_chat_by_id( |
| 1037 | + request: Request, |
| 1038 | + id: str, |
| 1039 | + form_data: CompactChatForm | None = None, |
| 1040 | + user=Depends(get_verified_user), |
| 1041 | + db: AsyncSession = Depends(get_async_session), |
| 1042 | +): |
| 1043 | + chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) |
| 1044 | + if not chat: |
| 1045 | + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) |
| 1046 | + |
| 1047 | + if await has_active_tasks(request.app.state.redis, id): |
| 1048 | + raise HTTPException( |
| 1049 | + status_code=status.HTTP_409_CONFLICT, |
| 1050 | + detail='Wait for the current response to finish before compacting.', |
| 1051 | + ) |
| 1052 | + |
| 1053 | + if not request.app.state.MODELS: |
| 1054 | + await get_all_models(request, user=user) |
| 1055 | + |
| 1056 | + history = (chat.chat or {}).get('history') or {} |
| 1057 | + messages_map = await Chats.get_messages_map_by_chat_id(id) |
| 1058 | + message_list = get_message_list(messages_map or history.get('messages') or {}, history.get('currentId')) |
| 1059 | + model_id = (form_data.model if form_data else None) or next( |
| 1060 | + (message.get('model') for message in reversed(message_list) if message.get('model')), |
| 1061 | + None, |
| 1062 | + ) |
| 1063 | + |
| 1064 | + if not model_id: |
| 1065 | + chat_models = (chat.chat or {}).get('models') or [] |
| 1066 | + model_id = chat_models[0] if chat_models else None |
| 1067 | + if not model_id: |
| 1068 | + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='No model found for context compaction.') |
| 1069 | + |
| 1070 | + return await compact_chat_branch(request, user, chat, model_id, request.app.state.MODELS) |
| 1071 | + |
| 1072 | + |
979 | 1073 | ############################ |
980 | 1074 | # GetChatById |
981 | 1075 | ############################ |
|
0 commit comments