GeneralTextMemory reverses Euclidean relevance order from Qdrant
Pre-submission checklist
- I searched existing issues and pull requests.
- I checked the documented Qdrant distance behavior against the MemOS implementation.
- This is specific to MemOS's GeneralTextMemory retrieval path.
Bug Description
GeneralTextMemory.search() reverses Qdrant's relevance ordering when the collection uses Euclidean distance. Qdrant returns the nearest matches first, but the memory layer sorts every score descending. With Euclidean scores, that puts the farthest match first within the selected top-k.
For distances [0.0, 0.7071067811865476, 2.0], the expected order is near, middle, far. The current code returns far, middle, near. With top_k=2, it returns middle, near.
How to Reproduce
Run this from a checkout with the core dependencies and qdrant-client installed (PYTHONPATH=src python reproduce.py). It uses embedded Qdrant and fixed embeddings; no model, API key, or external service is needed.
from tempfile import TemporaryDirectory
from unittest.mock import MagicMock, patch
from memos.configs.memory import GeneralTextMemoryConfig
from memos.memories.textual.general import GeneralTextMemory
from memos.memories.textual.item import TextualMemoryItem
vectors = {"query": [1.0, 0.0], "near": [1.0, 0.0], "middle": [0.5, 0.5], "far": [-1.0, 0.0]}
embedder = MagicMock()
embedder.embed.side_effect = lambda texts: [vectors[text] for text in texts]
with TemporaryDirectory() as directory:
config = GeneralTextMemoryConfig(
extractor_llm={"backend": "ollama", "config": {"model_name_or_path": "test"}},
embedder={"backend": "ollama", "config": {"model_name_or_path": "test"}},
vector_db={"backend": "qdrant", "config": {
"collection_name": "repro", "vector_dimension": 2,
"distance_metric": "euclidean", "path": directory,
}},
)
with patch("memos.memories.textual.general.LLMFactory.from_config"), patch(
"memos.memories.textual.general.EmbedderFactory.from_config", return_value=embedder
):
memory = GeneralTextMemory(config)
try:
memory.add([TextualMemoryItem(memory=text) for text in ["middle", "far", "near"]])
print("Backend distances:", [item.score for item in memory.vector_db.search(vectors["query"], 3)])
print("Memory top 3:", [item.memory for item in memory.search("query", 3)])
print("Memory top 2:", [item.memory for item in memory.search("query", 2)])
finally:
memory.vector_db.client.close()Actual output on 176d4f676a93e0e34ca9fd50091eff5ad3236506:
Backend distances: [0.0, 0.7071067811865476, 2.0]
Memory top 3: ['far', 'middle', 'near']
Memory top 2: ['middle', 'near']Expected:
Memory top 3: ['near', 'middle', 'far']
Memory top 2: ['near', 'middle']Environment
- MemOS: main and dev-v2.0.35 at
176d4f676a93e0e34ca9fd50091eff5ad3236506(package version 2.0.33) - Python 3.12.14
- macOS 15.5, ARM64
- qdrant-client 1.19.1, embedded mode
Additional Context
The extra descending sort is in src/memos/memories/textual/general.py. Qdrant already orders the results according to the collection metric. Qdrant's search documentation confirms that larger Euclidean scores are more distant.
I have a small fix that preserves the backend order and a regression using real local Qdrant for Euclidean, cosine, and dot-product collections with both top_k=2 and top_k=3.
Willingness to Implement
- I am willing to implement this.
Source: MemTensor/MemOS