Skip to content

fix(anthropic): allow effort="max" on Claude Opus 4.7 - #25958

Closed
shang309073819 wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
shang309073819:fix/effort-max-opus-4-7
Closed

fix(anthropic): allow effort="max" on Claude Opus 4.7#25958
shang309073819 wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
shang309073819:fix/effort-max-opus-4-7

Conversation

@shang309073819

Copy link
Copy Markdown

Title

fix(anthropic): allow effort="max" on Claude Opus 4.7

Relevant issues

Fixes #25957

Pre-Submission Checklist (Click to expand)

  • I have Read the Contributing Guide
  • I have added a test for this PR
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible

Type

🐛 Bug Fix

Changes

AnthropicConfig._apply_output_config previously rejected effort="max" for any model that wasn't Claude Opus 4.6:

# litellm/llms/anthropic/chat/transformation.py (before)
# ``max`` is Claude Opus 4.6 only (not Sonnet 4.6, not Opus 4.5/4.7).
# Keep this hardcoded so the error message is specific and stable.
if effort == "max" and not self._is_opus_4_6_model(model):
    raise ValueError(
        f"effort='max' is only supported by Claude Opus 4.6. "
        f"Got model: {model}"
    )

This guard was added in #22234 (back when 4.6 was the only model accepting max) and was intentionally kept in the day-0 4.7 PR (#25867). However, Anthropic's Messages API accepts output_config.effort="max" on claude-opus-4-7-* as well — calling the API directly with model=claude-opus-4-7-*, output_config={"effort":"max"}, and thinking={"type":"adaptive"} returns a normal 200 completion.

So LiteLLM was the only thing in the chain rejecting a perfectly valid configuration, breaking flows like Claude Code via ANTHROPIC_BASE_URL and any direct SDK call against Opus 4.7 with reasoning_effort="max".

