Merge pull request #1846 from Jehu/fix/memory-float32-json-serialization
Some checks failed
Build And Publish Docker Images / plan (push) Has been cancelled
Build And Publish Docker Images / build (push) Has been cancelled

fix(memory): keep numpy scalars out of metadata and API JSON
This commit is contained in:
Alessandro Frau 2026-08-23 17:01:09 +02:00 committed by GitHub
commit fe70025dc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 43 additions and 4 deletions

View file

@ -24,6 +24,7 @@
## Work Guidance
- Keep dashboard metadata JSON-safe without changing shared API serialization.
- Coordinate tool, prompt, and consolidation changes so saved memories remain useful and bounded.
## Verification

View file

@ -1,3 +1,5 @@
import numpy as np
from helpers.api import ApiHandler, Request, Response
from helpers import files
from helpers.localization import Localization
@ -216,7 +218,10 @@ class MemoryDashboard(ApiHandler):
def _format_memory_for_dashboard(self, m: Document) -> dict:
"""Format a memory document for the dashboard."""
metadata = m.metadata
metadata = dict(m.metadata)
similarity = metadata.get("_consolidation_similarity")
if isinstance(similarity, np.generic):
metadata["_consolidation_similarity"] = float(similarity)
timestamp = self._serialize_memory_timestamp(metadata.get("timestamp", "unknown"))
return {
"id": metadata.get("id", "unknown"),

View file

@ -614,7 +614,7 @@ class Memory:
res = max(
0, min(1, res)
) # float precision can cause values like 1.0000000596046448
return res
return float(res) # native float, not numpy scalar (JSON serializable)
@staticmethod
def format_docs_plain(docs: list[Document]) -> list[str]:

View file

@ -344,7 +344,7 @@ class MemoryConsolidator:
filter=f"area == '{area}'"
)
for doc, score in semantic_results:
doc.metadata['_consolidation_similarity'] = score
doc.metadata['_consolidation_similarity'] = float(score) if score is not None else 0.0
all_similar.append(doc)
# Step 3: Keyword-based searches with real scores
@ -358,7 +358,7 @@ class MemoryConsolidator:
filter=f"area == '{area}'"
)
for doc, score in keyword_results:
doc.metadata['_consolidation_similarity'] = score
doc.metadata['_consolidation_similarity'] = float(score) if score is not None else 0.0
all_similar.append(doc)
# Step 4: Deduplicate by document ID, keep highest score per memory ID

View file

@ -0,0 +1,33 @@
import json
import threading
import numpy as np
from langchain_core.documents import Document
from plugins._memory.api.memory_dashboard import MemoryDashboard
from plugins._memory.helpers.memory import Memory
def test_cosine_normalizer_returns_native_float():
score = Memory._cosine_normalizer(np.float32(0.8))
assert type(score) is float
def test_memory_dashboard_serializes_legacy_numpy_similarity():
dashboard = MemoryDashboard(app=None, thread_lock=threading.RLock())
document = Document(
page_content="legacy memory",
metadata={
"id": "memory-1",
"area": "main",
"timestamp": "unknown",
"_consolidation_similarity": np.float32(0.75),
},
)
formatted = dashboard._format_memory_for_dashboard(document)
assert type(formatted["metadata"]["_consolidation_similarity"]) is float
assert isinstance(document.metadata["_consolidation_similarity"], np.float32)
json.dumps(formatted)