@@ -36,7 +36,7 @@ async def test_redis_memory_query_with_mock() -> None:
3636 memory = RedisMemory (config = config )
3737
3838 mock_history .get_relevant .return_value = [
39- {"content" : "test content" , "tool_call_id " : '{"foo": "bar", "mime_type": "text/plain"}' }
39+ {"content" : "test content" , "metadata " : '{"foo": "bar", "mime_type": "text/plain"}' }
4040 ]
4141 result = await memory .query ("test" )
4242 assert len (result .results ) == 1
@@ -86,13 +86,26 @@ def semantic_config() -> RedisMemoryConfig:
8686 return RedisMemoryConfig (top_k = 5 , distance_threshold = 0.5 , model_name = "sentence-transformers/all-mpnet-base-v2" )
8787
8888
89+ @pytest .fixture
90+ def sequential_config () -> RedisMemoryConfig :
91+ """Create base configuration using semantic memory."""
92+ return RedisMemoryConfig (top_k = 5 , sequential = True )
93+
94+
8995@pytest_asyncio .fixture # type: ignore[reportUntypedFunctionDecorator]
9096async def semantic_memory (semantic_config : RedisMemoryConfig ) -> AsyncGenerator [RedisMemory ]:
9197 memory = RedisMemory (semantic_config )
9298 yield memory
9399 await memory .close ()
94100
95101
102+ @pytest_asyncio .fixture # type: ignore[reportUntypedFunctionDecorator]
103+ async def sequential_memory (sequential_config : RedisMemoryConfig ) -> AsyncGenerator [RedisMemory ]:
104+ memory = RedisMemory (sequential_config )
105+ yield memory
106+ await memory .close ()
107+
108+
96109## UNIT TESTS ##
97110def test_memory_config () -> None :
98111 default_config = RedisMemoryConfig ()
@@ -104,6 +117,7 @@ def test_memory_config() -> None:
104117 assert default_config .top_k == 10
105118 assert default_config .distance_threshold == 0.7
106119 assert default_config .model_name == "sentence-transformers/all-mpnet-base-v2"
120+ assert not default_config .sequential
107121
108122 # test we can specify each of these values
109123 url = "rediss://localhost:7010"
@@ -144,14 +158,36 @@ def test_memory_config() -> None:
144158
145159@pytest .mark .asyncio
146160@pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
147- async def test_create_semantic_memory () -> None :
148- config = RedisMemoryConfig (index_name = "semantic_agent" )
161+ @pytest .mark .parametrize ("sequential" , [True , False ])
162+ async def test_create_memory (sequential : bool ) -> None :
163+ config = RedisMemoryConfig (index_name = "semantic_agent" , sequential = sequential )
149164 memory = RedisMemory (config = config )
150165
151166 assert memory .message_history is not None
152167 await memory .close ()
153168
154169
170+ @pytest .mark .asyncio
171+ @pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
172+ async def test_specify_vectorizer () -> None :
173+ config = RedisMemoryConfig (index_name = "semantic_agent" , model_name = "redis/langcache-embed-v1" )
174+ memory = RedisMemory (config = config )
175+ assert memory .message_history ._vectorizer .dims == 768 # type: ignore[reportPrivateUsage]
176+ await memory .close ()
177+
178+ config = RedisMemoryConfig (
179+ index_name = "semantic_agent" , model_name = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
180+ )
181+ memory = RedisMemory (config = config )
182+ assert memory .message_history ._vectorizer .dims == 384 # type: ignore[reportPrivateUsage]
183+ await memory .close ()
184+
185+ # throw an error if a non-existant model name is passed
186+ config = RedisMemoryConfig (index_name = "semantic_agent" , model_name = "not-a-real-model" )
187+ with pytest .raises (OSError ):
188+ memory = RedisMemory (config = config )
189+
190+
155191@pytest .mark .asyncio
156192@pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
157193async def test_update_context (semantic_memory : RedisMemory ) -> None :
@@ -223,7 +259,7 @@ async def test_update_context(semantic_memory: RedisMemory) -> None:
223259
224260@pytest .mark .asyncio
225261@pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
226- async def test_add_and_query (semantic_memory : RedisMemory ) -> None :
262+ async def test_add_and_query_with_string (semantic_memory : RedisMemory ) -> None :
227263 content_1 = MemoryContent (
228264 content = "I enjoy fruits like apples, oranges, and bananas." , mime_type = MemoryMimeType .TEXT , metadata = {}
229265 )
@@ -251,6 +287,38 @@ async def test_add_and_query(semantic_memory: RedisMemory) -> None:
251287 assert memories .results [1 ].metadata == {"description" : "additional info" }
252288
253289
290+ @pytest .mark .asyncio
291+ @pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
292+ async def test_add_and_query_with_memory_content (semantic_memory : RedisMemory ) -> None :
293+ content_1 = MemoryContent (
294+ content = "I enjoy fruits like apples, oranges, and bananas." , mime_type = MemoryMimeType .TEXT , metadata = {}
295+ )
296+ await semantic_memory .add (content_1 )
297+
298+ # find matches with a similar query
299+ memories = await semantic_memory .query (MemoryContent (content = "Fruits that I like." , mime_type = MemoryMimeType .TEXT ))
300+ assert len (memories .results ) == 1
301+
302+ # don't return anything for dissimilar queries
303+ no_memories = await semantic_memory .query (
304+ MemoryContent (content = "The king of England" , mime_type = MemoryMimeType .TEXT )
305+ )
306+ assert len (no_memories .results ) == 0
307+
308+ # match multiple relevant memories
309+ content_2 = MemoryContent (
310+ content = "I also like mangos and pineapples." ,
311+ mime_type = MemoryMimeType .TEXT ,
312+ metadata = {"description" : "additional info" },
313+ )
314+ await semantic_memory .add (content_2 )
315+
316+ memories = await semantic_memory .query (MemoryContent (content = "Fruits that I like." , mime_type = MemoryMimeType .TEXT ))
317+ assert len (memories .results ) == 2
318+ assert memories .results [0 ].metadata == {}
319+ assert memories .results [1 ].metadata == {"description" : "additional info" }
320+
321+
254322@pytest .mark .asyncio
255323@pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
256324async def test_clear (semantic_memory : RedisMemory ) -> None :
@@ -283,9 +351,16 @@ async def test_close(semantic_config: RedisMemoryConfig) -> None:
283351## INTEGRATION TESTS ##
284352@pytest .mark .asyncio
285353@pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
286- async def test_basic_workflow (semantic_config : RedisMemoryConfig ) -> None :
354+ @pytest .mark .parametrize ("config_type" , ["sequential" , "semantic" ])
355+ async def test_basic_workflow (config_type : str ) -> None :
287356 """Test basic memory operations with semantic memory."""
288- memory = RedisMemory (config = semantic_config )
357+ if config_type == "sequential" :
358+ config = RedisMemoryConfig (top_k = 5 , sequential = True )
359+ else :
360+ config = RedisMemoryConfig (
361+ top_k = 5 , distance_threshold = 0.5 , model_name = "sentence-transformers/all-mpnet-base-v2"
362+ )
363+ memory = RedisMemory (config = config )
289364 await memory .clear ()
290365
291366 await memory .add (
@@ -318,6 +393,11 @@ async def test_text_memory_type(semantic_memory: RedisMemory) -> None:
318393 assert len (results .results ) > 0
319394 assert any ("Simple text content" in str (r .content ) for r in results .results )
320395
396+ # Query for text content with a MemoryContent object
397+ results = await semantic_memory .query (MemoryContent (content = "simple text content" , mime_type = MemoryMimeType .TEXT ))
398+ assert len (results .results ) > 0
399+ assert any ("Simple text content" in str (r .content ) for r in results .results )
400+
321401
322402@pytest .mark .asyncio
323403@pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
@@ -419,3 +499,57 @@ async def test_query_arguments(semantic_memory: RedisMemory) -> None:
419499 # limit search to only close matches
420500 results = await semantic_memory .query ("my favorite fruit are what?" , distance_threshold = 0.2 )
421501 assert len (results .results ) == 1
502+
503+ # get memories based on recency instead of relevance
504+ results = await semantic_memory .query ("fast sports cars" , sequential = True )
505+ assert len (results .results ) == 3
506+
507+ # setting 'sequential' to False results in default behaviour
508+ results = await semantic_memory .query ("my favorite fruit are what?" , sequential = False )
509+ assert len (results .results ) == 3
510+
511+
512+ @pytest .mark .asyncio
513+ @pytest .mark .skipif (not redis_available (), reason = "Redis instance not available locally" )
514+ async def test_sequential_memory_workflow (sequential_memory : RedisMemory ) -> None :
515+ await sequential_memory .clear ()
516+
517+ await sequential_memory .add (MemoryContent (content = "my favorite fruit are apples" , mime_type = MemoryMimeType .TEXT ))
518+ await sequential_memory .add (
519+ MemoryContent (
520+ content = "I read the encyclopedia britanica and my favorite section was on the Napoleonic Wars." ,
521+ mime_type = MemoryMimeType .TEXT ,
522+ )
523+ )
524+ await sequential_memory .add (
525+ MemoryContent (content = "Sharks have no idea that camels exist." , mime_type = MemoryMimeType .TEXT )
526+ )
527+ await sequential_memory .add (
528+ MemoryContent (
529+ content = "Python is a popular programming language used for machine learning and AI applications." ,
530+ mime_type = MemoryMimeType .TEXT ,
531+ )
532+ )
533+ await sequential_memory .add (
534+ MemoryContent (content = "Fifth random and unrelated sentence" , mime_type = MemoryMimeType .TEXT )
535+ )
536+
537+ # default search returns last 5 memories
538+ results = await sequential_memory .query ("what fruits do I like?" )
539+ assert len (results .results ) == 5
540+
541+ # limit search to 2 results
542+ results = await sequential_memory .query ("what fruits do I like?" , top_k = 2 )
543+ assert len (results .results ) == 2
544+
545+ # sequential memory does not consider semantic similarity
546+ results = await sequential_memory .query ("How do I make peanut butter sandwiches?" )
547+ assert len (results .results ) == 5
548+
549+ # seting 'sequential' to True in query method is redundant
550+ results = await sequential_memory .query ("fast sports cars" , sequential = True )
551+ assert len (results .results ) == 5
552+
553+ # setting 'sequential' to False with a Sequential memory object raises an error
554+ with pytest .raises (ValueError ):
555+ _ = await sequential_memory .query ("my favorite fruit are what?" , sequential = False )
0 commit comments