Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package io.stargate.sgv2.jsonapi.exception;

import java.util.EnumSet;
import java.util.Optional;
import java.util.UUID;

public class EmbeddingProviderException extends ServerException {

public static final Scope SCOPE = Scope.EMBEDDING_PROVIDER;
Expand All @@ -8,10 +12,41 @@ public EmbeddingProviderException(ErrorInstance errorInstance) {
super(errorInstance);
}

/**
* To construct an EmbeddingProviderException from EGW response.
*
* @see io.stargate.sgv2.jsonapi.service.embedding.gateway.EmbeddingGatewayClient
*/
public EmbeddingProviderException(String code, String title, String body) {
this(
new ErrorInstance(
UUID.randomUUID(),
FAMILY,
SCOPE,
code,
title,
body,
Optional.empty(),
EnumSet.noneOf(ExceptionFlags.class)));
}

public enum Code implements ErrorCode<EmbeddingProviderException> {
EMBEDDING_GATEWAY_NOT_AVAILABLE,
CLIENT_ERROR,
SERVER_ERROR;
EMBEDDING_GATEWAY_REQUEST_PROCESSING_ERROR,
EMBEDDING_GATEWAY_RATE_LIMITED,
EMBEDDING_GATEWAY_UNABLE_RESOLVE_AUTHENTICATION_TYPE,
EMBEDDING_GATEWAY_UNEXPECTED_RESPONSE,

EMBEDDING_REQUEST_ENCODING_ERROR,
EMBEDDING_RESPONSE_DECODING_ERROR,
EMBEDDING_PROVIDER_AUTHENTICATION_KEYS_NOT_PROVIDED,
EMBEDDING_PROVIDER_CLIENT_ERROR,
EMBEDDING_PROVIDER_RATE_LIMITED,
EMBEDDING_PROVIDER_SERVER_ERROR,
EMBEDDING_PROVIDER_TIMEOUT,
EMBEDDING_PROVIDER_UNAVAILABLE,
EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE,
;

private final ErrorTemplate<EmbeddingProviderException> template;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,16 @@
/** ErrorCode is our internal enum that provides codes and a default message for that error code. */
public enum ErrorCodeV1 {
/** Embedding provider service error codes. */
// !!! 16-Dec-2025, tatu: USED BY EMBEDDING-GATEWAY, DO NOT CONVERT "EMBEDDING_" entries yet
EMBEDDING_REQUEST_ENCODING_ERROR("Unable to create embedding provider request message"),
EMBEDDING_RESPONSE_DECODING_ERROR("Unable to parse embedding provider response message"),
EMBEDDING_PROVIDER_AUTHENTICATION_KEYS_NOT_PROVIDED(
"The Embedding Provider authentication keys not provided"),
EMBEDDING_PROVIDER_CLIENT_ERROR("The Embedding Provider returned a HTTP client error"),
EMBEDDING_PROVIDER_SERVER_ERROR("The Embedding Provider returned a HTTP server error"),
EMBEDDING_PROVIDER_RATE_LIMITED("The Embedding Provider rate limited the request"),
EMBEDDING_PROVIDER_TIMEOUT("The Embedding Provider timed out"),
EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"),
EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"),

VECTOR_SEARCH_NOT_SUPPORTED("Vector search is not enabled for the collection"),

VECTOR_SEARCH_INVALID_FUNCTION_NAME("Invalid vector search function name"),

VECTOR_SEARCH_TOO_BIG_VALUE("Vector embedding property '$vector' length too big"),

VECTORIZE_FEATURE_NOT_AVAILABLE("Vectorize feature is not available in the environment"),
VECTORIZE_SERVICE_NOT_REGISTERED("Vectorize service name provided is not registered : "),
VECTORIZE_SERVICE_TYPE_UNAVAILABLE("Vectorize service unavailable : "),
VECTORIZE_INVALID_AUTHENTICATION_TYPE("Invalid vectorize authentication type"),

VECTORIZE_CREDENTIAL_INVALID("Invalid credential name for vectorize"),

// NOTE: ones used/referenced by `embedding-gateway`, cannot remove:

INVALID_REQUEST("Request not supported by the data store"),

EMBEDDING_GATEWAY_ERROR_RATE_LIMIT("Embedding Gateway error rate limit reached for the tenant"),
EMBEDDING_GATEWAY_PROCESSING_ERROR("Embedding Gateway failed to process request");
;

private final String message;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ public class JsonApiException extends RuntimeException {
{
add(VECTOR_SEARCH_NOT_SUPPORTED);
add(VECTORIZE_FEATURE_NOT_AVAILABLE);
add(VECTORIZE_SERVICE_NOT_REGISTERED);
add(VECTORIZE_SERVICE_TYPE_UNAVAILABLE);
add(VECTORIZE_INVALID_AUTHENTICATION_TYPE);
add(VECTORIZE_CREDENTIAL_INVALID);
}
};
Expand All @@ -50,13 +47,7 @@ public class JsonApiException extends RuntimeException {
add(VECTOR_SEARCH_TOO_BIG_VALUE);
}
},
ErrorScope.SCHEMA,
new HashSet<>() {
{
add(INVALID_REQUEST);
}
},
ErrorScope.EMBEDDING);
ErrorScope.SCHEMA);

