diff --git a/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java b/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java index d1059711985..42b0d5ed39c 100644 --- a/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java +++ b/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -75,6 +76,7 @@ * @author Josh Long * @author Thomas Vitale * @author Soby Chacko + * @author Jinwoo Lee */ public class AzureVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -239,10 +241,7 @@ public List doSimilaritySearch(SearchRequest request) { final AzureSearchDocument entry = result.getDocument(AzureSearchDocument.class); - Map metadata = (StringUtils.hasText(entry.metadata())) - ? JSONObject.parseObject(entry.metadata(), new TypeReference<>() { - - }) : Map.of(); + Map metadata = parseMetadataToMutable(entry.metadata()); metadata.put(DocumentMetadata.DISTANCE.value(), 1.0 - result.getScore()); @@ -325,6 +324,21 @@ public Optional getNativeClient() { return Optional.of(client); } + static Map parseMetadataToMutable(@Nullable String metadataJson) { + if (!StringUtils.hasText(metadataJson)) { + return new HashMap<>(); + } + try { + Map parsed = JSONObject.parseObject(metadataJson, new TypeReference>() { + }); + return (parsed == null) ? new HashMap<>() : new HashMap<>(parsed); + } + catch (Exception ex) { + logger.warn("Failed to parse metadata JSON. Using empty metadata. json={}", metadataJson, ex); + return new HashMap<>(); + } + } + public record MetadataField(String name, SearchFieldDataType fieldType) { public static MetadataField text(String name) { diff --git a/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreMetadataTests.java b/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreMetadataTests.java new file mode 100644 index 00000000000..3de602a694c --- /dev/null +++ b/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreMetadataTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.vectorstore.azure; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link AzureVectorStore#parseMetadataToMutable(String)}. + * + * @author Jinwoo Lee + */ +class AzureVectorStoreMetadataTests { + + @Test + void returnsMutableMapForBlankOrNull() { + Map m1 = AzureVectorStore.parseMetadataToMutable(null); + m1.put("distance", 0.1); + assertThat(m1).containsEntry("distance", 0.1); + + Map m2 = AzureVectorStore.parseMetadataToMutable(""); + m2.put("distance", 0.2); + assertThat(m2).containsEntry("distance", 0.2); + + Map m3 = AzureVectorStore.parseMetadataToMutable(" "); + m3.put("distance", 0.3); + assertThat(m3).containsEntry("distance", 0.3); + } + + @Test + void wrapsParsedJsonInLinkedHashMapSoItIsMutable() { + Map map = AzureVectorStore.parseMetadataToMutable("{\"k\":\"v\"}"); + assertThat(map).containsEntry("k", "v"); + map.put("distance", 0.4); + assertThat(map).containsEntry("distance", 0.4); + } + +}