Skip to content

Commit 33b399b

Browse files
authored
Support custom weights params in RRF (#1322)
* Support Weights params in RRF Signed-off-by: Varun Jain <[email protected]>
1 parent 4d12707 commit 33b399b

File tree

10 files changed

+173
-27
lines changed

10 files changed

+173
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1414
### Enhancements
1515
- [Performance Improvement] Add custom bulk scorer for hybrid query (2-3x faster) ([#1289](https://github.com/opensearch-project/neural-search/pull/1289))
1616
- [Stats] Add stats for text chunking processor algorithms ([#1308](https://github.com/opensearch-project/neural-search/pull/1308))
17+
- Support custom weights in RRF normalization processor ([#1322](https://github.com/opensearch-project/neural-search/pull/1322))
1718

1819
### Bug Fixes
1920
- Fix score value as null for single shard when sorting is not done on score field ([#1277](https://github.com/opensearch-project/neural-search/pull/1277))
2021
- Return bad request for stats API calls with invalid stat names instead of ignoring them ([#1291](https://github.com/opensearch-project/neural-search/pull/1291))
2122

22-
2323
### Infrastructure
2424

2525
### Documentation

src/main/java/org/opensearch/neuralsearch/processor/combination/ArithmeticMeanScoreCombinationTechnique.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class ArithmeticMeanScoreCombinationTechnique implements ScoreCombination
2323
public static final String TECHNIQUE_NAME = "arithmetic_mean";
2424
private static final Set<String> SUPPORTED_PARAMS = Set.of(PARAM_NAME_WEIGHTS);
2525
private static final Float ZERO_SCORE = 0.0f;
26+
@ToString.Include
2627
private final List<Float> weights;
2728
private final ScoreCombinationUtil scoreCombinationUtil;
2829

src/main/java/org/opensearch/neuralsearch/processor/combination/RRFScoreCombinationTechnique.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
import org.opensearch.neuralsearch.processor.explain.ExplainableTechnique;
1010

1111
import java.util.List;
12+
import java.util.Map;
1213
import java.util.Objects;
14+
import java.util.Set;
1315

16+
import static org.opensearch.neuralsearch.processor.combination.ScoreCombinationUtil.PARAM_NAME_WEIGHTS;
1417
import static org.opensearch.neuralsearch.processor.explain.ExplanationUtils.describeCombinationTechnique;
1518

1619
/**
@@ -21,19 +24,40 @@
2124
public class RRFScoreCombinationTechnique implements ScoreCombinationTechnique, ExplainableTechnique {
2225
@ToString.Include
2326
public static final String TECHNIQUE_NAME = "rrf";
27+
private static final Set<String> SUPPORTED_PARAMS = Set.of(PARAM_NAME_WEIGHTS);
28+
@ToString.Include
29+
private final List<Float> weights;
30+
private static final Float ZERO_SCORE = 0.0f;
31+
private final ScoreCombinationUtil scoreCombinationUtil;
2432

25-
// Not currently using weights for RRF, no need to modify or verify these params
26-
public RRFScoreCombinationTechnique() {}
33+
public RRFScoreCombinationTechnique(final Map<String, Object> params, final ScoreCombinationUtil combinationUtil) {
34+
scoreCombinationUtil = combinationUtil;
35+
scoreCombinationUtil.validateParams(params, SUPPORTED_PARAMS);
36+
weights = scoreCombinationUtil.getWeights(params);
37+
}
2738

2839
@Override
2940
public float combine(final float[] scores) {
3041
if (Objects.isNull(scores)) {
3142
throw new IllegalArgumentException("scores array cannot be null");
3243
}
44+
scoreCombinationUtil.validateIfWeightsMatchScores(scores, weights);
3345
float sumScores = 0.0f;
34-
for (float score : scores) {
35-
sumScores += score;
46+
float sumOfWeights = 0;
47+
48+
for (int indexOfSubQuery = 0; indexOfSubQuery < scores.length; indexOfSubQuery++) {
49+
float score = scores[indexOfSubQuery];
50+
if (score >= 0.0) {
51+
float weight = scoreCombinationUtil.getWeightForSubQuery(weights, indexOfSubQuery);
52+
score = score * weight;
53+
sumScores += score;
54+
sumOfWeights += weight;
55+
}
3656
}
57+
if (sumOfWeights == 0.0f) {
58+
return ZERO_SCORE;
59+
}
60+
3761
return sumScores;
3862
}
3963

src/main/java/org/opensearch/neuralsearch/processor/combination/ScoreCombinationFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class ScoreCombinationFactory {
2727
GeometricMeanScoreCombinationTechnique.TECHNIQUE_NAME,
2828
params -> new GeometricMeanScoreCombinationTechnique(params, scoreCombinationUtil),
2929
RRFScoreCombinationTechnique.TECHNIQUE_NAME,
30-
params -> new RRFScoreCombinationTechnique()
30+
params -> new RRFScoreCombinationTechnique(params, scoreCombinationUtil)
3131
);
3232

3333
/**

src/main/java/org/opensearch/neuralsearch/processor/factory/RRFProcessorFactory.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,16 @@ public SearchPhaseResultsProcessor create(
6363
TECHNIQUE,
6464
RRFScoreCombinationTechnique.TECHNIQUE_NAME
6565
);
66-
// check for optional combination params
66+
67+
String rankConstantParam = RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT;
68+
if (combinationClause.containsKey(rankConstantParam)) {
69+
normalizationTechnique = scoreNormalizationFactory.createNormalization(
70+
RRFNormalizationTechnique.TECHNIQUE_NAME,
71+
Map.of(rankConstantParam, combinationClause.get(rankConstantParam))
72+
);
73+
}
6774
Map<String, Object> params = readOptionalMap(RRFProcessor.TYPE, tag, combinationClause, PARAMETERS);
68-
normalizationTechnique = scoreNormalizationFactory.createNormalization(RRFNormalizationTechnique.TECHNIQUE_NAME, params);
69-
scoreCombinationTechnique = scoreCombinationFactory.createCombination(combinationTechnique);
75+
scoreCombinationTechnique = scoreCombinationFactory.createCombination(combinationTechnique, params);
7076
}
7177
log.info(
7278
"Creating search phase results processor of type [{}] with normalization [{}] and combination [{}]",

src/test/java/org/opensearch/neuralsearch/processor/RRFProcessorIT.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.opensearch.neuralsearch.query.HybridQueryBuilder;
1212

1313
import java.util.ArrayList;
14+
import java.util.Arrays;
1415
import java.util.Collections;
1516
import java.util.HashMap;
1617
import java.util.Map;
@@ -43,6 +44,22 @@ public void testRRF_whenValidInput_thenSucceed() {
4344
assertEquals(0.016393442, (Double) hitsList.getFirst().get("_score"), DELTA_FOR_SCORE_ASSERTION);
4445
assertEquals(0.016129032, (Double) hitsList.get(1).get("_score"), DELTA_FOR_SCORE_ASSERTION);
4546
assertEquals(0.015873017, (Double) hitsList.getLast().get("_score"), DELTA_FOR_SCORE_ASSERTION);
47+
48+
createRRFSearchPipeline(RRF_SEARCH_PIPELINE, Arrays.asList(0.7, 0.3), false);
49+
Map<String, Object> weightedResults = search(
50+
RRF_INDEX_NAME,
51+
hybridQueryBuilder,
52+
null,
53+
5,
54+
Map.of("search_pipeline", RRF_SEARCH_PIPELINE)
55+
);
56+
Map<String, Object> weightedHits = (Map<String, Object>) weightedResults.get("hits");
57+
ArrayList<HashMap<String, Object>> weightedHitsList = (ArrayList<HashMap<String, Object>>) weightedHits.get("hits");
58+
assertEquals(3, weightedHitsList.size());
59+
assertEquals(0.011475409, (Double) weightedHitsList.getFirst().get("_score"), DELTA_FOR_SCORE_ASSERTION);
60+
assertEquals(0.011475409, (Double) weightedHitsList.get(1).get("_score"), DELTA_FOR_SCORE_ASSERTION);
61+
assertEquals(0.011290322, (Double) weightedHitsList.getLast().get("_score"), DELTA_FOR_SCORE_ASSERTION);
62+
4663
}
4764

4865
private HybridQueryBuilder getHybridQueryBuilder() {

src/test/java/org/opensearch/neuralsearch/processor/combination/RRFScoreCombinationTechniqueTests.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,48 @@
55
package org.opensearch.neuralsearch.processor.combination;
66

77
import java.util.List;
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
10+
import java.util.stream.IntStream;
11+
12+
import static org.opensearch.neuralsearch.processor.combination.ScoreCombinationUtil.PARAM_NAME_WEIGHTS;
813

914
public class RRFScoreCombinationTechniqueTests extends BaseScoreCombinationTechniqueTests {
1015

1116
private static final int RANK_CONSTANT = 60;
1217
private RRFScoreCombinationTechnique combinationTechnique;
18+
private ScoreCombinationUtil scoreCombinationUtil = new ScoreCombinationUtil();
1319

1420
public RRFScoreCombinationTechniqueTests() {
1521
this.expectedScoreFunction = (scores, weights) -> RRF(scores, weights);
16-
combinationTechnique = new RRFScoreCombinationTechnique();
22+
combinationTechnique = new RRFScoreCombinationTechnique(Map.of(), scoreCombinationUtil);
1723
}
1824

1925
public void testLogic_whenAllScoresPresentAndNoWeights_thenCorrectScores() {
20-
ScoreCombinationTechnique technique = new RRFScoreCombinationTechnique();
26+
ScoreCombinationTechnique technique = new RRFScoreCombinationTechnique(Map.of(), scoreCombinationUtil);
2127
testLogic_whenAllScoresPresentAndNoWeights_thenCorrectScores(technique);
2228
}
2329

2430
public void testLogic_whenNotAllScoresPresentAndNoWeights_thenCorrectScores() {
25-
ScoreCombinationTechnique technique = new RRFScoreCombinationTechnique();
31+
ScoreCombinationTechnique technique = new RRFScoreCombinationTechnique(Map.of(), scoreCombinationUtil);
2632
testLogic_whenNotAllScoresPresentAndNoWeights_thenCorrectScores(technique);
2733
}
2834

35+
public void testLogic_whenNotAllScoresAndWeightsPresent_thenCorrectScores() {
36+
List<Float> scores = List.of(1.0f, 0.0f, 0.6f);
37+
List<Double> weights = List.of(0.45, 0.15, 0.4);
38+
ScoreCombinationTechnique technique = new RRFScoreCombinationTechnique(Map.of(PARAM_NAME_WEIGHTS, weights), scoreCombinationUtil);
39+
// 1 x 0.45 + 0 x 0.15 + 0.6 x 0.4 = 0.69
40+
float expectedScore = 0.69f;
41+
testLogic_whenNotAllScoresAndWeightsPresent_thenCorrectScores(technique, scores, expectedScore);
42+
}
43+
44+
public void testRandomValues_whenNotAllScoresAndWeightsPresent_thenCorrectScores() {
45+
List<Double> weights = IntStream.range(0, RANDOM_SCORES_SIZE).mapToObj(i -> 1.0 / RANDOM_SCORES_SIZE).collect(Collectors.toList());
46+
ScoreCombinationTechnique technique = new RRFScoreCombinationTechnique(Map.of(PARAM_NAME_WEIGHTS, weights), scoreCombinationUtil);
47+
testRandomValues_whenNotAllScoresAndWeightsPresent_thenCorrectScores(technique, weights);
48+
}
49+
2950
public void testDescribe() {
3051
String description = combinationTechnique.describe();
3152
assertEquals("rrf", description);
@@ -63,9 +84,21 @@ public void testCombineWithNullInput() {
6384

6485
private float RRF(List<Float> scores, List<Double> weights) {
6586
float sumScores = 0.0f;
66-
for (float score : scores) {
67-
sumScores += score;
87+
float sumOfWeights = 0;
88+
89+
for (int indexOfSubQuery = 0; indexOfSubQuery < scores.size(); indexOfSubQuery++) {
90+
float score = scores.get(indexOfSubQuery);
91+
float weight = weights.get(indexOfSubQuery).floatValue();
92+
if (score >= 0.0) {
93+
score = score * weight;
94+
sumScores += score;
95+
sumOfWeights += weight;
96+
}
6897
}
98+
if (sumOfWeights == 0.0f) {
99+
return 0.0f;
100+
}
101+
69102
return sumScores;
70103
}
71104
}

src/test/java/org/opensearch/neuralsearch/processor/factory/RRFProcessorFactoryTests.java

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
*/
55
package org.opensearch.neuralsearch.processor.factory;
66

7+
import com.carrotsearch.randomizedtesting.RandomizedTest;
78
import lombok.SneakyThrows;
89
import org.opensearch.neuralsearch.processor.NormalizationProcessorWorkflow;
910
import org.opensearch.neuralsearch.processor.RRFProcessor;
1011
import org.opensearch.neuralsearch.processor.combination.ArithmeticMeanScoreCombinationTechnique;
1112
import org.opensearch.neuralsearch.processor.combination.ScoreCombinationFactory;
1213
import org.opensearch.neuralsearch.processor.combination.ScoreCombiner;
14+
import org.opensearch.neuralsearch.processor.normalization.RRFNormalizationTechnique;
1315
import org.opensearch.neuralsearch.processor.normalization.ScoreNormalizationFactory;
1416
import org.opensearch.neuralsearch.processor.normalization.ScoreNormalizer;
1517
import org.opensearch.search.pipeline.Processor;
1618
import org.opensearch.search.pipeline.SearchPhaseResultsProcessor;
1719
import org.opensearch.test.OpenSearchTestCase;
1820

21+
import java.util.Arrays;
1922
import java.util.HashMap;
2023
import java.util.Map;
2124

@@ -63,8 +66,22 @@ public void testCombinationParams_whenValidValues_thenSuccessful() {
6366
String tag = "tag";
6467
String description = "description";
6568
boolean ignoreFailure = false;
69+
double weight1 = RandomizedTest.randomDouble();
70+
double weight2 = 1.0f - weight1;
6671
Map<String, Object> config = new HashMap<>();
67-
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", PARAMETERS, new HashMap<>(Map.of("rank_constant", 100)))));
72+
config.put(
73+
COMBINATION_CLAUSE,
74+
new HashMap<>(
75+
Map.of(
76+
TECHNIQUE,
77+
"rrf",
78+
PARAMETERS,
79+
new HashMap<>(Map.of("weights", Arrays.asList(weight1, weight2))),
80+
RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT,
81+
100
82+
)
83+
)
84+
);
6885
Processor.PipelineContext pipelineContext = mock(Processor.PipelineContext.class);
6986
SearchPhaseResultsProcessor searchPhaseResultsProcessor = rrfProcessorFactory.create(
7087
processorFactories,
@@ -90,7 +107,7 @@ public void testInvalidCombinationParams_whenRankIsNegative_thenFail() {
90107
boolean ignoreFailure = false;
91108

92109
Map<String, Object> config = new HashMap<>();
93-
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", PARAMETERS, new HashMap<>(Map.of("rank_constant", -1)))));
110+
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT, -1)));
94111
Processor.PipelineContext pipelineContext = mock(Processor.PipelineContext.class);
95112
IllegalArgumentException exception = expectThrows(
96113
IllegalArgumentException.class,
@@ -114,7 +131,7 @@ public void testInvalidCombinationParams_whenRankIsTooLarge_thenFail() {
114131
boolean ignoreFailure = false;
115132

116133
Map<String, Object> config = new HashMap<>();
117-
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", PARAMETERS, new HashMap<>(Map.of("rank_constant", 50_000)))));
134+
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT, 50_000)));
118135
Processor.PipelineContext pipelineContext = mock(Processor.PipelineContext.class);
119136
IllegalArgumentException exception = expectThrows(
120137
IllegalArgumentException.class,
@@ -140,7 +157,7 @@ public void testInvalidCombinationParams_whenRankIsNotNumeric_thenFail() {
140157
Map<String, Object> config = new HashMap<>();
141158
config.put(
142159
COMBINATION_CLAUSE,
143-
new HashMap<>(Map.of(TECHNIQUE, "rrf", PARAMETERS, new HashMap<>(Map.of("rank_constant", "string"))))
160+
new HashMap<>(Map.of(TECHNIQUE, "rrf", RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT, "String"))
144161
);
145162
Processor.PipelineContext pipelineContext = mock(Processor.PipelineContext.class);
146163
IllegalArgumentException exception = expectThrows(
@@ -165,7 +182,7 @@ public void testInvalidCombinationName_whenUnsupportedFunction_thenFail() {
165182
Map<String, Object> config = new HashMap<>();
166183
config.put(
167184
COMBINATION_CLAUSE,
168-
new HashMap<>(Map.of(TECHNIQUE, "my_function", PARAMETERS, new HashMap<>(Map.of("rank_constant", 100))))
185+
new HashMap<>(Map.of(TECHNIQUE, "my_function", RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT, 100))
169186
);
170187
Processor.PipelineContext pipelineContext = mock(Processor.PipelineContext.class);
171188
IllegalArgumentException exception = expectThrows(
@@ -188,7 +205,7 @@ public void testInvalidTechniqueType_whenPassingNormalization_thenSuccessful() {
188205
boolean ignoreFailure = false;
189206

190207
Map<String, Object> config = new HashMap<>();
191-
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", PARAMETERS, new HashMap<>(Map.of("rank_constant", 100)))));
208+
config.put(COMBINATION_CLAUSE, new HashMap<>(Map.of(TECHNIQUE, "rrf", RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT, 100)));
192209
config.put(
193210
NORMALIZATION_CLAUSE,
194211
new HashMap<>(Map.of(TECHNIQUE, ArithmeticMeanScoreCombinationTechnique.TECHNIQUE_NAME, PARAMETERS, new HashMap<>(Map.of())))
@@ -205,6 +222,48 @@ public void testInvalidTechniqueType_whenPassingNormalization_thenSuccessful() {
205222
assertRRFProcessor(searchPhaseResultsProcessor);
206223
}
207224

225+
@SneakyThrows
226+
public void testWeightsParams_whenInvalidValues_thenFail() {
227+
RRFProcessorFactory rrfProcessorFactory = new RRFProcessorFactory(
228+
new NormalizationProcessorWorkflow(new ScoreNormalizer(), new ScoreCombiner()),
229+
new ScoreNormalizationFactory(),
230+
new ScoreCombinationFactory()
231+
);
232+
final Map<String, Processor.Factory<SearchPhaseResultsProcessor>> processorFactories = new HashMap<>();
233+
String tag = "tag";
234+
String description = "description";
235+
boolean ignoreFailure = false;
236+
237+
// First value is always 0.5
238+
double first = 0.5;
239+
// Second value is random between 0.3 and 1.0
240+
double second = 0.3 + (RandomizedTest.randomDouble() * 0.7);
241+
// Third value is random between 0.3 and 1.0
242+
double third = 0.3 + (RandomizedTest.randomDouble() * 0.7);
243+
// This ensures minimum sum of 1.1 (0.5 + 0.3 + 0.3)
244+
245+
Map<String, Object> config = new HashMap<>();
246+
config.put(
247+
COMBINATION_CLAUSE,
248+
new HashMap<>(
249+
Map.of(
250+
TECHNIQUE,
251+
"rrf",
252+
PARAMETERS,
253+
new HashMap<>(Map.of("weights", Arrays.asList(first, second, third))),
254+
RRFNormalizationTechnique.PARAM_NAME_RANK_CONSTANT,
255+
100
256+
)
257+
)
258+
);
259+
Processor.PipelineContext pipelineContext = mock(Processor.PipelineContext.class);
260+
IllegalArgumentException exception = expectThrows(
261+
IllegalArgumentException.class,
262+
() -> rrfProcessorFactory.create(processorFactories, tag, description, ignoreFailure, config, pipelineContext)
263+
);
264+
assertTrue(exception.getMessage().contains("sum of weights for combination must be equal to 1.0"));
265+
}
266+
208267
private static void assertRRFProcessor(SearchPhaseResultsProcessor searchPhaseResultsProcessor) {
209268
assertNotNull(searchPhaseResultsProcessor);
210269
assertTrue(searchPhaseResultsProcessor instanceof RRFProcessor);

src/test/java/org/opensearch/neuralsearch/query/HybridQueryExplainIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ public void testSpecificQueryTypes_whenMultiMatchAndKnn_thenSuccessful() {
546546
@SneakyThrows
547547
public void testExplain_whenRRFProcessor_thenSuccessful() {
548548
initializeIndexIfNotExist(TEST_MULTI_DOC_INDEX_NAME);
549-
createRRFSearchPipeline(RRF_SEARCH_PIPELINE, true);
549+
createRRFSearchPipeline(RRF_SEARCH_PIPELINE, Arrays.asList(), true);
550550

551551
HybridQueryBuilder hybridQueryBuilder = new HybridQueryBuilder();
552552
KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder()

src/testFixtures/java/org/opensearch/neuralsearch/BaseNeuralSearchIT.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,11 +2212,11 @@ protected enum ProcessorType {
22122212

22132213
@SneakyThrows
22142214
protected void createDefaultRRFSearchPipeline() {
2215-
createRRFSearchPipeline(RRF_SEARCH_PIPELINE, false);
2215+
createRRFSearchPipeline(RRF_SEARCH_PIPELINE, Arrays.asList(), false);
22162216
}
22172217

22182218
@SneakyThrows
2219-
protected void createRRFSearchPipeline(final String pipelineName, boolean addExplainResponseProcessor) {
2219+
protected void createRRFSearchPipeline(final String pipelineName, final List<Double> weights, boolean addExplainResponseProcessor) {
22202220
XContentBuilder builder = XContentFactory.jsonBuilder()
22212221
.startObject()
22222222
.field("description", "Post processor for hybrid search")
@@ -2225,10 +2225,16 @@ protected void createRRFSearchPipeline(final String pipelineName, boolean addExp
22252225
.startObject("score-ranker-processor")
22262226
.startObject("combination")
22272227
.field("technique", "rrf")
2228-
.endObject()
2229-
.endObject()
2230-
.endObject()
2231-
.endArray();
2228+
.startObject("parameters");
2229+
if (weights.size() > 0) {
2230+
builder.startArray("weights");
2231+
for (Double weight : weights) {
2232+
builder.value(weight);
2233+
}
2234+
builder.endArray();
2235+
}
2236+
2237+
builder.endObject().endObject().endObject().endObject().endArray();
22322238

22332239
if (addExplainResponseProcessor) {
22342240
builder.startArray("response_processors")

0 commit comments

Comments
 (0)