Skip to content

Commit a446242

Browse files
Add support for optional keyless authentication.
Implemented a new configuration property 'useKeylessAuth' to toggle between API key and Azure default credential authentication. Added necessary dependencies and updated tests to reflect the new changes. co-authored-by: mattgotteiner <mattgotteiner@users.noreply.github.com>
1 parent a7b5652 commit a446242

File tree

6 files changed

+64
-41
lines changed

6 files changed

+64
-41
lines changed

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@
213213
<gemfire.testcontainers.version>2.3.0</gemfire.testcontainers.version>
214214
<pinecone.version>0.8.0</pinecone.version>
215215
<fastjson.version>2.0.46</fastjson.version>
216+
<azure-core.version>1.53.0</azure-core.version>
217+
<azure-json.version>1.3.0</azure-json.version>
218+
<azure-identity.version>1.14.0</azure-identity.version>
219+
216220
<azure-search.version>11.6.1</azure-search.version>
217221
<azure-cosmos.version>5.17.1</azure-cosmos.version>
218222
<weaviate-client.version>4.5.1</weaviate-client.version>

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/azure.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ You can use the following properties in your Spring Boot configuration to custom
101101

102102
|`spring.ai.vectorstore.azure.url`|
103103
|`spring.ai.vectorstore.azure.api-key`|
104+
|`spring.ai.vectorstore.azure.useKeylessAuth`|false
104105
|`spring.ai.vectorstore.azure.initialize-schema`|false
105106
|`spring.ai.vectorstore.azure.index-name`|spring_ai_azure_vector_store
106107
|`spring.ai.vectorstore.azure.default-top-k`|4

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreAutoConfiguration.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.azure.core.credential.AzureKeyCredential;
2222
import com.azure.core.util.ClientOptions;
23+
import com.azure.identity.DefaultAzureCredentialBuilder;
2324
import com.azure.search.documents.indexes.SearchIndexClient;
2425
import com.azure.search.documents.indexes.SearchIndexClientBuilder;
2526
import io.micrometer.observation.ObservationRegistry;
@@ -56,10 +57,18 @@ public class AzureVectorStoreAutoConfiguration {
5657
public SearchIndexClient searchIndexClient(AzureVectorStoreProperties properties) {
5758
ClientOptions clientOptions = new ClientOptions();
5859
clientOptions.setApplicationId(APPLICATION_ID);
59-
return new SearchIndexClientBuilder().endpoint(properties.getUrl())
60-
.credential(new AzureKeyCredential(properties.getApiKey()))
61-
.clientOptions(clientOptions)
62-
.buildClient();
60+
if (properties.isUseKeylessAuth()) {
61+
return new SearchIndexClientBuilder().endpoint(properties.getUrl())
62+
.credential(new DefaultAzureCredentialBuilder().build())
63+
.clientOptions(clientOptions)
64+
.buildClient();
65+
}
66+
else {
67+
return new SearchIndexClientBuilder().endpoint(properties.getUrl())
68+
.credential(new AzureKeyCredential(properties.getApiKey()))
69+
.clientOptions(clientOptions)
70+
.buildClient();
71+
}
6372
}
6473

6574
@Bean

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreProperties.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public class AzureVectorStoreProperties extends CommonVectorStoreProperties {
4040

4141
private double defaultSimilarityThreshold = -1;
4242

43+
private boolean useKeylessAuth;
44+
4345
public String getUrl() {
4446
return this.url;
4547
}
@@ -80,4 +82,12 @@ public void setDefaultSimilarityThreshold(double defaultSimilarityThreshold) {
8082
this.defaultSimilarityThreshold = defaultSimilarityThreshold;
8183
}
8284

85+
public boolean isUseKeylessAuth() {
86+
return this.useKeylessAuth;
87+
}
88+
89+
public void setUseKeylessAuth(boolean useKeylessAuth) {
90+
this.useKeylessAuth = useKeylessAuth;
91+
}
92+
8393
}

vector-stores/spring-ai-azure-store/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@
5959
</exclusion>
6060
</exclusions>
6161
</dependency>
62+
<!-- https://mvnrepository.com/artifact/com.azure/azure-identity -->
63+
<dependency>
64+
<groupId>com.azure</groupId>
65+
<artifactId>azure-identity</artifactId>
66+
<version>${azure-identity.version}</version>
67+
</dependency>
68+
<!-- https://mvnrepository.com/artifact/com.azure/azure-core -->
69+
<dependency>
70+
<groupId>com.azure</groupId>
71+
<artifactId>azure-core</artifactId>
72+
<version>${azure-core.version}</version>
73+
</dependency>
74+
<!-- https://mvnrepository.com/artifact/com.azure/azure-json -->
75+
<dependency>
76+
<groupId>com.azure</groupId>
77+
<artifactId>azure-json</artifactId>
78+
<version>${azure-json.version}</version>
79+
</dependency>
6280
<dependency>
6381
<groupId>com.alibaba.fastjson2</groupId>
6482
<artifactId>fastjson2</artifactId>

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

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@
4444
*/
4545
public class AzureAiSearchFilterExpressionConverterTests {
4646

47-
private static String format(String text) {
48-
return text.trim().replace(" " + System.lineSeparator(), System.lineSeparator()) + System.lineSeparator();
49-
}
50-
5147
@Test
5248
public void testMissingFilterName() {
5349

@@ -79,10 +75,9 @@ public void testEQ() {
7975
List.of(MetadataField.text("country")));
8076

8177
// country == "BG"
78+
String expected = "meta_country eq 'BG'";
8279
String vectorExpr = converter.convertExpression(new Expression(EQ, new Key("country"), new Value("BG")));
83-
assertThat(format(vectorExpr)).isEqualTo("""
84-
meta_country eq 'BG'
85-
""");
80+
assertThat(vectorExpr).isEqualTo(expected);
8681
}
8782

8883
@Test
@@ -91,12 +86,11 @@ public void tesEqAndGte() {
9186
List.of(MetadataField.text("genre"), MetadataField.int32("year")));
9287

9388
// genre == "drama" AND year >= 2020
89+
String expected = "meta_genre eq 'drama' and meta_year ge 2020";
9490
String vectorExpr = converter
9591
.convertExpression(new Expression(AND, new Expression(EQ, new Key("genre"), new Value("drama")),
9692
new Expression(GTE, new Key("year"), new Value(2020))));
97-
assertThat(format(vectorExpr)).isEqualTo("""
98-
meta_genre eq 'drama' and meta_year ge 2020
99-
""");
93+
assertThat(vectorExpr).isEqualTo(expected);
10094
}
10195

10296
@Test
@@ -105,11 +99,10 @@ public void tesIn() {
10599
List.of(MetadataField.text("genre")));
106100

107101
// genre in ["comedy", "documentary", "drama"]
102+
String expected = " search.in(meta_genre, 'comedy,documentary,drama', ',')";
108103
String vectorExpr = converter.convertExpression(
109104
new Expression(IN, new Key("genre"), new Value(List.of("comedy", "documentary", "drama"))));
110-
assertThat(format(vectorExpr)).isEqualTo("""
111-
search.in(meta_genre, 'comedy,documentary,drama', ',')
112-
""");
105+
assertThat(vectorExpr).isEqualTo(expected);
113106
}
114107

