From d4b9d19645f74039574c3ba583abc7c74ce4a74e Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 28 Sep 2026 05:59:43 +0200 Subject: [PATCH] fix: system prompt and compacted context are lost after approving a tool call (#31501) With tool approval set to ask, the request sent to the model after approving a tool call left out the system prompt. In a compacted chat it also left out the conversation summary and sent the whole history again. The request after approval now has the same system prompt and compacted context as the one before it, plus the tool call and its result. The system prompt is picked the same way as for any other message: the chat Controls prompt, else your personal Settings prompt, else the admin default. A system prompt sent only in an API request is not kept by the server, so it is still missing after approval. Fixes #31499 --- backend/open_webui/utils/middleware.py | 12 +++++++++++- backend/open_webui/utils/tool_approval.py | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 64adaef668..6a3e4b6f5b 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2504,6 +2504,10 @@ async def process_chat_payload(request, form_data, user, metadata, model): except Exception: log.exception('Context compaction failed; continuing with full chat history') + metadata['context_start_message_id'] = next( + (message.get('id') for message in form_data.get('messages', []) if message.get('role') != 'system'), None + ) + # Process messages with OR-aligned output items for clean LLM messages for message in form_data.get('messages', []): output = message.get('output') @@ -3523,6 +3527,11 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) - assistant_message = await Chats.get_message_by_id_and_message_id(chat_id, message_id) if assistant_message: db_messages.append({k: v for k, v in assistant_message.items() if k in MESSAGE_REPLAY_KEYS}) + context_start_message_id = metadata.get('context_start_message_id') + start_index = next( + (index for index, message in enumerate(db_messages) if message.get('id') == context_start_message_id), 0 + ) + db_messages = db_messages[start_index:] for message in db_messages: output = message.get('output') # reasoning_details can be model/provider-bound, so only replay them @@ -3534,8 +3543,9 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) - ): message['output'] = strip_reasoning_details(output) + system_message = get_system_message(form_data.get('messages', [])) form_data['messages'] = process_messages_with_output( - db_messages, + [system_message, *db_messages] if system_message else db_messages, reasoning_format=get_reasoning_format(model), include_file_context=metadata.get('include_file_context', False), ) diff --git a/backend/open_webui/utils/tool_approval.py b/backend/open_webui/utils/tool_approval.py index e2c33e9a74..7d5376bb01 100644 --- a/backend/open_webui/utils/tool_approval.py +++ b/backend/open_webui/utils/tool_approval.py @@ -7,6 +7,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from open_webui.constants import ERROR_MESSAGES from open_webui.env import ENABLE_ADMIN_CHAT_ACCESS from open_webui.models.chats import Chats +from open_webui.models.config import Config +from open_webui.models.users import Users from open_webui.socket.main import get_event_emitter from open_webui.utils.json_codec import JSONCodec @@ -161,8 +163,17 @@ async def build_tool_approval_resume_payload(chat_id: str, message_id: str, chat raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail='Tool call message model is missing.') messages = [] - if params.get('system'): - messages.append({'role': 'system', 'content': params.get('system')}) + system_prompt = params.get('system') + if not system_prompt: + # Mirror the chat UI's system prompt fallback + user = await Users.get_user_by_id(chat.user_id) + ui_settings = (user.settings.ui if user and user.settings else None) or {} + system_prompt = ui_settings.get('system') + if system_prompt is None: + default_interface_settings = await Config.get('ui.default_interface_settings') or {} + system_prompt = default_interface_settings.get('system') + if system_prompt: + messages.append({'role': 'system', 'content': system_prompt}) return { 'stream': params.get('stream_response', True),