From 5c426208b1f7ef4b5097fdd80b273b9481075b98 Mon Sep 17 00:00:00 2001 From: ymuichiro Date: Sun, 23 Aug 2026 14:23:57 +0900 Subject: [PATCH] fix(proxy): correct native Skills routing edge cases --- .../openai_files_endpoints/common_utils.py | 2 +- litellm/skills/main.py | 6 +----- .../test_litellm/skills/test_native_skills.py | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/openai_files_endpoints/common_utils.py b/litellm/proxy/openai_files_endpoints/common_utils.py index 4583aed6a1c..45e34bc4e6d 100644 --- a/litellm/proxy/openai_files_endpoints/common_utils.py +++ b/litellm/proxy/openai_files_endpoints/common_utils.py @@ -1038,7 +1038,7 @@ def extract_model_param(request: "Request", request_body: Mapping[str, object]) body_model: Final = request_body.get("model") return ( body_model - if isinstance(body_model, str) + if isinstance(body_model, str) and body_model else request.query_params.get("model") or request.headers.get("x-litellm-model") ) diff --git a/litellm/skills/main.py b/litellm/skills/main.py index ef6f8176856..a890d4eac88 100644 --- a/litellm/skills/main.py +++ b/litellm/skills/main.py @@ -85,11 +85,7 @@ def _native_skill_request( method_path, request_fields = _NATIVE_SKILL_OPERATIONS[operation] extra_headers: Final = request_data.get("extra_headers") - headers: Final = ( - {**(extra_headers or {}), "Foundry-Features": "Skills=V1Preview"} # mutable-ok: SDK headers - if custom_llm_provider == "azure" - else extra_headers - ) + headers: Final = extra_headers params: Final = { # mutable-ok: SDK request parameters field: value for field, value in ( diff --git a/tests/test_litellm/skills/test_native_skills.py b/tests/test_litellm/skills/test_native_skills.py index 461732c5118..6ea1635d2bd 100644 --- a/tests/test_litellm/skills/test_native_skills.py +++ b/tests/test_litellm/skills/test_native_skills.py @@ -218,7 +218,7 @@ async def test_openai_sdk_handles_every_native_skill_operation() -> None: @pytest.mark.asyncio -async def test_azure_uses_preview_header_with_existing_sdk_client() -> None: +async def test_azure_does_not_use_foundry_preview_header_with_existing_sdk_client() -> None: requests: list[httpx.Request] = [] def handler(request: httpx.Request) -> httpx.Response: @@ -236,7 +236,7 @@ async def test_azure_uses_preview_header_with_existing_sdk_client() -> None: await GLOBAL_LOGGING_WORKER.flush() await client.close() - assert requests[0].headers["foundry-features"] == "Skills=V1Preview" + assert "foundry-features" not in requests[0].headers assert requests[0].headers["x-test-header"] == "present" @@ -283,6 +283,7 @@ async def test_router_model_configuration_overrides_request_provider() -> None: ("body_model", "query", "header_model", "expected"), [ ("body", "model=query", "header", "body"), + ("", "model=query", "header", "query"), (None, "model=query", "header", "query"), (None, "", "header", "header"), (None, "", None, None), @@ -461,6 +462,20 @@ def test_extract_model_param_ignores_non_string_body_model() -> None: assert extract_model_param(request, {"model": {"unexpected": "type"}}) is None +def test_extract_model_param_falls_back_from_empty_body_model() -> None: + request = Request( + { + "type": "http", + "method": "POST", + "path": "/v1/skills/skill_1", + "headers": [(b"x-litellm-model", b"header-model")], + "query_string": b"model=query-model", + } + ) + + assert extract_model_param(request, {"model": ""}) == "query-model" + + @pytest.mark.parametrize( ("api_base", "expected"), [