diff --git a/litellm/skills/main.py b/litellm/skills/main.py index a890d4eac88..885d2cb2d76 100644 --- a/litellm/skills/main.py +++ b/litellm/skills/main.py @@ -49,6 +49,7 @@ _NATIVE_SKILL_OPERATIONS: Final = MappingProxyType( "version_content": ("skills.versions.content.retrieve", ("skill_id", "version")), } ) +_NATIVE_ONLY_SKILL_OPERATIONS: Final = frozenset(_NATIVE_SKILL_OPERATIONS) - {"create", "list", "get", "delete"} # Initialize LiteLLM skills handler (lazy - only used when custom_llm_provider="litellm") _litellm_skills_handler = None @@ -70,6 +71,11 @@ def _azure_skills_api_base(api_base: str | None) -> str | None: return str(url.copy_with(path=path[: -len(suffix)] if suffix else path, query=None)).rstrip("/") +def _validate_skill_operation(operation: str, custom_llm_provider: str) -> None: + if operation in _NATIVE_ONLY_SKILL_OPERATIONS and custom_llm_provider not in _NATIVE_SKILL_PROVIDERS: + raise ValueError(f"{operation} skills operation is only supported for OpenAI and Azure OpenAI") + + def _native_skill_request( operation: str, request_data: dict[str, Any], # mutable-ok: logging and SDK dispatch consume request data @@ -274,6 +280,7 @@ def create_skill( # Determine provider if custom_llm_provider is None: custom_llm_provider = "anthropic" + _validate_skill_operation(kwargs.get("_skill_operation", "create"), custom_llm_provider) # Build create request create_request: Final[CreateSkillRequest] = {} @@ -474,6 +481,7 @@ def list_skills( # Determine provider if custom_llm_provider is None: custom_llm_provider = "anthropic" + _validate_skill_operation(kwargs.get("_skill_operation", "list"), custom_llm_provider) # Route to LiteLLM DB if custom_llm_provider="litellm_proxy" if custom_llm_provider == LlmProviders.LITELLM_PROXY.value: @@ -658,6 +666,7 @@ def get_skill( # Determine provider if custom_llm_provider is None: custom_llm_provider = "anthropic" + _validate_skill_operation(kwargs.get("_skill_operation", "get"), custom_llm_provider) # Route to LiteLLM DB if custom_llm_provider="litellm_proxy" if custom_llm_provider == LlmProviders.LITELLM_PROXY.value: @@ -833,6 +842,7 @@ def delete_skill( # Determine provider if custom_llm_provider is None: custom_llm_provider = "anthropic" + _validate_skill_operation(kwargs.get("_skill_operation", "delete"), custom_llm_provider) # Route to LiteLLM DB if custom_llm_provider="litellm_proxy" if custom_llm_provider == LlmProviders.LITELLM_PROXY.value: diff --git a/tests/test_litellm/skills/test_native_skills.py b/tests/test_litellm/skills/test_native_skills.py index 6ea1635d2bd..61602822c3e 100644 --- a/tests/test_litellm/skills/test_native_skills.py +++ b/tests/test_litellm/skills/test_native_skills.py @@ -23,7 +23,11 @@ from litellm.proxy.anthropic_endpoints.skills_endpoints import ( ) from litellm.proxy.openai_files_endpoints.common_utils import extract_model_param from litellm.router import Router -from litellm.skills.main import _azure_skills_api_base, _native_skill_request +from litellm.skills.main import ( + _azure_skills_api_base, + _native_skill_request, + _validate_skill_operation, +) from litellm.types.router import GenericLiteLLMParams SKILL = { @@ -448,6 +452,15 @@ def test_native_skill_request_rejects_uninitialized_openai_client() -> None: ) +@pytest.mark.parametrize( + "operation", + ["update", "content", "create_version", "list_versions", "version", "delete_version", "version_content"], +) +def test_native_only_skill_operations_reject_non_native_providers(operation: str) -> None: + with pytest.raises(ValueError, match="only supported for OpenAI and Azure OpenAI"): + _validate_skill_operation(operation, "anthropic") + + def test_extract_model_param_ignores_non_string_body_model() -> None: request = Request( {