Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ inference gateways.
| `openai` | `OPENAI_API_KEY` (+ optional `OPENAI_BASE_URL`) | api.openai.com (or any OpenAI-compatible URL) | `gpt-5.4` |
| `anthropic` | `ANTHROPIC_API_KEY` | api.anthropic.com | `claude-opus-4-6` |
| `anthropic_proxy` | `ANTHROPIC_PROXY_API_KEY` + `ANTHROPIC_PROXY_ENDPOINT_URL` | Any Vertex-style raw-predict proxy | `claude-sonnet-4-6` |
| `bedrock` | `AWS_PROFILE` (optional) + `AWS_REGION` — SigV4 via boto3 | AWS Bedrock Runtime | `us.anthropic.claude-sonnet-4-6-20250915-v1:0` |
| `nv_build` | `NVIDIA_INFERENCE_KEY` | build.nvidia.com | `deepseek-ai/deepseek-v4-flash` |

```bash
Expand All @@ -169,6 +170,18 @@ export ANTHROPIC_PROXY_API_KEY=your-bearer-token
export SKILLSPECTOR_MODEL=claude-sonnet-4-6
skillspector scan ./my-skill/

# AWS Bedrock (Claude via SigV4)
export SKILLSPECTOR_PROVIDER=bedrock
# Optional: select an AWS named profile. When unset, the standard
# boto3 credential chain (env vars, instance metadata, SSO, etc.) resolves.
# export AWS_PROFILE=my-profile
export AWS_REGION=us-west-2 # default if unset
# Default model: us.anthropic.claude-sonnet-4-6-20250915-v1:0
# Override with any Bedrock model ID, cross-region inference-profile
# ID, or your own application-inference-profile ARN:
# export SKILLSPECTOR_MODEL=us.anthropic.claude-opus-4-6-20250915-v1:0
skillspector scan ./my-skill/

# NVIDIA build.nvidia.com
export SKILLSPECTOR_PROVIDER=nv_build
export NVIDIA_INFERENCE_KEY=nvapi-...
Expand Down Expand Up @@ -404,14 +417,16 @@ Issues (2)

| Variable | Description | Required |
|----------|-------------|----------|
| `SKILLSPECTOR_PROVIDER` | Active LLM provider: `openai`, `anthropic`, or `nv_build`. Each provider has its own bundled `model_registry.yaml` and default model (see the LLM Analysis table above). Defaults to `nv_build`. | Optional |
| `SKILLSPECTOR_PROVIDER` | Active LLM provider: `openai`, `anthropic`, `anthropic_proxy`, `bedrock`, or `nv_build`. Each provider has its own bundled `model_registry.yaml` and default model (see the LLM Analysis table above). Defaults to `nv_build`. | Optional |
| `NVIDIA_INFERENCE_KEY` | Credential for the `nv_build` provider (build.nvidia.com). | Required for LLM analysis when `SKILLSPECTOR_PROVIDER=nv_build` |
| `OPENAI_API_KEY` | Credential for the OpenAI provider (`SKILLSPECTOR_PROVIDER=openai`). Also serves as the tier-2 fallback in the credential waterfall when the active provider returns no credentials. | Required for LLM analysis when `SKILLSPECTOR_PROVIDER=openai` |
| `OPENAI_BASE_URL` | Override the OpenAI endpoint (e.g. point at Ollama). | Optional |
| `ANTHROPIC_API_KEY` | Credential for the Anthropic provider (`SKILLSPECTOR_PROVIDER=anthropic`). | Required for LLM analysis when `SKILLSPECTOR_PROVIDER=anthropic` |
| `ANTHROPIC_PROXY_ENDPOINT_URL` | Full endpoint URL for the Anthropic proxy provider (Vertex-style raw-predict). | Required when `SKILLSPECTOR_PROVIDER=anthropic_proxy` |
| `ANTHROPIC_PROXY_API_KEY` | Bearer token for the Anthropic proxy provider. | Required when `SKILLSPECTOR_PROVIDER=anthropic_proxy` |
| `ANTHROPIC_PROXY_API_VERSION` | `anthropic_version` value sent in the request body (default: `vertex-2023-10-16`). | Optional |
| `AWS_PROFILE` | Named AWS profile for the Bedrock provider — authenticates via SigV4 through boto3. When unset, the standard boto3 credential chain (env vars, instance metadata, SSO, etc.) resolves. | Optional (used when `SKILLSPECTOR_PROVIDER=bedrock`) |
| `AWS_REGION` | AWS region for the Bedrock Runtime endpoint. Defaults to `us-west-2`. | Optional (used when `SKILLSPECTOR_PROVIDER=bedrock`) |
| `SKILLSPECTOR_MODEL` | Override the active provider's default model. See the LLM Analysis table for each provider's default. | Optional |
| `SKILLSPECTOR_MODEL_REGISTRY` | Override the bundled per-provider YAML registry (`src/skillspector/providers/<provider>/model_registry.yaml`) with a custom path. | Optional |
| `SKILLSPECTOR_LOG_LEVEL` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `WARNING`). | Optional |
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ dependencies = [
"langgraph>=1.0.10",
"langgraph-cli[inmem]>=0.4.14",
"langchain-anthropic>=1.4.5",
"langchain-aws>=0.2.0",
"langchain-core>=1.2.17",
"langchain-openai>=1.1.10",
"boto3>=1.34.0",
"langsmith>=0.7.30",
"yara-python>=4.5.0",
]
Expand Down
10 changes: 7 additions & 3 deletions src/skillspector/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ def scan(
Environment variables:

SKILLSPECTOR_PROVIDER Active LLM provider: openai | anthropic |
nv_build | nv_inference. Defaults to the
NVIDIA path (nv_inference, falling back to
nv_build in OSS builds).
anthropic_proxy | bedrock | nv_build |
nv_inference. Defaults to the NVIDIA path
(nv_inference, falling back to nv_build in
OSS builds).
SKILLSPECTOR_MODEL Override the active provider's default
model (applies to every analyzer slot).
SKILLSPECTOR_LOG_LEVEL DEBUG | INFO | WARNING | ERROR (default WARNING).
Expand All @@ -203,6 +204,9 @@ def scan(

OPENAI_API_KEY [+ OPENAI_BASE_URL] for SKILLSPECTOR_PROVIDER=openai
ANTHROPIC_API_KEY for SKILLSPECTOR_PROVIDER=anthropic
AWS_PROFILE (optional) + AWS_REGION for SKILLSPECTOR_PROVIDER=bedrock
(AWS_PROFILE: standard boto3 credential
chain when unset; AWS_REGION default: us-west-2)
NVIDIA_INFERENCE_KEY for the NVIDIA providers
"""
result = None
Expand Down
7 changes: 6 additions & 1 deletion src/skillspector/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
openai → OpenAIProvider (api.openai.com)
anthropic → AnthropicProvider (api.anthropic.com)
anthropic_proxy → AnthropicProxyProvider (Vertex-style raw-predict proxy)
bedrock → BedrockProvider (AWS Bedrock Runtime, SigV4)
nv_build → NvBuildProvider (build.nvidia.com)

When unset, the selector defaults to ``nv_build``.
Expand Down Expand Up @@ -69,6 +70,10 @@ def _select_active_provider() -> LLMProvider:
from .anthropic_proxy import AnthropicProxyProvider

return AnthropicProxyProvider()
if name == "bedrock":
from .bedrock import BedrockProvider

return BedrockProvider()
if name == "nv_build":
return NvBuildProvider()
if name in ("nv_inference", ""):
Expand All @@ -83,7 +88,7 @@ def _select_active_provider() -> LLMProvider:

raise ValueError(
f"Unknown SKILLSPECTOR_PROVIDER: {name!r}. "
"Expected one of: openai, anthropic, anthropic_proxy, nv_build (or unset)."
"Expected one of: openai, anthropic, anthropic_proxy, bedrock, nv_build (or unset)."
)


Expand Down
30 changes: 30 additions & 0 deletions src/skillspector/providers/bedrock/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""AWS Bedrock provider package (Claude via SigV4-authenticated Bedrock Runtime)."""

from .provider import (
BEDROCK_DEFAULT_MODEL,
BEDROCK_DEFAULT_REGION,
REGISTRY_PATH,
BedrockProvider,
)

__all__ = [
"BEDROCK_DEFAULT_MODEL",
"BEDROCK_DEFAULT_REGION",
"REGISTRY_PATH",
"BedrockProvider",
]
29 changes: 29 additions & 0 deletions src/skillspector/providers/bedrock/model_registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Token-budget metadata for the BedrockProvider (AWS Bedrock Runtime).
# Bundled with the package; consulted whenever the active provider is
# BedrockProvider.
#
# Format:
# models:
# "<model-label>":
# context_length: <int> # total context window in tokens (required)
# max_output_tokens: <int> # model's max output cap (optional)
#
# Keys are public Bedrock cross-region inference profile IDs. Users can
# also point SKILLSPECTOR_MODEL at a stock Bedrock model ID or their own
# application-inference-profile ARN — those won't be in this registry,
# so the scanner falls back to the default token budget for unknown
# models. Add entries here if you have a private ARN you want metadata
# for.

models:
"us.anthropic.claude-sonnet-4-6-20250915-v1:0":
context_length: 1000000
max_output_tokens: 128000

"us.anthropic.claude-opus-4-6-20250915-v1:0":
context_length: 1000000
max_output_tokens: 128000

"us.anthropic.claude-opus-4-5-20250514-v1:0":
context_length: 200000
max_output_tokens: 64000
144 changes: 144 additions & 0 deletions src/skillspector/providers/bedrock/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""AWS Bedrock provider — Claude models via the Bedrock Runtime.

Bedrock authenticates with AWS SigV4, not API keys. ``resolve_credentials``
returns ``None`` because the ``(api_key, base_url)`` shape doesn't fit;
``create_chat_model`` instead probes the boto3 credential chain and
constructs ``ChatBedrockConverse`` directly when AWS credentials resolve.

Environment variables:
``AWS_PROFILE`` — when set, used as the boto3 named profile.
When unset, the standard boto3 credential chain
(env vars, instance metadata, SSO, etc.) resolves.
``AWS_REGION`` — defaults to ``us-west-2``.
``SKILLSPECTOR_MODEL`` — overrides the default model (a Bedrock
model ID, a cross-region inference-profile ID, or your own
application-inference-profile ARN).
"""

from __future__ import annotations

import os
from pathlib import Path

import boto3
from botocore.config import Config as BotocoreConfig
from langchain_aws import ChatBedrockConverse
from langchain_core.language_models.chat_models import BaseChatModel

from skillspector.providers import registry

BEDROCK_DEFAULT_REGION = "us-west-2"
# Cross-region inference profile ID for Claude Sonnet 4.6. Public,
# available to any account with Anthropic-on-Bedrock model access.
# Users can override with SKILLSPECTOR_MODEL to point at a different
# model or their own application-inference-profile ARN.
BEDROCK_DEFAULT_MODEL = "us.anthropic.claude-sonnet-4-6-20250915-v1:0"
# Connect timeout for the Bedrock Runtime client. The per-call
# ``timeout`` from ``create_chat_model`` is applied as the read timeout.
_BEDROCK_CONNECT_TIMEOUT = 10

REGISTRY_PATH = str(Path(__file__).with_name("model_registry.yaml"))


class BedrockProvider:
"""AWS Bedrock provider — SigV4 auth via boto3, bundled-YAML metadata."""

DEFAULT_MODEL = BEDROCK_DEFAULT_MODEL
SLOT_DEFAULTS: dict[str, str] = {}

def resolve_credentials(self) -> tuple[str, str | None] | None:
"""Bedrock uses SigV4, not ``(api_key, base_url)`` — always returns ``None``.

``providers.resolve_chat_model_credentials`` treats ``None`` as "this
provider doesn't supply OpenAI-style credentials" and falls through
to its OpenAI fallback (which is irrelevant for Bedrock — the
chat-model construction path in ``create_chat_model`` is what
matters).
"""
return None

def create_chat_model(
self,
model: str,
*,
max_tokens: int,
timeout: float | None = 120,
) -> BaseChatModel | None:
"""Construct a ``ChatBedrockConverse`` bound to a Bedrock client.

Returns ``None`` if no AWS credentials resolve in the boto3
credential chain, so the orchestrator can fall through to its
OpenAI fallback. Otherwise returns a configured client.

``AWS_PROFILE`` selects a named profile when set; otherwise the
standard boto3 credential chain (env vars, instance metadata,
SSO, etc.) resolves. ``AWS_REGION`` defaults to ``us-west-2``.

``timeout`` is applied as the boto3 client's read timeout via a
``botocore.config.Config`` so long Bedrock calls actually time
out instead of hanging.

``ChatBedrockConverse`` requires an explicit ``provider``
argument when the model is supplied as an ARN (inference
profiles); we pin it to ``anthropic`` since this provider is
Claude-only. When a plain Bedrock model ID is supplied,
``provider`` is inferred from the prefix and we omit it.
"""
profile = os.environ.get("AWS_PROFILE", "").strip() or None
region = os.environ.get("AWS_REGION", "").strip() or BEDROCK_DEFAULT_REGION

session_kwargs: dict[str, object] = {"region_name": region}
if profile:
session_kwargs["profile_name"] = profile
session = boto3.Session(**session_kwargs)

# If nothing in the boto3 credential chain resolves, return None so
# the orchestrator can fall through.
if session.get_credentials() is None:
return None

client = session.client(
"bedrock-runtime",
region_name=region,
config=BotocoreConfig(
read_timeout=timeout,
connect_timeout=_BEDROCK_CONNECT_TIMEOUT,
),
)

kwargs: dict[str, object] = {
"model": model,
"client": client,
"region_name": region,
"max_tokens": max_tokens,
}
if model.startswith("arn:"):
kwargs["provider"] = "anthropic"

return ChatBedrockConverse(**kwargs)

def get_context_length(self, model: str) -> int | None:
return registry.lookup_context_length(REGISTRY_PATH, model)

def get_max_output_tokens(self, model: str) -> int | None:
return registry.lookup_max_output_tokens(REGISTRY_PATH, model)

def resolve_model(self, slot: str = "default") -> str:
"""Resolve model: ``SKILLSPECTOR_MODEL`` env > slot default > ``DEFAULT_MODEL``."""
user_input = os.environ.get("SKILLSPECTOR_MODEL", "").strip()
return user_input or self.SLOT_DEFAULTS.get(slot, "") or self.DEFAULT_MODEL
Loading