Skip to content

Conversation

@jinlee1703
Copy link
Contributor

Summary

When running a similarity search against Azure AI Search, AzureVectorStore injects the computed distance into the document metadata. For results with no metadata, the previous code used Map.of() (immutable), so metadata.put(...) could throw an UnsupportedOperationException.

This PR ensures the metadata map is always mutable before enrichment.

Root Cause

Map<String, Object> metadata = (StringUtils.hasText(entry.metadata()))
        ? JSONObject.parseObject(entry.metadata(), new TypeReference<>() {})
        : Map.of(); // ← immutable
metadata.put(DocumentMetadata.DISTANCE.value(), 1.0 - result.getScore()); // throws

What’s changed

  • Added parseMetadataToMutable(@Nullable String) helper in AzureVectorStore
    • Parses JSON into Map<String,Object> and always returns a new LinkedHashMap<>
    • Falls back to empty LinkedHashMap when input is blank or parsing returns null
    • Logs a warning if parsing fails but does not fail the query path
  • Replaced inline parsing in doSimilaritySearch(...) with the helper and then appends the distance field

Behavior

  • No API change.
  • Similarity results always contain a mutable metadata map, so distance injection no longer fails.
  • If metadata JSON is malformed, results are returned with an empty metadata map (plus a warning log), rather than failing the query.

Tests

  • Add AzureVectorStoreMetadataTests to verify the helper behavior:
    • blank / null / whitespace → returns an empty, mutable map
    • valid JSON → returns a mutable copy and allows enrichment (e.g., distance)

Module

  • vector-stores/spring-ai-azure-store

Related

Fixes #4117

…tance

Avoid UnsupportedOperationException by ensuring metadata is always a LinkedHashMap.
Add `parseMetadataToMutable` helper and use it in similarity search path.

Relates to spring-projects#4117

Signed-off-by: Jinwoo Lee <[email protected]>
Add `AzureVectorStoreMetadataParsingTests` to verify:
- valid JSON → mutable LinkedHashMap copy
- blank/invalid JSON → empty LinkedHashMap
- can inject `distance` without throwing UnsupportedOperationException

These tests fail on the previous implementation and pass with the fix,
guarding against regressions.

Relates to spring-projectsGH-4117

