|
@@ -38,6 +38,7 @@ import org.apache.lucene.util.BytesRef;
|
|
|
import org.apache.lucene.util.VectorUtil;
|
|
|
import org.elasticsearch.common.ParsingException;
|
|
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
|
|
+import org.elasticsearch.features.NodeFeature;
|
|
|
import org.elasticsearch.index.IndexVersion;
|
|
|
import org.elasticsearch.index.IndexVersions;
|
|
|
import org.elasticsearch.index.codec.vectors.ES813FlatVectorFormat;
|
|
@@ -73,6 +74,7 @@ import org.elasticsearch.search.vectors.RescoreKnnVectorQuery;
|
|
|
import org.elasticsearch.search.vectors.VectorData;
|
|
|
import org.elasticsearch.search.vectors.VectorSimilarityQuery;
|
|
|
import org.elasticsearch.xcontent.ToXContent;
|
|
|
+import org.elasticsearch.xcontent.ToXContentObject;
|
|
|
import org.elasticsearch.xcontent.XContentBuilder;
|
|
|
import org.elasticsearch.xcontent.XContentParser;
|
|
|
import org.elasticsearch.xcontent.XContentParser.Token;
|
|
@@ -112,6 +114,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
public static final IndexVersion NORMALIZE_COSINE = IndexVersions.NORMALIZED_VECTOR_COSINE;
|
|
|
public static final IndexVersion DEFAULT_TO_INT8 = DEFAULT_DENSE_VECTOR_TO_INT8_HNSW;
|
|
|
public static final IndexVersion LITTLE_ENDIAN_FLOAT_STORED_INDEX_VERSION = IndexVersions.V_8_9_0;
|
|
|
+ public static final IndexVersion ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS = IndexVersions.ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS;
|
|
|
+
|
|
|
+ public static final NodeFeature RESCORE_VECTOR_QUANTIZED_VECTOR_MAPPING = new NodeFeature("mapper.dense_vector.rescore_vector");
|
|
|
|
|
|
public static final String CONTENT_TYPE = "dense_vector";
|
|
|
public static final short MAX_DIMS_COUNT = 4096; // maximum allowed number of dimensions
|
|
@@ -210,10 +215,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
? new Int8HnswIndexOptions(
|
|
|
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
|
|
|
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
|
|
|
+ null,
|
|
|
null
|
|
|
)
|
|
|
: null,
|
|
|
- (n, c, o) -> o == null ? null : parseIndexOptions(n, o),
|
|
|
+ (n, c, o) -> o == null ? null : parseIndexOptions(n, o, indexVersionCreated),
|
|
|
m -> toType(m).indexOptions,
|
|
|
(b, n, v) -> {
|
|
|
if (v != null) {
|
|
@@ -1258,10 +1264,19 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ abstract static class QuantizedIndexOptions extends IndexOptions {
|
|
|
+ final RescoreVector rescoreVector;
|
|
|
+
|
|
|
+ QuantizedIndexOptions(VectorIndexType type, RescoreVector rescoreVector) {
|
|
|
+ super(type);
|
|
|
+ this.rescoreVector = rescoreVector;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public enum VectorIndexType {
|
|
|
HNSW("hnsw", false) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
Object mNode = indexOptionsMap.remove("m");
|
|
|
Object efConstructionNode = indexOptionsMap.remove("ef_construction");
|
|
|
if (mNode == null) {
|
|
@@ -1288,7 +1303,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
},
|
|
|
INT8_HNSW("int8_hnsw", true) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
Object mNode = indexOptionsMap.remove("m");
|
|
|
Object efConstructionNode = indexOptionsMap.remove("ef_construction");
|
|
|
Object confidenceIntervalNode = indexOptionsMap.remove("confidence_interval");
|
|
@@ -1304,8 +1319,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
if (confidenceIntervalNode != null) {
|
|
|
confidenceInterval = (float) XContentMapValues.nodeDoubleValue(confidenceIntervalNode);
|
|
|
}
|
|
|
+ RescoreVector rescoreVector = null;
|
|
|
+ if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
|
|
|
+ rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
|
|
|
+ }
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
- return new Int8HnswIndexOptions(m, efConstruction, confidenceInterval);
|
|
|
+ return new Int8HnswIndexOptions(m, efConstruction, confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1319,7 +1338,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
},
|
|
|
INT4_HNSW("int4_hnsw", true) {
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
Object mNode = indexOptionsMap.remove("m");
|
|
|
Object efConstructionNode = indexOptionsMap.remove("ef_construction");
|
|
|
Object confidenceIntervalNode = indexOptionsMap.remove("confidence_interval");
|
|
@@ -1335,8 +1354,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
if (confidenceIntervalNode != null) {
|
|
|
confidenceInterval = (float) XContentMapValues.nodeDoubleValue(confidenceIntervalNode);
|
|
|
}
|
|
|
+ RescoreVector rescoreVector = null;
|
|
|
+ if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
|
|
|
+ rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
|
|
|
+ }
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
- return new Int4HnswIndexOptions(m, efConstruction, confidenceInterval);
|
|
|
+ return new Int4HnswIndexOptions(m, efConstruction, confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1351,7 +1374,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
},
|
|
|
FLAT("flat", false) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
return new FlatIndexOptions();
|
|
|
}
|
|
@@ -1368,14 +1391,18 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
},
|
|
|
INT8_FLAT("int8_flat", true) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
Object confidenceIntervalNode = indexOptionsMap.remove("confidence_interval");
|
|
|
Float confidenceInterval = null;
|
|
|
if (confidenceIntervalNode != null) {
|
|
|
confidenceInterval = (float) XContentMapValues.nodeDoubleValue(confidenceIntervalNode);
|
|
|
}
|
|
|
+ RescoreVector rescoreVector = null;
|
|
|
+ if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
|
|
|
+ rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
|
|
|
+ }
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
- return new Int8FlatIndexOptions(confidenceInterval);
|
|
|
+ return new Int8FlatIndexOptions(confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1390,14 +1417,18 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
},
|
|
|
INT4_FLAT("int4_flat", true) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
Object confidenceIntervalNode = indexOptionsMap.remove("confidence_interval");
|
|
|
Float confidenceInterval = null;
|
|
|
if (confidenceIntervalNode != null) {
|
|
|
confidenceInterval = (float) XContentMapValues.nodeDoubleValue(confidenceIntervalNode);
|
|
|
}
|
|
|
+ RescoreVector rescoreVector = null;
|
|
|
+ if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
|
|
|
+ rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
|
|
|
+ }
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
- return new Int4FlatIndexOptions(confidenceInterval);
|
|
|
+ return new Int4FlatIndexOptions(confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1412,7 +1443,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
},
|
|
|
BBQ_HNSW("bbq_hnsw", true) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
Object mNode = indexOptionsMap.remove("m");
|
|
|
Object efConstructionNode = indexOptionsMap.remove("ef_construction");
|
|
|
if (mNode == null) {
|
|
@@ -1423,8 +1454,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
int m = XContentMapValues.nodeIntegerValue(mNode);
|
|
|
int efConstruction = XContentMapValues.nodeIntegerValue(efConstructionNode);
|
|
|
+ RescoreVector rescoreVector = null;
|
|
|
+ if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
|
|
|
+ rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
|
|
|
+ }
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
- return new BBQHnswIndexOptions(m, efConstruction);
|
|
|
+ return new BBQHnswIndexOptions(m, efConstruction, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1439,9 +1474,13 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
},
|
|
|
BBQ_FLAT("bbq_flat", true) {
|
|
|
@Override
|
|
|
- public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap) {
|
|
|
+ public IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion) {
|
|
|
+ RescoreVector rescoreVector = null;
|
|
|
+ if (indexVersion.onOrAfter(ADD_RESCORE_PARAMS_TO_QUANTIZED_VECTORS)) {
|
|
|
+ rescoreVector = RescoreVector.fromIndexOptions(indexOptionsMap);
|
|
|
+ }
|
|
|
MappingParser.checkNoRemainingFields(fieldName, indexOptionsMap);
|
|
|
- return new BBQFlatIndexOptions();
|
|
|
+ return new BBQFlatIndexOptions(rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1467,7 +1506,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
this.quantized = quantized;
|
|
|
}
|
|
|
|
|
|
- abstract IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap);
|
|
|
+ abstract IndexOptions parseIndexOptions(String fieldName, Map<String, ?> indexOptionsMap, IndexVersion indexVersion);
|
|
|
|
|
|
public abstract boolean supportsElementType(ElementType elementType);
|
|
|
|
|
@@ -1483,11 +1522,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static class Int8FlatIndexOptions extends IndexOptions {
|
|
|
+ static class Int8FlatIndexOptions extends QuantizedIndexOptions {
|
|
|
private final Float confidenceInterval;
|
|
|
|
|
|
- Int8FlatIndexOptions(Float confidenceInterval) {
|
|
|
- super(VectorIndexType.INT8_FLAT);
|
|
|
+ Int8FlatIndexOptions(Float confidenceInterval, RescoreVector rescoreVector) {
|
|
|
+ super(VectorIndexType.INT8_FLAT, rescoreVector);
|
|
|
this.confidenceInterval = confidenceInterval;
|
|
|
}
|
|
|
|
|
@@ -1498,6 +1537,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
if (confidenceInterval != null) {
|
|
|
builder.field("confidence_interval", confidenceInterval);
|
|
|
}
|
|
|
+ if (rescoreVector != null) {
|
|
|
+ rescoreVector.toXContent(builder, params);
|
|
|
+ }
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -1511,12 +1553,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
@Override
|
|
|
boolean doEquals(IndexOptions o) {
|
|
|
Int8FlatIndexOptions that = (Int8FlatIndexOptions) o;
|
|
|
- return Objects.equals(confidenceInterval, that.confidenceInterval);
|
|
|
+ return Objects.equals(confidenceInterval, that.confidenceInterval) && Objects.equals(rescoreVector, that.rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
int doHashCode() {
|
|
|
- return Objects.hash(confidenceInterval);
|
|
|
+ return Objects.hash(confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1567,13 +1609,13 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static class Int4HnswIndexOptions extends IndexOptions {
|
|
|
+ static class Int4HnswIndexOptions extends QuantizedIndexOptions {
|
|
|
private final int m;
|
|
|
private final int efConstruction;
|
|
|
private final float confidenceInterval;
|
|
|
|
|
|
- Int4HnswIndexOptions(int m, int efConstruction, Float confidenceInterval) {
|
|
|
- super(VectorIndexType.INT4_HNSW);
|
|
|
+ Int4HnswIndexOptions(int m, int efConstruction, Float confidenceInterval, RescoreVector rescoreVector) {
|
|
|
+ super(VectorIndexType.INT4_HNSW, rescoreVector);
|
|
|
this.m = m;
|
|
|
this.efConstruction = efConstruction;
|
|
|
// The default confidence interval for int4 is dynamic quantiles, this provides the best relevancy and is
|
|
@@ -1594,6 +1636,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
builder.field("m", m);
|
|
|
builder.field("ef_construction", efConstruction);
|
|
|
builder.field("confidence_interval", confidenceInterval);
|
|
|
+ if (rescoreVector != null) {
|
|
|
+ rescoreVector.toXContent(builder, params);
|
|
|
+ }
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -1601,12 +1646,15 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
@Override
|
|
|
public boolean doEquals(IndexOptions o) {
|
|
|
Int4HnswIndexOptions that = (Int4HnswIndexOptions) o;
|
|
|
- return m == that.m && efConstruction == that.efConstruction && Objects.equals(confidenceInterval, that.confidenceInterval);
|
|
|
+ return m == that.m
|
|
|
+ && efConstruction == that.efConstruction
|
|
|
+ && Objects.equals(confidenceInterval, that.confidenceInterval)
|
|
|
+ && Objects.equals(rescoreVector, that.rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public int doHashCode() {
|
|
|
- return Objects.hash(m, efConstruction, confidenceInterval);
|
|
|
+ return Objects.hash(m, efConstruction, confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1619,6 +1667,8 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
+ efConstruction
|
|
|
+ ", confidence_interval="
|
|
|
+ confidenceInterval
|
|
|
+ + ", rescore_vector="
|
|
|
+ + (rescoreVector == null ? "none" : rescoreVector)
|
|
|
+ "}";
|
|
|
}
|
|
|
|
|
@@ -1635,11 +1685,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static class Int4FlatIndexOptions extends IndexOptions {
|
|
|
+ static class Int4FlatIndexOptions extends QuantizedIndexOptions {
|
|
|
private final float confidenceInterval;
|
|
|
|
|
|
- Int4FlatIndexOptions(Float confidenceInterval) {
|
|
|
- super(VectorIndexType.INT4_FLAT);
|
|
|
+ Int4FlatIndexOptions(Float confidenceInterval, RescoreVector rescoreVector) {
|
|
|
+ super(VectorIndexType.INT4_FLAT, rescoreVector);
|
|
|
// The default confidence interval for int4 is dynamic quantiles, this provides the best relevancy and is
|
|
|
// effectively required for int4 to behave well across a wide range of data.
|
|
|
this.confidenceInterval = confidenceInterval == null ? 0f : confidenceInterval;
|
|
@@ -1656,6 +1706,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
builder.startObject();
|
|
|
builder.field("type", type);
|
|
|
builder.field("confidence_interval", confidenceInterval);
|
|
|
+ if (rescoreVector != null) {
|
|
|
+ rescoreVector.toXContent(builder, params);
|
|
|
+ }
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -1665,17 +1718,17 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
if (this == o) return true;
|
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
|
Int4FlatIndexOptions that = (Int4FlatIndexOptions) o;
|
|
|
- return Objects.equals(confidenceInterval, that.confidenceInterval);
|
|
|
+ return Objects.equals(confidenceInterval, that.confidenceInterval) && Objects.equals(rescoreVector, that.rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public int doHashCode() {
|
|
|
- return Objects.hash(confidenceInterval);
|
|
|
+ return Objects.hash(confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
- return "{type=" + type + ", confidence_interval=" + confidenceInterval + "}";
|
|
|
+ return "{type=" + type + ", confidence_interval=" + confidenceInterval + ", rescore_vector=" + rescoreVector + "}";
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1689,13 +1742,13 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
|
|
|
}
|
|
|
|
|
|
- static class Int8HnswIndexOptions extends IndexOptions {
|
|
|
+ static class Int8HnswIndexOptions extends QuantizedIndexOptions {
|
|
|
private final int m;
|
|
|
private final int efConstruction;
|
|
|
private final Float confidenceInterval;
|
|
|
|
|
|
- Int8HnswIndexOptions(int m, int efConstruction, Float confidenceInterval) {
|
|
|
- super(VectorIndexType.INT8_HNSW);
|
|
|
+ Int8HnswIndexOptions(int m, int efConstruction, Float confidenceInterval, RescoreVector rescoreVector) {
|
|
|
+ super(VectorIndexType.INT8_HNSW, rescoreVector);
|
|
|
this.m = m;
|
|
|
this.efConstruction = efConstruction;
|
|
|
this.confidenceInterval = confidenceInterval;
|
|
@@ -1716,6 +1769,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
if (confidenceInterval != null) {
|
|
|
builder.field("confidence_interval", confidenceInterval);
|
|
|
}
|
|
|
+ if (rescoreVector != null) {
|
|
|
+ rescoreVector.toXContent(builder, params);
|
|
|
+ }
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -1725,12 +1781,15 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
if (this == o) return true;
|
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
|
Int8HnswIndexOptions that = (Int8HnswIndexOptions) o;
|
|
|
- return m == that.m && efConstruction == that.efConstruction && Objects.equals(confidenceInterval, that.confidenceInterval);
|
|
|
+ return m == that.m
|
|
|
+ && efConstruction == that.efConstruction
|
|
|
+ && Objects.equals(confidenceInterval, that.confidenceInterval)
|
|
|
+ && Objects.equals(rescoreVector, that.rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public int doHashCode() {
|
|
|
- return Objects.hash(m, efConstruction, confidenceInterval);
|
|
|
+ return Objects.hash(m, efConstruction, confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1743,6 +1802,8 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
+ efConstruction
|
|
|
+ ", confidence_interval="
|
|
|
+ confidenceInterval
|
|
|
+ + ", rescore_vector="
|
|
|
+ + (rescoreVector == null ? "none" : rescoreVector)
|
|
|
+ "}";
|
|
|
}
|
|
|
|
|
@@ -1824,12 +1885,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static class BBQHnswIndexOptions extends IndexOptions {
|
|
|
+ static class BBQHnswIndexOptions extends QuantizedIndexOptions {
|
|
|
private final int m;
|
|
|
private final int efConstruction;
|
|
|
|
|
|
- BBQHnswIndexOptions(int m, int efConstruction) {
|
|
|
- super(VectorIndexType.BBQ_HNSW);
|
|
|
+ BBQHnswIndexOptions(int m, int efConstruction, RescoreVector rescoreVector) {
|
|
|
+ super(VectorIndexType.BBQ_HNSW, rescoreVector);
|
|
|
this.m = m;
|
|
|
this.efConstruction = efConstruction;
|
|
|
}
|
|
@@ -1848,12 +1909,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
@Override
|
|
|
boolean doEquals(IndexOptions other) {
|
|
|
BBQHnswIndexOptions that = (BBQHnswIndexOptions) other;
|
|
|
- return m == that.m && efConstruction == that.efConstruction;
|
|
|
+ return m == that.m && efConstruction == that.efConstruction && Objects.equals(rescoreVector, that.rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
int doHashCode() {
|
|
|
- return Objects.hash(m, efConstruction);
|
|
|
+ return Objects.hash(m, efConstruction, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1862,6 +1923,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
builder.field("type", type);
|
|
|
builder.field("m", m);
|
|
|
builder.field("ef_construction", efConstruction);
|
|
|
+ if (rescoreVector != null) {
|
|
|
+ rescoreVector.toXContent(builder, params);
|
|
|
+ }
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -1875,11 +1939,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static class BBQFlatIndexOptions extends IndexOptions {
|
|
|
+ static class BBQFlatIndexOptions extends QuantizedIndexOptions {
|
|
|
private final int CLASS_NAME_HASH = this.getClass().getName().hashCode();
|
|
|
|
|
|
- BBQFlatIndexOptions() {
|
|
|
- super(VectorIndexType.BBQ_FLAT);
|
|
|
+ BBQFlatIndexOptions(RescoreVector rescoreVector) {
|
|
|
+ super(VectorIndexType.BBQ_FLAT, rescoreVector);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -1907,6 +1971,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
|
builder.startObject();
|
|
|
builder.field("type", type);
|
|
|
+ if (rescoreVector != null) {
|
|
|
+ rescoreVector.toXContent(builder, params);
|
|
|
+ }
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -1920,6 +1987,41 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ record RescoreVector(float oversample) implements ToXContentObject {
|
|
|
+ static final String NAME = "rescore_vector";
|
|
|
+ static final String OVERSAMPLE = "oversample";
|
|
|
+
|
|
|
+ static RescoreVector fromIndexOptions(Map<String, ?> indexOptionsMap) {
|
|
|
+ Object rescoreVectorNode = indexOptionsMap.remove(NAME);
|
|
|
+ if (rescoreVectorNode == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ Map<String, Object> mappedNode = XContentMapValues.nodeMapValue(rescoreVectorNode, NAME);
|
|
|
+ Object oversampleNode = mappedNode.get(OVERSAMPLE);
|
|
|
+ if (oversampleNode == null) {
|
|
|
+ throw new IllegalArgumentException("Invalid rescore_vector value. Missing required field " + OVERSAMPLE);
|
|
|
+ }
|
|
|
+ return new RescoreVector((float) XContentMapValues.nodeDoubleValue(oversampleNode));
|
|
|
+ }
|
|
|
+
|
|
|
+ RescoreVector {
|
|
|
+ if (oversample < 1) {
|
|
|
+ throw new IllegalArgumentException("oversample must be greater than 1");
|
|
|
+ }
|
|
|
+ if (oversample > 10) {
|
|
|
+ throw new IllegalArgumentException("oversample must be less than or equal to 10");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
|
+ builder.startObject(NAME);
|
|
|
+ builder.field(OVERSAMPLE, oversample);
|
|
|
+ builder.endObject();
|
|
|
+ return builder;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public static final TypeParser PARSER = new TypeParser(
|
|
|
(n, c) -> new Builder(n, c.indexVersionCreated()),
|
|
|
notInMultiFields(CONTENT_TYPE)
|
|
@@ -2135,7 +2237,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
float[] queryVector,
|
|
|
int k,
|
|
|
int numCands,
|
|
|
- Float oversample,
|
|
|
+ Float queryOversample,
|
|
|
Query filter,
|
|
|
Float similarityThreshold,
|
|
|
BitSetProducer parentFilter
|
|
@@ -2157,6 +2259,14 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
|
|
|
int adjustedK = k;
|
|
|
+ // By default utilize the quantized oversample is configured
|
|
|
+ // allow the user provided at query time overwrite
|
|
|
+ Float oversample = queryOversample;
|
|
|
+ if (oversample == null
|
|
|
+ && indexOptions instanceof QuantizedIndexOptions quantizedIndexOptions
|
|
|
+ && quantizedIndexOptions.rescoreVector != null) {
|
|
|
+ oversample = quantizedIndexOptions.rescoreVector.oversample;
|
|
|
+ }
|
|
|
boolean rescore = needsRescore(oversample);
|
|
|
if (rescore) {
|
|
|
// Will get k * oversample for rescoring, and get the top k
|
|
@@ -2352,7 +2462,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return new Builder(leafName(), indexCreatedVersion).init(this);
|
|
|
}
|
|
|
|
|
|
- private static IndexOptions parseIndexOptions(String fieldName, Object propNode) {
|
|
|
+ private static IndexOptions parseIndexOptions(String fieldName, Object propNode, IndexVersion indexVersion) {
|
|
|
@SuppressWarnings("unchecked")
|
|
|
Map<String, ?> indexOptionsMap = (Map<String, ?>) propNode;
|
|
|
Object typeNode = indexOptionsMap.remove("type");
|
|
@@ -2365,7 +2475,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
throw new MapperParsingException("Unknown vector index options type [" + type + "] for field [" + fieldName + "]");
|
|
|
}
|
|
|
VectorIndexType parsedType = vectorIndexType.get();
|
|
|
- return parsedType.parseIndexOptions(fieldName, indexOptionsMap);
|
|
|
+ return parsedType.parseIndexOptions(fieldName, indexOptionsMap, indexVersion);
|
|
|
}
|
|
|
|
|
|
/**
|