203 lines
No EOL
7 KiB
Python
203 lines
No EOL
7 KiB
Python
"""
|
|
RLM-MEM - Auto-Linking Tests
|
|
Test suite for automatic link generation.
|
|
"""
|
|
|
|
import tempfile
|
|
import shutil
|
|
import unittest
|
|
import uuid
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from brain.scripts.memory_store import ChunkStore, Chunk, ChunkLinks, ChunkMetadata
|
|
from brain.scripts.auto_linker import (
|
|
AutoLinker,
|
|
create_chunk_with_links,
|
|
calculate_link_strength
|
|
)
|
|
except ImportError:
|
|
# For running directly
|
|
from memory_store import ChunkStore, Chunk, ChunkLinks, ChunkMetadata
|
|
from auto_linker import (
|
|
AutoLinker,
|
|
create_chunk_with_links,
|
|
calculate_link_strength
|
|
)
|
|
|
|
|
|
class TestAutoLinker(unittest.TestCase):
|
|
"""Test AutoLinker functionality."""
|
|
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.store = ChunkStore(self.temp_dir)
|
|
self.linker = AutoLinker(self.store, temporal_window_minutes=5)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_conversation_linking(self):
|
|
"""Test context_of links for same conversation."""
|
|
# Create first chunk in unique conversation
|
|
unique_conv = f"conv-test-1-{uuid.uuid4().hex[:8]}"
|
|
chunk1 = self.store.create_chunk(
|
|
"First message",
|
|
"note",
|
|
unique_conv,
|
|
5,
|
|
tags=[]
|
|
)
|
|
chunk1 = self.linker.link_on_create(chunk1)
|
|
|
|
# First chunk has no previous context
|
|
self.assertEqual(len(chunk1.links.context_of), 0)
|
|
|
|
# Create second chunk in same conversation
|
|
chunk2 = self.store.create_chunk(
|
|
"Second message",
|
|
"note",
|
|
unique_conv,
|
|
5,
|
|
tags=[]
|
|
)
|
|
chunk2 = self.linker.link_on_create(chunk2)
|
|
|
|
# Second chunk should link to first
|
|
self.assertIn(chunk1.id, chunk2.links.context_of)
|
|
|
|
def test_temporal_following(self):
|
|
"""Test follows links within temporal window."""
|
|
# Create chunks in same unique conversation
|
|
conv_id = f"conv-test-2-{uuid.uuid4().hex[:8]}"
|
|
chunk1 = self.store.create_chunk(
|
|
"Earlier message",
|
|
"note",
|
|
conv_id,
|
|
5,
|
|
tags=[]
|
|
)
|
|
chunk1 = self.linker.link_on_create(chunk1)
|
|
|
|
chunk2 = self.store.create_chunk(
|
|
"Later message",
|
|
"note",
|
|
conv_id,
|
|
5,
|
|
tags=[]
|
|
)
|
|
chunk2 = self.linker.link_on_create(chunk2)
|
|
|
|
# Second chunk should follow first
|
|
self.assertIn(chunk1.id, chunk2.links.follows)
|
|
|
|
def test_tag_related_linking(self):
|
|
"""Test related_to links for shared tags."""
|
|
# Create chunks with same tags but different conversations
|
|
unique_id = uuid.uuid4().hex[:8]
|
|
chunk1 = self.store.create_chunk(
|
|
"Feature A docs",
|
|
"note",
|
|
f"conv-docs-1-{unique_id}",
|
|
5,
|
|
tags=["documentation", "feature-a"]
|
|
)
|
|
chunk1 = self.linker.link_on_create(chunk1)
|
|
|
|
chunk2 = self.store.create_chunk(
|
|
"Feature A implementation",
|
|
"note",
|
|
f"conv-impl-1-{unique_id}",
|
|
5,
|
|
tags=["implementation", "feature-a"]
|
|
)
|
|
chunk2 = self.linker.link_on_create(chunk2)
|
|
|
|
# Should be related via shared "feature-a" tag (in chunk2)
|
|
self.assertIn(chunk1.id, chunk2.links.related_to)
|
|
# chunk1 should have been updated with bidirectional link
|
|
chunk1_refreshed = self.store.get_chunk(chunk1.id)
|
|
self.assertIn(chunk2.id, chunk1_refreshed.links.related_to)
|
|
|
|
def test_no_duplicate_context_links(self):
|
|
"""Test that related_to doesn't duplicate context_of."""
|
|
# Create two chunks in same conversation with shared tags
|
|
conv_id = f"conv-dedup-1-{uuid.uuid4().hex[:8]}"
|
|
chunk1 = self.store.create_chunk(
|
|
"First with tag",
|
|
"note",
|
|
conv_id,
|
|
5,
|
|
tags=["shared-tag"]
|
|
)
|
|
chunk1 = self.linker.link_on_create(chunk1)
|
|
|
|
chunk2 = self.store.create_chunk(
|
|
"Second with tag",
|
|
"note",
|
|
conv_id,
|
|
5,
|
|
tags=["shared-tag"]
|
|
)
|
|
chunk2 = self.linker.link_on_create(chunk2)
|
|
|
|
# Should have context_of link
|
|
self.assertIn(chunk1.id, chunk2.links.context_of)
|
|
|
|
# Should NOT have related_to link (would be duplicate)
|
|
self.assertNotIn(chunk1.id, chunk2.links.related_to)
|
|
|
|
|
|
class TestLinkStrength(unittest.TestCase):
|
|
"""Test link strength calculation."""
|
|
|
|
def test_context_of_strength(self):
|
|
"""Test context_of always has max strength."""
|
|
chunk1 = Chunk(id="a", content="t", tokens=1, type="note", metadata=None, links=ChunkLinks())
|
|
chunk2 = Chunk(id="b", content="t", tokens=1, type="note", metadata=None, links=ChunkLinks())
|
|
|
|
strength = calculate_link_strength(chunk1, chunk2, "context_of")
|
|
self.assertEqual(strength, 1.0)
|
|
|
|
def test_follows_strength_decay(self):
|
|
"""Test follows strength decays with time."""
|
|
now = datetime.utcnow()
|
|
|
|
meta1 = ChunkMetadata(created=(now - timedelta(minutes=1)).isoformat() + "Z", conversation_id="t")
|
|
chunk1 = Chunk(id="a", content="t", tokens=1, type="note", metadata=meta1, links=ChunkLinks())
|
|
|
|
meta2 = ChunkMetadata(created=now.isoformat() + "Z", conversation_id="t")
|
|
chunk2 = Chunk(id="b", content="t", tokens=1, type="note", metadata=meta2, links=ChunkLinks())
|
|
|
|
strength = calculate_link_strength(chunk2, chunk1, "follows")
|
|
self.assertGreaterEqual(strength, 0.8)
|
|
|
|
meta3 = ChunkMetadata(created=(now - timedelta(minutes=5)).isoformat() + "Z", conversation_id="t")
|
|
chunk3 = Chunk(id="c", content="t", tokens=1, type="note", metadata=meta3, links=ChunkLinks())
|
|
strength = calculate_link_strength(chunk2, chunk3, "follows")
|
|
self.assertEqual(strength, 0.3)
|
|
|
|
|
|
class TestIntegration(unittest.TestCase):
|
|
"""Integration tests combining multiple features."""
|
|
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.store = ChunkStore(self.temp_dir)
|
|
self.linker = AutoLinker(self.store)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
|
|
def test_create_chunk_with_links_wrapper(self):
|
|
"""Test the create_chunk_with_links wrapper."""
|
|
chunk = create_chunk_with_links(
|
|
self.store, self.linker,
|
|
"Test", "note", "conv-1", 1,
|
|
tags=["test"]
|
|
)
|
|
self.assertIsNotNone(chunk.id)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |