Skip to content

fix(databricks): translate reasoning_effort to thinking for Gemini 2.5 - #34001

Closed
devin-ai-integration[bot] wants to merge 14 commits into
litellm_internal_stagingfrom
devin/1784568628-databricks-gemini-reasoning-effort
Closed

devin-ai-integration[bot] wants to merge 14 commits into
litellm_internal_stagingfrom
devin/1784568628-databricks-gemini-reasoning-effort

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • reasoning_effort on a Databricks Gemini 2.5 model came back as a 400 from Databricks
  • The gateway only translated it to thinking when the model name contained claude
  • drop_params did not help, since Databricks reports reasoning_effort as supported everywhere

How it solves it:

  • A cost-map flag, supports_anthropic_thinking_payload, marks which Databricks models take the Anthropic thinking shape
  • Every Databricks Claude entry and both Gemini 2.5 entries carry the flag, so all of them get translated
  • Gemini 3, GPT-5 and GPT-OSS keep passing reasoning_effort through untouched, as Databricks documents
  • A Claude serving endpoint missing from the cost map still translates by name, as before

User Flow

Before: a developer calling a Databricks-hosted Gemini 2.5 model through the gateway with reasoning_effort gets a 400 back, while the same request against a Databricks Claude model works

  1. They send POST https://litellm-domain/v1/chat/completions with "model": "databricks-gemini-2-5-flash", a user message, and "reasoning_effort": "low"
  2. The response is 400 with Invalid JSON payload received. Unknown name "reasoning_effort" at 'generation_config': Cannot find field.
  3. They retry without reasoning_effort and get a 200, so they cannot control thinking on that model through the gateway

After: the same request returns 200 and the model reasons at the requested effort

  1. They send the same POST https://litellm-domain/v1/chat/completions with "model": "databricks-gemini-2-5-flash", a user message, and "reasoning_effort": "low"
  2. The response is 200 with the model's answer
  3. The same request against Databricks Claude, Gemini 3, GPT-5 and GPT-OSS models keeps returning 200 as before

Relevant issues

Reopened copy of #32786 with the merge conflicts against litellm_internal_staging resolved. The fix and its tests come from #32786 (head 2bff743); its author could not carry it further, so this copy takes it to merge

Linear ticket

Resolves LIT-7665

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Shared setup for both legs. One proxy per leg runs from a worktree checked out at that leg's commit, with the config below, a random free port, --detailed_debug so the payload the proxy sends to Databricks shows up in the log as a -d '{...}' line, and LITELLM_LOCAL_MODEL_COST_MAP=True so the cost map on the checkout is the one loaded. The Databricks workspace used here serves Claude and GPT-OSS but has no Gemini serving endpoint, so the Gemini 2.5 cases show the payload the proxy sends upstream (the part Databricks rejects with the 400) and the workspace answers 404 on both legs; the Claude and GPT-OSS cases are full round trips against the real endpoints

model_list:
  - model_name: databricks-gemini-2-5-flash
    litellm_params:
      model: databricks/databricks-gemini-2-5-flash
      api_key: os.environ/DATABRICKS_API_KEY
      api_base: os.environ/DATABRICKS_API_BASE
  - model_name: databricks-claude-sonnet-4-5
    litellm_params:
      model: databricks/databricks-claude-sonnet-4-5
      api_key: os.environ/DATABRICKS_API_KEY
      api_base: os.environ/DATABRICKS_API_BASE
  - model_name: databricks-claude-opus-4-7
    litellm_params:
      model: databricks/databricks-claude-opus-4-7
      api_key: os.environ/DATABRICKS_API_KEY
      api_base: os.environ/DATABRICKS_API_BASE
  - model_name: databricks-gpt-oss-120b
    litellm_params:
      model: databricks/databricks-gpt-oss-120b
      api_key: os.environ/DATABRICKS_API_KEY
      api_base: os.environ/DATABRICKS_API_BASE

general_settings:
  master_key: sk-1234
LITELLM_LOCAL_MODEL_COST_MAP=True python litellm/proxy/proxy_cli.py --config proxy_config.yaml --port $PORT --detailed_debug 2>&1 | tee litellm.log

The same request loop runs against each leg, followed by the grep that pulls the upstream payloads out of the log:

