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
This commit is contained in:
Classic298 2026-10-14 22:00:19 +02:00 • committed by GitHub
parent 980225e582
commit fb3d92a6ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 17 additions and 0 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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:

View file

@ -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,
)

View file

@ -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', {}),