mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-21 12:06:18 +00:00
fix(nginx): allow model-bound /api/ and /api/skills requests past 60 seconds (#5524)
* fix(nginx): allow model-bound /api/ and /api/skills requests past 60 seconds Two locations were left on nginx's 60s default while the routes behind them wait on Gateway. The /api/ catch-all carries the stateless POST /api/runs/wait, which blocks on wait_for_run_completion and cancels its run when the client disconnects, so a caller waiting on a longer run got a 504 and lost the run; it also carries POST /api/input-polish, which waits for a one-shot model call. /api/skills carries POST /api/skills/install, which runs one LLM security scan per file in the archive, and the custom-skill edit and rollback routes, which run one more each. None of them sets an application-level timeout, and only the sibling /api/skills/install/upload endpoint had been given the longer timeout, so the same archive failed at 60s depending on which endpoint installed it. Allow 600s on both locations, matching /api/langgraph/ and /api/threads, in all three copies of the nginx config. Each directive is pinned by its own test that parses the active directive per config. * docs(changelog): link the nginx /api/ and /api/skills timeout entry to #5524
This commit is contained in:
parent
6d725f1ccb
commit
9f79ddf9b6
14
CHANGELOG.md
14
CHANGELOG.md
@ -941,6 +941,19 @@ This release closes that milestone with **765 merged pull requests**.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **nginx:** Extend the 600-second read timeout to the two remaining locations
|
||||||
|
whose routes wait on the Gateway, both left on nginx's 60-second default by
|
||||||
|
the thread-route fix. Behind the `/api/` catch-all, the stateless
|
||||||
|
`POST /api/runs/wait` blocks on the same run-completion wait and cancels its
|
||||||
|
run when the client disconnects, so an API consumer waiting on a run longer
|
||||||
|
than 60 seconds got a 504 *and* a cancelled run, and the composer's
|
||||||
|
`POST /api/input-polish` waits for a one-shot model call. Behind
|
||||||
|
`/api/skills`, installing a `.skill` archive runs one LLM security scan per
|
||||||
|
file in it, and a custom-skill edit or rollback runs one more; none of them
|
||||||
|
sets its own timeout, and only the sibling `/api/skills/install/upload`
|
||||||
|
endpoint had been given the longer timeout, so the same install through
|
||||||
|
`POST /api/skills/install` failed at 60 seconds. Applied to the Docker,
|
||||||
|
local, and Helm configs. ([#5524])
|
||||||
- **nginx:** Stop thread routes that wait on a model call from failing at 60
|
- **nginx:** Stop thread routes that wait on a model call from failing at 60
|
||||||
seconds. The browser calls `/api/threads/*` directly, and that location had
|
seconds. The browser calls `/api/threads/*` directly, and that location had
|
||||||
no `proxy_read_timeout`, so nginx's 60-second default applied while
|
no `proxy_read_timeout`, so nginx's 60-second default applied while
|
||||||
@ -4255,3 +4268,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
|||||||
[#5501]: https://github.com/bytedance/deer-flow/pull/5501
|
[#5501]: https://github.com/bytedance/deer-flow/pull/5501
|
||||||
[#5504]: https://github.com/bytedance/deer-flow/pull/5504
|
[#5504]: https://github.com/bytedance/deer-flow/pull/5504
|
||||||
[#5505]: https://github.com/bytedance/deer-flow/pull/5505
|
[#5505]: https://github.com/bytedance/deer-flow/pull/5505
|
||||||
|
[#5524]: https://github.com/bytedance/deer-flow/pull/5524
|
||||||
|
|||||||
@ -727,6 +727,14 @@
|
|||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
|
- **nginx:** 把 600 秒读取超时扩展到其余两个会等待 Gateway 的 location,它们在线程路由的修复
|
||||||
|
之后仍沿用 nginx 默认的 60 秒。`/api/` 兜底 location 之后:无状态的 `POST /api/runs/wait`
|
||||||
|
阻塞在同一套运行完成等待上,并在客户端断开时取消该运行,因此等待超过 60 秒的 API 调用方会
|
||||||
|
同时收到 504 **并且**运行被取消;输入框的 `POST /api/input-polish` 则等待一次性模型调用。
|
||||||
|
`/api/skills` 之后:安装 `.skill` 压缩包会对其中每个文件各做一次 LLM 安全扫描,自定义技能的
|
||||||
|
编辑与回滚各再做一次,它们都没有自己的超时;此前只有同级的 `/api/skills/install/upload`
|
||||||
|
拿到了更长的超时,因此同样的安装经由 `POST /api/skills/install` 会在 60 秒失败。
|
||||||
|
Docker、本地开发与 Helm 配置均已应用。([#5524])
|
||||||
- **nginx:** 需要等待模型调用的线程路由不再在 60 秒时失败。浏览器直接调用 `/api/threads/*`,
|
- **nginx:** 需要等待模型调用的线程路由不再在 60 秒时失败。浏览器直接调用 `/api/threads/*`,
|
||||||
而该 location 没有设置 `proxy_read_timeout`,因此沿用 nginx 默认的 60 秒,而 `/api/langgraph/`
|
而该 location 没有设置 `proxy_read_timeout`,因此沿用 nginx 默认的 60 秒,而 `/api/langgraph/`
|
||||||
允许 600 秒。较慢的 `/compact` 会返回 504,但 Gateway 仍会继续执行并保存压缩结果,于是 UI
|
允许 600 秒。较慢的 `/compact` 会返回 504,但 Gateway 仍会继续执行并保存压缩结果,于是 UI
|
||||||
@ -3483,3 +3491,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
|||||||
[#5501]: https://github.com/bytedance/deer-flow/pull/5501
|
[#5501]: https://github.com/bytedance/deer-flow/pull/5501
|
||||||
[#5504]: https://github.com/bytedance/deer-flow/pull/5504
|
[#5504]: https://github.com/bytedance/deer-flow/pull/5504
|
||||||
[#5505]: https://github.com/bytedance/deer-flow/pull/5505
|
[#5505]: https://github.com/bytedance/deer-flow/pull/5505
|
||||||
|
[#5524]: https://github.com/bytedance/deer-flow/pull/5524
|
||||||
|
|||||||
@ -159,6 +159,22 @@ def test_threads_route_outlasts_blocking_gateway_calls(path):
|
|||||||
assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: the generic /api/threads location allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like /api/langgraph/"
|
assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: the generic /api/threads location allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like /api/langgraph/"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
||||||
|
def test_api_catchall_outlasts_blocking_gateway_calls(path):
|
||||||
|
"""The routes that wait on Gateway are not all under ``/api/threads``.
|
||||||
|
The stateless ``POST /api/runs/wait`` blocks on the same
|
||||||
|
``wait_for_run_completion`` and cancels its run when the client
|
||||||
|
disconnects, and the composer's ``POST /api/input-polish`` waits for a
|
||||||
|
one-shot model call. Both fall through to this catch-all, so it needs the
|
||||||
|
same read timeout as the thread routes."""
|
||||||
|
content = _read(path)
|
||||||
|
block = _extract_location_block(content, "/api/")
|
||||||
|
|
||||||
|
timeout_seconds = _parse_read_timeout_seconds(block)
|
||||||
|
|
||||||
|
assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: the /api/ catch-all allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like /api/langgraph/ and /api/threads"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
||||||
def test_skills_upload_route_allows_archive_plus_multipart_framing(path):
|
def test_skills_upload_route_allows_archive_plus_multipart_framing(path):
|
||||||
"""The upload route must stream archives and allow slow validation."""
|
"""The upload route must stream archives and allow slow validation."""
|
||||||
@ -170,6 +186,21 @@ def test_skills_upload_route_allows_archive_plus_multipart_framing(path):
|
|||||||
assert "proxy_read_timeout 600s;" in block
|
assert "proxy_read_timeout 600s;" in block
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
||||||
|
def test_skills_prefix_outlasts_the_llm_security_scan(path):
|
||||||
|
"""Installing a ``.skill`` archive runs one LLM security scan per file in
|
||||||
|
it, and editing or rolling back a custom skill runs one more; none of them
|
||||||
|
sets an application-level timeout. The upload endpoint above already gets
|
||||||
|
600s, but ``POST /api/skills/install`` and the custom-skill writes land
|
||||||
|
here, so this prefix needs it too."""
|
||||||
|
content = _read(path)
|
||||||
|
block = _extract_location_block(content, "/api/skills")
|
||||||
|
|
||||||
|
timeout_seconds = _parse_read_timeout_seconds(block)
|
||||||
|
|
||||||
|
assert timeout_seconds >= _MIN_BLOCKING_READ_TIMEOUT_SECONDS, f"{path}: /api/skills allows {timeout_seconds}s, expected at least {_MIN_BLOCKING_READ_TIMEOUT_SECONDS}s like its own /api/skills/install/upload endpoint"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
||||||
def test_skills_prefix_keeps_default_request_body_policy(path):
|
def test_skills_prefix_keeps_default_request_body_policy(path):
|
||||||
"""Large bodies must be allowed only on the admin upload endpoint."""
|
"""Large bodies must be allowed only on the admin upload endpoint."""
|
||||||
|
|||||||
@ -121,12 +121,14 @@ secrets:
|
|||||||
|
|
||||||
The default ingress annotations permit a 100 MiB local `.skill` archive plus
|
The default ingress annotations permit a 100 MiB local `.skill` archive plus
|
||||||
multipart framing, stream request bodies without ingress buffering, and allow
|
multipart framing, stream request bodies without ingress buffering, and allow
|
||||||
up to 600 seconds for a response, which skill validation and thread requests
|
up to 600 seconds for a response, which the API requests that wait on a model
|
||||||
that wait on a model call (such as `/compact`) both need. If you replace
|
call or a whole run all need — skill install and custom-skill edits (each file
|
||||||
`ingress.annotations`, preserve equivalent size, streaming, and
|
is scanned by an LLM), `/api/threads/{id}/compact`, `/api/input-polish`, and
|
||||||
response-timeout settings for your ingress controller, or local skill uploads
|
`/api/runs/wait`. If you replace `ingress.annotations`, preserve equivalent
|
||||||
may fail before DeerFlow completes the installation and those thread requests
|
size, streaming, and response-timeout settings for your ingress controller, or
|
||||||
may time out while Gateway is still working.
|
local skill uploads may fail before DeerFlow completes the installation and
|
||||||
|
those requests may time out while Gateway is still working — for
|
||||||
|
`/api/runs/wait` the disconnect also cancels the run.
|
||||||
|
|
||||||
Provide your model config under `config` (keep secrets as `$VAR` references —
|
Provide your model config under `config` (keep secrets as `$VAR` references —
|
||||||
they resolve from the `secrets` map):
|
they resolve from the `secrets` map):
|
||||||
|
|||||||
@ -133,6 +133,10 @@ data:
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
|
# Installing a .skill archive runs one LLM security scan per file, and a
|
||||||
|
# custom-skill edit or rollback runs one more; none sets its own timeout.
|
||||||
|
# The upload endpoint above already allows 600s for the same work.
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/agents {
|
location /api/agents {
|
||||||
@ -227,6 +231,10 @@ data:
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
|
# /api/runs/wait blocks on a whole run and /api/input-polish on a model
|
||||||
|
# call. nginx's 60s default would 504 them mid-work, and the waited run is
|
||||||
|
# cancelled on the disconnect.
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Everything else -> frontend (with WebSocket upgrade for HMR/sockets).
|
# Everything else -> frontend (with WebSocket upgrade for HMR/sockets).
|
||||||
|
|||||||
@ -167,6 +167,11 @@ http {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
|
|
||||||
|
# Installing a .skill archive runs one LLM security scan per file, and a
|
||||||
|
# custom-skill edit or rollback runs one more; none sets its own timeout.
|
||||||
|
# The upload endpoint above already allows 600s for the same work.
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Custom API: Agents endpoint
|
# Custom API: Agents endpoint
|
||||||
@ -301,6 +306,11 @@ http {
|
|||||||
|
|
||||||
# Disable buffering to avoid permission errors when nginx
|
# Disable buffering to avoid permission errors when nginx
|
||||||
# runs as a non-root user (e.g. local development).
|
# runs as a non-root user (e.g. local development).
|
||||||
|
|
||||||
|
# /api/runs/wait blocks on a whole run and /api/input-polish on a model
|
||||||
|
# call. nginx's 60s default would 504 them mid-work, and the waited run is
|
||||||
|
# cancelled on the disconnect.
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# All other requests go to frontend
|
# All other requests go to frontend
|
||||||
|
|||||||
@ -168,6 +168,11 @@ http {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
|
|
||||||
|
# Installing a .skill archive runs one LLM security scan per file, and a
|
||||||
|
# custom-skill edit or rollback runs one more; none sets its own timeout.
|
||||||
|
# The upload endpoint above already allows 600s for the same work.
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Custom API: Agents endpoint
|
# Custom API: Agents endpoint
|
||||||
@ -315,6 +320,11 @@ http {
|
|||||||
# runs as a non-root user (e.g. local development).
|
# runs as a non-root user (e.g. local development).
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
|
|
||||||
|
# /api/runs/wait blocks on a whole run and /api/input-polish on a model
|
||||||
|
# call. nginx's 60s default would 504 them mid-work, and the waited run is
|
||||||
|
# cancelled on the disconnect.
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# All other requests go to frontend
|
# All other requests go to frontend
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user