diff --git a/backend/open_webui/utils/chat.py b/backend/open_webui/utils/chat.py index fcd8151022..5ce6fffec6 100644 --- a/backend/open_webui/utils/chat.py +++ b/backend/open_webui/utils/chat.py @@ -203,27 +203,41 @@ async def generate_chat_completion( except Exception as e: raise e - if model.get('owned_by') == 'arena': + # Arena model — sub-model was already resolved by process_chat_payload. + # Inject selected_model_id into the response for the frontend. + metadata = form_data.get('metadata', {}) + selected_model_id = metadata.pop('selected_model_id', None) + # Also clear from request.state.metadata to prevent the merge at + # lines 177-179 from re-adding it on the recursive call. + if hasattr(request.state, 'metadata'): + request.state.metadata.pop('selected_model_id', None) + + # Fallback: if generate_chat_completion is called with an arena model + # from a path that did NOT go through process_chat_payload (e.g., + # background tasks for title/follow-up/tags generation), resolve now. + if not selected_model_id and model.get('owned_by') == 'arena': model_ids = model.get('info', {}).get('meta', {}).get('model_ids') filter_mode = model.get('info', {}).get('meta', {}).get('filter_mode') if model_ids and filter_mode == 'exclude': model_ids = [ - model['id'] - for model in list(request.app.state.MODELS.values()) - if model.get('owned_by') != 'arena' and model['id'] not in model_ids + available_model['id'] + for available_model in list(request.app.state.MODELS.values()) + if available_model.get('owned_by') != 'arena' and available_model['id'] not in model_ids ] - selected_model_id = None if isinstance(model_ids, list) and model_ids: selected_model_id = random.choice(model_ids) else: model_ids = [ - model['id'] for model in list(request.app.state.MODELS.values()) if model.get('owned_by') != 'arena' + available_model['id'] + for available_model in list(request.app.state.MODELS.values()) + if available_model.get('owned_by') != 'arena' ] selected_model_id = random.choice(model_ids) form_data['model'] = selected_model_id + if selected_model_id: if form_data.get('stream') == True: async def stream_wrapper(stream): diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 8379233c53..902c58a75d 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2102,6 +2102,35 @@ async def process_chat_payload(request, form_data, user, metadata, model): # -> Chat Code Interpreter (Form Data Update) -> (Default) Chat Tools Function Calling # -> Chat Files + # Arena model resolution — pick the sub-model now so all downstream + # processing (knowledge, capabilities, tools, params) uses its settings + # instead of the empty arena wrapper. + if model.get('owned_by') == 'arena': + arena_model_ids = model.get('info', {}).get('meta', {}).get('model_ids') + arena_filter_mode = model.get('info', {}).get('meta', {}).get('filter_mode') + if arena_model_ids and arena_filter_mode == 'exclude': + arena_model_ids = [ + available_model['id'] + for available_model in request.app.state.MODELS.values() + if available_model.get('owned_by') != 'arena' and available_model['id'] not in arena_model_ids + ] + + if isinstance(arena_model_ids, list) and arena_model_ids: + selected_model_id = random.choice(arena_model_ids) + else: + arena_model_ids = [ + available_model['id'] + for available_model in request.app.state.MODELS.values() + if available_model.get('owned_by') != 'arena' + ] + selected_model_id = random.choice(arena_model_ids) + + selected_model = request.app.state.MODELS.get(selected_model_id) + if selected_model: + model = selected_model + form_data['model'] = selected_model_id + metadata['selected_model_id'] = selected_model_id + form_data = apply_params_to_form_data(form_data, model) log.debug(f'form_data: {form_data}')