Skip to content

feat(ai-proxy): support phase-specific HTTP timeouts - #13801

Open
ruanweihong-aaa wants to merge 2 commits into
apache:masterfrom
ruanweihong-aaa:feat/ai-proxy-phase-timeouts
Open

feat(ai-proxy): support phase-specific HTTP timeouts#13801
ruanweihong-aaa wants to merge 2 commits into
apache:masterfrom
ruanweihong-aaa:feat/ai-proxy-phase-timeouts

Conversation

@ruanweihong-aaa

@ruanweihong-aaa ruanweihong-aaa commented Aug 10, 2026

Copy link
Copy Markdown

Description

ai-proxy and ai-proxy-multi currently expose a single timeout value that is applied to the HTTP connect, send, and read phases. This makes it difficult to fail fast during connection establishment or request upload while allowing a longer timeout for LLM response generation.

This PR adds three optional phase-specific timeout fields:

  • connect_timeout: timeout for establishing the upstream connection;
  • send_timeout: timeout for sending the request to the upstream;
  • read_timeout: timeout for each socket read from the upstream.

All three fields are expressed in milliseconds and accept integer values from 1 to 600000.

The change preserves backward compatibility:

  • when none of the new fields is configured, the transport continues to call the existing numeric set_timeout(timeout) path;
  • when only some phase-specific fields are configured, every omitted phase falls back to the existing timeout value;
  • existing configurations therefore keep their previous behavior.

Implementation details:

  • add the three fields to both the ai-proxy and ai-proxy-multi schemas;
  • build a complete connect/send/read timeout tuple at the ai-proxy transport call site;
  • use lua-resty-http's set_timeouts(connect, send, read) for phase-specific configuration while retaining the original set_timeout(timeout) compatibility path;
  • document the new fields in the English and Chinese plugin documentation;
  • add schema boundary tests, transport API tests, and delayed-upstream read-timeout regression coverage.

Compatibility

The new fields are optional. Existing users who only configure timeout, or rely on its default value, continue to use the original code path. The upper bound of each new field is the same as the existing timeout upper bound.

read_timeout is a per-socket-read timeout and is not a total wall-clock limit for streaming responses. Existing max_stream_duration_ms and max_response_bytes controls remain responsible for total streaming duration and response-size limits.

Verification

Targeted, source-matched Docker tests:

  • schema validation for ai-proxy and ai-proxy-multi, including valid boundaries and invalid values;
  • transport dispatch to set_timeouts(connect, send, read) and compatibility with the numeric set_timeout(timeout) path;
  • delayed-upstream read-timeout behavior with only read_timeout configured, exercising fallback to the existing timeout value for the omitted phases;
  • result: Files=3, Tests=63, Result: PASS.

Additional local dual-Gateway A/B verification used identical etcd, routes, Docker network, and controlled upstream fixtures for the pre-fix and fixed images:

  • read phase: pre-fix returned 200 after about 250 ms; fixed returned 504 after about 50 ms with read_timeout=50;
  • connect phase: pre-fix timed out after about 1 s; fixed timed out after about 50 ms with connect_timeout=50;
  • send phase: with a 60 MiB request and an upstream that accepted headers but did not consume the body, the fixed image timed out significantly earlier with send_timeout=50;
  • Gateway logs were checked to associate each failure with the intended connect, send, or read phase.

These results cover the tests related to this change; they are not a claim that the complete APISIX test suite was executed locally.

Which issue(s) this PR fixes:

Fixes #12072

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible

Add optional connect, send, and read timeout settings for ai-proxy and ai-proxy-multi while preserving the existing timeout fallback behavior.
Comment thread t/plugin/ai-proxy-timeout-callsite.t Outdated
@nic-6443
nic-6443 marked this pull request as ready for review August 14, 2026 08:56
Copilot AI lite review requested due to automatic review settings August 14, 2026 08:56
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Aug 14, 2026

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds phase-specific HTTP timeouts to ai-proxy / ai-proxy-multi while preserving the legacy single timeout behavior for backward compatibility.

