Skip to content

Commit c9986ef

Browse files
committed
Update vertex-ai-gemini to check OIDC TokenCredential
1 parent f55be2a commit c9986ef

File tree

14 files changed

+510
-2
lines changed

14 files changed

+510
-2
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
:project-version: 0.14.1
22
:langchain4j-version: 0.31.0
3-
:examples-dir: ./../examples/
3+
:examples-dir: ./../examples/

model-providers/vertex-ai-gemini/runtime/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
2424
</dependency>
2525

26+
<dependency>
27+
<groupId>io.quarkus.security</groupId>
28+
<artifactId>quarkus-security</artifactId>
29+
</dependency>
30+
2631
<dependency>
2732
<groupId>com.google.auth</groupId>
2833
<artifactId>google-auth-library-oauth2-http</artifactId>

model-providers/vertex-ai-gemini/runtime/src/main/java/io/quarkiverse/langchain4j/vertexai/runtime/gemini/VertxAiGeminiRestApi.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.util.regex.Pattern;
1111

1212
import jakarta.enterprise.context.ApplicationScoped;
13+
import jakarta.enterprise.inject.Instance;
14+
import jakarta.inject.Inject;
1315
import jakarta.ws.rs.BeanParam;
1416
import jakarta.ws.rs.POST;
1517
import jakarta.ws.rs.Path;
@@ -27,6 +29,7 @@
2729

