diff --git a/libraries-ai/pom.xml b/libraries-ai/pom.xml index e1f97e693fa5..3ad7dbc5a9b9 100644 --- a/libraries-ai/pom.xml +++ b/libraries-ai/pom.xml @@ -108,6 +108,11 @@ opencsv ${opencsv.version} + + io.github.sashirestela + simple-openai + ${simpleopenai.version} + @@ -133,6 +138,7 @@ 4.3.2 3.17.0 5.11 + 3.22.2 \ No newline at end of file diff --git a/libraries-ai/src/main/java/com/baeldung/simpleopenai/Client.java b/libraries-ai/src/main/java/com/baeldung/simpleopenai/Client.java new file mode 100644 index 000000000000..2119e0a6fd0c --- /dev/null +++ b/libraries-ai/src/main/java/com/baeldung/simpleopenai/Client.java @@ -0,0 +1,25 @@ +package com.baeldung.simpleopenai; + +import java.lang.System.Logger; + +import io.github.sashirestela.openai.SimpleOpenAIGeminiGoogle; + +public final class Client { + + public static final Logger LOGGER = System.getLogger("simpleopenai"); + public static final String CHAT_MODEL = "gemini-2.5-flash"; + public static final String EMBEDDING_MODEL = "text-embedding-004"; + + private Client() { + } + + public static SimpleOpenAIGeminiGoogle getClient() { + String apiKey = System.getenv("GEMINI_API_KEY"); + if (apiKey == null || apiKey.isBlank()) { + throw new IllegalStateException("GEMINI_API_KEY is not set"); + } + return SimpleOpenAIGeminiGoogle.builder() + .apiKey(apiKey) + .build(); + } +} diff --git a/libraries-ai/src/main/java/com/baeldung/simpleopenai/GeminiApiKeyCheck.java b/libraries-ai/src/main/java/com/baeldung/simpleopenai/GeminiApiKeyCheck.java new file mode 100644 index 000000000000..05179014a4f8 --- /dev/null +++ b/libraries-ai/src/main/java/com/baeldung/simpleopenai/GeminiApiKeyCheck.java @@ -0,0 +1,17 @@ +package com.baeldung.simpleopenai; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +public class GeminiApiKeyCheck { + + public static void main(String[] args) { + + Logger logger = System.getLogger("simpleopenai"); + logger.log(Level.INFO, + "GEMINI_API_KEY configured: {0}", + System.getenv("GEMINI_API_KEY") != null); + + } + +} diff --git a/libraries-ai/src/main/java/com/baeldung/simpleopenai/KeepingConversationStateInJava.java b/libraries-ai/src/main/java/com/baeldung/simpleopenai/KeepingConversationStateInJava.java new file mode 100644 index 000000000000..91b9981b96cd --- /dev/null +++ b/libraries-ai/src/main/java/com/baeldung/simpleopenai/KeepingConversationStateInJava.java @@ -0,0 +1,59 @@ +package com.baeldung.simpleopenai; + +import java.lang.System.Logger.Level; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.CompletableFuture; + +import io.github.sashirestela.openai.domain.chat.Chat; +import io.github.sashirestela.openai.domain.chat.ChatMessage; +import io.github.sashirestela.openai.domain.chat.ChatMessage.AssistantMessage; +import io.github.sashirestela.openai.domain.chat.ChatMessage.SystemMessage; +import io.github.sashirestela.openai.domain.chat.ChatMessage.UserMessage; +import io.github.sashirestela.openai.domain.chat.ChatRequest; + +public class KeepingConversationStateInJava { + + public static void main(String[] args) { + var client = Client.getClient(); + + List history = new ArrayList<>(); + history.add(SystemMessage.of( + "You are a helpful travel assistant. Answer briefly." + )); + + try (Scanner scanner = new Scanner(System.in)) { + while (true) { + System.out.print("You: "); + String input = scanner.nextLine(); + if (input == null || input.isBlank()) { + continue; + } + if ("exit".equalsIgnoreCase(input.trim())) { + break; + } + + history.add(UserMessage.of(input)); + + ChatRequest.ChatRequestBuilder chatRequestBuilder = + ChatRequest.builder().model(Client.CHAT_MODEL); + + for (ChatMessage message : history) { + chatRequestBuilder.message(message); + } + + ChatRequest chatRequest = chatRequestBuilder.build(); + + CompletableFuture chatFuture = + client.chatCompletions().create(chatRequest); + Chat chat = chatFuture.join(); + + String reply = chat.firstContent().toString(); + Client.LOGGER.log(Level.INFO, "Assistant: {0}", reply); + + history.add(AssistantMessage.of(reply)); + } + } + } +} diff --git a/libraries-ai/src/main/java/com/baeldung/simpleopenai/SingleTurnChatCompletion.java b/libraries-ai/src/main/java/com/baeldung/simpleopenai/SingleTurnChatCompletion.java new file mode 100644 index 000000000000..2c24174c1d23 --- /dev/null +++ b/libraries-ai/src/main/java/com/baeldung/simpleopenai/SingleTurnChatCompletion.java @@ -0,0 +1,30 @@ +package com.baeldung.simpleopenai; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.concurrent.CompletableFuture; + +import io.github.sashirestela.openai.domain.chat.Chat; +import io.github.sashirestela.openai.domain.chat.ChatMessage.UserMessage; +import io.github.sashirestela.openai.domain.chat.ChatRequest; + +public class SingleTurnChatCompletion { + + public static void main(String[] args) { + var client = Client.getClient(); + + ChatRequest chatRequest = ChatRequest.builder() + .model(Client.CHAT_MODEL) + .message(UserMessage.of( + "Suggest a weekend trip in Japan, no more than 60 words." + )) + .build(); + + CompletableFuture chatFuture = + client.chatCompletions().create(chatRequest); + Chat chat = chatFuture.join(); + + Logger logger = Client.LOGGER; + logger.log(Level.INFO, "Model reply: {0}", chat.firstContent()); + } +} diff --git a/libraries-ai/src/main/resources/simpleopenai/gemini-curl-tests.txt b/libraries-ai/src/main/resources/simpleopenai/gemini-curl-tests.txt new file mode 100644 index 000000000000..e741105c910f --- /dev/null +++ b/libraries-ai/src/main/resources/simpleopenai/gemini-curl-tests.txt @@ -0,0 +1,193 @@ +# Gemini curl tests for simple-openai compatible features +# +# Environment +# These examples assume a POSIX compatible shell such as bash on a Linux machine. +# The commands have been exercised on Linux and should also work on macOS with a few notes: +# - macOS provides base64 without the -w0 option. To produce base64 on a single line we can use: +# BASE64_IMAGE=$(base64 "$IMAGE_PATH" | tr -d '\n') +# instead of the base64 -w0 call in Test 4. +# On Windows the simplest way to run these commands is to use a Unix like environment such as +# Windows Subsystem for Linux or Git Bash. PowerShell can also be used but the syntax for +# environment variables and pipes will be different from the examples here. +# +# Prerequisites +# - curl +# - jq +# - base64 utility +# - a valid Gemini API key +# +# Before running the tests, let's set the GEMINI_API_KEY environment variable in our shell: +# +# export GEMINI_API_KEY="GEMINI_API_KEY_HERE" +# +# Every request uses the OpenAI compatible Gemini endpoints and will consume tokens from +# our Gemini free tier or paid quota. + +export GEMINI_API_KEY="..." + +--- +Test 1 - Basic chat completion (required for SimpleOpenAIGeminiGoogle) + +curl -s "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $GEMINI_API_KEY" \ + -d '{ + "model": "gemini-2.5-flash", + "messages": [ + { + "role": "user", + "content": "Explain what Baeldung is in at most 50 words." + } + ] + }' | jq . + +--- +Test 2 - Chat completions in streaming with Gemini (stream=true) + +curl -sN "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $GEMINI_API_KEY" \ + -d '{ + "model": "gemini-2.5-flash", + "stream": true, + "messages": [ + { + "role": "system", + "content": "You are an assistant that always reply in English." + }, + { + "role": "user", + "content": "Briefly describe the city of Florence in at most 80 words." + } + ] + }' + +--- +Test 3 - Function Calling (Tools) with Gemini + +curl -s "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $GEMINI_API_KEY" \ + -d '{ + "model": "gemini-2.5-flash", + "messages": [ + { + "role": "user", + "content": "What is the weather like in Chicago today? Please call the get_weather function instead of answering directly." + } + ], + "tools": [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. Chicago, IL" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"] + } + }, + "required": ["location"] + } + } + } + ], + "tool_choice": "auto" + }' | jq . + +--- +Test 4 - Chat completion with vision (image understanding) + +1. Convert the image to base64 and write the JSON payload to a file + +IMAGE_PATH="image.jpg" + +# Base64 on a single line +BASE64_IMAGE=$(base64 -w0 "$IMAGE_PATH") + +cat > /tmp/gemini_vision_request.json <