Skip to content

Commit 5f0123c

Browse files
Kirbstompertzolov
authored andcommitted
Add MongoDB Atlas Vector store
- Add VectorSearchAggregation used to actually preform the search on a given collection with embeddings. - add MongoDBVectorStore - Add MongoDBVectorStoreIT. Integration test runs fine given... - You have a mongo atlas cluster to connect to (local or remote) - You have the search index "spring_ai_vector_search" setup correctly - Need to explore getting around this - Need to filter results using threshold - Add postfilter for threshold values - While a post filter is not ideal, it gets the job done. The mongo team seems to be working on having it availible as a prefilter option, in which this implementation can be updated to use later. - implement filtering threshold - fix a few sonar issues - formatting - use higher default num_candidates - use builder for configuration - add documentation and some refactor - use consistent property in integration test - finish implementing filter support - add documentation to filter converter - add vector search index auto creation - Add to BOM. - Fix version to 1.0.0-SN. - Move expresion converter from core to models/mongodb. - Fix style and license headers
1 parent 9c19dc1 commit 5f0123c

File tree

10 files changed

+919
-0
lines changed

10 files changed

+919
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<module>spring-ai-spring-boot-starters/spring-ai-starter-bedrock-ai</module>
6161
<module>spring-ai-spring-boot-starters/spring-ai-starter-mistral-ai</module>
6262
<module>spring-ai-retry</module>
63+
<module>vector-stores/spring-ai-mongodb-atlas-store</module>
6364
</modules>
6465

6566
<organization>

spring-ai-bom/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@
174174
<version>${project.version}</version>
175175
</dependency>
176176

177+
<dependency>
178+
<groupId>org.springframework.ai</groupId>
179+
<artifactId>spring-ai-mongodb-atlas-store</artifactId>
180+
<version>${project.version}</version>
181+
</dependency>
182+
177183
<!-- Utilities -->
178184
<dependency>
179185
<groupId>org.springframework.ai</groupId>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.ai</groupId>
7+
<artifactId>spring-ai</artifactId>
8+
<version>1.0.0-SNAPSHOT</version>
9+
<relativePath>../../pom.xml</relativePath>
10+
</parent>
11+
<artifactId>spring-ai-mongodb-atlas-store</artifactId>
12+
<packaging>jar</packaging>
13+
<name>Spring AI MongoDB Atlas Vector Store</name>
14+
<description>Spring AI MongoDB Atlas Vector Store</description>
15+
<url>https://github.com/spring-projects-experimental/spring-ai</url>
16+
17+
<scm>
18+
<url>https://github.com/spring-projects/spring-ai</url>
19+
<connection>git://github.com/spring-projects/spring-ai.git</connection>
20+
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
21+
</scm>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>org.springframework.ai</groupId>
26+
<artifactId>spring-ai-core</artifactId>
27+
<version>${parent.version}</version>
28+
</dependency>
29+
<!-- MongoDB -->
30+
<dependency>
31+
<groupId>org.springframework.data</groupId>
32+
<artifactId>spring-data-mongodb</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.mongodb</groupId>
36+
<artifactId>mongodb-driver-sync</artifactId>
37+
</dependency>
38+
39+
<!-- TESTING -->
40+
<dependency>
41+
<groupId>org.springframework.ai</groupId>
42+
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
43+
<version>${parent.version}</version>
44+
<scope>test</scope>
45+
</dependency>
46+
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-test</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.testcontainers</groupId>
54+
<artifactId>junit-jupiter</artifactId>
55+
<version>${testcontainers.version}</version>
56+
<scope>test</scope>
57+
</dependency>
58+
</dependencies>
59+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2023 - 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.ai.vectorstore;
17+
18+
import org.springframework.ai.vectorstore.filter.Filter;
19+
import org.springframework.ai.vectorstore.filter.converter.AbstractFilterExpressionConverter;
20+
21+
import static org.springframework.ai.vectorstore.filter.Filter.ExpressionType.AND;
22+
import static org.springframework.ai.vectorstore.filter.Filter.ExpressionType.OR;
23+
24+
/**
25+
* Converts {@link Filter.Expression} into MongDB Atlas metadata filter expression format.
26+
* (https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#std-label-vectorSearch-agg-pipeline-filter)
27+
*
28+
* @author Chris Smith
29+
* @since 1.0.0
30+
*/
31+
public class MongoDBAtlasFilterExpressionConverter extends AbstractFilterExpressionConverter {
32+
33+
@Override
34+
protected void doExpression(Filter.Expression expression, StringBuilder context) {
35+
// Handling AND/OR
36+
if (AND.equals(expression.type()) || OR.equals(expression.type())) {
37+
doCompoundExpressionType(expression, context);
38+
}
39+
else {
40+
doSingleExpressionType(expression, context);
41+
}
42+
}
43+
44+
private void doCompoundExpressionType(Filter.Expression expression, StringBuilder context) {
45+
context.append("{");
46+
context.append(getOperationSymbol(expression));
47+
context.append(":[");
48+
this.convertOperand(expression.left(), context);
49+
context.append(",");
50+
this.convertOperand(expression.right(), context);
51+
context.append("]}");
52+
}
53+
54+
private void doSingleExpressionType(Filter.Expression expression, StringBuilder context) {
55+
context.append("{");
56+
this.convertOperand(expression.left(), context);
57+
context.append(":{");
58+
context.append(getOperationSymbol(expression));
59+
context.append(":");
60+
this.convertOperand(expression.right(), context);
61+
context.append("}}");
62+
}
63+
64+
private String getOperationSymbol(Filter.Expression exp) {
65+
switch (exp.type()) {
66+
case AND:
67+
return "$and";
68+
case OR:
69+
return "$or";
70+
case EQ:
71+
return "$eq";
72+
case NE:
73+
return "$ne";
74+
case LT:
75+
return "$lt";
76+
case LTE:
77+
return "$lte";
78+
case GT:
79+
return "$gt";
80+
case GTE:
81+
return "$gte";
82+
case IN:
83+
return "$in";
84+
case NIN:
85+
return "$nin";
86+
default:
87+
throw new RuntimeException("Not supported expression type:" + exp.type());
88+
}
89+
}
90+
91+
@Override
92+
protected void doKey(Filter.Key filterKey, StringBuilder context) {
93+
var identifier = (hasOuterQuotes(filterKey.key())) ? removeOuterQuotes(filterKey.key()) : filterKey.key();
94+
context.append("\"metadata." + identifier + "\"");
95+
}
96+
97+
}

0 commit comments

Comments
 (0)