mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(memory): drop unusable numbers in stale extensions and consolidation confidence (#7442)
* fix(memory): drop unusable numbers in stale extensions and consolidation confidence * fix(memory): log defaulted consolidation confidence and pin the int passthrough
This commit is contained in:
parent
46ccfab71a
commit
338cd26dc2
@ -183,6 +183,14 @@ def _log_dropped_confidence(raw: object) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _log_defaulted_consolidation_confidence(raw: object) -> None:
|
||||
"""Make a systematically out-of-range consolidation confidence visible."""
|
||||
logger.warning(
|
||||
"Defaulting a consolidation with out-of-range confidence %s to 0.9; expected 0.0-1.0. A model emitting a percentage scale would lose the confidence signal.",
|
||||
repr(raw)[:80],
|
||||
)
|
||||
|
||||
|
||||
def _normalize_memory_update_fact(fact: Any) -> dict[str, Any] | None:
|
||||
"""Normalize a single fact entry from a model-produced memory update."""
|
||||
if not isinstance(fact, dict):
|
||||
@ -363,7 +371,10 @@ def _normalize_memory_update_data(update_data: dict[str, Any]) -> dict[str, Any]
|
||||
# apply path never silently writes a zero-delta extension.
|
||||
raw_extend = entry.get("extend_by_days")
|
||||
if isinstance(raw_extend, (int, float)) and not isinstance(raw_extend, bool):
|
||||
extend_by = int(raw_extend)
|
||||
# int() raises on a non-finite float (a JSON number such as 1e400 parses
|
||||
# to inf), which would fail the whole batch; drop it like any other
|
||||
# unusable value. An int is kept as-is, including values beyond float range.
|
||||
extend_by = int(raw_extend) if isinstance(raw_extend, int) or math.isfinite(raw_extend) else 0
|
||||
if extend_by > 0:
|
||||
reason = entry.get("reason", "")
|
||||
normalized_stale_extensions.append(
|
||||
@ -402,8 +413,16 @@ def _normalize_memory_update_data(update_data: dict[str, Any]) -> dict[str, Any]
|
||||
if isinstance(_raw_conf, bool) or not isinstance(_raw_conf, (int, float)):
|
||||
_norm_conf = 0.9
|
||||
else:
|
||||
_f = float(_raw_conf)
|
||||
_norm_conf = _f if math.isfinite(_f) else 0.9
|
||||
# float() raises on an unbounded JSON integer such as 10**400, which
|
||||
# would fail the whole batch; fall back like any other invalid value.
|
||||
try:
|
||||
_f = float(_raw_conf)
|
||||
except OverflowError:
|
||||
_f = math.inf
|
||||
if not math.isfinite(_f):
|
||||
_log_defaulted_consolidation_confidence(_raw_conf)
|
||||
_f = 0.9
|
||||
_norm_conf = _f
|
||||
_raw_cat = consolidated.get("category")
|
||||
_norm_cat = _raw_cat.strip() if isinstance(_raw_cat, str) and _raw_cat.strip() else "context"
|
||||
normalized_consolidation.append(
|
||||
|
||||
72
backend/tests/test_memory_update_normalization.py
Normal file
72
backend/tests/test_memory_update_normalization.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""Tests for non-finite and out-of-range numbers in memory update responses.
|
||||
|
||||
A model can answer with a JSON number outside float range (``1e400`` parses to
|
||||
``inf``) or with ``NaN``, and an unbounded integer such as ``10**400`` raises
|
||||
``OverflowError`` in ``float()``. These optional fields are dropped or defaulted
|
||||
per entry, as ``_normalize_memory_update_fact`` already does, instead of failing
|
||||
the whole batch and losing the valid facts in the same response.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from deerflow.agents.memory.backends.deermem.deermem.core.updater import _parse_memory_update_response
|
||||
|
||||
_VALID_FACT = {
|
||||
"content": "User prefers dark mode",
|
||||
"category": "preference",
|
||||
"confidence": 0.9,
|
||||
"scope": "user",
|
||||
"durability": "durable",
|
||||
"authority": "descriptive",
|
||||
}
|
||||
|
||||
|
||||
def _response(extra):
|
||||
return '{"user":{},"history":{},"newFacts":[' + json.dumps(_VALID_FACT) + "]," + extra + "}"
|
||||
|
||||
|
||||
def test_infinite_extend_by_days_is_dropped():
|
||||
# 1e400 is outside float range, so json parses it to inf and int(inf) raises.
|
||||
parsed = _parse_memory_update_response(_response('"staleFactsToExtend":[{"id":"fact_1","extend_by_days":1e400}]'))
|
||||
|
||||
assert len(parsed["newFacts"]) == 1
|
||||
assert parsed["staleFactsToExtend"] == []
|
||||
|
||||
|
||||
def test_nan_extend_by_days_is_dropped():
|
||||
parsed = _parse_memory_update_response(_response('"staleFactsToExtend":[{"id":"fact_1","extend_by_days":NaN}]'))
|
||||
|
||||
assert len(parsed["newFacts"]) == 1
|
||||
assert parsed["staleFactsToExtend"] == []
|
||||
|
||||
|
||||
def test_unbounded_integer_extend_by_days_is_kept():
|
||||
# An integer literal beyond float range must survive as an int: the apply path
|
||||
# bounds it with min(days_since + extend_by, staleness_max_extension_days), which
|
||||
# works on arbitrary-precision ints but would raise OverflowError on int(float()).
|
||||
huge = "1" + "0" * 400
|
||||
parsed = _parse_memory_update_response(_response('"staleFactsToExtend":[{"id":"fact_1","extend_by_days":' + huge + "}]"))
|
||||
|
||||
assert len(parsed["newFacts"]) == 1
|
||||
assert parsed["staleFactsToExtend"][0]["extend_by_days"] == 10**400
|
||||
|
||||
|
||||
def test_unbounded_integer_consolidation_confidence_defaults(caplog):
|
||||
# float(10**400) raises OverflowError; the entry keeps its default confidence
|
||||
# and the default is logged so a systematically mis-scaled model stays visible.
|
||||
with caplog.at_level(logging.WARNING):
|
||||
parsed = _parse_memory_update_response(_response('"factsToConsolidate":[{"sourceIds":["fact_a","fact_b"],"consolidated":{"content":"Merged fact","category":"knowledge","confidence":' + "1" + "0" * 400 + "}}]"))
|
||||
|
||||
assert len(parsed["newFacts"]) == 1
|
||||
assert parsed["factsToConsolidate"][0]["consolidated"]["confidence"] == 0.9
|
||||
assert "out-of-range confidence" in caplog.text
|
||||
|
||||
|
||||
def test_finite_values_are_kept():
|
||||
parsed = _parse_memory_update_response(
|
||||
_response('"staleFactsToExtend":[{"id":"fact_1","extend_by_days":30}],"factsToConsolidate":[{"sourceIds":["fact_a","fact_b"],"consolidated":{"content":"Merged fact","category":"knowledge","confidence":0.7}}]')
|
||||
)
|
||||
|
||||
assert parsed["staleFactsToExtend"][0]["extend_by_days"] == 30
|
||||
assert parsed["factsToConsolidate"][0]["consolidated"]["confidence"] == 0.7
|
||||
Loading…
x
Reference in New Issue
Block a user