deer-flow/backend/tests/test_subagent_capacity_config.py
Hyeonsang Cho af67738521
fix(agents): treat a null max_total_subagents override as unset (#6088)
* fix(agents): treat a null max_total_subagents override as unset

An API caller sending "max_total_subagents": null in the run context
failed that run with a TypeError: the key was present, so
dict.get(key, default) returned None and building SubagentLimitMiddleware
compared None with an int.

Resolve the per-run delegation cap through one helper,
effective_total_subagents_per_run, which treats None as "use
subagents.max_total_per_run" and clamps to 1-50. The lead agent's
middleware and assembly paths, DeerFlowClient, and the system prompt all
use it, so the extension host policy and release policy now report the
enforced cap instead of an out-of-range request.

* docs(changelog): reference #6088 in the null max_total_subagents entry
2026-09-30 16:10:29 +08:00

68 lines
3.0 KiB
Python

from types import SimpleNamespace
import pytest
from pydantic import ValidationError
from deerflow.config.app_config import AppConfig
from deerflow.config.reload_boundary import STARTUP_ONLY_FIELDS, STARTUP_ONLY_PREFIX
from deerflow.config.subagent_batches_config import SubagentBatchesConfig
from deerflow.config.subagent_runtime_config import SubagentRuntimeConfig
from deerflow.config.subagents_config import SubagentsAppConfig, effective_subagent_concurrency, effective_total_subagents_per_run
def test_subagent_runtime_defaults_are_safe_and_bounded() -> None:
config = SubagentRuntimeConfig()
assert config.max_running == 3
assert config.max_queued == 64
assert config.admission_policy == "queue"
assert config.queue_timeout_seconds == 300
with pytest.raises(ValidationError):
SubagentRuntimeConfig(max_running=0)
with pytest.raises(ValidationError):
SubagentRuntimeConfig(max_running=65)
with pytest.raises(ValidationError):
SubagentRuntimeConfig(max_queued=10_001)
def test_subagent_batch_defaults_separate_total_live_and_running() -> None:
config = SubagentBatchesConfig()
assert config.enabled is False
assert config.max_items_per_batch == 5_000
assert config.default_max_live_items == 100
assert config.default_max_running_items == 3
with pytest.raises(ValidationError):
SubagentBatchesConfig(default_max_live_items=5, default_max_running_items=6)
with pytest.raises(ValidationError):
SubagentBatchesConfig(max_live_items_per_batch=4, default_max_live_items=5)
def test_subagent_capacity_sections_are_startup_only() -> None:
for name in ("subagent_runtime", "subagent_batches"):
assert name in STARTUP_ONLY_FIELDS
description = AppConfig.model_fields[name].description or ""
assert description.startswith(STARTUP_ONLY_PREFIX)
def test_ordinary_task_limit_never_advertises_more_than_real_process_slots() -> None:
config = SimpleNamespace(subagent_runtime=SubagentRuntimeConfig(max_running=8))
assert effective_subagent_concurrency(None, config) == 8
assert effective_subagent_concurrency(4, config) == 4
assert effective_subagent_concurrency(50, config) == 8
def test_ordinary_task_limit_uses_frozen_execution_capacity_after_reload() -> None:
reloaded_config = SimpleNamespace(subagent_runtime=SubagentRuntimeConfig(max_running=12))
assert effective_subagent_concurrency(None, reloaded_config, execution_capacity=3) == 3
assert effective_subagent_concurrency(10, reloaded_config, execution_capacity=3) == 3
def test_per_run_delegation_cap_treats_explicit_null_as_unset() -> None:
config = SimpleNamespace(subagents=SubagentsAppConfig(max_total_per_run=7))
assert effective_total_subagents_per_run(None, config) == 7
assert effective_total_subagents_per_run(5, config) == 5
assert effective_total_subagents_per_run(99, config) == 50
assert effective_total_subagents_per_run(0, config) == 1
assert effective_total_subagents_per_run(None, SimpleNamespace()) == 6