From cc6540089f43cda49ed5990733bb39c404267676 Mon Sep 17 00:00:00 2001 From: Thomas Vitale Date: Sat, 20 Jan 2024 22:47:55 +0100 Subject: [PATCH] Update output parsers and embedding samples --- .../TestChatModelsOllamaApplication.java | 4 + 01-chat-models/chat-models-openai/README.md | 2 +- .../chat-models-openai/build.gradle | 2 + .../ai/spring/ChatController.java | 2 +- .../TestChatModelsOpenaiApplication.java | 13 ++++ 02-prompts/prompts-basics-ollama/README.md | 17 +++++ .../src/main/resources/application.yml | 2 +- .../TestPromptBasicsOllamaApplication.java | 16 ++++ 02-prompts/prompts-basics-openai/README.md | 17 +++++ .../TestPromptsBasicsOpenAiApplication.java | 13 ++++ 02-prompts/prompts-messages-ollama/.gitignore | 37 +++++++++ 02-prompts/prompts-messages-ollama/README.md | 17 +++++ .../prompts-messages-ollama/build.gradle | 34 +++++++++ .../ai/spring/ChatController.java | 31 ++++++++ .../thomasvitale/ai/spring/ChatService.java | 53 +++++++++++++ .../PromptMessagesOllamaApplication.java | 13 ++++ .../src/main/resources/application.yml | 10 +++ .../main/resources/prompts/system-message.st | 3 + .../PromptMessagesOllamaApplicationTests.java | 13 ++++ .../TestPromptMessagesOllamaApplication.java | 29 +++++++ 02-prompts/prompts-messages-openai/README.md | 17 +++++ .../thomasvitale/ai/spring/ChatService.java | 3 +- .../main/resources/prompts/system-message.st | 5 +- .../TestPromptsMessagesOpenAiApplication.java | 13 ++++ .../prompts-templates-ollama/.gitignore | 37 +++++++++ 02-prompts/prompts-templates-ollama/README.md | 17 +++++ .../prompts-templates-ollama/build.gradle | 34 +++++++++ .../ai/spring/ChatController.java | 31 ++++++++ .../thomasvitale/ai/spring/ChatService.java | 75 +++++++++++++++++++ .../thomasvitale/ai/spring/MusicQuestion.java | 3 + .../PromptTemplatesOllamaApplication.java | 13 ++++ .../src/main/resources/application.yml | 10 +++ .../main/resources/prompts/system-message.st | 2 + ...PromptTemplatesOllamaApplicationTests.java | 13 ++++ .../TestPromptTemplatesOllamaApplication.java | 29 +++++++ 02-prompts/prompts-templates-openai/README.md | 17 +++++ .../main/resources/prompts/system-message.st | 3 +- ...TestPromptsTemplatesOpenAiApplication.java | 13 ++++ .../output-parsers-ollama/.gitignore | 37 +++++++++ .../output-parsers-ollama/README.md | 17 +++++ .../output-parsers-ollama/build.gradle | 34 +++++++++ .../thomasvitale/ai/spring/ArtistInfo.java | 3 + .../ai/spring/ChatController.java | 34 +++++++++ .../thomasvitale/ai/spring/ChatService.java | 68 +++++++++++++++++ .../thomasvitale/ai/spring/MusicQuestion.java | 3 + .../OutputParsersOllamaApplication.java | 13 ++++ .../src/main/resources/application.yml | 10 +++ .../main/resources/prompts/system-message.st | 2 + .../OutputParsersOllamaApplicationTests.java | 13 ++++ .../TestOutputParsersOllamaApplication.java | 29 +++++++ .../output-parsers-openai/README.md | 17 +++++ .../TestPromptsBasicsOpenAiApplication.java | 13 ++++ buildSrc/build.gradle | 2 +- settings.gradle | 3 + 54 files changed, 953 insertions(+), 8 deletions(-) create mode 100644 01-chat-models/chat-models-openai/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOpenaiApplication.java create mode 100644 02-prompts/prompts-basics-ollama/README.md create mode 100644 02-prompts/prompts-basics-openai/README.md create mode 100644 02-prompts/prompts-basics-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java create mode 100644 02-prompts/prompts-messages-ollama/.gitignore create mode 100644 02-prompts/prompts-messages-ollama/README.md create mode 100644 02-prompts/prompts-messages-ollama/build.gradle create mode 100644 02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java create mode 100644 02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java create mode 100644 02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplication.java create mode 100644 02-prompts/prompts-messages-ollama/src/main/resources/application.yml create mode 100644 02-prompts/prompts-messages-ollama/src/main/resources/prompts/system-message.st create mode 100644 02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplicationTests.java create mode 100644 02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptMessagesOllamaApplication.java create mode 100644 02-prompts/prompts-messages-openai/README.md create mode 100644 02-prompts/prompts-messages-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsMessagesOpenAiApplication.java create mode 100644 02-prompts/prompts-templates-ollama/.gitignore create mode 100644 02-prompts/prompts-templates-ollama/README.md create mode 100644 02-prompts/prompts-templates-ollama/build.gradle create mode 100644 02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java create mode 100644 02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java create mode 100644 02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java create mode 100644 02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplication.java create mode 100644 02-prompts/prompts-templates-ollama/src/main/resources/application.yml create mode 100644 02-prompts/prompts-templates-ollama/src/main/resources/prompts/system-message.st create mode 100644 02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplicationTests.java create mode 100644 02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptTemplatesOllamaApplication.java create mode 100644 02-prompts/prompts-templates-openai/README.md create mode 100644 02-prompts/prompts-templates-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsTemplatesOpenAiApplication.java create mode 100644 03-output-parsers/output-parsers-ollama/.gitignore create mode 100644 03-output-parsers/output-parsers-ollama/README.md create mode 100644 03-output-parsers/output-parsers-ollama/build.gradle create mode 100644 03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ArtistInfo.java create mode 100644 03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java create mode 100644 03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java create mode 100644 03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java create mode 100644 03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplication.java create mode 100644 03-output-parsers/output-parsers-ollama/src/main/resources/application.yml create mode 100644 03-output-parsers/output-parsers-ollama/src/main/resources/prompts/system-message.st create mode 100644 03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplicationTests.java create mode 100644 03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/TestOutputParsersOllamaApplication.java create mode 100644 03-output-parsers/output-parsers-openai/README.md create mode 100644 03-output-parsers/output-parsers-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java diff --git a/01-chat-models/chat-models-ollama/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOllamaApplication.java b/01-chat-models/chat-models-ollama/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOllamaApplication.java index 0189ddf..1aea43d 100644 --- a/01-chat-models/chat-models-ollama/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOllamaApplication.java +++ b/01-chat-models/chat-models-ollama/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOllamaApplication.java @@ -1,8 +1,10 @@ package com.thomasvitale.ai.spring; import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.restart.RestartScope; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; import org.springframework.test.context.DynamicPropertyRegistry; import org.testcontainers.containers.GenericContainer; @@ -10,6 +12,8 @@ public class TestChatModelsOllamaApplication { @Bean + @RestartScope + @Scope("singleton") // needed because of https://github.com/spring-projects/spring-boot/issues/35786 GenericContainer ollama(DynamicPropertyRegistry properties) { var ollama = new GenericContainer<>("ghcr.io/thomasvitale/ollama-llama2") .withExposedPorts(11434); diff --git a/01-chat-models/chat-models-openai/README.md b/01-chat-models/chat-models-openai/README.md index d1d095c..ec55ed6 100644 --- a/01-chat-models/chat-models-openai/README.md +++ b/01-chat-models/chat-models-openai/README.md @@ -1,7 +1,7 @@ # Chat Models: OpenAI ```shell -./gradlew bootRun +./gradlew bootTestRun ``` ```shell diff --git a/01-chat-models/chat-models-openai/build.gradle b/01-chat-models/chat-models-openai/build.gradle index f434c31..bfb9516 100644 --- a/01-chat-models/chat-models-openai/build.gradle +++ b/01-chat-models/chat-models-openai/build.gradle @@ -25,6 +25,8 @@ dependencies { testAndDevelopmentOnly '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' } tasks.named('test') { 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 3244510..c1ee1b0 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 @@ -15,7 +15,7 @@ class ChatController { } @GetMapping("/ai/chat") - String chat(@RequestParam(defaultValue = "What does Gandalf say to the Balrog?") String message) { + String chat(@RequestParam(defaultValue = "What did Gandalf say to the Balrog?") String message) { return chatClient.generate(message); } diff --git a/01-chat-models/chat-models-openai/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOpenaiApplication.java b/01-chat-models/chat-models-openai/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOpenaiApplication.java new file mode 100644 index 0000000..e9fdb7a --- /dev/null +++ b/01-chat-models/chat-models-openai/src/test/java/com/thomasvitale/ai/spring/TestChatModelsOpenaiApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; + +@TestConfiguration(proxyBeanMethods = false) +public class TestChatModelsOpenaiApplication { + + public static void main(String[] args) { + SpringApplication.from(ChatModelsOpenaiApplication::main).with(TestChatModelsOpenaiApplication.class).run(args); + } + +} diff --git a/02-prompts/prompts-basics-ollama/README.md b/02-prompts/prompts-basics-ollama/README.md new file mode 100644 index 0000000..a9a94df --- /dev/null +++ b/02-prompts/prompts-basics-ollama/README.md @@ -0,0 +1,17 @@ +# Prompts Basic: Ollama + +```shell +./gradlew bootTestRun +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/simple +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/prompt +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/full +``` diff --git a/02-prompts/prompts-basics-ollama/src/main/resources/application.yml b/02-prompts/prompts-basics-ollama/src/main/resources/application.yml index d566438..201a08d 100644 --- a/02-prompts/prompts-basics-ollama/src/main/resources/application.yml +++ b/02-prompts/prompts-basics-ollama/src/main/resources/application.yml @@ -2,7 +2,7 @@ spring: ai: ollama: chat: - model: mistral + model: llama2 options: temperature: 0.7 threads: diff --git a/02-prompts/prompts-basics-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptBasicsOllamaApplication.java b/02-prompts/prompts-basics-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptBasicsOllamaApplication.java index 7fd51e4..f90cab1 100644 --- a/02-prompts/prompts-basics-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptBasicsOllamaApplication.java +++ b/02-prompts/prompts-basics-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptBasicsOllamaApplication.java @@ -1,11 +1,27 @@ package com.thomasvitale.ai.spring; import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.restart.RestartScope; import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.GenericContainer; @TestConfiguration(proxyBeanMethods = false) public class TestPromptBasicsOllamaApplication { + @Bean + @RestartScope + @Scope("singleton") // needed because of https://github.com/spring-projects/spring-boot/issues/35786 + GenericContainer ollama(DynamicPropertyRegistry properties) { + var ollama = new GenericContainer<>("ghcr.io/thomasvitale/ollama-llama2") + .withExposedPorts(11434); + properties.add("spring.ai.ollama.base-url", + () -> "http://%s:%s".formatted(ollama.getHost(), ollama.getMappedPort(11434))); + return ollama; + } + public static void main(String[] args) { SpringApplication.from(PromptsBasicsOllamaApplication::main).with(TestPromptBasicsOllamaApplication.class).run(args); } diff --git a/02-prompts/prompts-basics-openai/README.md b/02-prompts/prompts-basics-openai/README.md new file mode 100644 index 0000000..f7d29e8 --- /dev/null +++ b/02-prompts/prompts-basics-openai/README.md @@ -0,0 +1,17 @@ +# Prompts Basic: OpenAI + +```shell +./gradlew bootTestRun +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/simple +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/prompt +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/full +``` diff --git a/02-prompts/prompts-basics-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java b/02-prompts/prompts-basics-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java new file mode 100644 index 0000000..18dc6ac --- /dev/null +++ b/02-prompts/prompts-basics-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; + +@TestConfiguration(proxyBeanMethods = false) +public class TestPromptsBasicsOpenAiApplication { + + public static void main(String[] args) { + SpringApplication.from(PromptsBasicsOpenAiApplication::main).with(TestPromptsBasicsOpenAiApplication.class).run(args); + } + +} diff --git a/02-prompts/prompts-messages-ollama/.gitignore b/02-prompts/prompts-messages-ollama/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/02-prompts/prompts-messages-ollama/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/02-prompts/prompts-messages-ollama/README.md b/02-prompts/prompts-messages-ollama/README.md new file mode 100644 index 0000000..1e41e4e --- /dev/null +++ b/02-prompts/prompts-messages-ollama/README.md @@ -0,0 +1,17 @@ +# Prompts Messages: Ollama + +```shell +./gradlew bootTestRun +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/single +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/multiple +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/external +``` diff --git a/02-prompts/prompts-messages-ollama/build.gradle b/02-prompts/prompts-messages-ollama/build.gradle new file mode 100644 index 0000000..a0ede90 --- /dev/null +++ b/02-prompts/prompts-messages-ollama/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'io.spring.dependency-management' +} + +group = 'com.thomasvitale' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '21' +} + +repositories { + mavenCentral() + maven { url 'https://repo.spring.io/milestone' } + maven { url 'https://repo.spring.io/snapshot' } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation "org.springframework.ai:spring-ai-ollama-spring-boot-starter:${springAiVersion}" + + testAndDevelopmentOnly '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' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java new file mode 100644 index 0000000..ebcbba2 --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -0,0 +1,31 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +class ChatController { + + private final ChatService chatService; + + ChatController(ChatService chatService) { + this.chatService = chatService; + } + + @PostMapping("/ai/chat/single") + String chatWithSingleMessage(@RequestBody String input) { + return chatService.chatWithSingleMessage(input).getContent(); + } + + @PostMapping("/ai/chat/multiple") + String chatWithMultipleMessages(@RequestBody String input) { + return chatService.chatWithMultipleMessages(input).getContent(); + } + + @PostMapping("/ai/chat/external") + String chatWithExternalMessage(@RequestBody String input) { + return chatService.chatWithExternalMessage(input).getContent(); + } + +} diff --git a/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java new file mode 100644 index 0000000..0e56cd3 --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -0,0 +1,53 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.ai.chat.ChatClient; +import org.springframework.ai.prompt.Prompt; +import org.springframework.ai.prompt.messages.AssistantMessage; +import org.springframework.ai.prompt.messages.SystemMessage; +import org.springframework.ai.prompt.messages.UserMessage; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +class ChatService { + + private final ChatClient chatClient; + + private final Resource systemMessageResource; + + ChatService(ChatClient chatClient, @Value("classpath:/prompts/system-message.st") Resource systemMessageResource) { + this.chatClient = chatClient; + this.systemMessageResource = systemMessageResource; + } + + AssistantMessage chatWithSingleMessage(String message) { + var userMessage = new UserMessage(message); + var prompt = new Prompt(userMessage); + var chatResponse = chatClient.generate(prompt); + return new AssistantMessage(chatResponse.getGeneration().getContent(), chatResponse.getGeneration().getProperties()); + } + + AssistantMessage chatWithMultipleMessages(String message) { + var systemMessage = new SystemMessage(""" + You are a helpful and polite assistant. + Answer in one sentence using a very formal language + and starting the answer with a formal greeting. + """); + var userMessage = new UserMessage(message); + var prompt = new Prompt(List.of(systemMessage, userMessage)); + var chatResponse = chatClient.generate(prompt); + return new AssistantMessage(chatResponse.getGeneration().getContent(), chatResponse.getGeneration().getProperties()); + } + + AssistantMessage chatWithExternalMessage(String message) { + var systemMessage = new SystemMessage(systemMessageResource); + var userMessage = new UserMessage(message); + var prompt = new Prompt(List.of(systemMessage, userMessage)); + var chatResponse = chatClient.generate(prompt); + return new AssistantMessage(chatResponse.getGeneration().getContent(), chatResponse.getGeneration().getProperties()); + } + +} diff --git a/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplication.java b/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplication.java new file mode 100644 index 0000000..a8a8852 --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/main/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PromptMessagesOllamaApplication { + + public static void main(String[] args) { + SpringApplication.run(PromptMessagesOllamaApplication.class, args); + } + +} diff --git a/02-prompts/prompts-messages-ollama/src/main/resources/application.yml b/02-prompts/prompts-messages-ollama/src/main/resources/application.yml new file mode 100644 index 0000000..201a08d --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/main/resources/application.yml @@ -0,0 +1,10 @@ +spring: + ai: + ollama: + chat: + model: llama2 + options: + temperature: 0.7 + threads: + virtual: + enabled: true diff --git a/02-prompts/prompts-messages-ollama/src/main/resources/prompts/system-message.st b/02-prompts/prompts-messages-ollama/src/main/resources/prompts/system-message.st new file mode 100644 index 0000000..f369734 --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/main/resources/prompts/system-message.st @@ -0,0 +1,3 @@ +You are a funny and hilarious assistant. +Answer in one sentence using a very informal language +and starting the answer with a knock knowck joke. \ No newline at end of file diff --git a/02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplicationTests.java b/02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplicationTests.java new file mode 100644 index 0000000..630a25b --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/PromptMessagesOllamaApplicationTests.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class PromptMessagesOllamaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptMessagesOllamaApplication.java b/02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptMessagesOllamaApplication.java new file mode 100644 index 0000000..278532c --- /dev/null +++ b/02-prompts/prompts-messages-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptMessagesOllamaApplication.java @@ -0,0 +1,29 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.restart.RestartScope; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.GenericContainer; + +@TestConfiguration(proxyBeanMethods = false) +public class TestPromptMessagesOllamaApplication { + + @Bean + @RestartScope + @Scope("singleton") // needed because of https://github.com/spring-projects/spring-boot/issues/35786 + GenericContainer ollama(DynamicPropertyRegistry properties) { + var ollama = new GenericContainer<>("ghcr.io/thomasvitale/ollama-llama2") + .withExposedPorts(11434); + properties.add("spring.ai.ollama.base-url", + () -> "http://%s:%s".formatted(ollama.getHost(), ollama.getMappedPort(11434))); + return ollama; + } + + public static void main(String[] args) { + SpringApplication.from(PromptMessagesOllamaApplication::main).with(TestPromptMessagesOllamaApplication.class).run(args); + } + +} diff --git a/02-prompts/prompts-messages-openai/README.md b/02-prompts/prompts-messages-openai/README.md new file mode 100644 index 0000000..1bf01fe --- /dev/null +++ b/02-prompts/prompts-messages-openai/README.md @@ -0,0 +1,17 @@ +# Prompts Messages: OpenAI + +```shell +./gradlew bootTestRun +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/single +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/multiple +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/external +``` diff --git a/02-prompts/prompts-messages-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/02-prompts/prompts-messages-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java index ca52b26..0e56cd3 100644 --- a/02-prompts/prompts-messages-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java +++ b/02-prompts/prompts-messages-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -33,7 +33,8 @@ AssistantMessage chatWithSingleMessage(String message) { AssistantMessage chatWithMultipleMessages(String message) { var systemMessage = new SystemMessage(""" You are a helpful and polite assistant. - Answer in one sentence. + Answer in one sentence using a very formal language + and starting the answer with a formal greeting. """); var userMessage = new UserMessage(message); var prompt = new Prompt(List.of(systemMessage, userMessage)); diff --git a/02-prompts/prompts-messages-openai/src/main/resources/prompts/system-message.st b/02-prompts/prompts-messages-openai/src/main/resources/prompts/system-message.st index 80ec0a9..f369734 100644 --- a/02-prompts/prompts-messages-openai/src/main/resources/prompts/system-message.st +++ b/02-prompts/prompts-messages-openai/src/main/resources/prompts/system-message.st @@ -1,2 +1,3 @@ -You are a funny and polite assistant. -Answer in one sentence. \ No newline at end of file +You are a funny and hilarious assistant. +Answer in one sentence using a very informal language +and starting the answer with a knock knowck joke. \ No newline at end of file diff --git a/02-prompts/prompts-messages-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsMessagesOpenAiApplication.java b/02-prompts/prompts-messages-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsMessagesOpenAiApplication.java new file mode 100644 index 0000000..bf7d348 --- /dev/null +++ b/02-prompts/prompts-messages-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsMessagesOpenAiApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; + +@TestConfiguration(proxyBeanMethods = false) +public class TestPromptsMessagesOpenAiApplication { + + public static void main(String[] args) { + SpringApplication.from(PromptsMessagesOpenAiApplication::main).with(TestPromptsMessagesOpenAiApplication.class).run(args); + } + +} diff --git a/02-prompts/prompts-templates-ollama/.gitignore b/02-prompts/prompts-templates-ollama/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/02-prompts/prompts-templates-ollama/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/02-prompts/prompts-templates-ollama/README.md b/02-prompts/prompts-templates-ollama/README.md new file mode 100644 index 0000000..73e9b89 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/README.md @@ -0,0 +1,17 @@ +# Prompts Templates: Ollama + +```shell +./gradlew bootTestRun +``` + +```shell +http :8080/ai/chat/user genre="rock" instrument="piano" +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/system +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/external +``` diff --git a/02-prompts/prompts-templates-ollama/build.gradle b/02-prompts/prompts-templates-ollama/build.gradle new file mode 100644 index 0000000..a0ede90 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'io.spring.dependency-management' +} + +group = 'com.thomasvitale' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '21' +} + +repositories { + mavenCentral() + maven { url 'https://repo.spring.io/milestone' } + maven { url 'https://repo.spring.io/snapshot' } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation "org.springframework.ai:spring-ai-ollama-spring-boot-starter:${springAiVersion}" + + testAndDevelopmentOnly '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' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java new file mode 100644 index 0000000..c5cfc29 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -0,0 +1,31 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +class ChatController { + + private final ChatService chatService; + + ChatController(ChatService chatService) { + this.chatService = chatService; + } + + @PostMapping("/ai/chat/user") + String chatWithUserMessageTemplate(@RequestBody MusicQuestion question) { + return chatService.chatWithUserMessageTemplate(question).getContent(); + } + + @PostMapping("/ai/chat/system") + String chatWithSystemMessageTemplate(@RequestBody String question) { + return chatService.chatWithSystemMessageTemplate(question).getContent(); + } + + @PostMapping("/ai/chat/external") + String chatWithSystemMessageTemplateExternal(@RequestBody String question) { + return chatService.chatWithSystemMessageTemplateExternal(question).getContent(); + } + +} diff --git a/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java new file mode 100644 index 0000000..8a2773d --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -0,0 +1,75 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.ai.chat.ChatClient; +import org.springframework.ai.prompt.Prompt; +import org.springframework.ai.prompt.PromptTemplate; +import org.springframework.ai.prompt.SystemPromptTemplate; +import org.springframework.ai.prompt.messages.AssistantMessage; +import org.springframework.ai.prompt.messages.UserMessage; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Random; + +@Service +class ChatService { + + private final ChatClient chatClient; + + private final Resource systemMessageResource; + + ChatService(ChatClient chatClient, @Value("classpath:/prompts/system-message.st") Resource systemMessageResource) { + this.chatClient = chatClient; + this.systemMessageResource = systemMessageResource; + } + + AssistantMessage chatWithUserMessageTemplate(MusicQuestion question) { + var userPromptTemplate = new PromptTemplate(""" + Tell me name and band of three musicians famous for playing in a {genre} band. + Consider only the musicians that play the {instrument} in that band. + """); + Map model = Map.of("instrument", question.instrument(), "genre", question.genre()); + var userMessage = userPromptTemplate.createMessage(model); + + var prompt = new Prompt(userMessage); + var chatResponse = chatClient.generate(prompt); + return new AssistantMessage(chatResponse.getGeneration().getContent(), chatResponse.getGeneration().getProperties()); + } + + AssistantMessage chatWithSystemMessageTemplate(String message) { + var systemPromptTemplate = new SystemPromptTemplate(""" + You are a helpful assistant that always replies starting with {greeting}. + """); + Map model = Map.of("greeting", randomGreeting()); + var systemMessage = systemPromptTemplate.createMessage(model); + + var userMessage = new UserMessage(message); + + var prompt = new Prompt(List.of(systemMessage, userMessage)); + + var chatResponse = chatClient.generate(prompt); + return new AssistantMessage(chatResponse.getGeneration().getContent(), chatResponse.getGeneration().getProperties()); + } + + AssistantMessage chatWithSystemMessageTemplateExternal(String message) { + var systemPromptTemplate = new SystemPromptTemplate(systemMessageResource); + Map model = Map.of("greeting", randomGreeting()); + var systemMessage = systemPromptTemplate.createMessage(model); + + var userMessage = new UserMessage(message); + + var prompt = new Prompt(List.of(systemMessage, userMessage)); + + var chatResponse = chatClient.generate(prompt); + return new AssistantMessage(chatResponse.getGeneration().getContent(), chatResponse.getGeneration().getProperties()); + } + + private String randomGreeting() { + var names = List.of("Howdy", "Ahoy", "Well, well, well"); + return names.get(new Random().nextInt(names.size())); + } + +} diff --git a/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java new file mode 100644 index 0000000..2068af4 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java @@ -0,0 +1,3 @@ +package com.thomasvitale.ai.spring; + +record MusicQuestion(String genre, String instrument){} diff --git a/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplication.java b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplication.java new file mode 100644 index 0000000..61b4342 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PromptTemplatesOllamaApplication { + + public static void main(String[] args) { + SpringApplication.run(PromptTemplatesOllamaApplication.class, args); + } + +} diff --git a/02-prompts/prompts-templates-ollama/src/main/resources/application.yml b/02-prompts/prompts-templates-ollama/src/main/resources/application.yml new file mode 100644 index 0000000..201a08d --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/main/resources/application.yml @@ -0,0 +1,10 @@ +spring: + ai: + ollama: + chat: + model: llama2 + options: + temperature: 0.7 + threads: + virtual: + enabled: true diff --git a/02-prompts/prompts-templates-ollama/src/main/resources/prompts/system-message.st b/02-prompts/prompts-templates-ollama/src/main/resources/prompts/system-message.st new file mode 100644 index 0000000..70add63 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/main/resources/prompts/system-message.st @@ -0,0 +1,2 @@ +You are a helpful assistant that always replies starting with {greeting} +and a knock knock joke. \ No newline at end of file diff --git a/02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplicationTests.java b/02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplicationTests.java new file mode 100644 index 0000000..7a6b448 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/PromptTemplatesOllamaApplicationTests.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class PromptTemplatesOllamaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptTemplatesOllamaApplication.java b/02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptTemplatesOllamaApplication.java new file mode 100644 index 0000000..739bed0 --- /dev/null +++ b/02-prompts/prompts-templates-ollama/src/test/java/com/thomasvitale/ai/spring/TestPromptTemplatesOllamaApplication.java @@ -0,0 +1,29 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.restart.RestartScope; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.GenericContainer; + +@TestConfiguration(proxyBeanMethods = false) +public class TestPromptTemplatesOllamaApplication { + + @Bean + @RestartScope + @Scope("singleton") // needed because of https://github.com/spring-projects/spring-boot/issues/35786 + GenericContainer ollama(DynamicPropertyRegistry properties) { + var ollama = new GenericContainer<>("ghcr.io/thomasvitale/ollama-llama2") + .withExposedPorts(11434); + properties.add("spring.ai.ollama.base-url", + () -> "http://%s:%s".formatted(ollama.getHost(), ollama.getMappedPort(11434))); + return ollama; + } + + public static void main(String[] args) { + SpringApplication.from(PromptTemplatesOllamaApplication::main).with(TestPromptTemplatesOllamaApplication.class).run(args); + } + +} diff --git a/02-prompts/prompts-templates-openai/README.md b/02-prompts/prompts-templates-openai/README.md new file mode 100644 index 0000000..9f9ff3c --- /dev/null +++ b/02-prompts/prompts-templates-openai/README.md @@ -0,0 +1,17 @@ +# Prompts Templates: OpenAI + +```shell +./gradlew bootTestRun +``` + +```shell +http :8080/ai/chat/user genre="rock" instrument="piano" +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/system +``` + +```shell +http --raw "What is the capital of Italy?" :8080/ai/chat/external +``` diff --git a/02-prompts/prompts-templates-openai/src/main/resources/prompts/system-message.st b/02-prompts/prompts-templates-openai/src/main/resources/prompts/system-message.st index bc2ba63..70add63 100644 --- a/02-prompts/prompts-templates-openai/src/main/resources/prompts/system-message.st +++ b/02-prompts/prompts-templates-openai/src/main/resources/prompts/system-message.st @@ -1 +1,2 @@ -You are a helpful assistant that always replies starting with {greeting}. \ No newline at end of file +You are a helpful assistant that always replies starting with {greeting} +and a knock knock joke. \ No newline at end of file diff --git a/02-prompts/prompts-templates-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsTemplatesOpenAiApplication.java b/02-prompts/prompts-templates-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsTemplatesOpenAiApplication.java new file mode 100644 index 0000000..f7d18e1 --- /dev/null +++ b/02-prompts/prompts-templates-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsTemplatesOpenAiApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; + +@TestConfiguration(proxyBeanMethods = false) +public class TestPromptsTemplatesOpenAiApplication { + + public static void main(String[] args) { + SpringApplication.from(PromptsTemplatesOpenAiApplication::main).with(TestPromptsTemplatesOpenAiApplication.class).run(args); + } + +} diff --git a/03-output-parsers/output-parsers-ollama/.gitignore b/03-output-parsers/output-parsers-ollama/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/03-output-parsers/output-parsers-ollama/README.md b/03-output-parsers/output-parsers-ollama/README.md new file mode 100644 index 0000000..d0f227f --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/README.md @@ -0,0 +1,17 @@ +# Output Parsers: Ollama + +```shell +./gradlew bootTestRun +``` + +```shell +http :8080/ai/chat/bean genre="rock" instrument="piano" +``` + +```shell +http :8080/ai/chat/map genre="rock" instrument="piano" +``` + +```shell +http :8080/ai/chat/list genre="rock" instrument="piano" +``` diff --git a/03-output-parsers/output-parsers-ollama/build.gradle b/03-output-parsers/output-parsers-ollama/build.gradle new file mode 100644 index 0000000..a0ede90 --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'io.spring.dependency-management' +} + +group = 'com.thomasvitale' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '21' +} + +repositories { + mavenCentral() + maven { url 'https://repo.spring.io/milestone' } + maven { url 'https://repo.spring.io/snapshot' } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation "org.springframework.ai:spring-ai-ollama-spring-boot-starter:${springAiVersion}" + + testAndDevelopmentOnly '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' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ArtistInfo.java b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ArtistInfo.java new file mode 100644 index 0000000..b59f98d --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ArtistInfo.java @@ -0,0 +1,3 @@ +package com.thomasvitale.ai.spring; + +public record ArtistInfo(String name, String band) {} diff --git a/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java new file mode 100644 index 0000000..080aad5 --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -0,0 +1,34 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +@RestController +class ChatController { + + private final ChatService chatService; + + ChatController(ChatService chatService) { + this.chatService = chatService; + } + + @PostMapping("/ai/chat/bean") + ArtistInfo chatWithBeanOutput(@RequestBody MusicQuestion question) { + return chatService.chatWithBeanOutput(question); + } + + @PostMapping("/ai/chat/map") + Map chatWithMapOutput(@RequestBody MusicQuestion question) { + return chatService.chatWithMapOutput(question); + } + + @PostMapping("/ai/chat/list") + List chatWithListOutput(@RequestBody MusicQuestion question) { + return chatService.chatWithListOutput(question); + } + +} diff --git a/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java new file mode 100644 index 0000000..0b1d7bf --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -0,0 +1,68 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.ai.chat.ChatClient; +import org.springframework.ai.parser.BeanOutputParser; +import org.springframework.ai.parser.ListOutputParser; +import org.springframework.ai.parser.MapOutputParser; +import org.springframework.ai.prompt.PromptTemplate; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +class ChatService { + + private final ChatClient chatClient; + + ChatService(ChatClient chatClient) { + this.chatClient = chatClient; + } + + ArtistInfo chatWithBeanOutput(MusicQuestion question) { + var outputParser = new BeanOutputParser<>(ArtistInfo.class); + + var userPromptTemplate = new PromptTemplate(""" + Tell me name and band of one musician famous for playing in a {genre} band. + Consider only the musicians that play the {instrument} in that band. + {format} + """); + Map model = Map.of("instrument", question.instrument(), "genre", question.genre(), "format", outputParser.getFormat()); + var prompt = userPromptTemplate.create(model); + + var chatResponse = chatClient.generate(prompt); + return outputParser.parse(chatResponse.getGeneration().getContent()); + } + + Map chatWithMapOutput(MusicQuestion question) { + var outputParser = new MapOutputParser(); + + var userPromptTemplate = new PromptTemplate(""" + Tell me name and band of one musician famous for playing in a {genre} band. + Consider only the musicians that play the {instrument} in that band. + {format} + """); + Map model = Map.of("instrument", question.instrument(), "genre", question.genre(), "format", outputParser.getFormat()); + var prompt = userPromptTemplate.create(model); + + var chatResponse = chatClient.generate(prompt); + return outputParser.parse(chatResponse.getGeneration().getContent()); + } + + List chatWithListOutput(MusicQuestion question) { + var outputParser = new ListOutputParser(new DefaultConversionService()); + + var userPromptTemplate = new PromptTemplate(""" + Tell me names of three musicians famous for playing in a {genre} band. + Consider only the musicians that play the {instrument} in that band. + {format} + """); + Map model = Map.of("instrument", question.instrument(), "genre", question.genre(), "format", outputParser.getFormat()); + var prompt = userPromptTemplate.create(model); + + var chatResponse = chatClient.generate(prompt); + return outputParser.parse(chatResponse.getGeneration().getContent()); + } + +} diff --git a/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java new file mode 100644 index 0000000..2068af4 --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/MusicQuestion.java @@ -0,0 +1,3 @@ +package com.thomasvitale.ai.spring; + +record MusicQuestion(String genre, String instrument){} diff --git a/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplication.java b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplication.java new file mode 100644 index 0000000..d3fc07d --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OutputParsersOllamaApplication { + + public static void main(String[] args) { + SpringApplication.run(OutputParsersOllamaApplication.class, args); + } + +} diff --git a/03-output-parsers/output-parsers-ollama/src/main/resources/application.yml b/03-output-parsers/output-parsers-ollama/src/main/resources/application.yml new file mode 100644 index 0000000..201a08d --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/resources/application.yml @@ -0,0 +1,10 @@ +spring: + ai: + ollama: + chat: + model: llama2 + options: + temperature: 0.7 + threads: + virtual: + enabled: true diff --git a/03-output-parsers/output-parsers-ollama/src/main/resources/prompts/system-message.st b/03-output-parsers/output-parsers-ollama/src/main/resources/prompts/system-message.st new file mode 100644 index 0000000..70add63 --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/main/resources/prompts/system-message.st @@ -0,0 +1,2 @@ +You are a helpful assistant that always replies starting with {greeting} +and a knock knock joke. \ No newline at end of file diff --git a/03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplicationTests.java b/03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplicationTests.java new file mode 100644 index 0000000..1e58113 --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/OutputParsersOllamaApplicationTests.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class OutputParsersOllamaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/TestOutputParsersOllamaApplication.java b/03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/TestOutputParsersOllamaApplication.java new file mode 100644 index 0000000..d9521e4 --- /dev/null +++ b/03-output-parsers/output-parsers-ollama/src/test/java/com/thomasvitale/ai/spring/TestOutputParsersOllamaApplication.java @@ -0,0 +1,29 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.restart.RestartScope; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.GenericContainer; + +@TestConfiguration(proxyBeanMethods = false) +public class TestOutputParsersOllamaApplication { + + @Bean + @RestartScope + @Scope("singleton") // needed because of https://github.com/spring-projects/spring-boot/issues/35786 + GenericContainer ollama(DynamicPropertyRegistry properties) { + var ollama = new GenericContainer<>("ghcr.io/thomasvitale/ollama-llama2") + .withExposedPorts(11434); + properties.add("spring.ai.ollama.base-url", + () -> "http://%s:%s".formatted(ollama.getHost(), ollama.getMappedPort(11434))); + return ollama; + } + + public static void main(String[] args) { + SpringApplication.from(OutputParsersOllamaApplication::main).with(TestOutputParsersOllamaApplication.class).run(args); + } + +} diff --git a/03-output-parsers/output-parsers-openai/README.md b/03-output-parsers/output-parsers-openai/README.md new file mode 100644 index 0000000..8d4b8cb --- /dev/null +++ b/03-output-parsers/output-parsers-openai/README.md @@ -0,0 +1,17 @@ +# Output Parsers: OpenAI + +```shell +./gradlew bootTestRun +``` + +```shell +http :8080/ai/chat/bean genre="rock" instrument="piano" +``` + +```shell +http :8080/ai/chat/map genre="rock" instrument="piano" +``` + +```shell +http :8080/ai/chat/list genre="rock" instrument="piano" +``` diff --git a/03-output-parsers/output-parsers-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java b/03-output-parsers/output-parsers-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java new file mode 100644 index 0000000..acac6eb --- /dev/null +++ b/03-output-parsers/output-parsers-openai/src/test/java/com/thomasvitale/ai/spring/TestPromptsBasicsOpenAiApplication.java @@ -0,0 +1,13 @@ +package com.thomasvitale.ai.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; + +@TestConfiguration(proxyBeanMethods = false) +public class TestPromptsBasicsOpenAiApplication { + + public static void main(String[] args) { + SpringApplication.from(OutputParsersBeanOpenAiApplication::main).with(TestPromptsBasicsOpenAiApplication.class).run(args); + } + +} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 3e28518..7dc6ca1 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -7,7 +7,7 @@ repositories { } ext { - set("springBootVersion", '3.2.1') + set("springBootVersion", '3.2.2') set("dependencyManagementVersion", '1.1.4') } diff --git a/settings.gradle b/settings.gradle index 2c7c0e5..9399055 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,9 +5,12 @@ include '01-chat-models:chat-models-openai' include '02-prompts:prompts-basics-ollama' include '02-prompts:prompts-basics-openai' +include '02-prompts:prompts-messages-ollama' include '02-prompts:prompts-messages-openai' +include '02-prompts:prompts-templates-ollama' include '02-prompts:prompts-templates-openai' +include '03-output-parsers:output-parsers-ollama' include '03-output-parsers:output-parsers-openai' include '04-embedding-models:embedding-models-openai'