diff --git a/pom.xml b/pom.xml index def0ffc46e..ff782d0e41 100644 --- a/pom.xml +++ b/pom.xml @@ -352,6 +352,7 @@ **/VectorTestUtils.java **/VAddParams.java **/VSimParams.java + **/VSimScoreAttribs.java **/*FunctionCommandsTest* diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index d04aeb2caa..ddd6071776 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -514,15 +514,14 @@ public Map build(Object data) { final List list = (List) data; if (list.isEmpty()) return Collections.emptyMap(); + final JedisByteMap map = new JedisByteMap<>(); if (list.get(0) instanceof KeyValue) { - final Map map = new LinkedHashMap<>(list.size(), 1f); for (Object o : list) { KeyValue kv = (KeyValue) o; map.put(BINARY.build(kv.getKey()), DOUBLE.build(kv.getValue())); } return map; } else { - final Map map = new LinkedHashMap<>(list.size() / 2, 1f); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { map.put(BINARY.build(iterator.next()), DOUBLE.build(iterator.next())); @@ -2249,7 +2248,73 @@ public String toString() { }; // Vector Set builders + public static final Builder> VSIM_SCORE_ATTRIBS_MAP = new Builder>() { + @Override + @SuppressWarnings("unchecked") + public Map build(Object data) { + if (data == null) return null; + List list = (List) data; + if (list.isEmpty()) return Collections.emptyMap(); + + if (list.get(0) instanceof KeyValue) { + final Map result = new LinkedHashMap<>(list.size(), 1f); + for (Object o : list) { + KeyValue kv = (KeyValue) o; + List scoreAndAttribs = (List) kv.getValue(); + result.put(STRING.build(kv.getKey()), + new VSimScoreAttribs(DOUBLE.build(scoreAndAttribs.get(0)), + STRING.build(scoreAndAttribs.get(1)))); + } + return result; + } else { + final Map result = new LinkedHashMap<>(list.size() / 3, 1f); + for (int i = 0; i < list.size(); i += 3) { + result.put(STRING.build(list.get(i)), + new VSimScoreAttribs(DOUBLE.build(list.get(i + 1)), STRING.build(list.get(i + 2)))); + } + return result; + } + } + + @Override + public String toString() { + return "Map"; + } + }; + + public static final Builder> VSIM_SCORE_ATTRIBS_BINARY_MAP = new Builder>() { + + @Override + @SuppressWarnings("unchecked") + public Map build(Object data) { + if (data == null) return null; + List list = (List) data; + if (list.isEmpty()) return Collections.emptyMap(); + + JedisByteMap result = new JedisByteMap<>(); + if (list.get(0) instanceof KeyValue) { + for (Object o : list) { + KeyValue kv = (KeyValue) o; + List scoreAndAttribs = (List) kv.getValue(); + result.put(BINARY.build(kv.getKey()), + new VSimScoreAttribs(DOUBLE.build(scoreAndAttribs.get(0)), + STRING.build(scoreAndAttribs.get(1)))); + } + } else { + for (int i = 0; i < list.size(); i += 3) { + result.put(BINARY.build(list.get(i)), + new VSimScoreAttribs(DOUBLE.build(list.get(i + 1)), STRING.build(list.get(i + 2)))); + } + } + return result; + } + + @Override + public String toString() { + return "Map"; + } + }; public static final Builder VEMB_RAW_RESULT = new Builder() { @Override diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 66a58c06fd..7359081dee 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -4883,6 +4883,15 @@ public final CommandObject> vsimWithScores(String key, float return new CommandObject<>(args, BuilderFactory.STRING_DOUBLE_MAP); } + public final CommandObject> vsimWithScoresAndAttribs(String key, float[] vector, VSimParams params) { + CommandArguments args = commandArguments(Command.VSIM).key(key); + addVectors(vector, args); + args.add(Keyword.WITHSCORES); + args.add(Keyword.WITHATTRIBS); + addOptionalParams(params, args); + return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_MAP); + } + public final CommandObject> vsimByElement(String key, String element) { return vsimByElement(key, element, null); } @@ -4902,6 +4911,15 @@ public final CommandObject> vsimByElementWithScores(String k return new CommandObject<>(args, BuilderFactory.STRING_DOUBLE_MAP); } + public final CommandObject> vsimByElementWithScoresAndAttribs(String key, String element, VSimParams params) { + CommandArguments args = commandArguments(Command.VSIM).key(key); + args.add(Keyword.ELE).add(element); + args.add(Keyword.WITHSCORES); + args.add(Keyword.WITHATTRIBS); + addOptionalParams(params, args); + return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_MAP); + } + public final CommandObject> vsim(byte[] key, float[] vector) { return vsim(key, vector, null); } @@ -4921,6 +4939,15 @@ public final CommandObject> vsimWithScores(byte[] key, float return new CommandObject<>(args, BuilderFactory.BINARY_DOUBLE_MAP); } + public final CommandObject> vsimWithScoresAndAttribs(byte[] key, float[] vector, VSimParams params) { + CommandArguments args = commandArguments(Command.VSIM).key(key); + addVectors(vector, args); + args.add(Keyword.WITHSCORES); + args.add(Keyword.WITHATTRIBS); + addOptionalParams(params, args); + return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_BINARY_MAP); + } + public final CommandObject> vsimByElement(byte[] key, byte[] element) { return vsimByElement(key, element, null); } @@ -4940,6 +4967,15 @@ public final CommandObject> vsimByElementWithScores(byte[] k return new CommandObject<>(args, BuilderFactory.BINARY_DOUBLE_MAP); } + public final CommandObject> vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, VSimParams params) { + CommandArguments args = commandArguments(Command.VSIM).key(key); + args.add(Keyword.ELE).add(element); + args.add(Keyword.WITHSCORES); + args.add(Keyword.WITHATTRIBS); + addOptionalParams(params, args); + return new CommandObject<>(args, BuilderFactory.VSIM_SCORE_ATTRIBS_BINARY_MAP); + } + public final CommandObject vdim(String key) { return new CommandObject<>(commandArguments(Command.VDIM).key(key), BuilderFactory.LONG); } diff --git a/src/main/java/redis/clients/jedis/Jedis.java b/src/main/java/redis/clients/jedis/Jedis.java index 3f00dc8b8e..1dc47145e3 100644 --- a/src/main/java/redis/clients/jedis/Jedis.java +++ b/src/main/java/redis/clients/jedis/Jedis.java @@ -10048,6 +10048,12 @@ public Map vsimWithScores(String key, float[] vector, VSimParams return connection.executeCommand(commandObjects.vsimWithScores(key, vector, params)); } + @Override + public Map vsimWithScoresAndAttribs(String key, float[] vector, VSimParams params) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params)); + } + @Override public List vsimByElement(String key, String element) { checkIsInMultiOrPipeline(); @@ -10066,6 +10072,12 @@ public Map vsimByElementWithScores(String key, String element, V return connection.executeCommand(commandObjects.vsimByElementWithScores(key, element, params)); } + @Override + public Map vsimByElementWithScoresAndAttribs(String key, String element, VSimParams params) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params)); + } + @Override public long vdim(String key) { checkIsInMultiOrPipeline(); @@ -10193,6 +10205,12 @@ public Map vsimWithScores(byte[] key, float[] vector, VSimParams return connection.executeCommand(commandObjects.vsimWithScores(key, vector, params)); } + @Override + public Map vsimWithScoresAndAttribs(byte[] key, float[] vector, VSimParams params) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params)); + } + @Override public List vsimByElement(byte[] key, byte[] element) { checkIsInMultiOrPipeline(); @@ -10211,6 +10229,12 @@ public Map vsimByElementWithScores(byte[] key, byte[] element, V return connection.executeCommand(commandObjects.vsimByElementWithScores(key, element, params)); } + @Override + public Map vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, VSimParams params) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params)); + } + @Override public long vdim(byte[] key) { checkIsInMultiOrPipeline(); diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index daa7a3c917..ccc185188a 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -342,7 +342,7 @@ public static enum Keyword implements Rawable { ARGS, RANK, NOW, VERSION, ADDR, SKIPME, USER, LADDR, FIELDS, CHANNELS, NUMPAT, NUMSUB, SHARDCHANNELS, SHARDNUMSUB, NOVALUES, MAXAGE, FXX, FNX, // Vector set keywords - REDUCE, CAS, NOQUANT, Q8, BIN, EF, SETATTR, M, VALUES, FP32, ELE, FILTER, FILTER_EF, TRUTH, NOTHREAD, RAW, EPSILON; + REDUCE, CAS, NOQUANT, Q8, BIN, EF, SETATTR, M, VALUES, FP32, ELE, FILTER, FILTER_EF, TRUTH, NOTHREAD, RAW, EPSILON, WITHATTRIBS; private final byte[] raw; diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 0e72c7372e..3f28728727 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -5223,6 +5223,11 @@ public Map vsimWithScores(String key, float[] vector, VSimParams return executeCommand(commandObjects.vsimWithScores(key, vector, params)); } + @Override + public Map vsimWithScoresAndAttribs(String key, float[] vector, VSimParams params) { + return executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params)); + } + @Override public List vsimByElement(String key, String element) { return executeCommand(commandObjects.vsimByElement(key, element)); @@ -5238,6 +5243,11 @@ public Map vsimByElementWithScores(String key, String element, V return executeCommand(commandObjects.vsimByElementWithScores(key, element, params)); } + @Override + public Map vsimByElementWithScoresAndAttribs(String key, String element, VSimParams params) { + return executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params)); + } + @Override public long vdim(String key) { return executeCommand(commandObjects.vdim(key)); @@ -5344,6 +5354,11 @@ public Map vsimWithScores(byte[] key, float[] vector, VSimParams return executeCommand(commandObjects.vsimWithScores(key, vector, params)); } + @Override + public Map vsimWithScoresAndAttribs(byte[] key, float[] vector, VSimParams params) { + return executeCommand(commandObjects.vsimWithScoresAndAttribs(key, vector, params)); + } + @Override public List vsimByElement(byte[] key, byte[] element) { return executeCommand(commandObjects.vsimByElement(key, element)); @@ -5359,6 +5374,11 @@ public Map vsimByElementWithScores(byte[] key, byte[] element, V return executeCommand(commandObjects.vsimByElementWithScores(key, element, params)); } + @Override + public Map vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, VSimParams params) { + return executeCommand(commandObjects.vsimByElementWithScoresAndAttribs(key, element, params)); + } + @Override public long vdim(byte[] key) { return executeCommand(commandObjects.vdim(key)); diff --git a/src/main/java/redis/clients/jedis/commands/VectorSetBinaryCommands.java b/src/main/java/redis/clients/jedis/commands/VectorSetBinaryCommands.java index c039dc0a1f..7dfc03ac28 100644 --- a/src/main/java/redis/clients/jedis/commands/VectorSetBinaryCommands.java +++ b/src/main/java/redis/clients/jedis/commands/VectorSetBinaryCommands.java @@ -1,12 +1,13 @@ package redis.clients.jedis.commands; -import java.util.List; -import java.util.Map; - import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.params.VAddParams; import redis.clients.jedis.params.VSimParams; import redis.clients.jedis.resps.RawVector; +import redis.clients.jedis.resps.VSimScoreAttribs; + +import java.util.List; +import java.util.Map; /** * Interface for Redis Vector Set binary commands. Vector sets are a new data type introduced in @@ -144,6 +145,21 @@ public interface VectorSetBinaryCommands { @Experimental Map vsimWithScores(byte[] key, float[] vector, VSimParams params); + /** + * VSIM Command Return elements + * similar to a given vector with their similarity scores and attributes. + *

+ * Time complexity: O(log(N)) where N is the number of elements in the vector set. + * @param key the name of the key that holds the vector set data + * @param vector the vector to use as similarity reference + * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be + * automatically added) + * @return map of element names to their similarity scores and attributes + */ + @Experimental + Map vsimWithScoresAndAttribs(byte[] key, float[] vector, + VSimParams params); + /** * VSIM Command Return elements * similar to a given element in the vector set. @@ -183,6 +199,21 @@ public interface VectorSetBinaryCommands { @Experimental Map vsimByElementWithScores(byte[] key, byte[] element, VSimParams params); + /** + * VSIM Command Return elements + * similar to a given element in the vector set with their similarity scores and attributes. + *

+ * Time complexity: O(log(N)) where N is the number of elements in the vector set. + * @param key the name of the key that holds the vector set data + * @param element the name of the element to use as similarity reference + * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be + * automatically added) + * @return map of element names to their similarity scores and attributes + */ + @Experimental + Map vsimByElementWithScoresAndAttribs(byte[] key, byte[] element, + VSimParams params); + /** * VDIM Command Return the number * of dimensions of the vectors in the specified vector set. diff --git a/src/main/java/redis/clients/jedis/commands/VectorSetCommands.java b/src/main/java/redis/clients/jedis/commands/VectorSetCommands.java index 965e86b991..b2cbb96f50 100644 --- a/src/main/java/redis/clients/jedis/commands/VectorSetCommands.java +++ b/src/main/java/redis/clients/jedis/commands/VectorSetCommands.java @@ -7,6 +7,7 @@ import redis.clients.jedis.params.VAddParams; import redis.clients.jedis.params.VSimParams; import redis.clients.jedis.resps.RawVector; +import redis.clients.jedis.resps.VSimScoreAttribs; import redis.clients.jedis.resps.VectorInfo; /** @@ -145,6 +146,21 @@ public interface VectorSetCommands { @Experimental Map vsimWithScores(String key, float[] vector, VSimParams params); + /** + * VSIM Command Return elements + * similar to a given vector with their similarity scores and attributes. + *

+ * Time complexity: O(log(N)) where N is the number of elements in the vector set. + * @param key the name of the key that holds the vector set data + * @param vector the vector to use as similarity reference + * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be + * automatically added) + * @return map of element names to their similarity scores and attributes + */ + @Experimental + Map vsimWithScoresAndAttribs(String key, float[] vector, + VSimParams params); + /** * VSIM Command Return elements * similar to a given element in the vector set. @@ -184,6 +200,21 @@ public interface VectorSetCommands { @Experimental Map vsimByElementWithScores(String key, String element, VSimParams params); + /** + * VSIM Command Return elements + * similar to a given element in the vector set with their similarity scores and attributes. + *

+ * Time complexity: O(log(N)) where N is the number of elements in the vector set. + * @param key the name of the key that holds the vector set data + * @param element the name of the element to use as similarity reference + * @param params additional parameters for the VSIM command (WITHSCORES and WITHATTRIBS will be + * automatically added) + * @return map of element names to their similarity scores and attributes + */ + @Experimental + Map vsimByElementWithScoresAndAttribs(String key, String element, + VSimParams params); + /** * VDIM Command Return the number * of dimensions of the vectors in the specified vector set. diff --git a/src/main/java/redis/clients/jedis/resps/VSimScoreAttribs.java b/src/main/java/redis/clients/jedis/resps/VSimScoreAttribs.java new file mode 100644 index 0000000000..4beab9fc17 --- /dev/null +++ b/src/main/java/redis/clients/jedis/resps/VSimScoreAttribs.java @@ -0,0 +1,63 @@ +package redis.clients.jedis.resps; + +import redis.clients.jedis.annots.Experimental; + +/** + * Response object containing both similarity score and attributes for VSIM command when used with + * WITHSCORES and WITHATTRIBS options. + */ +@Experimental +public class VSimScoreAttribs { + + private final Double score; + private final String attributes; + + /** + * Creates a new VSimScoreAttribs instance. + * @param score the similarity score (0.0 to 1.0) + * @param attributes the element attributes as JSON string, or null if no attributes + */ + public VSimScoreAttribs(Double score, String attributes) { + this.score = score; + this.attributes = attributes; + } + + /** + * Gets the similarity score. + * @return the similarity score between 0.0 and 1.0 + */ + public Double getScore() { + return score; + } + + /** + * Gets the element attributes. + * @return the attributes as JSON string, or null if no attributes are set + */ + public String getAttributes() { + return attributes; + } + + @Override + public String toString() { + return "VSimScoreAttribs{" + "score=" + score + ", attributes='" + attributes + '\'' + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + VSimScoreAttribs that = (VSimScoreAttribs) o; + + if (score != null ? !score.equals(that.score) : that.score != null) return false; + return attributes != null ? attributes.equals(that.attributes) : that.attributes == null; + } + + @Override + public int hashCode() { + int result = score != null ? score.hashCode() : 0; + result = 31 * result + (attributes != null ? attributes.hashCode() : 0); + return result; + } +} diff --git a/src/test/java/redis/clients/jedis/commands/jedis/VectorSetCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/VectorSetCommandsTest.java index 038e62bb9c..327a83dbf4 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/VectorSetCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/VectorSetCommandsTest.java @@ -12,6 +12,7 @@ import redis.clients.jedis.params.VAddParams; import redis.clients.jedis.params.VSimParams; import redis.clients.jedis.resps.RawVector; +import redis.clients.jedis.resps.VSimScoreAttribs; import redis.clients.jedis.resps.VectorInfo; import redis.clients.jedis.util.SafeEncoder; @@ -1441,6 +1442,81 @@ public void testVsimWithScores(TestInfo testInfo) { } } + /** + * Test VSIM with scores and attributes. + */ + @Test + @SinceRedisVersion("8.2.0") + public void testVsimWithScoresAndAttribs(TestInfo testInfo) { + String testKey = testInfo.getDisplayName() + ":test:vector:set:scores:attribs"; + + // Add test vectors with attributes + VAddParams addParams1 = new VAddParams().setAttr("category=test,priority=high"); + jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, "element1", addParams1); + + VAddParams addParams2 = new VAddParams().setAttr("category=prod,priority=low"); + jedis.vadd(testKey, new float[] { 0.15f, 0.25f, 0.35f }, "element2", addParams2); + + // Add element without attributes + jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, "element3"); + + VSimParams params = new VSimParams(); + Map similarWithScoresAndAttribs = jedis + .vsimWithScoresAndAttribs(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params); + assertNotNull(similarWithScoresAndAttribs); + assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap()))); + + // Verify scores and attributes are present + for (Map.Entry entry : similarWithScoresAndAttribs.entrySet()) { + String element = entry.getKey(); + VSimScoreAttribs data = entry.getValue(); + + assertNotNull(data); + assertNotNull(data.getScore()); + assertThat(data.getScore(), is(both(greaterThanOrEqualTo(0.0)).and(lessThanOrEqualTo(1.0)))); + + // Check attributes based on element + if ("element1".equals(element)) { + assertEquals("category=test,priority=high", data.getAttributes()); + } else if ("element2".equals(element)) { + assertEquals("category=prod,priority=low", data.getAttributes()); + } else if ("element3".equals(element)) { + assertNull(data.getAttributes()); // No attributes set + } + } + } + + /** + * Test VSIM by element with scores and attributes. + */ + @Test + @SinceRedisVersion("8.2.0") + public void testVsimByElementWithScoresAndAttribs(TestInfo testInfo) { + String testKey = testInfo.getDisplayName() + ":test:vector:set:element:scores:attribs"; + + // Add test vectors with attributes + VAddParams addParams1 = new VAddParams().setAttr("type=reference,quality=high"); + jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, "reference", addParams1); + + VAddParams addParams2 = new VAddParams().setAttr("type=similar,quality=medium"); + jedis.vadd(testKey, new float[] { 0.12f, 0.22f, 0.32f }, "similar1", addParams2); + + VAddParams addParams3 = new VAddParams().setAttr("type=different,quality=low"); + jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, "different", addParams3); + + VSimParams params = new VSimParams(); + Map similarWithScoresAndAttribs = jedis + .vsimByElementWithScoresAndAttribs(testKey, "reference", params); + assertNotNull(similarWithScoresAndAttribs); + assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap()))); + + // Reference element should have perfect similarity with itself + assertTrue(similarWithScoresAndAttribs.containsKey("reference")); + VSimScoreAttribs referenceData = similarWithScoresAndAttribs.get("reference"); + assertThat(referenceData.getScore(), is(closeTo(1.0, 0.001))); + assertEquals("type=reference,quality=high", referenceData.getAttributes()); + } + /** * Test VSIM command with binary keys and elements. Verifies vector similarity search works with * byte arrays. diff --git a/src/test/java/redis/clients/jedis/commands/unified/VectorSetCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/VectorSetCommandsTestBase.java index ca2a9ef4a2..9b30f01784 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/VectorSetCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/unified/VectorSetCommandsTestBase.java @@ -27,6 +27,7 @@ import redis.clients.jedis.params.VAddParams; import redis.clients.jedis.params.VSimParams; import redis.clients.jedis.resps.RawVector; +import redis.clients.jedis.resps.VSimScoreAttribs; import redis.clients.jedis.resps.VectorInfo; import redis.clients.jedis.util.SafeEncoder; import redis.clients.jedis.util.VectorTestUtils; @@ -1446,6 +1447,81 @@ public void testVsimWithScores(TestInfo testInfo) { } } + /** + * Test VSIM with scores and attributes. + */ + @Test + @SinceRedisVersion("8.2.0") + public void testVsimWithScoresAndAttribs(TestInfo testInfo) { + String testKey = testInfo.getDisplayName() + ":test:vector:set:scores:attribs"; + + // Add test vectors with attributes + VAddParams addParams1 = new VAddParams().setAttr("category=test,priority=high"); + jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, "element1", addParams1); + + VAddParams addParams2 = new VAddParams().setAttr("category=prod,priority=low"); + jedis.vadd(testKey, new float[] { 0.15f, 0.25f, 0.35f }, "element2", addParams2); + + // Add element without attributes + jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, "element3"); + + VSimParams params = new VSimParams(); + Map similarWithScoresAndAttribs = jedis + .vsimWithScoresAndAttribs(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params); + assertNotNull(similarWithScoresAndAttribs); + assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap()))); + + // Verify scores and attributes are present + for (Map.Entry entry : similarWithScoresAndAttribs.entrySet()) { + String element = entry.getKey(); + VSimScoreAttribs data = entry.getValue(); + + assertNotNull(data); + assertNotNull(data.getScore()); + assertThat(data.getScore(), is(both(greaterThanOrEqualTo(0.0)).and(lessThanOrEqualTo(1.0)))); + + // Check attributes based on element + if ("element1".equals(element)) { + assertEquals("category=test,priority=high", data.getAttributes()); + } else if ("element2".equals(element)) { + assertEquals("category=prod,priority=low", data.getAttributes()); + } else if ("element3".equals(element)) { + assertNull(data.getAttributes()); // No attributes set + } + } + } + + /** + * Test VSIM by element with scores and attributes. + */ + @Test + @SinceRedisVersion("8.2.0") + public void testVsimByElementWithScoresAndAttribs(TestInfo testInfo) { + String testKey = testInfo.getDisplayName() + ":test:vector:set:element:scores:attribs"; + + // Add test vectors with attributes + VAddParams addParams1 = new VAddParams().setAttr("type=reference,quality=high"); + jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, "reference", addParams1); + + VAddParams addParams2 = new VAddParams().setAttr("type=similar,quality=medium"); + jedis.vadd(testKey, new float[] { 0.12f, 0.22f, 0.32f }, "similar1", addParams2); + + VAddParams addParams3 = new VAddParams().setAttr("type=different,quality=low"); + jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, "different", addParams3); + + VSimParams params = new VSimParams(); + Map similarWithScoresAndAttribs = jedis + .vsimByElementWithScoresAndAttribs(testKey, "reference", params); + assertNotNull(similarWithScoresAndAttribs); + assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap()))); + + // Reference element should have perfect similarity with itself + assertTrue(similarWithScoresAndAttribs.containsKey("reference")); + VSimScoreAttribs referenceData = similarWithScoresAndAttribs.get("reference"); + assertThat(referenceData.getScore(), is(closeTo(1.0, 0.001))); + assertEquals("type=reference,quality=high", referenceData.getAttributes()); + } + /** * Test VSIM command with binary keys and elements. Verifies vector similarity search works with * byte arrays. @@ -1514,7 +1590,7 @@ public void testVsimBinaryWithScores(TestInfo testInfo) { assertThat(similarWithScores, is(not(anEmptyMap()))); // Element1 should have perfect similarity with itself - Double element1Score = getBinaryScoreForElement(similarWithScores, "element1"); + Double element1Score = similarWithScores.get("element1".getBytes()); assertNotNull(element1Score); assertThat(element1Score, is(closeTo(1.0, 0.001))); @@ -1547,16 +1623,99 @@ private List getBinaryElementNames(List binaryElements) { } /** - * Helper method to get score for a specific element from binary score map. + * Test VSIM command with binary keys and scores and attributes. Verifies vector similarity search + * returns scores and attributes with binary data. */ - private Double getBinaryScoreForElement(Map scoreMap, String elementName) { - byte[] elementBytes = elementName.getBytes(); - for (Map.Entry entry : scoreMap.entrySet()) { - if (java.util.Arrays.equals(entry.getKey(), elementBytes)) { - return entry.getValue(); + @Test + @SinceRedisVersion("8.2.0") + public void testVsimBinaryWithScoresAndAttribs(TestInfo testInfo) { + byte[] testKey = (testInfo.getDisplayName() + ":test:vector:set:binary:scores:attribs") + .getBytes(); + + setupVSimTestSetBinaryWithAttribs(testKey); + + // Test vsim with vector, scores and attributes (binary) + VSimParams params = new VSimParams(); + Map similarWithScoresAndAttribs = jedis + .vsimWithScoresAndAttribs(testKey, new float[] { 0.15f, 0.25f, 0.35f }, params); + assertNotNull(similarWithScoresAndAttribs); + assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap()))); + + // Verify scores and attributes are present and valid + for (Map.Entry entry : similarWithScoresAndAttribs.entrySet()) { + byte[] element = entry.getKey(); + VSimScoreAttribs data = entry.getValue(); + + assertNotNull(data); + assertNotNull(data.getScore()); + assertThat(data.getScore(), is(both(greaterThanOrEqualTo(0.0)).and(lessThanOrEqualTo(1.0)))); + + // Check attributes based on element + String elementName = new String(element); + if ("element1".equals(elementName)) { + assertEquals("category=test,priority=high", data.getAttributes()); + } else if ("element2".equals(elementName)) { + assertEquals("category=prod,priority=low", data.getAttributes()); + } else if ("element3".equals(elementName)) { + assertNull(data.getAttributes()); // No attributes set } } - return null; + + } + + /** + * Test VSIM by element command with binary keys and scores and attributes. Verifies element-based + * vector similarity search returns scores and attributes with binary data. + */ + @Test + @SinceRedisVersion("8.2.0") + public void testVsimByElementBinaryWithScoresAndAttribs(TestInfo testInfo) { + byte[] testKey = (testInfo.getDisplayName() + ":test:vector:set:element:binary:scores:attribs") + .getBytes(); + + // Add test vectors with attributes + byte[] referenceElement = "reference".getBytes(); + VAddParams addParams1 = new VAddParams().setAttr("type=reference,quality=high"); + jedis.vadd(testKey, new float[] { 0.1f, 0.2f, 0.3f }, referenceElement, addParams1); + + byte[] similar1Element = "similar1".getBytes(); + VAddParams addParams2 = new VAddParams().setAttr("type=similar,quality=medium"); + jedis.vadd(testKey, new float[] { 0.12f, 0.22f, 0.32f }, "similar1".getBytes(), addParams2); + + byte[] differentElement = "different".getBytes(); + VAddParams addParams3 = new VAddParams().setAttr("type=different,quality=low"); + jedis.vadd(testKey, new float[] { 0.9f, 0.8f, 0.7f }, "different".getBytes(), addParams3); + + VSimParams params = new VSimParams(); + Map similarWithScoresAndAttribs = jedis + .vsimByElementWithScoresAndAttribs(testKey, referenceElement, params); + assertNotNull(similarWithScoresAndAttribs); + assertThat(similarWithScoresAndAttribs, is(not(anEmptyMap()))); + + // Reference element should have perfect similarity with itself + VSimScoreAttribs referenceData = similarWithScoresAndAttribs.get(referenceElement); + assertThat(referenceData.getScore(), is(closeTo(1.0, 0.001))); + assertEquals("type=reference,quality=high", referenceData.getAttributes()); + + } + + /** + * Helper method to set up test vector set for binary VSIM tests with attributes. + */ + private void setupVSimTestSetBinaryWithAttribs(byte[] testKey) { + // Add test vectors with attributes - same as non-binary version + float[] vector1 = { 0.1f, 0.2f, 0.3f }; + float[] vector2 = { 0.15f, 0.25f, 0.35f }; + float[] vector3 = { 0.9f, 0.8f, 0.7f }; + + VAddParams addParams1 = new VAddParams().setAttr("category=test,priority=high"); + jedis.vadd(testKey, vector1, "element1".getBytes(), addParams1); + + VAddParams addParams2 = new VAddParams().setAttr("category=prod,priority=low"); + jedis.vadd(testKey, vector2, "element2".getBytes(), addParams2); + + // Element without attributes + jedis.vadd(testKey, vector3, "element3".getBytes()); } }