Skip to content

fix: forward provider timeout and bound fallback chains - #87

Merged
jmlago merged 1 commit into
mainfrom
fix/router-request-deadline
Jul 25, 2026
Merged

fix: forward provider timeout and bound fallback chains#87
jmlago merged 1 commit into
mainfrom
fix/router-request-deadline

Conversation

@jmlago

@jmlago jmlago commented Jul 25, 2026

Copy link
Copy Markdown
Member

Why

Production unary requests can outlive the 60-second ALB idle timeout when several provider fallbacks run sequentially. The ingress then reports an opaque gateway failure even though the router pod and target are healthy. Incoming timeout_ms was also accepted by the permissive request model but dropped before reaching the router contract.

What

  • model and forward timeout_ms for Chat Completions and Responses requests
  • cap each direct router execution across all retries and fallbacks
  • default the cap to 50 seconds via ROUTER_REQUEST_DEADLINE_MS, leaving margin before the ALB cutoff
  • cancel the active provider coroutine on expiry and return a structured HTTP 504 with deadline metadata
  • keep long-running Sigma flow streams unchanged because they already send heartbeats

Testing

  • nix-shell --run "python -m pytest tests -q"
  • 518 passed, 2 skipped

Summary by CodeRabbit

  • New Features

    • Added configurable request deadlines across chat, responses, compact, and streaming requests.
    • Requests that exceed the deadline now return a structured 504 timeout response.
    • Added optional timeout_ms support for chat and responses requests.
    • Added configuration guidance for setting the router deadline below proxy timeouts.
  • Bug Fixes

    • Ensured timed-out executions are cancelled and provider timeout settings are preserved.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The shim adds configurable router execution deadlines and propagates optional provider-attempt timeouts through chat and Responses requests. Compact, unary, and streaming paths now enforce the deadline, returning structured timeout failures and cancelling stalled host execution.

Changes

Router timeout controls

Layer / File(s) Summary
Deadline configuration and timeout contracts
shim.py, .env.example
Adds configurable request deadlines, timeout request fields, validation, cancellation via asyncio.timeout, and structured timeout results.
Execution path wiring
shim.py
Applies the deadline wrapper to compact, chat, and Responses unary and streaming execution, and forwards timeout_ms into router contracts.
Timeout behavior validation
tests/test_shim.py, tests/test_responses_shim.py, tests/test_shim_max_tokens.py
Tests provider timeout propagation, HTTP 504 timeout handling, decision-trace metadata, and cancellation.ey

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FastAPIShim
  participant RouterHost
  Client->>FastAPIShim: Send request with timeout_ms
  FastAPIShim->>RouterHost: Execute contract with timeout_ms
  RouterHost-->>FastAPIShim: Complete or exceed app deadline
  FastAPIShim-->>Client: Normal response or structured 504 timeout
Loading

Possibly related PRs

Suggested reviewers: muncleuscles, acastellana

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: forwarding provider timeouts and adding a hard deadline around fallback execution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/router-request-deadline

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.

@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: 2

🤖 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 `@shim.py`:
- Around line 264-294: Apply the existing _execute_with_deadline wrapper to the
unary stream:false host.execute_flow_async(...) call around the flow-dispatch
logic, so non-streaming requests return the structured timeout response when
request_deadline_ms expires. Leave the heartbeat-enabled streaming-flow path
unchanged and preserve the existing awaitable arguments and result handling.
- Line 950: Update the compaction result handling around _execute_with_deadline
so a timeout result is passed to _openai_error_from_router and surfaces as HTTP
504 before the empty-summary conversion. Preserve the existing 200 {"compacted":
false} soft-failure behavior for non-timeout seal failures.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d63d567-f2ce-4f32-b47d-d7eec79d9f02

📥 Commits

Reviewing files that changed from the base of the PR and between a89f18f and 9cdaef7.

📒 Files selected for processing (5)
  • .env.example
  • shim.py
  • tests/test_responses_shim.py
  • tests/test_shim.py
  • tests/test_shim_max_tokens.py

Comment thread shim.py
Comment on lines +264 to +294
async def _execute_with_deadline(awaitable):
"""Await one complete router run and cancel it at the outer deadline.

asyncio.timeout propagates cancellation into the active provider call,
so an expired request does not leave an orphan fallback chain running
after the HTTP response has finished.
"""
deadline = asyncio.timeout(request_deadline_s)
try:
async with deadline:
return await awaitable
except TimeoutError:
# Do not relabel a TimeoutError raised by the host itself: only the
# timeout context's own expiry is the outer request deadline.
if not deadline.expired():
raise
_log.warning(
"router request deadline exceeded after %d ms",
request_deadline_ms,
)
return {
"ok": False,
"error": "timeout",
"trace": {
"decision_path": [],
"request_deadline_exceeded": True,
"request_deadline_ms": request_deadline_ms,
"total_latency_ms": request_deadline_ms,
},
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply the deadline to unary flow execution.

Line 264 defines the complete-execution deadline, but shim.py Line 1133 calls host.execute_flow_async(...) directly. A stream: false flow can therefore outlive request_deadline_ms and be cut off upstream instead of returning the structured 504. Keep the heartbeat-enabled streaming-flow behavior unchanged, but wrap the unary call.

Proposed fix
-                    result = await host.execute_flow_async(req.flow_ir, contract)
+                    result = await _execute_with_deadline(
+                        host.execute_flow_async(req.flow_ir, contract)
+                    )
🤖 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 `@shim.py` around lines 264 - 294, Apply the existing _execute_with_deadline
wrapper to the unary stream:false host.execute_flow_async(...) call around the
flow-dispatch logic, so non-streaming requests return the structured timeout
response when request_deadline_ms expires. Leave the heartbeat-enabled
streaming-flow path unchanged and preserve the existing awaitable arguments and
result handling.

Comment thread shim.py
}
try:
res = await host.execute_async(contract)
res = await _execute_with_deadline(host.execute_async(contract))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return a 504 when compaction hits the deadline.

Line 950 can return the structured {ok: False, error: "timeout"} result, but the subsequent empty-summary path converts it into 200 {"compacted": false}. Surface deadline expiry through _openai_error_from_router before preserving the existing soft-failure behavior for other seal failures.

Proposed fix
         try:
             res = await _execute_with_deadline(host.execute_async(contract))
         except Exception as exc:
             admission = _policy_admission_error(exc)
             if admission is not None:
                 return _invalid_policy_response(admission)
             raise
+        if res.get("error") == "timeout":
+            return _openai_error_from_router(res)
 
         def _costed(body: dict) -> dict:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res = await _execute_with_deadline(host.execute_async(contract))
res = await _execute_with_deadline(host.execute_async(contract))
if res.get("error") == "timeout":
return _openai_error_from_router(res)
🤖 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 `@shim.py` at line 950, Update the compaction result handling around
_execute_with_deadline so a timeout result is passed to
_openai_error_from_router and surfaces as HTTP 504 before the empty-summary
conversion. Preserve the existing 200 {"compacted": false} soft-failure behavior
for non-timeout seal failures.

@jmlago
jmlago merged commit d65d05b into main Jul 25, 2026
1 check passed
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.

1 participant