-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
106d259
commit 55c9c69
Showing
34 changed files
with
1,482 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Secure chatbot using advanced RAG and a SQL database | ||
|
||
## Secure Movie Muse | ||
|
||
This example demonstrates how to create a secure chatbot with RAG using | ||
`quarkus-langchain4j`. This chatbot internally uses LLM-generated SQL | ||
queries to retrieve the relevant information from a PostgreSQL database. | ||
|
||
### Setup | ||
|
||
The demo requires that your Google account's full name and email are configured. | ||
You can use system or env properties, see `Running the example` section below. | ||
|
||
When the application starts, a registered user, the movie watcher, is allocated a random preferred movie genre . | ||
|
||
The setup is defined in the [Setup.java](./src/main/java/io/quarkiverse/langchain4j/samples/chatbot/Setup.java) class. | ||
|
||
The registered movie watchers are stored in a PostgreSQL database. When running the demo in dev mode (recommended), the database is automatically created and populated. | ||
|
||
The genre preferred by the registered movie watcher is used by a Movie `ContentRetriever` to sort the list of movies. | ||
|
||
## Running the example | ||
|
||
Users must authenticate with Google before they can start working with the Movie Muse. | ||
They also must register their OpenAI API key. | ||
|
||
### Google Authentication | ||
|
||
In order 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 `https://localhost:8443/login`. | ||
Google will generate a client id and secret, use them to set `quarkus.oidc.client-id` and `quarkus.oidc.credentials.secret` properties. | ||
|
||
### OpenAI API key | ||
|
||
You must provide your OpenAI API key: | ||
|
||
``` | ||
export QUARKUS_LANGCHAIN4J_OPENAI_API_KEY=<your-openai-api-key> | ||
``` | ||
|
||
Then, simply run the project in Dev mode: | ||
|
||
```shell | ||
mvn quarkus:dev -Dname="Firstname Familyname" [email protected] | ||
``` | ||
|
||
Note, you should use double quotes to register your Google account's full name. | ||
|
||
## Using the example | ||
|
||
Open your browser and navigate to `https://localhost:8443`, accept the demo certificate. | ||
Now, choose a `Login with Google` option. | ||
Once logged in, click an orange robot icon in the bottom right corner to open a chat window. | ||
Ask questions such as `Give me movies with a rating higher than 8`. | ||
|
||
The chatbot is available at the secure `wss:` scheme. | ||
|
||
It uses a SQL database with information about movies with their | ||
basic metadata (the database is populated with data from | ||
`src/main/resources/data/movies.csv` at startup). When you ask a question, an | ||
LLM is used to generate SQL queries necessary for answering your question. | ||
Check the application's log, the SQL queries and the retrieved data will be | ||
printed there. | ||
|
||
The chatbot will refer to you by your name during the conversation. | ||
|
||
## Security Considerations | ||
|
||
### HTTPS and WSS | ||
|
||
This demo requires the use of secure HTTPS and WSS WebSockets protocols only. | ||
|
||
### Chatbot is accessible to authenticated users only | ||
|
||
Only users who have authenticated with Google will see a page which contains a chatbot icon. | ||
It eliminates a risk of non-authenticated users attempting to trick LLM. | ||
|
||
### CORS | ||
|
||
Even though browsers do not enforce Single Origin Policy (SOP) for WebSockets HTTP upgrade requests, enabling | ||
CORS origin check can add an extra protection in combination with verifying the expected authentication credentials. | ||
|
||
For example, attackers can set `Origin` themselves but they will not have the HTTPS bound authentication session cookie | ||
which can be used to authenticate a WSS WebSockets upgrade request. | ||
Or if the authenticated user is tricked into visiting an unfriendly web site, then a WSS WebSockets upgrade request will fail | ||
at the Quarkus CORS check level. | ||
|
||
### Custom WebSocket ticket system | ||
|
||
In addition to requiring authentication over secure HTTPS, in combination with the CORS constraint for | ||
the HTTP upgrade to succeed, this demo shows a simple WebSockets ticket system to verify that the current HTTP upgrade request | ||
was made from the page allocated to authenticated users only. | ||
|
||
When the user completes the OpenId Connect Google authentication, a dynamically generated HTML page will contain a WSS HTTP upgrade link | ||
with a randomly generated ticket added at the authentication time, for example: `wss://localhost/chatbot?ticket=random_ticket_value`. | ||
|
||
HTTP upgrade checker will ensure that a matching ticket is found before permitting an upgrade. | ||
|
||
### User identity is visible to AI services and tools | ||
|
||
AI service and tools can access an ID `JsonWebToken` token which represents a successful user authentication and use it to complete its work. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<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"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-sample-secure-sql-chatbot</artifactId> | ||
<name>Quarkus LangChain4j - Sample - Secure Chatbot & RAG from a SQL database</name> | ||
<version>1.0-SNAPSHOT</version> | ||
|
||
<properties> | ||
<compiler-plugin.version>3.13.0</compiler-plugin.version> | ||
<maven.compiler.parameters>true</maven.compiler.parameters> | ||
<maven.compiler.release>17</maven.compiler.release> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> | ||
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id> | ||
<quarkus.platform.version>3.15.1</quarkus.platform.version> | ||
<skipITs>true</skipITs> | ||
<surefire-plugin.version>3.2.5</surefire-plugin.version> | ||
<quarkus-langchain4j.version>0.21.0</quarkus-langchain4j.version> | ||
</properties> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>${quarkus.platform.group-id}</groupId> | ||
<artifactId>${quarkus.platform.artifact-id}</artifactId> | ||
<version>${quarkus.platform.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-rest-jackson</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-websockets-next</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-oidc</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-jdbc-postgresql</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-hibernate-orm</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-hibernate-orm-panache</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-rest-qute</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-openai</artifactId> | ||
<version>${quarkus-langchain4j.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.dataformat</groupId> | ||
<artifactId>jackson-dataformat-csv</artifactId> | ||
</dependency> | ||
|
||
|
||
<!-- UI --> | ||
<dependency> | ||
<groupId>io.mvnpm</groupId> | ||
<artifactId>importmap</artifactId> | ||
<version>1.0.11</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mvnpm</groupId> | ||
<artifactId>lit</artifactId> | ||
<version>3.2.0</version> | ||
<scope>runtime</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mvnpm</groupId> | ||
<artifactId>wc-chatbot</artifactId> | ||
<version>0.2.0</version> | ||
<scope>runtime</scope> | ||
</dependency> | ||
|
||
<!-- Minimal dependencies to constrain the build --> | ||
<dependency> | ||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-openai-deployment</artifactId> | ||
<version>${quarkus-langchain4j.version}</version> | ||
<scope>test</scope> | ||
<type>pom</type> | ||
<exclusions> | ||
<exclusion> | ||
<groupId>*</groupId> | ||
<artifactId>*</artifactId> | ||
</exclusion> | ||
</exclusions> | ||
</dependency> | ||
</dependencies> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-maven-plugin</artifactId> | ||
<version>${quarkus.platform.version}</version> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>build</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>${compiler-plugin.version}</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>3.5.1</version> | ||
<configuration> | ||
<systemPropertyVariables> | ||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> | ||
<maven.home>${maven.home}</maven.home> | ||
</systemPropertyVariables> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<profiles> | ||
<profile> | ||
<id>native</id> | ||
<activation> | ||
<property> | ||
<name>native</name> | ||
</property> | ||
</activation> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<artifactId>maven-failsafe-plugin</artifactId> | ||
<version>3.5.1</version> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>integration-test</goal> | ||
<goal>verify</goal> | ||
</goals> | ||
<configuration> | ||
<systemPropertyVariables> | ||
<native.image.path> | ||
${project.build.directory}/${project.build.finalName}-runner | ||
</native.image.path> | ||
<java.util.logging.manager>org.jboss.logmanager.LogManager | ||
</java.util.logging.manager> | ||
<maven.home>${maven.home}</maven.home> | ||
</systemPropertyVariables> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
<properties> | ||
<quarkus.package.type>native</quarkus.package.type> | ||
</properties> | ||
</profile> | ||
|
||
<profile> | ||
<id>mvnpm</id> | ||
<repositories> | ||
<repository> | ||
<id>central</id> | ||
<name>central</name> | ||
<url>https://repo.maven.apache.org/maven2</url> | ||
</repository> | ||
<repository> | ||
<snapshots> | ||
<enabled>false</enabled> | ||
</snapshots> | ||
<id>mvnpm.org</id> | ||
<name>mvnpm</name> | ||
<url>https://repo.mvnpm.org/maven2</url> | ||
</repository> | ||
</repositories> | ||
</profile> | ||
</profiles> | ||
|
||
</project> |
51 changes: 51 additions & 0 deletions
51
...sql-chatbot/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ChatBotWebSocket.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package io.quarkiverse.langchain4j.sample.chatbot; | ||
|
||
import org.eclipse.microprofile.jwt.Claims; | ||
import org.eclipse.microprofile.jwt.JsonWebToken; | ||
|
||
import io.quarkus.logging.Log; | ||
import io.quarkus.oidc.IdToken; | ||
import io.quarkus.security.Authenticated; | ||
import jakarta.inject.Inject; | ||
|
||
import io.quarkus.websockets.next.OnOpen; | ||
import io.quarkus.websockets.next.OnTextMessage; | ||
import io.quarkus.websockets.next.WebSocket; | ||
|
||
@WebSocket(path = "/chatbot") | ||
@Authenticated | ||
public class ChatBotWebSocket { | ||
|
||
private final MovieMuse bot; | ||
|
||
@Inject | ||
@IdToken | ||
JsonWebToken idToken; | ||
|
||
public ChatBotWebSocket(MovieMuse bot) { | ||
this.bot = bot; | ||
} | ||
|
||
@OnOpen | ||
public String onOpen() { | ||
return "Hello, " + idToken.getName() + ", I'm MovieMuse, how can I help you?"; | ||
} | ||
|
||
@OnTextMessage | ||
public String onMessage(String message) { | ||
try { | ||
return idToken.getName() + ", " + bot.chat(message); | ||
} catch (MissingMovieWatcherException ex) { | ||
Log.error(ex); | ||
return """ | ||
Sorry, %s, looks like you did not register your name and email correctly. | ||
Please use '-Dname="%s"' (keep double quotes around your name) and '-Demail=%s' system properties at startup | ||
""" | ||
.formatted(idToken.getName(), idToken.getName(), idToken.getClaim(Claims.email)); | ||
} catch (Throwable ex) { | ||
Log.error(ex); | ||
return "Sorry, " + idToken.getName() | ||
+ ", an unexpected error occurred, can you please ask your question again ?"; | ||
} | ||
} | ||
} |
Oops, something went wrong.