Add PGVector VectorStore implementation
* Enable PGVector indexing support * Add same document ID vector update support * Add documentation for VectorStores
This commit is contained in:
committed by
Mark Pollack
parent
0218069613
commit
5a63cd840c
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,10 +26,11 @@ out
|
||||
/.gradletasknamecache
|
||||
**/*.flattened-pom.xml
|
||||
|
||||
vscode
|
||||
vscode
|
||||
settings.json
|
||||
|
||||
node
|
||||
node_modules
|
||||
package-lock.json
|
||||
package.json
|
||||
.vscode
|
||||
@@ -149,10 +149,10 @@ To build with only unit tests
|
||||
```
|
||||
|
||||
To build including integration tests.
|
||||
Set API key environment variables for OpenAI and Azure OpenAI before running.
|
||||
Set API key environment variables for OpenAI and Azure OpenAI before running.
|
||||
|
||||
```shell
|
||||
./mvnw clean package -Pintegration-tests
|
||||
./mvnw clean verify -Pintegration-tests
|
||||
```
|
||||
|
||||
To build the docs
|
||||
|
||||
11
pom.xml
11
pom.xml
@@ -19,6 +19,7 @@
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-openai</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-azure-openai</module>
|
||||
<module>spring-ai-docs</module>
|
||||
<module>vector-stores/spring-ai-pgvector-store</module>
|
||||
</modules>
|
||||
|
||||
<organization>
|
||||
@@ -61,7 +62,7 @@
|
||||
<java.version>17</java.version>
|
||||
|
||||
|
||||
<!-- prodution dependencies -->
|
||||
<!-- production dependencies -->
|
||||
<spring-boot.version>3.1.2</spring-boot.version>
|
||||
<stringtemplate.version>4.0.2</stringtemplate.version>
|
||||
<open-ai-client.version>0.12.0</open-ai-client.version>
|
||||
@@ -127,9 +128,6 @@
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
<configuration>
|
||||
<argLine>${surefireArgLine}</argLine>
|
||||
<excludes>
|
||||
<exclude>**/*IntegrationTests.java</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -247,11 +245,6 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${maven-failsafe-plugin.version}</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*IntegrationTests.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.experimental.ai</groupId>
|
||||
@@ -18,6 +19,10 @@
|
||||
<developerConnection>git@github.com:spring-projects-experimental/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<jsonschema.version>4.31.1</jsonschema.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- production dependencies -->
|
||||
@@ -46,21 +51,15 @@
|
||||
<dependency>
|
||||
<groupId>com.github.victools</groupId>
|
||||
<artifactId>jsonschema-generator</artifactId>
|
||||
<version>4.31.1</version>
|
||||
<version>${jsonschema.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.victools</groupId>
|
||||
<artifactId>jsonschema-module-jackson</artifactId>
|
||||
<version>4.31.1</version>
|
||||
<version>${jsonschema.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.kjetland</groupId>-->
|
||||
<!-- <artifactId>mbknor-jackson-jsonschema_2.13</artifactId>-->
|
||||
<!-- <version>1.0.39</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -6,14 +6,10 @@ import java.util.*;
|
||||
|
||||
public class Document {
|
||||
|
||||
private static String DEFAULT_TEXT_TEMPLATE = "{metadata_string}\n\n{text}";
|
||||
|
||||
private static String DEFAULT_METADATA_TEMPLATE = "{key}: {value}";
|
||||
|
||||
/**
|
||||
* Unique ID
|
||||
*/
|
||||
private String id = UUID.randomUUID().toString();
|
||||
private final String id;
|
||||
|
||||
private List<Double> embedding = new ArrayList<>();
|
||||
|
||||
@@ -25,28 +21,24 @@ public class Document {
|
||||
|
||||
// Type; introduce when support images, now only text.
|
||||
|
||||
// TODO: Rename to `content` instead.
|
||||
private String text;
|
||||
|
||||
private MetadataMode metadataMode = MetadataMode.NONE;
|
||||
|
||||
private List<String> excludedMetadataKeysForEmbedding;
|
||||
|
||||
private List<String> excludedMetadataKeysForLlm;
|
||||
|
||||
private List<String> relatedIds;
|
||||
|
||||
private String textTemplate = DEFAULT_TEXT_TEMPLATE;
|
||||
|
||||
private String metadataTemplate = DEFAULT_METADATA_TEMPLATE;
|
||||
|
||||
private String metadataSeparator = "\n";
|
||||
|
||||
public Document(String text) {
|
||||
this(UUID.randomUUID().toString(), text);
|
||||
}
|
||||
|
||||
public Document(String id, String text) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public Document(String text, Map<String, Object> metadata) {
|
||||
this(UUID.randomUUID().toString(), text, metadata);
|
||||
}
|
||||
|
||||
public Document(String id, String text, Map<String, Object> metadata) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
@@ -59,6 +51,43 @@ public class Document {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public List<Double> getEmbedding() {
|
||||
return embedding;
|
||||
}
|
||||
|
||||
public void setEmbedding(List<Double> embedding) {
|
||||
this.embedding = embedding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Document{" + "id='" + id + '\'' + ", metadata=" + metadata + ", text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
// TODO: Consider moving the following methods & fields in a seprarate
|
||||
// dedicated class. (e.g. DocumentService, DocumentUtil or alike)¬
|
||||
|
||||
// private List<String> excludedMetadataKeysForEmbedding;
|
||||
// private List<String> relatedIds;
|
||||
|
||||
private static String DEFAULT_TEXT_TEMPLATE = "{metadata_string}\n\n{text}";
|
||||
|
||||
private static String DEFAULT_METADATA_TEMPLATE = "{key}: {value}";
|
||||
|
||||
private final String textTemplate = DEFAULT_TEXT_TEMPLATE;
|
||||
|
||||
private final String metadataTemplate = DEFAULT_METADATA_TEMPLATE;
|
||||
|
||||
private final String metadataSeparator = "\n";
|
||||
|
||||
private MetadataMode metadataMode = MetadataMode.NONE;
|
||||
|
||||
private List<String> excludedMetadataKeysForLlm;
|
||||
|
||||
public String getContent() {
|
||||
return getContent(MetadataMode.ALL);
|
||||
}
|
||||
@@ -103,45 +132,28 @@ public class Document {
|
||||
return String.join(getMetadataSeparator(), metadataStringList);
|
||||
}
|
||||
|
||||
public String getTextTemplate() {
|
||||
private String getTextTemplate() {
|
||||
return textTemplate;
|
||||
}
|
||||
|
||||
public String getMetadataTemplate() {
|
||||
private String getMetadataTemplate() {
|
||||
return metadataTemplate;
|
||||
}
|
||||
|
||||
public String getMetadataSeparator() {
|
||||
private String getMetadataSeparator() {
|
||||
return metadataSeparator;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
// public void setTextTemplate(String textTemplate) {
|
||||
// this.textTemplate = textTemplate;
|
||||
// }
|
||||
|
||||
public List<Double> getEmbedding() {
|
||||
return embedding;
|
||||
}
|
||||
// public void setMetadataTemplate(String metadataTemplate) {
|
||||
// this.metadataTemplate = metadataTemplate;
|
||||
// }
|
||||
|
||||
public void setEmbedding(List<Double> embedding) {
|
||||
this.embedding = embedding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Document{" + "id='" + id + '\'' + ", metadata=" + metadata + ", text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
public void setTextTemplate(String textTemplate) {
|
||||
this.textTemplate = textTemplate;
|
||||
}
|
||||
|
||||
public void setMetadataTemplate(String metadataTemplate) {
|
||||
this.metadataTemplate = metadataTemplate;
|
||||
}
|
||||
|
||||
public void setMetadataSeparator(String metadataSeparator) {
|
||||
this.metadataSeparator = metadataSeparator;
|
||||
}
|
||||
// public void setMetadataSeparator(String metadataSeparator) {
|
||||
// this.metadataSeparator = metadataSeparator;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1 +1,202 @@
|
||||
= Vector Databases
|
||||
= Vector Databases
|
||||
|
||||
== Introduction
|
||||
Vector Databases are a specialized type of database that plays an essential role in AI applications.
|
||||
|
||||
In Vector Databases, queries differ from traditional relational databases.
|
||||
Instead of exact matches, they perform similarity searches.
|
||||
When given a vector as a query, a Vector Database returns vectors that are "similar" to the query vector.
|
||||
Further details on how this similarity is calculated at a high level in provided in a later section.
|
||||
|
||||
Vector Databases are used to integrate your data with AI Models.
|
||||
The first step in their usage is to load your data into a Vector Database.
|
||||
Then, when a user query is to be sent to the AI model, a set of similar documents is retrieved first.
|
||||
These documents then serve as the context for the user's question and are sent to the AI model along with the user's query.
|
||||
This technique is known as Retrieval Augmented Generation.
|
||||
|
||||
In the following sections, we will describe the Spring AI interface for using multiple Vector Database implementations and some high-level sample usage.
|
||||
|
||||
The last section attempts to demystify the underlying approach of similarity search of Vector Databases.
|
||||
|
||||
== Spring AI Vector Database Support
|
||||
|
||||
Spring AI provides an abstract API over Vector Databases using the interface `VectorStore`.
|
||||
|
||||
The API of the Vector store is shown below
|
||||
|
||||
```java
|
||||
public interface VectorStore {
|
||||
|
||||
void add(List<Document> documents);
|
||||
|
||||
Optional<Boolean> delete(List<String> idList);
|
||||
|
||||
List<Document> similaritySearch(String query);
|
||||
|
||||
List<Document> similaritySearch(String query, int k);
|
||||
|
||||
List<Document> similaritySearch(String query, int k, double threshold);
|
||||
```
|
||||
|
||||
The Spring AI library uses the `Document` class to model the `String` you want to store in the Vector Database along with the vector representing that `String`.
|
||||
The vector that representing the `String` is of the type `List<Double>` and is often called the string's embedding.
|
||||
|
||||
The `List<Double> is computed from the `String` using an implementation of the class `EmbeddingClient`.
|
||||
An Embedding Model is an example of an AI model, that transforms from `String` to a `List<Double>`.
|
||||
The embeddings are used internally by other AI Model implementations, such as models that convert `text` to `text` like ChatGPT.
|
||||
|
||||
`EmbeddingClient` that computes the embedding is passed as a constructor argument to `VectorStore` implementation.
|
||||
|
||||
|
||||
The `VectorStore` implementations supported by Spring AI are:
|
||||
|
||||
* InMemoryVectorStore
|
||||
* SimplePersistentVectorStore
|
||||
* PgVector - A Vector Store build on PostgreSQL.
|
||||
|
||||
More are implementations are coming, with Pinecone being the next implementation.
|
||||
|
||||
If you have a Vector Database that needs to be supported by Spring AI, please open an issue on GitHub or, even better, submit a Pull Request with an implementation.
|
||||
|
||||
== Usage
|
||||
|
||||
To compute the embeddings for a Vector Database, you need to pick an Embedding Model that matches the higher-level AI model being used.
|
||||
|
||||
For example, with OpenAI's ChatGPT, we use the `OpenAiEmbeddingClient` and the model name `text-embedding-ada-002`.
|
||||
|
||||
The Spring Boot Starter's autoconfiguation for OpenAI makes an implementation of `EmbeddingClient` available in the Spring Application Context for Dependency Injection.
|
||||
|
||||
The general usage of loading data into a vector store is something you do as a batch-type job, first loading data into Spring AI's `Document` class and then calling the `save` method.
|
||||
|
||||
Given a String `sourceFile` that represents a JSON file with data we want to load into the Vector Database, we use Spring AI's `JsonLoader` to load specific fields in the JSON file, which splits them up into small pieces and then passes those small pieces to the vector store implementation.
|
||||
The `VectorStore` implementation computes the embeddings and stores the JSON and the embedding in the Vector Database.
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
VectorStore vectorStore;
|
||||
|
||||
void load(String sourceFile) {}
|
||||
JsonLoader jsonLoader = new JsonLoader(new FileSystemResource(sourceFile),
|
||||
"price", "name", "shortDescription", "description", "tags");
|
||||
List<Document> documents = jsonLoader.load();
|
||||
this.vectorStore.add(documents);
|
||||
}
|
||||
```
|
||||
|
||||
Later, when a user question is to be passed into the AI Model, a similarity search is done to retrieve similar documents, which are then 'stuffed' into the prompt as context for the user's question.
|
||||
|
||||
```java
|
||||
|
||||
String question = <question from user>
|
||||
List<Document> similarDocuments = store.similaritySearch(question);
|
||||
```
|
||||
|
||||
There are additional options to be passed into the `similaritySearch` method that defines how many documents to retrieve and a threshold of the similarity search.
|
||||
|
||||
== Understanding Vectors
|
||||
|
||||
Vectors have dimensionality and a direction.
|
||||
For example, a picture of a two-dimensional vector stem:[\vec{a}] in the cartesian coordinate system pictured as an arrow.
|
||||
|
||||
image::vector_2d_coordinates.png[]
|
||||
|
||||
The head of the vector stem:[\vec{a}] is at the point stem:[(a_1, a_2)]
|
||||
The *x* coordinate value is stem:[a_1] and the *y* coordinate value is stem:[a_2] and are also referred to as the components of the vector.
|
||||
|
||||
== Similarity
|
||||
|
||||
Several mathematical formulas can be used to determine if two vectors are similar.
|
||||
|
||||
One of the most intuitive to visualize and understand is cosine similarity.
|
||||
|
||||
Look at the following pictures that show three sets of graphs.
|
||||
|
||||
image::vector_similarity.png[]
|
||||
|
||||
The vectors stem:[\vec{A}] and stem:[\vec{B}] are considered similar, when they are pointing close to each other, as in the first diagram.
|
||||
The vectors are considered unrelated when pointing perpendicular to each other and opposite when they point away from each other.
|
||||
|
||||
The angle between them, stem:[\theta], is a good measure of their similarity.
|
||||
How can the angle stem:[\theta] be computed?
|
||||
|
||||
We are all familiar with the https://en.wikipedia.org/wiki/Pythagorean_theorem#History[Pythagorean Theorem]
|
||||
|
||||
image:pythagorean-triangle.png[]
|
||||
|
||||
What about when the angle between *a* and *b* is not 90 degrees?
|
||||
|
||||
Enter the https://en.wikipedia.org/wiki/Law_of_cosines[Law of cosines]
|
||||
|
||||
|
||||
.Law of Cosines
|
||||
****
|
||||
stem:[a^2 + b^2 - 2ab\cos\theta = c^2]
|
||||
****
|
||||
|
||||
Showing this as a vector diagram
|
||||
|
||||
image:lawofcosines.png[]
|
||||
|
||||
|
||||
The magnitude of this vector is defined in terms of its components as:
|
||||
|
||||
.Magnitude
|
||||
****
|
||||
stem:[\vec{A} * \vec{A} = ||\vec{A}||^2 = A_1^2 + A_2^2 ]
|
||||
****
|
||||
|
||||
and the dot product between two vectors stem:[\vec{A}] and stem:[\vec{B}] is defined in terms of its components as:
|
||||
|
||||
|
||||
.Dot Product
|
||||
****
|
||||
stem:[\vec{A} * \vec{B} = A_1B_1 + A_2B_2]
|
||||
****
|
||||
|
||||
Rewriting the Law of Cosines with vector magnitudes and dot products gives:
|
||||
|
||||
.Law of Cosines in Vector form
|
||||
****
|
||||
stem:[||\vec{A}||^2 + ||\vec{B}||^2 - 2||\vec{A}||||\vec{B}||\cos\theta = ||\vec{C}||^2]
|
||||
****
|
||||
|
||||
|
||||
Replacing stem:[||\vec{C}||^2] with stem:[||\vec{B} - \vec{A}||^2] gives:
|
||||
|
||||
.Law of Cosines in Vector form only in terms of stem:[\vec{A}] and stem:[\vec{B}]
|
||||
|
||||
****
|
||||
stem:[||\vec{A}||^2 + ||\vec{B}||^2 - 2||\vec{A}||||\vec{B}||\cos\theta = ||\vec{B} - \vec{A}||^2]
|
||||
****
|
||||
|
||||
|
||||
https://towardsdatascience.com/cosine-similarity-how-does-it-measure-the-similarity-maths-behind-and-usage-in-python-50ad30aad7db[Expanding this out] gives us the formula for https://en.wikipedia.org/wiki/Cosine_similarity[Cosine Similarity].
|
||||
|
||||
.Cosine Similarity
|
||||
****
|
||||
stem:[similarity(vec{A},vec{B}) = \cos(\theta) = \frac{\vec{A}\cdot\vec{B}}{||\vec{A}\||\cdot||\vec{B}||]
|
||||
****
|
||||
|
||||
This formula works for dimensions higher than 2 or 3, though it is hard to visualize, https://projector.tensorflow.org/[but can be done to some extent].
|
||||
It is common for vectors in AI/ML applications to have hundreds or a thousand dimensions.
|
||||
|
||||
The similarity function in higher dimensions using the components of the vector is shown below.
|
||||
It expands the two-dimensional definitions of Magnitude and Dot Product given previously to *N* dimensions using the https://en.wikipedia.org/wiki/Summation[Summation mathematical syntax].
|
||||
|
||||
.Cosine Similarity with vector components
|
||||
****
|
||||
stem:[similarity(vec{A},vec{B}) = \cos(\theta) = \frac{ \sum_{i=1}^{n} {A_i B_i} }{ \sqrt{\sum_{i=1}^{n}{A_i^2} \cdot \sum_{i=1}^{n}{B_i^2}}]
|
||||
****
|
||||
|
||||
This is the key formula used in the simple implementation of a Vector Store and can be found in the `InMemoryVectorStore` implementation.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.springframework.ai.client.AiResponse;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.loader.impl.JsonLoader;
|
||||
import org.springframework.ai.openai.embedding.OpenAiEmbeddingClient;
|
||||
import org.springframework.ai.openai.testutils.AbstractIntegrationTest;
|
||||
import org.springframework.ai.openai.testutils.AbstractIT;
|
||||
import org.springframework.ai.prompt.Prompt;
|
||||
import org.springframework.ai.prompt.SystemPromptTemplate;
|
||||
import org.springframework.ai.prompt.messages.Message;
|
||||
@@ -28,9 +28,9 @@ import java.util.stream.Collectors;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
public class AcmeIntegrationTest extends AbstractIntegrationTest {
|
||||
public class AcmeIT extends AbstractIT {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AcmeIntegrationTest.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(AcmeIT.class);
|
||||
|
||||
@Value("classpath:/data/acme/bikes.json")
|
||||
private Resource bikesResource;
|
||||
@@ -3,7 +3,7 @@ package org.springframework.ai.openai.client;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.client.AiResponse;
|
||||
import org.springframework.ai.client.Generation;
|
||||
import org.springframework.ai.openai.testutils.AbstractIntegrationTest;
|
||||
import org.springframework.ai.openai.testutils.AbstractIT;
|
||||
import org.springframework.ai.parser.BeanOutputParser;
|
||||
import org.springframework.ai.parser.ListOutputParser;
|
||||
import org.springframework.ai.parser.MapOutputParser;
|
||||
@@ -24,7 +24,7 @@ import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
class ClientIntegrationTests extends AbstractIntegrationTest {
|
||||
class ClientIT extends AbstractIT {
|
||||
|
||||
@Value("classpath:/prompts/system-message.st")
|
||||
private Resource systemResource;
|
||||
@@ -10,7 +10,7 @@ import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
class EmbeddingIntegrationTest {
|
||||
class EmbeddingIT {
|
||||
|
||||
@Autowired
|
||||
private OpenAiEmbeddingClient embeddingClient;
|
||||
@@ -18,9 +18,9 @@ import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
public class AbstractIntegrationTest {
|
||||
public abstract class AbstractIT {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractIntegrationTest.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractIT.class);
|
||||
|
||||
@Autowired
|
||||
protected OpenAiClient openAiClient;
|
||||
16
vector-stores/spring-ai-pgvector-store/README.md
Normal file
16
vector-stores/spring-ai-pgvector-store/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
# PGvector VectorStore
|
||||
|
||||
Pgvector is an open-source extension for PostgreSQL that enables storing and searching over machine learning-generated embeddings. It provides different capabilities that let users identify both exact and approximate nearest neighbors. It is designed to work seamlessly with other PostgreSQL features, including indexing and querying.
|
||||
|
||||
## Start Postgres+PGVecgor DB:
|
||||
|
||||
```
|
||||
docker run -it --rm --name postgres -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres ankane/pgvector
|
||||
```
|
||||
|
||||
You can connect to this server like this:
|
||||
|
||||
```
|
||||
psql -U postgres -h localhost -p 5432
|
||||
```
|
||||
94
vector-stores/spring-ai-pgvector-store/pom.xml
Normal file
94
vector-stores/spring-ai-pgvector-store/pom.xml
Normal file
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.experimental.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-pgvector-store</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI Vector Store - pgvector</name>
|
||||
<description>Spring AI PGVector Vector Store</description>
|
||||
<url>https://github.com/spring-projects-experimental/spring-ai</url>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects-experimental/spring-ai</url>
|
||||
<connection>git://github.com/spring-projects-experimental/spring-ai.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects-experimental/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<pgvector.version>0.1.3</pgvector.version>
|
||||
<postgresql.version>42.6.0</postgresql.version>
|
||||
<spring-ai.version>0.2.0-SNAPSHOT</spring-ai.version>
|
||||
<!-- testing -->
|
||||
<testcontainers.version>1.19.0</testcontainers.version>
|
||||
<hikari-cp.version>4.0.3</hikari-cp.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.experimental.ai</groupId>
|
||||
<artifactId>spring-ai-core</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.pgvector</groupId>
|
||||
<artifactId>pgvector</artifactId>
|
||||
<version>${pgvector.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- TESTING -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.experimental.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>${hikari-cp.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright 2023-2023 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;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pgvector.PGvector;
|
||||
import org.postgresql.util.PGobject;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.embedding.EmbeddingClient;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class PgVectorStore implements VectorStore {
|
||||
|
||||
public static final int OPENAI_EMBEDDING_DIMENSION_SIZE = 1536;
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private final EmbeddingClient embeddingClient;
|
||||
|
||||
private int dimensions;
|
||||
|
||||
private PgDistanceType distanceType;
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* By default, pgvector performs exact nearest neighbor search, which provides perfect
|
||||
* recall. You can add an index to use approximate nearest neighbor search, which
|
||||
* trades some recall for speed. Unlike typical indexes, you will see different
|
||||
* results for queries after adding an approximate index.
|
||||
*/
|
||||
public enum PgIndexType {
|
||||
|
||||
/**
|
||||
* Performs exact nearest neighbor search, which provides perfect recall.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* An IVFFlat index divides vectors into lists, and then searches a subset of
|
||||
* those lists that are closest to the query vector. It has faster build times and
|
||||
* uses less memory than HNSW, but has lower query performance (in terms of
|
||||
* speed-recall tradeoff).
|
||||
*/
|
||||
IVFFLAT,
|
||||
/**
|
||||
* An HNSW index creates a multilayer graph. It has slower build times and uses
|
||||
* more memory than IVFFlat, but has better query performance (in terms of
|
||||
* speed-recall tradeoff). There’s no training step like IVFFlat, so the index can
|
||||
* be created without any data in the table.
|
||||
*/
|
||||
HNSW;
|
||||
|
||||
}
|
||||
|
||||
public enum PgDistanceType {
|
||||
|
||||
EuclideanDistance("<->", "vector_l2_ops"),
|
||||
|
||||
NegativeInnerProduct("<#>", "vector_ip_ops"),
|
||||
|
||||
CosineDistance("<=>", "vector_cosine_ops");
|
||||
|
||||
public final String operator;
|
||||
|
||||
public final String index;
|
||||
|
||||
PgDistanceType(String operator, String index) {
|
||||
this.operator = operator;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DocumentRowMapper implements RowMapper<Document> {
|
||||
|
||||
private static final String COLUMN_EMBEDDING = "embedding";
|
||||
|
||||
private static final String COLUMN_METADATA = "metadata";
|
||||
|
||||
private static final String COLUMN_ID = "id";
|
||||
|
||||
private static final String COLUMN_CONTENT = "content";
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public DocumentRowMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
String id = rs.getString(COLUMN_ID);
|
||||
String content = rs.getString(COLUMN_CONTENT);
|
||||
PGobject metadata = rs.getObject(COLUMN_METADATA, PGobject.class);
|
||||
PGobject embedding = rs.getObject(COLUMN_EMBEDDING, PGobject.class);
|
||||
|
||||
Document document = new Document(id, content, toMap(metadata));
|
||||
document.setEmbedding(toDoubleList(embedding));
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private List<Double> toDoubleList(PGobject embedding) throws SQLException {
|
||||
float[] floatArray = new PGvector(embedding.getValue()).toArray();
|
||||
List<Double> doubleEmbedding = IntStream.range(0, floatArray.length)
|
||||
.mapToDouble(i -> floatArray[i])
|
||||
.boxed()
|
||||
.collect(Collectors.toList());
|
||||
return doubleEmbedding;
|
||||
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(PGobject pgObject) {
|
||||
|
||||
String source = pgObject.getValue();
|
||||
try {
|
||||
return (Map<String, Object>) objectMapper.readValue(source, Map.class);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PgVectorStore(JdbcTemplate jdbcTemplate, EmbeddingClient embeddingClient) {
|
||||
this(jdbcTemplate, embeddingClient, OPENAI_EMBEDDING_DIMENSION_SIZE,
|
||||
PgVectorStore.PgDistanceType.CosineDistance, false, PgIndexType.NONE);
|
||||
}
|
||||
|
||||
public PgVectorStore(JdbcTemplate jdbcTemplate, EmbeddingClient embeddingClient, int dimensions,
|
||||
PgDistanceType distanceType, boolean removeExistingVectorStoreTable, PgIndexType createIndexMethod) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.embeddingClient = embeddingClient;
|
||||
this.dimensions = dimensions;
|
||||
this.distanceType = distanceType;
|
||||
|
||||
// Add PGVector support.
|
||||
this.jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS vector");
|
||||
// Add JSONB support.
|
||||
this.jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS hstore");
|
||||
// Add UUID support.
|
||||
this.jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"");
|
||||
|
||||
// Remove existing VectorStoreTable
|
||||
if (removeExistingVectorStoreTable) {
|
||||
this.jdbcTemplate.execute("DROP TABLE IF EXISTS vector_store");
|
||||
}
|
||||
|
||||
// TODO: we create id of type UUID, while the Document's id is String!!!
|
||||
// TODO: remove injection!
|
||||
this.jdbcTemplate
|
||||
.execute("CREATE TABLE IF NOT EXISTS vector_store ( " + "id uuid DEFAULT uuid_generate_v4 () PRIMARY KEY, "
|
||||
+ "content text, " + "metadata json, " + "embedding vector(" + this.dimensions + "))");
|
||||
|
||||
if (createIndexMethod != PgIndexType.NONE) {
|
||||
this.jdbcTemplate.execute("CREATE INDEX ON vector_store USING " + createIndexMethod.name() + " (embedding "
|
||||
+ distanceType.index + ")");
|
||||
}
|
||||
|
||||
this.jdbcTemplate
|
||||
.execute("CREATE TABLE IF NOT EXISTS vector_store ( " + "id uuid DEFAULT uuid_generate_v4 () PRIMARY KEY, "
|
||||
+ "content text, " + "metadata json, " + "embedding vector(" + this.dimensions + "))");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(List<Document> documents) {
|
||||
for (Document document : documents) {
|
||||
List<Double> embedding = this.embeddingClient.embed(document);
|
||||
document.setEmbedding(embedding);
|
||||
|
||||
UUID id = UUID.fromString(document.getId());
|
||||
String content = document.getText(); // TODO: shall we use the text of text +
|
||||
// metadata?
|
||||
Map<String, Object> metadata = document.getMetadata();
|
||||
PGvector pgEmbedding = new PGvector(toFloatArray(embedding));
|
||||
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO vector_store (id, content, metadata, embedding) VALUES (?, ?, ?::jsonb, ?) "
|
||||
+ "ON CONFLICT (id) DO " + "UPDATE SET content = ? , metadata = ?::jsonb , embedding = ? ",
|
||||
id, content, metadata, pgEmbedding, content, metadata, pgEmbedding);
|
||||
}
|
||||
}
|
||||
|
||||
private float[] toFloatArray(List<Double> embeddingDouble) {
|
||||
float[] embeddingFloat = new float[embeddingDouble.size()];
|
||||
int i = 0;
|
||||
for (Double d : embeddingDouble) {
|
||||
embeddingFloat[i++] = d.floatValue();
|
||||
}
|
||||
return embeddingFloat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> delete(List<String> idList) {
|
||||
int updateCount = 0;
|
||||
for (String id : idList) {
|
||||
int count = jdbcTemplate.update("DELETE FROM vector_store WHERE id = ?", UUID.fromString(id));
|
||||
|
||||
updateCount = updateCount + count;
|
||||
}
|
||||
|
||||
return Optional.of(updateCount == idList.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Document> similaritySearch(String query) {
|
||||
return this.similaritySearch(query, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Document> similaritySearch(String query, int k) {
|
||||
PGvector queryEmbedding = getQueryEmbedding(query);
|
||||
return this.jdbcTemplate.query(
|
||||
"SELECT * FROM vector_store ORDER BY embedding " + this.distanceType.operator + " ? LIMIT ?",
|
||||
new DocumentRowMapper(this.objectMapper), queryEmbedding, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Document> similaritySearch(String query, int k, double threshold) {
|
||||
PGvector queryEmbedding = getQueryEmbedding(query);
|
||||
return this.jdbcTemplate.query(
|
||||
"SELECT * FROM vector_store ORDER BY embedding " + this.distanceType.operator + " ? < ? LIMIT ? ",
|
||||
new DocumentRowMapper(this.objectMapper), queryEmbedding, threshold, k);
|
||||
}
|
||||
|
||||
private PGvector getQueryEmbedding(String query) {
|
||||
List<Double> embedding = this.embeddingClient.embed(query);
|
||||
return new PGvector(toFloatArray(embedding));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2023-2023 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.embedding.EmbeddingClient;
|
||||
import org.springframework.ai.vectorstore.PgVectorStore.PgIndexType;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@Testcontainers
|
||||
public class PgVectorStoreIT {
|
||||
|
||||
@Container
|
||||
static GenericContainer<?> postgresContainer = new GenericContainer<>("ankane/pgvector")
|
||||
.withEnv("POSTGRES_USER", "postgres")
|
||||
.withEnv("POSTGRES_PASSWORD", "postgres")
|
||||
.withExposedPorts(5432);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TestApplication.class)
|
||||
.withPropertyValues("spring.datasource.type=com.zaxxer.hikari.HikariDataSource",
|
||||
"spring.ai.openai.apiKey=" + System.getenv("SPRING_AI_OPENAI_API_KEY"),
|
||||
|
||||
// JdbcTemplate configuration
|
||||
String.format("app.datasource.url=jdbc:postgresql://localhost:%d/%s",
|
||||
postgresContainer.getMappedPort(5432), "postgres"),
|
||||
"app.datasource.username=postgres", "app.datasource.password=postgres",
|
||||
"app.datasource.type=com.zaxxer.hikari.HikariDataSource");
|
||||
|
||||
@Test
|
||||
public void vectorStoreTest() {
|
||||
contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)).run(context -> {
|
||||
|
||||
VectorStore vectorStore = context.getBean(VectorStore.class);
|
||||
|
||||
List<Document> documents = List.of(
|
||||
new Document(
|
||||
"Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!",
|
||||
Collections.singletonMap("meta1", "meta1")),
|
||||
new Document("Hello World Hello World Hello World Hello World Hello World Hello World Hello World"),
|
||||
new Document(
|
||||
"Great Depression Great Depression Great Depression Great Depression Great Depression Great Depression",
|
||||
Collections.singletonMap("meta2", "meta2")));
|
||||
|
||||
vectorStore.add(documents);
|
||||
|
||||
List<Document> results = vectorStore.similaritySearch("Great", 1);
|
||||
|
||||
assertThat(results).hasSize(1);
|
||||
Document resultDoc = results.get(0);
|
||||
assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId());
|
||||
assertThat(resultDoc.getText()).isEqualTo(
|
||||
"Great Depression Great Depression Great Depression Great Depression Great Depression Great Depression");
|
||||
assertThat(resultDoc.getMetadata()).isEqualTo(Collections.singletonMap("meta2", "meta2"));
|
||||
|
||||
// Remove all documents from the store
|
||||
vectorStore.delete(documents.stream().map(doc -> doc.getId()).collect(Collectors.toList()));
|
||||
|
||||
List<Document> results2 = vectorStore.similaritySearch("Great", 1);
|
||||
assertThat(results2).hasSize(0);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void documentUpdateTest() {
|
||||
|
||||
contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)).run(context -> {
|
||||
|
||||
VectorStore vectorStore = context.getBean(VectorStore.class);
|
||||
|
||||
Document document = new Document(UUID.randomUUID().toString(), "Spring AI rocks!!",
|
||||
Collections.singletonMap("meta1", "meta1"));
|
||||
|
||||
vectorStore.add(List.of(document));
|
||||
|
||||
List<Document> results = vectorStore.similaritySearch("Spring", 5);
|
||||
|
||||
assertThat(results).hasSize(1);
|
||||
Document resultDoc = results.get(0);
|
||||
assertThat(resultDoc.getId()).isEqualTo(document.getId());
|
||||
assertThat(resultDoc.getText()).isEqualTo("Spring AI rocks!!");
|
||||
assertThat(resultDoc.getMetadata()).isEqualTo(Collections.singletonMap("meta1", "meta1"));
|
||||
|
||||
Document sameIdDocument = new Document(document.getId(),
|
||||
"The World is Big and Salvation Lurks Around the Corner",
|
||||
Collections.singletonMap("meta2", "meta2"));
|
||||
|
||||
vectorStore.add(List.of(sameIdDocument));
|
||||
|
||||
results = vectorStore.similaritySearch("FooBar", 5);
|
||||
|
||||
assertThat(results).hasSize(1);
|
||||
resultDoc = results.get(0);
|
||||
assertThat(resultDoc.getId()).isEqualTo(document.getId());
|
||||
assertThat(resultDoc.getText()).isEqualTo("The World is Big and Salvation Lurks Around the Corner");
|
||||
assertThat(resultDoc.getMetadata()).isEqualTo(Collections.singletonMap("meta2", "meta2"));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
|
||||
public static class TestApplication {
|
||||
|
||||
@Bean
|
||||
public VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingClient embeddingClient) {
|
||||
return new PgVectorStore(jdbcTemplate, embeddingClient, 1536, PgVectorStore.PgDistanceType.CosineDistance,
|
||||
true, PgIndexType.HNSW);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcTemplate myJdbcTemplate(DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties("app.datasource")
|
||||
public DataSourceProperties dataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HikariDataSource dataSource(DataSourceProperties dataSourceProperties) {
|
||||
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user