From 272af702c9b1b7ff5c3769ccaef6145d9d18547b Mon Sep 17 00:00:00 2001 From: Thomas Vitale Date: Wed, 28 Aug 2024 23:29:57 +0200 Subject: [PATCH] Update chat model examples --- .../chat-models-mistral-ai/README.md | 2 -- .../chat-models-multiple-providers/README.md | 2 -- .../ai/spring/ChatController.java | 2 +- .../ai/spring/model/ChatModelController.java | 2 +- 01-chat-models/chat-models-openai/README.md | 4 +-- .../ai/spring/ChatController.java | 4 +-- .../ai/spring/model/ChatModelController.java | 4 +-- 02-prompts/prompts-basics-openai/README.md | 2 -- 02-prompts/prompts-messages-openai/README.md | 2 -- .../ai/spring/model/ChatModelService.java | 4 +-- 02-prompts/prompts-templates-openai/README.md | 2 -- .../ai/spring/model/ChatModelService.java | 4 +-- .../thomasvitale/ai/spring/ChatService.java | 3 ++- .../structured-output-openai/README.md | 2 -- .../thomasvitale/ai/spring/ChatService.java | 4 +-- .../ai/spring/model/ChatModelService.java | 1 + .../src/main/resources/application.yml | 2 +- .../multimodality-openai/README.md | 2 -- .../function-calling-mistral-ai/README.md | 8 ++++-- .../thomasvitale/ai/spring/BookService.java | 2 +- .../ai/spring/ChatController.java | 5 ++++ .../thomasvitale/ai/spring/ChatService.java | 21 ++++++++++++++- .../ai/spring/model/ChatModelController.java | 5 ++++ .../ai/spring/model/ChatModelService.java | 27 ++++++++++++++++++- ...ctionCallingMistralAiApplicationTests.java | 2 +- .../function-calling-ollama/README.md | 6 +++++ .../thomasvitale/ai/spring/BookService.java | 2 +- .../ai/spring/ChatController.java | 5 ++++ .../thomasvitale/ai/spring/ChatService.java | 21 ++++++++++++++- .../ai/spring/model/ChatModelController.java | 5 ++++ .../ai/spring/model/ChatModelService.java | 27 ++++++++++++++++++- .../function-calling-openai/README.md | 8 ++++-- .../thomasvitale/ai/spring/BookService.java | 2 +- .../ai/spring/ChatController.java | 5 ++++ .../thomasvitale/ai/spring/ChatService.java | 21 ++++++++++++++- .../ai/spring/model/ChatModelController.java | 5 ++++ .../ai/spring/model/ChatModelService.java | 27 ++++++++++++++++++- ...FunctionCallingOpenAiApplicationTests.java | 2 +- .../embedding-models-mistral-ai/README.md | 2 -- 39 files changed, 208 insertions(+), 48 deletions(-) diff --git a/01-chat-models/chat-models-mistral-ai/README.md b/01-chat-models/chat-models-mistral-ai/README.md index 811caa8..f0ef70d 100644 --- a/01-chat-models/chat-models-mistral-ai/README.md +++ b/01-chat-models/chat-models-mistral-ai/README.md @@ -50,8 +50,6 @@ class ChatController { The application relies on the Mistral AI API for providing LLMs. -### When using Mistral AI - 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. diff --git a/01-chat-models/chat-models-multiple-providers/README.md b/01-chat-models/chat-models-multiple-providers/README.md index 5f9fe9e..aa66376 100644 --- a/01-chat-models/chat-models-multiple-providers/README.md +++ b/01-chat-models/chat-models-multiple-providers/README.md @@ -10,8 +10,6 @@ This example shows how to use both OpenAI and Mistral AI in the same application The application relies on OpenAI API and Mistral AI API for providing LLMs. -### When using OpenAI and Mistral AI - First, make sure you have an [OpenAI account](https://platform.openai.com/signup). Then, define an environment variable with the OpenAI API Key associated to your OpenAI account as the value. 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 96328fa..0bb48a1 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 @@ -56,7 +56,7 @@ String chatWithOpenAiOptions(@RequestParam(defaultValue = "What did Gandalf say return openAichatClient.prompt() .user(question) .options(OpenAiChatOptions.builder() - .withModel("gpt-4-turbo") + .withModel("gpt-4o-mini") .withTemperature(1.0f) .build()) .call() 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 a1612d8..7450546 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 @@ -47,7 +47,7 @@ String chatWithMistralAiOptions(@RequestParam(defaultValue = "What did Gandalf s @GetMapping("/chat/openai-options") String chatWithOpenAiOptions(@RequestParam(defaultValue = "What did Gandalf say to the Balrog?") String question) { return openAiChatModel.call(new Prompt(question, OpenAiChatOptions.builder() - .withModel("gpt-4-turbo") + .withModel("gpt-4o-mini") .withTemperature(1.0f) .build())) .getResult().getOutput().getContent(); diff --git a/01-chat-models/chat-models-openai/README.md b/01-chat-models/chat-models-openai/README.md index e391b0a..cdde5a8 100644 --- a/01-chat-models/chat-models-openai/README.md +++ b/01-chat-models/chat-models-openai/README.md @@ -50,8 +50,6 @@ class ChatController { The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - First, make sure you have an [OpenAI account](https://platform.openai.com/signup). Then, define an environment variable with the OpenAI API Key associated to your OpenAI account as the value. @@ -86,7 +84,7 @@ The next request is configured with a custom temperature value to obtain a more http :8080/chat/generic-options question=="Why is a raven like a writing desk? Give a short answer." -b ``` -The next request is configured with Open AI-specific customizations. +The next request is configured with OpenAI-specific customizations. ```shell http :8080/chat/provider-options question=="What can you see beyond what you can see? Give a short answer." -b 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 71f8fc3..889e646 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 @@ -44,9 +44,9 @@ String chatWithProviderOptions(@RequestParam(defaultValue = "What did Gandalf sa return chatClient.prompt() .user(question) .options(OpenAiChatOptions.builder() - .withModel("gpt-4-turbo") + .withModel("gpt-4o-mini") + .withTemperature(0.9f) .withUser("jon.snow") - .withFrequencyPenalty(1.3f) .build()) .call() .content(); 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 d8a1a91..063eb7b 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 @@ -39,9 +39,9 @@ String chatWithGenericOptions(@RequestParam(defaultValue = "What did Gandalf say @GetMapping("/chat/provider-options") String chatWithProviderOptions(@RequestParam(defaultValue = "What did Gandalf say to the Balrog?") String question) { return chatModel.call(new Prompt(question, OpenAiChatOptions.builder() - .withModel("gpt-4-turbo") + .withModel("gpt-4o-mini") + .withTemperature(0.9f) .withUser("jon.snow") - .withFrequencyPenalty(1.3f) .build())) .getResult().getOutput().getContent(); } diff --git a/02-prompts/prompts-basics-openai/README.md b/02-prompts/prompts-basics-openai/README.md index 38fcbf6..c68486d 100644 --- a/02-prompts/prompts-basics-openai/README.md +++ b/02-prompts/prompts-basics-openai/README.md @@ -6,8 +6,6 @@ Prompting using simple text with LLMs via OpenAI. The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - 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. diff --git a/02-prompts/prompts-messages-openai/README.md b/02-prompts/prompts-messages-openai/README.md index 9bc5d7d..876ab66 100644 --- a/02-prompts/prompts-messages-openai/README.md +++ b/02-prompts/prompts-messages-openai/README.md @@ -6,8 +6,6 @@ Prompting using structured messages and roles with LLMs via OpenAI. The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - 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. diff --git a/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java index 2ec304c..1d5ccf2 100644 --- a/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java +++ b/02-prompts/prompts-templates-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java @@ -35,9 +35,9 @@ String chatWithUserMessageTemplate(MusicQuestion question) { 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 prompt = userPromptTemplate.create(model); + var chatResponse = chatModel.call(prompt); return chatResponse.getResult().getOutput().getContent(); } diff --git a/02-prompts/prompts-templates-openai/README.md b/02-prompts/prompts-templates-openai/README.md index 96a8c95..a83eca2 100644 --- a/02-prompts/prompts-templates-openai/README.md +++ b/02-prompts/prompts-templates-openai/README.md @@ -6,8 +6,6 @@ Prompting using templates with LLMs via OpenAI. The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - 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. diff --git a/02-prompts/prompts-templates-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java b/02-prompts/prompts-templates-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java index 2ec304c..1d5ccf2 100644 --- a/02-prompts/prompts-templates-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java +++ b/02-prompts/prompts-templates-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java @@ -35,9 +35,9 @@ String chatWithUserMessageTemplate(MusicQuestion question) { 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 prompt = userPromptTemplate.create(model); + var chatResponse = chatModel.call(prompt); return chatResponse.getResult().getOutput().getContent(); } diff --git a/03-structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/03-structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java index a7f9c66..594f06c 100644 --- a/03-structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java +++ b/03-structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -2,6 +2,7 @@ import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.converter.ListOutputConverter; +import org.springframework.ai.converter.MapOutputConverter; import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.support.DefaultConversionService; @@ -54,7 +55,7 @@ Map chatWithMapOutput(MusicQuestion question) { .param("instrument", question.instrument()) ) .call() - .entity(new ParameterizedTypeReference<>() {}); + .entity(new MapOutputConverter()); } List chatWithListOutput(MusicQuestion question) { diff --git a/03-structured-output/structured-output-openai/README.md b/03-structured-output/structured-output-openai/README.md index 2bb3bd0..5261e6e 100644 --- a/03-structured-output/structured-output-openai/README.md +++ b/03-structured-output/structured-output-openai/README.md @@ -6,8 +6,6 @@ Converting the LLM output to structured Java objects via OpenAI. The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - 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. diff --git a/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java index cff6955..b93d209 100644 --- a/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java +++ b/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -2,9 +2,9 @@ import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.converter.ListOutputConverter; +import org.springframework.ai.converter.MapOutputConverter; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.stereotype.Service; @@ -55,7 +55,7 @@ Map chatWithMapOutput(MusicQuestion question) { .param("instrument", question.instrument()) ) .call() - .entity(new ParameterizedTypeReference<>() {}); + .entity(new MapOutputConverter()); } List chatWithListOutput(MusicQuestion question) { diff --git a/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java b/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java index 26d048c..5ce2b6c 100644 --- a/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java +++ b/03-structured-output/structured-output-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java @@ -80,6 +80,7 @@ ArtistInfoVariant chatWithJsonOutput(MusicQuestion question) { """); Map model = Map.of("instrument", question.instrument(), "genre", question.genre()); var prompt = userPromptTemplate.create(model, OpenAiChatOptions.builder() + .withModel("gpt-4o-2024-08-06") .withResponseFormat(new OpenAiApi.ChatCompletionRequest.ResponseFormat(OpenAiApi.ChatCompletionRequest.ResponseFormat.Type.JSON_SCHEMA, outputConverter.getJsonSchema())) .build()); diff --git a/03-structured-output/structured-output-openai/src/main/resources/application.yml b/03-structured-output/structured-output-openai/src/main/resources/application.yml index c3f5e9b..411949e 100644 --- a/03-structured-output/structured-output-openai/src/main/resources/application.yml +++ b/03-structured-output/structured-output-openai/src/main/resources/application.yml @@ -4,5 +4,5 @@ spring: api-key: ${OPENAI_API_KEY} chat: options: - model: gpt-4o-mini + model: gpt-4o temperature: 0.7 diff --git a/04-multimodality/multimodality-openai/README.md b/04-multimodality/multimodality-openai/README.md index 7c7d718..5814915 100644 --- a/04-multimodality/multimodality-openai/README.md +++ b/04-multimodality/multimodality-openai/README.md @@ -6,8 +6,6 @@ Multimodality with LLMs via OpenAI. The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - First, make sure you have an [OpenAI account](https://platform.openai.com/signup). Then, define an environment variable with the OpenAI API Key associated to your OpenAI account as the value. diff --git a/05-function-calling/function-calling-mistral-ai/README.md b/05-function-calling/function-calling-mistral-ai/README.md index 1da125b..d13cf95 100644 --- a/05-function-calling/function-calling-mistral-ai/README.md +++ b/05-function-calling/function-calling-mistral-ai/README.md @@ -6,8 +6,6 @@ Function calling via Mistral AI. The application relies on the Mistral AI API for providing LLMs. -### When using Mistral AI - 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. @@ -35,3 +33,9 @@ Try passing your custom prompt and check the result. ```shell http :8080/chat/function authorName=="Philip Pullman" -b ``` + +Try again. This time, the function calling strategy is configured in the call at runtime. + +```shell +http :8080/chat/function/explicit authorName=="Philip Pullman" -b +``` diff --git a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/BookService.java b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/BookService.java index 767b6fd..8251571 100644 --- a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/BookService.java +++ b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/BookService.java @@ -19,7 +19,7 @@ public class BookService { books.put(5, new Book("The Silmarillion", "J.R.R. Tolkien")); } - List getBooksByAuthor(Author author) { + public List getBooksByAuthor(Author author) { return books.values().stream() .filter(book -> author.name().equals(book.author())) .toList(); diff --git a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java index ea2a07f..1ffddc6 100644 --- a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -21,4 +21,9 @@ String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { return chatService.getAvailableBooksBy(authorName); } + @GetMapping("/chat/function/explicit") + String chatVariant(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { + return chatService.getAvailableBooksByWithExplicitFunction(authorName); + } + } diff --git a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatService.java index 5f4853d..de2f18a 100644 --- a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatService.java +++ b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -9,9 +9,11 @@ @Service class ChatService { + private final BookService bookService; private final ChatClient chatClient; - ChatService(ChatClient.Builder chatClientBuilder) { + ChatService(BookService bookService, ChatClient.Builder chatClientBuilder) { + this.bookService = bookService; this.chatClient = chatClientBuilder.build(); } @@ -27,4 +29,21 @@ String getAvailableBooksBy(String authorName) { .content(); } + String getAvailableBooksByWithExplicitFunction(String authorName) { + var userPromptTemplate = "What books written by {author} are available to read?"; + return chatClient.prompt() + .user(userSpec -> userSpec + .text(userPromptTemplate) + .param("author", authorName) + ) + .function( + "BooksByAuthor", + "Get the list of available books written by the given author", + BookService.Author.class, + bookService::getBooksByAuthor + ) + .call() + .content(); + } + } diff --git a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index ce31dbd..00326f5 100644 --- a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -23,4 +23,9 @@ String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { return chatService.getAvailableBooksBy(authorName); } + @GetMapping("/chat/function/explicit") + String chatVariant(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { + return chatService.getAvailableBooksByWithExplicitFunction(authorName); + } + } diff --git a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java index 9531b0b..dd23c84 100644 --- a/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java +++ b/05-function-calling/function-calling-mistral-ai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java @@ -1,10 +1,13 @@ package com.thomasvitale.ai.spring.model; +import com.thomasvitale.ai.spring.BookService; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.mistralai.MistralAiChatOptions; +import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Map; import java.util.Set; @@ -14,9 +17,11 @@ @Service class ChatModelService { + private final BookService bookService; private final ChatModel chatModel; - ChatModelService(ChatModel chatModel) { + ChatModelService(BookService bookService, ChatModel chatModel) { + this.bookService = bookService; this.chatModel = chatModel; } @@ -33,4 +38,24 @@ String getAvailableBooksBy(String authorName) { return chatResponse.getResult().getOutput().getContent(); } + String getAvailableBooksByWithExplicitFunction(String authorName) { + var userPromptTemplate = new PromptTemplate(""" + What books written by {author} are available to read? + """); + Map model = Map.of("author", authorName); + var prompt = userPromptTemplate.create(model, MistralAiChatOptions.builder() + .withFunctionCallbacks(List.of( + FunctionCallbackWrapper.builder(bookService::getBooksByAuthor) + .withDescription("Get the list of available books written by the given author") + .withName("BooksByAuthor") + .withInputType(BookService.Author.class) + .withResponseConverter(Object::toString) + .build() + )) + .build()); + + var chatResponse = chatModel.call(prompt); + return chatResponse.getResult().getOutput().getContent(); + } + } diff --git a/05-function-calling/function-calling-mistral-ai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingMistralAiApplicationTests.java b/05-function-calling/function-calling-mistral-ai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingMistralAiApplicationTests.java index 3a4250d..1df06f8 100644 --- a/05-function-calling/function-calling-mistral-ai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingMistralAiApplicationTests.java +++ b/05-function-calling/function-calling-mistral-ai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingMistralAiApplicationTests.java @@ -19,7 +19,7 @@ class FunctionCallingMistralAiApplicationTests { WebTestClient webTestClient; @ParameterizedTest - @ValueSource(strings = {"/chat/function", "/model/chat/function"}) + @ValueSource(strings = {"/chat/function", "/model/chat/function", "/chat/function/explicit", "/model/chat/function/explicit"}) void chat(String path) { webTestClient .get() diff --git a/05-function-calling/function-calling-ollama/README.md b/05-function-calling/function-calling-ollama/README.md index 480c88b..f8b0ee6 100644 --- a/05-function-calling/function-calling-ollama/README.md +++ b/05-function-calling/function-calling-ollama/README.md @@ -43,3 +43,9 @@ Try passing your custom prompt and check the result. ```shell http :8080/chat/function authorName=="Philip Pullman" -b ``` + +Try again. This time, the function calling strategy is configured in the call at runtime. + +```shell +http :8080/chat/function/explicit authorName=="Philip Pullman" -b +``` diff --git a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/BookService.java b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/BookService.java index 767b6fd..8251571 100644 --- a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/BookService.java +++ b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/BookService.java @@ -19,7 +19,7 @@ public class BookService { books.put(5, new Book("The Silmarillion", "J.R.R. Tolkien")); } - List getBooksByAuthor(Author author) { + public List getBooksByAuthor(Author author) { return books.values().stream() .filter(book -> author.name().equals(book.author())) .toList(); diff --git a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java index ea2a07f..1ffddc6 100644 --- a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -21,4 +21,9 @@ String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { return chatService.getAvailableBooksBy(authorName); } + @GetMapping("/chat/function/explicit") + String chatVariant(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { + return chatService.getAvailableBooksByWithExplicitFunction(authorName); + } + } diff --git a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java index 5f4853d..de2f18a 100644 --- a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java +++ b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -9,9 +9,11 @@ @Service class ChatService { + private final BookService bookService; private final ChatClient chatClient; - ChatService(ChatClient.Builder chatClientBuilder) { + ChatService(BookService bookService, ChatClient.Builder chatClientBuilder) { + this.bookService = bookService; this.chatClient = chatClientBuilder.build(); } @@ -27,4 +29,21 @@ String getAvailableBooksBy(String authorName) { .content(); } + String getAvailableBooksByWithExplicitFunction(String authorName) { + var userPromptTemplate = "What books written by {author} are available to read?"; + return chatClient.prompt() + .user(userSpec -> userSpec + .text(userPromptTemplate) + .param("author", authorName) + ) + .function( + "BooksByAuthor", + "Get the list of available books written by the given author", + BookService.Author.class, + bookService::getBooksByAuthor + ) + .call() + .content(); + } + } diff --git a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index ce31dbd..00326f5 100644 --- a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -23,4 +23,9 @@ String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { return chatService.getAvailableBooksBy(authorName); } + @GetMapping("/chat/function/explicit") + String chatVariant(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { + return chatService.getAvailableBooksByWithExplicitFunction(authorName); + } + } diff --git a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java index 788f8e8..0bf3a06 100644 --- a/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java +++ b/05-function-calling/function-calling-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java @@ -1,10 +1,13 @@ package com.thomasvitale.ai.spring.model; +import com.thomasvitale.ai.spring.BookService; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Map; import java.util.Set; @@ -14,9 +17,11 @@ @Service class ChatModelService { + private final BookService bookService; private final ChatModel chatModel; - ChatModelService(ChatModel chatModel) { + ChatModelService(BookService bookService, ChatModel chatModel) { + this.bookService = bookService; this.chatModel = chatModel; } @@ -33,4 +38,24 @@ String getAvailableBooksBy(String authorName) { return chatResponse.getResult().getOutput().getContent(); } + String getAvailableBooksByWithExplicitFunction(String authorName) { + var userPromptTemplate = new PromptTemplate(""" + What books written by {author} are available to read? + """); + Map model = Map.of("author", authorName); + var prompt = userPromptTemplate.create(model, OllamaOptions.builder() + .withFunctionCallbacks(List.of( + FunctionCallbackWrapper.builder(bookService::getBooksByAuthor) + .withDescription("Get the list of available books written by the given author") + .withName("BooksByAuthor") + .withInputType(BookService.Author.class) + .withResponseConverter(Object::toString) + .build() + )) + .build()); + + var chatResponse = chatModel.call(prompt); + return chatResponse.getResult().getOutput().getContent(); + } + } diff --git a/05-function-calling/function-calling-openai/README.md b/05-function-calling/function-calling-openai/README.md index 83470d0..64927a5 100644 --- a/05-function-calling/function-calling-openai/README.md +++ b/05-function-calling/function-calling-openai/README.md @@ -6,8 +6,6 @@ Function calling via OpenAI. The application relies on an OpenAI API for providing LLMs. -### When using OpenAI - 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. @@ -35,3 +33,9 @@ Try passing your custom prompt and check the result. ```shell http :8080/chat/function authorName=="Philip Pullman" -b ``` + +Try again. This time, the function calling strategy is configured in the call at runtime. + +```shell +http :8080/chat/function/explicit authorName=="Philip Pullman" -b +``` diff --git a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/BookService.java b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/BookService.java index 767b6fd..8251571 100644 --- a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/BookService.java +++ b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/BookService.java @@ -19,7 +19,7 @@ public class BookService { books.put(5, new Book("The Silmarillion", "J.R.R. Tolkien")); } - List getBooksByAuthor(Author author) { + public List getBooksByAuthor(Author author) { return books.values().stream() .filter(book -> author.name().equals(book.author())) .toList(); diff --git a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java index ea2a07f..1ffddc6 100644 --- a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -21,4 +21,9 @@ String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { return chatService.getAvailableBooksBy(authorName); } + @GetMapping("/chat/function/explicit") + String chatVariant(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { + return chatService.getAvailableBooksByWithExplicitFunction(authorName); + } + } diff --git a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java index 5f4853d..de2f18a 100644 --- a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java +++ b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/ChatService.java @@ -9,9 +9,11 @@ @Service class ChatService { + private final BookService bookService; private final ChatClient chatClient; - ChatService(ChatClient.Builder chatClientBuilder) { + ChatService(BookService bookService, ChatClient.Builder chatClientBuilder) { + this.bookService = bookService; this.chatClient = chatClientBuilder.build(); } @@ -27,4 +29,21 @@ String getAvailableBooksBy(String authorName) { .content(); } + String getAvailableBooksByWithExplicitFunction(String authorName) { + var userPromptTemplate = "What books written by {author} are available to read?"; + return chatClient.prompt() + .user(userSpec -> userSpec + .text(userPromptTemplate) + .param("author", authorName) + ) + .function( + "BooksByAuthor", + "Get the list of available books written by the given author", + BookService.Author.class, + bookService::getBooksByAuthor + ) + .call() + .content(); + } + } diff --git a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index ce31dbd..00326f5 100644 --- a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -23,4 +23,9 @@ String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { return chatService.getAvailableBooksBy(authorName); } + @GetMapping("/chat/function/explicit") + String chatVariant(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) { + return chatService.getAvailableBooksByWithExplicitFunction(authorName); + } + } diff --git a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java index c080ebb..1dc7696 100644 --- a/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java +++ b/05-function-calling/function-calling-openai/src/main/java/com/thomasvitale/ai/spring/model/ChatModelService.java @@ -1,10 +1,13 @@ package com.thomasvitale.ai.spring.model; +import com.thomasvitale.ai.spring.BookService; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Map; import java.util.Set; @@ -14,9 +17,11 @@ @Service class ChatModelService { + private final BookService bookService; private final ChatModel chatModel; - ChatModelService(ChatModel chatModel) { + public ChatModelService(BookService bookService, ChatModel chatModel) { + this.bookService = bookService; this.chatModel = chatModel; } @@ -33,4 +38,24 @@ String getAvailableBooksBy(String authorName) { return chatResponse.getResult().getOutput().getContent(); } + String getAvailableBooksByWithExplicitFunction(String authorName) { + var userPromptTemplate = new PromptTemplate(""" + What books written by {author} are available to read? + """); + Map model = Map.of("author", authorName); + var prompt = userPromptTemplate.create(model, OpenAiChatOptions.builder() + .withFunctionCallbacks(List.of( + FunctionCallbackWrapper.builder(bookService::getBooksByAuthor) + .withDescription("Get the list of available books written by the given author") + .withName("BooksByAuthor") + .withInputType(BookService.Author.class) + .withResponseConverter(Object::toString) + .build() + )) + .build()); + + var chatResponse = chatModel.call(prompt); + return chatResponse.getResult().getOutput().getContent(); + } + } diff --git a/05-function-calling/function-calling-openai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingOpenAiApplicationTests.java b/05-function-calling/function-calling-openai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingOpenAiApplicationTests.java index c350b06..4bc1627 100644 --- a/05-function-calling/function-calling-openai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingOpenAiApplicationTests.java +++ b/05-function-calling/function-calling-openai/src/test/java/com/thomasvitale/ai/spring/FunctionCallingOpenAiApplicationTests.java @@ -19,7 +19,7 @@ class FunctionCallingOpenAiApplicationTests { WebTestClient webTestClient; @ParameterizedTest - @ValueSource(strings = {"/chat/function", "/model/chat/function"}) + @ValueSource(strings = {"/chat/function", "/model/chat/function", "/chat/function/explicit", "/model/chat/function/explicit"}) void chat(String path) { webTestClient .get() diff --git a/06-embedding-models/embedding-models-mistral-ai/README.md b/06-embedding-models/embedding-models-mistral-ai/README.md index bbb1bf6..a34d406 100644 --- a/06-embedding-models/embedding-models-mistral-ai/README.md +++ b/06-embedding-models/embedding-models-mistral-ai/README.md @@ -29,8 +29,6 @@ class EmbeddingController { The application relies on the Mistral AI API for providing LLMs. -### When using Mistral AI - 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.