AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters.
fix(backend): teach LlmModel about OpenRouter Anthropic aliases + dry-run sim default Haiku OR-slug (#13177)
### Why / What / How
**Why:** PR #13171 (SECRT-2368) flipped the dry-run simulator default to
`LlmModel.CLAUDE_4_5_HAIKU.value` = `"claude-haiku-4-5-20251001"`. That
broke every dry-run on dev because the LLM-simulation path
(`_call_llm_for_simulation`) hits OpenRouter's OpenAI-compat endpoint
(`/api/v1/chat/completions`), which only accepts canonical
`<vendor>/<model>` slugs and returns HTTP 400 `"<id> is not a valid
model ID"` for direct-Anthropic snapshot IDs. User-visible: `[SIMULATOR
ERROR — NOT A BLOCK FAILURE] Failed after 5 attempts: Error code: 400 -
'claude-haiku-4-5-20251001 is not a valid model ID'`.
**Verified empirically against OpenRouter:**
| Slug | `/v1/chat/completions` (OpenAI-compat) | `/v1/messages`
(Anthropic-compat) |
|---|---|---|
| `claude-haiku-4-5-20251001` (snapshot) | ❌ 400 not valid | ✅ accepted
|
| `anthropic/claude-haiku-4-5` (OR slug) | ✅ accepted | ✅ accepted |
| `anthropic/claude-haiku-4-5-20251001` (prefix + snapshot) | ❌ 400 not
valid | — |
So the right default for the simulator is the **OpenRouter slug**
(`anthropic/claude-haiku-4-5`), and we need `LlmModel` to resolve that
string back to the existing `CLAUDE_4_5_HAIKU` enum member so
`OrchestratorBlock.Input.model` validation still passes.
**What:**
- Add `_OPENROUTER_ALIASES: dict[str, str]` in `backend/blocks/llm.py`
mapping the three Claude 4.5 OpenRouter slugs
(`anthropic/claude-haiku-4-5`, `anthropic/claude-opus-4-5`,
`anthropic/claude-sonnet-4-5`) to their direct-Anthropic snapshot enum
values. The newer 4.6/4.7 entries don't carry the snapshot suffix, so
the existing prefix-strip path already covers them.
- Extend `LlmModel._missing_` to consult `_OPENROUTER_ALIASES` first,
then fall back to the existing provider-prefix strip.
- Change `_DEFAULT_SIMULATOR_MODEL` (in `backend/executor/simulator.py`)
and `ChatConfig.simulation_model` default (in
`backend/copilot/config.py`) to the bare string
`"anthropic/claude-haiku-4-5"`. Both files drop their now-unused
`LlmModel` import.
- Update `TestDefaultSimulatorModel` to pin the new default + assert it
resolves to `LlmModel.CLAUDE_4_5_HAIKU` via the alias map, plus a `"/"
in default` guard so a future revert to a snapshot ID trips at unit-test
time.
- New `TestLlmModelMissingHandler` covering: alias-map resolution,
prefix-strip fallback, and unknown-slug raising.
**How:**
- **LLM-simulation path** (`_call_llm_for_simulation`): sends
`"anthropic/claude-haiku-4-5"` straight to OpenRouter's OpenAI-compat
endpoint via `get_openai_client(prefer_openrouter=True)` — accepted.
- **Orchestrator dry-run path** (`prepare_dry_run` injects
`"anthropic/claude-haiku-4-5"` as `model` on `OrchestratorBlock.Input`):
Pydantic calls `LlmModel("anthropic/claude-haiku-4-5")`, hits
`_missing_`, alias map returns `CLAUDE_4_5_HAIKU`. The orchestrator's
Anthropic SDK then sends the enum's `.value`
(`"claude-haiku-4-5-20251001"`) to OpenRouter's Anthropic-compat
endpoint, which accepts both formats — also fine.
**Why not duplicate enum entries?** Per reviewer feedback ("this is
weird having multiple models"): adding `CLAUDE_4_5_HAIKU_OPENROUTER`
alongside the existing direct-Anthropic entry would duplicate metadata +
cost lookups for the same model and pull the OpenRouter/direct routing
distinction into the enum itself. The alias map keeps `LlmModel` as one
entry per model while still letting both identifiers resolve through the
validator.
### Changes 🏗️
- `_OPENROUTER_ALIASES` map + extended `_missing_` in `LlmModel`.
- Dry-run simulator default flipped from Haiku snapshot ID to Haiku
OpenRouter slug.
- New unit tests for the alias-resolution path; updated
`TestDefaultSimulatorModel` pin.
- No `LlmModel` enum members added or removed → no `llm.md` regen
needed.
### Checklist 📋
#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] `poetry run pytest
backend/executor/simulator_test.py::TestDefaultSimulatorModel
backend/executor/simulator_test.py::TestPrepareDryRun
backend/copilot/tools/test_dry_run.py::test_prepare_dry_run_orchestrator_block`
— all green
- [x] `poetry run pytest
backend/blocks/test/test_llm.py::TestLlmModelMissingHandler` — 3 new
tests, all green
- [x] Empirical OpenRouter probes recorded above
- [x] Direct e2e check: `LlmModel("anthropic/claude-haiku-4-5") is
LlmModel.CLAUDE_4_5_HAIKU` ✓; `LlmModel("anthropic/claude-opus-4-7") is
LlmModel.CLAUDE_4_7_OPUS` ✓
- [x] `poetry run ruff format` + `poetry run ruff check` clean on all
changed files
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Z
Zamil Majdy committed
9d138e217f94aa6d22b077e2e0f9aa2da2d8f4eb
Parent: 09368cd
Committed by GitHub <noreply@github.com>
on 5/21/2026, 8:05:59 AM