2828import com .fasterxml .jackson .core .JsonProcessingException ;
2929import com .fasterxml .jackson .databind .ObjectMapper ;
3030
31+ import org .slf4j .Logger ;
32+ import org .slf4j .LoggerFactory ;
33+
3134import org .springframework .ai .chroma .vectorstore .ChromaApi .QueryRequest .Include ;
3235import org .springframework .ai .chroma .vectorstore .common .ChromaApiConstants ;
3336import org .springframework .ai .util .json .JsonParser ;
5255 */
5356public class ChromaApi {
5457
58+ private static final Logger logger = LoggerFactory .getLogger (ChromaApi .class );
59+
5560 public static Builder builder () {
5661 return new Builder ();
5762 }
@@ -62,6 +67,9 @@ public static Builder builder() {
6267 // Regular expression pattern that looks for a message.
6368 private static final Pattern MESSAGE_ERROR_PATTERN = Pattern .compile ("\" message\" :\" (.*?)\" " );
6469
70+ // Regular expression pattern that looks for NotFoundError in JSON error response.
71+ private static final Pattern NOT_FOUND_ERROR_PATTERN = Pattern .compile ("NotFoundError\\ ('([^']*)'\\ )" );
72+
6573 private static final String X_CHROMA_TOKEN_NAME = "x-chroma-token" ;
6674
6775 private final ObjectMapper objectMapper ;
@@ -136,12 +144,21 @@ public Tenant getTenant(String tenantName) {
136144 .retrieve ()
137145 .body (Tenant .class );
138146 }
139- catch (HttpServerErrorException | HttpClientErrorException e ) {
140- String msg = this .getErrorMessage (e );
141- if (String .format ("Tenant [%s] not found" , tenantName ).equals (msg )) {
147+ catch (HttpClientErrorException e ) {
148+ if (isNotFoundError (e , "Tenant" , tenantName )) {
149+ String errorMessage = this .getErrorMessage (e );
150+ if (StringUtils .hasText (errorMessage )) {
151+ logger .debug ("Tenant [{}] does not exist: {}, returning null" , tenantName , errorMessage );
152+ }
153+ else {
154+ logger .debug ("Tenant [{}] does not exist, returning null" , tenantName );
155+ }
142156 return null ;
143157 }
144- throw new RuntimeException (msg , e );
158+ throw new RuntimeException (this .getErrorMessage (e ), e );
159+ }
160+ catch (HttpServerErrorException e ) {
161+ throw new RuntimeException (this .getErrorMessage (e ), e );
145162 }
146163 }
147164
@@ -165,12 +182,23 @@ public Database getDatabase(String tenantName, String databaseName) {
165182 .retrieve ()
166183 .body (Database .class );
167184 }
168- catch (HttpServerErrorException | HttpClientErrorException e ) {
169- String msg = this .getErrorMessage (e );
170- if (msg .startsWith (String .format ("Database [%s] not found." , databaseName ))) {
185+ catch (HttpClientErrorException e ) {
186+ if (isNotFoundError (e , "Database" , databaseName )) {
187+ String errorMessage = this .getErrorMessage (e );
188+ if (StringUtils .hasText (errorMessage )) {
189+ logger .debug ("Database [{}] in tenant [{}] does not exist: {}, returning null" , databaseName ,
190+ tenantName , errorMessage );
191+ }
192+ else {
193+ logger .debug ("Database [{}] in tenant [{}] does not exist, returning null" , databaseName ,
194+ tenantName );
195+ }
171196 return null ;
172197 }
173- throw new RuntimeException (msg , e );
198+ throw new RuntimeException (this .getErrorMessage (e ), e );
199+ }
200+ catch (HttpServerErrorException e ) {
201+ throw new RuntimeException (this .getErrorMessage (e ), e );
174202 }
175203 }
176204
@@ -226,12 +254,24 @@ public Collection getCollection(String tenantName, String databaseName, String c
226254 .retrieve ()
227255 .body (Collection .class );
228256 }
229- catch (HttpServerErrorException | HttpClientErrorException e ) {
230- String msg = this .getErrorMessage (e );
231- if (String .format ("Collection [%s] does not exists" , collectionName ).equals (msg )) {
257+ catch (HttpClientErrorException e ) {
258+ if (isNotFoundError (e , "Collection" , collectionName )) {
259+ String errorMessage = this .getErrorMessage (e );
260+ if (StringUtils .hasText (errorMessage )) {
261+ logger .debug (
262+ "Collection [{}] in database [{}] and tenant [{}] does not exist: {}, returning null" ,
263+ collectionName , databaseName , tenantName , errorMessage );
264+ }
265+ else {
266+ logger .debug ("Collection [{}] in database [{}] and tenant [{}] does not exist, returning null" ,
267+ collectionName , databaseName , tenantName );
268+ }
232269 return null ;
233270 }
234- throw new RuntimeException (msg , e );
271+ throw new RuntimeException (this .getErrorMessage (e ), e );
272+ }
273+ catch (HttpServerErrorException e ) {
274+ throw new RuntimeException (this .getErrorMessage (e ), e );
235275 }
236276 }
237277
@@ -326,7 +366,76 @@ private void httpHeaders(HttpHeaders headers) {
326366 }
327367 }
328368
369+ private boolean isNotFoundError (HttpClientErrorException e , String resourceType , String resourceName ) {
370+ // First, check the response body for JSON error response
371+ String responseBody = e .getResponseBodyAsString ();
372+ if (StringUtils .hasText (responseBody )) {
373+ try {
374+ ErrorResponse errorResponse = this .objectMapper .readValue (responseBody , ErrorResponse .class );
375+ String error = errorResponse .error ();
376+ // Check for NotFoundError('message') format
377+ if (NOT_FOUND_ERROR_PATTERN .matcher (error ).find ()) {
378+ return true ;
379+ }
380+ // Check for "Resource [name] not found." format
381+ String expectedPattern = String .format ("%s [%s] not found." , resourceType , resourceName );
382+ if (error .startsWith (expectedPattern )) {
383+ return true ;
384+ }
385+ // Check if error contains "not found" (case insensitive)
386+ if (error .toLowerCase ().contains ("not found" )) {
387+ return true ;
388+ }
389+ }
390+ catch (JsonProcessingException ex ) {
391+ // If JSON parsing fails, check the response body directly
392+ String expectedPattern = String .format ("%s [%s] not found." , resourceType , resourceName );
393+ if (responseBody .contains (expectedPattern ) || responseBody .toLowerCase ().contains ("not found" )) {
394+ return true ;
395+ }
396+ }
397+ }
398+
399+ // Check the exception message
400+ String errorMessage = e .getMessage ();
401+ if (StringUtils .hasText (errorMessage )) {
402+ // Check for "Resource [name] not found." format
403+ String expectedPattern = String .format ("%s [%s] not found." , resourceType , resourceName );
404+ if (errorMessage .contains (expectedPattern )) {
405+ return true ;
406+ }
407+ // Check if error message contains "not found" (case insensitive)
408+ if (errorMessage .toLowerCase ().contains ("not found" )) {
409+ return true ;
410+ }
411+ }
412+
413+ return false ;
414+ }
415+
329416 private String getErrorMessage (HttpStatusCodeException e ) {
417+ // First, try to parse the response body as JSON error response
418+ String responseBody = e .getResponseBodyAsString ();
419+ if (StringUtils .hasText (responseBody )) {
420+ try {
421+ ErrorResponse errorResponse = this .objectMapper .readValue (responseBody , ErrorResponse .class );
422+ // Extract error message from NotFoundError('message') format
423+ Matcher notFoundErrorMatcher = NOT_FOUND_ERROR_PATTERN .matcher (errorResponse .error ());
424+ if (notFoundErrorMatcher .find ()) {
425+ return notFoundErrorMatcher .group (1 );
426+ }
427+ // Return the error as-is if it doesn't match NotFoundError pattern
428+ return errorResponse .error ();
429+ }
430+ catch (JsonProcessingException ex ) {
431+ // If JSON parsing fails, return the response body as-is if it's not empty
432+ if (StringUtils .hasText (responseBody .trim ())) {
433+ return responseBody .trim ();
434+ }
435+ logger .debug ("Failed to parse error response as JSON, falling back to exception message" , ex );
436+ }
437+ }
438+
330439 var errorMessage = e .getMessage ();
331440
332441 // If the error message is empty or null, return an empty string
@@ -622,6 +731,15 @@ private static class CollectionList extends ArrayList<Collection> {
622731
623732 }
624733
734+ /**
735+ * Chroma API error response.
736+ *
737+ * @param error The error message from Chroma API.
738+ */
739+ @ JsonInclude (JsonInclude .Include .NON_NULL )
740+ private record ErrorResponse (@ JsonProperty ("error" ) String error ) {
741+ }
742+
625743 public static final class Builder {
626744
627745 private String baseUrl = ChromaApiConstants .DEFAULT_BASE_URL ;
0 commit comments