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
This commit is contained in:
Classic298 2026-09-28 05:59:43 +02:00 • committed by GitHub
parent b00745c8d0
commit d4b9d19645
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 24 additions and 3 deletions

View file

@ -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),
)

View file

@ -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),