Skip to content

Commit a047ccf

Browse files
committed
Add attribution headers to outbound API requests
Inject X-Title header on all provider requests when telemetry is enabled, and add OpenRouter-specific attribution headers (HTTP-Referer, X-OpenRouter-Title, X-OpenRouter-Categories) to the predefined OpenRouter provider entry. - Add ATTRIBUTION_TITLE, ATTRIBUTION_REFERER, and OPENROUTER_ATTRIBUTION_HEADERS constants - Add extra_headers to the OpenRouter predefined provider - Inject X-Title in ModelFacade.consolidate_kwargs() gated on TELEMETRY_ENABLED, with user/provider headers taking precedence - Update and extend tests for both config and engine packages Fixes #519 Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
1 parent aee3d3f commit a047ccf

File tree

4 files changed

+59
-4
lines changed

4 files changed

+59
-4
lines changed

packages/data-designer-config/src/data_designer/config/utils/constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,15 @@ class NordColor(Enum):
298298

299299
OPENROUTER_API_KEY_ENV_VAR_NAME = "OPENROUTER_API_KEY"
300300

301+
ATTRIBUTION_TITLE = "NeMo Data Designer"
302+
ATTRIBUTION_REFERER = "https://github.com/NVIDIA-NeMo/DataDesigner"
303+
304+
OPENROUTER_ATTRIBUTION_HEADERS: dict[str, str] = {
305+
"HTTP-Referer": ATTRIBUTION_REFERER,
306+
"X-OpenRouter-Title": ATTRIBUTION_TITLE,
307+
"X-OpenRouter-Categories": "programming-app",
308+
}
309+
301310
PREDEFINED_PROVIDERS = [
302311
{
303312
"name": NVIDIA_PROVIDER_NAME,
@@ -316,6 +325,7 @@ class NordColor(Enum):
316325
"endpoint": "https://openrouter.ai/api/v1",
317326
"provider_type": "openai",
318327
"api_key": OPENROUTER_API_KEY_ENV_VAR_NAME,
328+
"extra_headers": OPENROUTER_ATTRIBUTION_HEADERS,
319329
},
320330
]
321331

packages/data-designer-config/tests/config/test_default_model_settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,21 @@ def test_get_builtin_model_providers():
100100
assert builtin_model_providers[0].endpoint == "https://integrate.api.nvidia.com/v1"
101101
assert builtin_model_providers[0].provider_type == "openai"
102102
assert builtin_model_providers[0].api_key == "NVIDIA_API_KEY"
103+
assert builtin_model_providers[0].extra_headers is None
103104
assert builtin_model_providers[1].name == "openai"
104105
assert builtin_model_providers[1].endpoint == "https://api.openai.com/v1"
105106
assert builtin_model_providers[1].provider_type == "openai"
106107
assert builtin_model_providers[1].api_key == "OPENAI_API_KEY"
108+
assert builtin_model_providers[1].extra_headers is None
107109
assert builtin_model_providers[2].name == "openrouter"
108110
assert builtin_model_providers[2].endpoint == "https://openrouter.ai/api/v1"
109111
assert builtin_model_providers[2].provider_type == "openai"
110112
assert builtin_model_providers[2].api_key == "OPENROUTER_API_KEY"
113+
assert builtin_model_providers[2].extra_headers == {
114+
"HTTP-Referer": "https://github.com/NVIDIA-NeMo/DataDesigner",
115+
"X-OpenRouter-Title": "NeMo Data Designer",
116+
"X-OpenRouter-Categories": "programming-app",
117+
}
111118

112119

113120
def test_get_default_model_configs_path_exists(tmp_path: Path):

packages/data-designer-engine/src/data_designer/engine/models/facade.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import TYPE_CHECKING, Any
1111

1212
from data_designer.config.models import GenerationType, ModelConfig, ModelProvider
13+
from data_designer.config.utils.constants import ATTRIBUTION_TITLE
1314
from data_designer.config.utils.image_helpers import is_image_diffusion_model
1415
from data_designer.engine.mcp.errors import MCPConfigurationError
1516
from data_designer.engine.model_provider import ModelProviderRegistry
@@ -30,6 +31,7 @@
3031
get_exception_primary_cause,
3132
)
3233
from data_designer.engine.models.parsers.errors import ParserException
34+
from data_designer.engine.models.telemetry import TELEMETRY_ENABLED
3335
from data_designer.engine.models.usage import ImageUsageStats, ModelUsageStats, RequestUsageStats, TokenUsageStats
3436
from data_designer.engine.models.utils import ChatMessage, prompt_to_messages
3537

