diff --git a/strix/report/dedupe.py b/strix/report/dedupe.py index 1cc0a66a..d97e0af3 100644 --- a/strix/report/dedupe.py +++ b/strix/report/dedupe.py @@ -13,8 +13,10 @@ from openai.types.responses import ResponseOutputMessage from strix.config import load_settings from strix.config.models import ( + LLMTR_API_BASE, StrixProvider, configure_sdk_model_defaults, + is_llmtr_model, ) from strix.core.inputs import make_model_settings from strix.report.state import get_global_report_state @@ -45,6 +47,12 @@ def _dedupe_extra_args(dedupe: DedupeSettings) -> dict[str, str]: extra["api_key"] = dedupe.api_key.strip() if dedupe.api_base and dedupe.api_base.strip(): extra["api_base"] = dedupe.api_base.strip() + elif is_llmtr_model(dedupe.model): + # A dedicated llmtr/ dedupe model reaches the LLMTR gateway only through + # its endpoint. The global default is derived from the main model, which + # may be a different provider, so pin the gateway per call here rather + # than relying on the shared litellm.api_base. + extra["api_base"] = LLMTR_API_BASE return extra diff --git a/tests/test_dedupe_model.py b/tests/test_dedupe_model.py index b17946e9..2466d851 100644 --- a/tests/test_dedupe_model.py +++ b/tests/test_dedupe_model.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from strix.config import loader from strix.config.settings import DedupeSettings -from strix.report.dedupe import _dedupe_model_settings +from strix.report.dedupe import _dedupe_extra_args, _dedupe_model_settings if TYPE_CHECKING: @@ -44,6 +44,35 @@ def test_dedupe_endpoint_sent_per_call() -> None: assert (settings.extra_args or {})["api_key"] == "dedupe-key" +def test_llmtr_dedupe_model_pins_gateway_endpoint_per_call() -> None: + # A dedicated llmtr/ dedupe model reaches the gateway only via its endpoint. + # The global default is keyed to the main model, which may be a different + # provider, so the endpoint must ride on the dedupe request itself. + dedupe = DedupeSettings( + STRIX_DEDUPE_MODEL="llmtr/openai/gpt-5.5", + DEDUPE_LLM_API_KEY="llmtr-key", + ) + extra = _dedupe_extra_args(dedupe) + assert extra["api_base"] == "https://llmtr.com/v1" + assert extra["api_key"] == "llmtr-key" + + +def test_llmtr_dedupe_model_respects_explicit_endpoint() -> None: + # An explicit DEDUPE_LLM_API_BASE (e.g. a proxy in front of LLMTR) wins over + # the gateway default. + dedupe = DedupeSettings( + STRIX_DEDUPE_MODEL="llmtr/openai/gpt-5.5", + DEDUPE_LLM_API_KEY="llmtr-key", + DEDUPE_LLM_API_BASE="https://proxy.example/v1", + ) + assert _dedupe_extra_args(dedupe)["api_base"] == "https://proxy.example/v1" + + +def test_non_llmtr_dedupe_model_gets_no_injected_endpoint() -> None: + dedupe = DedupeSettings(STRIX_DEDUPE_MODEL="openai/gpt-5.5", DEDUPE_LLM_API_KEY="k") + assert "api_base" not in _dedupe_extra_args(dedupe) + + def test_dedicated_dedupe_model_uses_own_headers_not_main() -> None: dedupe = DedupeSettings( STRIX_DEDUPE_MODEL="deepseek/cheap",