Skip to content

Commit 9877bab

Browse files
[ML] Refactor Inference API chat completion error handling (#132475)
* Refactoring * Created a utils class * Fixed order bug * Working tests * Fixing tests * Fixing long line issue * fixing syntax * Addressing feedback and adding some tests
1 parent 8dca924 commit 9877bab

23 files changed

+452
-669
lines changed

x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/retry/ChatCompletionErrorResponseHandler.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.inference.external.http.retry;
99

10+
import org.elasticsearch.core.Nullable;
1011
import org.elasticsearch.rest.RestStatus;
1112
import org.elasticsearch.xpack.core.inference.results.UnifiedChatCompletionException;
1213
import org.elasticsearch.xpack.inference.external.http.HttpResult;
@@ -22,9 +23,9 @@
2223
public class ChatCompletionErrorResponseHandler {
2324
private static final String STREAM_ERROR = "stream_error";
2425

25-
private final UnifiedChatCompletionErrorParser unifiedChatCompletionErrorParser;
26+
private final UnifiedChatCompletionErrorParserContract unifiedChatCompletionErrorParser;
2627

27-
public ChatCompletionErrorResponseHandler(UnifiedChatCompletionErrorParser errorParser) {
28+
public ChatCompletionErrorResponseHandler(UnifiedChatCompletionErrorParserContract errorParser) {
2829
this.unifiedChatCompletionErrorParser = Objects.requireNonNull(errorParser);
2930
}
3031

@@ -49,14 +50,18 @@ private UnifiedChatCompletionException buildChatCompletionErrorInternal(
4950
restStatus,
5051
errorMessage,
5152
errorResponse.type(),
52-
errorResponse.code(),
53+
code(errorResponse.code(), restStatus),
5354
errorResponse.param()
5455
);
5556
} else {
5657
return buildDefaultChatCompletionError(errorResponse, errorMessage, restStatus);
5758
}
5859
}
5960

61+
private static String code(@Nullable String code, RestStatus status) {
62+
return code != null ? code : status.name().toLowerCase(Locale.ROOT);
63+
}
64+
6065
/**
6166
* Builds a default {@link UnifiedChatCompletionException} for a streaming request.
6267
* This method is used when an error response is received we were unable to parse it in the format we were expecting.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import org.elasticsearch.xpack.inference.external.http.HttpResult;
1111

12-
public interface UnifiedChatCompletionErrorParser {
12+
public interface UnifiedChatCompletionErrorParserContract {
1313
UnifiedChatCompletionErrorResponse parse(HttpResult result);
1414

1515
UnifiedChatCompletionErrorResponse parse(String result);

x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/retry/UnifiedChatCompletionErrorResponse.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,51 @@
88
package org.elasticsearch.xpack.inference.external.http.retry;
99

1010
import org.elasticsearch.core.Nullable;
11+
import org.elasticsearch.xcontent.ConstructingObjectParser;
12+
import org.elasticsearch.xcontent.ParseField;
13+
import org.elasticsearch.xpack.inference.external.http.HttpResult;
1114

1215
import java.util.Objects;
16+
import java.util.Optional;
1317

1418
public class UnifiedChatCompletionErrorResponse extends ErrorResponse {
19+
20+
// Default for testing
21+
static final ConstructingObjectParser<Optional<UnifiedChatCompletionErrorResponse>, Void> ERROR_OBJECT_PARSER =
22+
new ConstructingObjectParser<>("streaming_error", true, args -> Optional.ofNullable((UnifiedChatCompletionErrorResponse) args[0]));
23+
private static final ConstructingObjectParser<UnifiedChatCompletionErrorResponse, Void> ERROR_BODY_PARSER =
24+
new ConstructingObjectParser<>(
25+
"streaming_error",
26+
true,
27+
args -> new UnifiedChatCompletionErrorResponse((String) args[0], (String) args[1], (String) args[2], (String) args[3])
28+
);
29+
30+
static {
31+
ERROR_BODY_PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("message"));
32+
ERROR_BODY_PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("type"));
33+
ERROR_BODY_PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), new ParseField("code"));
34+
ERROR_BODY_PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), new ParseField("param"));
35+
36+
ERROR_OBJECT_PARSER.declareObjectOrNull(
37+
ConstructingObjectParser.optionalConstructorArg(),
38+
ERROR_BODY_PARSER,
39+
null,
40+
new ParseField("error")
41+
);
42+
}
43+
44+
public static final UnifiedChatCompletionErrorParserContract ERROR_PARSER = UnifiedChatCompletionErrorResponseUtils
45+
.createErrorParserWithObjectParser(ERROR_OBJECT_PARSER);
1546
public static final UnifiedChatCompletionErrorResponse UNDEFINED_ERROR = new UnifiedChatCompletionErrorResponse();
1647

48+
/**
49+
* Standard error response parser.
50+
* @param response The error response as an HttpResult
51+
*/
52+
public static UnifiedChatCompletionErrorResponse fromHttpResult(HttpResult response) {
53+
return ERROR_PARSER.parse(response);
54+
}
55+
1756
@Nullable
1857
private final String code;
1958
@Nullable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.inference.external.http.retry;
9+
10+
import org.elasticsearch.core.CheckedFunction;
11+
import org.elasticsearch.xcontent.ConstructingObjectParser;
12+
import org.elasticsearch.xcontent.XContentFactory;
13+
import org.elasticsearch.xcontent.XContentParser;
14+
import org.elasticsearch.xcontent.XContentParserConfiguration;
15+
import org.elasticsearch.xcontent.XContentType;
16+
import org.elasticsearch.xpack.inference.external.http.HttpResult;
17+
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.Optional;
20+
import java.util.concurrent.Callable;
21+
22+
public class UnifiedChatCompletionErrorResponseUtils {
23+
24+
/**
25+
* Creates a {@link UnifiedChatCompletionErrorParserContract} that parses the error response as a string.
26+
* This is useful for cases where the error response is too complicated to parse.
27+
*
28+
* @param type The type of the error, used for categorization.
29+
* @return A {@link UnifiedChatCompletionErrorParserContract} instance.
30+
*/
31+
public static UnifiedChatCompletionErrorParserContract createErrorParserWithStringify(String type) {
32+
return new UnifiedChatCompletionErrorParserContract() {
33+
@Override
34+
public UnifiedChatCompletionErrorResponse parse(HttpResult result) {
35+
try {
36+
String errorMessage = new String(result.body(), StandardCharsets.UTF_8);
37+
return new UnifiedChatCompletionErrorResponse(errorMessage, type, null, null);
38+
} catch (Exception e) {
39+
// swallow the error
40+
}
41+
42+
return UnifiedChatCompletionErrorResponse.UNDEFINED_ERROR;
43+
}
44+
45+
@Override
46+
public UnifiedChatCompletionErrorResponse parse(String result) {
47+
return new UnifiedChatCompletionErrorResponse(result, type, null, null);
48+
}
49+
};
50+
}
51+
52+
/**
53+
* Creates a {@link UnifiedChatCompletionErrorParserContract} that uses a {@link ConstructingObjectParser} to parse the error response.
54+
* This is useful for cases where the error response can be parsed into an object.
55+
*
56+
* @param objectParser The {@link ConstructingObjectParser} to use for parsing the error response.
57+
* @return A {@link UnifiedChatCompletionErrorParserContract} instance.
58+
*/
59+
public static UnifiedChatCompletionErrorParserContract createErrorParserWithObjectParser(
60+
ConstructingObjectParser<Optional<UnifiedChatCompletionErrorResponse>, Void> objectParser
61+
) {
62+
return new UnifiedChatCompletionErrorParser<>((parser) -> objectParser.apply(parser, null));
63+
}
64+
65+
/**
66+
* Creates a {@link UnifiedChatCompletionErrorParserContract} that uses a generic parser function to parse the error response.
67+
* This is useful for cases where the error response can be parsed using custom logic, typically when parsing from a map.
68+
*
69+
* @param genericParser The function that takes an {@link XContentParser} and returns an
70+
* {@link Optional<UnifiedChatCompletionErrorResponse>}.
71+
* @param <E> The type of exception that the parser can throw.
72+
* @return A {@link UnifiedChatCompletionErrorParserContract} instance.
73+
*/
74+
public static <E extends Exception> UnifiedChatCompletionErrorParserContract createErrorParserWithGenericParser(
75+
CheckedFunction<XContentParser, Optional<UnifiedChatCompletionErrorResponse>, E> genericParser
76+
) {
77+
return new UnifiedChatCompletionErrorParser<>(genericParser);
78+
}
79+
80+
private record UnifiedChatCompletionErrorParser<E extends Exception>(
81+
CheckedFunction<XContentParser, Optional<UnifiedChatCompletionErrorResponse>, E> genericParser
82+
) implements UnifiedChatCompletionErrorParserContract {
83+
84+
@Override
85+
public UnifiedChatCompletionErrorResponse parse(HttpResult result) {
86+
return executeGenericParser(genericParser, createHttpResultXContentParserFunction(result));
87+
}
88+
89+
@Override
90+
public UnifiedChatCompletionErrorResponse parse(String result) {
91+
return executeGenericParser(genericParser, createStringXContentParserFunction(result));
92+
}
93+
94+
}
95+
96+
private static Callable<XContentParser> createHttpResultXContentParserFunction(HttpResult response) {
97+
return () -> XContentFactory.xContent(XContentType.JSON).createParser(XContentParserConfiguration.EMPTY, response.body());
98+
}
99+
100+
private static Callable<XContentParser> createStringXContentParserFunction(String response) {
101+
return () -> XContentFactory.xContent(XContentType.JSON).createParser(XContentParserConfiguration.EMPTY, response);
102+
}
103+
104+
private static <E extends Exception> UnifiedChatCompletionErrorResponse executeGenericParser(
105+
CheckedFunction<XContentParser, Optional<UnifiedChatCompletionErrorResponse>, E> genericParser,
106+
Callable<XContentParser> createXContentParser
107+
) {
108+
try (XContentParser parser = createXContentParser.call()) {
109+
return genericParser.apply(parser).orElse(UnifiedChatCompletionErrorResponse.UNDEFINED_ERROR);
110+
} catch (Exception e) {
111+
// swallow the error
112+
}
113+
114+
return UnifiedChatCompletionErrorResponse.UNDEFINED_ERROR;
115+
}
116+
117+
private UnifiedChatCompletionErrorResponseUtils() {}
118+
}

x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/streaming/StreamingErrorResponse.java

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)