Add OCI GenAI embedding model support
This commit introduces support for Oracle Cloud Infrastructure (OCI) GenAI embedding models in Spring AI. It includes: * New OCIEmbeddingModel class for interacting with OCI GenAI API * Auto-configuration for easy setup and integration * Properties for configuring OCI connection and embedding options * Documentation updates explaining usage and configuration * Integration tests to verify functionality Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
This commit is contained in:
committed by
Mark Pollack
parent
5da44c4b45
commit
ccf190c77c
1
models/spring-ai-oci-genai/README.md
Normal file
1
models/spring-ai-oci-genai/README.md
Normal file
@@ -0,0 +1 @@
|
||||
[Oracle Cloud Infrastructure GenAI Documentation](https://docs.oracle.com/en-us/iaas/Content/generative-ai/overview.htm)
|
||||
69
models/spring-ai-oci-genai/pom.xml
Normal file
69
models/spring-ai-oci-genai/pom.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-oci-genai</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI Model - OCI GenAI</name>
|
||||
<description>OCI GenAI models support</description>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
<connection>git://github.com/spring-projects/spring-ai.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- production dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.oci.sdk</groupId>
|
||||
<artifactId>oci-java-sdk-shaded-full</artifactId>
|
||||
<version>${oci-sdk-version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.oci.sdk</groupId>
|
||||
<artifactId>oci-java-sdk-addons-oke-workload-identity</artifactId>
|
||||
<version>${oci-sdk-version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- NOTE: Required only by the @ConstructorBinding. -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-test</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2024 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.oci;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInference;
|
||||
import com.oracle.bmc.generativeaiinference.model.DedicatedServingMode;
|
||||
import com.oracle.bmc.generativeaiinference.model.EmbedTextDetails;
|
||||
import com.oracle.bmc.generativeaiinference.model.EmbedTextResult;
|
||||
import com.oracle.bmc.generativeaiinference.model.OnDemandServingMode;
|
||||
import com.oracle.bmc.generativeaiinference.model.ServingMode;
|
||||
import com.oracle.bmc.generativeaiinference.requests.EmbedTextRequest;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.springframework.ai.chat.metadata.EmptyUsage;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.embedding.AbstractEmbeddingModel;
|
||||
import org.springframework.ai.embedding.Embedding;
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
import org.springframework.ai.embedding.EmbeddingRequest;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
import org.springframework.ai.embedding.EmbeddingResponseMetadata;
|
||||
import org.springframework.ai.embedding.observation.DefaultEmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.observation.conventions.AiProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.ai.embedding.EmbeddingModel} implementation that uses the
|
||||
* OCI GenAI Embedding API.
|
||||
*
|
||||
* @author Anders Swanson
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class OCIEmbeddingModel extends AbstractEmbeddingModel {
|
||||
|
||||
// The OCI GenAI API has a batch size of 96 for embed text requests.
|
||||
private static final int EMBEDTEXT_BATCH_SIZE = 96;
|
||||
|
||||
private static final EmbeddingModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultEmbeddingModelObservationConvention();
|
||||
|
||||
private final GenerativeAiInference genAi;
|
||||
|
||||
private final OCIEmbeddingOptions options;
|
||||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
private final EmbeddingModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
|
||||
|
||||
public OCIEmbeddingModel(GenerativeAiInference genAi, OCIEmbeddingOptions options) {
|
||||
this(genAi, options, ObservationRegistry.NOOP);
|
||||
}
|
||||
|
||||
public OCIEmbeddingModel(GenerativeAiInference genAi, OCIEmbeddingOptions options,
|
||||
ObservationRegistry observationRegistry) {
|
||||
Assert.notNull(genAi, "com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient must not be null");
|
||||
Assert.notNull(options, "options must not be null");
|
||||
Assert.notNull(observationRegistry, "observationRegistry must not be null");
|
||||
this.genAi = genAi;
|
||||
this.options = options;
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmbeddingResponse call(EmbeddingRequest request) {
|
||||
Assert.notEmpty(request.getInstructions(), "At least one text is required!");
|
||||
OCIEmbeddingOptions runtimeOptions = mergeOptions(request.getOptions(), options);
|
||||
List<EmbedTextRequest> embedTextRequests = createRequests(request.getInstructions(), runtimeOptions);
|
||||
|
||||
EmbeddingModelObservationContext context = EmbeddingModelObservationContext.builder()
|
||||
.embeddingRequest(request)
|
||||
.provider(AiProvider.OCI_GENAI.value())
|
||||
.requestOptions(runtimeOptions)
|
||||
.build();
|
||||
|
||||
return EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION
|
||||
.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> context,
|
||||
this.observationRegistry)
|
||||
.observe(() -> embedAllWithContext(embedTextRequests, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] embed(Document document) {
|
||||
return embed(document.getContent());
|
||||
}
|
||||
|
||||
private EmbeddingResponse embedAllWithContext(List<EmbedTextRequest> embedTextRequests,
|
||||
EmbeddingModelObservationContext context) {
|
||||
String modelId = null;
|
||||
AtomicInteger index = new AtomicInteger(0);
|
||||
List<Embedding> embeddings = new ArrayList<>();
|
||||
for (EmbedTextRequest embedTextRequest : embedTextRequests) {
|
||||
EmbedTextResult embedTextResult = genAi.embedText(embedTextRequest).getEmbedTextResult();
|
||||
if (modelId == null) {
|
||||
modelId = embedTextResult.getModelId();
|
||||
}
|
||||
for (List<Float> e : embedTextResult.getEmbeddings()) {
|
||||
float[] data = toFloats(e);
|
||||
embeddings.add(new Embedding(data, index.getAndIncrement()));
|
||||
}
|
||||
}
|
||||
EmbeddingResponseMetadata metadata = new EmbeddingResponseMetadata();
|
||||
metadata.setModel(modelId);
|
||||
metadata.setUsage(new EmptyUsage());
|
||||
EmbeddingResponse embeddingResponse = new EmbeddingResponse(embeddings, metadata);
|
||||
context.setResponse(embeddingResponse);
|
||||
return embeddingResponse;
|
||||
}
|
||||
|
||||
private ServingMode servingMode(OCIEmbeddingOptions embeddingOptions) {
|
||||
return switch (embeddingOptions.getServingMode()) {
|
||||
case "dedicated" -> DedicatedServingMode.builder().endpointId(embeddingOptions.getModel()).build();
|
||||
case "on-demand" -> OnDemandServingMode.builder().modelId(embeddingOptions.getModel()).build();
|
||||
default -> throw new IllegalArgumentException(
|
||||
"unknown serving mode for OCI embedding model: " + embeddingOptions.getServingMode());
|
||||
};
|
||||
}
|
||||
|
||||
private List<EmbedTextRequest> createRequests(List<String> inputs, OCIEmbeddingOptions embeddingOptions) {
|
||||
int size = inputs.size();
|
||||
List<EmbedTextRequest> requests = new ArrayList<>();
|
||||
for (int i = 0; i < inputs.size(); i += EMBEDTEXT_BATCH_SIZE) {
|
||||
List<String> batch = inputs.subList(i, Math.min(i + EMBEDTEXT_BATCH_SIZE, size));
|
||||
requests.add(createRequest(batch, embeddingOptions));
|
||||
}
|
||||
return requests;
|
||||
}
|
||||
|
||||
private EmbedTextRequest createRequest(List<String> inputs, OCIEmbeddingOptions embeddingOptions) {
|
||||
EmbedTextDetails embedTextDetails = EmbedTextDetails.builder()
|
||||
.servingMode(servingMode(embeddingOptions))
|
||||
.compartmentId(embeddingOptions.getCompartment())
|
||||
.inputs(inputs)
|
||||
.truncate(Objects.requireNonNullElse(embeddingOptions.getTruncate(), EmbedTextDetails.Truncate.End))
|
||||
.build();
|
||||
return EmbedTextRequest.builder().embedTextDetails(embedTextDetails).build();
|
||||
}
|
||||
|
||||
private OCIEmbeddingOptions mergeOptions(EmbeddingOptions embeddingOptions, OCIEmbeddingOptions defaultOptions) {
|
||||
if (embeddingOptions instanceof OCIEmbeddingOptions) {
|
||||
OCIEmbeddingOptions dynamicOptions = ModelOptionsUtils.merge(embeddingOptions, defaultOptions,
|
||||
OCIEmbeddingOptions.class);
|
||||
if (dynamicOptions != null) {
|
||||
return dynamicOptions;
|
||||
}
|
||||
}
|
||||
return defaultOptions;
|
||||
}
|
||||
|
||||
private float[] toFloats(List<Float> embedding) {
|
||||
float[] floats = new float[embedding.size()];
|
||||
for (int i = 0; i < embedding.size(); i++) {
|
||||
floats[i] = embedding.get(i);
|
||||
}
|
||||
return floats;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2024 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.oci;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.oracle.bmc.generativeaiinference.model.EmbedTextDetails;
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
|
||||
/**
|
||||
* The configuration information for OCI embedding requests
|
||||
*
|
||||
* @author Anders Swanson
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class OCIEmbeddingOptions implements EmbeddingOptions {
|
||||
|
||||
private @JsonProperty("model") String model;
|
||||
|
||||
private @JsonProperty("compartment") String compartment;
|
||||
|
||||
private @JsonProperty("servingMode") String servingMode;
|
||||
|
||||
private @JsonProperty("truncate") EmbedTextDetails.Truncate truncate;
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final OCIEmbeddingOptions options = new OCIEmbeddingOptions();
|
||||
|
||||
public Builder withModel(String model) {
|
||||
this.options.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCompartment(String compartment) {
|
||||
this.options.setCompartment(compartment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withServingMode(String servingMode) {
|
||||
this.options.setServingMode(servingMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruncate(EmbedTextDetails.Truncate truncate) {
|
||||
this.options.truncate = truncate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OCIEmbeddingOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used by OCI GenAI.
|
||||
* @return null
|
||||
*/
|
||||
@Override
|
||||
public Integer getDimensions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getCompartment() {
|
||||
return compartment;
|
||||
}
|
||||
|
||||
public void setCompartment(String compartment) {
|
||||
this.compartment = compartment;
|
||||
}
|
||||
|
||||
public String getServingMode() {
|
||||
return servingMode;
|
||||
}
|
||||
|
||||
public void setServingMode(String servingMode) {
|
||||
this.servingMode = servingMode;
|
||||
}
|
||||
|
||||
public EmbedTextDetails.Truncate getTruncate() {
|
||||
return truncate;
|
||||
}
|
||||
|
||||
public void setTruncate(EmbedTextDetails.Truncate truncate) {
|
||||
this.truncate = truncate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2024 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.oci;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import com.oracle.bmc.Region;
|
||||
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
|
||||
|
||||
public class BaseEmbeddingModelTest {
|
||||
|
||||
public static final String OCI_COMPARTMENT_ID_KEY = "OCI_COMPARTMENT_ID";
|
||||
|
||||
public static final String EMBEDDING_MODEL_V2 = "cohere.embed-english-light-v2.0";
|
||||
|
||||
public static final String EMBEDDING_MODEL_V3 = "cohere.embed-english-v3.0";
|
||||
|
||||
private static final String CONFIG_FILE = Paths.get(System.getProperty("user.home"), ".oci", "config").toString();
|
||||
|
||||
private static final String PROFILE = "DEFAULT";
|
||||
|
||||
private static final String REGION = "us-chicago-1";
|
||||
|
||||
private static final String COMPARTMENT_ID = System.getenv(OCI_COMPARTMENT_ID_KEY);
|
||||
|
||||
/**
|
||||
* Create an OCIEmbeddingModel instance using a config file authentication provider.
|
||||
* @return OCIEmbeddingModel instance
|
||||
*/
|
||||
public static OCIEmbeddingModel get() {
|
||||
try {
|
||||
ConfigFileAuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(
|
||||
CONFIG_FILE, PROFILE);
|
||||
GenerativeAiInferenceClient aiClient = GenerativeAiInferenceClient.builder()
|
||||
.region(Region.valueOf(REGION))
|
||||
.build(authProvider);
|
||||
OCIEmbeddingOptions options = OCIEmbeddingOptions.builder()
|
||||
.withModel(EMBEDDING_MODEL_V2)
|
||||
.withCompartment(COMPARTMENT_ID)
|
||||
.withServingMode("on-demand")
|
||||
.build();
|
||||
return new OCIEmbeddingModel(aiClient, options);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2024 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.oci;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.embedding.EmbeddingRequest;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.ai.oci.BaseEmbeddingModelTest.OCI_COMPARTMENT_ID_KEY;
|
||||
|
||||
@EnabledIfEnvironmentVariable(named = OCI_COMPARTMENT_ID_KEY, matches = ".+")
|
||||
public class OCIEmbeddingModelIT extends BaseEmbeddingModelTest {
|
||||
|
||||
private final OCIEmbeddingModel embeddingModel = get();
|
||||
|
||||
private final List<String> content = List.of("How many states are in the USA?", "How many states are in India?");
|
||||
|
||||
@Test
|
||||
void embed() {
|
||||
float[] embedding = embeddingModel.embed(new Document("How many provinces are in Canada?"));
|
||||
assertThat(embedding).hasSize(1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
void call() {
|
||||
EmbeddingResponse response = embeddingModel.call(new EmbeddingRequest(content, null));
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getResults()).hasSize(2);
|
||||
assertThat(response.getMetadata().getModel()).isEqualTo(EMBEDDING_MODEL_V2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callWithOptions() {
|
||||
EmbeddingResponse response = embeddingModel
|
||||
.call(new EmbeddingRequest(content, OCIEmbeddingOptions.builder().withModel(EMBEDDING_MODEL_V3).build()));
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getResults()).hasSize(2);
|
||||
assertThat(response.getMetadata().getModel()).isEqualTo(EMBEDDING_MODEL_V3);
|
||||
}
|
||||
|
||||
}
|
||||
4
pom.xml
4
pom.xml
@@ -67,6 +67,7 @@
|
||||
<module>models/spring-ai-huggingface</module>
|
||||
<module>models/spring-ai-minimax</module>
|
||||
<module>models/spring-ai-mistral-ai</module>
|
||||
<module>models/spring-ai-oci-genai</module>
|
||||
<module>models/spring-ai-ollama</module>
|
||||
<module>models/spring-ai-openai</module>
|
||||
<module>models/spring-ai-postgresml</module>
|
||||
@@ -86,6 +87,7 @@
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-huggingface</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-minimax</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-mistral-ai</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-oci-genai</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-ollama</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-openai</module>
|
||||
<module>spring-ai-spring-boot-starters/spring-ai-starter-postgresml-embedding</module>
|
||||
@@ -157,8 +159,10 @@
|
||||
<awssdk.version>2.26.7</awssdk.version>
|
||||
|
||||
<jackson.version>2.16.1</jackson.version>
|
||||
|
||||
<djl.version>0.30.0</djl.version>
|
||||
<onnxruntime.version>1.19.2</onnxruntime.version>
|
||||
<oci-sdk-version>3.46.1</oci-sdk-version>
|
||||
<com.google.cloud.version>26.41.0</com.google.cloud.version>
|
||||
<qdrant.version>1.9.1</qdrant.version>
|
||||
<spring-retry.version>2.0.5</spring-retry.version>
|
||||
|
||||
@@ -76,6 +76,12 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-oci-genai</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-ollama</artifactId>
|
||||
@@ -313,6 +319,12 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-oci-genai-spring-boot-starter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
|
||||
|
||||
@@ -34,7 +34,8 @@ public enum AiProvider {
|
||||
OLLAMA("ollama"),
|
||||
OPENAI("openai"),
|
||||
SPRING_AI("spring_ai"),
|
||||
VERTEX_AI("vertex_ai");
|
||||
VERTEX_AI("vertex_ai"),
|
||||
OCI_GENAI("oci_genai");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
@@ -37,13 +37,13 @@
|
||||
// **** xref:api/chat/functions/zhipuai-chat-functions.adoc[Function Calling]
|
||||
*** xref:api/chat/watsonx-ai-chat.adoc[watsonx.AI]
|
||||
** xref:api/embeddings.adoc[]
|
||||
*** xref:api/embeddings/azure-openai-embeddings.adoc[Azure OpenAI]
|
||||
*** xref:api/bedrock.adoc[Amazon Bedrock]
|
||||
**** xref:api/embeddings/bedrock-cohere-embedding.adoc[Cohere]
|
||||
**** xref:api/embeddings/bedrock-titan-embedding.adoc[Titan]
|
||||
*** xref:api/embeddings/azure-openai-embeddings.adoc[Azure OpenAI]
|
||||
*** xref:api/embeddings/mistralai-embeddings.adoc[Mistral AI]
|
||||
*** xref:api/embeddings/minimax-embeddings.adoc[MiniMax]
|
||||
*** xref:api/embeddings/oci-genai-embeddings.adoc[OCI GenAI]
|
||||
*** xref:api/embeddings/ollama-embeddings.adoc[Ollama]
|
||||
*** xref:api/embeddings/onnx.adoc[(ONNX) Transformers]
|
||||
*** xref:api/embeddings/openai-embeddings.adoc[OpenAI]
|
||||
|
||||
@@ -168,3 +168,4 @@ Internally the various `EmbeddingModel` implementations use different low-level
|
||||
* xref:api/embeddings/vertexai-embeddings-text.adoc[Spring AI VertexAI Embeddings]
|
||||
* xref:api/embeddings/vertexai-embeddings-palm2.adoc[Spring AI VertexAI PaLM2 Embeddings]
|
||||
* xref:api/embeddings/mistralai-embeddings.adoc[Spring AI Mistral AI Embeddings]
|
||||
* xref:api/embeddings/oci-genai-embeddings.adoc[Spring AI Oracle Cloud Infrastructure GenAI Embeddings]
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
= Oracle Cloud Infrastructure (OCI) GenAI Embeddings
|
||||
|
||||
https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/[OCI GenAI Service] offers text embedding with on-demand models, or dedicated AI clusters.
|
||||
|
||||
The https://docs.oracle.com/en-us/iaas/Content/generative-ai/embed-models.htm[OCI Embedding Models Page] and https://docs.oracle.com/en-us/iaas/Content/generative-ai/use-playground-embed.htm[OCI Text Embeddings Page] provide detailed information about using and hosting embedding models on OCI.
|
||||
|
||||
== Prerequisites
|
||||
|
||||
=== Add Repositories and BOM
|
||||
|
||||
Spring AI artifacts are published in Spring Milestone and Snapshot repositories. Refer to the xref:getting-started.adoc#repositories[Repositories] section to add these repositories to your build system.
|
||||
|
||||
To help with dependency management, Spring AI provides a BOM (bill of materials) to ensure that a consistent version of Spring AI is used throughout the entire project. Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build system.
|
||||
|
||||
== Auto-configuration
|
||||
|
||||
Spring AI provides Spring Boot auto-configuration for the OCI GenAI Embedding Client.
|
||||
To enable it add the following dependency to your project's Maven `pom.xml` file:
|
||||
|
||||
[source, xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-oci-genai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
or to your Gradle `build.gradle` build file.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.ai:spring-ai-oci-genai-spring-boot-starter'
|
||||
}
|
||||
----
|
||||
|
||||
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
|
||||
|
||||
=== Embedding Properties
|
||||
|
||||
The prefix `spring.ai.oci.genai` is the property prefix to configure the connection to OCI GenAI.
|
||||
|
||||
[cols="3,5,1"]
|
||||
|====
|
||||
| Property | Description | Default
|
||||
|
||||
| spring.ai.oci.genai.authenticationType | The type of authentication to use when authenticating to OCI. May be `file`, `instance-principal`, `workload-identity`, or `simple`. | file
|
||||
| spring.ai.oci.genai.region | OCI service region. | us-chicago-1
|
||||
| spring.ai.oci.genai.tenantId | OCI tenant OCID, used when authenticating with `simple` auth. | -
|
||||
| spring.ai.oci.genai.userId | OCI user OCID, used when authenticating with `simple` auth. | -
|
||||
| spring.ai.oci.genai.fingerprint | Private key fingerprint, used when authenticating with `simple` auth. | -
|
||||
| spring.ai.oci.genai.privateKey | Private key content, used when authenticating with `simple` auth. | -
|
||||
| spring.ai.oci.genai.passPhrase | Optional private key passphrase, used when authenticating with `simple` auth and a passphrase protected private key. | -
|
||||
| spring.ai.oci.genai.file | Path to OCI config file. Used when authenticating with `file` auth. | <user's home directory>/.oci/config
|
||||
| spring.ai.oci.genai.profile | OCI profile name. Used when authenticating with `file` auth. | DEFAULT
|
||||
| spring.ai.oci.genai.endpoint | Optional OCI GenAI endpoint. | -
|
||||
|
||||
|====
|
||||
|
||||
|
||||
The prefix `spring.ai.oci.genai.embedding` is the property prefix that configures the `EmbeddingModel` implementation for OCI GenAI
|
||||
|
||||
[cols="3,5,1"]
|
||||
|====
|
||||
| Property | Description | Default
|
||||
|
||||
| spring.ai.oci.genai.embedding.enabled | Enable OCI GenAI embedding model. | true
|
||||
| spring.ai.oci.genai.embedding.compartment | Model compartment OCID. | -
|
||||
| spring.ai.oci.genai.embedding.servingMode | The model serving mode to be used. May be `on-demand`, or `dedicated`. | on-demand
|
||||
| spring.ai.oci.genai.embedding.truncate | How to truncate text if it overruns the embedding context. May be `START`, or `END`. | END
|
||||
| spring.ai.oci.genai.embedding.model | The model or model endpoint used for embeddings. | -
|
||||
|====
|
||||
|
||||
TIP: All properties prefixed with `spring.ai.oci.genai.embedding.options` can be overridden at runtime by adding a request specific <<embedding-options>> to the `EmbeddingRequest` call.
|
||||
|
||||
== Runtime Options [[embedding-options]]
|
||||
|
||||
The `OCIEmbeddingOptions` provides the configuration information for the embedding requests.
|
||||
The `OCIEmbeddingOptions` offers a builder to create the options.
|
||||
|
||||
At start time use the `OCIEmbeddingOptions` constructor to set the default options used for all embedding requests.
|
||||
At run-time you can override the default options, by passing a `OCIEmbeddingOptions` instance with your to the `EmbeddingRequest` request.
|
||||
|
||||
For example to override the default model name for a specific request:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
EmbeddingResponse embeddingResponse = embeddingModel.call(
|
||||
new EmbeddingRequest(List.of("Hello World", "World is big and salvation is near"),
|
||||
OCIEmbeddingOptions.builder()
|
||||
.withModel("my-other-embedding-model")
|
||||
.build()
|
||||
));
|
||||
----
|
||||
|
||||
|
||||
== Sample Code
|
||||
|
||||
This will create a `EmbeddingModel` implementation that you can inject into your class.
|
||||
Here is an example of a simple `@Controller` class that uses the `EmbeddingModel` implementation.
|
||||
|
||||
[source,application.properties]
|
||||
----
|
||||
spring.ai.oci.genai.embedding.model=<your model>
|
||||
spring.ai.oci.genai.embedding.compartment=<your model compartment>
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RestController
|
||||
public class EmbeddingController {
|
||||
|
||||
private final EmbeddingModel embeddingModel;
|
||||
|
||||
@Autowired
|
||||
public EmbeddingController(EmbeddingModel embeddingModel) {
|
||||
this.embeddingModel = embeddingModel;
|
||||
}
|
||||
|
||||
@GetMapping("/ai/embedding")
|
||||
public Map embed(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
|
||||
EmbeddingResponse embeddingResponse = this.embeddingModel.embedForResponse(List.of(message));
|
||||
return Map.of("embedding", embeddingResponse);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Manual Configuration
|
||||
|
||||
If you prefer not to use the Spring Boot auto-configuration, you can manually configure the `OCIEmbeddingModel` in your application.
|
||||
For this add the `spring-oci-genai-openai` dependency to your project's Maven `pom.xml` file:
|
||||
[source, xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-oci-genai-openai</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
or to your Gradle `build.gradle` build file.
|
||||
|
||||
[source,gradle]
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.ai:spring-oci-genai-openai'
|
||||
}
|
||||
----
|
||||
|
||||
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
|
||||
|
||||
Next, create an `OCIEmbeddingModel` instance and use it to compute the similarity between two input texts:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
final String EMBEDDING_MODEL = "cohere.embed-english-light-v2.0";
|
||||
final String CONFIG_FILE = Paths.get(System.getProperty("user.home"), ".oci", "config").toString();
|
||||
final String PROFILE = "DEFAULT";
|
||||
final String REGION = "us-chicago-1";
|
||||
final String COMPARTMENT_ID = System.getenv("OCI_COMPARTMENT_ID");
|
||||
|
||||
var authProvider = new ConfigFileAuthenticationDetailsProvider(
|
||||
CONFIG_FILE, PROFILE);
|
||||
var aiClient = GenerativeAiInferenceClient.builder()
|
||||
.region(Region.valueOf(REGION))
|
||||
.build(authProvider);
|
||||
var options = OCIEmbeddingOptions.builder()
|
||||
.withModel(EMBEDDING_MODEL)
|
||||
.withCompartment(COMPARTMENT_ID)
|
||||
.withServingMode("on-demand")
|
||||
.build();
|
||||
var embeddingModel = new OCIEmbeddingModel(aiClient, options);
|
||||
List<Double> embedding = embeddingModel.embed(new Document("How many provinces are in Canada?"));
|
||||
----
|
||||
@@ -63,6 +63,13 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-oci-genai</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-huggingface</artifactId>
|
||||
@@ -425,6 +432,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>oracle-free</artifactId>
|
||||
<version>1.19.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2024 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.autoconfigure.oci.genai;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Anders Swanson
|
||||
*/
|
||||
@ConfigurationProperties(OCIConnectionProperties.CONFIG_PREFIX)
|
||||
public class OCIConnectionProperties {
|
||||
|
||||
private static final String DEFAULT_PROFILE = "DEFAULT";
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.oci.genai";
|
||||
|
||||
public enum AuthenticationType {
|
||||
|
||||
FILE("file"), INSTANCE_PRINCIPAL("instance-principal"), WORKLOAD_IDENTITY("workload-identity"),
|
||||
SIMPLE("simple");
|
||||
|
||||
private final String authType;
|
||||
|
||||
AuthenticationType(String authType) {
|
||||
this.authType = authType;
|
||||
}
|
||||
|
||||
public String getAuthType() {
|
||||
return this.authType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AuthenticationType authenticationType = AuthenticationType.FILE;
|
||||
|
||||
private String profile;
|
||||
|
||||
private String file = Paths.get(System.getProperty("user.home"), ".oci", "config").toString();
|
||||
|
||||
private String tenantId;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String fingerprint;
|
||||
|
||||
private String privateKey;
|
||||
|
||||
private String passPhrase;
|
||||
|
||||
private String region = "us-chicago-1";
|
||||
|
||||
private String endpoint;
|
||||
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public void setRegion(String region) {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
public String getPassPhrase() {
|
||||
return passPhrase;
|
||||
}
|
||||
|
||||
public void setPassPhrase(String passPhrase) {
|
||||
this.passPhrase = passPhrase;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
public void setFingerprint(String fingerprint) {
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(String tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public String getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(String file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return StringUtils.hasText(profile) ? profile : DEFAULT_PROFILE;
|
||||
}
|
||||
|
||||
public void setProfile(String profile) {
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public AuthenticationType getAuthenticationType() {
|
||||
return authenticationType;
|
||||
}
|
||||
|
||||
public void setAuthenticationType(AuthenticationType authenticationType) {
|
||||
this.authenticationType = authenticationType;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2024 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.autoconfigure.oci.genai;
|
||||
|
||||
import com.oracle.bmc.generativeaiinference.model.EmbedTextDetails;
|
||||
import org.springframework.ai.oci.OCIEmbeddingOptions;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Anders Swanson
|
||||
*/
|
||||
@ConfigurationProperties(OCIEmbeddingModelProperties.CONFIG_PREFIX)
|
||||
public class OCIEmbeddingModelProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.oci.genai.embedding";
|
||||
|
||||
private ServingMode servingMode = ServingMode.ON_DEMAND;
|
||||
|
||||
private EmbedTextDetails.Truncate truncate = EmbedTextDetails.Truncate.End;
|
||||
|
||||
private String compartment;
|
||||
|
||||
private String model;
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
public OCIEmbeddingOptions getEmbeddingOptions() {
|
||||
return OCIEmbeddingOptions.builder()
|
||||
.withCompartment(compartment)
|
||||
.withModel(model)
|
||||
.withServingMode(servingMode.getMode())
|
||||
.withTruncate(truncate)
|
||||
.build();
|
||||
}
|
||||
|
||||
public ServingMode getServingMode() {
|
||||
return servingMode;
|
||||
}
|
||||
|
||||
public void setServingMode(ServingMode servingMode) {
|
||||
this.servingMode = servingMode;
|
||||
}
|
||||
|
||||
public String getCompartment() {
|
||||
return compartment;
|
||||
}
|
||||
|
||||
public void setCompartment(String compartment) {
|
||||
this.compartment = compartment;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public EmbedTextDetails.Truncate getTruncate() {
|
||||
return truncate;
|
||||
}
|
||||
|
||||
public void setTruncate(EmbedTextDetails.Truncate truncate) {
|
||||
this.truncate = truncate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2024 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.autoconfigure.oci.genai;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.oracle.bmc.ClientConfiguration;
|
||||
import com.oracle.bmc.Region;
|
||||
import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.SimplePrivateKeySupplier;
|
||||
import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
|
||||
import com.oracle.bmc.retrier.RetryConfiguration;
|
||||
import org.springframework.ai.oci.OCIEmbeddingModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Anders Swanson
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ GenerativeAiInferenceClient.class, OCIEmbeddingModel.class })
|
||||
@EnableConfigurationProperties({ OCIConnectionProperties.class, OCIEmbeddingModelProperties.class })
|
||||
public class OCIGenAiAutoConfiguration {
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION)
|
||||
.build();
|
||||
GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder()
|
||||
.configuration(clientConfiguration);
|
||||
if (StringUtils.hasText(properties.getRegion())) {
|
||||
builder.region(Region.valueOf(properties.getRegion()));
|
||||
}
|
||||
if (StringUtils.hasText(properties.getEndpoint())) {
|
||||
builder.endpoint(properties.getEndpoint());
|
||||
}
|
||||
return builder.build(authenticationProvider(properties));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = OCIEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCIEmbeddingModelProperties properties) {
|
||||
return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions());
|
||||
}
|
||||
|
||||
private static BasicAuthenticationDetailsProvider authenticationProvider(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
return switch (properties.getAuthenticationType()) {
|
||||
case FILE -> new ConfigFileAuthenticationDetailsProvider(properties.getFile(), properties.getProfile());
|
||||
case INSTANCE_PRINCIPAL -> InstancePrincipalsAuthenticationDetailsProvider.builder().build();
|
||||
case WORKLOAD_IDENTITY -> OkeWorkloadIdentityAuthenticationDetailsProvider.builder().build();
|
||||
case SIMPLE -> SimpleAuthenticationDetailsProvider.builder()
|
||||
.userId(properties.getUserId())
|
||||
.tenantId(properties.getTenantId())
|
||||
.fingerprint(properties.getFingerprint())
|
||||
.privateKeySupplier(new SimplePrivateKeySupplier(properties.getPrivateKey()))
|
||||
.passPhrase(properties.getPassPhrase())
|
||||
.region(Region.valueOf(properties.getRegion()))
|
||||
.build();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2024 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.autoconfigure.oci.genai;
|
||||
|
||||
/**
|
||||
* @author Anders Swanson
|
||||
*/
|
||||
public enum ServingMode {
|
||||
|
||||
ON_DEMAND("on-demand"), DEDICATED("dedicated");
|
||||
|
||||
private final String mode;
|
||||
|
||||
ServingMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.oci.genai.OCIGenAiAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.stabilityai.StabilityAiImageAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.transformers.TransformersEmbeddingModelAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.huggingface.HuggingfaceChatAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2024 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.autoconfigure.oci.genai;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.springframework.ai.embedding.EmbeddingRequest;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
import org.springframework.ai.oci.OCIEmbeddingModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@EnabledIfEnvironmentVariable(named = OCIGenAiAutoConfigurationIT.COMPARTMENT_ID_KEY, matches = ".+")
|
||||
public class OCIGenAiAutoConfigurationIT {
|
||||
|
||||
public static final String COMPARTMENT_ID_KEY = "OCI_COMPARTMENT_ID";
|
||||
|
||||
private final String CONFIG_FILE = Paths.get(System.getProperty("user.home"), ".oci", "config").toString();
|
||||
|
||||
private final String COMPARTMENT_ID = System.getenv(COMPARTMENT_ID_KEY);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.oci.genai.authenticationType=file",
|
||||
"spring.ai.oci.genai.file=" + CONFIG_FILE,
|
||||
"spring.ai.oci.genai.embedding.compartment=" + COMPARTMENT_ID,
|
||||
"spring.ai.oci.genai.embedding.servingMode=on-demand",
|
||||
"spring.ai.oci.genai.embedding.model=cohere.embed-english-light-v2.0"
|
||||
// @formatter:on
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void embeddings() {
|
||||
contextRunner.run(context -> {
|
||||
OCIEmbeddingModel embeddingModel = context.getBean(OCIEmbeddingModel.class);
|
||||
assertThat(embeddingModel).isNotNull();
|
||||
EmbeddingResponse response = embeddingModel
|
||||
.call(new EmbeddingRequest(List.of("There are 50 states in the USA", "Canada has 10 provinces"), null));
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getResults()).hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-oci-genai-spring-boot-starter</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI Starter - OCI GenAI</name>
|
||||
<description>Spring AI OCI GenAI Auto Configuration</description>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
<connection>git://github.com/spring-projects/spring-ai.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-oci-genai</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Reference in New Issue
Block a user