diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index cc46184d4a..2b42c028b1 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2157,10 +2157,13 @@ async def convert_url_images_to_base64(form_data, user=None): return form_data +MESSAGE_REPLAY_KEYS = ('id', 'role', 'content', 'output', 'files', 'contextSummary', 'usage', 'model') + + async def load_messages_from_db(chat_id: str, message_id: str) -> Optional[list[dict]]: """ Load the message chain from DB up to message_id, - keeping only LLM-relevant fields (role, content, output). + keeping only fields needed to rebuild the LLM payload. """ messages_map = await Chats.get_messages_map_by_chat_id(chat_id) if not messages_map: @@ -2170,10 +2173,7 @@ async def load_messages_from_db(chat_id: str, message_id: str) -> Optional[list[ if not db_messages: return None - return [ - {k: v for k, v in msg.items() if k in ('id', 'role', 'content', 'output', 'files', 'contextSummary', 'usage')} - for msg in db_messages - ] + return [{k: v for k, v in msg.items() if k in MESSAGE_REPLAY_KEYS} for msg in db_messages] def get_reasoning_format(model: dict) -> str | None: @@ -2194,6 +2194,13 @@ def get_reasoning_format(model: dict) -> str | None: return None +def strip_reasoning_details(output: list) -> list: + return [ + {key: value for key, value in item.items() if key != 'reasoning_details'} if isinstance(item, dict) else item + for item in output + ] + + def process_messages_with_output( messages: list[dict], reasoning_format: str | None = None, @@ -2220,7 +2227,7 @@ def process_messages_with_output( continue clean_message = dict(message) - for key in ('id', 'files', 'output', 'contextSummary', 'context_summary', 'usage'): + for key in ('id', 'files', 'output', 'model', 'contextSummary', 'context_summary', 'usage'): clean_message.pop(key, None) processed.append(clean_message) @@ -2416,13 +2423,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): if assistant_message_id: assistant_message = await Chats.get_message_by_id_and_message_id(chat_id, assistant_message_id) if assistant_message and (assistant_message.get('content') or assistant_message.get('output')): - db_messages.append( - { - k: v - for k, v in assistant_message.items() - if k in ('id', 'role', 'content', 'output', 'files', 'contextSummary', 'usage') - } - ) + db_messages.append({k: v for k, v in assistant_message.items() if k in MESSAGE_REPLAY_KEYS}) system_message = get_system_message(form_data.get('messages', [])) form_data['messages'] = [system_message, *db_messages] if system_message else db_messages @@ -2486,6 +2487,13 @@ async def process_chat_payload(request, form_data, user, metadata, model): log.exception('Context compaction failed; continuing with full chat history') # Process messages with OR-aligned output items for clean LLM messages + for message in form_data.get('messages', []): + output = message.get('output') + # reasoning_details can be model/provider-bound, so only replay them + # for output produced by the same model. + if message.get('role') == 'assistant' and message.get('model') != model['id'] and isinstance(output, list): + message['output'] = strip_reasoning_details(output) + form_data['messages'] = process_messages_with_output( form_data.get('messages', []), reasoning_format=get_reasoning_format(model), @@ -3365,13 +3373,18 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) - if db_messages: 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 ('id', 'role', 'content', 'output', 'files', 'contextSummary', 'usage') - } - ) + db_messages.append({k: v for k, v in assistant_message.items() if k in MESSAGE_REPLAY_KEYS}) + for message in db_messages: + output = message.get('output') + # reasoning_details can be model/provider-bound, so only replay them + # for output produced by the same model. + if ( + message.get('role') == 'assistant' + and message.get('model') != model['id'] + and isinstance(output, list) + ): + message['output'] = strip_reasoning_details(output) + form_data['messages'] = process_messages_with_output( db_messages, reasoning_format=get_reasoning_format(model),