FTHybridVectorParams.java

package redis.clients.jedis.search.hybrid;

import redis.clients.jedis.CommandArguments;
import redis.clients.jedis.annots.Experimental;
import redis.clients.jedis.params.IParams;
import redis.clients.jedis.util.JedisAsserts;

import java.util.ArrayList;
import java.util.List;

import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;

/**
 * Arguments for the VSIM (Vector Similarity) clause in FT.HYBRID command. Configures vector search
 * with KNN or RANGE methods.
 */
@Experimental
public class FTHybridVectorParams implements IParams {

  private String field;
  private String vector;
  private VectorMethod method;
  private final List<String> filters = new ArrayList<>();
  private String scoreAlias;

  private FTHybridVectorParams() {
  }

  /**
   * @return a new {@link Builder} for {@link FTHybridVectorParams}.
   */
  public static Builder builder() {
    return new Builder();
  }

  /**
   * Builder for {@link FTHybridVectorParams}.
   */
  public static class Builder {
    private final FTHybridVectorParams instance = new FTHybridVectorParams();

    /**
     * Build the {@link FTHybridVectorParams} instance.
     * @return the configured arguments
     */
    public FTHybridVectorParams build() {
      JedisAsserts.notNull(instance.field, "Field is required for VSIM clause");
      JedisAsserts.notNull(instance.vector, "Vector is required for VSIM clause");
      JedisAsserts.notNull(instance.method, "Method (KNN or RANGE) is required for VSIM clause");

      return instance;
    }

    /**
     * Set the vector field name.
     * @param field the field name (e.g., "@embedding")
     * @return this builder
     */
    public Builder field(String field) {
      instance.field = field;
      return this;
    }

    /**
     * Set the param name to reference the query vector BLOB.
     * @param vector the vector param name
     * @return this builder
     */
    public Builder vector(String vector) {
      instance.vector = vector;
      return this;
    }

    /**
     * Set the vector search method (KNN or RANGE).
     * @param method the vector search method
     * @return this builder
     */
    public Builder method(VectorMethod method) {
      instance.method = method;
      return this;
    }

    /**
     * Add a FILTER expression for pre-filtering documents before vector scoring. Can be called
     * multiple times to add multiple filters.
     * @param filter the filter expression
     * @return this builder
     */
    public Builder filter(String filter) {
      JedisAsserts.notNull(filter, "Filter expression must not be null");

      instance.filters.add(filter);
      return this;
    }

    /**
     * Set an alias for the vector distance score in the results.
     * @param scoreAlias the score alias name
     * @return this builder
     */
    public Builder scoreAlias(String scoreAlias) {
      instance.scoreAlias = scoreAlias;
      return this;
    }
  }

  @Override
  public void addParams(CommandArguments args) {
    args.add(VSIM);
    args.add(field);
    if (vector.startsWith("$")) {
      args.add(vector);
    } else {
      args.add(String.format("$%s", vector));
    }

    method.addParams(args);

    // FILTER inside VSIM - can have multiple filters
    for (String filter : filters) {
      args.add(FILTER);
      args.add(filter);
    }

    if (scoreAlias != null) {
      args.add(YIELD_SCORE_AS);
      args.add(scoreAlias);
    }
  }

  /**
   * Base interface for vector search methods.
   */
  public interface VectorMethod extends IParams {
  }

  /**
   * KNN (K-Nearest Neighbors) vector search method.
   */
  public static class Knn implements VectorMethod {
    private final int k;
    private Integer efRuntime;

    private Knn(int k) {
      this.k = k;
    }

    /**
     * Create a KNN method with the specified K value.
     * @param k the number of nearest neighbors to return
     * @return a new Knn instance
     */
    public static Knn of(int k) {
      return new Knn(k);
    }

    /**
     * Set the EF_RUNTIME parameter for HNSW algorithm.
     * @param efRuntime the EF_RUNTIME value
     * @return this Knn instance
     */
    public Knn efRuntime(int efRuntime) {
      this.efRuntime = efRuntime;
      return this;
    }

    @Override
    public void addParams(CommandArguments args) {
      args.add(KNN);
      int paramCount = efRuntime != null ? 4 : 2;
      args.add(paramCount);
      args.add(K);
      args.add(k);
      if (efRuntime != null) {
        args.add(EF_RUNTIME);
        args.add(efRuntime);
      }
    }
  }

  /**
   * RANGE vector search method.
   */
  public static class Range implements VectorMethod {
    private final double radius;
    private Double epsilon;

    private Range(double radius) {
      this.radius = radius;
    }

    /**
     * Create a RANGE method with the specified radius.
     * @param radius the search radius
     * @return a new Range instance
     */
    public static Range of(double radius) {
      return new Range(radius);
    }

    /**
     * Set the epsilon parameter for range search.
     * @param epsilon the epsilon value
     * @return this Range instance
     */
    public Range epsilon(double epsilon) {
      this.epsilon = epsilon;
      return this;
    }

    @Override
    public void addParams(CommandArguments args) {
      args.add(RANGE);
      int paramCount = epsilon != null ? 4 : 2;
      args.add(paramCount);
      args.add(RADIUS);
      args.add(radius);
      if (epsilon != null) {
        args.add(EPSILON);
        args.add(epsilon);
      }
    }
  }
}