From d8d8a34114f442008aaaf8a4cc7e487c844db951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=BE=99?= <76432572+nankingjing@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:00:45 +0800 Subject: [PATCH] fix(memory): coerce null confidence when ranking search results (#4076) search_memory_facts sorted matches by fact.get("confidence", 0), which returns None for a fact whose confidence key is explicitly null, crashing the sort comparison. Use _coerce_source_confidence so null/malformed confidence values are normalized and clamped before ranking. --- .../harness/deerflow/agents/memory/updater.py | 2 +- backend/tests/test_memory_search.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/packages/harness/deerflow/agents/memory/updater.py b/backend/packages/harness/deerflow/agents/memory/updater.py index 1763b3d6f..c73d5aba2 100644 --- a/backend/packages/harness/deerflow/agents/memory/updater.py +++ b/backend/packages/harness/deerflow/agents/memory/updater.py @@ -239,7 +239,7 @@ def search_memory_facts( continue matched.append(fact) - matched.sort(key=lambda f: f.get("confidence", 0), reverse=True) + matched.sort(key=_coerce_source_confidence, reverse=True) return matched[:limit] diff --git a/backend/tests/test_memory_search.py b/backend/tests/test_memory_search.py index 9801e3bf0..b617325e0 100644 --- a/backend/tests/test_memory_search.py +++ b/backend/tests/test_memory_search.py @@ -94,6 +94,35 @@ class TestSearchMemoryFacts: assert results[1]["confidence"] == 0.6 assert results[2]["confidence"] == 0.3 + def test_null_confidence_does_not_crash_sort(self, tmp_path, monkeypatch): + """A fact stored with ``"confidence": null`` (corrupted/hand-edited memory) + must not break the confidence sort. ``.get("confidence", 0)`` returns the + stored ``None`` and comparing None with floats raises TypeError; the coerce + helper defaults null to a finite midpoint instead.""" + null_fact = { + "id": "fact_null", + "content": "Fact with null confidence", + "category": "context", + "confidence": None, + "createdAt": "2026-07-09T00:00:00Z", + "source": "test", + } + facts = [ + _make_fact("Fact high", confidence=0.9), + null_fact, + _make_fact("Fact low", confidence=0.2), + ] + _setup_memory(tmp_path, monkeypatch, facts) + + # Must not raise TypeError during the confidence sort. + results = search_memory_facts("Fact") + + assert len(results) == 3 + # Highest real confidence still sorts first; null (coerced to 0.5) sits + # between the 0.9 and 0.2 facts. + assert results[0]["content"] == "Fact high" + assert {r["content"] for r in results} == {"Fact high", "Fact with null confidence", "Fact low"} + def test_respects_limit(self, tmp_path, monkeypatch): """Should return at most `limit` results.""" facts = [_make_fact(f"Fact {i}", confidence=0.5) for i in range(20)]