115108
@Test
@@ -118,11 +111,10 @@ public void tesNin() {
118111
List.of(MetadataField.text("genre")));
119112

120113
// genre in ["comedy", "documentary", "drama"]
114+
String expected = " not search.in(meta_genre, 'comedy,documentary,drama', ',')";
121115
String vectorExpr = converter.convertExpression(
122116
new Expression(NIN, new Key("genre"), new Value(List.of("comedy", "documentary", "drama"))));
123-
assertThat(format(vectorExpr)).isEqualTo("""
124-
not search.in(meta_genre, 'comedy,documentary,drama', ',')
125-
""");
117+
assertThat(vectorExpr).isEqualTo(expected);
126118
}
127119

128120
@Test
@@ -131,13 +123,12 @@ public void testNe() {
131123
List.of(MetadataField.text("city"), MetadataField.int64("year"), MetadataField.text("country")));
132124

133125
// year >= 2020 OR country == "BG" AND city != "Sofia"
126+
String expected = "meta_year ge 2020 or meta_country eq 'BG' and meta_city ne 'Sofia'";
134127
String vectorExpr = converter
135128
.convertExpression(new Expression(OR, new Expression(GTE, new Key("year"), new Value(2020)),
136129
new Expression(AND, new Expression(EQ, new Key("country"), new Value("BG")),
137130
new Expression(NE, new Key("city"), new Value("Sofia")))));
138-
assertThat(format(vectorExpr)).isEqualTo("""
139-
meta_year ge 2020 or meta_country eq 'BG' and meta_city ne 'Sofia'
140-
""");
131+
assertThat(vectorExpr).isEqualTo(expected);
141132
}
142133

