Skip to content

Commit 4e195d6

Browse files
[Feat] New API - Claude Skills API (Anthropic) (#17042)
* init readme * init BaseSkillsAPIConfig * init types for Skills APIs * add feat: add create, list, retrieve skills * add base skills config * add BaseSkillsAPIConfig * add get_provider_skills_api_config * init skills * add ANTHROPIC_SKILLS_API_BETA_VERSION * init skills APIs * working list, get skills * working e2e skills API anthropic API * add _prepare_skill_multipart_request * add skills routes to llm api routes * router _initialize_skills_endpoints * add fix skills endpoints * add convert_upload_files_to_file_data * fix routing skills endpoints * fix route llm request * Potential fix for code scanning alert no. 3806: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 3809: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix ruff checks * test_initialize_skills_endpoints * fix claude skills mypy linting errors --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent a807fe4 commit 4e195d6

File tree

25 files changed

+2821
-16
lines changed

25 files changed

+2821
-16
lines changed

litellm/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,8 @@ def add_known_models():
12711271
OpenAIOSeriesConfig as OpenAIO1Config, # maintain backwards compatibility
12721272
OpenAIOSeriesConfig,
12731273
)
1274+
from .llms.anthropic.skills.transformation import AnthropicSkillsConfig
1275+
from .llms.base_llm.skills.transformation import BaseSkillsAPIConfig
12741276

12751277
from .llms.gradient_ai.chat.transformation import GradientAIConfig
12761278

@@ -1367,6 +1369,18 @@ def add_known_models():
13671369
from .llms.lemonade.chat.transformation import LemonadeChatConfig
13681370
from .llms.snowflake.embedding.transformation import SnowflakeEmbeddingConfig
13691371
from .main import * # type: ignore
1372+
1373+
# Skills API
1374+
from .skills.main import (
1375+
create_skill,
1376+
acreate_skill,
1377+
list_skills,
1378+
alist_skills,
1379+
get_skill,
1380+
aget_skill,
1381+
delete_skill,
1382+
adelete_skill,
1383+
)
13701384
from .integrations import *
13711385
from .llms.custom_httpx.async_client_cleanup import close_litellm_async_clients
13721386
from .exceptions import (
@@ -1404,6 +1418,16 @@ def add_known_models():
14041418
from .rerank_api.main import *
14051419
from .llms.anthropic.experimental_pass_through.messages.handler import *
14061420
from .responses.main import *
1421+
from .skills.main import (
1422+
create_skill,
1423+
acreate_skill,
1424+
list_skills,
1425+
alist_skills,
1426+
get_skill,
1427+
aget_skill,
1428+
delete_skill,
1429+
adelete_skill,
1430+
)
14071431
from .containers.main import *
14081432
from .ocr.main import *
14091433
from .search.main import *

litellm/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@
291291

292292
############### LLM Provider Constants ###############
293293
### ANTHROPIC CONSTANTS ###
294+
ANTHROPIC_SKILLS_API_BETA_VERSION = "skills-2025-10-02"
294295
ANTHROPIC_WEB_SEARCH_TOOL_MAX_USES = {
295296
"low": 1,
296297
"medium": 5,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Anthropic Skills API integration"""
2+
3+
from .transformation import AnthropicSkillsConfig
4+
5+
__all__ = ["AnthropicSkillsConfig"]
6+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Anthropic Skills API
2+
3+
This folder maintains the integration for the Anthropic Skills API.
4+
5+
You can do the following with the Anthropic Skills API:
6+
7+
1. Create a new skill
8+
2. List all skills
9+
3. Get a skill
10+
4. Delete a skill
11+
12+
13+
Versions:
14+
- Create Skill Version
15+
- List Skill Versions
16+
- Get Skill Version
17+
- Delete Skill Version
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
"""
2+
Anthropic Skills API configuration and transformations
3+
"""
4+
5+
from typing import Any, Dict, Optional, Tuple
6+
7+
import httpx
8+
9+
from litellm._logging import verbose_logger
10+
from litellm.llms.base_llm.skills.transformation import (
11+
BaseSkillsAPIConfig,
12+
LiteLLMLoggingObj,
13+
)
14+
from litellm.types.llms.anthropic_skills import (
15+
CreateSkillRequest,
16+
DeleteSkillResponse,
17+
ListSkillsParams,
18+
ListSkillsResponse,
19+
Skill,
20+
)
21+
from litellm.types.router import GenericLiteLLMParams
22+
from litellm.types.utils import LlmProviders
23+
24+
25+
class AnthropicSkillsConfig(BaseSkillsAPIConfig):
26+
"""Anthropic-specific Skills API configuration"""
27+
28+
@property
29+
def custom_llm_provider(self) -> LlmProviders:
30+
return LlmProviders.ANTHROPIC
31+
32+
def validate_environment(
33+
self, headers: dict, litellm_params: Optional[GenericLiteLLMParams]
34+
) -> dict:
35+
"""Add Anthropic-specific headers"""
36+
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
37+
38+
# Get API key
39+
api_key = None
40+
if litellm_params:
41+
api_key = litellm_params.api_key
42+
api_key = AnthropicModelInfo.get_api_key(api_key)
43+
44+
if not api_key:
45+
raise ValueError("ANTHROPIC_API_KEY is required for Skills API")
46+
47+
# Add required headers
48+
headers["x-api-key"] = api_key
49+
headers["anthropic-version"] = "2023-06-01"
50+
51+
# Add beta header for skills API
52+
from litellm.constants import ANTHROPIC_SKILLS_API_BETA_VERSION
53+
54+
if "anthropic-beta" not in headers:
55+
headers["anthropic-beta"] = ANTHROPIC_SKILLS_API_BETA_VERSION
56+
elif isinstance(headers["anthropic-beta"], list):
57+
if ANTHROPIC_SKILLS_API_BETA_VERSION not in headers["anthropic-beta"]:
58+
headers["anthropic-beta"].append(ANTHROPIC_SKILLS_API_BETA_VERSION)
59+
elif isinstance(headers["anthropic-beta"], str):
60+
if ANTHROPIC_SKILLS_API_BETA_VERSION not in headers["anthropic-beta"]:
61+
headers["anthropic-beta"] = [headers["anthropic-beta"], ANTHROPIC_SKILLS_API_BETA_VERSION]
62+
63+
headers["content-type"] = "application/json"
64+
65+
return headers
66+
67+
def get_complete_url(
68+
self,
69+
api_base: Optional[str],
70+
endpoint: str,
71+
skill_id: Optional[str] = None,
72+
) -> str:
73+
"""Get complete URL for Anthropic Skills API"""
74+
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
75+
76+
if api_base is None:
77+
api_base = AnthropicModelInfo.get_api_base()
78+
79+
if skill_id:
80+
return f"{api_base}/v1/skills/{skill_id}?beta=true"
81+
return f"{api_base}/v1/{endpoint}?beta=true"
82+
83+
def transform_create_skill_request(
84+
self,
85+
create_request: CreateSkillRequest,
86+
litellm_params: GenericLiteLLMParams,
87+
headers: dict,
88+
) -> Dict:
89+
"""Transform create skill request for Anthropic"""
90+
verbose_logger.debug(
91+
"Transforming create skill request: %s", create_request
92+
)
93+
94+
# Anthropic expects the request body directly
95+
request_body = {k: v for k, v in create_request.items() if v is not None}
96+
97+
return request_body
98+
99+
def transform_create_skill_response(
100+
self,
101+
raw_response: httpx.Response,
102+
logging_obj: LiteLLMLoggingObj,
103+
) -> Skill:
104+
"""Transform Anthropic response to Skill object"""
105+
response_json = raw_response.json()
106+
verbose_logger.debug(
107+
"Transforming create skill response: %s", response_json
108+
)
109+
110+
return Skill(**response_json)
111+
112+
def transform_list_skills_request(
113+
self,
114+
list_params: ListSkillsParams,
115+
litellm_params: GenericLiteLLMParams,
116+
headers: dict,
117+
) -> Tuple[str, Dict]:
118+
"""Transform list skills request for Anthropic"""
119+
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
120+
121+
api_base = AnthropicModelInfo.get_api_base(
122+
litellm_params.api_base if litellm_params else None
123+
)
124+
url = self.get_complete_url(api_base=api_base, endpoint="skills")
125+
126+
# Build query parameters
127+
query_params: Dict[str, Any] = {}
128+
if "limit" in list_params and list_params["limit"]:
129+
query_params["limit"] = list_params["limit"]
130+
if "page" in list_params and list_params["page"]:
131+
query_params["page"] = list_params["page"]
132+
if "source" in list_params and list_params["source"]:
133+
query_params["source"] = list_params["source"]
134+
135+
verbose_logger.debug(
136+
"List skills request made to Anthropic Skills endpoint with params: %s", query_params
137+
)
138+
139+
return url, query_params
140+
141+
def transform_list_skills_response(
142+
self,
143+
raw_response: httpx.Response,
144+
logging_obj: LiteLLMLoggingObj,
145+
) -> ListSkillsResponse:
146+
"""Transform Anthropic response to ListSkillsResponse"""
147+
response_json = raw_response.json()
148+
verbose_logger.debug(
149+
"Transforming list skills response: %s", response_json
150+
)
151+
152+
return ListSkillsResponse(**response_json)
153+
154+
def transform_get_skill_request(
155+
self,
156+
skill_id: str,
157+
api_base: str,
158+
litellm_params: GenericLiteLLMParams,
159+
headers: dict,
160+
) -> Tuple[str, Dict]:
161+
"""Transform get skill request for Anthropic"""
162+
url = self.get_complete_url(
163+
api_base=api_base, endpoint="skills", skill_id=skill_id
164+
)
165+
166+
verbose_logger.debug("Get skill request - URL: %s", url)
167+
168+
return url, headers
169+
170+
def transform_get_skill_response(
171+
self,
172+
raw_response: httpx.Response,
173+
logging_obj: LiteLLMLoggingObj,
174+
) -> Skill:
175+
"""Transform Anthropic response to Skill object"""
176+
response_json = raw_response.json()
177+
verbose_logger.debug(
178+
"Transforming get skill response: %s", response_json
179+
)
180+
181+
return Skill(**response_json)
182+
183+
def transform_delete_skill_request(
184+
self,
185+
skill_id: str,
186+
api_base: str,
187+
litellm_params: GenericLiteLLMParams,
188+
headers: dict,
189+
) -> Tuple[str, Dict]:
190+
"""Transform delete skill request for Anthropic"""
191+
url = self.get_complete_url(
192+
api_base=api_base, endpoint="skills", skill_id=skill_id
193+
)
194+
195+
verbose_logger.debug("Delete skill request - URL: %s", url)
196+
197+
return url, headers
198+
199+
def transform_delete_skill_response(
200+
self,
201+
raw_response: httpx.Response,
202+
logging_obj: LiteLLMLoggingObj,
203+
) -> DeleteSkillResponse:
204+
"""Transform Anthropic response to DeleteSkillResponse"""
205+
response_json = raw_response.json()
206+
verbose_logger.debug(
207+
"Transforming delete skill response: %s", response_json
208+
)
209+
210+
return DeleteSkillResponse(**response_json)
211+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Base Skills API configuration"""
2+
3+
from .transformation import BaseSkillsAPIConfig
4+
5+
__all__ = ["BaseSkillsAPIConfig"]
6+

0 commit comments

Comments
 (0)