From fb584673b1c5c7b58b7f0a68cf7bcf48a12bcb0c Mon Sep 17 00:00:00 2001 From: snoopuppy582 Date: Tue, 12 May 2026 23:13:29 +0900 Subject: [PATCH 1/2] test: cover mem_feedback basics --- tests/mem_feedback/__init__.py | 1 + tests/mem_feedback/test_base.py | 6 +++ tests/mem_feedback/test_config.py | 84 +++++++++++++++++++++++++++++++ tests/mem_feedback/test_utils.py | 83 ++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 tests/mem_feedback/__init__.py create mode 100644 tests/mem_feedback/test_base.py create mode 100644 tests/mem_feedback/test_config.py create mode 100644 tests/mem_feedback/test_utils.py diff --git a/tests/mem_feedback/__init__.py b/tests/mem_feedback/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/mem_feedback/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/mem_feedback/test_base.py b/tests/mem_feedback/test_base.py new file mode 100644 index 000000000..79953d2e3 --- /dev/null +++ b/tests/mem_feedback/test_base.py @@ -0,0 +1,6 @@ +from memos.mem_feedback.base import BaseMemFeedback +from tests.utils import check_module_base_class + + +def test_base_mem_feedback_class_contract(): + check_module_base_class(BaseMemFeedback) diff --git a/tests/mem_feedback/test_config.py b/tests/mem_feedback/test_config.py new file mode 100644 index 000000000..31b1362f1 --- /dev/null +++ b/tests/mem_feedback/test_config.py @@ -0,0 +1,84 @@ +import pytest + +from pydantic import ValidationError + +from memos.configs.memory import MemFeedbackConfig, MemoryConfigFactory + + +def _llm_config() -> dict: + return { + "backend": "ollama", + "config": {"model_name_or_path": "llama3"}, + } + + +def _embedder_config() -> dict: + return { + "backend": "ollama", + "config": { + "model_name_or_path": "nomic-embed-text", + "embedding_dims": 768, + }, + } + + +def _graph_db_config() -> dict: + return { + "backend": "polardb", + "config": { + "host": "localhost", + "user": "postgres", + "password": "postgres", + "db_name": "memos_test", + }, + } + + +def _mem_reader_config() -> dict: + return { + "backend": "simple_struct", + "config": { + "llm": _llm_config(), + "embedder": _embedder_config(), + "chunker": {"backend": "sentence", "config": {}}, + }, + } + + +def _feedback_config() -> dict: + return { + "extractor_llm": _llm_config(), + "embedder": _embedder_config(), + "graph_db": _graph_db_config(), + "mem_reader": _mem_reader_config(), + } + + +def test_mem_feedback_config_accepts_valid_local_config(): + config = MemFeedbackConfig.model_validate(_feedback_config()) + + assert config.extractor_llm.backend == "ollama" + assert config.embedder.backend == "ollama" + assert config.graph_db.backend == "polardb" + assert config.mem_reader.backend == "simple_struct" + + +def test_mem_feedback_config_defaults_optional_behavior_fields(): + config = MemFeedbackConfig.model_validate(_feedback_config()) + + assert config.reorganize is False + assert config.memory_size is None + assert config.reranker is None + + +def test_memory_config_factory_registers_mem_feedback_backend(): + factory = MemoryConfigFactory(backend="mem_feedback", config=_feedback_config()) + + assert isinstance(factory.config, MemFeedbackConfig) + + +def test_mem_feedback_config_rejects_extra_fields(): + invalid_config = {**_feedback_config(), "unknown_option": True} + + with pytest.raises(ValidationError): + MemFeedbackConfig.model_validate(invalid_config) diff --git a/tests/mem_feedback/test_utils.py b/tests/mem_feedback/test_utils.py new file mode 100644 index 000000000..109d83ff6 --- /dev/null +++ b/tests/mem_feedback/test_utils.py @@ -0,0 +1,83 @@ +import pytest + +from memos.mem_feedback.utils import ( + estimate_tokens, + extract_bracket_content, + extract_square_brackets_content, + general_split_into_chunks, + make_mem_item, + should_keep_update, +) + + +def test_estimate_tokens_returns_zero_for_empty_text(): + assert estimate_tokens("") == 0 + + +def test_estimate_tokens_counts_mixed_language_text(): + assert estimate_tokens("hello memory 世界") > 0 + + +def test_should_keep_update_rejects_unchanged_text(): + assert not should_keep_update("same memory", "same memory") + + +def test_should_keep_update_accepts_small_edit(): + old_text = "the user prefers concise answers" + new_text = "the user prefers concise answers with examples" + + assert should_keep_update(new_text, old_text) + + +def test_should_keep_update_rejects_large_rewrite(): + assert not should_keep_update("zzzzzzzzzz", "aaaaaaaaaa") + + +def test_general_split_into_chunks_groups_small_items(): + chunks = general_split_into_chunks( + [{"text": "short"}, {"text": "also short"}], + max_tokens_per_chunk=100, + ) + + assert chunks == [[{"text": "short"}, {"text": "also short"}]] + + +def test_extract_bracket_content_parses_json_payload(): + assert extract_bracket_content('prefix {"operation": "ADD", "text": "memory"} suffix') == { + "operation": "ADD", + "text": "memory", + } + + +def test_extract_square_brackets_content_parses_json_payload(): + assert extract_square_brackets_content('prefix [{"operation": "UPDATE"}] suffix') == [ + {"operation": "UPDATE"} + ] + + +def test_extract_bracket_content_raises_for_missing_payload(): + with pytest.raises(ValueError, match="No curly brace content"): + extract_bracket_content("no json here") + + +def test_make_mem_item_carries_feedback_metadata(): + item = make_mem_item( + "prefers concise answers", + info={"user_id": "user-1", "session_id": "session-1", "app_id": "app-1"}, + user_name="alice", + tags=["feedback"], + key="answer_style", + embedding=[0.1, 0.2], + sources=[{"type": "feedback", "content": "too long"}], + background="from user feedback", + type="fine", + ) + + assert item.memory == "prefers concise answers" + assert item.metadata.user_id == "user-1" + assert item.metadata.session_id == "session-1" + assert item.metadata.user_name == "alice" + assert item.metadata.tags == ["feedback"] + assert item.metadata.key == "answer_style" + assert item.metadata.embedding == [0.1, 0.2] + assert item.metadata.info == {"app_id": "app-1"} From 46d77ffed04cdfaab0de05a6144fe9c2f6414852 Mon Sep 17 00:00:00 2001 From: snoopuppy582 Date: Wed, 13 May 2026 22:12:16 +0900 Subject: [PATCH 2/2] test: cover mem feedback workflow --- tests/mem_feedback/test_feedback.py | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/mem_feedback/test_feedback.py diff --git a/tests/mem_feedback/test_feedback.py b/tests/mem_feedback/test_feedback.py new file mode 100644 index 000000000..7e8d3b718 --- /dev/null +++ b/tests/mem_feedback/test_feedback.py @@ -0,0 +1,45 @@ +from unittest.mock import Mock + +from memos.mem_feedback.feedback import MemFeedback + + +def test_process_feedback_runs_answer_and_core_workflows(): + feedback = MemFeedback.__new__(MemFeedback) + feedback._generate_answer = Mock(return_value="corrected answer") + feedback.process_feedback_core = Mock( + return_value={"record": {"add": [{"id": "memory-1"}], "update": []}} + ) + + chat_history = [{"role": "user", "content": "The response was too long."}] + info = {"source": "unit-test"} + + result = feedback.process_feedback( + "user-1", + "alice", + chat_history, + "Please remember that I prefer concise answers.", + info, + corrected_answer=True, + session_id="session-1", + task_id="task-1", + ) + + assert result == { + "answer": "corrected answer", + "record": {"add": [{"id": "memory-1"}], "update": []}, + } + feedback._generate_answer.assert_called_once_with( + chat_history, + "Please remember that I prefer concise answers.", + corrected_answer=True, + ) + feedback.process_feedback_core.assert_called_once_with( + "user-1", + "alice", + chat_history, + "Please remember that I prefer concise answers.", + info, + corrected_answer=True, + session_id="session-1", + task_id="task-1", + )