143134
@Test
@@ -146,14 +137,12 @@ public void testGroup() {
146137
List.of(MetadataField.text("city"), MetadataField.int64("year"), MetadataField.text("country")));
147138

148139
// (year >= 2020 OR country == "BG") AND city != "Sofia"
140+
String expected = "(meta_year ge 2020 or meta_country eq 'BG') and meta_city ne 'Sofia'";
149141
String vectorExpr = converter.convertExpression(new Expression(AND,
150142
new Group(new Expression(OR, new Expression(GTE, new Key("year"), new Value(2020)),
151143
new Expression(EQ, new Key("country"), new Value("BG")))),
152144
new Expression(NE, new Key("city"), new Value("Sofia"))));
153-
154-
assertThat(format(vectorExpr)).isEqualTo("""
155-
(meta_year ge 2020 or meta_country eq 'BG') and meta_city ne 'Sofia'
156-
""");
145+
assertThat(vectorExpr).isEqualTo(expected);
157146
}
158147

159148
@Test
@@ -162,14 +151,12 @@ public void tesBoolean() {
162151
List.of(MetadataField.bool("isOpen"), MetadataField.int64("year"), MetadataField.text("country")));
163152

164153
// isOpen == true AND year >= 2020 AND country IN ["BG", "NL", "US"]
154+
String expected = "meta_isOpen eq true and meta_year ge 2020 and search.in(meta_country, 'BG,NL,US', ',')";
165155
String vectorExpr = converter.convertExpression(new Expression(AND,
166156
new Expression(AND, new Expression(EQ, new Key("isOpen"), new Value(true)),
167157
new Expression(GTE, new Key("year"), new Value(2020))),
168158
new Expression(IN, new Key("country"), new Value(List.of("BG", "NL", "US")))));
169-
170-
assertThat(format(vectorExpr)).isEqualTo("""
171-
meta_isOpen eq true and meta_year ge 2020 and search.in(meta_country, 'BG,NL,US', ',')
172-
""");
159+
assertThat(vectorExpr).isEqualTo(expected);
173160
}
174161

175162
@Test
@@ -181,27 +168,21 @@ public void testDecimal() {
181168
String vectorExpr = converter
182169
.convertExpression(new Expression(AND, new Expression(GTE, new Key("temperature"), new Value(-15.6)),
183170
new Expression(LTE, new Key("temperature"), new Value(20.13))));
184-
185-
assertThat(format(vectorExpr)).isEqualTo("""
186-
meta_temperature ge -15.6 and meta_temperature le 20.13
187-
""");
171+
String expected = "meta_temperature ge -15.6 and meta_temperature le 20.13";
188172
}
189173

190174
@Test
191175
public void testComplexIdentifiers() {
192176
FilterExpressionConverter converter = new AzureAiSearchFilterExpressionConverter(
193177
List.of(MetadataField.text("country 1 2 3")));
194178

179+
String expected = "'meta_country 1 2 3' eq 'BG'";
195180
String vectorExpr = converter
196181
.convertExpression(new Expression(EQ, new Key("\"country 1 2 3\""), new Value("BG")));
197-
assertThat(format(vectorExpr)).isEqualTo("""
198-
'meta_country 1 2 3' eq 'BG'
199-
""");
182+
assertThat(vectorExpr).isEqualTo(expected);
200183

201184
vectorExpr = converter.convertExpression(new Expression(EQ, new Key("'country 1 2 3'"), new Value("BG")));
202-
assertThat(format(vectorExpr)).isEqualTo("""
203-
'meta_country 1 2 3' eq 'BG'
204-
""");
185+
assertThat(vectorExpr).isEqualTo(expected);
205186
}
206187

207188
}

0 commit comments

Comments
 (0)