diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 594149d343..bc9d2a4cb5 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1168,6 +1168,7 @@ async def chat_completion( metadata = { 'user_id': user.id, + 'user_agent': request.headers.get('user-agent', '') or '', 'chat_id': form_data.pop('chat_id', None) or '', 'user_message': user_message, 'user_message_id': user_message.get('id') if user_message else None, diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index da3060ae7b..7fc75996ae 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -38,7 +38,7 @@ from open_webui.models.models import Models from open_webui.models.users import UserModel from open_webui.utils.access_control import check_model_access from open_webui.utils.auth import get_admin_user, get_verified_user -from open_webui.utils.headers import include_user_info_headers +from open_webui.utils.headers import get_custom_headers, include_user_info_headers from open_webui.utils.misc import calculate_sha256 from open_webui.utils.payload import ( apply_model_params_to_body_ollama, @@ -99,6 +99,8 @@ async def send_request( stream: bool = False, content_type: str | None = None, metadata: dict | None = None, + api_config: dict | None = None, + request: Request | None = None, ): r = None streaming = False @@ -115,6 +117,10 @@ async def send_request( if metadata and metadata.get('chat_id'): headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get('chat_id') + # Custom per-connection headers last so admin-set headers take precedence. + if api_config and api_config.get('headers'): + headers.update(get_custom_headers(api_config['headers'], user, metadata, request=request)) + r = await session.request( method, url, @@ -1100,6 +1106,8 @@ async def generate_chat_completion( stream=form_data.stream, content_type='application/x-ndjson', metadata=metadata, + api_config=api_config, + request=request, ) @@ -1185,6 +1193,8 @@ async def generate_openai_completion( user=user, stream=payload.get('stream', False), metadata=metadata, + api_config=api_config, + request=request, ) @@ -1242,6 +1252,8 @@ async def generate_openai_chat_completion( user=user, stream=payload.get('stream', False), metadata=metadata, + api_config=api_config, + request=request, ) @@ -1294,6 +1306,8 @@ async def generate_anthropic_messages( user=user, stream=payload.get('stream', False), content_type='text/event-stream' if payload.get('stream', False) else None, + api_config=api_config, + request=request, ) @@ -1352,6 +1366,8 @@ async def generate_responses( user=user, stream=payload.get('stream', False), content_type='text/event-stream' if payload.get('stream', False) else None, + api_config=api_config, + request=request, ) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index a30e24d8e4..bcf8718e96 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -209,7 +209,7 @@ async def get_headers_and_cookies( headers['Authorization'] = f'Bearer {token}' if config.get('headers') and isinstance(config.get('headers'), dict): - custom_headers = get_custom_headers(config.get('headers'), user, metadata) + custom_headers = get_custom_headers(config.get('headers'), user, metadata, request=request) headers.update(custom_headers) return headers, cookies diff --git a/backend/open_webui/utils/headers.py b/backend/open_webui/utils/headers.py index 5dda0e451f..f7b7297083 100644 --- a/backend/open_webui/utils/headers.py +++ b/backend/open_webui/utils/headers.py @@ -59,12 +59,22 @@ def include_user_info_headers(headers: dict, user: Optional[Any] = None) -> dict } -def get_custom_headers(custom_headers: dict, user=None, metadata: dict = None) -> dict: +def get_custom_headers(custom_headers: dict, user=None, metadata: dict = None, request=None) -> dict: if not custom_headers or not isinstance(custom_headers, dict): return {} metadata = metadata or {} + # UA from the live request; fall back to metadata for detached RAG/tool calls. + user_agent = '' + if request is not None: + try: + user_agent = request.headers.get('user-agent', '') or '' + except Exception: + user_agent = '' + if not user_agent: + user_agent = metadata.get('user_agent', '') or '' + # Extract user_message info for tree mapping user_message = metadata.get('user_message') or {} user_message_id = metadata.get('user_message_id', '') or (user_message.get('id', '') if user_message else '') @@ -83,6 +93,7 @@ def get_custom_headers(custom_headers: dict, user=None, metadata: dict = None) - '{{USER_NAME}}': (user.name.strip() if user else '') or '', '{{USER_EMAIL}}': (user.email.strip() if user else '') or '', '{{USER_ROLE}}': (user.role if user else '') or '', + '{{USER_AGENT}}': user_agent, } parsed_headers = {} diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index 37f4a7eb68..26b1190465 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -436,7 +436,7 @@ - {#if !ollama && !direct} + {#if !direct}