Skip to content

Commit 03cdf9c

Browse files
eric-tramelprzemekboruta
authored andcommitted
feat: add generic and OpenRouter attribution headers (NVIDIA-NeMo#542)
1 parent da475d9 commit 03cdf9c

4 files changed

Lines changed: 157 additions & 5 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,17 @@ 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+
310+
# OpenRouter attribution is injected in the engine so telemetry opt-out can
311+
# suppress it cleanly for both default and existing provider configs.
301312
PREDEFINED_PROVIDERS = [
302313
{
303314
"name": NVIDIA_PROVIDER_NAME,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,17 @@ 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 is None
111114

112115

113116
def test_get_default_model_configs_path_exists(tmp_path: Path):

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
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 (
14+
ATTRIBUTION_TITLE,
15+
OPENROUTER_ATTRIBUTION_HEADERS,
16+
OPENROUTER_PROVIDER_NAME,
17+
)
1318
from data_designer.config.utils.image_helpers import is_image_diffusion_model
1419
from data_designer.engine.mcp.errors import MCPConfigurationError
1520
from data_designer.engine.model_provider import ModelProviderRegistry
@@ -30,6 +35,7 @@
3035
get_exception_primary_cause,
3136
)
3237
from data_designer.engine.models.parsers.errors import ParserException
38+
from data_designer.engine.models.telemetry import TELEMETRY_ENABLED
3339
from data_designer.engine.models.usage import ImageUsageStats, ModelUsageStats, RequestUsageStats, TokenUsageStats
3440
from data_designer.engine.models.utils import ChatMessage, prompt_to_messages
3541

@@ -156,7 +162,21 @@ def consolidate_kwargs(self, **kwargs: Any) -> dict[str, Any]:
156162
if self.model_provider.extra_body:
157163
kwargs["extra_body"] = {**kwargs.get("extra_body", {}), **self.model_provider.extra_body}
158164
if self.model_provider.extra_headers:
159-
kwargs["extra_headers"] = {**kwargs.get("extra_headers", {}), **self.model_provider.extra_headers}
165+
kwargs["extra_headers"] = {**(kwargs.get("extra_headers") or {}), **self.model_provider.extra_headers}
166+
# Inject framework-level attribution header when telemetry is enabled.
167+
# Applied last so that user-supplied or provider-level headers take precedence.
168+
if TELEMETRY_ENABLED:
169+
headers = kwargs.get("extra_headers") or {}
170+
if "X-Title" not in headers:
171+
kwargs["extra_headers"] = {"X-Title": ATTRIBUTION_TITLE, **headers}
172+
# Inject OpenRouter-specific attribution headers when the provider is
173+
# OpenRouter. This ensures attribution works even when existing users
174+
# have ``extra_headers: null`` in their provider config. Provider- or
175+
# user-supplied values take precedence (only missing keys are filled).
176+
if self.model_provider.name == OPENROUTER_PROVIDER_NAME:
177+
headers = kwargs.get("extra_headers") or {}
178+
merged = {**OPENROUTER_ATTRIBUTION_HEADERS, **headers}
179+
kwargs["extra_headers"] = merged
160180
return kwargs
161181

162182
# --- completion / acompletion ---

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

Lines changed: 122 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,8 +233,117 @@ 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"},
237+
}
238+
239+
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 takes precedence over the 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+
stub_model_facade.model_provider.extra_headers = None
261+
result = stub_model_facade.consolidate_kwargs(extra_headers={"X-Title": "Caller App"})
262+
assert result["extra_headers"]["X-Title"] == "Caller App"
263+
264+
265+
def test_consolidate_kwargs_with_explicit_none_extra_headers(
266+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
267+
) -> None:
268+
"""Explicit None extra_headers does not break provider merges or framework attribution."""
269+
stub_model_facade.model_provider.extra_headers = {"hello": "world"}
270+
result = stub_model_facade.consolidate_kwargs(extra_headers=None)
271+
assert result["extra_headers"] == {"X-Title": "NeMo Data Designer", "hello": "world"}
272+
273+
274+
def test_consolidate_kwargs_openrouter_attribution(
275+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
276+
) -> None:
277+
"""OpenRouter-specific attribution headers are injected when provider is openrouter."""
278+
stub_model_facade.model_provider.name = "openrouter"
279+
stub_model_facade.model_provider.extra_headers = None
280+
result = stub_model_facade.consolidate_kwargs()
281+
assert result["extra_headers"] == {
282+
"X-Title": "NeMo Data Designer",
283+
"HTTP-Referer": "https://github.com/NVIDIA-NeMo/DataDesigner",
284+
"X-OpenRouter-Title": "NeMo Data Designer",
285+
"X-OpenRouter-Categories": "programming-app",
286+
}
287+
288+
289+
def test_consolidate_kwargs_openrouter_user_override_preserved(
290+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
291+
) -> None:
292+
"""User-supplied OpenRouter headers take precedence over framework defaults."""
293+
stub_model_facade.model_provider.name = "openrouter"
294+
stub_model_facade.model_provider.extra_headers = None
295+
result = stub_model_facade.consolidate_kwargs(
296+
extra_headers={"X-OpenRouter-Title": "Custom App", "X-Custom": "value"}
297+
)
298+
# User-supplied X-OpenRouter-Title should NOT be overwritten
299+
assert result["extra_headers"]["X-OpenRouter-Title"] == "Custom App"
300+
assert result["extra_headers"]["X-Custom"] == "value"
301+
# Framework defaults still fill in missing keys
302+
assert result["extra_headers"]["HTTP-Referer"] == "https://github.com/NVIDIA-NeMo/DataDesigner"
303+
assert result["extra_headers"]["X-OpenRouter-Categories"] == "programming-app"
304+
assert result["extra_headers"]["X-Title"] == "NeMo Data Designer"
305+
306+
307+
def test_consolidate_kwargs_openrouter_provider_headers_preserved(
308+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
309+
) -> None:
310+
"""Provider-level OpenRouter headers override programmatic injection."""
311+
stub_model_facade.model_provider.name = "openrouter"
312+
stub_model_facade.model_provider.extra_headers = {
313+
"HTTP-Referer": "https://custom-site.example.com",
314+
"X-OpenRouter-Title": "Provider Title",
228315
}
316+
result = stub_model_facade.consolidate_kwargs()
317+
# Provider-level values take precedence
318+
assert result["extra_headers"]["HTTP-Referer"] == "https://custom-site.example.com"
319+
assert result["extra_headers"]["X-OpenRouter-Title"] == "Provider Title"
320+
# Framework still fills in what's missing
321+
assert result["extra_headers"]["X-OpenRouter-Categories"] == "programming-app"
322+
assert result["extra_headers"]["X-Title"] == "NeMo Data Designer"
323+
324+
325+
@patch("data_designer.engine.models.facade.TELEMETRY_ENABLED", False)
326+
def test_consolidate_kwargs_openrouter_no_attribution_when_telemetry_off(
327+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
328+
) -> None:
329+
"""OpenRouter attribution headers are NOT injected when telemetry is disabled."""
330+
stub_model_facade.model_provider.name = "openrouter"
331+
stub_model_facade.model_provider.extra_headers = None
332+
result = stub_model_facade.consolidate_kwargs()
333+
assert "extra_headers" not in result
334+
335+
336+
def test_consolidate_kwargs_non_openrouter_no_openrouter_headers(
337+
stub_model_configs: list[Any], stub_model_facade: ModelFacade
338+
) -> None:
339+
"""Non-openrouter providers do NOT get OpenRouter-specific headers."""
340+
stub_model_facade.model_provider.name = "nvidia"
341+
stub_model_facade.model_provider.extra_headers = None
342+
result = stub_model_facade.consolidate_kwargs()
343+
assert result["extra_headers"] == {"X-Title": "NeMo Data Designer"}
344+
assert "HTTP-Referer" not in result["extra_headers"]
345+
assert "X-OpenRouter-Title" not in result["extra_headers"]
346+
assert "X-OpenRouter-Categories" not in result["extra_headers"]
229347

230348

231349
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)