Changes:

  • Adds connect_timeout, send_timeout, and read_timeout to plugin schemas and documentation (EN/ZH).
  • Updates the AI HTTP transport to support lua-resty-http set_timeouts(connect, send, read) alongside the existing set_timeout(timeout) path.
  • Extends test coverage for schema boundaries, transport dispatch, and read-timeout regression behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
t/plugin/ai-transport-http.t Adds transport-level tests asserting set_timeouts vs set_timeout dispatch.
t/plugin/ai-proxy.t Adds schema boundary tests for new fields + delayed-upstream read-timeout regression tests.
t/plugin/ai-proxy-multi.t Adds schema boundary tests for new fields.
docs/zh/latest/plugins/ai-proxy.md Documents new timeout fields and fallback behavior (ZH).
docs/zh/latest/plugins/ai-proxy-multi.md Documents new timeout fields and fallback behavior (ZH).
docs/en/latest/plugins/ai-proxy.md Documents new timeout fields and fallback behavior (EN).
docs/en/latest/plugins/ai-proxy-multi.md Documents new timeout fields and fallback behavior (EN).
apisix/plugins/ai-transport/http.lua Implements timeout table support via set_timeouts with legacy fallback.
apisix/plugins/ai-proxy/schema.lua Adds schema fields for phase timeouts to both proxy schemas.
apisix/plugins/ai-proxy/base.lua Builds connect/send/read timeout tuple (with fallback) at transport call site.

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

Comment on lines +362 to +399
content_by_lua_block {
local orig_http = package.loaded["resty.http"]
local orig_transport = package.loaded["apisix.plugins.ai-transport.http"]

package.loaded["resty.http"] = {
new = function()
return {
set_timeout = function()
error("legacy set_timeout must not be used")
end,
set_timeouts = function(_, connect, send, read)
ngx.say(connect, ",", send, ",", read)
end,
connect = function() return true end,
request = function() return {headers = {}, status = 200} end,
}
end,
}

package.loaded["apisix.plugins.ai-transport.http"] = nil
local transport = require("apisix.plugins.ai-transport.http")
local res, err = transport.request({
host = "127.0.0.1",
port = 80,
path = "/",
body = {},
}, {
connect_timeout = 101,
send_timeout = 202,
read_timeout = 303,
})
if not res then
ngx.say(err)
end

package.loaded["resty.http"] = orig_http
package.loaded["apisix.plugins.ai-transport.http"] = orig_transport
}
Comment on lines +409 to +442
content_by_lua_block {
local orig_http = package.loaded["resty.http"]
local orig_transport = package.loaded["apisix.plugins.ai-transport.http"]

package.loaded["resty.http"] = {
new = function()
return {
set_timeout = function(_, timeout)
ngx.say(timeout)
end,
set_timeouts = function()
error("set_timeouts must not be used for numeric callers")
end,
connect = function() return true end,
request = function() return {headers = {}, status = 200} end,
}
end,
}

package.loaded["apisix.plugins.ai-transport.http"] = nil
local transport = require("apisix.plugins.ai-transport.http")
local res, err = transport.request({
host = "127.0.0.1",
port = 80,
path = "/",
body = {},
}, 456)
if not res then
ngx.say(err)
end

package.loaded["resty.http"] = orig_http
package.loaded["apisix.plugins.ai-transport.http"] = orig_transport
}
| logging.summaries | boolean | False | false | | If true, logs request LLM model, duration, request, and response tokens. |
| logging.payloads | boolean | False | false | | If true, logs request and response payload. |
| timeout | integer | False | 30000 | 1 - 600000 | Request timeout in milliseconds when requesting the LLM service. Applied per socket operation (connect / send / read block); does not cap the total duration of a streaming response. |
| timeout | integer | False | 30000 | 1 - 600000 | Default request timeout in milliseconds when requesting the LLM service. It remains the fallback for each phase below and is used for all phases when no phase-specific timeout is configured. |
Comment on lines +350 to +360
maximum = 600000,
},
send_timeout = {
type = "integer",
minimum = 1,
maximum = 600000,
},
read_timeout = {
type = "integer",
minimum = 1,
maximum = 600000,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: more precise control over the timeout for requests to the LLM service

3 participants