Skip to content

[Fix] Request Timeout needs to be also fetched from litellm_settings.request_timeout - #25591

Merged
ishaan-berri merged 11 commits into
BerriAI:litellm_ishaan_april15_2from
harish876:azure-anthropic-timeout-bug
Apr 16, 2026
Merged

[Fix] Request Timeout needs to be also fetched from litellm_settings.request_timeout#25591
ishaan-berri merged 11 commits into
BerriAI:litellm_ishaan_april15_2from
harish876:azure-anthropic-timeout-bug

Conversation

@harish876

@harish876 harish876 commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Pre-Submission checklist

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

  • [ X] I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

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

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

Type

🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test

Changes

litellm/main.py

  • Added _resolve_completion_timeout() with an explicit None chain (avoids or/truthiness bugs):

    1. Named timeout argument
    2. kwargs["timeout"]
    3. kwargs["request_timeout"] (model / deployment alias)
    4. getattr(litellm, "request_timeout", None) — picks up litellm_settings.request_timeout after proxy config load
    5. Default 600 seconds
  • Preserved httpx.Timeout behavior: unchanged for providers where supports_httpx_timeout is true; otherwise coerce using read timeout (or 600.0).

  • completion() sets timeout = _resolve_completion_timeout(...) so all provider branches (including azure_aiazure_anthropic_chat_completions.completion) see the same resolved value.

  • Docstring on the helper describes sources: deployment/model config vs litellm_settings.request_timeout.

@vercel

vercel Bot commented Apr 12, 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 14, 2026 11:27pm

Request Review

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codspeed-hq

codspeed-hq Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing harish876:azure-anthropic-timeout-bug (9ae30ed) with main (7b36cfc)

Open in CodSpeed

@codecov

codecov Bot commented Apr 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

add fetch ``request_timeout`` from litellm_settings
@harish876
harish876 marked this pull request as ready for review April 13, 2026 23:44
@greptile-apps

greptile-apps Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a CompletionTimeout helper class (litellm_core_utils/completion_timeout.py) to properly resolve completion() timeouts from all sources — per-call, per-model, and litellm_settings.request_timeout (via litellm.request_timeout). It uses a sentinel approach: the package-default 6000.0 value is treated as "not explicitly set" for the completion path and coerced to 600.0, while other values pass through unchanged.

The http_handler.py also consolidates the previously scattered hardcoded 600.0 / 5.0 literals into the new COMPLETION_HTTP_FALLBACK_SECONDS and HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS constants.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/improvement suggestions that do not block functionality.

Previous thread concerns (print statement, sentinel applied to per-call timeouts, constants-change widening scope) are all resolved. The core fix — reading litellm.request_timeout via getattr and coercing the package default sentinel to 600 s — is correct and well-tested for non-6000 values. Remaining P2 items are minor and do not block merge.

litellm/litellm_core_utils/completion_timeout.py — sentinel edge case for global_timeout==6000; litellm/llms/custom_httpx/http_handler.py — _DEFAULT_TIMEOUT widened from 5s to 600s

Important Files Changed

Filename Overview
litellm/litellm_core_utils/completion_timeout.py New helper class correctly resolves timeout from model params → kwargs → global litellm.request_timeout fallback; sentinel logic is confined to the fallback path, not applied to explicit per-call values.
litellm/constants.py Adds COMPLETION_HTTP_FALLBACK_SECONDS (600s) and HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS (5s) constants; DEFAULT_REQUEST_TIMEOUT_SECONDS remains 6000s to avoid widening scope to Router/TTS paths.
litellm/main.py Wires CompletionTimeout.resolve() into the completion() dispatch path; global_timeout reads litellm.request_timeout so proxy config changes propagate correctly.
litellm/llms/custom_httpx/http_handler.py _DEFAULT_TIMEOUT changed from httpx.Timeout(5.0, connect=5.0) to httpx.Timeout(600.0, connect=5.0) — factory functions were already using 600s explicitly, but direct instantiation of HTTPHandler()/AsyncHTTPHandler() with no timeout now gets 600s instead of 5s.
tests/test_litellm/test_completion_timeout_resolution.py Good unit coverage for CompletionTimeout.resolve() — explicit, kwargs, global sentinel, httpx coercion paths all tested; no real network calls.
tests/test_litellm/llms/azure_ai/claude/test_main_azure_anthropic_timeout.py Tests that main.py correctly forwards explicit timeout=42.5 to the Azure Anthropic handler; no real network calls.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["completion(timeout=T, **kwargs)"] --> B{T is not None?}
    B -- Yes --> E[resolved = T]
    B -- No --> C{kwargs timeout?}
    C -- kwargs.timeout --> E
    C -- kwargs.request_timeout --> E
    C -- Neither --> D["_fallback_when_no_explicit_timeout(litellm.request_timeout)"]
    D --> F{global_timeout is None or == 6000.0?}
    F -- Yes --> G["resolved = 600.0 (COMPLETION_HTTP_FALLBACK_SECONDS)"]
    F -- No --> H["resolved = float(global_timeout)"]
    E --> I{isinstance httpx.Timeout and provider supports it?}
    G --> I
    H --> I
    I -- Yes --> J[Return httpx.Timeout as-is]
    I -- No, httpx.Timeout --> K["resolved = read_timeout or 600.0"]
    I -- float/str --> L["resolved = float(resolved)"]
    K --> M[Return float]
    L --> M
Loading

Reviews (9): Last reviewed commit: "modify default timeout values, replacing..." | Re-trigger Greptile

