#2387·MemOS

GeneralTextMemory reverses Euclidean relevance order from Qdrant

Author: Shy7777Created Sep 18, 2026Updated Sep 18, 2026
Labelstypes:bugai:failedarea:memorystatus:in-progress

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.

python
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.