Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/mem_feedback/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

6 changes: 6 additions & 0 deletions tests/mem_feedback/test_base.py
Original file line number Diff line number Diff line change
@@ -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)
84 changes: 84 additions & 0 deletions tests/mem_feedback/test_config.py
Original file line number Diff line number Diff line change
@@ -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)
45 changes: 45 additions & 0 deletions tests/mem_feedback/test_feedback.py
Original file line number Diff line number Diff line change
@@ -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",
)
83 changes: 83 additions & 0 deletions tests/mem_feedback/test_utils.py
Original file line number Diff line number Diff line change
@@ -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"}
Loading