protected JsonApiException(ErrorCodeV1 errorCode) {
this(errorCode, errorCode.getMessage(), null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
Expand Down Expand Up @@ -177,11 +176,6 @@ public static RuntimeException translateThrowable(Throwable throwable) {
case APIException ae -> ae;
case JsonApiException jae -> jae;

// ##########
// WEIRD ONES FROM throwableToErrorMapper
// TODO: AARON - why would this happen ? was in old ThrowableToErrorMapper
case TimeoutException te -> ErrorCodeV1.EMBEDDING_PROVIDER_TIMEOUT.toApiException();

// ##########
// # QUARKUS ERRORS
// these are the client 4xx errors , no change means we return them as-is e.g. the 4XX code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static io.stargate.sgv2.jsonapi.config.constants.DocumentConstants.Fields.VECTOR_EMBEDDING_FIELD;
import static io.stargate.sgv2.jsonapi.config.constants.DocumentConstants.Fields.VECTOR_EMBEDDING_TEXT_FIELD;
import static io.stargate.sgv2.jsonapi.exception.ErrorCodeV1.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -125,11 +124,15 @@ public Uni<Boolean> vectorize(List<JsonNode> documents) {

// check if we get back the same number of vectors that we asked for
if (vectorData.size() != vectorizeTexts.size()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Embedding provider '%s' didn't return the expected number of embeddings. Expect: '%d'. Actual: '%d'",
collectionVectorDefinition.vectorizeDefinition().provider(),
vectorizeTexts.size(),
vectorData.size());
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE
.get(
Map.of(
"errorMessage",
"Embedding provider '%s' didn't return the expected number of embeddings. Expect: '%d'. Actual: '%d'"
.formatted(
collectionVectorDefinition.vectorizeDefinition().provider(),
vectorizeTexts.size(),
vectorData.size())));
}
for (int vectorPosition = 0;
vectorPosition < vectorData.size();
Expand All @@ -139,11 +142,17 @@ public Uni<Boolean> vectorize(List<JsonNode> documents) {
float[] vector = vectorData.get(vectorPosition);
// check if all vectors have the expected size
if (vector.length != collectionVectorDefinition.vectorSize()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'",
collectionVectorDefinition.vectorizeDefinition().provider(),
collectionVectorDefinition.vectorSize(),
vector.length);
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE
.get(
Map.of(
"errorMessage",
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'"
.formatted(
collectionVectorDefinition
.vectorizeDefinition()
.provider(),
collectionVectorDefinition.vectorSize(),
vector.length)));
}
final ArrayNode arrayNode = nodeFactory.arrayNode(vector.length);
for (float listValue : vector) {
Expand Down Expand Up @@ -194,11 +203,14 @@ public Uni<float[]> vectorize(String vectorizeContent) {
float[] vector = vectorData.get(0);
// check if vector have the expected size
if (vector.length != collectionVectorDefinition.vectorSize()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'",
collectionVectorDefinition.vectorizeDefinition().provider(),
collectionVectorDefinition.vectorSize(),
vector.length);
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.get(
Map.of(
"errorMessage",
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'"
.formatted(
collectionVectorDefinition.vectorizeDefinition().provider(),
collectionVectorDefinition.vectorSize(),
vector.length)));
}
return vector;
});
Expand Down Expand Up @@ -241,11 +253,15 @@ public Uni<Boolean> vectorize(SortClause sortClause) {
vectorConfig.getColumnDefinition(VECTOR_EMBEDDING_TEXT_FIELD).orElseThrow();
// check if vector have the expected size
if (vector.length != collectionVectorDefinition.vectorSize()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'",
collectionVectorDefinition.vectorizeDefinition().provider(),
collectionVectorDefinition.vectorSize(),
vector.length);
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE
.get(
Map.of(
"errorMessage",
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'"
.formatted(
collectionVectorDefinition.vectorizeDefinition().provider(),
collectionVectorDefinition.vectorSize(),
vector.length)));
}
// 12-Jun-2025, tatu: Important! Due to original bad design, we need to allow
// replacing of vectorize sort with resolved vector sort:
Expand Down Expand Up @@ -317,9 +333,11 @@ private Uni<List<float[]>> vectorizeTexts(
vectorData -> {
// Copied from vectorize(List<JsonNode> documents) above leaving as is for now
if (vectorData.size() != textsToVectorize.size()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Embedding provider '%s' didn't return the expected number of embeddings. Expect: '%d'. Actual: '%d'",
providerName, textsToVectorize.size(), vectorData.size());
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.get(
Map.of(
"errorMessage",
"Embedding provider '%s' didn't return the expected number of embeddings. Expect: '%d'. Actual: '%d'"
.formatted(providerName, textsToVectorize.size(), vectorData.size())));
}
return vectorData;
});
Expand Down Expand Up @@ -376,11 +394,14 @@ public void setVector(float[] vector) {
private void validateVector(float[] vector) {
// Copied from vectorize(List<JsonNode> documents) above leaving as is for now - aaron
if (vector.length != vectorType.getDimension()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'",
vectorType.getVectorizeDefinition().provider(),
vectorType.getDimension(),
vector.length);
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.get(
Map.of(
"errorMessage",
"Embedding provider '%s' did not return expected embedding length. Expect: '%d'. Actual: '%d'"
.formatted(
vectorType.getVectorizeDefinition().provider(),
vectorType.getDimension(),
vector.length)));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.stargate.sgv2.jsonapi.service.embedding.configuration;

import static io.stargate.sgv2.jsonapi.exception.ErrorCodeV1.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE;

import io.stargate.sgv2.jsonapi.exception.EmbeddingProviderException;
import io.stargate.sgv2.jsonapi.exception.JsonApiException;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientResponseContext;
Expand All @@ -10,6 +9,7 @@
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -53,8 +53,8 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re

// Throw error if there is no response body
if (!responseContext.hasEntity()) {
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"No response body from the embedding provider");
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.get(
Map.of("errorMessage", "No response body from the embedding provider"));
}

// response should always be JSON; if not, error out, include raw response message for
Expand All @@ -72,9 +72,12 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re
logger.error(
"Cannot convert the provider's error response to string: " + e.getMessage(), e);
}
throw EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.toApiException(
"Expected response Content-Type ('application/json' or 'text/json') from the embedding provider but found '%s'; HTTP Status: %s; The response body is: '%s'.",
contentType, responseContext.getStatus(), responseBody);

throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE.get(
Map.of(
"errorMessage",
"Expected response Content-Type ('application/json' or 'text/json') from the embedding provider but found '%s'; HTTP Status: %s; The response body is: '%s'."
.formatted(contentType, responseContext.getStatus(), responseBody)));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.stargate.sgv2.jsonapi.service.embedding.configuration;

import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1;
import io.stargate.sgv2.jsonapi.exception.EmbeddingProviderException;
import io.stargate.sgv2.jsonapi.service.provider.ModelProvider;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -44,7 +44,9 @@ public ServiceConfigStore.ServiceConfig getConfiguration(ModelProvider modelProv

var providerConfig = providersConfig.providers().get(modelProvider.apiName());
if (providerConfig == null || !providerConfig.enabled()) {
throw ErrorCodeV1.VECTORIZE_SERVICE_TYPE_UNAVAILABLE.toApiException(modelProvider.apiName());
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_UNAVAILABLE.get(
"errorMessage",
"The service provider '%s' is not supported.".formatted(modelProvider.apiName()));
}

Objects.requireNonNull(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
import io.stargate.embedding.gateway.EmbeddingService;
import io.stargate.sgv2.jsonapi.api.request.EmbeddingCredentials;
import io.stargate.sgv2.jsonapi.api.request.tenant.Tenant;
import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1;
import io.stargate.sgv2.jsonapi.exception.JsonApiException;
import io.stargate.sgv2.jsonapi.exception.*;
import io.stargate.sgv2.jsonapi.service.embedding.configuration.EmbeddingProvidersConfig;
import io.stargate.sgv2.jsonapi.service.embedding.configuration.ServiceConfigStore;
import io.stargate.sgv2.jsonapi.service.embedding.operation.EmbeddingProvider;
import io.stargate.sgv2.jsonapi.service.provider.ModelProvider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

/** Grpc client for embedding gateway service */
public class EmbeddingGatewayClient extends EmbeddingProvider {
Expand Down Expand Up @@ -172,7 +169,14 @@ else if (value instanceof Boolean)
embeddingResponse = grpcGatewayClient.embed(gatewayRequest);
} catch (StatusRuntimeException e) {
if (e.getStatus().getCode().equals(Status.Code.DEADLINE_EXCEEDED)) {
throw ErrorCodeV1.EMBEDDING_PROVIDER_TIMEOUT.toApiException(e, e.getMessage());
throw EmbeddingProviderException.Code.EMBEDDING_PROVIDER_TIMEOUT.get(
Map.of(
"provider",
modelProvider().apiName(),
"httpStatus",
String.valueOf(e.getStatus().getCode()),
"errorMessage",
e.getMessage()));
}
throw e;
}
Expand All @@ -182,9 +186,10 @@ else if (value instanceof Boolean)
.transform(
gatewayResponse -> {
if (gatewayResponse.hasError()) {
throw new JsonApiException(
ErrorCodeV1.valueOf(gatewayResponse.getError().getErrorCode()),
gatewayResponse.getError().getErrorMessage());
throw new EmbeddingProviderException(
gatewayResponse.getError().getErrorCode(),
gatewayResponse.getError().getErrorTitle(),
gatewayResponse.getError().getErrorBody());
}
// aaron - 10 June 2025 - previous code would silently swallow no data returned
// but grpc will make sure resp.getEmbeddingsList() is never null
Expand Down
Loading