diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 6cbf0155cb..a629011362 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -267,7 +267,7 @@ from open_webui.utils.oauth import ( from open_webui.utils.plugin import install_tool_and_function_dependencies from open_webui.utils.redis import get_redis_client from open_webui.utils.security_headers import SecurityHeadersMiddleware -from open_webui.utils.session_pool import cleanup_response, get_session, stream_wrapper +from open_webui.utils.session_pool import cleanup_response, get_client_timeout, get_session, stream_wrapper from open_webui.utils.tool_approval import ( ResolveToolCallForm, build_tool_approval_resume_payload, @@ -1908,7 +1908,7 @@ async def count_message_tokens( async def passthrough_anthropic_messages(request: Request, form_data: dict, user) -> Response | dict: - requested_model, payload, url, key, headers, cookies = await openai.get_anthropic_token_count_target( + requested_model, payload, url, key, headers, cookies = await openai.get_anthropic_request_target( request, form_data, user ) request_url = f'{url.rstrip("/")}/messages' @@ -1924,7 +1924,7 @@ async def passthrough_anthropic_messages(request: Request, form_data: dict, user headers=headers, cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, - timeout=aiohttp.ClientTimeout(total=openai.AIOHTTP_CLIENT_TIMEOUT), + timeout=get_client_timeout(stream=bool(payload.get('stream'))), ) if 'text/event-stream' in response.headers.get('Content-Type', ''): diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 452fabf4e5..5988006059 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -39,7 +39,7 @@ from open_webui.models.groups import Groups from open_webui.models.models import Models from open_webui.models.users import UserModel from open_webui.utils.access_control import check_model_access, has_connection_access, has_permission -from open_webui.utils.anthropic import get_anthropic_models, is_anthropic_url +from open_webui.utils.anthropic import ANTHROPIC_VERSION, get_anthropic_models, is_anthropic_url from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.headers import get_custom_headers, include_user_info_headers from open_webui.utils.json_codec import JSONCodec @@ -451,8 +451,8 @@ async def send_model_management_request( await cleanup_response(response) -async def get_anthropic_token_count_target(request: Request, form_data: dict, user: UserModel): - """Resolve the upstream LiteLLM connection for an Anthropic token-count request.""" +async def get_anthropic_request_target(request: Request, form_data: dict, user: UserModel): + """Resolve the upstream connection, payload and auth headers for a native Anthropic request.""" requested_model = form_data.get('model') if not requested_model: raise HTTPException(status_code=400, detail='model is required') @@ -480,14 +480,20 @@ async def get_anthropic_token_count_target(request: Request, form_data: dict, us payload['model'] = strip_provider_model_prefix(payload['model'], prefix_id) headers, cookies = await get_headers_and_cookies(request, url, key, api_config, user=user) + + # Anthropic's native endpoints reject bearer auth, the key belongs in x-api-key. + if is_anthropic_url(url): + headers.setdefault('anthropic-version', ANTHROPIC_VERSION) + if api_config.get('auth_type') in (None, 'bearer'): + headers.pop('Authorization', None) + headers.setdefault('x-api-key', key) + return requested_model, payload, url, key, headers, cookies async def count_anthropic_tokens(request: Request, form_data: dict, user: UserModel) -> int: """Forward an Anthropic token-count request through an OpenAI-compatible connection.""" - requested_model, payload, url, key, headers, cookies = await get_anthropic_token_count_target( - request, form_data, user - ) + requested_model, payload, url, key, headers, cookies = await get_anthropic_request_target(request, form_data, user) request_url = f'{url.rstrip("/")}/messages/count_tokens' response = None diff --git a/backend/open_webui/utils/anthropic.py b/backend/open_webui/utils/anthropic.py index 36fe988282..3d49643cec 100644 --- a/backend/open_webui/utils/anthropic.py +++ b/backend/open_webui/utils/anthropic.py @@ -12,6 +12,8 @@ from open_webui.utils.json_codec import JSONCodec log = logging.getLogger(__name__) +ANTHROPIC_VERSION = '2023-06-01' + ANTHROPIC_CONVERTED_REQUEST_PARAMS = { 'model', 'messages', @@ -48,7 +50,7 @@ async def get_anthropic_models(url: str, key: str, user: UserModel = None) -> di async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session: headers = { 'x-api-key': key, - 'anthropic-version': '2023-06-01', + 'anthropic-version': ANTHROPIC_VERSION, } if ENABLE_FORWARD_USER_INFO_HEADERS and user: