-
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
[Feat] New API - Claude Skills API (Anthropic) #17042
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1b5e6eb
41c83e7
4baa20e
341862b
c6bb974
5aad732
894ddbf
47a8475
8c3c91c
eb9381a
c21b004
35072ab
295db31
230e8fd
9116bdb
089a838
906b3ab
a6b84a2
efa2101
bcff2cd
c9eee15
21312e7
0280fad
f25dd08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| """Anthropic Skills API integration""" | ||
|
|
||
| from .transformation import AnthropicSkillsConfig | ||
|
|
||
| __all__ = ["AnthropicSkillsConfig"] | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Anthropic Skills API | ||
|
|
||
| This folder maintains the integration for the Anthropic Skills API. | ||
|
|
||
| You can do the following with the Anthropic Skills API: | ||
|
|
||
| 1. Create a new skill | ||
| 2. List all skills | ||
| 3. Get a skill | ||
| 4. Delete a skill | ||
|
|
||
|
|
||
| Versions: | ||
| - Create Skill Version | ||
| - List Skill Versions | ||
| - Get Skill Version | ||
| - Delete Skill Version |
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,211 @@ | |||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||
| Anthropic Skills API configuration and transformations | |||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| from typing import Any, Dict, Optional, Tuple | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| import httpx | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| from litellm._logging import verbose_logger | |||||||||||||||||||||||||||||
| from litellm.llms.base_llm.skills.transformation import ( | |||||||||||||||||||||||||||||
| BaseSkillsAPIConfig, | |||||||||||||||||||||||||||||
| LiteLLMLoggingObj, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| from litellm.types.llms.anthropic_skills import ( | |||||||||||||||||||||||||||||
| CreateSkillRequest, | |||||||||||||||||||||||||||||
| DeleteSkillResponse, | |||||||||||||||||||||||||||||
| ListSkillsParams, | |||||||||||||||||||||||||||||
| ListSkillsResponse, | |||||||||||||||||||||||||||||
| Skill, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| from litellm.types.router import GenericLiteLLMParams | |||||||||||||||||||||||||||||
| from litellm.types.utils import LlmProviders | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| class AnthropicSkillsConfig(BaseSkillsAPIConfig): | |||||||||||||||||||||||||||||
| """Anthropic-specific Skills API configuration""" | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| @property | |||||||||||||||||||||||||||||
| def custom_llm_provider(self) -> LlmProviders: | |||||||||||||||||||||||||||||
| return LlmProviders.ANTHROPIC | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def validate_environment( | |||||||||||||||||||||||||||||
| self, headers: dict, litellm_params: Optional[GenericLiteLLMParams] | |||||||||||||||||||||||||||||
| ) -> dict: | |||||||||||||||||||||||||||||
| """Add Anthropic-specific headers""" | |||||||||||||||||||||||||||||
| from litellm.llms.anthropic.common_utils import AnthropicModelInfo | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Get API key | |||||||||||||||||||||||||||||
| api_key = None | |||||||||||||||||||||||||||||
| if litellm_params: | |||||||||||||||||||||||||||||
| api_key = litellm_params.api_key | |||||||||||||||||||||||||||||
| api_key = AnthropicModelInfo.get_api_key(api_key) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| if not api_key: | |||||||||||||||||||||||||||||
| raise ValueError("ANTHROPIC_API_KEY is required for Skills API") | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Add required headers | |||||||||||||||||||||||||||||
| headers["x-api-key"] = api_key | |||||||||||||||||||||||||||||
| headers["anthropic-version"] = "2023-06-01" | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Add beta header for skills API | |||||||||||||||||||||||||||||
| from litellm.constants import ANTHROPIC_SKILLS_API_BETA_VERSION | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| if "anthropic-beta" not in headers: | |||||||||||||||||||||||||||||
| headers["anthropic-beta"] = ANTHROPIC_SKILLS_API_BETA_VERSION | |||||||||||||||||||||||||||||
| elif isinstance(headers["anthropic-beta"], list): | |||||||||||||||||||||||||||||
| if ANTHROPIC_SKILLS_API_BETA_VERSION not in headers["anthropic-beta"]: | |||||||||||||||||||||||||||||
| headers["anthropic-beta"].append(ANTHROPIC_SKILLS_API_BETA_VERSION) | |||||||||||||||||||||||||||||
| elif isinstance(headers["anthropic-beta"], str): | |||||||||||||||||||||||||||||
| if ANTHROPIC_SKILLS_API_BETA_VERSION not in headers["anthropic-beta"]: | |||||||||||||||||||||||||||||
| headers["anthropic-beta"] = [headers["anthropic-beta"], ANTHROPIC_SKILLS_API_BETA_VERSION] | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| headers["content-type"] = "application/json" | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return headers | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def get_complete_url( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| api_base: Optional[str], | |||||||||||||||||||||||||||||
| endpoint: str, | |||||||||||||||||||||||||||||
| skill_id: Optional[str] = None, | |||||||||||||||||||||||||||||
| ) -> str: | |||||||||||||||||||||||||||||
| """Get complete URL for Anthropic Skills API""" | |||||||||||||||||||||||||||||
| from litellm.llms.anthropic.common_utils import AnthropicModelInfo | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| if api_base is None: | |||||||||||||||||||||||||||||
| api_base = AnthropicModelInfo.get_api_base() | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| if skill_id: | |||||||||||||||||||||||||||||
| return f"{api_base}/v1/skills/{skill_id}?beta=true" | |||||||||||||||||||||||||||||
| return f"{api_base}/v1/{endpoint}?beta=true" | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_create_skill_request( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| create_request: CreateSkillRequest, | |||||||||||||||||||||||||||||
| litellm_params: GenericLiteLLMParams, | |||||||||||||||||||||||||||||
| headers: dict, | |||||||||||||||||||||||||||||
| ) -> Dict: | |||||||||||||||||||||||||||||
| """Transform create skill request for Anthropic""" | |||||||||||||||||||||||||||||
| verbose_logger.debug( | |||||||||||||||||||||||||||||
| "Transforming create skill request: %s", create_request | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Anthropic expects the request body directly | |||||||||||||||||||||||||||||
| request_body = {k: v for k, v in create_request.items() if v is not None} | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return request_body | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_create_skill_response( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| raw_response: httpx.Response, | |||||||||||||||||||||||||||||
| logging_obj: LiteLLMLoggingObj, | |||||||||||||||||||||||||||||
| ) -> Skill: | |||||||||||||||||||||||||||||
| """Transform Anthropic response to Skill object""" | |||||||||||||||||||||||||||||
| response_json = raw_response.json() | |||||||||||||||||||||||||||||
| verbose_logger.debug( | |||||||||||||||||||||||||||||
| "Transforming create skill response: %s", response_json | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return Skill(**response_json) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_list_skills_request( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| list_params: ListSkillsParams, | |||||||||||||||||||||||||||||
| litellm_params: GenericLiteLLMParams, | |||||||||||||||||||||||||||||
| headers: dict, | |||||||||||||||||||||||||||||
| ) -> Tuple[str, Dict]: | |||||||||||||||||||||||||||||
| """Transform list skills request for Anthropic""" | |||||||||||||||||||||||||||||
| from litellm.llms.anthropic.common_utils import AnthropicModelInfo | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| api_base = AnthropicModelInfo.get_api_base( | |||||||||||||||||||||||||||||
| litellm_params.api_base if litellm_params else None | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| url = self.get_complete_url(api_base=api_base, endpoint="skills") | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Build query parameters | |||||||||||||||||||||||||||||
| query_params: Dict[str, Any] = {} | |||||||||||||||||||||||||||||
| if "limit" in list_params and list_params["limit"]: | |||||||||||||||||||||||||||||
| query_params["limit"] = list_params["limit"] | |||||||||||||||||||||||||||||
| if "page" in list_params and list_params["page"]: | |||||||||||||||||||||||||||||
| query_params["page"] = list_params["page"] | |||||||||||||||||||||||||||||
| if "source" in list_params and list_params["source"]: | |||||||||||||||||||||||||||||
| query_params["source"] = list_params["source"] | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| verbose_logger.debug( | |||||||||||||||||||||||||||||
| "List skills request made to Anthropic Skills endpoint with params: %s", query_params | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return url, query_params | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_list_skills_response( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| raw_response: httpx.Response, | |||||||||||||||||||||||||||||
| logging_obj: LiteLLMLoggingObj, | |||||||||||||||||||||||||||||
| ) -> ListSkillsResponse: | |||||||||||||||||||||||||||||
| """Transform Anthropic response to ListSkillsResponse""" | |||||||||||||||||||||||||||||
| response_json = raw_response.json() | |||||||||||||||||||||||||||||
| verbose_logger.debug( | |||||||||||||||||||||||||||||
| "Transforming list skills response: %s", response_json | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return ListSkillsResponse(**response_json) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_get_skill_request( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| skill_id: str, | |||||||||||||||||||||||||||||
| api_base: str, | |||||||||||||||||||||||||||||
| litellm_params: GenericLiteLLMParams, | |||||||||||||||||||||||||||||
| headers: dict, | |||||||||||||||||||||||||||||
| ) -> Tuple[str, Dict]: | |||||||||||||||||||||||||||||
| """Transform get skill request for Anthropic""" | |||||||||||||||||||||||||||||
| url = self.get_complete_url( | |||||||||||||||||||||||||||||
| api_base=api_base, endpoint="skills", skill_id=skill_id | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| verbose_logger.debug("Get skill request - URL: %s", url) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return url, headers | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_get_skill_response( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| raw_response: httpx.Response, | |||||||||||||||||||||||||||||
| logging_obj: LiteLLMLoggingObj, | |||||||||||||||||||||||||||||
| ) -> Skill: | |||||||||||||||||||||||||||||
| """Transform Anthropic response to Skill object""" | |||||||||||||||||||||||||||||
| response_json = raw_response.json() | |||||||||||||||||||||||||||||
| verbose_logger.debug( | |||||||||||||||||||||||||||||
| "Transforming get skill response: %s", response_json | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return Skill(**response_json) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_delete_skill_request( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| skill_id: str, | |||||||||||||||||||||||||||||
| api_base: str, | |||||||||||||||||||||||||||||
| litellm_params: GenericLiteLLMParams, | |||||||||||||||||||||||||||||
| headers: dict, | |||||||||||||||||||||||||||||
| ) -> Tuple[str, Dict]: | |||||||||||||||||||||||||||||
| """Transform delete skill request for Anthropic""" | |||||||||||||||||||||||||||||
| url = self.get_complete_url( | |||||||||||||||||||||||||||||
| api_base=api_base, endpoint="skills", skill_id=skill_id | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| verbose_logger.debug("Delete skill request - URL: %s", url) | |||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading This expression logs sensitive data (secret) Error loading related location Loading
Copilot AutofixAI 8 days ago To address this vulnerability, we must prevent direct logging of potentially sensitive information, in this case the full API URL including the base. The recommended solution is to remove or sanitize the logging statement, so any output does not reveal the API base (or other secrets), but still allows useful logging (such as endpoint and skill ID). Best approach: verbose_logger.debug("Delete skill request - URL: %s", url)with a log statement that only includes safe metadata (e.g., the endpoint and skill ID, not the API base). For instance: verbose_logger.debug("Delete skill request - Endpoint: skills, Skill ID: %s", skill_id)This maintains sufficient context for debugging without risking secret leakage. Changes needed:
Suggested changeset
1
litellm/llms/anthropic/skills/transformation.py
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Positive FeedbackNegative Feedback
Refresh and try again.
|
|||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return url, headers | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def transform_delete_skill_response( | |||||||||||||||||||||||||||||
| self, | |||||||||||||||||||||||||||||
| raw_response: httpx.Response, | |||||||||||||||||||||||||||||
| logging_obj: LiteLLMLoggingObj, | |||||||||||||||||||||||||||||
| ) -> DeleteSkillResponse: | |||||||||||||||||||||||||||||
| """Transform Anthropic response to DeleteSkillResponse""" | |||||||||||||||||||||||||||||
| response_json = raw_response.json() | |||||||||||||||||||||||||||||
| verbose_logger.debug( | |||||||||||||||||||||||||||||
| "Transforming delete skill response: %s", response_json | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| return DeleteSkillResponse(**response_json) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| """Base Skills API configuration""" | ||
|
|
||
| from .transformation import BaseSkillsAPIConfig | ||
|
|
||
| __all__ = ["BaseSkillsAPIConfig"] | ||
|
|
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
Copilot Autofix
AI 8 days ago
We should avoid logging the full URL (which may contain sensitive information from the secret manager) in cleartext.
The best fix is to sanitize the value before logging, such as by redacting the potentially sensitive portion of the URL or by omitting the log statement entirely.
The minimal-impact approach:
transform_get_skill_request, replace the debug statement on line 166 that logsurlwith a safe log.Options:
To preserve the existing functionality and logging context, and assuming the rest of the codebase does not require this log, we will simply remove or redact the sensitive component.
Edit required:
litellm/llms/anthropic/skills/transformation.py, intransform_get_skill_request, replace line 166:url.Given only the code shown, the safest route is simply removing the debug statement that logs the full URL.