diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 7941d80790..3dde15af21 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -3623,7 +3623,31 @@ async def non_streaming_chat_response_handler(response, ctx): # otherwise generate from response content response_output = response_data.get('output') if not response_output: - response_output = [ + choice_message = choices[0].get('message', {}) + reasoning_content = choice_message.get('reasoning_content') or choice_message.get('reasoning') + reasoning_details = choice_message.get('reasoning_details') + response_output = [] + if reasoning_content or reasoning_details: + reasoning_item = { + 'type': 'reasoning', + 'id': output_id('r'), + 'status': 'completed', + 'start_tag': '', + 'end_tag': '', + 'attributes': {'type': 'reasoning_content'}, + 'content': ( + [{'type': 'output_text', 'text': reasoning_content}] + if reasoning_content + else [] + ), + 'summary': None, + } + if reasoning_details: + reasoning_item['reasoning_details'] = ( + reasoning_details if isinstance(reasoning_details, list) else [reasoning_details] + ) + response_output.append(reasoning_item) + response_output.append( { 'type': 'message', 'id': output_id('msg'), @@ -3631,7 +3655,7 @@ async def non_streaming_chat_response_handler(response, ctx): 'role': 'assistant', 'content': [{'type': 'output_text', 'text': content}], } - ] + ) await event_emitter( { @@ -4398,7 +4422,8 @@ async def streaming_chat_response_handler(response, ctx): or delta.get('reasoning') or delta.get('thinking') ) - if reasoning_content: + reasoning_details = delta.get('reasoning_details') + if reasoning_content or reasoning_details: if not output or output[-1].get('type') != 'reasoning': reasoning_item = { 'type': 'reasoning', @@ -4415,20 +4440,28 @@ async def streaming_chat_response_handler(response, ctx): else: reasoning_item = output[-1] - # Append to reasoning content - parts = reasoning_item.get('content', []) - if parts and parts[-1].get('type') == 'output_text': - parts[-1]['text'] += reasoning_content - else: - reasoning_item['content'] = [ - { - 'type': 'output_text', - 'text': reasoning_content, - } - ] + if reasoning_content: + # Append to reasoning content + parts = reasoning_item.get('content', []) + if parts and parts[-1].get('type') == 'output_text': + parts[-1]['text'] += reasoning_content + else: + reasoning_item['content'] = [ + { + 'type': 'output_text', + 'text': reasoning_content, + } + ] - data = {'content': serialize_output(full_output())} - delta_type = 'content' + data = {'content': serialize_output(full_output())} + delta_type = 'content' + + if reasoning_details: + reasoning_item.setdefault('reasoning_details', []).extend( + reasoning_details + if isinstance(reasoning_details, list) + else [reasoning_details] + ) if value: if ( diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index c5c6528d10..af7c7e0ae9 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -250,10 +250,16 @@ def convert_output_to_messages( pending_tool_calls = [] pending_content = [] pending_reasoning = [] # Only populated when reasoning_format == 'reasoning_content' + pending_reasoning_details = [] def flush_pending(): - nonlocal pending_content, pending_tool_calls, pending_reasoning - if not pending_content and not pending_tool_calls and not pending_reasoning: + nonlocal pending_content, pending_tool_calls, pending_reasoning, pending_reasoning_details + if ( + not pending_content + and not pending_tool_calls + and not pending_reasoning + and not pending_reasoning_details + ): return message = { @@ -265,10 +271,14 @@ def convert_output_to_messages( if pending_reasoning: message['reasoning_content'] = '\n'.join(pending_reasoning) + if pending_reasoning_details: + message['reasoning_details'] = pending_reasoning_details + messages.append(message) pending_content = [] pending_tool_calls = [] pending_reasoning = [] + pending_reasoning_details = [] for item in output: item_type = item.get('type', '') @@ -339,7 +349,8 @@ def convert_output_to_messages( ) elif item_type == 'reasoning': - if not reasoning_format: + reasoning_details = item.get('reasoning_details') if raw else None + if not reasoning_format and not reasoning_details: continue reasoning_text = '' @@ -360,6 +371,11 @@ def convert_output_to_messages( # llama.cpp: collect for reasoning_content field pending_reasoning.append(reasoning_text) + if reasoning_details: + pending_reasoning_details.extend( + reasoning_details if isinstance(reasoning_details, list) else [reasoning_details] + ) + elif item_type == 'open_webui:code_interpreter': # Always include code interpreter content so the LLM knows # the code was already executed and doesn't retry.