From 478d1785fd27f400c614a5700f5b38ec9de412f9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 21 Sep 2026 11:21:42 -0400 Subject: [PATCH] refac Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com> --- backend/open_webui/socket/main.py | 3 +- backend/open_webui/utils/middleware.py | 75 ++++++++++++++++---------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index 29af592458..2e0969ab2e 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -1083,7 +1083,8 @@ async def _make_channel_emitter(request_info): state['output'] = copy.deepcopy(output) now = time.time() - if done or (now - state['last_emit_at']) >= THROTTLE_INTERVAL: + # Tool boundaries must publish all results before waiting on the next model response. + if done or data.get('flush') or (now - state['last_emit_at']) >= THROTTLE_INTERVAL: state['last_emit_at'] = now await _emit_channel_update(content, done, output if isinstance(output, list) else None) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index ad784658c8..884d13656b 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -4974,6 +4974,14 @@ async def streaming_chat_response_handler(response, ctx): **response_data, 'output_index': response_data['output_index'] + len(prior_output), } + if prior_output and isinstance(response_data.get('response'), dict): + # response.output is this round only; the response.completed reducer drops earlier rounds + return { + **response_data, + 'response': { + key: value for key, value in response_data['response'].items() if key != 'output' + }, + } return response_data async def flush_pending_delta_data(threshold: int = 0): @@ -5808,6 +5816,7 @@ async def streaming_chat_response_handler(response, ctx): if responses_api_tool_calls: tool_calls.append(_split_tool_calls(responses_api_tool_calls)) + output_start = len(prior_output) try: await stream_body_handler(response, form_data) finally: @@ -5839,6 +5848,42 @@ async def streaming_chat_response_handler(response, ctx): get_content_from_message(original_system_message) if original_system_message else None ) + async def emit_output(): + # Channels publish whole messages; Continue can merge into the preceding item. + snapshot = continuing or (metadata.get('chat_id') or '').startswith('channel:') + frontend_output = [] + for item in full_output() if snapshot else full_output()[output_start:]: + if item.get('type') == 'function_call_output': + # input_image parts are base64 data URIs only for the LLM, via convert_output_to_messages + item = { + **item, + 'output': [ + part for part in item.get('output', []) if part.get('type') != 'input_image' + ], + } + frontend_output.append(item) + + if snapshot: + await event_emitter( + { + 'type': 'chat:completion', + 'data': {'output': frontend_output, 'flush': True}, + } + ) + return + + for output_index, item in enumerate(frontend_output, start=output_start): + await event_emitter( + { + 'type': 'response:completion', + 'data': { + 'type': 'response.output_item.done', + 'output_index': output_index, + 'item': item, + }, + } + ) + while tool_calls and ( max_tool_call_iterations is None or tool_call_iterations < max_tool_call_iterations ): @@ -5906,14 +5951,7 @@ async def streaming_chat_response_handler(response, ctx): ) return - await event_emitter( - { - 'type': 'chat:completion', - 'data': { - 'output': full_output(), - }, - } - ) + await emit_output() tools = metadata.get('tools', {}) @@ -6180,25 +6218,7 @@ async def streaming_chat_response_handler(response, ctx): ) tool_call_sources.clear() - # Strip input_image parts (large base64 data URIs) from the - # output sent to the frontend — they're only for LLM consumption - # via convert_output_to_messages. - frontend_output = [] - for item in full_output(): - if item.get('type') == 'function_call_output': - parts = item.get('output', []) - if any(p.get('type') == 'input_image' for p in parts): - item = {**item, 'output': [p for p in parts if p.get('type') != 'input_image']} - frontend_output.append(item) - - await event_emitter( - { - 'type': 'chat:completion', - 'data': { - 'output': frontend_output, - }, - } - ) + await emit_output() try: new_form_data = { @@ -6297,6 +6317,7 @@ async def streaming_chat_response_handler(response, ctx): if not msg_parts or (len(msg_parts) == 1 and not msg_parts[0].get('text', '').strip()): prior_output.pop() output = [] + output_start = len(prior_output) await stream_body_handler(res, new_form_data) output = full_output() prior_output = []