mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-19 19:16:17 +00:00
fix(nginx): allow model-bound /api/threads requests past 60 seconds (#5505)
* fix(nginx): allow model-bound /api/threads requests past 60 seconds The browser calls /api/threads/* directly rather than through /api/langgraph/, and the generic `location ~ ^/api/threads` block set no proxy_read_timeout, so nginx's 60s default applied. /compact and /suggestions hold the response open for a whole model call, and /runs/wait for a whole run. Past 60s nginx returned 504 mid-work: the compaction still committed behind the failed request, and /runs/wait cancelled its run on the disconnect (on_disconnect defaults to cancel). Allow 600s on that location, matching /api/langgraph/, in all three copies of the nginx config: Docker, local dev, and the Helm ConfigMap. The regression test parses the active directive per config, so a missing, commented-out, lowered, or misplaced timeout fails. * docs(changelog): link the nginx /api/threads timeout entry to #5505 --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
22ae3d0e95
commit
3cfc9c58fd
10
CHANGELOG.md
10
CHANGELOG.md
@ -941,6 +941,15 @@ This release closes that milestone with **765 merged pull requests**.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **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
|
||||
no `proxy_read_timeout`, so nginx's 60-second default applied while
|
||||
`/api/langgraph/` allowed 600. A slow `/compact` returned 504 while Gateway
|
||||
kept going and still saved the compaction, so the UI showed an error for
|
||||
work that had been applied, inviting a retry that compacts it again.
|
||||
`/suggestions` hit the same limit, and `/runs/wait` cancelled its run when
|
||||
nginx dropped the connection. The Docker, local, and Helm configs now allow
|
||||
600 seconds on that location. ([#5505])
|
||||
- **middleware:** Stop loop detection from cutting off an agent that pages
|
||||
through a file. `read_file` calls were keyed by 200-line buckets, so every
|
||||
read shorter than a bucket collapsed onto its neighbours: five sequential
|
||||
@ -4245,3 +4254,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#5496]: https://github.com/bytedance/deer-flow/pull/5496
|
||||
[#5501]: https://github.com/bytedance/deer-flow/pull/5501
|
||||
[#5504]: https://github.com/bytedance/deer-flow/pull/5504
|
||||
[#5505]: https://github.com/bytedance/deer-flow/pull/5505
|
||||
|
||||
@ -727,6 +727,11 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **nginx:** 需要等待模型调用的线程路由不再在 60 秒时失败。浏览器直接调用 `/api/threads/*`,
|
||||
而该 location 没有设置 `proxy_read_timeout`,因此沿用 nginx 默认的 60 秒,而 `/api/langgraph/`
|
||||
允许 600 秒。较慢的 `/compact` 会返回 504,但 Gateway 仍会继续执行并保存压缩结果,于是 UI
|
||||
对已经生效的操作显示错误,诱使用户重试并再次压缩。`/suggestions` 也受同一限制,`/runs/wait`
|
||||
则会在 nginx 断开连接时取消其运行。Docker、本地开发与 Helm 配置现在都为该 location 允许 600 秒。([#5505])
|
||||
- **中间件:** 循环检测不再中断正在分段读取文件的智能体。此前 `read_file` 调用按 200 行分桶作为
|
||||
键,因此任何短于一个桶的读取都会与相邻读取塌缩到同一个键:连续五次 40 行读取会哈希成相同值并
|
||||
触发硬停止,运行被迫给出最终答复并带上 `stop_reason=loop_capped`——而这恰恰是 `read_file` 自身
|
||||
@ -3477,3 +3482,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#5496]: https://github.com/bytedance/deer-flow/pull/5496
|
||||
[#5501]: https://github.com/bytedance/deer-flow/pull/5501
|
||||
[#5504]: https://github.com/bytedance/deer-flow/pull/5504
|
||||
[#5505]: https://github.com/bytedance/deer-flow/pull/5505
|
||||
|
||||
@ -52,6 +52,11 @@ _MAX_EXPECTED_BODY_SIZE_BYTES = 100 * 1024 * 1024
|
||||
|
||||
_SIZE_MULTIPLIERS = {"": 1, "k": 1024, "m": 1024**2, "g": 1024**3}
|
||||
|
||||
# The read timeout /api/langgraph/ already allows for a model-bound request.
|
||||
_MIN_BLOCKING_READ_TIMEOUT_SECONDS = 600
|
||||
|
||||
_DURATION_MULTIPLIERS = {"": 1, "s": 1, "m": 60, "h": 3600}
|
||||
|
||||
|
||||
def _read(path: str) -> str:
|
||||
return (REPO_ROOT / path).read_text(encoding="utf-8")
|
||||
@ -88,6 +93,14 @@ def _parse_body_size_bytes(block: str) -> int:
|
||||
return int(value) * _SIZE_MULTIPLIERS[unit.lower()]
|
||||
|
||||
|
||||
def _parse_read_timeout_seconds(block: str) -> int:
|
||||
# Anchored to the start of a line so a commented-out directive does not count.
|
||||
match = re.search(r"^\s*proxy_read_timeout\s+(\d+)([smh]?)\s*;", block, re.M)
|
||||
assert match, "no active proxy_read_timeout directive"
|
||||
value, unit = match.groups()
|
||||
return int(value) * _DURATION_MULTIPLIERS[unit]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
||||
def test_langgraph_route_disables_request_buffering(path):
|
||||
content = _read(path)
|
||||
@ -130,6 +143,22 @@ def test_uploads_route_still_has_its_own_body_size_settings(path):
|
||||
assert "proxy_request_buffering off;" in block
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", NGINX_CONFIGS)
|
||||
def test_threads_route_outlasts_blocking_gateway_calls(path):
|
||||
"""The browser calls ``/api/threads/*`` directly, not through
|
||||
``/api/langgraph/``. Routes such as ``/compact`` and ``/suggestions`` hold
|
||||
the response open for a whole model call, and ``/runs/wait`` for a whole
|
||||
run. Under nginx's 60s default the client gets a 504 mid-work: the
|
||||
compaction still commits behind the failed request, and ``/runs/wait``
|
||||
cancels its run on the disconnect."""
|
||||
content = _read(path)
|
||||
block = _extract_location_block(content, "~ ^/api/threads")
|
||||
|
||||
timeout_seconds = _parse_read_timeout_seconds(block)
|
||||
|
||||
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_skills_upload_route_allows_archive_plus_multipart_framing(path):
|
||||
"""The upload route must stream archives and allow slow validation."""
|
||||
|
||||
@ -121,10 +121,12 @@ secrets:
|
||||
|
||||
The default ingress annotations permit a 100 MiB local `.skill` archive plus
|
||||
multipart framing, stream request bodies without ingress buffering, and allow
|
||||
up to 600 seconds for validation. If you replace `ingress.annotations`,
|
||||
preserve equivalent size, streaming, and response-timeout settings for your
|
||||
ingress controller or local skill uploads may fail before DeerFlow completes
|
||||
the installation.
|
||||
up to 600 seconds for a response, which skill validation and thread requests
|
||||
that wait on a model call (such as `/compact`) both need. If you replace
|
||||
`ingress.annotations`, preserve equivalent size, streaming, and
|
||||
response-timeout settings for your ingress controller, or local skill uploads
|
||||
may fail before DeerFlow completes the installation and those thread requests
|
||||
may time out while Gateway is still working.
|
||||
|
||||
Provide your model config under `config` (keep secrets as `$VAR` references —
|
||||
they resolve from the `secrets` map):
|
||||
|
||||
@ -163,6 +163,10 @@ data:
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||
# /compact and /suggestions hold the response open for a model call,
|
||||
# /runs/wait for a whole run. nginx's 60s default would 504 them mid-work:
|
||||
# the compaction still commits, and the waited run is cancelled.
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location /docs {
|
||||
|
||||
@ -225,6 +225,10 @@ http {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||
|
||||
# /compact and /suggestions hold the response open for a model call,
|
||||
# /runs/wait for a whole run. nginx's 60s default would 504 them mid-work:
|
||||
# the compaction still commits, and the waited run is cancelled.
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
# API Documentation: Swagger UI
|
||||
|
||||
@ -234,6 +234,11 @@ http {
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# /compact and /suggestions hold the response open for a model call,
|
||||
# /runs/wait for a whole run. nginx's 60s default would 504 them mid-work:
|
||||
# the compaction still commits, and the waited run is cancelled.
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
# API Documentation: Swagger UI
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user