mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-25 07:28:07 +00:00
fix(config): sync _memory_config with AppConfig auto-reload (#4208)
* fix(config): sync _memory_config with AppConfig auto-reload get_memory_config() now calls get_app_config() before returning the cached _memory_config singleton. get_app_config() auto-reloads when the config file signature changes, and its _apply_singleton_configs() calls load_memory_config_from_dict() to populate _memory_config. Without this side effect, changing memory.mode in config.yaml (e.g. from "middleware" to "tool") would never take effect at runtime because _memory_config retained the stale value. Only call get_app_config() when _app_config has already been loaded to avoid picking up a config file as a side effect of the first access to get_memory_config(), which would break callers that expect module-level defaults (e.g. unit tests). Catch FileNotFoundError for environments without a config file. Closes #4204. * fix(memory): broaden config-reload guard and add isolated-path tests Addresses two review findings from willem-bd: 1. Broken-config crash risk: broaden the except from FileNotFoundError to Exception so transiently broken config.yaml (YAMLError, ValidationError, ValueError) falls back to the last-good singleton instead of crashing per-turn hot paths (memory_middleware, summarization_hook, lead_agent/prompt.py). 2. Missing regression test: add test_get_memory_config_self_syncs_... (calls get_memory_config() after file mutation with no intervening get_app_config() call) and test_get_memory_config_falls_back_on_... (verifies broken config does not crash, returns cached value).
This commit is contained in:
parent
8153e68eb8
commit
9eebc6a9e5
@ -121,7 +121,36 @@ _memory_config: MemoryConfig = MemoryConfig()
|
||||
|
||||
|
||||
def get_memory_config() -> MemoryConfig:
|
||||
"""Get the current memory configuration."""
|
||||
"""Get the current memory configuration.
|
||||
|
||||
``_memory_config`` is only refreshed as a side effect of ``get_app_config()``
|
||||
reloading (via ``_apply_singleton_configs`` -> ``load_memory_config_from_dict``).
|
||||
A reader that reaches memory config without going through ``get_app_config()``
|
||||
first -- e.g. the agent factory deciding whether to bind the memory tools --
|
||||
would otherwise see a stale ``memory.mode`` after a ``config.yaml`` edit, even
|
||||
though ``memory.*`` is documented as hot-reloadable. Trigger the same
|
||||
signature-checked reload here so the singleton follows the config file.
|
||||
|
||||
If ``get_app_config()`` has never been called (``_app_config`` is ``None``),
|
||||
there is no stale config to refresh, so we keep the pre-existing behaviour
|
||||
of returning the in-memory singleton. This avoids picking up a config file
|
||||
as a side effect of the first access to ``get_memory_config()``, which would
|
||||
break callers that expect module-level defaults (e.g. unit tests).
|
||||
"""
|
||||
# Lazy import: app_config imports this module, so a top-level import cycles.
|
||||
from .app_config import _app_config, get_app_config
|
||||
|
||||
if _app_config is not None:
|
||||
try:
|
||||
get_app_config()
|
||||
except Exception:
|
||||
# If the config file is transiently broken (invalid YAML, schema
|
||||
# violation, missing env var, etc.), keep the last-good singleton
|
||||
# so an in-flight turn completes normally instead of crashing.
|
||||
logger.warning(
|
||||
"Failed to reload app config from get_memory_config(); falling back to cached memory config.",
|
||||
exc_info=True,
|
||||
)
|
||||
return _memory_config
|
||||
|
||||
|
||||
|
||||
@ -611,3 +611,63 @@ def test_get_app_config_does_not_mutate_singletons_when_reload_validation_fails(
|
||||
assert get_store() is initial_store
|
||||
finally:
|
||||
_reset_config_singletons()
|
||||
|
||||
|
||||
def test_get_memory_config_self_syncs_without_prior_get_app_config(tmp_path, monkeypatch):
|
||||
"""get_memory_config() triggers reload when file changes without prior get_app_config().
|
||||
|
||||
Background memory paths (updater/queue/storage) never call get_app_config()
|
||||
directly. This test pins the fix: calling get_memory_config() in isolation
|
||||
— after mutating the file, with no intervening get_app_config() — reflects
|
||||
the new value.
|
||||
"""
|
||||
config_path = tmp_path / "config.yaml"
|
||||
extensions_path = tmp_path / "extensions_config.json"
|
||||
_write_extensions_config(extensions_path)
|
||||
|
||||
_write_config_with_sections(config_path, {"memory": {"enabled": False}})
|
||||
|
||||
monkeypatch.setenv("DEER_FLOW_CONFIG_PATH", str(config_path))
|
||||
monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(extensions_path))
|
||||
reset_app_config()
|
||||
|
||||
try:
|
||||
get_app_config()
|
||||
assert get_memory_config().enabled is False
|
||||
|
||||
_write_config_with_sections(config_path, {"memory": {"enabled": True}})
|
||||
next_mtime = config_path.stat().st_mtime + 5
|
||||
os.utime(config_path, (next_mtime, next_mtime))
|
||||
|
||||
assert get_memory_config().enabled is True
|
||||
finally:
|
||||
_reset_config_singletons()
|
||||
|
||||
|
||||
def test_get_memory_config_falls_back_on_broken_config(tmp_path, monkeypatch):
|
||||
"""get_memory_config() does not crash on transiently broken config.yaml.
|
||||
|
||||
get_app_config() can raise yaml.YAMLError, ValidationError, or ValueError.
|
||||
get_memory_config() catches them and returns the last-good singleton.
|
||||
"""
|
||||
config_path = tmp_path / "config.yaml"
|
||||
extensions_path = tmp_path / "extensions_config.json"
|
||||
_write_extensions_config(extensions_path)
|
||||
|
||||
_write_config_with_sections(config_path, {"memory": {"enabled": False}})
|
||||
|
||||
monkeypatch.setenv("DEER_FLOW_CONFIG_PATH", str(config_path))
|
||||
monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(extensions_path))
|
||||
reset_app_config()
|
||||
|
||||
try:
|
||||
get_app_config()
|
||||
assert get_memory_config().enabled is False
|
||||
|
||||
config_path.write_text("memory: {enabled: true\n")
|
||||
next_mtime = config_path.stat().st_mtime + 5
|
||||
os.utime(config_path, (next_mtime, next_mtime))
|
||||
|
||||
assert get_memory_config().enabled is False
|
||||
finally:
|
||||
_reset_config_singletons()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user