diff --git a/libs/partners/openrouter/langchain_openrouter/chat_models.py b/libs/partners/openrouter/langchain_openrouter/chat_models.py index 5ea9943786d..a31c4386940 100644 --- a/libs/partners/openrouter/langchain_openrouter/chat_models.py +++ b/libs/partners/openrouter/langchain_openrouter/chat_models.py @@ -152,14 +152,27 @@ class ChatOpenRouter(BaseChatModel): """OpenRouter API base URL. Maps to SDK `server_url`.""" app_url: str | None = Field( - default_factory=from_env("OPENROUTER_APP_URL", default=None), + default_factory=from_env( + "OPENROUTER_APP_URL", + default="https://docs.langchain.com/oss", + ), ) - """Application URL for OpenRouter attribution. Maps to `HTTP-Referer` header.""" + """Application URL for OpenRouter attribution. + + Maps to `HTTP-Referer` header. + + See https://openrouter.ai/docs/app-attribution for details. + """ app_title: str | None = Field( - default_factory=from_env("OPENROUTER_APP_TITLE", default=None), + default_factory=from_env("OPENROUTER_APP_TITLE", default="langchain"), ) - """Application title for OpenRouter attribution. Maps to `X-Title` header.""" + """Application title for OpenRouter attribution. + + Maps to `X-Title` header. + + See https://openrouter.ai/docs/app-attribution for details. + """ request_timeout: int | None = Field(default=None, alias="timeout") """Timeout for requests in milliseconds. Maps to SDK `timeout_ms`.""" diff --git a/libs/partners/openrouter/tests/unit_tests/__snapshots__/test_standard.ambr b/libs/partners/openrouter/tests/unit_tests/__snapshots__/test_standard.ambr index 2e4d376538a..f0574a19267 100644 --- a/libs/partners/openrouter/tests/unit_tests/__snapshots__/test_standard.ambr +++ b/libs/partners/openrouter/tests/unit_tests/__snapshots__/test_standard.ambr @@ -7,6 +7,8 @@ 'ChatOpenRouter', ]), 'kwargs': dict({ + 'app_title': 'langchain', + 'app_url': 'https://docs.langchain.com/oss', 'max_retries': 2, 'max_tokens': 100, 'model_name': 'openai/gpt-4o-mini', diff --git a/libs/partners/openrouter/tests/unit_tests/test_chat_models.py b/libs/partners/openrouter/tests/unit_tests/test_chat_models.py index e35bcf9f2dc..2e41c43c3e0 100644 --- a/libs/partners/openrouter/tests/unit_tests/test_chat_models.py +++ b/libs/partners/openrouter/tests/unit_tests/test_chat_models.py @@ -310,6 +310,32 @@ class TestChatOpenRouterInstantiation: call_kwargs = mock_cls.call_args[1] assert call_kwargs["x_title"] == "My App" + def test_default_attribution_headers(self) -> None: + """Test that default attribution headers are sent when not overridden.""" + with patch("openrouter.OpenRouter") as mock_cls: + mock_cls.return_value = MagicMock() + ChatOpenRouter( + model=MODEL_NAME, + api_key=SecretStr("test-key"), + ) + call_kwargs = mock_cls.call_args[1] + assert call_kwargs["http_referer"] == ("https://docs.langchain.com/oss") + assert call_kwargs["x_title"] == "langchain" + + def test_user_attribution_overrides_defaults(self) -> None: + """Test that user-supplied attribution overrides the defaults.""" + with patch("openrouter.OpenRouter") as mock_cls: + mock_cls.return_value = MagicMock() + ChatOpenRouter( + model=MODEL_NAME, + api_key=SecretStr("test-key"), + app_url="https://my-custom-app.com", + app_title="My Custom App", + ) + call_kwargs = mock_cls.call_args[1] + assert call_kwargs["http_referer"] == "https://my-custom-app.com" + assert call_kwargs["x_title"] == "My Custom App" + def test_reasoning_in_params(self) -> None: """Test that `reasoning` is included in default params.""" model = _make_model(reasoning={"effort": "high"}) diff --git a/libs/partners/openrouter/uv.lock b/libs/partners/openrouter/uv.lock index ce01861a456..85487c9ef5b 100644 --- a/libs/partners/openrouter/uv.lock +++ b/libs/partners/openrouter/uv.lock @@ -318,7 +318,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.13" +version = "1.2.15" source = { editable = "../../core" } dependencies = [ { name = "jsonpatch" },