mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(config): reject boolean integer settings in app_config (#6171)
* fix(config): reject boolean integer settings in app_config Follows the loop-detection and circuit-breaker guards: YAML booleans next to integer knobs load cleanly, and Pydantic coerces booleans to 1/0. - recursion_limit coerced from `true` to 1, so every Gateway run that does not supply its own limit hit the LangGraph recursion ceiling at the first super-step (max_recursion_limit likewise clamped the ceiling to 1). - The five llm_call integers coerced similarly: retry_max_attempts as `true` disabled retries, max_concurrent_calls as `true` serialized the pipeline. All seven integer fields now fail configuration loading with a field-specific error, while valid integers and numeric strings keep their existing behavior. Mirrors the loop-detection helper (#6017) and the in-file circuit-breaker validator. Signed-off-by: yetuge <2219677952@qq.com> * docs(changelog): record the app-config boolean rejection Changelog entries for #6171, mirroring the #6017 entry format in both languages. Signed-off-by: yetuge <2219677952@qq.com> * test(config): pin the False-side coercion regression in the boolean guards Per review: the tests only exercised `True`. On the `ge=1` fields `false` already failed the constraint pre-fix, but on the `ge=0` fields (`max_concurrent_calls`, the two delay fields) `false` coerced silently to the harmless-looking `0`, disabling the concurrency cap with no error. Parametrize both boolean tests over `[True, False]` to lock that regression in as well. Signed-off-by: yetuge <2219677952@qq.com> --------- Signed-off-by: yetuge <2219677952@qq.com>
This commit is contained in:
parent
6176b057a2
commit
6e76c14384
@ -513,6 +513,14 @@ This release closes that milestone with **301 merged pull requests**.
|
||||
limits to one. All integer threshold fields now fail configuration loading
|
||||
with a field-specific error while valid integers and numeric strings retain
|
||||
their existing behavior.([#6017])
|
||||
- **agents:** App-config integer settings now reject YAML booleans instead of
|
||||
coercing `true` to `1`. A configuration such as `recursion_limit: true`
|
||||
previously made every Gateway run that does not supply its own limit hit the
|
||||
LangGraph recursion ceiling at the first super-step, and booleans on the
|
||||
`llm_call` integers (`retry_max_attempts`, `max_concurrent_calls`, the two
|
||||
backoff delays) collapsed retries and the concurrency cap to one. All seven
|
||||
integer fields now fail configuration loading with a field-specific error
|
||||
while valid integers and numeric strings retain their existing behavior.([#6171])
|
||||
- **uploads:** Converted Markdown ownership is now recorded when a document is
|
||||
converted. `list_uploaded_files` hides only verified conversion outputs, and
|
||||
document outlines use only the recorded companion; a user-uploaded Markdown
|
||||
@ -7611,3 +7619,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#6201]: https://github.com/bytedance/deer-flow/pull/6201
|
||||
[#6202]: https://github.com/bytedance/deer-flow/pull/6202
|
||||
|
||||
[#6171]: https://github.com/bytedance/deer-flow/pull/6171
|
||||
|
||||
@ -454,6 +454,12 @@
|
||||
跟踪窗口、工具频率和按工具覆盖项中的布尔值也会把对应限制缩小为
|
||||
1。现在所有整数阈值字段都会在配置加载阶段按字段名报错,同时保持
|
||||
有效整数和数字字符串的既有行为。([#6017])
|
||||
- **智能体:** 应用配置的整数设置现在会拒绝 YAML 布尔值,而不是把 `true`
|
||||
静默转换为 `1`。此前若配置 `recursion_limit: true`,所有未自带递归上限的
|
||||
Gateway 运行都会在第一个 LangGraph 超级步就触达递归上限;`llm_call` 下的
|
||||
整数字段(`retry_max_attempts`、`max_concurrent_calls` 与两个退避延迟)中的
|
||||
布尔值也会把重试次数和并发上限缩小为 1。现在全部七个整数字段都会在配置
|
||||
加载阶段按字段名报错,同时保持有效整数和数字字符串的既有行为。([#6171])
|
||||
- **上传:** 文档转换时现在会记录原文件与 Markdown 的归属关系。
|
||||
`list_uploaded_files` 只隐藏归属已验证的转换文件,文档大纲也只读取
|
||||
记录中指定的 Markdown;用户自行上传的同名文件会正常显示,不会被
|
||||
@ -6360,5 +6366,6 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#6135]: https://github.com/bytedance/deer-flow/pull/6135
|
||||
[#6138]: https://github.com/bytedance/deer-flow/pull/6138
|
||||
[#6140]: https://github.com/bytedance/deer-flow/pull/6140
|
||||
[#6171]: https://github.com/bytedance/deer-flow/pull/6171
|
||||
[#6201]: https://github.com/bytedance/deer-flow/pull/6201
|
||||
[#6202]: https://github.com/bytedance/deer-flow/pull/6202
|
||||
|
||||
@ -7,7 +7,7 @@ from typing import Any, Literal, Self
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, ValidationInfo, field_validator, model_validator
|
||||
|
||||
from deerflow.config.acp_config import ACPAgentConfig, load_acp_config_from_dict
|
||||
from deerflow.config.agent_storage_config import AgentStorageConfig
|
||||
@ -74,6 +74,13 @@ CONFIG_FILE_DATABASE_DEFAULTS = {
|
||||
}
|
||||
|
||||
|
||||
def _reject_boolean_int(value: object, info: ValidationInfo) -> object:
|
||||
"""Reject YAML `true`/`false` before Pydantic coerces them to `1`/`0`."""
|
||||
if isinstance(value, bool):
|
||||
raise ValueError(f"{info.field_name} must be an integer, not a boolean")
|
||||
return value
|
||||
|
||||
|
||||
class CircuitBreakerConfig(BaseModel):
|
||||
"""Configuration for the LLM Circuit Breaker."""
|
||||
|
||||
@ -152,6 +159,18 @@ class LlmCallConfig(BaseModel):
|
||||
),
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"max_concurrent_calls",
|
||||
"retry_max_attempts",
|
||||
"retry_base_delay_ms",
|
||||
"retry_cap_delay_ms",
|
||||
"burst_retry_base_delay_ms",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
def _reject_boolean_llm_call_settings(cls, value: object, info: ValidationInfo) -> object:
|
||||
return _reject_boolean_int(value, info)
|
||||
|
||||
|
||||
class LoggingEnhanceConfig(BaseModel):
|
||||
"""Request trace logging enhancement settings.
|
||||
@ -250,6 +269,12 @@ class AppConfig(BaseModel):
|
||||
ge=1,
|
||||
description="Hard server-side ceiling for configured defaults and client-supplied run recursion_limit values. Values above this are clamped; prevents runaway LangGraph super-steps (LLM cost / DoS).",
|
||||
)
|
||||
|
||||
@field_validator("recursion_limit", "max_recursion_limit", mode="before")
|
||||
@classmethod
|
||||
def _reject_boolean_recursion_limits(cls, value: object, info: ValidationInfo) -> object:
|
||||
return _reject_boolean_int(value, info)
|
||||
|
||||
models: list[ModelConfig] = Field(default_factory=list, description="Available models")
|
||||
sandbox: SandboxConfig = Field(
|
||||
description=format_field_description(
|
||||
|
||||
51
backend/tests/test_app_config_boolean_thresholds.py
Normal file
51
backend/tests/test_app_config_boolean_thresholds.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""YAML booleans must not coerce into the integer thresholds in app_config.
|
||||
|
||||
`config.yaml` legitimately mixes booleans (`enabled: true`) with integer knobs on
|
||||
neighbouring lines, so a typo like `recursion_limit: true` loads cleanly — and
|
||||
Pydantic coerces it to `1`, which for the recursion limit means every Gateway run
|
||||
dies at the first LangGraph super-step. Mirrors the loop-detection and circuit
|
||||
breaker guards already in the repo.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from deerflow.config.app_config import AppConfig, LlmCallConfig
|
||||
from deerflow.config.sandbox_config import SandboxConfig
|
||||
|
||||
LLM_CALL_INT_FIELDS = (
|
||||
"max_concurrent_calls",
|
||||
"retry_max_attempts",
|
||||
"retry_base_delay_ms",
|
||||
"retry_cap_delay_ms",
|
||||
"burst_retry_base_delay_ms",
|
||||
)
|
||||
|
||||
|
||||
def _app_config(**overrides: object) -> AppConfig:
|
||||
return AppConfig(sandbox=SandboxConfig(use="local"), **overrides)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [True, False])
|
||||
@pytest.mark.parametrize("field", LLM_CALL_INT_FIELDS)
|
||||
def test_llm_call_config_rejects_boolean_integers(field: str, value: bool) -> None:
|
||||
with pytest.raises(ValidationError, match="must be an integer, not a boolean"):
|
||||
LlmCallConfig(**{field: value})
|
||||
|
||||
|
||||
def test_llm_call_config_keeps_numeric_inputs() -> None:
|
||||
config = LlmCallConfig(max_concurrent_calls="4", retry_max_attempts=3)
|
||||
assert config.max_concurrent_calls == 4
|
||||
assert config.retry_max_attempts == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [True, False])
|
||||
@pytest.mark.parametrize("field", ["recursion_limit", "max_recursion_limit"])
|
||||
def test_recursion_limits_reject_booleans(field: str, value: bool) -> None:
|
||||
with pytest.raises(ValidationError, match="must be an integer, not a boolean"):
|
||||
_app_config(**{field: value})
|
||||
|
||||
|
||||
def test_recursion_limits_keep_numeric_inputs() -> None:
|
||||
config = _app_config(recursion_limit="25")
|
||||
assert config.recursion_limit == 25
|
||||
Loading…
x
Reference in New Issue
Block a user