diff --git a/libs/deepagents/deepagents/_models.py b/libs/deepagents/deepagents/_models.py index 722a84bd4b..417eceb3bb 100644 --- a/libs/deepagents/deepagents/_models.py +++ b/libs/deepagents/deepagents/_models.py @@ -39,9 +39,10 @@ def resolve_model(model: str | BaseChatModel) -> BaseChatModel: String models are resolved via `init_chat_model`, composed with any provider-specific initialization behavior registered in the - `ProviderProfile` registry. Built-in registrations supply the OpenAI - Responses API default and OpenRouter app attribution headers; users can - layer additional providers or overrides via `register_provider_profile`. + `ProviderProfile` registry. Built-in registrations supply NVIDIA NIM and + OpenRouter app attribution headers plus the OpenAI Responses API default; + users can layer additional providers or overrides via + `register_provider_profile`. Args: model: Model string (e.g. `"openai:gpt-5.4"`) or pre-configured diff --git a/libs/deepagents/deepagents/profiles/_builtin_profiles.py b/libs/deepagents/deepagents/profiles/_builtin_profiles.py index 6fc717885f..d2feb19f5b 100644 --- a/libs/deepagents/deepagents/profiles/_builtin_profiles.py +++ b/libs/deepagents/deepagents/profiles/_builtin_profiles.py @@ -33,7 +33,7 @@ _openai_codex, ) from deepagents.profiles.harness.harness_profiles import _HARNESS_PROFILES -from deepagents.profiles.provider import _openai, _openrouter +from deepagents.profiles.provider import _nvidia, _openai, _openrouter from deepagents.profiles.provider.provider_profiles import _PROVIDER_PROFILES logger = logging.getLogger(__name__) @@ -145,6 +145,7 @@ def _ensure_builtin_profiles_loaded() -> None: saved_harness_profiles = dict(_HARNESS_PROFILES) saved_bootstrap_harness_keys = _BOOTSTRAP_HARNESS_KEYS try: + _nvidia.register() _openai.register() _openrouter.register() _anthropic_opus_4_7.register() diff --git a/libs/deepagents/deepagents/profiles/provider/_nvidia.py b/libs/deepagents/deepagents/profiles/provider/_nvidia.py new file mode 100644 index 0000000000..c5398d9769 --- /dev/null +++ b/libs/deepagents/deepagents/profiles/provider/_nvidia.py @@ -0,0 +1,50 @@ +"""Built-in NVIDIA provider profile and helpers. + +Injects Deep Agents app-origin attribution into NVIDIA NIM requests via the +header supported by `langchain-nvidia-ai-endpoints`. + +Registered directly by `_ensure_builtin_profiles_loaded` during the first +profile-registry access. Not exposed as an `importlib.metadata` entry point — +built-ins ship with the SDK and should not depend on install-time metadata to +activate. +""" + +from __future__ import annotations + +from typing import Any + +from deepagents.profiles.provider.provider_profiles import ( + ProviderProfile, + _register_provider_profile_impl, +) + +_NVIDIA_BILLING_ORIGIN_HEADER = "X-BILLING-INVOKE-ORIGIN" +"""NVIDIA NIM header used to attribute requests to an originating app.""" + +_NVIDIA_APP_ORIGIN = "DeepAgents" +"""Deep Agents identity reported to NVIDIA NIM.""" + + +def _nvidia_attribution_kwargs() -> dict[str, Any]: + """Build default NVIDIA NIM app-attribution kwargs. + + `ChatNVIDIA` accepts `default_headers` and merges them into sync, async, + and streaming requests. Returning a new nested mapping on every call keeps + profile resolution isolated between model instances. + + Returns: + Dictionary of kwargs to spread into `init_chat_model`. + """ + return { + "default_headers": { + _NVIDIA_BILLING_ORIGIN_HEADER: _NVIDIA_APP_ORIGIN, + } + } + + +def register() -> None: + """Register the built-in NVIDIA provider profile.""" + _register_provider_profile_impl( + "nvidia", + ProviderProfile(init_kwargs_factory=_nvidia_attribution_kwargs), + ) diff --git a/libs/deepagents/deepagents/profiles/provider/provider_profiles.py b/libs/deepagents/deepagents/profiles/provider/provider_profiles.py index cfb5f02f6f..60a07c1434 100644 --- a/libs/deepagents/deepagents/profiles/provider/provider_profiles.py +++ b/libs/deepagents/deepagents/profiles/provider/provider_profiles.py @@ -13,8 +13,9 @@ runtime state (e.g. environment variables). """ # Built-in profiles are registered lazily on first registry access for -# `"openai"` (enables the Responses API by default) and `"openrouter"` -# (enforces a minimum version and injects app-attribution headers). +# `"nvidia"` (injects NIM app-attribution headers), `"openai"` (enables the +# Responses API by default), and `"openrouter"` (enforces a minimum version and +# injects app-attribution headers). # Additional providers or per-model overrides can be registered with # `register_provider_profile`. diff --git a/libs/deepagents/tests/unit_tests/test_models.py b/libs/deepagents/tests/unit_tests/test_models.py index b1be08167f..88a4a03af3 100644 --- a/libs/deepagents/tests/unit_tests/test_models.py +++ b/libs/deepagents/tests/unit_tests/test_models.py @@ -31,6 +31,11 @@ _merge_middleware, _merge_profiles, ) +from deepagents.profiles.provider._nvidia import ( + _NVIDIA_APP_ORIGIN, + _NVIDIA_BILLING_ORIGIN_HEADER, + _nvidia_attribution_kwargs, +) from deepagents.profiles.provider._openrouter import ( _OPENROUTER_ALLOW_AZURE_ENV, _OPENROUTER_APP_TITLE, @@ -104,6 +109,19 @@ def test_openrouter_prefix_sets_attribution(self) -> None: ) assert result is mock.return_value + def test_nvidia_prefix_sets_billing_origin(self) -> None: + with patch("deepagents._models.init_chat_model") as mock: + mock.return_value = MagicMock(spec=BaseChatModel) + result = resolve_model("nvidia:nvidia/nemotron-3-super-120b-a12b") + + mock.assert_called_once_with( + "nvidia:nvidia/nemotron-3-super-120b-a12b", + default_headers={ + _NVIDIA_BILLING_ORIGIN_HEADER: _NVIDIA_APP_ORIGIN, + }, + ) + assert result is mock.return_value + def test_openrouter_env_var_overrides_app_url(self) -> None: env = {"OPENROUTER_APP_URL": "https://custom.app"} with ( @@ -1148,6 +1166,10 @@ def test_openrouter_provider_profile_has_pre_init_and_factory(self) -> None: assert profile.pre_init is not None assert profile.init_kwargs_factory is not None + def test_nvidia_provider_profile_has_attribution_factory(self) -> None: + profile = get_provider_profile("nvidia:nvidia/nemotron-3-super-120b-a12b") + assert profile.init_kwargs_factory is _nvidia_attribution_kwargs + def test_openai_has_no_built_in_harness_profile(self) -> None: assert _get_harness_profile("openai:gpt-5") is None @@ -1787,6 +1809,26 @@ def test_openrouter_runs_pre_init_and_factory(self) -> None: _, kwargs = mock.call_args assert "app_url" in kwargs or "app_title" in kwargs + def test_nvidia_runs_attribution_factory(self) -> None: + with patch("deepagents._models.init_chat_model") as mock: + mock.return_value = MagicMock(spec=BaseChatModel) + resolve_model("nvidia:nvidia/nemotron-3-super-120b-a12b") + + _, kwargs = mock.call_args + assert kwargs["default_headers"] == { + _NVIDIA_BILLING_ORIGIN_HEADER: _NVIDIA_APP_ORIGIN, + } + + def test_nvidia_caller_headers_override_attribution_default(self) -> None: + custom_headers = {_NVIDIA_BILLING_ORIGIN_HEADER: "CustomHarness"} + + kwargs = apply_provider_profile( + "nvidia:nvidia/nemotron-3-super-120b-a12b", + {"default_headers": custom_headers}, + ) + + assert kwargs["default_headers"] is custom_headers + def test_unknown_provider_passes_no_extra_kwargs(self) -> None: with patch("deepagents._models.init_chat_model") as mock: mock.return_value = MagicMock(spec=BaseChatModel)