From c34864e71748aa238dc257b02dbbc8152ea77756 Mon Sep 17 00:00:00 2001 From: liugddx Date: Wed, 12 Nov 2025 07:59:19 +0800 Subject: [PATCH 1/2] feat: enhance error handling in ChromaApi with detailed logging for not found exceptions Signed-off-by: liugddx --- .../ai/chroma/vectorstore/ChromaApi.java | 142 ++++++++++++++++-- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java index 7f55ab62d4a..ec4b344a6b7 100644 --- a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java +++ b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java @@ -28,6 +28,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.springframework.ai.chroma.vectorstore.ChromaApi.QueryRequest.Include; import org.springframework.ai.chroma.vectorstore.common.ChromaApiConstants; import org.springframework.ai.util.json.JsonParser; @@ -52,6 +55,8 @@ */ public class ChromaApi { + private static final Logger logger = LoggerFactory.getLogger(ChromaApi.class); + public static Builder builder() { return new Builder(); } @@ -62,6 +67,9 @@ public static Builder builder() { // Regular expression pattern that looks for a message. private static final Pattern MESSAGE_ERROR_PATTERN = Pattern.compile("\"message\":\"(.*?)\""); + // Regular expression pattern that looks for NotFoundError in JSON error response. + private static final Pattern NOT_FOUND_ERROR_PATTERN = Pattern.compile("NotFoundError\\('([^']*)'\\)"); + private static final String X_CHROMA_TOKEN_NAME = "x-chroma-token"; private final ObjectMapper objectMapper; @@ -136,12 +144,21 @@ public Tenant getTenant(String tenantName) { .retrieve() .body(Tenant.class); } - catch (HttpServerErrorException | HttpClientErrorException e) { - String msg = this.getErrorMessage(e); - if (String.format("Tenant [%s] not found", tenantName).equals(msg)) { + catch (HttpClientErrorException e) { + if (isNotFoundError(e, "Tenant", tenantName)) { + String errorMessage = this.getErrorMessage(e); + if (StringUtils.hasText(errorMessage)) { + logger.debug("Tenant [{}] does not exist: {}, returning null", tenantName, errorMessage); + } + else { + logger.debug("Tenant [{}] does not exist, returning null", tenantName); + } return null; } - throw new RuntimeException(msg, e); + throw new RuntimeException(this.getErrorMessage(e), e); + } + catch (HttpServerErrorException e) { + throw new RuntimeException(this.getErrorMessage(e), e); } } @@ -165,12 +182,23 @@ public Database getDatabase(String tenantName, String databaseName) { .retrieve() .body(Database.class); } - catch (HttpServerErrorException | HttpClientErrorException e) { - String msg = this.getErrorMessage(e); - if (msg.startsWith(String.format("Database [%s] not found.", databaseName))) { + catch (HttpClientErrorException e) { + if (isNotFoundError(e, "Database", databaseName)) { + String errorMessage = this.getErrorMessage(e); + if (StringUtils.hasText(errorMessage)) { + logger.debug("Database [{}] in tenant [{}] does not exist: {}, returning null", databaseName, + tenantName, errorMessage); + } + else { + logger.debug("Database [{}] in tenant [{}] does not exist, returning null", databaseName, + tenantName); + } return null; } - throw new RuntimeException(msg, e); + throw new RuntimeException(this.getErrorMessage(e), e); + } + catch (HttpServerErrorException e) { + throw new RuntimeException(this.getErrorMessage(e), e); } } @@ -226,12 +254,24 @@ public Collection getCollection(String tenantName, String databaseName, String c .retrieve() .body(Collection.class); } - catch (HttpServerErrorException | HttpClientErrorException e) { - String msg = this.getErrorMessage(e); - if (String.format("Collection [%s] does not exists", collectionName).equals(msg)) { + catch (HttpClientErrorException e) { + if (isNotFoundError(e, "Collection", collectionName)) { + String errorMessage = this.getErrorMessage(e); + if (StringUtils.hasText(errorMessage)) { + logger.debug( + "Collection [{}] in database [{}] and tenant [{}] does not exist: {}, returning null", + collectionName, databaseName, tenantName, errorMessage); + } + else { + logger.debug("Collection [{}] in database [{}] and tenant [{}] does not exist, returning null", + collectionName, databaseName, tenantName); + } return null; } - throw new RuntimeException(msg, e); + throw new RuntimeException(this.getErrorMessage(e), e); + } + catch (HttpServerErrorException e) { + throw new RuntimeException(this.getErrorMessage(e), e); } } @@ -326,7 +366,76 @@ private void httpHeaders(HttpHeaders headers) { } } + private boolean isNotFoundError(HttpClientErrorException e, String resourceType, String resourceName) { + // First, check the response body for JSON error response + String responseBody = e.getResponseBodyAsString(); + if (StringUtils.hasText(responseBody)) { + try { + ErrorResponse errorResponse = this.objectMapper.readValue(responseBody, ErrorResponse.class); + String error = errorResponse.error(); + // Check for NotFoundError('message') format + if (NOT_FOUND_ERROR_PATTERN.matcher(error).find()) { + return true; + } + // Check for "Resource [name] not found." format + String expectedPattern = String.format("%s [%s] not found.", resourceType, resourceName); + if (error.startsWith(expectedPattern)) { + return true; + } + // Check if error contains "not found" (case insensitive) + if (error.toLowerCase().contains("not found")) { + return true; + } + } + catch (JsonProcessingException ex) { + // If JSON parsing fails, check the response body directly + String expectedPattern = String.format("%s [%s] not found.", resourceType, resourceName); + if (responseBody.contains(expectedPattern) || responseBody.toLowerCase().contains("not found")) { + return true; + } + } + } + + // Check the exception message + String errorMessage = e.getMessage(); + if (StringUtils.hasText(errorMessage)) { + // Check for "Resource [name] not found." format + String expectedPattern = String.format("%s [%s] not found.", resourceType, resourceName); + if (errorMessage.contains(expectedPattern)) { + return true; + } + // Check if error message contains "not found" (case insensitive) + if (errorMessage.toLowerCase().contains("not found")) { + return true; + } + } + + return false; + } + private String getErrorMessage(HttpStatusCodeException e) { + // First, try to parse the response body as JSON error response + String responseBody = e.getResponseBodyAsString(); + if (StringUtils.hasText(responseBody)) { + try { + ErrorResponse errorResponse = this.objectMapper.readValue(responseBody, ErrorResponse.class); + // Extract error message from NotFoundError('message') format + Matcher notFoundErrorMatcher = NOT_FOUND_ERROR_PATTERN.matcher(errorResponse.error()); + if (notFoundErrorMatcher.find()) { + return notFoundErrorMatcher.group(1); + } + // Return the error as-is if it doesn't match NotFoundError pattern + return errorResponse.error(); + } + catch (JsonProcessingException ex) { + // If JSON parsing fails, return the response body as-is if it's not empty + if (StringUtils.hasText(responseBody.trim())) { + return responseBody.trim(); + } + logger.debug("Failed to parse error response as JSON, falling back to exception message", ex); + } + } + var errorMessage = e.getMessage(); // If the error message is empty or null, return an empty string @@ -622,6 +731,15 @@ private static class CollectionList extends ArrayList { } + /** + * Chroma API error response. + * + * @param error The error message from Chroma API. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private record ErrorResponse(@JsonProperty("error") String error) { + } + public static final class Builder { private String baseUrl = ChromaApiConstants.DEFAULT_BASE_URL; From 00dee40d8237d8b050358d8855d7fa3f61a652e2 Mon Sep 17 00:00:00 2001 From: liugddx Date: Wed, 12 Nov 2025 07:59:58 +0800 Subject: [PATCH 2/2] feat: enhance error handling in ChromaApi with detailed logging and NotFoundError detection Signed-off-by: liugddx --- .../ai/chroma/vectorstore/ChromaApi.java | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java index ec4b344a6b7..f631653360a 100644 --- a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java +++ b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java @@ -258,8 +258,7 @@ public Collection getCollection(String tenantName, String databaseName, String c if (isNotFoundError(e, "Collection", collectionName)) { String errorMessage = this.getErrorMessage(e); if (StringUtils.hasText(errorMessage)) { - logger.debug( - "Collection [{}] in database [{}] and tenant [{}] does not exist: {}, returning null", + logger.debug("Collection [{}] in database [{}] and tenant [{}] does not exist: {}, returning null", collectionName, databaseName, tenantName, errorMessage); } else { @@ -372,21 +371,21 @@ private boolean isNotFoundError(HttpClientErrorException e, String resourceType, if (StringUtils.hasText(responseBody)) { try { ErrorResponse errorResponse = this.objectMapper.readValue(responseBody, ErrorResponse.class); - String error = errorResponse.error(); - // Check for NotFoundError('message') format - if (NOT_FOUND_ERROR_PATTERN.matcher(error).find()) { - return true; - } - // Check for "Resource [name] not found." format - String expectedPattern = String.format("%s [%s] not found.", resourceType, resourceName); - if (error.startsWith(expectedPattern)) { - return true; - } - // Check if error contains "not found" (case insensitive) - if (error.toLowerCase().contains("not found")) { - return true; - } - } + String error = errorResponse.error(); + // Check for NotFoundError('message') format + if (NOT_FOUND_ERROR_PATTERN.matcher(error).find()) { + return true; + } + // Check for "Resource [name] not found." format + String expectedPattern = String.format("%s [%s] not found.", resourceType, resourceName); + if (error.startsWith(expectedPattern)) { + return true; + } + // Check if error contains "not found" (case insensitive) + if (error.toLowerCase().contains("not found")) { + return true; + } + } catch (JsonProcessingException ex) { // If JSON parsing fails, check the response body directly String expectedPattern = String.format("%s [%s] not found.", resourceType, resourceName); @@ -419,14 +418,14 @@ private String getErrorMessage(HttpStatusCodeException e) { if (StringUtils.hasText(responseBody)) { try { ErrorResponse errorResponse = this.objectMapper.readValue(responseBody, ErrorResponse.class); - // Extract error message from NotFoundError('message') format - Matcher notFoundErrorMatcher = NOT_FOUND_ERROR_PATTERN.matcher(errorResponse.error()); - if (notFoundErrorMatcher.find()) { - return notFoundErrorMatcher.group(1); - } - // Return the error as-is if it doesn't match NotFoundError pattern - return errorResponse.error(); - } + // Extract error message from NotFoundError('message') format + Matcher notFoundErrorMatcher = NOT_FOUND_ERROR_PATTERN.matcher(errorResponse.error()); + if (notFoundErrorMatcher.find()) { + return notFoundErrorMatcher.group(1); + } + // Return the error as-is if it doesn't match NotFoundError pattern + return errorResponse.error(); + } catch (JsonProcessingException ex) { // If JSON parsing fails, return the response body as-is if it's not empty if (StringUtils.hasText(responseBody.trim())) {