Skip to content

fix(anthropic): clear error for non-streaming timeout guard; add first-class timeout param - #1255

Merged
tbille merged 5 commits into
mainfrom
fix/anthropic-timeout-handling
Aug 11, 2026
Merged

tbille merged 5 commits into
mainfrom
fix/anthropic-timeout-handling

Conversation

@peteski22

@peteski22 peteski22 commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes the opaque failure reported in #1251: a non-streaming Anthropic completion with a large max_tokens (roughly > 128000/6 ≈ 21,333) raises a bare ValueError: Streaming is required for operations that may take longer than 10 minutes. from the SDK's client-side pre-flight check, before any request is sent, even when the actual response is tiny.

While investigating I confirmed that a timeout already reaches the Anthropic SDK today (via completion kwargs → messages.create(timeout=...) and via client_args={"timeout": ...} → AsyncAnthropic(timeout=...)), and either one already lifts the guard. So the real gaps were (a) timeout was not a first-class, documented, typed parameter, and (b) when no timeout is set the caller hits the SDK's opaque error with no hint at the fix. This PR closes both:

  • First-class timeout parameter on completion()/acompletion() and AnyLLM.completion/acompletion, mirroring how prompt_cache_key is surfaced. It is forwarded to the provider through kwargs only when set, rather than being added to CompletionParams. Cross-provider handling: because it rides kwargs to every provider, those whose SDK accepts a timeout keyword (OpenAI-based, groq, cerebras, together, anthropic) honor it directly; Gemini (timeout kwarg rejected by Gemini provider — should be a first-class parameter #901)/Bedrock (Bedrock: BYO credentials aren't forwarded to boto3, no bearer-token support, and timeout kwarg breaks completions #1240) already map it; and providers whose SDK has no per-request timeout are handled explicitly so they neither TypeError nor silently mis-send it — mistral (timeout_ms), cohere (request_options), and warn+drop for xai, ollama, watsonx, sagemaker, lmstudio (huggingface already stripped it). A central, enforced version of this is tracked in Centralize per-request timeout handling across providers (opt-in support, like prompt_cache_key) #1262. An explicit timeout=None is treated the same as omitting it, so an unbounded timeout is not expressible via the typed parameter.
  • Clearer Anthropic error: the provider now catches the SDK's specific pre-flight guard and re-raises an InvalidRequestError that names the levers (Pass a timeout (in seconds) or use stream=True), while re-raising any unrelated ValueError untouched. The single point of coupling to the SDK's wording is isolated in a named constant; the SDK exposes only a bare ValueError (no error code/subtype) for this condition, so matching the (version-stable) message is the only available signal, and a real-client test would surface any future reword in CI.

PR Type

  • 🆕 New Feature
  • 🐛 Bug Fix

Relevant issues

Fixes #1251
Related: #901 (Gemini timeout), #1240 (Bedrock timeout)

Checklist

  • I understand the code I am submitting.
  • I have added unit tests that prove my fix/feature works
  • I have run this code locally and verified it fixes the issue.
  • New and existing tests pass locally
  • Documentation was updated where necessary
  • I have read and followed the contribution guidelines
  • AI Usage:
    • AI was used for drafting/refactoring.

Notes on tests

New tests are regression-verified (they fail on main, pass with the change):

  • tests/unit/test_completion.py: timeout is exposed on all four entry points; forwarded to the provider only when set; explicit None is dropped.
  • tests/unit/providers/test_anthropic_provider.py: without a timeout the large-max_tokens non-streaming path now raises a clear InvalidRequestError (driven through a real AsyncAnthropic client so the SDK guard actually runs); with a timeout it bypasses the guard and reaches transport.

pre-commit (ruff + format + mypy) is clean. The only failing tests in the full local tests/unit run are pre-existing, unrelated optional-SDK import errors (lmstudio not installed locally), identical to the main baseline.

AI Usage Information

  • AI Model used: Claude Opus 4.8

  • AI Developer Tool used: Claude Code

  • Any other info you'd like to share: Used collaboratively — AI assisted with investigation, drafting, and iteration under human review and direction (including auditing and fixing cross-provider timeout handling surfaced in review).

  • I am an AI Agent filling out this form (check box if true)

Summary by CodeRabbit

  • New Features

    • Added optional timeout controls for synchronous and asynchronous chat completions.
    • Configured timeouts are passed to supported providers, while unset values preserve existing behaviour.
  • Bug Fixes

    • Improved handling of oversized non-streaming Anthropic requests with clear guidance to set a timeout or enable streaming.
    • Unrelated provider errors continue to be reported unchanged.

@peteski22
peteski22 temporarily deployed to integration-tests August 10, 2026 08:17 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Changes

The completion APIs now accept optional request timeouts and forward set values to providers. Providers adapt timeout values to their SDKs. Anthropic translates matching non-streaming timeout guard errors into InvalidRequestError.

Completion timeout support

Layer / File(s) Summary
API timeout forwarding
src/any_llm/any_llm.py, src/any_llm/api.py, tests/unit/test_completion.py
completion and acompletion accept optional timeouts. Set values are forwarded to providers, while unset values are omitted.
Provider timeout adapters
src/any_llm/providers/cohere/cohere.py, src/any_llm/providers/mistral/mistral.py, src/any_llm/providers/xai/xai.py, tests/unit/providers/test_cohere_provider.py, tests/unit/providers/test_mistral_provider.py, tests/unit/providers/test_xai_provider.py
Cohere uses request_options, Mistral uses milliseconds, and xAI drops per-request timeouts with a warning.
Anthropic timeout guard handling
src/any_llm/providers/anthropic/base.py, tests/unit/providers/test_anthropic_provider.py
Matching Anthropic non-streaming timeout guard errors become InvalidRequestError instances with timeout and stream=True guidance. Other ValueError instances remain unchanged.

Possibly related issues

  • mozilla-ai/any-llm#1251: This PR adds the timeout propagation and Anthropic error handling described by the issue.

Possibly related PRs

  • mozilla-ai/any-llm#1116: Both PRs adapt timeout handling for provider completion calls.
  • mozilla-ai/any-llm#1247: Both PRs extend completion APIs with optional forwarded parameters. This PR adds timeout, while that PR adds prompt_cache_key.

Suggested labels: 1.24.0

Suggested reviewers: tbille

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description covers the change, issue links, change types, tests, checklist, documentation, and AI usage information.
Title check ✅ Passed The title clearly identifies the Anthropic error fix and the addition of a first-class timeout parameter.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/anthropic-timeout-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.56098% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/any_llm/providers/sagemaker/sagemaker.py 50.00% 0 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
src/any_llm/any_llm.py 80.00% <100.00%> (+1.05%) ⬆️
src/any_llm/api.py 93.85% <ø> (ø)
src/any_llm/providers/anthropic/base.py 94.98% <100.00%> (+0.30%) ⬆️
src/any_llm/providers/cohere/cohere.py 95.45% <100.00%> (+0.14%) ⬆️
src/any_llm/providers/lmstudio/lmstudio.py 90.56% <100.00%> (+0.18%) ⬆️
src/any_llm/providers/mistral/mistral.py 93.64% <100.00%> (+0.07%) ⬆️
src/any_llm/providers/ollama/ollama.py 79.26% <100.00%> (+0.25%) ⬆️
src/any_llm/providers/watsonx/watsonx.py 81.18% <100.00%> (+0.57%) ⬆️
src/any_llm/providers/xai/xai.py 71.65% <100.00%> (+0.68%) ⬆️
src/any_llm/providers/sagemaker/sagemaker.py 50.39% <50.00%> (+0.79%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/any_llm/any_llm.py (1)

580-598: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Forward timeout on the non-streaming synchronous path.

Line 598 forwards timeout only when stream is true. The call at lines 604-612 omits it. Therefore, AnyLLM.completion(..., timeout=600) silently uses the provider default for the normal non-streaming path.

Add timeout=timeout to the second self.acompletion(...) call. Add a regression test for synchronous non-streaming completion.

Proposed fix
         response = run_async_in_sync(
             self.acompletion(
                 model=model,
                 messages=messages,
                 response_format=response_format,
                 stream=stream,
                 prompt_cache_key=prompt_cache_key,
+                timeout=timeout,
                 **kwargs,
             ),

As per coding guidelines, test every new branch, including error, raise, and edge paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/any_llm/any_llm.py` around lines 580 - 598, Forward the caller’s timeout
through the non-streaming self.acompletion call in AnyLLM.completion, matching
the existing streaming path so completion(..., timeout=...) reaches the
provider. Add a regression test covering synchronous non-streaming completion
and verifying the timeout is propagated.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/any_llm/any_llm.py`:
- Line 580: Add timeout: float | None = ... explicitly to the overload
declarations for both completion and acompletion, while retaining the existing
**kwargs: Any and implementation behavior.

In `@tests/unit/providers/test_anthropic_provider.py`:
- Around line 125-147: Add a focused async test for
AnthropicProvider._acompletion where messages.create raises an unrelated
ValueError, then assert the exact same exception is propagated unchanged. Keep
the existing timeout-guard test intact and mock the client call so no network
access occurs.
- Around line 162-164: Combine the nested context managers in the _acompletion
test into a single with statement containing both patch.object and pytest.raises
contexts, while preserving the existing RuntimeError assertion and call
behavior.

---

Outside diff comments:
In `@src/any_llm/any_llm.py`:
- Around line 580-598: Forward the caller’s timeout through the non-streaming
self.acompletion call in AnyLLM.completion, matching the existing streaming path
so completion(..., timeout=...) reaches the provider. Add a regression test
covering synchronous non-streaming completion and verifying the timeout is
propagated.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: da1488f1-9e0d-48e9-9f74-ed0533a67431

📥 Commits

Reviewing files that changed from the base of the PR and between f2b11ab and 9d06f13.

📒 Files selected for processing (5)
  • src/any_llm/any_llm.py
  • src/any_llm/api.py
  • src/any_llm/providers/anthropic/base.py
  • tests/unit/providers/test_anthropic_provider.py
  • tests/unit/test_completion.py

Comment thread src/any_llm/any_llm.py
Comment thread tests/unit/providers/test_anthropic_provider.py
Comment thread tests/unit/providers/test_anthropic_provider.py Outdated
@peteski22
peteski22 force-pushed the fix/anthropic-timeout-handling branch from 9d06f13 to ed8bedd Compare August 10, 2026 09:13
@peteski22
peteski22 temporarily deployed to integration-tests August 10, 2026 09:13 — with GitHub Actions Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/test_completion.py`:
- Around line 42-46: Expand tests/unit/test_completion.py lines 42-46 by
patching AnyLLM.create and exercising top-level completion() and acompletion()
with both configured and None timeout values, asserting each provider receives
the expected timeout semantics. At tests/unit/test_completion.py lines 79-90,
add a synchronous stream=True test that consumes the returned iterator and
verifies _acompletion() receives the configured timeout; cover every changed
branch.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fa70face-8165-41d4-b2a8-7791bba0a954

📥 Commits

Reviewing files that changed from the base of the PR and between 9d06f13 and ed8bedd.

📒 Files selected for processing (3)
  • src/any_llm/any_llm.py
  • tests/unit/providers/test_anthropic_provider.py
  • tests/unit/test_completion.py

Comment thread tests/unit/test_completion.py
@peteski22
peteski22 force-pushed the fix/anthropic-timeout-handling branch from ed8bedd to df07c12 Compare August 10, 2026 09:25
@peteski22
peteski22 temporarily deployed to integration-tests August 10, 2026 09:25 — with GitHub Actions Inactive
@peteski22
peteski22 requested review from tbille and a lite review from Copilot August 10, 2026 15:31

Copilot AI 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.

Pull request overview

This PR improves the Anthropic provider experience by introducing a first-class per-request timeout parameter across the public completion entry points and by translating Anthropic SDK’s opaque non-streaming timeout-guard ValueError into an actionable InvalidRequestError.

Changes:

  • Added an optional timeout parameter to completion() / acompletion() and AnyLLM.completion / AnyLLM.acompletion, forwarding it to providers only when set.
  • Updated the Anthropic provider to catch the SDK’s non-streaming timeout-guard ValueError and re-raise a clearer InvalidRequestError that explains how to fix it.
  • Added unit tests covering timeout exposure/forwarding and the Anthropic non-streaming guard translation behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/any_llm/api.py Exposes timeout on top-level completion and acompletion and documents it.
src/any_llm/any_llm.py Adds timeout to AnyLLM completion APIs and forwards it via kwargs only when set.
src/any_llm/providers/anthropic/base.py Translates Anthropic SDK non-streaming guard ValueError into InvalidRequestError with guidance.
tests/unit/test_completion.py Verifies timeout is exposed on all public completion entry points and forwarded correctly (including dropping explicit None).
tests/unit/providers/test_anthropic_provider.py Adds regression tests for the non-streaming guard and confirms timeout bypasses it.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/any_llm/providers/anthropic/base.py Outdated
Comment thread src/any_llm/providers/anthropic/base.py Outdated
Comment thread tests/unit/providers/test_anthropic_provider.py
@peteski22
peteski22 force-pushed the fix/anthropic-timeout-handling branch from df07c12 to e11f166 Compare August 10, 2026 19:16
@peteski22
peteski22 temporarily deployed to integration-tests August 10, 2026 19:16 — with GitHub Actions Inactive
@njbrake
njbrake temporarily deployed to integration-tests August 10, 2026 20:27 — with GitHub Actions Inactive

@njbrake njbrake 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.

Note: this comment was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Heads up that I pushed 77ef72d to this branch. Drop it if you'd rather own the change.

The Anthropic half is solid, and the bot findings all look addressed. Marker-matching is the right call given the SDK exposes no error code or subtype for that guard, and driving it through a real AsyncAnthropic client means a future reword fails in CI rather than slipping through.

One correction to the description: "every other provider are unaffected, no cross-provider regression" does not hold. timeout is forwarded through **kwargs to every provider, and three SDKs have no such keyword, so they raise before anything is sent:

mistral -> TypeError: Chat.complete_async() got an unexpected keyword argument 'timeout'
cohere  -> TypeError: AsyncV2Client.chat() got an unexpected keyword argument 'timeout'
xai     -> TypeError: BaseClient.create() got an unexpected keyword argument 'timeout'

Reproduced against the real SDKs. Not a regression in the strict sense, since timeout in kwargs did the same on main. But a typed, documented parameter on the universal API is a promise, and prompt_cache_key sets the precedent for handling partial support deliberately instead of leaking a vendor TypeError.

77ef72d maps it per provider: timeout_ms for mistral, request_options for cohere, and a warning pointing at client_args for xai, whose SDK only sets timeouts on the gRPC client. That follows Bedrock #1240. All three now reach transport instead of failing on the keyword.

Three left for you:

  1. watsonx passes the converted kwargs as params= to model_inference.achat, so timeout would land as a generation parameter. I read that rather than ran it, so worth confirming. sagemaker may share the shape.

  2. "An explicit None is dropped, leaving the default path unchanged" holds for the default but not for an explicit None. On main, timeout=None reaches the SDK, where is_given(None) is true, so it lifts the guard. Now it is indistinguishable from unset and the same call raises InvalidRequestError. Worth a docstring line, since the typed signature makes "no limit" unreachable.

  3. amessages() still surfaces the bare ValueError; the translation only wraps the _acompletion call site. Same guard, same file.

@peteski22

Copy link
Copy Markdown
Contributor Author

@njbrake kept 8690e9a. Verified the three follow-ups and audited the rest of the providers against the real SDKs / actual code paths (not just method signatures — huggingface already strips timeout, so the signature alone would have been misleading).

Full scope found:

  • Silent mis-send (the dangerous class): sagemaker (json.dumps'd into Body), watsonx (params=). Both now pop timeout.
  • TypeError: ollama (chat() has no timeout kwarg). groq/cerebras/together accept a timeout param, azure takes **kwargs, and huggingface already stripped it.
  • Folded into a provider config with no timeout concept: lmstudio.

Pushed on top of your commit:

  1. b5d672d — warn+drop for ollama, watsonx, sagemaker, lmstudio, matching your xai precedent; one test each.
  2. 10a992d — items 2 & 3: extracted the guard translation into a shared helper and applied it to _amessages too, and documented that timeout=None is treated as unset. (Confirmed is_given(None) is True in the SDK, so on main None lifted the guard; it's now equivalent to omitting it, and an unbounded timeout isn't expressible via the typed param.)
  3. Corrected the PR description's "no cross-provider regression" line.

Filed #1262 for the better-engineered central approach (an opt-in support declaration like prompt_cache_key), so a future provider can't silently reintroduce this.

peteski22 and others added 5 commits August 11, 2026 13:20
Expose `timeout` (in seconds) as a typed, documented parameter on the public
completion()/acompletion() functions and on AnyLLM.completion/acompletion,
mirroring how prompt_cache_key is surfaced.

It is forwarded to the provider through kwargs only when set, rather than
added to CompletionParams, so providers that already read `timeout` from
kwargs (Gemini, Bedrock) and every other provider are unaffected. An explicit
None is dropped, leaving the default path and its provider behavior unchanged.

Relates to #1251.
…uard

The Anthropic SDK runs a client-side pre-flight check that rejects a
non-streaming completion whose max_tokens could exceed its time limit for a
single response, raising a bare ValueError before any request is sent. Large
max_tokens requests therefore failed with an opaque message even though the
actual response might be tiny.

Catch that specific guard and raise an InvalidRequestError that names the
levers a caller has (pass a timeout, or stream), while re-raising any
unrelated ValueError untouched. The one point of coupling to the SDK's
wording is isolated in a named constant.

Fixes #1251.
The new first-class `timeout` parameter is forwarded through kwargs to every
provider, but three SDKs have no such keyword and raised
`TypeError: ... got an unexpected keyword argument 'timeout'` before any
request was sent:

- mistral names it `timeout_ms`, so translate seconds to milliseconds.
- cohere carries it inside `request_options`, so fold it in there without
  overriding an explicit one.
- xai sets timeouts on the gRPC client only, so drop it with a warning
  pointing at `client_args`, matching the bedrock precedent.

Verified against the real SDKs: all three now reach the transport layer
instead of failing on an unexpected keyword.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sonx, sagemaker, lmstudio

The first-class `timeout` parameter is forwarded through kwargs to every provider, but four
more SDKs cannot honor a per-request timeout and would otherwise mishandle it before any
request is sent:

- ollama's chat() has no timeout keyword, so it raised TypeError.
- watsonx forwards these kwargs as generation parameters, so timeout was sent as an invalid
  model field.
- SageMaker serializes these kwargs into the request body, so timeout was sent as a payload
  field.
- LM Studio folds these kwargs into the prediction config, which has no per-request timeout.

Each now pops `timeout` and warns, pointing at client_args, matching the xai precedent;
huggingface already stripped it.

A central, enforced design is tracked in #1262.
Relates to #1251.
…; document timeout=None

The pre-flight guard translation only wrapped the completion path, so the native messages API
(_amessages) still surfaced the bare SDK ValueError for a large-max_tokens non-streaming
request. Extract the translation into a shared helper and apply it to both paths.

Also document that an explicit `timeout=None` is treated the same as omitting it: the typed
signature makes an unbounded timeout unreachable via the parameter.

Relates to #1251.
@tbille
tbille force-pushed the fix/anthropic-timeout-handling branch from 10a992d to ee23627 Compare August 11, 2026 11:20
@tbille
tbille temporarily deployed to integration-tests August 11, 2026 11:20 — with GitHub Actions Inactive
@tbille
tbille merged commit 2107c19 into main Aug 11, 2026
12 checks passed
@tbille
tbille deleted the fix/anthropic-timeout-handling branch August 11, 2026 11:22
@github-actions github-actions Bot added the 1.25.0 Included in release 1.25.0 label Aug 11, 2026
pull Bot pushed a commit to pepe57/any-llm that referenced this pull request Aug 12, 2026
…rs (mozilla-ai#1263)

## Description

Follow-up to mozilla-ai#1255. That PR added a first-class `timeout` parameter and
forwarded it to every provider through `**kwargs`, handling each SDK
shape individually (map where supported, warn+drop where not). As mozilla-ai#1262
notes, that per-provider approach is whack-a-mole: a new provider that
forwards kwargs to its SDK would silently `TypeError` or mis-send
`timeout` again, with nothing to catch it. The two silent mis-sends
(`sagemaker`, `watsonx`) are the dangerous class.

This PR centralizes the contract the way `prompt_cache_key` already does
with `PROMPT_CACHE_KEY_SUPPORT`:

- A per-provider `TIMEOUT_SUPPORT` capability: `Literal["unsupported",
"native", "mapped"]`, defaulting to `unsupported`.
- A single guard in the base `acompletion`
(`_validate_and_forward_timeout`) that routes `timeout`:
- `native`/`mapped` → forwarded via `kwargs` (mapped providers translate
it in their own conversion);
- `unsupported` → raises a clear `UnsupportedParameterError` (hinting to
set it on the client via `client_args`) instead of leaking a vendor
`TypeError` or silently mis-sending the value.
- `timeout=None` is treated as unset in one place, so an unbounded
timeout is not expressible.

A new provider now inherits the safe `unsupported` default and must opt
in explicitly, so the failure mode is a loud, testable default rather
than a silent per-SDK surprise.

### Provider classification
- **native** (SDK accepts `timeout` directly): OpenAI base (covers all
OpenAI-compatible and config-registry providers), Anthropic base,
`cerebras`, `groq`, `together`.
- **mapped** (translated): Google base / `gemini`+`vertexai`
(`http_options` ms), `bedrock` (per-timeout boto3 client), `cohere`
(`request_options`), `mistral` (`timeout_ms`).
- **unsupported** (no per-request timeout): `huggingface`, `lmstudio`,
`ollama`, `sagemaker`, `watsonx`, `xai`, `azure`. The now-dead
per-provider warn+drop code is removed; each records *why* it is
unsupported in a comment (e.g. `watsonx`/`sagemaker` would otherwise
mis-send `timeout` into the request body).

### Behavior change
For `unsupported` providers, passing a per-request `timeout` now
**raises `UnsupportedParameterError`** rather than logging a warning and
dropping it (the mozilla-ai#1255 behavior). This matches the `prompt_cache_key`
precedent and avoids silently proceeding without a requested bound.
Client-level timeouts via `client_args` are unaffected. Since the typed
`timeout` shipped only in mozilla-ai#1255, regression exposure is minimal.

### Note on `azure`
`azure` (Azure AI Inference SDK) is deliberately left at the
conservative `unsupported` default: it was untouched by mozilla-ai#1255, is not
verified to honor a per-request timeout, and its SDK rejects unknown
transport kwargs. It is flagged in-code for a maintainer to promote to
`native`/`mapped` with a verifying test if appropriate.

### Notes on tests
- Added `test_acompletion_rejects_timeout_for_unsupported_provider`
(central rejection path). The existing forward and `None`-drop tests
plus the mapped-translation tests (`cohere`/`mistral`/`gemini`) continue
to cover the supported paths; the obsolete per-provider warn+drop tests
were removed.
- `pre-commit` (ruff + ruff-format + mypy strict) is clean for the
changed code (no new mypy errors versus the `main` baseline).
- The only failing tests in a local `tests/unit` run are pre-existing
and identical to `main`: the `mistralai` `ThinkChunk.signature` SDK
drift and the `lmstudio` optional SDK not being installed. Neither is
touched by this change.
- Integration tests were not run locally (they need real keys or the
`run-integration-tests` label). The change is provider-SDK-agnostic at
the boundary; a real `cohere`/`mistral`/`gemini`/`bedrock` run would
confirm the mapped translation end to end.

## PR Type

- 🆕 New Feature
- 💅 Refactor

## Relevant issues

Fixes mozilla-ai#1262
Follow-up to mozilla-ai#1255 (per-provider timeout handling)

## Checklist

- [x] I understand the code I am submitting.
- [x] I have added unit tests that prove my fix/feature works
- [x] I have run this code locally and verified it fixes the issue.
- [x] New and existing tests pass locally
- [x] Documentation was updated where necessary
- [x] I have read and followed the [contribution
guidelines](https://github.com/mozilla-ai/any-llm/blob/main/CONTRIBUTING.md)
- [x] **AI Usage:**
    - [ ] No AI was used.
    - [x] AI was used for drafting/refactoring.
    - [ ] This is fully AI-generated.

## AI Usage Information

- AI Model used: Claude Opus 4.8
- AI Developer Tool used: Claude Code
- Any other info you'd like to share: Claude was used collaboratively.
It assisted with investigation, design, drafting, and iteration under
human review and direction, including the decision to raise
`UnsupportedParameterError` (over a centralized warn+drop) and restoring
the per-provider rationale for the `unsupported` classifications.

- [x] I am an AI Agent filling out this form (check box if true)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added per-request timeout support to synchronous and asynchronous
completion and messages requests.
* Timeout values are automatically forwarded or converted for compatible
providers.
  * Omitting a timeout preserves provider defaults.
* Client-level timeout configuration remains available where per-request
timeouts are unsupported.

* **Bug Fixes**
* Unsupported per-request timeouts are now rejected clearly before
requests are sent.
* Improved timeout forwarding and validation across synchronous and
asynchronous APIs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: njbrake <njbrake@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
integration-tests — ee23627f Deployed Aug 11, 2026 by tbille via run-docs-tests #2375
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.25.0 Included in release 1.25.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Anthropic provider has no timeout handling; large max_tokens non-streaming completions fail pre-flight with an opaque ValueError

4 participants