Skip to content

Commit 21c2773

Browse files
committed
GH-2137: Add getNativeClient API to VectorStore interface
Fixes: #2137 Adds getNativeClient API to VectorStore interface allowing access to the underlying native client implementation. This change: - Adds getNativeClient() default method to VectorStore interface returning Optional<T> - Implements getNativeClient() in all vector store implementations exposing their respective native clients - Adds integration tests verifying native client access for all implementations Signed-off-by: Soby Chacko <[email protected]>
1 parent 3d3c20d commit 21c2773

File tree

35 files changed

+325
-2
lines changed

35 files changed

+325
-2
lines changed

spring-ai-core/src/main/java/org/springframework/ai/vectorstore/VectorStore.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,24 @@ default List<Document> similaritySearch(String query) {
108108
return this.similaritySearch(SearchRequest.builder().query(query).build());
109109
}
110110

111+
/**
112+
* Returns the native client if available in this vector store implementation.
113+
*
114+
* Note on usage: 1. Returns empty Optional when no native client is available 2. Due
115+
* to Java type erasure, runtime type checking is not possible
116+
*
117+
* Example usage: When working with implementation with known native client:
118+
* Optional<NativeClientType> client = vectorStore.getNativeClient();
119+
*
120+
* Note: Using Optional<?> will return the native client if one exists, rather than an
121+
* empty Optional. For type safety, prefer using the specific client type.
122+
* @return Optional containing native client if available, empty Optional otherwise
123+
* @param <T> The type of the native client
124+
*/
125+
default <T> Optional<T> getNativeClient() {
126+
return Optional.empty();
127+
}
128+
111129
/**
112130
* Builder interface for creating VectorStore instances. Implements a fluent builder
113131
* pattern for configuring observation-related settings.

vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,13 @@ public VectorStoreObservationContext.Builder createObservationContextBuilder(Str
372372
.similarityMetric("cosine");
373373
}
374374

375+
@Override
376+
public <T> Optional<T> getNativeClient() {
377+
@SuppressWarnings("unchecked")
378+
T client = (T) this.container;
379+
return Optional.of(client);
380+
}
381+
375382
/**
376383
* Builder class for creating {@link CosmosDBVectorStore} instances.
377384
* <p>

vector-stores/spring-ai-azure-cosmos-db-store/src/test/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStoreIT.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,9 +19,11 @@
1919
import java.util.HashMap;
2020
import java.util.List;
2121
import java.util.Map;
22+
import java.util.Optional;
2223
import java.util.UUID;
2324

2425
import com.azure.cosmos.CosmosAsyncClient;
26+
import com.azure.cosmos.CosmosAsyncContainer;
2527
import com.azure.cosmos.CosmosClientBuilder;
2628
import org.junit.jupiter.api.BeforeEach;
2729
import org.junit.jupiter.api.Test;
@@ -169,6 +171,15 @@ void testSimilaritySearchWithFilter() {
169171
assertThat(results4).isEmpty();
170172
}
171173

174+
@Test
175+
void getNativeClientTest() {
176+
this.contextRunner.run(context -> {
177+
CosmosDBVectorStore vectorStore = context.getBean(CosmosDBVectorStore.class);
178+
Optional<CosmosAsyncContainer> nativeClient = vectorStore.getNativeClient();
179+
assertThat(nativeClient).isPresent();
180+
});
181+
}
182+
172183
@SpringBootConfiguration
173184
@EnableAutoConfiguration
174185
public static class TestApplication {

vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,13 @@ public VectorStoreObservationContext.Builder createObservationContextBuilder(Str
332332
.similarityMetric(this.initializeSchema ? VectorStoreSimilarityMetric.COSINE.value() : null);
333333
}
334334

335+
@Override
336+
public <T> Optional<T> getNativeClient() {
337+
@SuppressWarnings("unchecked")
338+
T client = (T) this.searchClient;
339+
return Optional.of(client);
340+
}
341+
335342
public record MetadataField(String name, SearchFieldDataType fieldType) {
336343

337344
public static MetadataField text(String name) {

vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreIT.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,10 +23,12 @@
2323
import java.util.Date;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Optional;
2627
import java.util.UUID;
2728
import java.util.concurrent.TimeUnit;
2829

2930
import com.azure.core.credential.AzureKeyCredential;
31+
import com.azure.search.documents.SearchClient;
3032
import com.azure.search.documents.indexes.SearchIndexClient;
3133
import com.azure.search.documents.indexes.SearchIndexClientBuilder;
3234
import org.awaitility.Awaitility;
@@ -318,6 +320,15 @@ public void searchThresholdTest() {
318320
});
319321
}
320322

323+
@Test
324+
void getNativeClientTest() {
325+
this.contextRunner.run(context -> {
326+
AzureVectorStore vectorStore = context.getBean(AzureVectorStore.class);
327+
Optional<SearchClient> nativeClient = vectorStore.getNativeClient();
328+
assertThat(nativeClient).isPresent();
329+
});
330+
}
331+
321332
@SpringBootConfiguration
322333
@EnableAutoConfiguration
323334
public static class Config {

vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/vectorstore/cassandra/CassandraVectorStore.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,13 @@ private void ensureTableColumnsExist(int vectorDimension) {
712712
}
713713
}
714714

715+
@Override
716+
public <T> Optional<T> getNativeClient() {
717+
@SuppressWarnings("unchecked")
718+
T client = (T) this.session;
719+
return Optional.of(client);
720+
}
721+
715722
/**
716723
* Indexes are automatically created with COSINE. This can be changed manually via
717724
* cqlsh

vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraVectorStoreIT.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Collections;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Optional;
2425
import java.util.UUID;
2526
import java.util.stream.Collectors;
2627

@@ -524,6 +525,15 @@ void deleteWithComplexFilterExpression() {
524525
});
525526
}
526527

528+
@Test
529+
void getNativeClientTest() {
530+
this.contextRunner.run(context -> {
531+
CassandraVectorStore vectorStore = context.getBean(CassandraVectorStore.class);
532+
Optional<CqlSession> nativeClient = vectorStore.getNativeClient();
533+
assertThat(nativeClient).isPresent();
534+
});
535+
}
536+
527537
@SpringBootConfiguration
528538
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
529539
public static class TestApplication {

vector-stores/spring-ai-coherence-store/src/main/java/org/springframework/ai/vectorstore/coherence/CoherenceVectorStore.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ public VectorStoreObservationContext.Builder createObservationContextBuilder(Str
275275
.dimensions(this.embeddingModel.dimensions());
276276
}
277277

278+
@Override
279+
public <T> Optional<T> getNativeClient() {
280+
@SuppressWarnings("unchecked")
281+
T client = (T) this.session;
282+
return Optional.of(client);
283+
}
284+
278285
/**
279286
* Builder class for creating {@link CoherenceVectorStore} instances.
280287
* <p>

vector-stores/spring-ai-coherence-store/src/test/java/org/springframework/ai/vectorstore/coherence/CoherenceVectorStoreIT.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Iterator;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Optional;
2627
import java.util.UUID;
2728
import java.util.stream.Stream;
2829

@@ -290,6 +291,15 @@ public void searchWithThreshold() {
290291
});
291292
}
292293

294+
@Test
295+
void getNativeClientTest() {
296+
this.contextRunner.run(context -> {
297+
CoherenceVectorStore vectorStore = context.getBean(CoherenceVectorStore.class);
298+
Optional<Session> nativeClient = vectorStore.getNativeClient();
299+
assertThat(nativeClient).isPresent();
300+
});
301+
}
302+
293303
private static boolean isSortedByDistance(final List<Document> documents) {
294304
final List<Double> distances = documents.stream()
295305
.map(doc -> (Double) doc.getMetadata().get(DocumentMetadata.DISTANCE.value()))

vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,13 @@ private String getSimilarityMetric() {
354354
return SIMILARITY_TYPE_MAPPING.get(this.options.getSimilarity()).value();
355355
}
356356

357+
@Override
358+
public <T> Optional<T> getNativeClient() {
359+
@SuppressWarnings("unchecked")
360+
T client = (T) this.elasticsearchClient;
361+
return Optional.of(client);
362+
}
363+
357364
/**
358365
* Creates a new builder instance for ElasticsearchVectorStore.
359366
* @return a new ElasticsearchBuilder instance

0 commit comments

Comments
 (0)