diff --git a/.gitignore b/.gitignore index 549e00a..a8c9502 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +.env ### STS ### .apt_generated diff --git a/pom.xml b/pom.xml index 4fc80f5..30586fb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.0 + 3.5.9 dev.danvega @@ -28,16 +28,27 @@ 21 - 1.1.0 + 1.1.2 - + + me.paulschwarz + spring-dotenv + 4.0.0 + + org.springframework.boot spring-boot-starter-web org.springframework.ai spring-ai-starter-model-openai + compile + + + org.springframework.ai + spring-ai-starter-model-ollama + test org.springframework.ai @@ -91,6 +102,14 @@ pom import + + + org.testcontainers + testcontainers-bom + 1.21.1 + pom + import + diff --git a/src/main/java/dev/danvega/workshop/Application.java b/src/main/java/dev/danvega/workshop/Application.java index df07099..6d7c562 100644 --- a/src/main/java/dev/danvega/workshop/Application.java +++ b/src/main/java/dev/danvega/workshop/Application.java @@ -1,7 +1,13 @@ package dev.danvega.workshop; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import java.io.IOException; @@ -12,4 +18,15 @@ public static void main(String[] args) { SpringApplication.run(Application.class, args); } + @Bean + @Primary + public ChatModel primaryChatModel(OpenAiChatModel openAiChatModel) { + return openAiChatModel; + } + + @Bean + @Primary + public EmbeddingModel primaryEmbeddingModel(OpenAiEmbeddingModel openAiEmbeddingModel) { + return openAiEmbeddingModel; + } } diff --git a/src/main/java/dev/danvega/workshop/SimpleChatController.java b/src/main/java/dev/danvega/workshop/SimpleChatController.java index b86e822..77304be 100644 --- a/src/main/java/dev/danvega/workshop/SimpleChatController.java +++ b/src/main/java/dev/danvega/workshop/SimpleChatController.java @@ -1,6 +1,7 @@ package dev.danvega.workshop; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,7 +18,7 @@ public SimpleChatController(ChatClient.Builder builder) { @GetMapping("/java") public String chat() { return chatClient.prompt() - .user("Tell me an intresting fact about Java") + .user("Tell me an interesting fact about Java") .call() .content(); } diff --git a/src/main/java/dev/danvega/workshop/evals/ReviewService.java b/src/main/java/dev/danvega/workshop/evals/ReviewService.java index 0712a63..725d33b 100644 --- a/src/main/java/dev/danvega/workshop/evals/ReviewService.java +++ b/src/main/java/dev/danvega/workshop/evals/ReviewService.java @@ -4,6 +4,8 @@ import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.stereotype.Service; +import java.util.Objects; + @Service public class ReviewService { @@ -20,11 +22,12 @@ public Sentiment classifySentiment(String review) { Classify the sentiment of the following text as POSITIVE, NEGATIVE, or NEUTRAL. \ Your response must be only one of these three words."""; - return chatClient.prompt() - .system(systemPrompt) - .user(review) - .call() - .entity(Sentiment.class); + return Objects.requireNonNull(chatClient.prompt() + .system(systemPrompt) + .user(review) + .call() + .entity(SentimentResponse.class)) + .getSentiment(); } } diff --git a/src/main/java/dev/danvega/workshop/evals/SentimentResponse.java b/src/main/java/dev/danvega/workshop/evals/SentimentResponse.java new file mode 100644 index 0000000..218bd2c --- /dev/null +++ b/src/main/java/dev/danvega/workshop/evals/SentimentResponse.java @@ -0,0 +1,16 @@ +package dev.danvega.workshop.evals; + + +public class SentimentResponse { + private Sentiment sentiment; + + public SentimentResponse() {} + + public Sentiment getSentiment() { + return sentiment; + } + + public void setSentiment(Sentiment sentiment) { + this.sentiment = sentiment; + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 16cbed4..e0ceb82 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,9 +1,9 @@ spring.application.name=ai-workshop spring.ai.openai.api-key=${OPENAI_API_KEY} -spring.ai.openai.chat.options.model=gpt-4o +spring.ai.openai.chat.options.model=gpt-4o-mini spring.threads.virtual.enabled=true - spring.ai.ollama.chat.model=bespokeai/minicheck -spring.ai.ollama.chat.temperature=0.1 +spring.ai.ollama.chat.options.temperature=0.1 +spring.ai.ollama.base-url=http://localhost:11434 logging.level.dev.danvega.workshop=DEBUG \ No newline at end of file diff --git a/src/test/java/dev/danvega/workshop/evals/ChatClientTestConfig.java b/src/test/java/dev/danvega/workshop/evals/ChatClientTestConfig.java index 8e93885..64f651a 100644 --- a/src/test/java/dev/danvega/workshop/evals/ChatClientTestConfig.java +++ b/src/test/java/dev/danvega/workshop/evals/ChatClientTestConfig.java @@ -1,8 +1,13 @@ package dev.danvega.workshop.evals; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.evaluation.FactCheckingEvaluator; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; @TestConfiguration class ChatClientTestConfig { @@ -11,4 +16,11 @@ class ChatClientTestConfig { ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } + + + @Bean + public FactCheckingEvaluator factCheckingEvaluator(ChatClient.Builder chatClientBuilder) { + return FactCheckingEvaluator.builder(chatClientBuilder) + .build(); + } } diff --git a/src/test/java/dev/danvega/workshop/evals/FactCheckingEvaluatorTest.java b/src/test/java/dev/danvega/workshop/evals/FactCheckingEvaluatorTest.java index c0fd362..25cc786 100644 --- a/src/test/java/dev/danvega/workshop/evals/FactCheckingEvaluatorTest.java +++ b/src/test/java/dev/danvega/workshop/evals/FactCheckingEvaluatorTest.java @@ -8,8 +8,11 @@ import org.springframework.ai.evaluation.EvaluationRequest; import org.springframework.ai.evaluation.EvaluationResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.ollama.OllamaContainer; @@ -20,7 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest +@ActiveProfiles("test") @Testcontainers +@Import(ChatClientTestConfig.class) public class FactCheckingEvaluatorTest { private static final String OLLAMA_IMAGE = "ollama/ollama:0.1.48"; @@ -30,12 +35,13 @@ public class FactCheckingEvaluatorTest { @ServiceConnection(name = "spring.ai.ollama.base-url") static final OllamaContainer ollama = new OllamaContainer(OLLAMA_IMAGE); + @Autowired private FactCheckingEvaluator factCheckingEvaluator; - @BeforeEach - void setUp(@Autowired ChatClient.Builder builder) { - factCheckingEvaluator = new FactCheckingEvaluator(builder); - } +// @BeforeEach +// void setUp(@Autowired FactCheckingEvaluator evaluator) { +// factCheckingEvaluator = evaluator; +// } @Test void passes_when_claim_is_true() {