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
4 changes: 3 additions & 1 deletion .github/wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,4 +340,6 @@ SVS
Hitless
hitless
noninstantiability
Changelog
Changelog
WITHATTRIBS
vsimWithScoreWithAttribs
2 changes: 1 addition & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ If you need any support, meet Lettuce at

---

**Full Changelog**: [6.8.0.RELEASE...7.0.0.BETA1](https://github.com/redis/lettuce/compare/6.8.0.RELEASE...v7.0.0.BETA1)
**Full Changelog**: [6.8.0.RELEASE...7.0.0.BETA1](https://github.com/redis/lettuce/compare/6.8.0.RELEASE...v7.0.0.BETA1)
41 changes: 39 additions & 2 deletions docs/user-guide/vector-sets.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,47 @@ VSimArgs simArgs = VSimArgs.Builder
.build();

Map<String, Double> resultsWithScores = vectorSet.vsimWithScore("points", simArgs, 0.9, 0.1);
resultsWithScores.forEach((element, score) ->
resultsWithScores.forEach((element, score) ->
System.out.println(element + ": " + score));
```

### Similarity Cutoff with EPSILON

Use EPSILON to apply a maximum distance cutoff so that only sufficiently similar results are returned.
The cutoff is defined as a distance threshold epsilon in [0.0, 1.0]; results must have similarity ≥ 1 − epsilon.
Smaller epsilon values yield fewer, more similar results.

```java
VSimArgs simArgs = VSimArgs.Builder
.count(10)
.epsilon(0.2) // distance cutoff; results have similarity >= 0.8
.build();

Map<String, Double> results = vectorSet.vsimWithScore("points", simArgs, 0.9, 0.1);
```


### Including Attributes in Results with WITHATTRIBS

Attributes are included by using the API variant that emits WITHATTRIBS. Use vsimWithScoreWithAttribs(...) to obtain scores and attributes per element.

```java
VSimArgs args = VSimArgs.Builder
.count(10)
.epsilon(0.2)
.build();

Map<String, VSimScoreAttribs> results = vectorSet.vsimWithScoreWithAttribs("points", args, 0.9, 0.1);
results.forEach((element, sa) -> {
double score = sa.getScore();
String attrs = sa.getAttributes();
System.out.println(element + ": score=" + score + ", attrs=" + attrs);
});
```

Note: WITHATTRIBS requires a Redis version that supports returning attributes (Redis 8.2+). Methods are marked @Experimental and subject to change.


## Element Attributes and Filtering

### Setting and Getting Attributes
Expand Down Expand Up @@ -761,4 +798,4 @@ public class VectorMigrationService {
public Map<String, Object> getMetadata() { return metadata; }
}
}
```
```
21 changes: 21 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import io.lettuce.core.search.arguments.SugGetArgs;
import io.lettuce.core.search.arguments.SynUpdateArgs;
import io.lettuce.core.vector.RawVector;
import io.lettuce.core.vector.VSimScoreAttribs;
import io.lettuce.core.vector.VectorMetadata;

import java.time.Duration;
Expand Down Expand Up @@ -2073,6 +2074,26 @@ public RedisFuture<Map<V, Double>> vsimWithScore(K key, VSimArgs args, V element
return dispatch(vectorSetCommandBuilder.vsimWithScore(key, args, element));
}

@Override
public RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, Double... vectors) {
return dispatch(vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, null, vectors));
}

@Override
public RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, V element) {
return dispatch(vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, null, element));
}

@Override
public RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs args, Double... vectors) {
return dispatch(vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, args, vectors));
}

@Override
public RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs args, V element) {
return dispatch(vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, args, element));
}

@Override
public RedisFuture<List<K>> keys(K pattern) {
return dispatch(commandBuilder.keys(pattern));
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import io.lettuce.core.tracing.TraceContextProvider;
import io.lettuce.core.tracing.Tracing;
import io.lettuce.core.vector.RawVector;
import io.lettuce.core.vector.VSimScoreAttribs;
import io.lettuce.core.vector.VectorMetadata;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.ImmediateEventExecutor;
Expand Down Expand Up @@ -2138,6 +2139,26 @@ public Mono<Map<V, Double>> vsimWithScore(K key, VSimArgs args, V element) {
return createMono(() -> vectorSetCommandBuilder.vsimWithScore(key, args, element));
}

@Override
public Mono<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, Double... vectors) {
return createMono(() -> vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, null, vectors));
}

@Override
public Mono<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, V element) {
return createMono(() -> vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, null, element));
}

@Override
public Mono<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs args, Double... vectors) {
return createMono(() -> vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, args, vectors));
}

@Override
public Mono<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs args, V element) {
return createMono(() -> vectorSetCommandBuilder.vsimWithScoreWithAttribs(key, args, element));
}

@Override
public Flux<K> keys(K pattern) {
return createDissolvingFlux(() -> commandBuilder.keys(pattern));
Expand Down
51 changes: 49 additions & 2 deletions src/main/java/io/lettuce/core/RedisVectorSetCommandBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.lettuce.core.protocol.RedisCommand;
import io.lettuce.core.vector.RawVector;
import io.lettuce.core.vector.VectorMetadata;
import io.lettuce.core.vector.VSimScoreAttribs;

import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -383,7 +384,7 @@ public Command<K, V, List<V>> vsim(K key, VSimArgs vSimArgs, Double[] vectors) {
if (vectors.length > 1) {
args.add(CommandKeyword.VALUES);
args.add(vectors.length);
Arrays.stream(vectors).map(Object::toString).forEach(args::add);
Arrays.stream(vectors).forEach(args::add);
} else {
args.add(vectors[0]);
}
Expand Down Expand Up @@ -463,7 +464,7 @@ public Command<K, V, Map<V, Double>> vsimWithScore(K key, VSimArgs vSimArgs, Dou
if (vectors.length > 1) {
args.add(CommandKeyword.VALUES);
args.add(vectors.length);
Arrays.stream(vectors).map(Object::toString).forEach(args::add);
Arrays.stream(vectors).forEach(args::add);
} else {
args.add(vectors[0]);
}
Expand Down Expand Up @@ -500,4 +501,50 @@ public Command<K, V, Map<V, Double>> vsimWithScore(K key, VSimArgs vSimArgs, V e
return createCommand(VSIM, new ValueDoubleMapOutput<>(codec), args);
}

// WITHSCORES WITHATTRIBS variants
public Command<K, V, Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, Double[] vectors) {
return vsimWithScoreWithAttribs(key, null, vectors);
}

public Command<K, V, Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, V element) {
return vsimWithScoreWithAttribs(key, null, element);
}

public Command<K, V, Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs vSimArgs, Double[] vectors) {
notNullKey(key);
notEmpty(vectors);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);

if (vectors.length > 1) {
args.add(CommandKeyword.VALUES);
args.add(vectors.length);
Arrays.stream(vectors).forEach(args::add);
} else {
args.add(vectors[0]);
}

args.add(WITHSCORES).add(WITHATTRIBS);

if (vSimArgs != null) {
vSimArgs.build(args);
}

return createCommand(VSIM, new VSimScoreAttribsMapOutput<>(codec), args);
}

public Command<K, V, Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs vSimArgs, V element) {
notNullKey(key);
notNullKey(element);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).add(ELE).addValue(element).add(WITHSCORES)
.add(WITHATTRIBS);

if (vSimArgs != null) {
vSimArgs.build(args);
}

return createCommand(VSIM, new VSimScoreAttribsMapOutput<>(codec), args);
}

}
21 changes: 21 additions & 0 deletions src/main/java/io/lettuce/core/VSimArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.lettuce.core.protocol.CommandKeyword;

import java.util.Optional;
import java.util.OptionalDouble;

/**
* Argument list builder for the Redis <a href="https://redis.io/docs/latest/commands/vsim/">VSIM</a> command. Static import the
Expand All @@ -36,6 +37,8 @@ public class VSimArgs implements CompositeArgument {

private Optional<Boolean> noThread = Optional.empty();

private Optional<Double> epsilon = Optional.empty();

/**
* Builder entry points for {@link VSimArgs}.
* <p>
Expand Down Expand Up @@ -266,10 +269,28 @@ public VSimArgs noThread(boolean noThread) {
return this;
}

/**
* Sets the EPSILON distance cutoff for approximate vector similarity matching; results must have similarity ≥ 1 − epsilon.
* In other words, this is a maximum distance threshold used to filter VSIM results.
*
* @param delta the similarity threshold delta value, must be within [0.0, 1.0] inclusive
* @return {@code this}
* @throws IllegalArgumentException if delta is outside the valid range [0.0, 1.0]
*/
public VSimArgs epsilon(double delta) {
if (delta < 0.0 || delta > 1.0) {
throw new IllegalArgumentException("EPSILON must be in range [0.0, 1.0], got: " + delta);
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should include information about the valid range format to match the method documentation. Consider changing to 'EPSILON must be in range [0.0, 1.0]' to match line 324 in the test file.

Suggested change
throw new IllegalArgumentException("EPSILON must be in range [0.0, 1.0], got: " + delta);
throw new IllegalArgumentException("EPSILON must be in range [0.0, 1.0]");

Copilot uses AI. Check for mistakes.
}
this.epsilon = Optional.of(delta);
return this;
}

@Override
public <K, V> void build(CommandArgs<K, V> args) {
count.ifPresent(Long -> args.add(CommandKeyword.COUNT).add(Long));

epsilon.ifPresent(d -> args.add(CommandKeyword.EPSILON).add(d));

explorationFactor.ifPresent(Long -> args.add(CommandKeyword.EF).add(Long));

filter.ifPresent(s -> args.add(CommandKeyword.FILTER).add(s));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.json.JsonValue;
import io.lettuce.core.vector.RawVector;
import io.lettuce.core.vector.VSimScoreAttribs;
import io.lettuce.core.vector.VectorMetadata;

/**
Expand Down Expand Up @@ -487,4 +488,78 @@ public interface RedisVectorSetAsyncCommands<K, V> {
@Experimental
RedisFuture<Map<V, Double>> vsimWithScore(K key, VSimArgs args, V element);

/**
* Finds the most similar vectors to the given query vector in the vector set stored at {@code key} and returns them with
* their similarity scores and attributes.
* <p>
* The similarity scores represent the distance between the query vector and the result vectors. Attributes are returned as
* a server-provided string.
* <p>
* Time complexity: O(log(N)) where N is the number of elements in the vector set
*
* @param key the key of the vector set
* @param vectors the query vector values as floating point numbers
* @return a map of elements to their (score, attributes), or an empty map if the key does not exist
* @since 7.0
* @see <a href="https://redis.io/docs/latest/commands/vsim/">Redis Documentation: VSIM</a>
*/
@Experimental
RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, Double... vectors);

/**
* Finds the most similar vectors to the given element's vector in the vector set stored at {@code key} and returns them
* with their similarity scores and attributes.
* <p>
* The similarity scores represent the distance between the specified element's vector and the result vectors. Attributes
* are returned as a server-provided string.
* <p>
* Time complexity: O(log(N)) where N is the number of elements in the vector set
*
* @param key the key of the vector set
* @param element the name of the element whose vector will be used as the query
* @return a map of elements to their (score, attributes), or an empty map if the key or element does not exist
* @since 7.0
* @see <a href="https://redis.io/docs/latest/commands/vsim/">Redis Documentation: VSIM</a>
*/
@Experimental
RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, V element);

/**
* Finds the most similar vectors to the given query vector in the vector set stored at {@code key} with additional options
* and returns them with their similarity scores and attributes.
* <p>
* The {@link VSimArgs} allows configuring various options such as the number of results ({@code COUNT}), epsilon cutoff
* ({@code EPSILON}), exploration factor ({@code EF}), and filtering.
* <p>
* Time complexity: O(log(N)) where N is the number of elements in the vector set
*
* @param key the key of the vector set
* @param args the additional arguments for the VSIM command
* @param vectors the query vector values as floating point numbers
* @return a map of elements to their (score, attributes), or an empty map if the key does not exist
* @since 7.0
* @see <a href="https://redis.io/docs/latest/commands/vsim/">Redis Documentation: VSIM</a>
*/
@Experimental
RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs args, Double... vectors);

/**
* Finds the most similar vectors to the given element's vector in the vector set stored at {@code key} with additional
* options and returns them with their similarity scores and attributes.
* <p>
* This method combines using an existing element's vector as the query with the ability to specify additional options via
* {@link VSimArgs}. Attributes are returned as a server-provided string.
* <p>
* Time complexity: O(log(N)) where N is the number of elements in the vector set
*
* @param key the key of the vector set
* @param element the name of the element whose vector will be used as the query
* @param args the additional arguments for the VSIM command
* @return a map of elements to their (score, attributes), or an empty map if the key or element does not exist
* @since 7.0
* @see <a href="https://redis.io/docs/latest/commands/vsim/">Redis Documentation: VSIM</a>
*/
@Experimental
RedisFuture<Map<V, VSimScoreAttribs>> vsimWithScoreWithAttribs(K key, VSimArgs args, V element);

}
Loading