Skip to content

fix(llm): honor GRAPHIFY_API_TIMEOUT in the bedrock backend - #2283

Open
zhiyanliu wants to merge 1 commit into
Graphify-Labs:v8from
zhiyanliu:fix/bedrock-honor-api-timeout
Open

fix(llm): honor GRAPHIFY_API_TIMEOUT in the bedrock backend#2283
zhiyanliu wants to merge 1 commit into
Graphify-Labs:v8from
zhiyanliu:fix/bedrock-honor-api-timeout

Conversation

@zhiyanliu

@zhiyanliu zhiyanliu commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Both bedrock-runtime clients — the primary extraction path (_call_bedrock) and the secondary dispatch path (the bedrock branch of _call_llm) — are built with no botocore config, so Converse uses botocore's 60s default read timeout and ignores GRAPHIFY_API_TIMEOUT / --api-timeout entirely. A long opus-class generation then dies with Read timeout on endpoint URL regardless of how high the timeout is set.

This wires both client constructions through a botocore.config.Config that sets read_timeout=_resolve_api_timeout() (default 600s), a 10s connect_timeout, and retries from _resolve_max_retries() in adaptive mode.

This mirrors the fixes that closed the same gap for the claude-cli subprocess (#1112 / #1111) and the secondary LLM dispatch path (#1442). Bedrock was the last cloud backend still ignoring the knob, and the README env-var table already advertised the timeout as general — so the gap was easy to trip over.

Root cause

session.client("bedrock-runtime") was called with no config=, at both sites. botocore's default read_timeout is 60s, so a Converse call that generates for longer is cut off at 60s with a Read timeout on endpoint URL error — the env var never reaches boto3.

What changed

  • _call_bedrock and the _call_llm bedrock branch now build the client with botocore.config.Config(read_timeout=_resolve_api_timeout(), connect_timeout=10, retries=…).
  • README env-var row updated to include Bedrock.
  • _fake_boto3 test fixture registers botocore.config and captures the client config; added coverage asserting the timeout is wired (override + default) alongside the existing image-bytes test.

Verification

Reproduced against a real Bedrock model (global.anthropic.claude-opus-5): before, large doc chunks failed with Read timeout on endpoint URL under a raised GRAPHIFY_API_TIMEOUT; after, the value reaches boto3's read_timeout and the chunks complete. Added a test that intercepts client construction and asserts read_timeout == GRAPHIFY_API_TIMEOUT (and the 600s default when unset). ruff check clean on the changed files; the full image-vision suite passes.

Happy to revisit the connect_timeout / retry-mode choices if you'd prefer to match a different convention.

Fixes #2284

The two bedrock-runtime clients (primary extraction in _call_bedrock and
the secondary dispatch path in _call_llm) were built with no botocore
config, so Converse used botocore's 60s default read timeout and ignored
GRAPHIFY_API_TIMEOUT / --api-timeout entirely. A long opus-class
generation then died with "Read timeout on endpoint URL" no matter how
high the timeout was set.

Both client constructions now pass a botocore.config.Config wiring
read_timeout to _resolve_api_timeout() (default 600s), a 10s
connect_timeout, and retries from _resolve_max_retries() in adaptive
mode. This mirrors the fixes that closed the same gap for the claude-cli
subprocess (Graphify-Labs#1112/Graphify-Labs#1111) and the secondary LLM dispatch path (Graphify-Labs#1442) --
bedrock was the last cloud backend still ignoring the knob.

Also updates the README env-var row, which listed the timeout as
applying to HTTP/claude-cli/Anthropic only, and the _fake_boto3 test
fixture to register botocore.config and capture the client config so the
new coverage can assert the timeout is wired.

@graphify-labs graphify-labs 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.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR wires the GRAPHIFY_API_TIMEOUT (and retry) settings into the AWS Bedrock backend by constructing the bedrock-runtime boto3 client with an explicit botocore.config.Config (read/connect timeouts and retry policy) in both _call_bedrock and the _call_llm bedrock branch. The README's GRAPHIFY_API_TIMEOUT row is updated to mention the Bedrock backend, and the test suite's fake boto3 fixture is extended to stub botocore.config and capture client-construction kwargs, with two new tests asserting the timeout/retry values reach the client both when the env var is set and when it defaults. Surface area is limited to the Bedrock client setup in graphify/llm.py, one README table row, and the image-vision test file's boto3 fakes plus two added tests.

Worth a look

  • Double retry layering: botocore retries stacked on top of application-level retry loopgraphify/llm.py:1631 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 617 functions depend on the 212 node(s) this change touches.

Health — this change adds coupling hotspots:

  • worse: _call_bedrock() — 4 callers, 7 callees

Verification — 617 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 427 function(s) in the blast radius were not formally verified this run

· 1 more finding(s) on lines outside this diff (see the check run).

@zhiyanliu

Copy link
Copy Markdown
Author

Thanks for the review. On the double-retry-layering note — I looked at how the application-level retry actually classifies failures, and I don't think the two layers meaningfully stack here, but wanted to lay out the evidence so a maintainer can decide.

The adaptive retry in _extract_with_adaptive_retry only re-runs a chunk when _looks_like_context_exceeded returns true; every other exception is re-raised. That helper matches on context-window markers (context length, too many tokens, prompt is too long, and similar). A boto3 Read timeout on endpoint URL doesn't match any of them, so a read timeout is never retried by the application layer — it surfaces as a failed chunk, which is exactly the behavior in the issue. The botocore max_attempts and the application loop therefore act on largely disjoint failure classes: botocore retries throttling/5xx/connection errors, while the application layer only splits on context overflow and re-raises the rest.

The retry policy on the Bedrock client also mirrors the existing cloud backends rather than introducing a new pattern — the OpenAI, Anthropic, and Azure clients each already pass max_retries=_resolve_max_retries() to their SDK's built-in retry, on top of the same application loop. This change brings Bedrock in line with them; if the layering is a concern it's a pre-existing property shared by all four backends, not something this PR adds.

Happy to drop the retries block (or the adaptive mode) and keep only the read/connect timeouts if you'd prefer the Bedrock client not to carry an SDK-level retry policy.

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.

bedrock backend ignores GRAPHIFY_API_TIMEOUT (botocore 60s default read timeout)

1 participant