fix(llm): honor openrouter_app_title only alongside a custom referer (#453)
OpenRouter keys app pages by HTTP-Referer; X-Title only renames that page. A custom openrouter_app_title on the default referer therefore renamed the shared EvoScientist app page for everyone. Force the default title whenever the resolved referer is the default, silently, so usage keeps being attributed to EvoScientist; a private fork still overrides both together.
This commit is contained in:
@@ -334,7 +334,9 @@ class EvoScientistConfig:
|
||||
openrouter_anthropic_prompt_cache: bool = True
|
||||
# OpenRouter app attribution (issue #339). Sent only for the openrouter
|
||||
# provider; identifies EvoScientist in OpenRouter's app rankings/analytics.
|
||||
# Override (e.g. a private fork) via these fields or their env vars.
|
||||
# Override (e.g. a private fork) via these fields or their env vars. A custom
|
||||
# title only takes effect together with a custom referer: OpenRouter keys app
|
||||
# pages by referer, so a lone title would rename the shared EvoScientist page.
|
||||
# Defaults live in the module constants above (also imported by llm/models.py).
|
||||
openrouter_http_referer: str = OPENROUTER_DEFAULT_HTTP_REFERER
|
||||
openrouter_app_title: str = OPENROUTER_DEFAULT_APP_TITLE
|
||||
|
||||
@@ -501,6 +501,11 @@ def get_chat_model(
|
||||
os.environ.get("EVOSCIENTIST_OPENROUTER_APP_TITLE", "").strip()
|
||||
or OPENROUTER_DEFAULT_APP_TITLE,
|
||||
)
|
||||
# OpenRouter keys app pages by HTTP-Referer and X-Title only renames that
|
||||
# page, so a custom title on the default referer would rename the shared
|
||||
# EvoScientist page for everyone. Honor it only with a custom referer.
|
||||
if kwargs["app_url"] == OPENROUTER_DEFAULT_HTTP_REFERER:
|
||||
kwargs["app_title"] = OPENROUTER_DEFAULT_APP_TITLE
|
||||
# app_categories must be a list[str] (langchain-openrouter joins it into
|
||||
# the X-OpenRouter-Categories header); split the comma-separated config
|
||||
# value and drop blanks so a stray comma/space can't emit an empty one.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for EvoScientist LLM module."""
|
||||
|
||||
import warnings
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -864,6 +865,34 @@ class TestThirdPartyRouting:
|
||||
# An explicit list is preserved verbatim, not re-split.
|
||||
assert call_kwargs["app_categories"] == ["only-this"]
|
||||
|
||||
@pytest.mark.parametrize("source", ["env", "kwarg"])
|
||||
@patch("EvoScientist.llm.models.init_chat_model")
|
||||
def test_openrouter_title_override_without_referer_falls_back_silently(
|
||||
self, mock_init, monkeypatch, source
|
||||
):
|
||||
"""OpenRouter keys app pages by HTTP-Referer, so a custom title on the
|
||||
default referer would rename the shared EvoScientist page. It is
|
||||
replaced by the default title, without any user-facing warning,
|
||||
whether the title came from the env (config) or an explicit kwarg."""
|
||||
mock_init.return_value = "mock_model"
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
for _env in self._APP_ATTR_ENV:
|
||||
monkeypatch.delenv(_env, raising=False)
|
||||
extra = {}
|
||||
if source == "env":
|
||||
monkeypatch.setenv("EVOSCIENTIST_OPENROUTER_APP_TITLE", "Acme")
|
||||
else:
|
||||
extra["app_title"] = "Acme"
|
||||
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always")
|
||||
get_chat_model("x-ai/grok-4.3", provider="openrouter", **extra)
|
||||
|
||||
assert not [w for w in caught if "openrouter" in str(w.message).lower()]
|
||||
call_kwargs = mock_init.call_args[1]
|
||||
assert call_kwargs["app_url"] == "https://github.com/EvoScientist/EvoScientist"
|
||||
assert call_kwargs["app_title"] == "EvoScientist"
|
||||
|
||||
@patch("EvoScientist.llm.models.init_chat_model")
|
||||
def test_non_openrouter_providers_get_no_app_attribution(
|
||||
self, mock_init, monkeypatch
|
||||
|
||||
Reference in New Issue
Block a user