From fb3d92a6ef56b5a8a6820f615f6e6294c44eaad7 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:00:19 +0200 Subject: [PATCH] feat: record the requested model as its own field in the audit log (#32429) The audit log only showed which model a request used inside the logged request body, which is cut off at MAX_BODY_LOG_SIZE, so the model went missing whenever a long prompt came before it. Audit entries for chat and completion requests, including the OpenAI, Ollama and Anthropic-compatible endpoints, now carry a model_id field with the model the client asked for, at every AUDIT_LOG_LEVEL and however long the request is. Other requests log it as null. Fixes #30097 --- backend/open_webui/main.py | 2 ++ backend/open_webui/routers/ollama.py | 7 +++++++ backend/open_webui/routers/openai.py | 5 +++++ backend/open_webui/utils/audit.py | 2 ++ backend/open_webui/utils/logger.py | 1 + 5 files changed, 17 insertions(+) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 5db490fbf2..c9540eeb9d 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1152,6 +1152,7 @@ async def chat_completion( await get_all_models(request, user=user) model_id = form_data.get('model', None) + request.state.model_id = model_id model_item = form_data.pop('model_item', {}) tasks = form_data.pop('background_tasks', None) @@ -2059,6 +2060,7 @@ async def generate_messages( Anthropic's x-api-key header (via middleware translation). """ requested_model = form_data.get('model', '') + request.state.model_id = requested_model input_tokens = None try: input_tokens = await openai.count_anthropic_tokens(request, form_data, user) diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index fd5bf331af..2d2b0fe397 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -1033,6 +1033,7 @@ async def generate_completion( if not await Config.get('ollama.enable'): raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) + request.state.model_id = form_data.model await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL) await validate_ollama_backend_idx(request, form_data.model, url_idx, user) @@ -1164,6 +1165,8 @@ async def generate_chat_completion( payload.pop('metadata', None) model_id = payload['model'] + if not hasattr(request.state, 'model_id'): + request.state.model_id = model_id model_info = await Models.get_model_by_id(model_id) if model_info is not None: @@ -1266,6 +1269,7 @@ async def generate_openai_completion( payload = form_data.model_dump(exclude_none=True, exclude=['metadata']) model_id = form_data.model + request.state.model_id = model_id model_info = await Models.get_model_by_id(model_id) if model_info is not None: if model_info.base_model_id: @@ -1370,6 +1374,7 @@ async def generate_openai_chat_completion( payload = form_data.model_dump(exclude_none=True, exclude=['metadata']) model_id = form_data.model + request.state.model_id = model_id model_info = await Models.get_model_by_id(model_id) if model_info is not None: if model_info.base_model_id: @@ -1427,6 +1432,7 @@ async def generate_anthropic_messages( payload = {**form_data} model_id = payload.get('model', '') + request.state.model_id = model_id model_info = await Models.get_model_by_id(model_id) if model_info: @@ -1485,6 +1491,7 @@ async def generate_responses( payload = form_data.model_dump() model_id = form_data.model + request.state.model_id = model_id model_info = await Models.get_model_by_id(model_id) if model_info: diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 0f59316524..ee96e53c99 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -1434,6 +1434,9 @@ async def generate_chat_completion( metadata = payload.pop('metadata', None) model_id = form_data.get('model') + # keep the model the client asked for when reached from /api/chat/completions + if not hasattr(request.state, 'model_id'): + request.state.model_id = model_id model_info = await Models.get_model_by_id(model_id) # Check model info and override the payload @@ -1821,6 +1824,7 @@ async def responses( idx = 0 model_id = form_data.model + request.state.model_id = model_id # Enforce per-model access control await check_model_access(user, await Models.get_model_by_id(model_id), BYPASS_MODEL_ACCESS_CONTROL) @@ -1944,6 +1948,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): idx = 0 model_id = payload.get('model') if isinstance(payload, dict) else None + request.state.model_id = model_id if model_id: models = request.app.state.OPENAI_MODELS if not models or model_id not in models: diff --git a/backend/open_webui/utils/audit.py b/backend/open_webui/utils/audit.py index aace99dd54..64a3345844 100644 --- a/backend/open_webui/utils/audit.py +++ b/backend/open_webui/utils/audit.py @@ -43,6 +43,7 @@ class AuditLogEntry: request_uri: str user_agent: Optional[str] = None source_ip: Optional[str] = None + model_id: Optional[str] = None # `Request` audit level properties request_object: Any = None # `Request Response` level @@ -309,6 +310,7 @@ class AuditLoggingMiddleware: response_status_code=context.metadata.get('response_status_code', None), source_ip=request.client.host if request.client else None, user_agent=request.headers.get('user-agent'), + model_id=getattr(request.state, 'model_id', None), request_object=request_body, response_object=response_body, ) diff --git a/backend/open_webui/utils/logger.py b/backend/open_webui/utils/logger.py index f54b7f9840..3ab517f396 100644 --- a/backend/open_webui/utils/logger.py +++ b/backend/open_webui/utils/logger.py @@ -156,6 +156,7 @@ def file_format(record: 'Record'): 'response_status_code': record['extra'].get('response_status_code', 0), 'source_ip': record['extra'].get('source_ip', ''), 'user_agent': record['extra'].get('user_agent', ''), + 'model_id': record['extra'].get('model_id', ''), 'request_object': record['extra'].get('request_object', b''), 'response_object': record['extra'].get('response_object', b''), 'extra': record['extra'].get('extra', {}),