diff --git a/bom/pom.xml b/bom/pom.xml index 149886f4d34..0f140eeba5a 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -350,6 +350,16 @@ helidon-microprofile-health ${helidon.version} + + io.helidon.ia + helidon-ia-api + ${helidon.version} + + + io.helidon.ia + helidon-ia-openid + ${helidon.version} + io.helidon.microprofile.server helidon-microprofile-server diff --git a/ia/api/pom.xml b/ia/api/pom.xml new file mode 100644 index 00000000000..96378bb67cb --- /dev/null +++ b/ia/api/pom.xml @@ -0,0 +1,42 @@ + + + + + 4.0.0 + + io.helidon.ia + helidon-ia-project + 4.1.0-SNAPSHOT + + + helidon-ia-api + Helidon IA API + + + Integration with IA API + + + + + io.helidon.common + helidon-common + + + + diff --git a/ia/api/src/main/java/io/helidon/ia/api/ChatModel.java b/ia/api/src/main/java/io/helidon/ia/api/ChatModel.java new file mode 100644 index 00000000000..acac403e36a --- /dev/null +++ b/ia/api/src/main/java/io/helidon/ia/api/ChatModel.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.api; + +/** + * Class to be extended by different LLM implementations that provides + * the methods to operate with it. + * + * @param the input of the LLM + * @param the output of LLM + */ +public interface ChatModel { + + /** + * Invokes the LLM. + * Throws a runtime exception if errors. + * + * @param in the input of the LLM + * @return the output of LLM + */ + OUT call(IN in); +} diff --git a/ia/api/src/main/java/module-info.java b/ia/api/src/main/java/module-info.java new file mode 100644 index 00000000000..5a6ebca0fbc --- /dev/null +++ b/ia/api/src/main/java/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * IA API module. + * + */ +module io.helidon.ia.api { + + requires io.helidon.common; + + exports io.helidon.ia.api; +} \ No newline at end of file diff --git a/ia/openai/pom.xml b/ia/openai/pom.xml new file mode 100644 index 00000000000..44c4d8b4787 --- /dev/null +++ b/ia/openai/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + io.helidon.ia + helidon-ia-project + 4.1.0-SNAPSHOT + + + helidon-ia-openai + Helidon IA OpenId + + + Integration with IA OpenId + + + + + io.helidon.ia + helidon-ia-api + + + org.glassfish.jersey.core + jersey-client + + + org.junit.jupiter + junit-jupiter-api + test + + + org.mockito + mockito-core + test + + + + diff --git a/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIChatModel.java b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIChatModel.java new file mode 100644 index 00000000000..73734e60839 --- /dev/null +++ b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIChatModel.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import java.util.Objects; + +import io.helidon.ia.api.ChatModel; + +/** + * Class that handles OpenAI communications specified in + * Chat . + */ +public class OpenAIChatModel implements ChatModel { + + private final Client client; + private final String url; + private final String apiKey; + + private OpenAIChatModel(Client client, String url, String apiKey) { + this.client = client; + this.url = url; + this.apiKey = apiKey; + } + + @Override + public OpenAIResponse call(OpenAIRequest in) { + Response response = client.target(url).path("/v1/chat/completions") + .request() + .header("Authorization", "Bearer " + apiKey) + .accept(MediaType.APPLICATION_JSON_TYPE) + .post(Entity.entity(in, MediaType.APPLICATION_JSON_TYPE)); + if (response.getStatus() != 200) { + throw new IllegalStateException("Invalid response " + response.getStatus() + + ": " + response.readEntity(String.class)); + } else { + return response.readEntity(OpenAIResponse.class); + } + } + + /** + * Create a builder. + * + * @return a new fluent API builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Create a OpenAIChatModel for the given API key and client. + * + * @param apiKey the OpenAI API key + * @param client the client + * @return a OpenAIChatModel default instance + */ + public static OpenAIChatModel create(String apiKey, Client client) { + return builder().apiKey(apiKey).client(client).build(); + } + + /** + * Fluent API builder for {@link io.helidon.ia.openid.OpenAIChatModel}. + */ + public static final class Builder implements io.helidon.common.Builder { + + private static final String DEFAULT_OPENID_URL = "https://api.openai.com"; + private Client client; + private String url = DEFAULT_OPENID_URL; + private String apiKey; + + private Builder() { + } + + @Override + public OpenAIChatModel build() { + Objects.requireNonNull(apiKey, "OpenAI API key is mandatory."); + Objects.requireNonNull(client, "Client is mandatory to make HTTP requests to OpenAI."); + return new OpenAIChatModel(client, url, apiKey); + } + + /** + * Configure client for HTTP requests, so multiple instances of {@link OpenAIChatModel} can + * re-use the same client for performance reasons. + * The client will never be closed by {@link OpenAIChatModel}. + * + * @param client the configured client + * @return updated builder instance + */ + public Builder client(Client client) { + this.client = client; + return this; + } + + /** + * The URL of OpenAI. It defaults to {@link #DEFAULT_OPENID_URL}. + * + * @param url of OpenAI + * @return updated builder instance + */ + public Builder url(String url) { + this.url = url; + return this; + } + + /** + * Configure the API key to authenticate with OpenAI. + * + * @param apiKey + * @return updated builder instance + */ + public Builder apiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + } + +} diff --git a/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIChoice.java b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIChoice.java new file mode 100644 index 00000000000..b96efeef54a --- /dev/null +++ b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIChoice.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import java.io.Serializable; + +/** + * Represents a choice made by OpenAI. + * This class encapsulates the index of the choice, the message associated with the choice, + * and the reason why the choice was finished. + * It implements {@link Serializable} for object serialization. + * + */ +public class OpenAIChoice implements Serializable { + + private static final long serialVersionUID = 1L; + private int index; + private OpenAIMessage message; + private String finish_reason; + + /** + * Required public constructor. + */ + public OpenAIChoice() { + } + + /** + * Gets the index of the choice. + * + * @return the index of the choice. + */ + public int getIndex() { + return index; + } + + /** + * Sets the index of the choice. + * + * @param index the index of the choice. + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * Gets the message associated with the choice. + * + * @return the message associated with the choice. + */ + public OpenAIMessage getMessage() { + return message; + } + + /** + * Sets the message associated with the choice. + * + * @param message the message associated with the choice. + */ + public void setMessage(OpenAIMessage message) { + this.message = message; + } + + /** + * Gets the reason why the choice was finished. + * + * @return the reason why the choice was finished. + */ + public String getFinish_reason() { + return finish_reason; + } + + /** + * Sets the reason why the choice was finished. + * + * @param finish_reason the reason why the choice was finished. + */ + public void setFinish_reason(String finish_reason) { + this.finish_reason = finish_reason; + } + +} + diff --git a/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIMessage.java b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIMessage.java new file mode 100644 index 00000000000..4dc97432e21 --- /dev/null +++ b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIMessage.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import java.io.Serializable; + +/** + * Represents a message in the OpenAI context. + * This class encapsulates the role and content of a message. + * It implements {@link Serializable} for object serialization. + * + */ +public class OpenAIMessage implements Serializable { + + private static final long serialVersionUID = 1L; + private String role; + private String content; + + /** + * Required public constructor. + */ + public OpenAIMessage() { + } + + /** + * Gets the role of the message sender. + * + * @return the role of the message sender. + */ + public String getRole() { + return role; + } + + /** + * Sets the role of the message sender. + * + * @param role the role of the message sender. + */ + public void setRole(String role) { + this.role = role; + } + + /** + * Gets the content of the message. + * + * @return the content of the message. + */ + public String getContent() { + return content; + } + + /** + * Sets the content of the message. + * + * @param content the content of the message. + */ + public void setContent(String content) { + this.content = content; + } +} diff --git a/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIRequest.java b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIRequest.java new file mode 100644 index 00000000000..ced1852af7b --- /dev/null +++ b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import java.io.Serializable; +import java.util.List; + +/** + * Represents a request to the OpenAI API. + * This class encapsulates the model to be used and the list of messages included in the request. + * It implements {@link Serializable} for object serialization. + * + */ +public class OpenAIRequest implements Serializable { + + private static final long serialVersionUID = 1L; + private static final String DEFAULT_OPENID_MODEL = "gpt-4o"; + private String model = DEFAULT_OPENID_MODEL; + private List messages = List.of(); + + /** + * Required public constructor. + */ + public OpenAIRequest() { + } + + /** + * Gets the model to be used for the request. + * The default model is "gpt-4o". + * + * @return the model to be used for the request. + */ + public String getModel() { + return model; + } + + /** + * Sets the model to be used for the request. + * + * @param model the model to be used for the request. + */ + public void setModel(String model) { + this.model = model; + } + + /** + * Gets the list of messages included in the request. + * + * @return the list of messages included in the request. + */ + public List getMessages() { + return messages; + } + + /** + * Sets the list of messages included in the request. + * + * @param messages the list of messages included in the request. + */ + public void setMessages(List messages) { + this.messages = messages; + } +} + diff --git a/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIResponse.java b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIResponse.java new file mode 100644 index 00000000000..91cc5adbaf9 --- /dev/null +++ b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIResponse.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import java.io.Serializable; +import java.util.List; + +/** + * Represents a response from the OpenAI API. + * This class encapsulates various details about the response such as + * the ID, object type, creation timestamp, model used, system fingerprint, + * finish reason, choices made, and usage statistics. + * It implements {@link Serializable} for object serialization. + * + */ +public class OpenAIResponse implements Serializable { + + private static final long serialVersionUID = 1L; + private String id; + private String object; + private long created; + private String model; + private String system_fingerprint; + private String finish_reason; + private List choices = List.of(); + private OpenAIUsage usage; + + /** + * Required public constructor. + */ + public OpenAIResponse() { + } + + /** + * Gets the ID of the response. + * + * @return the ID of the response. + */ + public String getId() { + return id; + } + + /** + * Sets the ID of the response. + * + * @param id the ID of the response. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Gets the object type of the response. + * + * @return the object type of the response. + */ + public String getObject() { + return object; + } + + /** + * Sets the object type of the response. + * + * @param object the object type of the response. + */ + public void setObject(String object) { + this.object = object; + } + + /** + * Gets the creation timestamp of the response. + * + * @return the creation timestamp of the response. + */ + public long getCreated() { + return created; + } + + /** + * Sets the creation timestamp of the response. + * + * @param created the creation timestamp of the response. + */ + public void setCreated(long created) { + this.created = created; + } + + /** + * Gets the model used to generate the response. + * + * @return the model used to generate the response. + */ + public String getModel() { + return model; + } + + /** + * Sets the model used to generate the response. + * + * @param model the model used to generate the response. + */ + public void setModel(String model) { + this.model = model; + } + + /** + * Gets the system fingerprint of the response. + * + * @return the system fingerprint of the response. + */ + public String getSystem_fingerprint() { + return system_fingerprint; + } + + /** + * Sets the system fingerprint of the response. + * + * @param system_fingerprint the system fingerprint of the response. + */ + public void setSystem_fingerprint(String system_fingerprint) { + this.system_fingerprint = system_fingerprint; + } + + /** + * Gets the reason why the response was finished. + * + * @return the reason why the response was finished. + */ + public String getFinish_reason() { + return finish_reason; + } + + /** + * Sets the reason why the response was finished. + * + * @param finish_reason the reason why the response was finished. + */ + public void setFinish_reason(String finish_reason) { + this.finish_reason = finish_reason; + } + + /** + * Gets the list of choices included in the response. + * + * @return the list of choices included in the response. + */ + public List getChoices() { + return choices; + } + + /** + * Sets the list of choices included in the response. + * + * @param choices the list of choices included in the response. + */ + public void setChoices(List choices) { + this.choices = choices; + } + + /** + * Gets the usage statistics of the response. + * + * @return the usage statistics of the response. + */ + public OpenAIUsage getUsage() { + return usage; + } + + /** + * Sets the usage statistics of the response. + * + * @param usage the usage statistics of the response. + */ + public void setUsage(OpenAIUsage usage) { + this.usage = usage; + } +} + diff --git a/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIUsage.java b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIUsage.java new file mode 100644 index 00000000000..cd26e8aa5dc --- /dev/null +++ b/ia/openai/src/main/java/io/helidon/ia/openai/OpenAIUsage.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import java.io.Serializable; + +/** + * Represents the usage statistics for OpenAI. + * This class is used to keep track of token usage for prompts and completions. + * It implements {@link Serializable} for object serialization. + * + */ +public class OpenAIUsage implements Serializable { + + private static final long serialVersionUID = 1L; + private int prompt_tokens; + private int completion_tokens; + private int total_tokens; + + /** + * Required public constructor. + */ + public OpenAIUsage() { + } + + /** + * Gets the number of tokens used for the prompt. + * + * @return the number of prompt tokens. + */ + public int getPrompt_tokens() { + return prompt_tokens; + } + + /** + * Sets the number of tokens used for the prompt. + * + * @param prompt_tokens the number of prompt tokens. + */ + public void setPrompt_tokens(int prompt_tokens) { + this.prompt_tokens = prompt_tokens; + } + + /** + * Gets the number of tokens used for the completion. + * + * @return the number of completion tokens. + */ + public int getCompletion_tokens() { + return completion_tokens; + } + + /** + * Sets the number of tokens used for the completion. + * + * @param completion_tokens the number of completion tokens. + */ + public void setCompletion_tokens(int completion_tokens) { + this.completion_tokens = completion_tokens; + } + + /** + * Gets the total number of tokens used (prompt + completion). + * + * @return the total number of tokens. + */ + public int getTotal_tokens() { + return total_tokens; + } + + /** + * Sets the total number of tokens used (prompt + completion). + * + * @param total_tokens the total number of tokens. + */ + public void setTotal_tokens(int total_tokens) { + this.total_tokens = total_tokens; + } +} \ No newline at end of file diff --git a/ia/openai/src/main/java/module-info.java b/ia/openai/src/main/java/module-info.java new file mode 100644 index 00000000000..e68bdc98ddb --- /dev/null +++ b/ia/openai/src/main/java/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * IA OpenId module. + * + */ +module io.helidon.ia.openai { + + requires io.helidon.ia.api; + requires io.helidon.common; + requires jakarta.ws.rs; + +} \ No newline at end of file diff --git a/ia/openai/src/test/java/io/helidon/ia/openai/OpenAIChatModelTest.java b/ia/openai/src/test/java/io/helidon/ia/openai/OpenAIChatModelTest.java new file mode 100644 index 00000000000..06d6cd09780 --- /dev/null +++ b/ia/openai/src/test/java/io/helidon/ia/openai/OpenAIChatModelTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.ia.openai; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class OpenAIChatModelTest { + + @Test + void test() { + try (Client client = ClientBuilder.newClient()) { +// OpenAIChatModel chatModel = OpenAIChatModel.create("api-key", client); +// OpenAIMessage message = new OpenAIMessage(); +// message.setContent("Say hello"); +// OpenAIRequest req = new OpenAIRequest(); +// req.setMessages(List.of(message)); +// chatModel.call(new OpenAIRequest()); + } + } +} diff --git a/ia/pom.xml b/ia/pom.xml new file mode 100644 index 00000000000..3d85303738f --- /dev/null +++ b/ia/pom.xml @@ -0,0 +1,40 @@ + + + + + 4.0.0 + + io.helidon + helidon-project + 4.1.0-SNAPSHOT + + + io.helidon.ia + helidon-ia-project + Helidon IA Project + + pom + + + api + openai + + diff --git a/pom.xml b/pom.xml index ca3cf682537..89ca3004f41 100644 --- a/pom.xml +++ b/pom.xml @@ -207,6 +207,7 @@ health helidon http + ia inject integrations jersey