2830
import io.quarkus.arc.DefaultBean;
2931
import io.quarkus.rest.client.reactive.jackson.ClientObjectMapper;
32+
import io.quarkus.security.credential.TokenCredential;
3033
import io.vertx.core.Handler;
3134
import io.vertx.core.MultiMap;
3235
import io.vertx.core.buffer.Buffer;
@@ -128,6 +131,9 @@ class TokenFilter implements ResteasyReactiveClientRequestFilter {
128131
private final ExecutorService executorService;
129132
private final AuthProvider authProvider;
130133

134+
@Inject
135+
Instance<TokenCredential> tokenCredential;
136+
131137
public TokenFilter(ExecutorService executorService, AuthProvider authProvider) {
132138
this.executorService = executorService;
133139
this.authProvider = authProvider;
@@ -140,12 +146,19 @@ public void filter(ResteasyReactiveClientRequestContext context) {
140146
@Override
141147
public void run() {
142148
try {
143-
context.getHeaders().add("Authorization", "Bearer " + authProvider.getBearerToken());
149+
context.getHeaders().add("Authorization", "Bearer " + getBearerToken());
144150
context.resume();
145151
} catch (Exception e) {
146152
context.resume(e);
147153
}
148154
}
155+
156+
private String getBearerToken() {
157+
if (tokenCredential.isResolvable() && tokenCredential.get().getToken() != null) {
158+
return tokenCredential.get().getToken();
159+
}
160+
return authProvider.getBearerToken();
161+
}
149162
});
150163
}
151164
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
<module>samples/chatbot</module>
203203
<module>samples/chatbot-easy-rag</module>
204204
<module>samples/sql-chatbot</module>
205+
<module>samples/secure-vertex-ai-gemini-poem</module>
205206
</modules>
206207
</profile>
207208
</profiles>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Secure Vertex AI Gemini Poem Demo
2+
3+
This demo showcases the implementation of a secure Vertex AI Gemini Poem Demo which is available only to users authenticated with Google.
4+
5+
## The Demo
6+
7+
### Setup
8+
9+
The demo asks Vertex AI Gemini LLM to write a short 1 paragraph poem, using the access token acquired during the OIDC authorization code flow.
10+
11+
### AI Service
12+
13+
This demo leverages the AI service abstraction, with the interaction between the LLM and the application handled through the AIService interface.
14+
15+
The `io.quarkiverse.langchain4j.sample.PoemAiService` interface uses specific annotations to define the LLM:
16+
17+
```java
18+
package io.quarkiverse.langchain4j.sample;
19+
20+
import dev.langchain4j.service.SystemMessage;
21+
import dev.langchain4j.service.UserMessage;
22+
import io.quarkiverse.langchain4j.RegisterAiService;
23+
24+
@RegisterAiService
25+
public interface PoemAiService {
26+
27+
/**
28+
* Ask the LLM to create a poem about Enterprise Java.
29+
*
30+
* @return the poem
31+
*/
32+
@SystemMessage("You are a professional poet")
33+
@UserMessage("""
34+
Write a short 1 paragraph funny poem about Enterprise Java.
35+
""")
36+
String writeAPoem();
37+
38+
}
39+
40+
### Using the AI service
41+
42+
Once defined, you can inject the AI service as a regular bean, and use it:
43+
44+
```java
45+
package io.quarkiverse.langchain4j.sample;
46+
47+
import java.net.URISyntaxException;
48+
49+
import io.quarkus.security.Authenticated;
50+
import jakarta.ws.rs.GET;
51+
import jakarta.ws.rs.Path;
52+
53+
@Path("/poem")
54+
@Authenticated
55+
public class PoemResource {
56+
57+
private final PoemAiService aiService;
58+
59+
public PoemResource(PoemAiService aiService) throws URISyntaxException {
60+
this.aiService = aiService;
61+
}
62+
63+
@GET
64+
public String getPoem() {
65+
return aiService.writeAPoem();
66+
}
67+
}
68+
69+
```
70+
71+
`PoemResource` can only be accessed by authenticated users.
72+
73+
## Google Authentication
74+
75+
This demo requires users to authenticate with Google.
76+
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.
77+
Name your Google application as `Quarkus LangChain4j AI`, and make sure an allowed callback URL is set to `http://localhost:8080/login`.
78+
Google will generate a client id and secret, use them to set `quarkus.oidc.client-id` and `quarkus.oidc.credentials.secret` properties:
79+
80+
```properties
81+
quarkus.oidc.provider=google
82+
quarkus.oidc.client-id=${GOOGLE_CLIENT_ID}
83+
quarkus.oidc.credentials.secret=${GOOGLE_CLIENT_SECRET}
84+
quarkus.oidc.authentication.extra-params.scope=https://www.googleapis.com/auth/generative-language.retriever
85+
quarkus.oidc.authentication.redirect-path=/login
86+
87+
vertex-ai-region=europe-west2
88+
quarkus.langchain4j.vertexai.gemini.location=https://${vertex-ai-region}-aiplatform.googleapis.com
89+
quarkus.langchain4j.vertexai.gemini.project-id=${GOOGLE_PROJECT_ID}
90+
```
91+
92+
## Running the Demo
93+
94+
To run the demo, use the following commands:
95+
96+
```shell
97+
mvn quarkus:dev
98+
```
99+
100+
Then, access `http://localhost:8080`, login to Google, and follow a provided application link to read the poem.
101+
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>io.quarkiverse.langchain4j</groupId>
6+
<artifactId>quarkus-langchain4j-sample-secure-vertex-ai-gemini-poem</artifactId>
7+
<name>Quarkus LangChain4j - Sample - Secure Vertex AI Gemini Poem</name>
8+
<version>1.0-SNAPSHOT</version>
9+
10+
<properties>
11+
<compiler-plugin.version>3.13.0</compiler-plugin.version>
12+
<maven.compiler.parameters>true</maven.compiler.parameters>
13+
<maven.compiler.release>17</maven.compiler.release>
14+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
16+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
17+
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
18+
<quarkus.platform.version>3.8.2</quarkus.platform.version>
19+
<skipITs>true</skipITs>
20+
<surefire-plugin.version>3.2.5</surefire-plugin.version>
21+
<quarkus-langchain4j.version>999-SNAPSHOT</quarkus-langchain4j.version>
22+
</properties>
23+
24+
<dependencyManagement>
25+
<dependencies>
26+
<dependency>
27+
<groupId>${quarkus.platform.group-id}</groupId>
28+
<artifactId>${quarkus.platform.artifact-id}</artifactId>
29+
<version>${quarkus.platform.version}</version>
30+
<type>pom</type>
31+
<scope>import</scope>
32+
</dependency>
33+
</dependencies>
34+
</dependencyManagement>
35+
36+
<dependencies>
37+
<dependency>
38+
<groupId>io.quarkus</groupId>
39+
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>io.quarkus</groupId>
43+
<artifactId>quarkus-oidc</artifactId>
44+
</dependency>
45+
<dependency>
46+
<groupId>io.quarkiverse.langchain4j</groupId>
47+
<artifactId>quarkus-langchain4j-vertex-ai-gemini</artifactId>
48+
<version>${quarkus-langchain4j.version}</version>
49+
</dependency>
50+
<dependency>
51+
<groupId>io.quarkus</groupId>
52+
<artifactId>quarkus-resteasy-reactive-qute</artifactId>
53+
</dependency>
54+
</dependencies>
55+
<build>
56+
<plugins>
57+
<plugin>
58+
<groupId>io.quarkus</groupId>
59+
<artifactId>quarkus-maven-plugin</artifactId>
60+
<version>${quarkus.platform.version}</version>
61+
<executions>
62+
<execution>
63+
<goals>
64+
<goal>build</goal>
65+
</goals>
66+
</execution>
67+
</executions>
68+
</plugin>
69+
<plugin>
70+
<artifactId>maven-compiler-plugin</artifactId>
71+
<version>${compiler-plugin.version}</version>
72+
</plugin>
73+
<plugin>
74+
<artifactId>maven-surefire-plugin</artifactId>
75+
<version>3.2.5</version>
76+
<configuration>
77+
<systemPropertyVariables>
78+
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
79+
<maven.home>${maven.home}</maven.home>
80+
</systemPropertyVariables>
81+
</configuration>
82+
</plugin>
83+
</plugins>
84+
</build>
85+
86+
<profiles>
87+
<profile>
88+
<id>native</id>
89+
<activation>
90+
<property>
91+
<name>native</name>
92+
</property>
93+
</activation>
94+
<build>
95+
<plugins>
96+
<plugin>
97+
<artifactId>maven-failsafe-plugin</artifactId>
98+
<version>3.2.5</version>
99+
<executions>
100+
<execution>
101+
<goals>
102+
<goal>integration-test</goal>
103+
<goal>verify</goal>
104+
</goals>
105+
<configuration>
106+
<systemPropertyVariables>
107+
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
108+
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
109+
<maven.home>${maven.home}</maven.home>
110+
</systemPropertyVariables>
111+
</configuration>
112+
</execution>
113+
</executions>
114+
</plugin>
115+
</plugins>
116+
</build>
117+
<properties>
118+
<quarkus.package.type>native</quarkus.package.type>
119+
</properties>
120+
</profile>
121+
</profiles>
122+
</project>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.quarkiverse.langchain4j.sample;
2+
3+
import org.eclipse.microprofile.jwt.JsonWebToken;
4+
5+
import io.quarkus.oidc.IdToken;
6+
import io.quarkus.qute.Template;
7+
import io.quarkus.qute.TemplateInstance;
8+
import io.quarkus.security.Authenticated;
9+
import jakarta.inject.Inject;
10+
import jakarta.ws.rs.GET;
11+
import jakarta.ws.rs.Path;
12+
import jakarta.ws.rs.Produces;
13+
14+
/**
15+
* Login resource which returns a poem welcome page to the authenticated user
16+
*/
17+
@Path("/login")
18+
@Authenticated
19+
public class LoginResource {
20+
21+
@Inject
22+
@IdToken
23+
JsonWebToken idToken;
24+
25+
@Inject
26+
Template poem;
27+
28+
@GET
29+
@Produces("text/html")
30+
public TemplateInstance poem() {
31+
return poem.data("name", idToken.getName());
32+
}
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.quarkiverse.langchain4j.sample;
2+
3+
import io.quarkus.oidc.OidcSession;
4+
import io.quarkus.security.Authenticated;
5+
import jakarta.inject.Inject;
6+
import jakarta.ws.rs.GET;
7+
import jakarta.ws.rs.Path;
8+
import jakarta.ws.rs.core.Context;
9+
import jakarta.ws.rs.core.Response;
10+
import jakarta.ws.rs.core.UriInfo;
11+
12+
/**
13+
* Logout resource
14+
*/
15+
@Path("/logout")
16+
@Authenticated
17+
public class LogoutResource {
18+
19+
@Inject
20+
OidcSession session;
21+
22+
@GET
23+
public Response logout(@Context UriInfo uriInfo) {
24+
// remove the local session cookie
25+
session.logout().await().indefinitely();
26+
// redirect to the login page
27+
return Response.seeOther(uriInfo.getBaseUriBuilder().path("login").build()).build();
28+
}
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.quarkiverse.langchain4j.sample;
2+
3+
import dev.langchain4j.service.SystemMessage;
4+
import dev.langchain4j.service.UserMessage;
5+
import io.quarkiverse.langchain4j.RegisterAiService;
6+
7+
@RegisterAiService
8+
public interface PoemAiService {
9+
10+
/**
11+
* Ask the LLM to create a poem about Enterprise Java.
12+
*
13+
* @return the poem
14+
*/
15+
@SystemMessage("You are a professional poet")
16+
@UserMessage("""
17+
Write a short 1 paragraph funny poem about Enterprise Java.
18+
""")
19+
String writeAPoem();
20+
21+
}

0 commit comments

Comments
 (0)