@@ -642,3 +642,231 @@ def mock_echo(self, message):
642
642
finally :
643
643
client .close ()
644
644
client .connection_pool .disconnect ()
645
+
646
+
647
+ def test_vector_storage_json (redis_url , fake_embeddings : CharacterEmbeddings ) -> None :
648
+ """Test JSON vector storage (default behavior)."""
649
+ # Test data
650
+ docs = [
651
+ ("doc1" , {"text" : "hello world" }),
652
+ ("doc2" , {"text" : "hello universe" }),
653
+ ("doc3" , {"text" : "goodbye world" }),
654
+ ]
655
+
656
+ index_config = {
657
+ "dims" : fake_embeddings .dims ,
658
+ "embed" : fake_embeddings ,
659
+ "distance_type" : "cosine" ,
660
+ "fields" : ["text" ],
661
+ # vector_storage_type defaults to "json"
662
+ }
663
+
664
+ ttl_config = {"default_ttl" : 2 , "refresh_on_read" : True }
665
+
666
+ with RedisStore .from_conn_string (
667
+ redis_url , index = index_config , ttl = ttl_config
668
+ ) as store :
669
+ store .setup ()
670
+
671
+ # Insert documents
672
+ for key , value in docs :
673
+ store .put (("test_json" ,), key , value )
674
+
675
+ # Test vector search functionality
676
+ results = store .search (("test_json" ,), query = "hello" )
677
+ assert len (results ) >= 2 , "Vector search failed for JSON storage"
678
+
679
+ # Verify both hello documents are found
680
+ doc_keys = [r .key for r in results ]
681
+ assert "doc1" in doc_keys , "doc1 not found in JSON storage"
682
+ assert "doc2" in doc_keys , "doc2 not found in JSON storage"
683
+
684
+ # Test that scores are reasonable (should be > 0 for cosine similarity)
685
+ for result in results :
686
+ if result .key in ["doc1" , "doc2" ]:
687
+ assert (
688
+ result .score > 0
689
+ ), f"Invalid score for JSON storage: { result .score } "
690
+
691
+ # Test retrieval by key still works
692
+ item = store .get (("test_json" ,), "doc1" )
693
+ assert item is not None , "Get operation failed for JSON storage"
694
+ assert (
695
+ item .value ["text" ] == "hello world"
696
+ ), "Retrieved wrong value for JSON storage"
697
+
698
+
699
+ def test_vector_storage_hash (redis_url , fake_embeddings : CharacterEmbeddings ) -> None :
700
+ """Test hash vector storage for improved memory efficiency."""
701
+ # Test data
702
+ docs = [
703
+ ("doc1" , {"text" : "hello world" }),
704
+ ("doc2" , {"text" : "hello universe" }),
705
+ ("doc3" , {"text" : "goodbye world" }),
706
+ ]
707
+
708
+ index_config = {
709
+ "dims" : fake_embeddings .dims ,
710
+ "embed" : fake_embeddings ,
711
+ "distance_type" : "cosine" ,
712
+ "fields" : ["text" ],
713
+ "vector_storage_type" : "hash" , # Enable hash storage
714
+ }
715
+
716
+ ttl_config = {"default_ttl" : 2 , "refresh_on_read" : True }
717
+
718
+ with RedisStore .from_conn_string (
719
+ redis_url , index = index_config , ttl = ttl_config
720
+ ) as store :
721
+ store .setup ()
722
+
723
+ # Insert documents
724
+ for key , value in docs :
725
+ store .put (("test_hash" ,), key , value )
726
+
727
+ # Test vector search functionality
728
+ results = store .search (("test_hash" ,), query = "hello" )
729
+ assert len (results ) >= 2 , "Vector search failed for hash storage"
730
+
731
+ # Verify both hello documents are found
732
+ doc_keys = [r .key for r in results ]
733
+ assert "doc1" in doc_keys , "doc1 not found in hash storage"
734
+ assert "doc2" in doc_keys , "doc2 not found in hash storage"
735
+
736
+ # Test that scores are reasonable (should be > 0 for cosine similarity)
737
+ for result in results :
738
+ if result .key in ["doc1" , "doc2" ]:
739
+ assert (
740
+ result .score > 0
741
+ ), f"Invalid score for hash storage: { result .score } "
742
+
743
+ # Test retrieval by key still works
744
+ item = store .get (("test_hash" ,), "doc1" )
745
+ assert item is not None , "Get operation failed for hash storage"
746
+ assert (
747
+ item .value ["text" ] == "hello world"
748
+ ), "Retrieved wrong value for hash storage"
749
+
750
+
751
+ def test_vector_search_hash (redis_url , fake_embeddings : CharacterEmbeddings ) -> None :
752
+ """Test vector search functionality with hash storage."""
753
+ index_config = {
754
+ "dims" : fake_embeddings .dims ,
755
+ "embed" : fake_embeddings ,
756
+ "distance_type" : "cosine" ,
757
+ "fields" : ["text" ],
758
+ "vector_storage_type" : "hash" ,
759
+ }
760
+
761
+ ttl_config = {"default_ttl" : 2 , "refresh_on_read" : True }
762
+
763
+ with RedisStore .from_conn_string (
764
+ redis_url , index = index_config , ttl = ttl_config
765
+ ) as store :
766
+ store .setup ()
767
+
768
+ # Insert documents with text that can be embedded
769
+ docs = [
770
+ ("doc1" , {"text" : "short text" }),
771
+ ("doc2" , {"text" : "longer text document" }),
772
+ ("doc3" , {"text" : "longest text document here" }),
773
+ ]
774
+
775
+ for key , value in docs :
776
+ store .put (("test" ,), key , value )
777
+
778
+ # Search with query
779
+ results = store .search (("test" ,), query = "longer text" )
780
+ assert len (results ) >= 2
781
+
782
+ # Doc2 and doc3 should be closer matches to "longer text"
783
+ doc_keys = [r .key for r in results ]
784
+ assert "doc2" in doc_keys
785
+ assert "doc3" in doc_keys
786
+
787
+
788
+ def test_vector_search_with_filters_hash (
789
+ redis_url , fake_embeddings : CharacterEmbeddings
790
+ ) -> None :
791
+ """Test vector search with additional filters using hash storage."""
792
+ index_config = {
793
+ "dims" : fake_embeddings .dims ,
794
+ "embed" : fake_embeddings ,
795
+ "distance_type" : "cosine" ,
796
+ "fields" : ["text" ],
797
+ "vector_storage_type" : "hash" ,
798
+ }
799
+
800
+ ttl_config = {"default_ttl" : 2 , "refresh_on_read" : True }
801
+
802
+ with RedisStore .from_conn_string (
803
+ redis_url , index = index_config , ttl = ttl_config
804
+ ) as store :
805
+ store .setup ()
806
+
807
+ # Insert test documents
808
+ docs = [
809
+ ("doc1" , {"text" : "red apple" , "color" : "red" , "score" : 4.5 }),
810
+ ("doc2" , {"text" : "red car" , "color" : "red" , "score" : 3.0 }),
811
+ ("doc3" , {"text" : "green apple" , "color" : "green" , "score" : 4.0 }),
812
+ ("doc4" , {"text" : "blue car" , "color" : "blue" , "score" : 3.5 }),
813
+ ]
814
+
815
+ for key , value in docs :
816
+ store .put (("test" ,), key , value )
817
+
818
+ # Search for "apple" within red items
819
+ results = store .search (("test" ,), query = "apple" , filter = {"color" : "red" })
820
+ assert len (results ) >= 1
821
+ # Doc1 should be the closest match for "apple" with color=red
822
+ assert results [0 ].key == "doc1"
823
+
824
+ # Search for "car" within red items
825
+ results = store .search (("test" ,), query = "car" , filter = {"color" : "red" })
826
+ assert len (results ) >= 1
827
+ # Doc2 should be the closest match for "car" with color=red
828
+ assert results [0 ].key == "doc2"
829
+
830
+
831
+ def test_vector_update_with_score_verification_hash (
832
+ redis_url , fake_embeddings : CharacterEmbeddings
833
+ ) -> None :
834
+ """Test that updating items properly updates their embeddings with hash storage."""
835
+ index_config = {
836
+ "dims" : fake_embeddings .dims ,
837
+ "embed" : fake_embeddings ,
838
+ "distance_type" : "cosine" ,
839
+ "fields" : ["text" ],
840
+ "vector_storage_type" : "hash" ,
841
+ }
842
+
843
+ ttl_config = {"default_ttl" : 2 , "refresh_on_read" : True }
844
+
845
+ with RedisStore .from_conn_string (
846
+ redis_url , index = index_config , ttl = ttl_config
847
+ ) as store :
848
+ store .setup ()
849
+
850
+ store .put (("test" ,), "doc1" , {"text" : "zany zebra xylophone" })
851
+ store .put (("test" ,), "doc2" , {"text" : "something about dogs" })
852
+
853
+ # Search for a term similar to doc1's content
854
+ results_initial = store .search (("test" ,), query = "zany xylophone" )
855
+ assert len (results_initial ) >= 1
856
+ assert results_initial [0 ].key == "doc1"
857
+ initial_score = results_initial [0 ].score
858
+
859
+ # Update doc1 to be about dogs instead
860
+ store .put (("test" ,), "doc1" , {"text" : "new text about dogs" })
861
+
862
+ # The original query should now match doc1 less strongly
863
+ results_after = store .search (("test" ,), query = "zany xylophone" )
864
+ assert len (results_after ) >= 1
865
+ after_score = next ((r .score for r in results_after if r .key == "doc1" ), None )
866
+ if after_score is not None :
867
+ assert after_score < initial_score
868
+
869
+ # A dog-related query should now match doc1 more strongly
870
+ results_new = store .search (("test" ,), query = "dogs text" )
871
+ doc1_score = next ((r .score for r in results_new if r .key == "doc1" ), None )
872
+ assert doc1_score is not None
0 commit comments