Skip to content

Commit 087d4e5

Browse files
use ConstructingObjectParser for response parsing
1 parent dc6f320 commit 087d4e5

File tree

2 files changed

+42
-43
lines changed

2 files changed

+42
-43
lines changed

x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceDenseTextEmbeddingsResponseEntity.java

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,23 @@
77

88
package org.elasticsearch.xpack.inference.external.response.elastic;
99

10-
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
1110
import org.elasticsearch.common.xcontent.XContentParserUtils;
11+
import org.elasticsearch.xcontent.ConstructingObjectParser;
12+
import org.elasticsearch.xcontent.ParseField;
1213
import org.elasticsearch.xcontent.XContentFactory;
13-
import org.elasticsearch.xcontent.XContentParser;
1414
import org.elasticsearch.xcontent.XContentParserConfiguration;
1515
import org.elasticsearch.xcontent.XContentType;
1616
import org.elasticsearch.xpack.core.inference.results.TextEmbeddingFloatResults;
1717
import org.elasticsearch.xpack.inference.external.http.HttpResult;
1818
import org.elasticsearch.xpack.inference.external.request.Request;
1919

2020
import java.io.IOException;
21-
import java.util.Collections;
2221
import java.util.List;
2322

24-
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
25-
import static org.elasticsearch.common.xcontent.XContentParserUtils.parseList;
26-
import static org.elasticsearch.xpack.inference.external.response.XContentUtils.moveToFirstToken;
27-
import static org.elasticsearch.xpack.inference.external.response.XContentUtils.positionParserAtTokenAfterField;
23+
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
2824

2925
public class ElasticInferenceServiceDenseTextEmbeddingsResponseEntity {
3026

31-
private static final String FAILED_TO_FIND_FIELD_TEMPLATE =
32-
"Failed to find required field [%s] in Elastic Inference Service dense text embeddings response";
33-
3427
/**
3528
* Parses the Elastic Inference Service Dense Text Embeddings response.
3629
*
@@ -64,43 +57,51 @@ public class ElasticInferenceServiceDenseTextEmbeddingsResponseEntity {
6457
* </code>
6558
* </pre>
6659
*/
67-
6860
public static TextEmbeddingFloatResults fromResponse(Request request, HttpResult response) throws IOException {
69-
var parserConfig = XContentParserConfiguration.EMPTY.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE);
70-
71-
try (XContentParser jsonParser = XContentFactory.xContent(XContentType.JSON).createParser(parserConfig, response.body())) {
72-
moveToFirstToken(jsonParser);
73-
74-
XContentParser.Token token = jsonParser.currentToken();
75-
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, jsonParser);
61+
try (var p = XContentFactory.xContent(XContentType.JSON).createParser(XContentParserConfiguration.EMPTY, response.body())) {
62+
return EmbeddingFloatResult.PARSER.apply(p, null).toTextEmbeddingFloatResults();
63+
}
64+
}
7665

77-
positionParserAtTokenAfterField(jsonParser, "data", FAILED_TO_FIND_FIELD_TEMPLATE);
66+
public record EmbeddingFloatResult(List<EmbeddingFloatResultEntry> embeddingResults) {
67+
@SuppressWarnings("unchecked")
68+
public static final ConstructingObjectParser<EmbeddingFloatResult, Void> PARSER = new ConstructingObjectParser<>(
69+
EmbeddingFloatResult.class.getSimpleName(),
70+
true,
71+
args -> new EmbeddingFloatResult((List<EmbeddingFloatResultEntry>) args[0])
72+
);
7873

79-
List<TextEmbeddingFloatResults.Embedding> parsedEmbeddings = parseList(
80-
jsonParser,
81-
(parser, index) -> ElasticInferenceServiceDenseTextEmbeddingsResponseEntity.parseTextEmbeddingObject(parser)
74+
static {
75+
// Custom field declaration to handle array of arrays format
76+
PARSER.declareField(
77+
constructorArg(),
78+
(parser, context) -> {
79+
return XContentParserUtils.parseList(parser, (p, index) -> {
80+
List<Float> embedding = XContentParserUtils.parseList(p, (innerParser, innerIndex) -> innerParser.floatValue());
81+
return EmbeddingFloatResultEntry.fromFloatArray(embedding);
82+
});
83+
},
84+
new ParseField("data"),
85+
org.elasticsearch.xcontent.ObjectParser.ValueType.OBJECT_ARRAY
8286
);
83-
84-
if (parsedEmbeddings.isEmpty()) {
85-
return new TextEmbeddingFloatResults(Collections.emptyList());
86-
}
87-
88-
return new TextEmbeddingFloatResults(parsedEmbeddings);
8987
}
90-
}
9188

92-
private static TextEmbeddingFloatResults.Embedding parseTextEmbeddingObject(XContentParser parser) throws IOException {
93-
List<Float> embeddingValueList = parseList(
94-
parser,
95-
ElasticInferenceServiceDenseTextEmbeddingsResponseEntity::parseEmbeddingFloatValueList
96-
);
97-
return TextEmbeddingFloatResults.Embedding.of(embeddingValueList);
89+
public TextEmbeddingFloatResults toTextEmbeddingFloatResults() {
90+
return new TextEmbeddingFloatResults(
91+
embeddingResults.stream().map(entry -> TextEmbeddingFloatResults.Embedding.of(entry.embedding)).toList()
92+
);
93+
}
9894
}
9995

100-
private static float parseEmbeddingFloatValueList(XContentParser parser) throws IOException {
101-
XContentParser.Token token = parser.currentToken();
102-
XContentParserUtils.ensureExpectedToken(XContentParser.Token.VALUE_NUMBER, token, parser);
103-
return parser.floatValue();
96+
/**
97+
* Represents a single embedding entry in the response.
98+
* For the Elastic Inference Service, each entry is just an array of floats (no wrapper object).
99+
* This is a simpler wrapper that just holds the float array.
100+
*/
101+
public record EmbeddingFloatResultEntry(List<Float> embedding) {
102+
public static EmbeddingFloatResultEntry fromFloatArray(List<Float> floats) {
103+
return new EmbeddingFloatResultEntry(floats);
104+
}
104105
}
105106

106107
private ElasticInferenceServiceDenseTextEmbeddingsResponseEntity() {}

x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreatorTests.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import static org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests.createSender;
4949
import static org.elasticsearch.xpack.inference.services.ServiceComponentsTests.createWithEmptySettings;
5050
import static org.hamcrest.Matchers.contains;
51+
import static org.hamcrest.Matchers.containsString;
5152
import static org.hamcrest.Matchers.equalTo;
5253
import static org.hamcrest.Matchers.hasSize;
5354
import static org.hamcrest.Matchers.instanceOf;
@@ -407,10 +408,7 @@ public void testSend_FailsFromInvalidResponseFormat_ForDenseTextEmbeddingsAction
407408
);
408409

409410
var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT));
410-
assertThat(
411-
thrownException.getMessage(),
412-
is("Failed to parse object: expecting token of type [START_ARRAY] but found [START_OBJECT]")
413-
);
411+
assertThat(thrownException.getMessage(), containsString("[EmbeddingFloatResult] failed to parse field [data]"));
414412

415413
assertThat(webServer.requests(), hasSize(1));
416414
assertNull(webServer.requests().get(0).getUri().getQuery());

0 commit comments

Comments
 (0)