This PR allows effort="max" on both Opus 4.6 and Opus 4.7. _is_opus_4_7_model already exists on AnthropicConfig (added in #25867), so the diff is minimal:

if effort == "max" and not (
    self._is_opus_4_6_model(model) or self._is_opus_4_7_model(model)
):
    raise ValueError(
        f"effort='max' is only supported by Claude Opus 4.6 and 4.7. "
        f"Got model: {model}"
    )

I kept the substring-style check (rather than switching to a model-map supports_max_reasoning_effort lookup) so date-variant model names (e.g. claude-opus-4-7-20260408) keep working without requiring every dated entry to be present in the cost map. Switching to a data-driven lookup would be a reasonable follow-up but is out of scope for this fix.

Tests

$ pytest tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py -k "effort or output_config or opus"
......................                                                   [100%]
22 passed, 115 deselected in 0.32s

@vercel

vercel Bot commented Apr 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 17, 2026 11:28pm

Request Review

@CLAassistant

CLAassistant commented Apr 17, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes #25957 by allowing effort=\"max\" on Claude Opus 4.7 (and also correctly enables it for Sonnet 4.6 per Anthropic's documentation). It fully addresses the prior review concern by replacing the hardcoded _is_opus_4_6_model substring guard with the data-driven _supports_effort_level(model, \"max\") helper, mirroring the existing xhigh pattern — enabling future models via a pure model-map change. The ProviderSpecificModelInfo TypedDict and _get_model_info_helper are updated to carry the new supports_max_reasoning_effort field.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style nits and the core logic is correct.

The hardcoded model check is fully replaced with the data-driven approach required by the team's custom rule; supports_max_reasoning_effort is wired through all layers (TypedDict → helper → model map); tests are strengthened not weakened. The only open finding is a trivial indentation inconsistency in two JSON entries.

model_prices_and_context_window.json and its backup — minor indentation inconsistency on the vertex_ai/claude-sonnet-4-6 and vertex_ai/claude-sonnet-4-6@default entries.

Important Files Changed

Filename Overview
litellm/llms/anthropic/chat/transformation.py Switches the effort=max guard from a hardcoded _is_opus_4_6_model substring check to the data-driven _supports_effort_level(model, max) helper, matching the team's model-map rule and the existing xhigh pattern.
model_prices_and_context_window.json Adds supports_max_reasoning_effort: true to Opus 4.6/4.7 and Sonnet 4.6 entries (including regional/vertex variants); vertex_ai/claude-sonnet-4-6 and its @default variant have a minor indentation inconsistency on the new key.
litellm/model_prices_and_context_window_backup.json Mirror of model_prices_and_context_window.json changes; same indentation inconsistency on vertex_ai/claude-sonnet-4-6 entries.
litellm/types/utils.py Adds supports_max_reasoning_effort: Optional[bool] to ProviderSpecificModelInfo TypedDict, completing the type coverage for the new model-map key.
litellm/utils.py Threads supports_max_reasoning_effort through _get_model_info_helper so the new TypedDict field is populated from the model cost map.
tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py Updates error-message assertions to match the new generic wording, inverts test_max_effort_rejected_for_sonnet_46 to test_max_effort_accepted_for_sonnet_46 per Anthropic docs, and adds test_max_effort_accepted_for_opus_47 as a regression test for #25957.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["_apply_output_config(model, effort)"] --> B{effort == max?}
    B -- No --> E[Pass through to request]
    B -- Yes --> C["_supports_effort_level(model, 'max')"]
    C --> D["_get_model_info_helper looks up\nsupports_max_reasoning_effort\nin model cost map JSON"]
    D -- true --> E
    D -- "false or not found" --> F["raise ValueError: effort=max not supported"]
    E --> G[Set output_config on request data]

    subgraph "Entries with supports_max_reasoning_effort: true"
        M1["claude-opus-4-6 and dated variants"]
        M2["claude-opus-4-7 and dated variants"]
        M3["claude-sonnet-4-6 and dated variants"]
        M4["All regional and vertex_ai variants"]
    end
Loading

Reviews (7): Last reviewed commit: "test(anthropic): force LITELLM_LOCAL_MOD..." | Re-trigger Greptile

Comment on lines 1541 to 1547
if effort == "max" and not (
self._is_opus_4_6_model(model) or self._is_opus_4_7_model(model)
):
raise ValueError(
f"effort='max' is only supported by Claude Opus 4.6. "
f"effort='max' is only supported by Claude Opus 4.6 and 4.7. "
f"Got model: {model}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded model-flag violates the "use model map" rule

The team rule requires that model-capability flags live in model_prices_and_context_window.json and be queried via _supports_effort_level / get_model_info, not hardcoded substring checks. The xhigh effort level already follows this pattern (one line below); max should too.

The Opus 4.7 entries (claude-opus-4-7, claude-opus-4-7-20260416) already carry supports_xhigh_reasoning_effort: true but are missing supports_max_reasoning_effort: true. The complete fix is a two-part change:

  1. Add "supports_max_reasoning_effort": true to the Opus 4.7 entries in model_prices_and_context_window.json (and its bedrock/azure_ai/regional variants).
  2. Replace the expanded substring check with the existing data-driven helper:
if effort == "max" and not self._supports_effort_level(model, "max"):
    raise ValueError(
        f"effort='max' is only supported by Claude Opus 4.6 and 4.7. "
        f"Got model: {model}"
    )

This makes enabling max for any future model a pure JSON change—exactly the pattern the project enforces.

Rule Used: What: Do not hardcode model-specific flags in the ... (source)

@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@shang309073819

Copy link
Copy Markdown
Author

Note on the failing lint check:

After the chore: refresh uv.lock commit, the uv lock --check step now passes, but the next step (black --check --exclude '/enterprise/' .) fails on 331 pre-existing files in litellm/ that haven't been formatted with the pinned black==24.10.0 (they're stuck on an older style). None of these files are touched by this PR — only litellm/llms/anthropic/chat/transformation.py is, and within it only lines 1534-1545 (the effort == "max" guard).

Concretely, black --diff against my changed file shows reformat hunks at lines 1015, 1122, 1198, 1224, 1573, 1869 — all in code that predates this PR and is unchanged here.

The reason previous PRs against litellm_internal_staging haven't surfaced this is that the lint workflow was already failing earlier, on uv lock --check, and exiting before reaching the black step (e.g. run #24567699793 exits at the lockfile step). Now that I've refreshed the lockfile, the workflow proceeds and exposes the pre-existing black drift.

Happy to either:

  1. Leave it as-is and let the maintainer merge with the lint job overridden (since the failure isn't from this PR), or
  2. Send a separate chore(format): apply black 24.10.0 across litellm/ PR (~331 files, mechanical, no logic changes) before this one.

Let me know which you'd prefer. The actual bugfix in this PR is small and self-contained, and unit-test, core-utils, all proxy jobs, Vertex, etc. are passing.

# Opus 4.6 and 4.7, so we keep both substring checks here for
# date-variant tolerance (e.g. claude-opus-4-7-20260408). See #25957.
if effort == "max" and not (
self._is_opus_4_6_model(model) or self._is_opus_4_7_model(model)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shang309073819

Copy link
Copy Markdown
Author

Thanks both — addressed in 532c47a.

@greptile-apps (P2): swapped the hardcoded _is_opus_4_6_model OR _is_opus_4_7_model substring guard for the same data-driven _supports_effort_level(model, "max") pattern the xhigh branch one line below already uses. Adding support for a new model is now a pure model-map change.

@congqiao: good catch — confirmed against Anthropic's effort docs1 that max is supported on Opus 4.6, Opus 4.7, and Sonnet 4.6. Added supports_max_reasoning_effort: true to:

  • the 10 Opus 4.7 entries (anthropic / bedrock_converse {global,us,eu,au} / azure_ai / vertex_ai)
  • the 9 Sonnet 4.6 entries (same providers)

Plumbed supports_max_reasoning_effort through ModelInfoBase / ModelInfo so get_model_info() surfaces it (mirrors the existing xhigh flag).

Tests:

  • flipped test_max_effort_rejected_for_sonnet_46test_max_effort_accepted_for_sonnet_46
  • updated opus-4.5 rejection test to match the new error message (effort='max' is not supported by this model)
  • repointed the opus-4.6 acceptance test at a SKU that exists in the map (claude-opus-4-6-20260205)

All 137 tests in test_anthropic_chat_transformation.py pass locally.

Footnotes

  1. https://platform.claude.com/docs/en/build-with-claude/effort — "The effort parameter is supported by ... Claude Opus 4.7, Claude Opus 4.6, and Claude Sonnet 4.6."

@shang309073819

Copy link
Copy Markdown
Author

Rebased onto latest litellm_internal_staging to clear the uv.lock conflict from PRs #25872 / #25873 (yj_bump_apr16_2 + extras bump). The conflict was purely metadata in uv.lock (literally one line — litellm == 1.83.9 arrived from upstream while we had 1.83.7 pinned earlier). No code-level conflicts in any of the changed files.

Rebase summary:

  • Force-pushed fix/effort-max-opus-4-7 (now at fa541915) onto current upstream tip f69b9d65.
  • All 4 commits preserved (fix → uv.lock refresh → data-driven refactor → LITELLM_LOCAL_MODEL_COST_MAP test isolation).
  • All 137 tests in tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py still pass locally (LITELLM_LOCAL_MODEL_COST_MAP=True).
  • The 27 effort/max-related tests pass independently.

Re: the lint check — same situation as before (the black 24.10.0 drift on 331 pre-existing files surfaces because the uv.lock step now passes). Happy to file a separate chore(format) PR if you'd prefer that ordering. The actual fix in this PR is unchanged and self-contained.

mergeable should now flip to MERGEABLE. Ready for review whenever you have a moment 🙏

Anthropic's Messages API accepts `output_config.effort="max"` on
`claude-opus-4-7-*` models, but `_apply_output_config` hardcoded the
gate to `_is_opus_4_6_model`, so any request hitting LiteLLM with
Opus 4.7 + effort=max was rejected with a 400 before reaching Anthropic.

This was preserved on purpose in BerriAI#25867 (day-0 4.7 PR) under the old
assumption that `max` was Opus-4.6-only. The Anthropic API has since
extended `max` support to Opus 4.7.

Allow the effort=max guard to also accept Opus 4.7. Update the two
existing rejection tests to match the new error message and add a new
regression test for Opus 4.7.

Fixes BerriAI#25957
Address review feedback on BerriAI#25958:

1. @greptile-apps (P2): replace hardcoded `_is_opus_4_6_model OR
   _is_opus_4_7_model` substring check with the same data-driven
   `_supports_effort_level(model, 'max')` pattern already used for
   `xhigh` one line below. Adding support for a new model is now a
   pure model-map change.

2. @congqiao: per Anthropic's effort docs[1], `max` is supported by
   Claude Opus 4.6, Opus 4.7, AND Sonnet 4.6 — the previous patch
   only enabled 4.7. This commit also enables max on Sonnet 4.6.

Changes:
* litellm/llms/anthropic/chat/transformation.py
  - swap substring guard for `_supports_effort_level(model, 'max')`
* model_prices_and_context_window.json (+ backup)
  - add `supports_max_reasoning_effort: true` to the 10 Opus 4.7
    entries (anthropic / bedrock_converse / azure_ai / vertex_ai)
  - add `supports_max_reasoning_effort: true` to the 9 Sonnet 4.6
    entries (anthropic / bedrock_converse / azure_ai / vertex_ai)
* litellm/types/utils.py + litellm/utils.py
  - add `supports_max_reasoning_effort` to ModelInfoBase / ModelInfo
    so `get_model_info()` surfaces the new flag (mirrors the
    existing xhigh handling)
* tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py
  - flip `test_max_effort_rejected_for_sonnet_46` to
    `test_max_effort_accepted_for_sonnet_46`
  - update opus-4.5 rejection test to match new error message
  - point the opus-4.6 acceptance test at a SKU that exists in the
    model map (claude-opus-4-6-20260205)

[1] https://platform.claude.com/docs/en/build-with-claude/effort
    "The effort parameter is supported by ... Claude Opus 4.7,
    Claude Opus 4.6, and Claude Sonnet 4.6."
@shang309073819
shang309073819 force-pushed the fix/effort-max-opus-4-7 branch from fa54191 to cb38100 Compare April 19, 2026 12:44
@shang309073819

Copy link
Copy Markdown
Author

Rebased onto latest litellm_internal_staging (2f22a1293ebump litellm-proxy-extras to 0.4.67) to clear the merge conflict from the 1.83.9 → 1.83.10 bump (PR #26043 / yj_bump_apr18).

Rebase summary:

  • Force-pushed fix/effort-max-opus-4-7 (now at cb38100ea3) onto current upstream tip.
  • The standalone chore: refresh uv.lock for litellm 1.83.9 / extras 0.4.66 commit was dropped during rebase (auto-skipped) since upstream already carries an equivalent or newer uv.lock for 1.83.10 / extras 0.4.67. The 3 substantive commits are preserved:
    • de196e4c41 — fix: allow effort='max' on Claude Opus 4.7
    • 364a556062 — refactor: make effort='max' data-driven via model map
    • cb38100ea3 — test: force LITELLM_LOCAL_MODEL_COST_MAP for new max-effort tests
  • All 137 tests in tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py still pass locally with LITELLM_LOCAL_MODEL_COST_MAP=True.

Re: the previous All Other Providers / Run tests failure on the prior push — that was caused by a leftover Git merge-conflict marker (<<<<<<< worktree-rustling-wishing-kite) at tests/test_litellm/llms/bedrock/test_bedrock_common_utils.py:37 that landed on litellm_internal_staging independently of this PR. It is already cleaned up on the current upstream tip, so this run should pass.

Re: the lint check — same pre-existing situation as before: uv lock --check is now satisfied (we inherit upstream's lock), and black --check will continue to flag the same ~331 pre-existing files in litellm/ that this PR does not touch. Happy to send a separate chore(format): apply black 24.10.0 across litellm/ PR if a maintainer prefers that route.

PR is otherwise green: unit-test, core-utils, all proxy-*, vertex-ai, mcp-secrets-misc, responses-caching-types, Greptile, Veria, Codecov, GitGuardian, CLA — all passing.

@krrishdholakia @ishaan-jaff — could one of you take a look when you have a moment? The bug is small (just lets effort="max" through for Opus 4.7, which the Anthropic API accepts) and the fix is fully data-driven now. Thanks!

…t tests

CI fetches model_prices_and_context_window.json from `main` at runtime,
so the new `supports_max_reasoning_effort` flags added in this PR are
not yet visible to the live CI run. Mirror the established pattern from
`tests/litellm_utils_tests/test_utils.py::test_supports_reasoning`:
set LITELLM_LOCAL_MODEL_COST_MAP=True and reload `litellm.model_cost`
inside each new accept-test, so they read the model-map version that is
in this same PR.

Affected tests:
* test_max_effort_accepted_for_opus_46
* test_max_effort_accepted_for_opus_47
* test_max_effort_accepted_for_sonnet_46

Locally verified: 137 / 137 pass in test_anthropic_chat_transformation.py
@shang309073819
shang309073819 force-pushed the fix/effort-max-opus-4-7 branch from cb38100 to 1625adc Compare April 20, 2026 05:03
@codgician

Copy link
Copy Markdown
Contributor

Any updates on this PR?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the stale label Jul 25, 2026
@github-actions github-actions Bot closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: effort="max" hardcoded as Opus 4.6 only — Opus 4.7 rejected even though Anthropic API accepts it

4 participants