From 49430de42d42ae1a6dd85f2aab24ced23a2e4c60 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Apr 2026 15:00:17 +0900 Subject: [PATCH] refac --- backend/open_webui/utils/middleware.py | 47 ++++++++++++-------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 19e23cf73..6db969261 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -455,7 +455,7 @@ def serialize_output(output: list) -> str: Convert OR-aligned output items to HTML for display. For LLM consumption, use convert_output_to_messages() instead. """ - content = '' + parts: list[str] = [] # First pass: collect function_call_output items by call_id for lookup tool_outputs = {} @@ -472,53 +472,48 @@ def serialize_output(output: list) -> str: if 'text' in content_part: text = content_part.get('text', '').strip() if text: - content = f'{content}{text}\n' + parts.append(text) elif item_type == 'function_call': - # Render tool call inline with its result (if available) - if content and not content.endswith('\n'): - content += '\n' - call_id = item.get('call_id', '') name = item.get('name', '') arguments = item.get('arguments', '') result_item = tool_outputs.get(call_id) if result_item: - result_text = '' + result_parts: list[str] = [] for result_output in result_item.get('output', []): if 'text' in result_output: output_text = result_output.get('text', '') - result_text += str(output_text) if not isinstance(output_text, str) else output_text + result_parts.append(str(output_text) if not isinstance(output_text, str) else output_text) + result_text = ''.join(result_parts) files = result_item.get('files') embeds = result_item.get('embeds', '') - content += f'
\nTool Executed\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n
\n' + parts.append(f'
\nTool Executed\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n
') else: - content += f'
\nExecuting...\n
\n' + parts.append(f'
\nExecuting...\n
') elif item_type == 'function_call_output': # Already handled inline with function_call above pass elif item_type in _OPENAI_TOOL_DISPLAY_NAMES: - if content and not content.endswith('\n'): - content += '\n' status = item.get('status', 'in_progress') done = status in ('completed', 'failed', 'incomplete') or idx != len(output) - 1 - content += _render_openai_tool_call_handler(item, done) + parts.append(_render_openai_tool_call_handler(item, done).rstrip('\n')) elif item_type == 'reasoning': - reasoning_content = '' + reasoning_parts: list[str] = [] # Check for 'summary' (new structure) or 'content' (legacy/fallback) source_list = item.get('summary', []) or item.get('content', []) for content_part in source_list: if 'text' in content_part: - reasoning_content += content_part.get('text', '') + reasoning_parts.append(content_part.get('text', '')) elif 'summary' in content_part: # Handle potential nested logic if any pass - reasoning_content = reasoning_content.strip() + reasoning_content = ''.join(reasoning_parts).strip() duration = item.get('duration') status = item.get('status', 'in_progress') @@ -527,9 +522,6 @@ def serialize_output(output: list) -> str: # render as done (a subsequent item means reasoning is complete) is_last_item = idx == len(output) - 1 - if content and not content.endswith('\n'): - content += '\n' - display = html.escape( '\n'.join( (f'> {line}' if not line.startswith('>') else line) for line in reasoning_content.splitlines() @@ -537,19 +529,22 @@ def serialize_output(output: list) -> str: ) if status == 'completed' or duration is not None or not is_last_item: - content = f'{content}
\nThought for {duration or 0} seconds\n{display}\n
\n' + parts.append(f'
\nThought for {duration or 0} seconds\n{display}\n
') else: - content = f'{content}
\nThinking…\n{display}\n
\n' + parts.append(f'
\nThinking…\n{display}\n
') elif item_type == 'open_webui:code_interpreter': + # Code interpreter needs to inspect/mutate prior accumulated content + # to strip trailing unclosed code fences — materialize only here. + content = '\n'.join(parts) content_stripped, original_whitespace = split_content_and_whitespace(content) if is_opening_code_block(content_stripped): content = content_stripped.rstrip('`').rstrip() + original_whitespace else: content = content_stripped + original_whitespace - if content and not content.endswith('\n'): - content += '\n' + # Re-split back into parts list after mutation + parts = [content] if content else [] # Render the code_interpreter item as a
block # so the frontend Collapsible renders "Analyzing..."/"Analyzed". @@ -575,11 +570,11 @@ def serialize_output(output: list) -> str: output_attr = f' output="{html.escape(output_json)}"' if status == 'completed' or duration is not None or not is_last_item: - content += f'
\nAnalyzed\n{display}\n
\n' + parts.append(f'
\nAnalyzed\n{display}\n
') else: - content += f'
\nAnalyzing…\n{display}\n
\n' + parts.append(f'
\nAnalyzing…\n{display}\n
') - return content.strip() + return '\n'.join(parts).strip() def deep_merge(target, source):