mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(channels): honor the configured recursion limit in IM runs (#7521)
* fix(channels): honor the configured recursion limit in IM runs Every IM channel run asked the Gateway for recursion_limit 100, because the channel manager seeded each run config with it. The Gateway honors a request value, so the top-level recursion_limit never reached IM runs, and a budget of 100 could stop a run after a handful of tool-calling turns (six in one measurement with the default lead-agent middleware; the count changes with the middleware chain). IM runs now send no recursion_limit of their own and get the Gateway's configured default, clamped by max_recursion_limit, like Gateway API runs. A session config value still takes precedence, and GitHub channel runs keep their floor of 250. The README and config.example.yaml IM examples no longer pin session.config.recursion_limit to 100. * docs(changelog): reference #7521 for the IM recursion-limit fix --------- Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
d58d6251d5
commit
c36bb699de
@ -1397,6 +1397,13 @@ As of 2026-10-21, the milestone includes **829 merged pull requests**.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **channels:** IM channel runs now use the top-level `recursion_limit`, as
|
||||
Gateway API runs do, instead of a fixed `100` that could end a multi-step
|
||||
task after a few tool-calling turns without an answer. A `recursion_limit`
|
||||
in an IM channel's `session.config` still takes precedence; earlier README
|
||||
examples set it to `100`, so delete that line from `config.yaml` to follow
|
||||
the top-level value. GitHub channel runs are unchanged. ([#7521])
|
||||
|
||||
- **sandbox:** The AIO provider's async acquire no longer checks the thread's
|
||||
skill directory on the event loop. Picking the sandbox ID ran a filesystem
|
||||
`stat` and path resolution inline on every thread acquire, which stalls the
|
||||
@ -13091,3 +13098,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#7445]: https://github.com/bytedance/deer-flow/pull/7445
|
||||
[#7449]: https://github.com/bytedance/deer-flow/pull/7449
|
||||
[#7507]: https://github.com/bytedance/deer-flow/pull/7507
|
||||
[#7521]: https://github.com/bytedance/deer-flow/pull/7521
|
||||
|
||||
@ -1112,6 +1112,11 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **渠道:** IM 渠道的运行现在与 Gateway API 运行一样使用顶层 `recursion_limit`,不再固定
|
||||
发送 `100`;此前多步任务可能在几轮工具调用后就中止,用户收不到回答。IM 渠道
|
||||
`session.config` 中的 `recursion_limit` 仍然优先;早先的 README 示例把它设为 `100`,
|
||||
如需跟随顶层设置,请从 `config.yaml` 删除这一行。GitHub 渠道的运行不变。([#7521])
|
||||
|
||||
- **沙箱:** AIO 提供方的异步 acquire 不再在事件循环上检查线程的技能目录。此前每次
|
||||
线程 acquire 选择沙箱 ID 时,都会在事件循环上直接执行文件系统 `stat` 和路径解析;
|
||||
`DEER_FLOW_HOME` 位于较慢或网络存储上时会阻塞事件循环。现在这一步与 acquire 的
|
||||
@ -10451,3 +10456,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#7445]: https://github.com/bytedance/deer-flow/pull/7445
|
||||
[#7449]: https://github.com/bytedance/deer-flow/pull/7449
|
||||
[#7507]: https://github.com/bytedance/deer-flow/pull/7507
|
||||
[#7521]: https://github.com/bytedance/deer-flow/pull/7521
|
||||
|
||||
@ -537,8 +537,10 @@ Gateway runs use the top-level `recursion_limit` in `config.yaml` when an API
|
||||
request does not provide one. The default is `100`; valid per-request values
|
||||
take precedence, and `max_recursion_limit` (default `1000`) caps both. Changes
|
||||
apply to the next run without restarting the Gateway. This top-level setting
|
||||
applies to Gateway API runs; IM channel and embedded `DeerFlowClient` runs
|
||||
retain their own defaults and per-call override paths.
|
||||
applies to Gateway API and IM channel runs. A `recursion_limit` in an IM
|
||||
channel's `session.config` takes precedence, and GitHub channel runs keep
|
||||
their own floor. Embedded `DeerFlowClient` runs retain their own defaults and
|
||||
per-call override paths.
|
||||
|
||||
A custom frontend can add an optional `context.client` to a Gateway run request:
|
||||
a client name, capabilities such as `artifacts` or `charts`, and display
|
||||
@ -1192,8 +1194,6 @@ channels:
|
||||
# Optional: global session defaults for all mobile channels
|
||||
session:
|
||||
assistant_id: lead_agent # or a custom agent name; custom agents are routed via lead_agent + agent_name
|
||||
config:
|
||||
recursion_limit: 100
|
||||
context:
|
||||
thinking_enabled: true
|
||||
is_plan_mode: false
|
||||
|
||||
@ -328,8 +328,6 @@ channels:
|
||||
# Optional: global session defaults for all mobile channels
|
||||
session:
|
||||
assistant_id: lead_agent
|
||||
config:
|
||||
recursion_limit: 100
|
||||
context:
|
||||
thinking_enabled: true
|
||||
is_plan_mode: false
|
||||
|
||||
@ -315,8 +315,6 @@ channels:
|
||||
# オプション: 全モバイルチャネルのグローバルセッションデフォルト
|
||||
session:
|
||||
assistant_id: lead_agent
|
||||
config:
|
||||
recursion_limit: 100
|
||||
context:
|
||||
thinking_enabled: true
|
||||
is_plan_mode: false
|
||||
|
||||
@ -378,7 +378,7 @@ Docker 빌드는 기본적으로 업스트림 `uv` 레지스트리를 사용합
|
||||
|
||||
백엔드 프로세스는 다음 설정 접근 시 `config.yaml` 변경 사항을 자동으로 반영하므로, 개발 중 모델 메타데이터를 업데이트할 때 수동으로 재시작할 필요가 없습니다.
|
||||
|
||||
Gateway 실행(run)은 API 요청이 값을 지정하지 않은 경우 최상위 `recursion_limit` 값(`config.yaml`에 설정)을 사용합니다. 기본값은 `100`이며, 유효한 요청별 값이 우선하고 `max_recursion_limit`(기본값 `1000`)이 두 값 모두의 상한이 됩니다. 변경 사항은 Gateway를 재시작하지 않아도 다음 실행부터 적용됩니다. 이 최상위 설정은 Gateway API 실행에 적용되며, IM 채널 실행과 임베디드 `DeerFlowClient` 실행은 각자의 기본값과 호출별 재정의 경로를 그대로 유지합니다.
|
||||
Gateway 실행(run)은 API 요청이 값을 지정하지 않은 경우 최상위 `recursion_limit` 값(`config.yaml`에 설정)을 사용합니다. 기본값은 `100`이며, 유효한 요청별 값이 우선하고 `max_recursion_limit`(기본값 `1000`)이 두 값 모두의 상한이 됩니다. 변경 사항은 Gateway를 재시작하지 않아도 다음 실행부터 적용됩니다. 이 최상위 설정은 Gateway API 실행과 IM 채널 실행에 적용됩니다. IM 채널의 `session.config`에 지정한 `recursion_limit`이 우선하며, GitHub 채널 실행은 자체 하한을 유지합니다. 임베디드 `DeerFlowClient` 실행은 각자의 기본값과 호출별 재정의 경로를 그대로 유지합니다.
|
||||
체크포인트 저장소 설정인 `database.checkpoint_channel_mode`와 `database.checkpoint_delta.snapshot_frequency`(기본값 `10`)는 예외입니다. 두 설정 모두 프로세스가 처음 에이전트를 빌드할 때(`DeerFlowClient`를 통한 경우 포함) 고정되므로, 안전하게 변경하려면 프로세스를 재시작해야 합니다.
|
||||
|
||||
선택 사항인 `database.checkpoint_cache` 섹션(delta 채널 모드 전용)은 구체화된 체크포인트 이력을 캐시합니다. `type`은 `memory`(기본값) 또는 `redis`이며, `max_entries: 0`으로 설정하면 캐시가 비활성화됩니다. `redis` 백엔드는 Gateway/비동기 경로 전용이며, 동기 TUI/임베디드 경로는 `memory`만 지원합니다. 이 캐시는 성능만을 위한 것으로 비활성화해도 결과가 동일합니다. 따라서 고정되는 일이 없으며, 하나의 체크포인트 데이터베이스를 공유하는 워커들이 서로 다른 캐시 설정으로 실행되어도 안전합니다. Redis 스레드 정리는 설정된 `key_prefix`를 glob 메타문자까지 포함해 리터럴 텍스트로 취급하므로, 삭제는 해당 네임스페이스와 스레드 안에서만 이루어집니다.
|
||||
@ -706,8 +706,6 @@ channels:
|
||||
# Optional: global session defaults for all mobile channels
|
||||
session:
|
||||
assistant_id: lead_agent # or a custom agent name; custom agents are routed via lead_agent + agent_name
|
||||
config:
|
||||
recursion_limit: 100
|
||||
context:
|
||||
thinking_enabled: true
|
||||
is_plan_mode: false
|
||||
|
||||
@ -335,7 +335,7 @@ O tráfego de controle do sandbox AIO local é sempre direto: endereços de loop
|
||||
|
||||
Os processos do backend pegam as mudanças do `config.yaml` automaticamente no próximo acesso à configuração, então atualizar metadados de modelo não exige reinício manual durante o desenvolvimento.
|
||||
|
||||
Os runs do Gateway usam o `recursion_limit` de nível superior do `config.yaml` quando a requisição de API não informa um. O padrão é `100`; valores válidos por requisição têm precedência, e `max_recursion_limit` (padrão `1000`) limita os dois. As mudanças valem para o próximo run sem reiniciar o Gateway. Essa configuração de nível superior se aplica aos runs da API do Gateway; os runs de canais de IM e do `DeerFlowClient` embutido mantêm os próprios padrões e caminhos de override por chamada. As configurações de armazenamento de checkpoint `database.checkpoint_channel_mode` e `database.checkpoint_delta.snapshot_frequency` (padrão `10`) são exceções: as duas ficam congeladas quando o processo constrói um agente pela primeira vez (inclusive via `DeerFlowClient`) e exigem reinício do processo para serem alteradas com segurança.
|
||||
Os runs do Gateway usam o `recursion_limit` de nível superior do `config.yaml` quando a requisição de API não informa um. O padrão é `100`; valores válidos por requisição têm precedência, e `max_recursion_limit` (padrão `1000`) limita os dois. As mudanças valem para o próximo run sem reiniciar o Gateway. Essa configuração de nível superior se aplica aos runs da API do Gateway e dos canais de IM. Um `recursion_limit` no `session.config` de um canal de IM tem precedência, e os runs do canal GitHub mantêm o próprio piso. Os runs do `DeerFlowClient` embutido mantêm os próprios padrões e caminhos de override por chamada. As configurações de armazenamento de checkpoint `database.checkpoint_channel_mode` e `database.checkpoint_delta.snapshot_frequency` (padrão `10`) são exceções: as duas ficam congeladas quando o processo constrói um agente pela primeira vez (inclusive via `DeerFlowClient`) e exigem reinício do processo para serem alteradas com segurança.
|
||||
|
||||
A seção opcional `database.checkpoint_cache` (apenas no modo de canal delta) faz cache dos históricos de checkpoint materializados: `type` é `memory` (padrão) ou `redis`, e `max_entries: 0` desativa o cache. O backend `redis` funciona só no Gateway/async; o caminho síncrono da TUI e do cliente embutido aceita apenas `memory`. O cache serve só para desempenho, os resultados são idênticos com ele desativado. Por isso ele nunca é congelado, e workers que compartilham o mesmo banco de checkpoints podem usar configurações de cache diferentes sem problema.
|
||||
|
||||
@ -606,8 +606,6 @@ channels:
|
||||
# Optional: global session defaults for all mobile channels
|
||||
session:
|
||||
assistant_id: lead_agent # or a custom agent name; custom agents are routed via lead_agent + agent_name
|
||||
config:
|
||||
recursion_limit: 100
|
||||
context:
|
||||
thinking_enabled: true
|
||||
is_plan_mode: false
|
||||
|
||||
@ -462,8 +462,6 @@ channels:
|
||||
# 可选:所有移动端渠道共用的全局 session 默认值
|
||||
session:
|
||||
assistant_id: lead_agent # 也可以填自定义 agent 名;渠道层会自动转换为 lead_agent + agent_name
|
||||
config:
|
||||
recursion_limit: 100
|
||||
context:
|
||||
thinking_enabled: true
|
||||
is_plan_mode: false
|
||||
|
||||
@ -87,12 +87,13 @@ IN_PROCESS_CACHE_MAX_ENTRIES = 4096
|
||||
MAX_CHANNEL_AGENT_LIST_ITEMS = 50
|
||||
MAX_CHANNEL_AGENT_DESCRIPTION_CHARS = 120
|
||||
|
||||
# Lead-agent recursion budget (LangGraph super-steps for the lead graph only).
|
||||
# This is independent of subagent depth: a `task()` dispatch runs the whole
|
||||
# subagent inside ONE lead tools-node step, and subagents enforce their own
|
||||
# limit via `subagents.max_turns` (see SubagentExecutor). Do not conflate this
|
||||
# 100 with the general-purpose subagent's max_turns.
|
||||
DEFAULT_RUN_CONFIG: dict[str, Any] = {"recursion_limit": 100}
|
||||
# No lead-agent recursion budget here: like Gateway API runs, IM runs get
|
||||
# the Gateway's configured top-level ``recursion_limit`` (clamped by
|
||||
# ``max_recursion_limit``) unless a session ``config.recursion_limit`` sets
|
||||
# one. The budget counts lead-graph super-steps only: a `task()` dispatch runs
|
||||
# the whole subagent inside ONE lead tools-node step, and subagents enforce
|
||||
# their own limit via `subagents.max_turns` (see SubagentExecutor).
|
||||
DEFAULT_RUN_CONFIG: dict[str, Any] = {}
|
||||
DEFAULT_RUN_CONTEXT: dict[str, Any] = {
|
||||
"thinking_enabled": True,
|
||||
"is_plan_mode": False,
|
||||
@ -2190,7 +2191,7 @@ class ChannelManager:
|
||||
if isinstance(override, int) and override > 0:
|
||||
run_config["recursion_limit"] = override
|
||||
else:
|
||||
run_config["recursion_limit"] = max(run_config.get("recursion_limit", 100), policy.default_recursion_limit)
|
||||
run_config["recursion_limit"] = max(run_config.get("recursion_limit", 0), policy.default_recursion_limit)
|
||||
|
||||
return assistant_id, run_config, run_context
|
||||
|
||||
|
||||
@ -53,8 +53,9 @@ class ChannelRunPolicy:
|
||||
for an IM channel).
|
||||
default_recursion_limit: When set, the manager raises
|
||||
``run_config["recursion_limit"]`` to ``max(existing,
|
||||
limit)``. None leaves the global default (100) untouched —
|
||||
interactive chat turns don't need 250 super-steps.
|
||||
limit)``. None keeps the session config's value, if
|
||||
any; without one the run gets the Gateway's configured
|
||||
``recursion_limit``.
|
||||
credentials_provider: Optional async hook that mutates
|
||||
``run_context`` with platform-specific credentials. Called
|
||||
after ``_resolve_run_params``. Exceptions are caught and
|
||||
|
||||
@ -106,7 +106,7 @@ def register_policy() -> None:
|
||||
is_interactive=False,
|
||||
interaction_mode="webhook",
|
||||
# Autonomous coder runs (clone -> edit -> test -> push -> PR)
|
||||
# routinely need more than the 100 super-step interactive ceiling.
|
||||
# routinely need more than 100 super-steps.
|
||||
# Per-agent overrides via GitHubAgentConfig.recursion_limit still
|
||||
# win (read in ChannelManager._resolve_run_params from msg.metadata).
|
||||
default_recursion_limit=250,
|
||||
|
||||
@ -1331,9 +1331,9 @@ def build_run_config(
|
||||
load the matching ``agents/<name>/SOUL.md`` and per-agent config —
|
||||
without it the agent silently runs as the default lead agent.
|
||||
|
||||
This mirrors the channel manager's ``_resolve_run_params`` logic except for
|
||||
the recursion default: Gateway API runs use the configured top-level
|
||||
``recursion_limit``, while IM channel runs retain their own default.
|
||||
This mirrors the channel manager's ``_resolve_run_params`` logic. IM
|
||||
channel runs send no ``recursion_limit`` unless their session config or
|
||||
channel policy sets one, so they get the same configured default here.
|
||||
"""
|
||||
# Lead-agent recursion budget (LangGraph super-steps for the lead graph
|
||||
# only). Independent of subagent depth: a `task()` dispatch runs the whole
|
||||
|
||||
@ -380,9 +380,11 @@ or configured value to a server ceiling (`max_recursion_limit` in `config.yaml`,
|
||||
default `1000`) so a single run cannot execute unbounded graph steps (runaway
|
||||
LLM cost / DoS); invalid or non-positive request values fall back to the
|
||||
configured default. Both top-level fields are read per run, so edits apply to
|
||||
the next request without restarting the Gateway. This top-level setting applies
|
||||
to Gateway API runs only; IM channel and embedded `DeerFlowClient` runs retain
|
||||
their own defaults and override paths.
|
||||
the next request without restarting the Gateway. IM channel runs get the same
|
||||
default unless a channel `session.config` sets `recursion_limit` (GitHub channel
|
||||
runs have their own floor; see
|
||||
[CONFIGURATION.md](CONFIGURATION.md#recursion-limits)); embedded
|
||||
`DeerFlowClient` runs retain their own defaults and override paths.
|
||||
|
||||
**Configurable Options:**
|
||||
- `model_name` (string): Override the default model
|
||||
|
||||
@ -145,9 +145,11 @@ configured default. `max_recursion_limit` (default `1000`) caps both sources to
|
||||
limit runaway LLM cost. Both settings are read per run, so changes apply to the
|
||||
next request without a Gateway restart.
|
||||
|
||||
These settings apply to Gateway API runs. IM channel runs and embedded
|
||||
`DeerFlowClient` runs retain their own defaults and can be overridden through
|
||||
their channel/client-specific configuration or per-call options.
|
||||
These settings also apply to IM channel runs. A `recursion_limit` in an IM
|
||||
channel's `session.config` (for all channels, one channel or one user) takes
|
||||
precedence. The GitHub channel instead uses `250`, or a higher
|
||||
`session.config` value, unless the agent sets `github.recursion_limit`.
|
||||
Embedded `DeerFlowClient` runs retain their own defaults and per-call options.
|
||||
|
||||
### Models
|
||||
|
||||
|
||||
@ -5261,19 +5261,33 @@ class TestResolveRunParamsUserId:
|
||||
assert run_context["channel_user_id"] == "U-platform"
|
||||
|
||||
def test_github_channel_gets_raised_recursion_limit(self):
|
||||
"""Autonomous GitHub coding runs (clone → edit → test → push → PR) need
|
||||
more super-steps than an interactive chat turn. The default
|
||||
``recursion_limit`` of 100 is raised for the github channel only."""
|
||||
"""Autonomous GitHub coding runs (clone → edit → test → push → PR) get
|
||||
a channel floor of 250 super-steps; other channels send no
|
||||
``recursion_limit`` and leave the budget to the Gateway."""
|
||||
manager = self._manager()
|
||||
|
||||
gh_msg = InboundMessage(channel_name="github", chat_id="zhfeng/llm-gateway", user_id="zhfeng", text="hi")
|
||||
_, gh_config, _ = manager._resolve_run_params(gh_msg, "thread-1")
|
||||
assert gh_config["recursion_limit"] >= 250
|
||||
|
||||
# Interactive channels keep the default ceiling.
|
||||
slack_msg = InboundMessage(channel_name="slack", chat_id="C1", user_id="u", text="hi")
|
||||
_, slack_config, _ = manager._resolve_run_params(slack_msg, "thread-1")
|
||||
assert slack_config["recursion_limit"] == 100
|
||||
assert "recursion_limit" not in slack_config
|
||||
|
||||
def test_im_run_uses_gateway_configured_recursion_limit(self):
|
||||
"""An IM run without a session value gets the operator's top-level
|
||||
``recursion_limit``, like Gateway API runs, instead of a fixed 100."""
|
||||
from app.gateway.services import build_run_config
|
||||
from deerflow.config.app_config import AppConfig, reset_app_config, set_app_config
|
||||
|
||||
set_app_config(AppConfig.model_validate({"sandbox": {"use": "deerflow.sandbox.local:LocalSandboxProvider"}, "recursion_limit": 700}))
|
||||
try:
|
||||
manager = self._manager()
|
||||
msg = InboundMessage(channel_name="telegram", chat_id="c", user_id="u", text="hi")
|
||||
_, run_config, _ = manager._resolve_run_params(msg, "thread-1")
|
||||
assert build_run_config("thread-1", run_config, None)["recursion_limit"] == 700
|
||||
finally:
|
||||
reset_app_config()
|
||||
|
||||
def test_github_channel_recursion_limit_respects_higher_override(self):
|
||||
"""An explicit higher recursion_limit in channel/user config must not be
|
||||
|
||||
@ -3119,8 +3119,6 @@ run_ownership:
|
||||
# # Optional: default mobile/session settings for all IM channels
|
||||
# session:
|
||||
# assistant_id: lead_agent # or a custom agent name; custom agents route via lead_agent + agent_name
|
||||
# config:
|
||||
# recursion_limit: 100
|
||||
# context:
|
||||
# thinking_enabled: true
|
||||
# is_plan_mode: false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user