Comment thread litellm/main.py Outdated
Comment thread litellm/main.py Outdated
@harish876 harish876 changed the title [Test] Add Azure async chat completion timeout test. WIP [Fix] Add Azure async chat completion timeout test. Apr 14, 2026
@harish876 harish876 changed the title [Fix] Add Azure async chat completion timeout test. [Fix] Request Timeout needs to be also fetched from litellm_settings.request_timeout Apr 14, 2026
Comment thread litellm/constants.py
@@ -393,7 +393,7 @@
)

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.

P1 request_timeout default change widens scope beyond completion()

Changing the default from 6000 to 600 fixes the completion() regression flagged in the previous thread, but litellm.request_timeout is also the default for Router.__init__ (self.timeout = timeout or litellm.request_timeout, router.py:530), speech() (main.py:6760), and the Anthropic / Azure-Anthropic / OpenAI count-token handlers. All of these would silently drop from a 6000 s ceiling to 600 s for users who have not set an explicit timeout. Long-running router calls or TTS jobs that complete in 600–6000 s will now time out.

Per the "avoid backwards-incompatible changes without user-controlled flags" rule, consider keeping the constant at 6000 (or introducing a separate COMPLETION_REQUEST_TIMEOUT constant) and only using the explicit-600 fallback inside _resolve_completion_timeout() itself, where you control the scope.

Rule Used: What: avoid backwards-incompatible changes without... (source)

@ishaan-berri ishaan-berri left a comment

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.

what problem does this solve ?

Comment thread litellm/main.py Outdated
return entry


def _resolve_completion_timeout(

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.

this should be it's own file + class

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently, we have 2 issues related to timeout. The timeout parameters can be defined either globally under litellm_settings.request_timeout or at a per model level. In the completion route this litellm_settings.request_timeout config is never read, and hence defaults to a 600s timeout. This doesnt solve the root cause, but a potential fix.

@ishaan-berri ishaan-berri left a comment

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.

  • this just solves it for /chat/completions what about /responses, /messages do they have the same bug ?
  • IS there a simpler way to fix this across call types

Comment thread litellm/main.py Outdated
resolved_from_litellm_request_timeout_attr = True
if timeout is None:
timeout = 600
elif (

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.

this section looks super complicated, can you write cleaner code here ?

it's hard to follow how this logic is set

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. The reason for this logic is due to the fact that the fallback/default value for litellm.request_timeout is different than the 600s. To avoid this, this logic is mangled, but agreed to the point that this code looks complex. Working on this to make it readable.

@gitguardian

gitguardian Bot commented Apr 14, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
29203053 Triggered Generic Password fdccbcb .circleci/config.yml View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@harish876

Copy link
Copy Markdown
Contributor Author
  • this just solves it for /chat/completions what about /responses, /messages do they have the same bug ?
  • IS there a simpler way to fix this across call types
  • If the fix at completions solves the issue being looked at, then the same helper can be used across multiple handlers.
  • I have not looked at this from the scope of multiple call types. Identified the bug while debugging timeout issues across a subset of providers.

Comment on lines +51 to +54
if not isinstance(resolved, httpx.Timeout) and float(resolved) == float(
DEFAULT_REQUEST_TIMEOUT_SECONDS
):
resolved = 600.0

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.

P1 Sentinel check silently truncates an explicit timeout=6000 from the caller

The sentinel float(resolved) == float(DEFAULT_REQUEST_TIMEOUT_SECONDS) runs over all resolved values, including those taken from model_timeout (the value the caller passed directly to completion()). A user who calls completion(..., timeout=6000) or sets request_timeout: 6000 in their model config will silently receive 600 s instead — a 10× reduction. The old or-based chain did not apply any such cap, so this is a backwards-incompatible regression for anyone who deliberately configured a long timeout.

The guard should only trigger when the value originated from the package default, not from an explicit caller-supplied timeout:

# Only coerce the sentinel when no per-call or per-model timeout was set
def _is_global_sentinel(value: float) -> bool:
    return float(value) == float(DEFAULT_REQUEST_TIMEOUT_SECONDS)

if model_timeout is not None:
    resolved = model_timeout
elif kwargs.get("timeout") is not None:
    resolved = kwargs["timeout"]
elif kwargs.get("request_timeout") is not None:
    resolved = kwargs["request_timeout"]
else:
    # Only here can we safely coerce the sentinel
    raw = global_timeout if global_timeout is not None else 600
    resolved = 600.0 if (not isinstance(raw, httpx.Timeout) and _is_global_sentinel(float(raw))) else raw

With this structure the coercion never reaches values the caller set explicitly.

Rule Used: What: avoid backwards-incompatible changes without... (source)

elif kwargs.get("request_timeout") is not None:
resolved = kwargs["request_timeout"]
else:
resolved = global_timeout if global_timeout is not None else 600

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.

this looks odd, can you write this cleanly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sure, will look into this again

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Logic for this is cleaned up

elif kwargs.get("request_timeout") is not None:
resolved = kwargs["request_timeout"]
else:
resolved = global_timeout if global_timeout is not None else 600

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.

600, should be a constant

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.

why is it not DEFAULT_REQUEST_TIMEOUT_SECONDS ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

if litellm_settings.request_timeout is unset, then it falls back to DEFAULT_REQUEST_TIMEOUT_SECONDS. The default for chat completion is 600, since thats the default for openai as well. So specifically the default for chat completion is 600.

Agreed, will make it into a const. We did not define default for chat completion in constants before, we can do it now. Will fix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is fixed

…eslve function readable by moving fallback logic to a seperate function
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