diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ApplicationConfiguration.java b/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ApplicationConfiguration.java new file mode 100644 index 0000000..7aaa6b9 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ApplicationConfiguration.java @@ -0,0 +1,17 @@ +package com.ks.perplexity; + +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration(proxyBeanMethods = false) +@Profile("perplexity") +public class ApplicationConfiguration { + + @Bean + public ChatService chatService(@Autowired OpenAiChatModel openAiChatModel) { + return new ChatService(openAiChatModel); + } +} diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ChatApplication.java b/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ChatApplication.java new file mode 100644 index 0000000..ea57643 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ChatApplication.java @@ -0,0 +1,12 @@ +package com.ks.perplexity; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ChatApplication { + public static void main(String[] args) { + SpringApplication.run(ChatApplication.class, args); + } + +} diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ChatService.java b/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ChatService.java new file mode 100644 index 0000000..f6c75b0 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/perplexity/ChatService.java @@ -0,0 +1,33 @@ +package com.ks.perplexity; + +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; + +public class ChatService { + private OpenAiChatModel chatModel; + + public ChatService(OpenAiChatModel chatModel) { + this.chatModel = chatModel; + } + + public String chat(String userInstruction) { + chatModel.call(userInstruction); + return chatModel.call(userInstruction); + } + + public String chat(String userInstruction, String model, String temperature) { + OpenAiChatOptions chatOptions = OpenAiChatOptions.builder() + .model(model) + .temperature(Double.parseDouble(temperature)) + .build(); + Prompt prompt = Prompt.builder() + .content(userInstruction) + .chatOptions(chatOptions) + .build(); + return chatModel.call(prompt) + .getResult() + .getOutput() + .getText(); + } +} diff --git a/springboot-modules/spring-ai/src/main/resources/application-perplexity.properties b/springboot-modules/spring-ai/src/main/resources/application-perplexity.properties new file mode 100644 index 0000000..0adc5cf --- /dev/null +++ b/springboot-modules/spring-ai/src/main/resources/application-perplexity.properties @@ -0,0 +1,8 @@ +spring.application.name=spring-ai-perplexity + +spring.ai.openai.api-key=pplx-xxxxxxx +spring.ai.openai.base-url=https://api.perplexity.ai +spring.ai.openai.chat.completions-path=/chat/completions + +spring.ai.openai.chat.options.model=sonar-pro +spring.ai.openai.chat.options.temperature=0.2 \ No newline at end of file diff --git a/springboot-modules/spring-ai/src/main/resources/puml/spring-openai-cld.puml b/springboot-modules/spring-ai/src/main/resources/puml/spring-openai-cld.puml new file mode 100644 index 0000000..8ea9aa2 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/resources/puml/spring-openai-cld.puml @@ -0,0 +1,68 @@ +@startuml +'https://plantuml.com/class-diagram +set namespaceSeparator none +scale 1 +skinparam padding 0 +skinparam ranksep 50 +hide empty attributes +skinparam Handwritten false +skinparam ClassBorderColor black +skinparam BackgroundColor #F0EDDE +skinparam ClassAttributeFontColor #222222 +skinparam ClassFontStyle bold + +skinparam class { + ArrowColor #3C88A3 + ArrowFontColor #3C88A3 + hide empty attributes + skinparam Handwritten false + skinparam ClassBorderColor black + BackgroundColor #FFFFFF +} + +interface ChatOptions { + +getTemperature() + +getMaxTokens() +} + +class OpenAiChatOptions { + +getTemperature(): double + +getMaxTokens(): int + +getModel(): String + +builder():OpenAiChatOptions.Builder +} + +class "OpenAiChatOptions.Builder" as ob { + +temperature(double):OpenAiChatOptions.Builder + +maxTokens(int):OpenAiChatOptions.Builder + +model(String):OpenAiChatOptions.Builder + +build(): OpenAiChatOptions +} + + +class "OpenAiChatModel" as om { + +call(Prompt prompt):ChatResponse + +call(Prompt prompt, OpenAiChatOptions chatOptions):ChatResponse + +call(String prompt):String + +getDefaultOptions():ChatOptions +} + +class "Prompt" as p { + +Prompt(String prompt, ChatOptions chatOptions) + +builder(): Prompt.Builder +} + +class "ChatResponse" as cr { + +getResult(): Generation +} + +ChatOptions <|.up. OpenAiChatOptions:implements + +ob ..> OpenAiChatOptions : builds + +om .down.> p : uses +om -up-> cr : call(Prompt):ChatResponse +om -right-> OpenAiChatOptions : uses + + +@enduml \ No newline at end of file diff --git a/springboot-modules/spring-ai/src/test/java/com/ks/perplexity/PerplexityOpenAiLiveTest.java b/springboot-modules/spring-ai/src/test/java/com/ks/perplexity/PerplexityOpenAiLiveTest.java new file mode 100644 index 0000000..eee728c --- /dev/null +++ b/springboot-modules/spring-ai/src/test/java/com/ks/perplexity/PerplexityOpenAiLiveTest.java @@ -0,0 +1,70 @@ +package com.ks.perplexity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("perplexity") +public class PerplexityOpenAiLiveTest { + + private final Logger logger = LoggerFactory.getLogger(PerplexityOpenAiLiveTest.class); + + @Autowired + private ChatService chatservice; + + @Autowired + private OpenAiChatModel openAiChatModel; + + @Test + void whenPerplexityChatClientAutoConfigured_thenExecute() { + String userPrompt = """ + Context: + 1. The book "The Stand" is a fantasy novel by Stephen King. + 2. "Sense and Sensibility" is a romance novel by Jane Austen, + published by Penguin Books. + 3. "The ABC Murders" is a mystery novel by Agatha Christie, + published by HarperCollins. + Question: + Name a book written by Agatha Christie, + based strictly on the context above. + """; + String response = chatservice.chat(userPrompt); + logger.info("Response from Perplexity: {}", response); + assertThat(response).isNotNull() + .isNotEmpty() + .containsIgnoringCase("The ABC Murders") + .doesNotContainIgnoringCase("The Stand", "Sense and Sensibility"); + } + + @Test + void whenPerplexityChatClientAutoConfigured_thenExamineClientProperties() { + ChatOptions chatOptions = openAiChatModel.getDefaultOptions(); + + assertThat(chatOptions).isInstanceOf(OpenAiChatOptions.class); + + assertThat(chatOptions.getModel()).isEqualTo("sonar-pro"); + + assertThat(chatOptions.getTemperature()).isEqualTo(0.2); + } + + @Test + void whenPerplexityChatClientCustomized_thenExecute() { + String userPrompt = """ + Who won the latest FIFA World Cup? Just the name of the country without any citations, please. + """; + String response = chatservice.chat(userPrompt, "sonar", "0.1"); + logger.info("Response from Perplexity: {}", response); + assertThat(response).isNotNull() + .isNotEmpty() + .containsIgnoringCase("Argentina"); + } +}