Signed-off-by: Jinwoo Lee <[email protected]>
@jinlee1703 jinlee1703 changed the title Azure AI Search: make metadata mutable in AzureVectorStore to prevent UnsupportedOperationException (Fixes #4117) Azure AI Search: make metadata mutable in AzureVectorStore to prevent UnsupportedOperationException Aug 13, 2025

static Map<String, Object> parseMetadataToMutable(@Nullable String metadataJson) {
if (!StringUtils.hasText(metadataJson)) {
return new LinkedHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason you used a LinkedHashMap?
It seems like a regular HashMap would work just as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the question! I used LinkedHashMap to preserve iteration order so that the parsed JSON key order is kept and the injected distance appears last. This gives deterministic logging/serialization and makes debugging easier. There’s no functional dependency on ordering though—if the project prefers HashMap, I’m happy to switch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it doesn’t affect the outcome, there may not be a significant difference, but since HashMap is more efficient, it seems better to change it to a HashMap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point—updated to use HashMap since ordering isn’t required. The helper now returns a new HashMap<> and tests still pass. PTAL. ✅ (4b44359)

@dev-jonghoonpark
Copy link
Contributor

At the top of the class, please add your name along with the @author tag.

…oMutable

We don’t rely on insertion order for metadata, so a regular HashMap is sufficient.
This change addresses reviewer feedback while still ensuring the returned map is
always mutable by creating a defensive copy or returning an empty instance.

Relates to spring-projects#4117

Signed-off-by: Jinwoo Lee <[email protected]>
@dev-jonghoonpark
Copy link
Contributor

Please add copyright to top of AzureVectorStoreMetadataTests.java

- Add contributor name with @author tag to AzureVectorStore (per reviewer feedback).
- Add brief class-level Javadoc to AzureVectorStoreMetadataTests to clarify purpose.
- No functional changes.

Refs spring-projects#4117.

Signed-off-by: Jinwoo Lee <[email protected]>
Add the standard copyright/license header to `AzureVectorStoreMetadataTests`
to satisfy Spring’s checkstyle and spring-javaformat requirements.
No functional changes.

Signed-off-by: Jinwoo Lee <[email protected]>
@jinlee1703 jinlee1703 changed the title Azure AI Search: make metadata mutable in AzureVectorStore to prevent UnsupportedOperationException Fix: Make metadata mutable in AzureVectorStore (Azure AI Search) Aug 13, 2025
@sobychacko sobychacko merged commit f3962a6 into spring-projects:main Aug 14, 2025
2 checks passed
spring-builds pushed a commit that referenced this pull request Aug 14, 2025
* fix(azure): parse metadata JSON into mutable Map before injecting distance

Avoid UnsupportedOperationException by ensuring metadata is always a LinkedHashMap.
Add `parseMetadataToMutable` helper and use it in similarity search path.

* test(azure): add unit tests for mutable metadata parsing and distance

Add `AzureVectorStoreMetadataParsingTests` to verify:
- valid JSON → mutable LinkedHashMap copy
- blank/invalid JSON → empty LinkedHashMap
- can inject `distance` without throwing UnsupportedOperationException

These tests fail on the previous implementation and pass with the fix,
guarding against regressions.

* refactor(azure): replace LinkedHashMap with HashMap in parseMetadataToMutable

We don’t rely on insertion order for metadata, so a regular HashMap is sufficient.
This change addresses reviewer feedback while still ensuring the returned map is
always mutable by creating a defensive copy or returning an empty instance.

Fixes #4117

Signed-off-by: Jinwoo Lee <[email protected]>
(cherry picked from commit f3962a6)
scionaltera pushed a commit to scionaltera/spring-ai that referenced this pull request Sep 3, 2025
…ing-projects#4131)

* fix(azure): parse metadata JSON into mutable Map before injecting distance

Avoid UnsupportedOperationException by ensuring metadata is always a LinkedHashMap.
Add `parseMetadataToMutable` helper and use it in similarity search path.

* test(azure): add unit tests for mutable metadata parsing and distance

Add `AzureVectorStoreMetadataParsingTests` to verify:
- valid JSON → mutable LinkedHashMap copy
- blank/invalid JSON → empty LinkedHashMap
- can inject `distance` without throwing UnsupportedOperationException

These tests fail on the previous implementation and pass with the fix,
guarding against regressions.

* refactor(azure): replace LinkedHashMap with HashMap in parseMetadataToMutable

We don’t rely on insertion order for metadata, so a regular HashMap is sufficient.
This change addresses reviewer feedback while still ensuring the returned map is
always mutable by creating a defensive copy or returning an empty instance.

Auto-cherry-pick to 1.0.x
Fixes spring-projects#4117

Signed-off-by: Jinwoo Lee <[email protected]>
chedim pushed a commit to couchbaselabs/spring-ai that referenced this pull request Sep 19, 2025
…ing-projects#4131)

* fix(azure): parse metadata JSON into mutable Map before injecting distance

Avoid UnsupportedOperationException by ensuring metadata is always a LinkedHashMap.
Add `parseMetadataToMutable` helper and use it in similarity search path.

* test(azure): add unit tests for mutable metadata parsing and distance

Add `AzureVectorStoreMetadataParsingTests` to verify:
- valid JSON → mutable LinkedHashMap copy
- blank/invalid JSON → empty LinkedHashMap
- can inject `distance` without throwing UnsupportedOperationException

These tests fail on the previous implementation and pass with the fix,
guarding against regressions.

* refactor(azure): replace LinkedHashMap with HashMap in parseMetadataToMutable

We don’t rely on insertion order for metadata, so a regular HashMap is sufficient.
This change addresses reviewer feedback while still ensuring the returned map is
always mutable by creating a defensive copy or returning an empty instance.

Auto-cherry-pick to 1.0.x
Fixes spring-projects#4117

Signed-off-by: Jinwoo Lee <[email protected]>
Willam2004 pushed a commit to Willam2004/spring-ai that referenced this pull request Oct 11, 2025
…ing-projects#4131)

* fix(azure): parse metadata JSON into mutable Map before injecting distance

Avoid UnsupportedOperationException by ensuring metadata is always a LinkedHashMap.
Add `parseMetadataToMutable` helper and use it in similarity search path.

* test(azure): add unit tests for mutable metadata parsing and distance

Add `AzureVectorStoreMetadataParsingTests` to verify:
- valid JSON → mutable LinkedHashMap copy
- blank/invalid JSON → empty LinkedHashMap
- can inject `distance` without throwing UnsupportedOperationException

These tests fail on the previous implementation and pass with the fix,
guarding against regressions.

* refactor(azure): replace LinkedHashMap with HashMap in parseMetadataToMutable

We don’t rely on insertion order for metadata, so a regular HashMap is sufficient.
This change addresses reviewer feedback while still ensuring the returned map is
always mutable by creating a defensive copy or returning an empty instance.

Auto-cherry-pick to 1.0.x
Fixes spring-projects#4117

Signed-off-by: Jinwoo Lee <[email protected]>
Signed-off-by: 家娃 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AzureVectorStore throws UnsupportedOperationException when performing similarity search due to immutable metadata map

3 participants