for pair in databricks-gemini-2-5-flash:low databricks-claude-sonnet-4-5:low databricks-claude-opus-4-7:high databricks-gpt-oss-120b:low; do
  m=${pair%%:*}; e=${pair##*:}
  echo "=== $m reasoning_effort=$e ==="
  curl -s -w '\nHTTP %{http_code}\n' "http://localhost:$PORT/v1/chat/completions" \
    -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
    -d "{\"model\":\"$m\",\"messages\":[{\"role\":\"user\",\"content\":\"Why is the sky blue? Answer in one sentence.\"}],\"reasoning_effort\":\"$e\"}"
done
for m in databricks-gemini-2-5-flash databricks-claude-sonnet-4-5; do
  echo "=== /v1/responses $m reasoning.effort=low ==="
  curl -s -w '\nHTTP %{http_code}\n' "http://localhost:$PORT/v1/responses" \
    -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
    -d "{\"model\":\"$m\",\"input\":\"Why is the sky blue? Answer in one sentence.\",\"reasoning\":{\"effort\":\"low\"}}"
done
grep "^-d '{" litellm.log

Before (559247f)

Gemini 2.5 Flash, /v1/chat/completions, reasoning_effort low

  1. Send the request from the loop above with "model": "databricks-gemini-2-5-flash" and "reasoning_effort": "low"
  2. Response: HTTP 404 from this workspace, litellm.NotFoundError: DatabricksException - {"error_code":"ENDPOINT_NOT_FOUND", ...}
  3. Upstream payload from the log, with reasoning_effort sent as is, which is what a workspace that serves the model rejects with Unknown name "reasoning_effort" at 'generation_config':
-d '{'model': 'databricks-gemini-2-5-flash', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'reasoning_effort': 'low'}'

Claude Sonnet 4.5, /v1/chat/completions, reasoning_effort low

  1. Send the request with "model": "databricks-claude-sonnet-4-5" and "reasoning_effort": "low"
  2. Response: HTTP 200, "content": "The sky is blue because molecules in Earth's atmosphere scatter short-wavelength blue light ...", with "reasoning_content" filled in
  3. Upstream payload from the log:
-d '{'model': 'databricks-claude-sonnet-4-5', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, 'max_tokens': 5120}'

Claude Opus 4.7, /v1/chat/completions, reasoning_effort high

  1. Send the request with "model": "databricks-claude-opus-4-7" and "reasoning_effort": "high"
  2. Response: HTTP 200, "content": "The sky appears blue because molecules in Earth's atmosphere scatter shorter wavelengths of sunlight (blue) ..."
  3. Upstream payload from the log:
-d '{'model': 'databricks-claude-opus-4-7', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'thinking': {'type': 'adaptive', 'display': 'summarized'}, 'output_config': {'effort': 'high'}}'

GPT-OSS 120B, /v1/chat/completions, reasoning_effort low

  1. Send the request with "model": "databricks-gpt-oss-120b" and "reasoning_effort": "low"
  2. Response: HTTP 200, "content": "The sky appears blue because molecules in Earth's atmosphere scatter shorter (blue) wavelengths ...", "reasoning_content": "Provide concise answer."
  3. Upstream payload from the log:
-d '{'model': 'databricks-gpt-oss-120b', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'reasoning_effort': 'low'}'

Gemini 2.5 Flash, /v1/responses, reasoning.effort low

  1. Send the request with "model": "databricks-gemini-2-5-flash" and "reasoning": {"effort": "low"}
  2. Response: HTTP 404 from this workspace, same ENDPOINT_NOT_FOUND body
  3. Upstream payload from the log, again carrying reasoning_effort:
-d '{'model': 'databricks-gemini-2-5-flash', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'reasoning_effort': 'low'}'

Claude Sonnet 4.5, /v1/responses, reasoning.effort low

  1. Send the request with "model": "databricks-claude-sonnet-4-5" and "reasoning": {"effort": "low"}
  2. Response: HTTP 200, a resp_... object with the answer
  3. Upstream payload from the log: 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, 'max_tokens': 5120

After (c7b607c)

Gemini 2.5 Flash, /v1/chat/completions, reasoning_effort low

  1. Send the same request with "model": "databricks-gemini-2-5-flash" and "reasoning_effort": "low"
  2. Response: HTTP 404 from this workspace, same ENDPOINT_NOT_FOUND body, since it has no Gemini endpoint
  3. Upstream payload from the log now carries thinking and no reasoning_effort, the shape Databricks accepts for Gemini 2.5:
-d '{'model': 'databricks-gemini-2-5-flash', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, 'max_tokens': 5120}'

Claude Sonnet 4.5, /v1/chat/completions, reasoning_effort low

  1. Send the same request with "model": "databricks-claude-sonnet-4-5" and "reasoning_effort": "low"
  2. Response: HTTP 200, "content": "The sky appears blue because molecules in Earth's atmosphere scatter shorter blue wavelengths ...", with "reasoning_content" filled in
  3. Upstream payload from the log, byte for byte the Before payload:
-d '{'model': 'databricks-claude-sonnet-4-5', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, 'max_tokens': 5120}'

Claude Opus 4.7, /v1/chat/completions, reasoning_effort high

  1. Send the same request with "model": "databricks-claude-opus-4-7" and "reasoning_effort": "high"
  2. Response: HTTP 200, "content": "The sky appears blue because molecules in Earth's atmosphere scatter shorter blue wavelengths of sunlight more strongly ..."
  3. Upstream payload from the log, byte for byte the Before payload, so the adaptive thinking path for the newer Claude models is unchanged:
-d '{'model': 'databricks-claude-opus-4-7', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'thinking': {'type': 'adaptive', 'display': 'summarized'}, 'output_config': {'effort': 'high'}}'

GPT-OSS 120B, /v1/chat/completions, reasoning_effort low

  1. Send the same request with "model": "databricks-gpt-oss-120b" and "reasoning_effort": "low"
  2. Response: HTTP 200, "content": "The sky appears blue because molecules in Earth's atmosphere scatter shorter (blue) wavelengths ...", "reasoning_content": "Provide concise answer."
  3. Upstream payload from the log, byte for byte the Before payload, so reasoning_effort still passes through for the models that take it natively:
-d '{'model': 'databricks-gpt-oss-120b', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'stream': False, 'reasoning_effort': 'low'}'

Gemini 2.5 Flash, /v1/responses, reasoning.effort low

  1. Send the same request with "model": "databricks-gemini-2-5-flash" and "reasoning": {"effort": "low"}
  2. Response: HTTP 404 from this workspace, same ENDPOINT_NOT_FOUND body
  3. Upstream payload from the log now carries thinking instead of reasoning_effort, so the Responses API route gets the same fix:
-d '{'model': 'databricks-gemini-2-5-flash', 'messages': [{'role': 'user', 'content': 'Why is the sky blue? Answer in one sentence.'}], 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, 'max_tokens': 5120}'

Claude Sonnet 4.5, /v1/responses, reasoning.effort low

  1. Send the same request with "model": "databricks-claude-sonnet-4-5" and "reasoning": {"effort": "low"}
  2. Response: HTTP 200, a resp_... object with the answer
  3. Upstream payload from the log, byte for byte the Before payload: 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, 'max_tokens': 5120

Type

🐛 Bug Fix

Caveats (if any)

Low

  • A Databricks model that is neither flagged in the cost map nor named claude still passes reasoning_effort through unchanged. That is the documented behavior for Gemini 3, GPT-5 and GPT-OSS, and it means a future Anthropic-shaped model on Databricks needs its cost map entry flagged before the translation kicks in
  • The Gemini 2.5 leg is verified by the payload the proxy sends upstream, since the QA workspace has no Gemini serving endpoint; the 400 in the User Flow is the one the customer hit on a workspace that serves the model
  • The thinking tokens guard from fix(databricks): translate reasoning_effort to thinking for Gemini 2.5 #32786 already landed on the base branch on its own, so it is not part of this diff

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/aacc049712434d50888cf457809a50b1

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a 400 error from Databricks when reasoning_effort is sent to Gemini 2.5 models by extending the reasoning_effort → Anthropic thinking translation beyond Claude to include Gemini 2.5. The gate was a hardcoded "claude" in model string check; it is now backed by a supports_anthropic_thinking_payload flag in the model cost map, looked up via the standard supports_* factory pattern.

  • Introduces _uses_anthropic_thinking_param(model) on DatabricksConfig that combines the existing Claude name-guard (for unmapped serving endpoints) with a model-cost-map lookup via the new supports_anthropic_thinking_payload helper, and updates the reasoning_effort translation gate to call it.
  • Adds "supports_anthropic_thinking_payload": true to all Databricks Claude entries and both Gemini 2.5 entries (Flash and Pro) in model_prices_and_context_window.json; Gemini 3+, GPT-5, and GPT-OSS are intentionally omitted so they continue passing reasoning_effort through unchanged.
  • Adds 12 new mock-only tests covering the full model-family matrix, all effort levels, dot-notation normalization, and reasoning_effort="none" behaviour.

Confidence Score: 5/5

  • Safe to merge — the change is narrowly scoped to the Databricks reasoning_effort translation path and adds no new risk to existing Claude or pass-through model families.
  • The fix is a straightforward extension of an existing, well-tested translation pattern. The new _uses_anthropic_thinking_param helper correctly combines the Claude name-guard fallback with a model-cost-map lookup, models that should pass reasoning_effort through unchanged are not flagged in the JSON, and the 12 new unit tests cover all intended model families and edge cases without making real network calls.
  • No files require special attention.

Important Files Changed

Filename Overview
litellm/llms/databricks/chat/transformation.py Adds _uses_anthropic_thinking_param static method that combines the existing "claude" in model name guard with a model-cost-map lookup via supports_anthropic_thinking_payload, then uses it to gate the reasoning_effort → thinking translation. Logic is correct and consistent with the established pattern.
litellm/utils.py Adds supports_anthropic_thinking_payload helper that delegates to _supports_factory (same pattern as all other supports_* functions), and wires the new JSON key into _get_model_info_helper. No issues.
litellm/types/utils.py Adds `supports_anthropic_thinking_payload: ReadOnly[bool
model_prices_and_context_window.json Adds "supports_anthropic_thinking_payload": true to all Databricks Claude entries (maintaining backward compat) and to the two Databricks Gemini 2.5 entries (Flash and Pro). Gemini 3+, GPT-5, and GPT-OSS intentionally omitted so they continue to pass reasoning_effort through unchanged.
model_prices_and_context_window.schema.json Adds supports_anthropic_thinking_payload as a boolean property to the JSON schema definition, consistent with the other supports_* capability flags.
tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py Adds 12 new mock-only tests covering: Claude translation (mapped + unmapped endpoint), adaptive-Claude output_config path, Gemini 2.5 Flash/Pro across all effort levels, dot-notation normalization, Gemini 2.0 pass-through, reasoning_effort="none" drop, and Gemini 3/GPT-5/GPT-OSS pass-through. Fixture scoped to autouse=False per prior review feedback.
tests/test_litellm/test_utils.py Adds "supports_anthropic_thinking_payload" to the local schema in the JSON-validation test, keeping it in sync with model_prices_and_context_window.schema.json.

Reviews (5): Last reviewed commit: "fix(databricks): keep the Claude fallbac..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py Outdated
Comment thread litellm/llms/databricks/chat/transformation.py
…on schema

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@codspeed

codspeed Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing devin/1784568628-databricks-gemini-reasoning-effort (c7b607c) with litellm_internal_staging (a73454b)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (559247f) during the generation of this report, so a73454b was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

…ture

Addresses Greptile P2 feedback: reject an adaptive thinking payload for non-Claude Databricks models instead of silently emitting a shape the Gemini endpoint can't parse, and scope the local-cost-map fixture to the tests that need it rather than autouse.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai - review

…i-reasoning-effort

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai - review

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team August 23, 2026 21:16
…us/sonnet 4-6

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Added the missing flag to databricks-claude-opus-4-6 and databricks-claude-sonnet-4-6 in both JSON files. @greptileai

milan-berri and others added 3 commits August 28, 2026 21:48
…i-reasoning-effort

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…hinking payload

Gate the reasoning_effort translation on the cost-map flag or the model name containing
claude, so unmapped Claude serving endpoints keep translating. Flag the newer Claude
entries that were missing it. Expose supports_anthropic_thinking_payload as a public
helper next to the other supports_* wrappers instead of importing the private factory.
Drop the adaptive-only guard, since the adaptive flags only ever match Claude ids, and
add regression tests for an unmapped Claude endpoint and an adaptive Claude model
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

❌ Cannot revive Devin session - the session is too old. Please start a new session instead.

View session

@mateo-berri

Copy link
Copy Markdown
Contributor

Superseded by #40909, the same commits and tip c7b607c on a litellm_ branch so CircleCI runs the legacy suites

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants