diff --git a/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java index 7c5be59..d21e20c 100644 --- a/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -29,7 +29,7 @@ String chat(String question) { } @GetMapping("/chat/generic-options") - String chatWithGenericOptions(String question) { + String chatGenericOptions(String question) { return chatClient .prompt(question) .options(ChatOptionsBuilder.builder() @@ -41,7 +41,7 @@ String chatWithGenericOptions(String question) { } @GetMapping("/chat/provider-options") - String chatWithProviderOptions(String question) { + String chatProviderOptions(String question) { return chatClient .prompt(question) .options(MistralAiChatOptions.builder() diff --git a/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index f43eecf..f47463d 100644 --- a/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/01-chat-models/chat-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -29,7 +29,7 @@ String chat(String question) { } @GetMapping("/chat/generic-options") - String chatWithGenericOptions(String question) { + String chatGenericOptions(String question) { return chatModel.call(new Prompt(question, ChatOptionsBuilder.builder() .withModel(MistralAiApi.ChatModel.OPEN_MIXTRAL_7B.getName()) .withTemperature(0.9) @@ -38,7 +38,7 @@ String chatWithGenericOptions(String question) { } @GetMapping("/chat/provider-options") - String chatWithProviderOptions(String question) { + String chatProviderOptions(String question) { return chatModel.call(new Prompt(question, MistralAiChatOptions.builder() .withSafePrompt(true) .build())) diff --git a/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/ChatController.java index b0af509..bda6792 100644 --- a/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -41,7 +41,7 @@ String chatOpenAi(String question) { } @GetMapping("/chat/mistral-ai-options") - String chatWithMistralAiOptions(String question) { + String chatMistralAiOptions(String question) { return mistralAichatClient .prompt(question) .options(MistralAiChatOptions.builder() @@ -53,7 +53,7 @@ String chatWithMistralAiOptions(String question) { } @GetMapping("/chat/openai-options") - String chatWithOpenAiOptions(String question) { + String chatOpenAiOptions(String question) { return openAichatClient .prompt(question) .options(OpenAiChatOptions.builder() diff --git a/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index 4104038..ed0b3ad 100644 --- a/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/01-chat-models/chat-models-multiple-providers/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -37,7 +37,7 @@ String chatOpenAi(String question) { } @GetMapping("/chat/mistral-ai-options") - String chatWithMistralAiOptions(String question) { + String chatMistralAiOptions(String question) { return mistralAiChatModel.call(new Prompt(question, MistralAiChatOptions.builder() .withModel(MistralAiApi.ChatModel.OPEN_MIXTRAL_7B.getValue()) .withTemperature(1.0) @@ -46,7 +46,7 @@ String chatWithMistralAiOptions(String question) { } @GetMapping("/chat/openai-options") - String chatWithOpenAiOptions(String question) { + String chatOpenAiOptions(String question) { return openAiChatModel.call(new Prompt(question, OpenAiChatOptions.builder() .withModel(OpenAiApi.ChatModel.GPT_4_O_MINI.getValue()) .withTemperature(1.0) diff --git a/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java index 1ed1f76..4e1b170 100644 --- a/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -28,7 +28,7 @@ String chat(String question) { } @GetMapping("/chat/generic-options") - String chatWithGenericOptions(String question) { + String chatGenericOptions(String question) { return chatClient .prompt(question) .options(ChatOptionsBuilder.builder() @@ -40,7 +40,7 @@ String chatWithGenericOptions(String question) { } @GetMapping("/chat/provider-options") - String chatWithProviderOptions(String question) { + String chatProviderOptions(String question) { return chatClient .prompt(question) .options(OllamaOptions.builder() @@ -51,7 +51,7 @@ String chatWithProviderOptions(String question) { } @GetMapping("/chat/huggingface") - String chatWithHuggingFace(String question) { + String chatHuggingFace(String question) { return chatClient .prompt(question) .options(ChatOptionsBuilder.builder() diff --git a/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index 4e0bf60..dadb533 100644 --- a/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/01-chat-models/chat-models-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -28,7 +28,7 @@ String chat(String question) { } @GetMapping("/chat/generic-options") - String chatWithGenericOptions(String question) { + String chatGenericOptions(String question) { return chatModel.call(new Prompt(question, ChatOptionsBuilder.builder() .withModel("llama3.2:1b") .withTemperature(0.9) @@ -37,7 +37,7 @@ String chatWithGenericOptions(String question) { } @GetMapping("/chat/provider-options") - String chatWithProviderOptions(String question) { + String chatProviderOptions(String question) { return chatModel.call(new Prompt(question, OllamaOptions.builder() .withRepeatPenalty(1.5) .build())) @@ -45,7 +45,7 @@ String chatWithProviderOptions(String question) { } @GetMapping("/chat/huggingface") - String chatWithHuggingFace(String question) { + String chatHuggingFace(String question) { return chatModel.call(new Prompt(question, ChatOptionsBuilder.builder() .withModel("hf.co/SanctumAI/Llama-3.2-1B-Instruct-GGUF") .build())) diff --git a/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java index a2c44b3..9b151b6 100644 --- a/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -29,7 +29,7 @@ String chat(String question) { } @GetMapping("/chat/generic-options") - String chatWithGenericOptions(String question) { + String chatGenericOptions(String question) { return chatClient .prompt(question) .options(ChatOptionsBuilder.builder() @@ -41,7 +41,7 @@ String chatWithGenericOptions(String question) { } @GetMapping("/chat/provider-options") - String chatWithProviderOptions(String question) { + String chatProviderOptions(String question) { return chatClient .prompt(question) .options(OpenAiChatOptions.builder() diff --git a/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index b3b480a..60df13d 100644 --- a/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/01-chat-models/chat-models-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -29,7 +29,7 @@ String chat(String question) { } @GetMapping("/chat/generic-options") - String chatWithGenericOptions(String question) { + String chatGenericOptions(String question) { return chatModel.call(new Prompt(question, ChatOptionsBuilder.builder() .withModel(OpenAiApi.ChatModel.GPT_4_O_MINI.getValue()) .withTemperature(0.9) @@ -38,7 +38,7 @@ String chatWithGenericOptions(String question) { } @GetMapping("/chat/provider-options") - String chatWithProviderOptions(String question) { + String chatProviderOptions(String question) { return chatModel.call(new Prompt(question, OpenAiChatOptions.builder() .withLogprobs(true) .build())) diff --git a/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java index 1a8f46c..bc939cb 100644 --- a/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -23,7 +23,7 @@ public ChatController(ChatClient.Builder chatClientBuilder) { } @GetMapping("/chat/image/file") - String chatFromImageFile(String question) { + String chatImageFile(String question) { return chatClient.prompt() .user(userSpec -> userSpec .text(question) diff --git a/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index 5fa30b6..b9ac5de 100644 --- a/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/04-multimodality/multimodality-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -28,7 +28,7 @@ class ChatModelController { } @GetMapping("/chat/image/file") - String chatFromImageFile(String question) { + String chatImageFile(String question) { var userMessage = new UserMessage(question, new Media(MimeTypeUtils.IMAGE_PNG, image)); var prompt = new Prompt(userMessage); var chatResponse = chatModel.call(prompt); diff --git a/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java index 30cf7ee..b00c368 100644 --- a/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -27,7 +27,7 @@ public ChatController(ChatClient.Builder chatClientBuilder) { } @GetMapping("/chat/image/file") - String chatFromImageFile(String question) { + String chatImageFile(String question) { return chatClient.prompt() .user(userSpec -> userSpec .text(question) @@ -38,7 +38,7 @@ String chatFromImageFile(String question) { } @GetMapping("/chat/image/url") - String chatFromImageUrl(String question) throws MalformedURLException { + String chatImageUrl(String question) throws MalformedURLException { var imageUrl = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"; var url = URI.create(imageUrl).toURL(); return chatClient.prompt() diff --git a/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index 8b54e9e..64c5120 100644 --- a/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/04-multimodality/multimodality-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -31,7 +31,7 @@ class ChatModelController { } @GetMapping("/chat/image/file") - String chatFromImageFile(String question) { + String chatImageFile(String question) { var userMessage = new UserMessage(question, new Media(MimeTypeUtils.IMAGE_PNG, image)); var prompt = new Prompt(userMessage); var chatResponse = chatModel.call(prompt); @@ -39,7 +39,7 @@ String chatFromImageFile(String question) { } @GetMapping("/chat/image/url") - String chatFromImageUrl(String question) throws MalformedURLException { + String chatImageUrl(String question) throws MalformedURLException { var imageUrl = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"; var url = URI.create(imageUrl).toURL(); diff --git a/06-embedding-models/embedding-models-mistral-ai/README.md b/06-embedding-models/embedding-models-mistral-ai/README.md index a34d406..32b57b8 100644 --- a/06-embedding-models/embedding-models-mistral-ai/README.md +++ b/06-embedding-models/embedding-models-mistral-ai/README.md @@ -9,34 +9,37 @@ Spring AI provides an `EmbeddingModel` abstraction for integrating with LLMs via When using the _Spring AI Mistral AI Spring Boot Starter_, an `EmbeddingModel` object is autoconfigured for you to use Mistral AI. ```java -@RestController -class EmbeddingController { - private final EmbeddingModel embeddingModel; - - EmbeddingController(EmbeddingModel embeddingModel) { - this.embeddingModel = embeddingModel; - } - - @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); - return "Size of the embedding vector: " + embeddings.size(); - } +@Bean +CommandLineRunner embed(EmbeddingModel embeddingModel) { + return _ -> { + var embeddings = embeddingModel.embed("And Gandalf yelled: 'You shall not pass!'"); + System.out.println("Size of the embedding vector: " + embeddings.length); + }; } ``` -## Running the application +## Mistral AI The application relies on the Mistral AI API for providing LLMs. -First, make sure you have a [Mistral AI account](https://console.mistral.ai). -Then, define an environment variable with the Mistral AI API Key associated to your Mistral AI account as the value. +### Create a Mistral AI account + +Visit [https://console.mistral.ai](console.mistral.ai) and sign up for a new account. +You can choose the "Experiment" plan, which gives you access to the Mistral APIs for free. + +### Configure API Key + +In the Mistral AI console, navigate to _API Keys_ and generate a new API key. +Copy and securely store your API key on your machine as an environment variable. +The application will use it to access the Mistral AI API. ```shell -export SPRING_AI_MISTRALAI_API_KEY= +export MISTRALAI_API_KEY= ``` -Finally, run the Spring Boot application. +## Running the application + +Run the application. ```shell ./gradlew bootRun @@ -44,21 +47,23 @@ Finally, run the Spring Boot application. ## Calling the application -You can now call the application that will use Mistral AI and _mistral-embed_ to generate a vector representation (embeddings) of a default text. -This example uses [httpie](https://httpie.io) to send HTTP requests. +> [!NOTE] +> These examples use the [httpie](https://httpie.io) CLI to send HTTP requests. + +Call the application that will use an embedding model to generate embeddings for your query. ```shell -http :8080/embed +http :8080/embed query=="The capital of Italy is Rome" ``` -Try passing your custom prompt and check the result. +The next request is configured with generic portable options. ```shell -http :8080/embed message=="The capital of Italy is Rome" +http :8080/embed/generic-options query=="The capital of Italy is Rome" -b ``` -The next request is configured with Mistral AI-specific customizations. +The next request is configured with the provider's specific options. ```shell -http :8080/embed/mistral-ai-options message=="The capital of Italy is Rome" +http :8080/embed/provider-options query=="The capital of Italy is Rome" -b ``` diff --git a/06-embedding-models/embedding-models-mistral-ai/build.gradle b/06-embedding-models/embedding-models-mistral-ai/build.gradle index b3e2bfc..61ca316 100644 --- a/06-embedding-models/embedding-models-mistral-ai/build.gradle +++ b/06-embedding-models/embedding-models-mistral-ai/build.gradle @@ -25,11 +25,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.ai:spring-ai-mistral-ai-spring-boot-starter' - testAndDevelopmentOnly 'org.springframework.boot:spring-boot-devtools' + developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-testcontainers' - testImplementation 'org.testcontainers:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/06-embedding-models/embedding-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java b/06-embedding-models/embedding-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java index d196daf..0d025a2 100644 --- a/06-embedding-models/embedding-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java +++ b/06-embedding-models/embedding-models-mistral-ai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java @@ -1,10 +1,11 @@ package com.thomasvitale.ai.spring; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.mistralai.MistralAiEmbeddingOptions; +import org.springframework.ai.mistralai.api.MistralAiApi; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -19,15 +20,24 @@ class EmbeddingController { } @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); + String embed(String query) { + var embeddings = embeddingModel.embed(query); return "Size of the embedding vector: " + embeddings.length; } - @GetMapping("/embed/mistral-ai-options") - String embedWithMistralAiOptions(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(message), MistralAiEmbeddingOptions.builder() - .withModel("mistral-embed") + @GetMapping("/embed/generic-options") + String embedGenericOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), EmbeddingOptionsBuilder.builder() + .withModel(MistralAiApi.EmbeddingModel.EMBED.getValue()) + .build())) + .getResult().getOutput(); + return "Size of the embedding vector: " + embeddings.length; + } + + @GetMapping("/embed/provider-options") + String embedProviderOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), MistralAiEmbeddingOptions.builder() + .withEncodingFormat("float") .build())) .getResult().getOutput(); return "Size of the embedding vector: " + embeddings.length; diff --git a/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsMistralAiApplicationTests.java b/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsMistralAiApplicationTests.java index 4619167..29c1ad8 100644 --- a/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsMistralAiApplicationTests.java +++ b/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsMistralAiApplicationTests.java @@ -1,13 +1,37 @@ package com.thomasvitale.ai.spring; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; -@SpringBootTest +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWebTestClient(timeout = "60s") +@EnabledIfEnvironmentVariable(named = "MISTRALAI_API_KEY", matches = ".*") class EmbeddingModelsMistralAiApplicationTests { - @Test - void contextLoads() { + @Autowired + WebTestClient webTestClient; + + @ParameterizedTest + @ValueSource(strings = {"/embed", "/embed/generic-options", "/embed/provider-options"}) + void chat(String path) { + webTestClient + .get() + .uri(uriBuilder -> uriBuilder + .path(path) + .queryParam("query", "Rivendell") + .build()) + .exchange() + .expectStatus().isOk() + .expectBody(String.class).value(result -> { + assertThat(result).containsIgnoringCase("1024"); + }); } } diff --git a/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsMistralAiApplication.java b/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsMistralAiApplication.java deleted file mode 100644 index ded9d88..0000000 --- a/06-embedding-models/embedding-models-mistral-ai/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsMistralAiApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.thomasvitale.ai.spring; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.test.context.TestConfiguration; - -@TestConfiguration(proxyBeanMethods = false) -public class TestEmbeddingModelsMistralAiApplication { - - public static void main(String[] args) { - SpringApplication.from(EmbeddingModelsMistralAiApplication::main).with(TestEmbeddingModelsMistralAiApplication.class).run(args); - } - -} diff --git a/06-embedding-models/embedding-models-ollama/README.md b/06-embedding-models/embedding-models-ollama/README.md index 7c57136..d4cb515 100644 --- a/06-embedding-models/embedding-models-ollama/README.md +++ b/06-embedding-models/embedding-models-ollama/README.md @@ -9,40 +9,33 @@ Spring AI provides an `EmbeddingModel` abstraction for integrating with LLMs via When using the _Spring AI Ollama Spring Boot Starter_, an `EmbeddingModel` object is autoconfigured for you to use Ollama. ```java -@RestController -class EmbeddingController { - private final EmbeddingModel embeddingModel; - - EmbeddingController(EmbeddingModel embeddingModel) { - this.embeddingModel = embeddingModel; - } - - @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); - return "Size of the embedding vector: " + embeddings.size(); - } +@Bean +CommandLineRunner embed(EmbeddingModel embeddingModel) { + return _ -> { + var embeddings = embeddingModel.embed("And Gandalf yelled: 'You shall not pass!'"); + System.out.println("Size of the embedding vector: " + embeddings.length); + }; } ``` -## Running the application - -The application relies on Ollama for providing LLMs. You can either run Ollama locally on your laptop, or rely on the Testcontainers support in Spring Boot to spin up an Ollama service automatically. -Either way, Spring AI will take care of pulling the needed Ollama models if not already available in your instance. +## Ollama -### Ollama as a native application +The application relies on Ollama for providing LLMs. You can either run Ollama locally on your laptop, +or rely on the Testcontainers support in Spring Boot to spin up an Ollama service automatically. +If you choose the first option, make sure you have [Ollama](https://ollama.ai) installed and running on your laptop. +Either way, Spring AI will take care of pulling the needed Ollama models when the application starts, +if they are not available yet on your machine. -First, make sure you have [Ollama](https://ollama.ai) installed on your laptop. +## Running the application -Then, run the Spring Boot application. +If you're using the native Ollama application, run the application as follows. ```shell ./gradlew bootRun ``` -### Ollama as a dev service with Testcontainers - -The application relies on the native Testcontainers support in Spring Boot to spin up an Ollama service at startup time. +If you want to rely on the native Testcontainers support in Spring Boot to spin up an Ollama service at startup time, +run the application as follows. ```shell ./gradlew bootTestRun @@ -50,21 +43,23 @@ The application relies on the native Testcontainers support in Spring Boot to sp ## Calling the application -You can now call the application that will use Ollama to generate a vector representation (embeddings) of a default text. -This example uses [httpie](https://httpie.io) to send HTTP requests. +> [!NOTE] +> These examples use the [httpie](https://httpie.io) CLI to send HTTP requests. + +Call the application that will use an embedding model to generate embeddings for your query. ```shell -http :8080/embed +http :8080/embed query=="The capital of Italy is Rome" ``` -Try passing your custom prompt and check the result. +The next request is configured with generic portable options. ```shell -http :8080/embed message=="The capital of Italy is Rome" +http :8080/embed/generic-options query=="The capital of Italy is Rome" -b ``` -The next request is configured with custom options. +The next request is configured with the provider's specific options. ```shell -http :8080/embed/generic-options message=="The capital of Italy is Rome" +http :8080/embed/provider-options query=="The capital of Italy is Rome" -b ``` diff --git a/06-embedding-models/embedding-models-ollama/build.gradle b/06-embedding-models/embedding-models-ollama/build.gradle index 0264598..dcc3499 100644 --- a/06-embedding-models/embedding-models-ollama/build.gradle +++ b/06-embedding-models/embedding-models-ollama/build.gradle @@ -29,11 +29,9 @@ dependencies { testAndDevelopmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-starter-webflux' - testImplementation 'org.springframework.boot:spring-boot-testcontainers' testImplementation 'org.springframework.ai:spring-ai-spring-boot-testcontainers' - testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.testcontainers:ollama' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/06-embedding-models/embedding-models-ollama/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java b/06-embedding-models/embedding-models-ollama/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java index 2c84e93..56073e3 100644 --- a/06-embedding-models/embedding-models-ollama/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java +++ b/06-embedding-models/embedding-models-ollama/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java @@ -4,8 +4,8 @@ import org.springframework.ai.embedding.EmbeddingOptionsBuilder; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.ollama.api.OllamaModel; +import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -20,18 +20,27 @@ class EmbeddingController { } @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); + String embed(String query) { + var embeddings = embeddingModel.embed(query); return "Size of the embedding vector: " + embeddings.length; } @GetMapping("/embed/generic-options") - String embedWithGenericOptions(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(message), EmbeddingOptionsBuilder.builder() + String embedGenericOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), EmbeddingOptionsBuilder.builder() .withModel(OllamaModel.NOMIC_EMBED_TEXT.getName()) .build())) .getResult().getOutput(); return "Size of the embedding vector: " + embeddings.length; } + @GetMapping("/embed/provider-options") + String embedProviderOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), OllamaOptions.builder() + .withLowVRAM(true) + .build())) + .getResult().getOutput(); + return "Size of the embedding vector: " + embeddings.length; + } + } diff --git a/06-embedding-models/embedding-models-ollama/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOllamaApplicationTests.java b/06-embedding-models/embedding-models-ollama/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOllamaApplicationTests.java index 4614b27..087aea6 100644 --- a/06-embedding-models/embedding-models-ollama/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOllamaApplicationTests.java +++ b/06-embedding-models/embedding-models-ollama/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOllamaApplicationTests.java @@ -19,11 +19,14 @@ class EmbeddingModelsOllamaApplicationTests { WebTestClient webTestClient; @ParameterizedTest - @ValueSource(strings = {"/embed", "/embed/generic-options"}) + @ValueSource(strings = {"/embed", "/embed/generic-options", "/embed/provider-options"}) void chat(String path) { webTestClient .get() - .uri(path) + .uri(uriBuilder -> uriBuilder + .path(path) + .queryParam("query", "Rivendell") + .build()) .exchange() .expectStatus().isOk() .expectBody(String.class).value(result -> { diff --git a/06-embedding-models/embedding-models-openai/README.md b/06-embedding-models/embedding-models-openai/README.md index def22d6..a4c49e2 100644 --- a/06-embedding-models/embedding-models-openai/README.md +++ b/06-embedding-models/embedding-models-openai/README.md @@ -9,36 +9,36 @@ Spring AI provides an `EmbeddingModel` abstraction for integrating with LLMs via When using the _Spring AI OpenAI Spring Boot Starter_, an `EmbeddingModel` object is autoconfigured for you to use OpenAI. ```java -@RestController -class EmbeddingController { - private final EmbeddingModel embeddingModel; - - EmbeddingController(EmbeddingModel embeddingModel) { - this.embeddingModel = embeddingModel; - } - - @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); - return "Size of the embedding vector: " + embeddings.size(); - } +@Bean +CommandLineRunner embed(EmbeddingModel embeddingModel) { + return _ -> { + var embeddings = embeddingModel.embed("And Gandalf yelled: 'You shall not pass!'"); + System.out.println("Size of the embedding vector: " + embeddings.length); + }; } ``` -## Running the application +## OpenAI + +The application relies on the OpenAI API for providing LLMs. + +### Create an OpenAI account -The application relies on an OpenAI API for providing LLMs. +Visit [https://platform.openai.com](platform.openai.com) and sign up for a new account. -### When using OpenAI +### Configure API Key -First, make sure you have an OpenAI account. -Then, define an environment variable with the OpenAI API Key associated to your OpenAI account as the value. +In the OpenAI console, navigate to _Dashboard > API Keys_ and generate a new API key. +Copy and securely store your API key on your machine as an environment variable. +The application will use it to access the OpenAI API. ```shell -export SPRING_AI_OPENAI_API_KEY= +export OPENAI_API_KEY= ``` -Finally, run the Spring Boot application. +## Running the application + +Run the application. ```shell ./gradlew bootRun @@ -46,21 +46,23 @@ Finally, run the Spring Boot application. ## Calling the application -You can now call the application that will use OpenAI and _text-embedding-ada-002_ to generate a vector representation (embeddings) of a default text. -This example uses [httpie](https://httpie.io) to send HTTP requests. +> [!NOTE] +> These examples use the [httpie](https://httpie.io) CLI to send HTTP requests. + +Call the application that will use an embedding model to generate embeddings for your query. ```shell -http :8080/embed +http :8080/embed query=="The capital of Italy is Rome" ``` -Try passing your custom prompt and check the result. +The next request is configured with generic portable options. ```shell -http :8080/embed message=="The capital of Italy is Rome" +http :8080/embed/generic-options query=="The capital of Italy is Rome" -b ``` -The next request is configured with OpenAI-specific customizations. +The next request is configured with the provider's specific options. ```shell -http :8080/embed/openai-options message=="The capital of Italy is Rome" +http :8080/embed/provider-options query=="The capital of Italy is Rome" -b ``` diff --git a/06-embedding-models/embedding-models-openai/build.gradle b/06-embedding-models/embedding-models-openai/build.gradle index ee09a76..54d165c 100644 --- a/06-embedding-models/embedding-models-openai/build.gradle +++ b/06-embedding-models/embedding-models-openai/build.gradle @@ -25,11 +25,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter' - testAndDevelopmentOnly 'org.springframework.boot:spring-boot-devtools' + developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-testcontainers' - testImplementation 'org.testcontainers:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/06-embedding-models/embedding-models-openai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java b/06-embedding-models/embedding-models-openai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java index 6c295af..b83cae2 100644 --- a/06-embedding-models/embedding-models-openai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java +++ b/06-embedding-models/embedding-models-openai/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java @@ -1,10 +1,11 @@ package com.thomasvitale.ai.spring; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.openai.OpenAiEmbeddingOptions; +import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -19,16 +20,24 @@ class EmbeddingController { } @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); + String embed(String query) { + var embeddings = embeddingModel.embed(query); return "Size of the embedding vector: " + embeddings.length; } - @GetMapping("/embed/openai-options") - String embedWithOpenAiOptions(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(message), OpenAiEmbeddingOptions.builder() - .withModel("text-embedding-3-small") - .withUser("jon.snow") + @GetMapping("/embed/generic-options") + String embedGenericOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), EmbeddingOptionsBuilder.builder() + .withModel(OpenAiApi.EmbeddingModel.TEXT_EMBEDDING_3_SMALL.getValue()) + .build())) + .getResult().getOutput(); + return "Size of the embedding vector: " + embeddings.length; + } + + @GetMapping("/embed/provider-options") + String embedProviderOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), OpenAiEmbeddingOptions.builder() + .withEncodingFormat("float") .build())) .getResult().getOutput(); return "Size of the embedding vector: " + embeddings.length; diff --git a/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOpenAiApplicationTests.java b/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOpenAiApplicationTests.java index b5ec201..83dadbe 100644 --- a/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOpenAiApplicationTests.java +++ b/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsOpenAiApplicationTests.java @@ -1,13 +1,37 @@ package com.thomasvitale.ai.spring; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; -@SpringBootTest +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWebTestClient(timeout = "60s") +@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") class EmbeddingModelsOpenAiApplicationTests { - @Test - void contextLoads() { + @Autowired + WebTestClient webTestClient; + + @ParameterizedTest + @ValueSource(strings = {"/embed", "/embed/generic-options", "/embed/provider-options"}) + void chat(String path) { + webTestClient + .get() + .uri(uriBuilder -> uriBuilder + .path(path) + .queryParam("query", "Rivendell") + .build()) + .exchange() + .expectStatus().isOk() + .expectBody(String.class).value(result -> { + assertThat(result).containsIgnoringCase("1536"); + }); } } diff --git a/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsOpenAiApplication.java b/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsOpenAiApplication.java deleted file mode 100644 index 9140ca5..0000000 --- a/06-embedding-models/embedding-models-openai/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsOpenAiApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.thomasvitale.ai.spring; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.test.context.TestConfiguration; - -@TestConfiguration(proxyBeanMethods = false) -public class TestEmbeddingModelsOpenAiApplication { - - public static void main(String[] args) { - SpringApplication.from(EmbeddingModelsOpenAiApplication::main).with(TestEmbeddingModelsOpenAiApplication.class).run(args); - } - -} diff --git a/06-embedding-models/embedding-models-transformers/README.md b/06-embedding-models/embedding-models-transformers/README.md index f5b1acb..9a964eb 100644 --- a/06-embedding-models/embedding-models-transformers/README.md +++ b/06-embedding-models/embedding-models-transformers/README.md @@ -6,28 +6,28 @@ Vector transformation (embeddings) with LLMs via ONNX Sentence Transformers. Spring AI provides an `EmbeddingModel` abstraction for integrating with LLMs via several providers, including ONNX Sentence Transformers. -When using the _Spring AI Transformers Spring Boot Starter_, an `EmbeddingModel` object is autoconfigured for you to use ONNX Sentence Transformers. +When using the _Spring AI Transformers Spring Boot Starter_, an `EmbeddingModel` object is autoconfigured for you to ONNX Sentence Transformers. ```java -@RestController -class EmbeddingController { - private final EmbeddingModel embeddingModel; - - EmbeddingController(EmbeddingModel embeddingModel) { - this.embeddingModel = embeddingModel; - } - - @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); - return "Size of the embedding vector: " + embeddings.size(); - } +@Bean +CommandLineRunner embed(EmbeddingModel embeddingModel) { + return _ -> { + var embeddings = embeddingModel.embed("And Gandalf yelled: 'You shall not pass!'"); + System.out.println("Size of the embedding vector: " + embeddings.length); + }; } ``` +## ONNX Sentence Transformers + +The application relies on the ONNX Sentence Transformers for providing LLMs. +ONNX provides an in-process runtime to run model inference directly in Java. +Spring AI will take care of pulling the needed models when the application starts, +if they are not available yet on your machine. + ## Running the application -Run the Spring Boot application. +Run the application. ```shell ./gradlew bootRun @@ -35,15 +35,17 @@ Run the Spring Boot application. ## Calling the application -You can now call the application that will use the _all-MiniLM-L6-v2_ model from HuggingFace to generate a vector representation (embeddings) of a default text. -This example uses [httpie](https://httpie.io) to send HTTP requests. +> [!NOTE] +> These examples use the [httpie](https://httpie.io) CLI to send HTTP requests. + +Call the application that will use an embedding model to generate embeddings for your query. ```shell -http :8080/embed +http :8080/embed query=="The capital of Italy is Rome" ``` -Try passing your custom prompt and check the result. +The next request is configured with generic portable options. ```shell -http :8080/embed message=="The capital of Italy is Rome" +http :8080/embed/generic-options query=="The capital of Italy is Rome" -b ``` diff --git a/06-embedding-models/embedding-models-transformers/build.gradle b/06-embedding-models/embedding-models-transformers/build.gradle index f902bdb..062e06d 100644 --- a/06-embedding-models/embedding-models-transformers/build.gradle +++ b/06-embedding-models/embedding-models-transformers/build.gradle @@ -25,11 +25,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.ai:spring-ai-transformers-spring-boot-starter' - testAndDevelopmentOnly 'org.springframework.boot:spring-boot-devtools' + developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-testcontainers' - testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.springframework:spring-webflux' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/06-embedding-models/embedding-models-transformers/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java b/06-embedding-models/embedding-models-transformers/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java index 3d67768..3f2796b 100644 --- a/06-embedding-models/embedding-models-transformers/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java +++ b/06-embedding-models/embedding-models-transformers/src/main/java/com/thomasvitale/ai/spring/EmbeddingController.java @@ -1,10 +1,13 @@ package com.thomasvitale.ai.spring; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingOptionsBuilder; +import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController class EmbeddingController { @@ -15,8 +18,17 @@ class EmbeddingController { } @GetMapping("/embed") - String embed(@RequestParam(defaultValue = "And Gandalf yelled: 'You shall not pass!'") String message) { - var embeddings = embeddingModel.embed(message); + String embed(String query) { + var embeddings = embeddingModel.embed(query); + return "Size of the embedding vector: " + embeddings.length; + } + + @GetMapping("/embed/generic-options") + String embedGenericOptions(String query) { + var embeddings = embeddingModel.call(new EmbeddingRequest(List.of(query), EmbeddingOptionsBuilder.builder() + .withDimensions(384) + .build())) + .getResult().getOutput(); return "Size of the embedding vector: " + embeddings.length; } diff --git a/06-embedding-models/embedding-models-transformers/src/main/resources/application.yml b/06-embedding-models/embedding-models-transformers/src/main/resources/application.yml index ecc4d15..364d5f7 100644 --- a/06-embedding-models/embedding-models-transformers/src/main/resources/application.yml +++ b/06-embedding-models/embedding-models-transformers/src/main/resources/application.yml @@ -1,8 +1,8 @@ spring: -# ai: -# embedding: -# transformer: -# onnx: -# model-uri: https://huggingface.co/intfloat/e5-small-v2/raw/main/model.onnx -# tokenizer: -# uri: https://huggingface.co/intfloat/e5-small-v2/raw/main/tokenizer.json + ai: + embedding: + transformer: + onnx: + model-uri: https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx + tokenizer: + uri: https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/raw/main/tokenizer.json diff --git a/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsTransformersApplicationTests.java b/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsTransformersApplicationTests.java index f748c0b..0c48f28 100644 --- a/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsTransformersApplicationTests.java +++ b/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/EmbeddingModelsTransformersApplicationTests.java @@ -1,13 +1,35 @@ package com.thomasvitale.ai.spring; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; -@SpringBootTest +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWebTestClient(timeout = "60s") class EmbeddingModelsTransformersApplicationTests { - @Test - void contextLoads() { + @Autowired + WebTestClient webTestClient; + + @ParameterizedTest + @ValueSource(strings = {"/embed", "/embed/generic-options"}) + void chat(String path) { + webTestClient + .get() + .uri(uriBuilder -> uriBuilder + .path(path) + .queryParam("query", "Rivendell") + .build()) + .exchange() + .expectStatus().isOk() + .expectBody(String.class).value(result -> { + assertThat(result).containsIgnoringCase("384"); + }); } } diff --git a/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsTransformersApplication.java b/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsTransformersApplication.java deleted file mode 100644 index 30c8e2d..0000000 --- a/06-embedding-models/embedding-models-transformers/src/test/java/com/thomasvitale/ai/spring/TestEmbeddingModelsTransformersApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.thomasvitale.ai.spring; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.test.context.TestConfiguration; - -@TestConfiguration(proxyBeanMethods = false) -public class TestEmbeddingModelsTransformersApplication { - - public static void main(String[] args) { - SpringApplication.from(EmbeddingModelsTransformersApplication::main).with(TestEmbeddingModelsTransformersApplication.class).run(args); - } - -}