@@ -157,6 +159,12 @@ def consolidate_kwargs(self, **kwargs: Any) -> dict[str, Any]:
157159
kwargs["extra_body"] = {**kwargs.get("extra_body", {}), **self.model_provider.extra_body}
158160
if self.model_provider.extra_headers:
159161
kwargs["extra_headers"] = {**kwargs.get("extra_headers", {}), **self.model_provider.extra_headers}
162+
# Inject framework-level attribution header when telemetry is enabled.
163+
# Applied last so that user-supplied or provider-level headers take precedence.
164+
if TELEMETRY_ENABLED:
165+
headers = kwargs.get("extra_headers", {})
166+
if "X-Title" not in headers:
167+
kwargs["extra_headers"] = {"X-Title": ATTRIBUTION_TITLE, **headers}
160168
return kwargs
161169

162170
# --- completion / acompletion ---

packages/data-designer-engine/tests/engine/models/test_facade.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,20 +202,29 @@ def test_usage_stats_property(stub_model_facade: ModelFacade) -> None:
202202

203203

204204
def test_consolidate_kwargs(stub_model_configs: list[Any], stub_model_facade: ModelFacade) -> None:
205-
# Model config generate kwargs are used as base, and purpose is removed
205+
# Model config generate kwargs are used as base, and purpose is removed.
206+
# When telemetry is enabled (default), X-Title is injected.
206207
result = stub_model_facade.consolidate_kwargs(purpose="test")
207-
assert result == stub_model_configs[0].inference_parameters.generate_kwargs
208+
assert result == {
209+
**stub_model_configs[0].inference_parameters.generate_kwargs,
210+
"extra_headers": {"X-Title": "NeMo Data Designer"},
211+
}
208212

209213
# kwargs overrides model config generate kwargs
210214
result = stub_model_facade.consolidate_kwargs(temperature=0.01, purpose="test")
211-
assert result == {**stub_model_configs[0].inference_parameters.generate_kwargs, "temperature": 0.01}
215+
assert result == {
216+
**stub_model_configs[0].inference_parameters.generate_kwargs,
217+
"temperature": 0.01,
218+
"extra_headers": {"X-Title": "NeMo Data Designer"},
219+
}
212220

213221
# Provider extra_body overrides all other kwargs
214222
stub_model_facade.model_provider.extra_body = {"foo_provider": "bar_provider"}
215223
result = stub_model_facade.consolidate_kwargs(extra_body={"foo": "bar"}, purpose="test")
216224
assert result == {
217225
**stub_model_configs[0].inference_parameters.generate_kwargs,
218226
"extra_body": {"foo_provider": "bar_provider", "foo": "bar"},
227+
"extra_headers": {"X-Title": "NeMo Data Designer"},
219228
}
220229

221230
# Provider extra_headers merges with caller headers (provider takes precedence)
@@ -224,10 +233,31 @@ def test_consolidate_kwargs(stub_model_configs: list[Any], stub_model_facade: Mo
224233
result = stub_model_facade.consolidate_kwargs(extra_headers={"hello": "caller", "X-Trace-ID": "abc"})
225234
assert result == {
226235
**stub_model_configs[0].inference_parameters.generate_kwargs,
227-
"extra_headers": {"hello": "world", "hola": "mundo", "X-Trace-ID": "abc"},
236+
"extra_headers": {"X-Title": "NeMo Data Designer", "hello": "world", "hola": "mundo", "X-Trace-ID": "abc"},
228237
}
229238

230239

240+
@patch("data_designer.engine.models.facade.TELEMETRY_ENABLED", False)
241+
def test_consolidate_kwargs_telemetry_disabled(stub_model_configs: list[Any], stub_model_facade: ModelFacade) -> None:
242+
"""Framework attribution headers are omitted when telemetry is disabled."""
243+
result = stub_model_facade.consolidate_kwargs()
244+
assert "extra_headers" not in result
245+
246+
# Provider extra_headers still applied even with telemetry off
247+
stub_model_facade.model_provider.extra_headers = {"Custom": "header"}
248+
result = stub_model_facade.consolidate_kwargs()
249+
assert result["extra_headers"] == {"Custom": "header"}
250+
251+
252+
def test_consolidate_kwargs_user_x_title_override(
253+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
254+
) -> None:
255+
"""User-supplied X-Title via provider extra_headers takes precedence over framework default."""
256+
stub_model_facade.model_provider.extra_headers = {"X-Title": "My Custom App"}
257+
result = stub_model_facade.consolidate_kwargs()
258+
assert result["extra_headers"]["X-Title"] == "My Custom App"
259+
260+
231261
@pytest.mark.parametrize(
232262
"skip_usage_tracking",
233263
[

0 commit comments

Comments
 (0)