mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-21 10:15:47 +00:00
fix(memory): treat explicit null backend_config values as omitted in DeerMemConfig (#4217)
config.example.yaml ships backend_config.model: as a bare key whose children
are all comments, which YAML parses to None (make config-upgrade then writes
an explicit model: null). DeerMemConfig.model is a non-Optional field with a
default, so from_backend_config(**{"model": None}) raised a ValidationError
and every run failed with "Input should be a valid dictionary or instance of
DeerMemModelConfig". Drop None entries in from_backend_config so YAML null /
empty keys fall back to field defaults, matching the documented "empty =
host default LLM" semantics. Upstream bug (#4122 schema); regression-pinned
in test_deermem_self_contained.py.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -203,10 +203,16 @@ class DeerMemConfig(BaseModel):
|
||||
typo (e.g. ``storage_pat`` missing the ``h``) does not silently fall
|
||||
back to the default and write memory to an unintended location --
|
||||
mirrors the host layer's ``load_memory_config_from_dict`` warning.
|
||||
|
||||
``None`` values are dropped so they fall back to the field default:
|
||||
YAML renders an empty key (``model:`` with only commented children, as
|
||||
shipped in ``config.example.yaml``) as ``None``, which non-Optional
|
||||
fields like ``model`` would otherwise reject even though omitting the
|
||||
key entirely is valid.
|
||||
"""
|
||||
if not backend_config:
|
||||
return cls()
|
||||
known = {k: v for k, v in backend_config.items() if k in cls.model_fields}
|
||||
known = {k: v for k, v in backend_config.items() if k in cls.model_fields and v is not None}
|
||||
unknown = sorted(k for k in backend_config if k not in cls.model_fields)
|
||||
if unknown:
|
||||
logger.warning(
|
||||
|
||||
@@ -396,3 +396,37 @@ def test_from_backend_config_silent_on_known_keys(caplog):
|
||||
with caplog.at_level("WARNING", logger=cfg_logger):
|
||||
DeerMemConfig.from_backend_config({"storage_path": "/tmp/x", "max_facts": 20})
|
||||
assert not any("Unknown backend_config keys" in r.message for r in caplog.records)
|
||||
|
||||
|
||||
def test_from_backend_config_null_values_fall_back_to_defaults():
|
||||
"""Explicit YAML ``null`` values must behave like omitted keys, not crash.
|
||||
|
||||
``config.example.yaml`` ships ``backend_config.model:`` as a bare key with
|
||||
commented children, which YAML parses to ``None`` (and ``make
|
||||
config-upgrade`` writes it out as an explicit ``model: null``). Non-Optional
|
||||
fields like ``model: DeerMemModelConfig`` reject an explicit ``None`` even
|
||||
though the omitted key would use the field default — so the shipped example
|
||||
config crashed every run with a DeerMemConfig ValidationError."""
|
||||
from deerflow.agents.memory.backends.deermem.deermem.config import (
|
||||
DeerMemConfig,
|
||||
DeerMemModelConfig,
|
||||
)
|
||||
|
||||
cfg = DeerMemConfig.from_backend_config({"model": None, "debounce_seconds": None, "storage_path": "/tmp/x"})
|
||||
|
||||
# None entries fall back to field defaults; real values still parse
|
||||
assert isinstance(cfg.model, DeerMemModelConfig)
|
||||
assert cfg.model.model is None # default = no extraction LLM configured
|
||||
assert cfg.debounce_seconds == DeerMemConfig().debounce_seconds
|
||||
assert cfg.storage_path == "/tmp/x"
|
||||
|
||||
|
||||
def test_from_backend_config_null_values_do_not_warn_as_unknown(caplog):
|
||||
"""Dropped ``None`` entries are known keys — they must not trip the
|
||||
unknown-key typo warning."""
|
||||
from deerflow.agents.memory.backends.deermem.deermem.config import DeerMemConfig
|
||||
|
||||
cfg_logger = "deerflow.agents.memory.backends.deermem.deermem.config"
|
||||
with caplog.at_level("WARNING", logger=cfg_logger):
|
||||
DeerMemConfig.from_backend_config({"model": None})
|
||||
assert not any("Unknown backend_config keys" in r.message for r in caplog.records)
|
||||
|
||||
Reference in New Issue
Block a user