Honour explicit OpenRouter --model when it equals the adapter default
Some checks failed
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled

The adapter compared self._model to _DEFAULT_MODEL ("anthropic/claude-sonnet-4")
to decide whether to honour the constructor's model. When a caller passes
that exact value via --model, the comparison treats it as "not specified"
and falls through to RunConfig.model_name, which defaults to "gpt-4". So
every llm-connect call started with --provider openrouter --model
anthropic/claude-sonnet-4 actually landed on OpenAI's gpt-4 — and on
gpt-4 OpenAI's structured-output response_format requires a model with
schema support that gpt-4 lacks, returning 400. The CUST-WP-0045 canary
hit this for hours; the smoke probes that worked were the ones with no
json_schema, where gpt-4 returned fine.

Track _explicit_model separately so a constructor or LLMConfig that
matches the default is still treated as a real intent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 14:50:37 +02:00
parent 583ab57a59
commit 1b01f0edf4

View File

@@ -37,6 +37,14 @@ class OpenRouterAdapter(LLMAdapter):
max_retries: Optional[int] = None,
):
self._config = config or LLMConfig()
# Track whether the model was explicitly supplied (constructor or
# LLMConfig). Comparing self._model to _DEFAULT_MODEL is not enough —
# callers who pass --model anthropic/claude-sonnet-4 happen to match
# the default and would otherwise be misrouted to RunConfig.model_name
# (which defaults to "gpt-4" — quietly sending every call to OpenAI's
# gpt-4 model, which is what broke the activity-core CUST-WP-0045
# canary on 2026-06-02).
self._explicit_model = model is not None or self._config.model is not None
self._model = model or self._config.model or _DEFAULT_MODEL
self._api_base = (api_base or self._config.api_base).rstrip("/")
self._system_prompt = system_prompt
@@ -56,7 +64,14 @@ class OpenRouterAdapter(LLMAdapter):
def execute_prompt(self, prompt: str, config: RunConfig) -> LLMResponse:
self._preflight_budget(config)
model = self._model if self._model != _DEFAULT_MODEL else (config.model_name or self._model)
# Explicit constructor/LLMConfig model wins; only fall back to the
# per-call RunConfig.model_name when the adapter wasn't told what to
# use. RunConfig.model_name defaults to "gpt-4", so falling back
# unconditionally would silently misroute callers.
if self._explicit_model:
model = self._model
else:
model = config.model_name or self._model
messages: list[Dict[str, str]] = []
if self._system_prompt: