diff --git a/model-auth-providers/oidc-model-auth-provider/deployment/pom.xml b/model-auth-providers/oidc-model-auth-provider/deployment/pom.xml new file mode 100644 index 000000000..c793bc294 --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/deployment/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-oidc-model-auth-provider-parent + 999-SNAPSHOT + + quarkus-langchain4j-oidc-model-auth-provider-deployment + Quarkus LangChain4j - OpenId Connect (OIDC) ModelAuthProvider - Deployment + + + io.quarkiverse.langchain4j + quarkus-langchain4j-oidc-model-auth-provider + ${project.version} + + + io.quarkiverse.langchain4j + quarkus-langchain4j-core-deployment + ${project.version} + + + io.quarkus + quarkus-junit5-internal + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/model-auth-providers/oidc-model-auth-provider/deployment/src/main/java/io/quarkiverse/langchain4j/oidc/deployment/OidcModelAuthProviderBuildConfig.java b/model-auth-providers/oidc-model-auth-provider/deployment/src/main/java/io/quarkiverse/langchain4j/oidc/deployment/OidcModelAuthProviderBuildConfig.java new file mode 100644 index 000000000..ea6dc7306 --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/deployment/src/main/java/io/quarkiverse/langchain4j/oidc/deployment/OidcModelAuthProviderBuildConfig.java @@ -0,0 +1,19 @@ +package io.quarkiverse.langchain4j.oidc.deployment; + +import static io.quarkus.runtime.annotations.ConfigPhase.BUILD_TIME; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigDocDefault; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +@ConfigRoot(phase = BUILD_TIME) +@ConfigMapping(prefix = "quarkus.langchain4j.oidc-model-auth-provider") +public interface OidcModelAuthProviderBuildConfig { + /** + * Whether the OIDC ModelAuthProvider should be enabled + */ + @ConfigDocDefault("true") + Optional enabled(); +} diff --git a/model-auth-providers/oidc-model-auth-provider/deployment/src/main/java/io/quarkiverse/langchain4j/oidc/deployment/OidcModelAuthProviderProcessor.java b/model-auth-providers/oidc-model-auth-provider/deployment/src/main/java/io/quarkiverse/langchain4j/oidc/deployment/OidcModelAuthProviderProcessor.java new file mode 100644 index 000000000..5daec248d --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/deployment/src/main/java/io/quarkiverse/langchain4j/oidc/deployment/OidcModelAuthProviderProcessor.java @@ -0,0 +1,35 @@ +package io.quarkiverse.langchain4j.oidc.deployment; + +import java.util.function.BooleanSupplier; + +import io.quarkiverse.langchain4j.oidc.runtime.OidcModelAuthProvider; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +@BuildSteps(onlyIf = OidcModelAuthProviderProcessor.IsEnabled.class) +public class OidcModelAuthProviderProcessor { + private static final String FEATURE = "langchain4j-oidc-model-auth-provider"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + public void additionalBeans(BuildProducer additionalBeans) { + AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder().setUnremovable(); + builder.addBeanClass(OidcModelAuthProvider.class); + additionalBeans.produce(builder.build()); + } + + public static class IsEnabled implements BooleanSupplier { + OidcModelAuthProviderBuildConfig config; + + public boolean getAsBoolean() { + return config.enabled().orElse(true); + } + } +} diff --git a/model-auth-providers/oidc-model-auth-provider/pom.xml b/model-auth-providers/oidc-model-auth-provider/pom.xml new file mode 100644 index 000000000..98a4e8857 --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-parent + 999-SNAPSHOT + ../../pom.xml + + quarkus-langchain4j-oidc-model-auth-provider-parent + Quarkus LangChain4j - OpenId Connect (OIDC) ModelAuthProvider - Parent + pom + + + deployment + runtime + + + + diff --git a/model-auth-providers/oidc-model-auth-provider/runtime/pom.xml b/model-auth-providers/oidc-model-auth-provider/runtime/pom.xml new file mode 100644 index 000000000..6d413a9fd --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/runtime/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-oidc-model-auth-provider-parent + 999-SNAPSHOT + + quarkus-langchain4j-oidc-model-auth-provider + Quarkus LangChain4j - OpenId Connect (OIDC) ModelAuthProvider - Runtime + + + io.quarkus + quarkus-arc + + + io.quarkus.security + quarkus-security + + + io.quarkiverse.langchain4j + quarkus-langchain4j-core + ${project.version} + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/model-auth-providers/oidc-model-auth-provider/runtime/src/main/java/io/quarkiverse/langchain4j/oidc/runtime/OidcModelAuthProvider.java b/model-auth-providers/oidc-model-auth-provider/runtime/src/main/java/io/quarkiverse/langchain4j/oidc/runtime/OidcModelAuthProvider.java new file mode 100644 index 000000000..f54fc593a --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/runtime/src/main/java/io/quarkiverse/langchain4j/oidc/runtime/OidcModelAuthProvider.java @@ -0,0 +1,17 @@ +package io.quarkiverse.langchain4j.oidc.runtime; + +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import io.quarkiverse.langchain4j.runtime.auth.ModelAuthProvider; +import io.quarkus.security.credential.TokenCredential; + +public class OidcModelAuthProvider implements ModelAuthProvider { + @Inject + Instance tokenCredential; + + @Override + public String getAuthorization(Input input) { + return tokenCredential.isResolvable() ? "Bearer " + tokenCredential.get().getToken() : null; + } +} diff --git a/model-auth-providers/oidc-model-auth-provider/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/model-auth-providers/oidc-model-auth-provider/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 000000000..0a37fa22d --- /dev/null +++ b/model-auth-providers/oidc-model-auth-provider/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,14 @@ +name: LangChain4j OpenId Connect (OIDC) ModelAuthProvider +artifact: ${project.groupId}:${project.artifactId}:${project.version} +description: Provides ModelAuthProvider which uses OIDC bearer or authorization code flow access tokens +metadata: + keywords: + - ai + - langchain4j + - oidc + - security + guide: "https://docs.quarkiverse.io/quarkus-langchain4j/dev/index.html" + categories: + - "security" + status: "experimental" + diff --git a/model-providers/vertex-ai-gemini/deployment/src/test/java/io/quarkiverse/langchain4j/vertexai/gemini/deployment/VertexAiGeminiChatLanguageModelSmokeTest.java b/model-providers/vertex-ai-gemini/deployment/src/test/java/io/quarkiverse/langchain4j/vertexai/gemini/deployment/VertexAiGeminiChatLanguageModelSmokeTest.java index 502b66802..06ba85351 100644 --- a/model-providers/vertex-ai-gemini/deployment/src/test/java/io/quarkiverse/langchain4j/vertexai/gemini/deployment/VertexAiGeminiChatLanguageModelSmokeTest.java +++ b/model-providers/vertex-ai-gemini/deployment/src/test/java/io/quarkiverse/langchain4j/vertexai/gemini/deployment/VertexAiGeminiChatLanguageModelSmokeTest.java @@ -17,9 +17,9 @@ import com.github.tomakehurst.wiremock.verification.LoggedRequest; import dev.langchain4j.model.chat.ChatLanguageModel; +import io.quarkiverse.langchain4j.runtime.auth.ModelAuthProvider; import io.quarkiverse.langchain4j.testing.internal.WiremockAware; import io.quarkiverse.langchain4j.vertexai.runtime.gemini.VertexAiGeminiChatLanguageModel; -import io.quarkiverse.langchain4j.vertexai.runtime.gemini.VertxAiGeminiRestApi; import io.quarkus.arc.ClientProxy; import io.quarkus.test.QuarkusUnitTest; @@ -111,11 +111,13 @@ void test() { } @Singleton - public static class DummyAuthProvider implements VertxAiGeminiRestApi.AuthProvider { + public static class DummyAuthProvider implements ModelAuthProvider { + @Override - public String getBearerToken() { - return API_KEY; + public String getAuthorization(Input input) { + return "Bearer " + API_KEY; } + } } diff --git a/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/VertxAiGeminiRestApi.java b/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/VertxAiGeminiRestApi.java index 4e3d3023d..91632e74d 100644 --- a/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/VertxAiGeminiRestApi.java +++ b/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/VertxAiGeminiRestApi.java @@ -5,15 +5,19 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.net.URI; import java.util.concurrent.ExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; import jakarta.ws.rs.BeanParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MultivaluedMap; +import org.eclipse.microprofile.context.ManagedExecutor; import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; @@ -25,7 +29,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.auth.oauth2.GoogleCredentials; -import io.quarkus.arc.DefaultBean; +import io.quarkiverse.langchain4j.runtime.auth.ModelAuthProvider; +import io.quarkiverse.langchain4j.runtime.auth.ModelAuthProvider.Input; +import io.quarkiverse.langchain4j.vertexai.runtime.gemini.config.ChatModelConfig; import io.quarkus.rest.client.reactive.jackson.ClientObjectMapper; import io.vertx.core.Handler; import io.vertx.core.MultiMap; @@ -102,17 +108,10 @@ public ApiMetadata build() { } } - interface AuthProvider { - - String getBearerToken(); - } - - @ApplicationScoped - @DefaultBean - class ApplicationDefaultAuthProvider implements AuthProvider { + class ApplicationDefaultAuthProvider implements ModelAuthProvider { @Override - public String getBearerToken() { + public String getAuthorization(Input input) { try { var credentials = GoogleCredentials.getApplicationDefault(); credentials.refreshIfExpired(); @@ -126,11 +125,17 @@ public String getBearerToken() { class TokenFilter implements ResteasyReactiveClientRequestFilter { private final ExecutorService executorService; - private final AuthProvider authProvider; + private final ModelAuthProvider defaultAuthorizer; + private final ModelAuthProvider authorizer; + + @Inject + Instance model; - public TokenFilter(ExecutorService executorService, AuthProvider authProvider) { + public TokenFilter(ManagedExecutor executorService) { this.executorService = executorService; - this.authProvider = authProvider; + this.defaultAuthorizer = new ApplicationDefaultAuthProvider(); + this.authorizer = ModelAuthProvider.resolve( + model != null && model.isResolvable() ? model.get().modelId() : null).orElse(null); } @Override @@ -140,7 +145,12 @@ public void filter(ResteasyReactiveClientRequestContext context) { @Override public void run() { try { - context.getHeaders().add("Authorization", "Bearer " + authProvider.getBearerToken()); + final Input authInput = new AuthInputImpl(context.getMethod(), context.getUri(), context.getHeaders()); + String authorization = authorizer != null ? authorizer.getAuthorization(authInput) : null; + if (authorization == null) { + authorization = defaultAuthorizer.getAuthorization(authInput); + } + context.getHeaders().add("Authorization", authorization); context.resume(); } catch (Exception e) { context.resume(e); @@ -148,6 +158,12 @@ public void run() { } }); } + + private record AuthInputImpl( + String method, + URI uri, + MultivaluedMap headers) implements ModelAuthProvider.Input { + } } class VertxAiClientLogger implements ClientLogger { diff --git a/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/config/LangChain4jVertexAiGeminiConfig.java b/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/config/LangChain4jVertexAiGeminiConfig.java index 4a852590c..41b0aafbf 100644 --- a/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/config/LangChain4jVertexAiGeminiConfig.java +++ b/model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/config/LangChain4jVertexAiGeminiConfig.java @@ -61,7 +61,7 @@ interface VertexAiGeminiConfig { Optional baseUrl(); /** - * Whether to enable the integration. Defaults to {@code true}, which means requests are made to the Anthropic + * Whether to enable the integration. Defaults to {@code true}, which means requests are made to the Vertex AI Gemini * provider. * Set to {@code false} to disable all requests. */ diff --git a/pom.xml b/pom.xml index 713d7681b..7854f6ae0 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,8 @@ model-providers/vertex-ai-gemini model-providers/watsonx + model-auth-providers/oidc-model-auth-provider + quarkus-integrations/websockets-next rag/easy-rag @@ -199,6 +201,7 @@ samples/review-triage samples/fraud-detection samples/secure-fraud-detection + samples/secure-vertex-ai-gemini-poem samples/chatbot samples/chatbot-easy-rag samples/sql-chatbot diff --git a/samples/secure-vertex-ai-gemini-poem/README.md b/samples/secure-vertex-ai-gemini-poem/README.md new file mode 100644 index 000000000..bceb80462 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/README.md @@ -0,0 +1,115 @@ +# Secure Vertex AI Gemini Poem Demo + +This demo showcases the implementation of a secure Vertex AI Gemini Poem Demo which is available only to users authenticated with Google. + +## The Demo + +Demo asks Vertex AI Gemini LLM to write a short 1 paragraph poem, using the access token acquired during the OIDC authorization code flow + +### Setup + +This demo requires users to authenticate with Google. + +All you need to do is to register an application with Google, follow steps listed in the [Quarkus Google](https://quarkus.io/guides/security-openid-connect-providers#google) section. + +Name your Google application as `Quarkus LangChain4j AI`, and make sure an allowed callback URL is set to `http://localhost:8080/login`. +Google will generate a client id and secret, use them to set `quarkus.oidc.client-id` and `quarkus.oidc.credentials.secret` properties: + +```properties +quarkus.oidc.provider=google +quarkus.oidc.client-id=${GOOGLE_CLIENT_ID} +quarkus.oidc.credentials.secret=${GOOGLE_CLIENT_SECRET} +quarkus.oidc.authentication.extra-params.scope=https://www.googleapis.com/auth/generative-language.retriever,https://www.googleapis.com/auth/cloud-platform +quarkus.oidc.authentication.redirect-path=/login + +# See https://cloud.google.com/vertex-ai/docs/geeral/locations +vertex-ai-region=europe-west2 + +quarkus.langchain4j.vertexai.gemini.location=${vertex-ai-region} +quarkus.langchain4j.vertexai.gemini.project-id=${GOOGLE_PROJECT_ID} +``` + +You must enable Vertex AI API in your Google Cloud project. + +Set `GOOGLE_PROJECT_ID` to the id of your Google Cloud project. + +### AI Service + +This demo leverages the AI service abstraction, with the interaction between the LLM and the application handled through the AIService interface. + +The `io.quarkiverse.langchain4j.sample.PoemAiService` interface uses specific annotations to define the LLM: + +```java +package io.quarkiverse.langchain4j.sample; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; + +@RegisterAiService +public interface PoemAiService { + + /** + * Ask the LLM to create a poem about Enterprise Java. + * + * @return the poem + */ + @SystemMessage("You are a professional poet") + @UserMessage(""" + Write a short 1 paragraph poem about Java. Set an author name to the model name which created the poem. + """) + String writeAPoem(); + +} + +### Using the AI service + +Once defined, you can inject the AI service as a regular bean, and use it: + +```java +package io.quarkiverse.langchain4j.sample; + +import java.net.URISyntaxException; + +import io.quarkus.security.Authenticated; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/poem") +@Authenticated +public class PoemResource { + + private final PoemAiService aiService; + + public PoemResource(PoemAiService aiService) throws URISyntaxException { + this.aiService = aiService; + } + + @GET + public String getPoem() { + return aiService.writeAPoem(); + } +} + +``` + +`PoemResource` can only be accessed by authenticated users. + +## Security Considerations + +This demo makes it possible to access Google Vertex AI API enabled in the Google Cloud project only to users who: + +* Authenticated to Quarkus REST PoemService with Google using OIDC authorization code flow. +* Authorized `Quarkus LangChain4j AI` application registered in the Google Cloud project to use the access token to access Google Generative API on behalf of the currently authentiicated user. This authorization is requested from users during the authentication process and is configured by adding additional `quarkus.oidc.authentication.extra-params.scope=https://www.googleapis.com/auth/generative-language.retriever,https://www.googleapis.com/auth/cloud-platform` scopes in the application properties. +* Quarkus LangChain4j vertex-ai-gemini model provider uses this authorized token on behalf of the current user to access Google Vertex AI endpoint. + +## Running the Demo + +To run the demo, use the following commands: + +```shell +mvn quarkus:dev +``` + +Then, access `http://localhost:8080`, login to Google, and follow a provided application link to read the poem. + diff --git a/samples/secure-vertex-ai-gemini-poem/pom.xml b/samples/secure-vertex-ai-gemini-poem/pom.xml new file mode 100644 index 000000000..f06de3e9b --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-sample-secure-vertex-ai-gemini-poem + Quarkus LangChain4j - Sample - Secure Vertex AI Gemini Poem + 1.0-SNAPSHOT + + + 3.13.0 + true + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 3.12.0.CR1 + true + 3.2.5 + 999-SNAPSHOT + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-oidc + + + io.quarkiverse.langchain4j + quarkus-langchain4j-oidc-model-auth-provider + ${quarkus-langchain4j.version} + + + io.quarkiverse.langchain4j + quarkus-langchain4j-vertex-ai-gemini + ${quarkus-langchain4j.version} + + + io.quarkus + quarkus-resteasy-reactive-qute + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.platform.version} + + + + build + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + maven-surefire-plugin + 3.2.5 + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + 3.2.5 + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + + diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/LoginResource.java b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/LoginResource.java new file mode 100644 index 000000000..c4fb929c2 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/LoginResource.java @@ -0,0 +1,33 @@ +package io.quarkiverse.langchain4j.sample; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.oidc.IdToken; +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateInstance; +import io.quarkus.security.Authenticated; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +/** + * Login resource which returns a poem welcome page to the authenticated user + */ +@Path("/login") +@Authenticated +public class LoginResource { + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + Template poem; + + @GET + @Produces("text/html") + public TemplateInstance poem() { + return poem.data("name", idToken.getName()); + } +} diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/LogoutResource.java b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/LogoutResource.java new file mode 100644 index 000000000..e2635772e --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/LogoutResource.java @@ -0,0 +1,29 @@ +package io.quarkiverse.langchain4j.sample; + +import io.quarkus.oidc.OidcSession; +import io.quarkus.security.Authenticated; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; + +/** + * Logout resource + */ +@Path("/logout") +@Authenticated +public class LogoutResource { + + @Inject + OidcSession session; + + @GET + public Response logout(@Context UriInfo uriInfo) { + // remove the local session cookie + session.logout().await().indefinitely(); + // redirect to the login page + return Response.seeOther(uriInfo.getBaseUriBuilder().path("login").build()).build(); + } +} diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/PoemAiService.java b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/PoemAiService.java new file mode 100644 index 000000000..3cf57b662 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/PoemAiService.java @@ -0,0 +1,21 @@ +package io.quarkiverse.langchain4j.sample; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; + +@RegisterAiService +public interface PoemAiService { + + /** + * Ask the LLM to create a poem about Enterprise Java. + * + * @return the poem + */ + @SystemMessage("You are a professional poet") + @UserMessage(""" + Write a short 1 paragraph poem about Java. Set an author name to the model name which created the poem. + """) + String writeAPoem(); + +} diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/PoemResource.java b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/PoemResource.java new file mode 100644 index 000000000..1d3e05952 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/java/io/quarkiverse/langchain4j/sample/PoemResource.java @@ -0,0 +1,23 @@ +package io.quarkiverse.langchain4j.sample; + +import java.net.URISyntaxException; + +import io.quarkus.security.Authenticated; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/poem") +@Authenticated +public class PoemResource { + + private final PoemAiService aiService; + + public PoemResource(PoemAiService aiService) throws URISyntaxException { + this.aiService = aiService; + } + + @GET + public String getPoem() { + return aiService.writeAPoem(); + } +} diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/resources/META-INF/resources/images/google.png b/samples/secure-vertex-ai-gemini-poem/src/main/resources/META-INF/resources/images/google.png new file mode 100644 index 000000000..e2503df4e Binary files /dev/null and b/samples/secure-vertex-ai-gemini-poem/src/main/resources/META-INF/resources/images/google.png differ diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/resources/META-INF/resources/index.html b/samples/secure-vertex-ai-gemini-poem/src/main/resources/META-INF/resources/index.html new file mode 100644 index 000000000..c5e900b81 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,133 @@ + + + + + Secure Poem Resource + + + + + + +
+
+
+

Login

+ + + + +
Login with Google
+ +
+
+
+ +
+ +
+ + + + diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/resources/application.properties b/samples/secure-vertex-ai-gemini-poem/src/main/resources/application.properties new file mode 100644 index 000000000..af8d30303 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/resources/application.properties @@ -0,0 +1,14 @@ +quarkus.oidc.provider=google +quarkus.oidc.client-id=${GOOGLE_CLIENT_ID} +quarkus.oidc.credentials.secret=${GOOGLE_CLIENT_SECRET} +quarkus.oidc.authentication.extra-params.scope=https://www.googleapis.com/auth/generative-language.retriever,https://www.googleapis.com/auth/cloud-platform +quarkus.oidc.authentication.redirect-path=/login + +# See https://cloud.google.com/vertex-ai/docs/geeral/locations +vertex-ai-region=europe-west2 + +quarkus.langchain4j.vertexai.gemini.location=${vertex-ai-region} +quarkus.langchain4j.vertexai.gemini.project-id=${GOOGLE_PROJECT_ID} + +quarkus.langchain4j.vertexai.gemini.log-requests=true +quarkus.langchain4j.vertexai.gemini.log-responses=true diff --git a/samples/secure-vertex-ai-gemini-poem/src/main/resources/templates/poem.html b/samples/secure-vertex-ai-gemini-poem/src/main/resources/templates/poem.html new file mode 100644 index 000000000..170d82875 --- /dev/null +++ b/samples/secure-vertex-ai-gemini-poem/src/main/resources/templates/poem.html @@ -0,0 +1,18 @@ + + + + +Secure Poem Resource + + +

Hello {name} !

+ + + + +
+ Read a poem +
+ Logout + +