diff --git a/pom.xml b/pom.xml index fbe66681e..b0cb2f964 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,7 @@ samples/cli-translator samples/review-triage samples/fraud-detection + samples/secure-fraud-detection samples/chatbot samples/chatbot-easy-rag samples/sql-chatbot diff --git a/samples/secure-fraud-detection/README.md b/samples/secure-fraud-detection/README.md new file mode 100644 index 000000000..0fbb748fb --- /dev/null +++ b/samples/secure-fraud-detection/README.md @@ -0,0 +1,118 @@ +# Secure Fraud Detection Demo + +This demo showcases the implementation of a secure fraud detection system which is available only to users authenticated with Google. +It uses the `gpt-3.5-turbo` LLM, use `quarkus.langchain4j.openai.chat-model.model-name` property to select a different model. + +## The Demo + +### Setup + +The demo requires that your Google account's name and email are configured. +You can use system or env properties, see `Running the Demo` section below. + +When the application starts, 5 transactions with random amounts between 1 and 1000 are generated for the registered user. +A random city is also assigned to each transaction. + +The setup is defined in the [Setup.java](./src/main/java/io/quarkiverse/langchain4j/samples/Setup.java) class. + +The registered user and transactions are stored in a PostgreSQL database. When running the demo in dev mode (recommended), the database is automatically created and populated. + +### Content Retrieval + +To enable fraud detection, we provide the LLM with access to the custom [FraudDetectionContentRetriever](./src/main/java/io/quarkiverse/langchain4j/samples/FraudDetectionContentRetriever.java) content retriever. + +`FraudDetectionContentRetriever` is registered by [FraudDetectionRetrievalAugmentor](./src/main/java/io/quarkiverse/langchain4j/samples/FraudDetectionRetrievalAugmentor.java). + +It retrieves transaction data for the currently authenticated user through two Panache repositories: + +- [CustomerRepository.java](./src/main/java/io/quarkiverse/langchain4j/samples/CustomerRepository.java) +- [TransactionRepository.java](./src/main/java/io/quarkiverse/langchain4j/samples/TransactionRepository.java) + +It extracts the authenticated user's name and email from a custom memory id string representation. Memory id is created by [SecureMemoryIdProvider](./src/main/java/io/quarkiverse/langchain4j/samples/SecureMemoryIdProvider.java) from the authenticated security identity. `SecureMemoryIdProvider` is registered as a Java service provider. + +Currently, the memory id has the following format: `userName:userEmail#suffix` with an AI service specific `#suffix` added by the extension runtime in order to correctly segregate memory of concurrent requests to different AI services. + +### 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.FraudDetectionAi` interface uses specific annotations to define the LLM: + +```java +@RegisterAiService(retrievalAugmentor = FraudDetectionRetrievalAugmentor.class) +``` + +For each message, the prompt is engineered to help the LLM understand the context and answer the request: + +```java + @SystemMessage(""" + You are a bank account fraud detection AI. You have to detect frauds in transactions. + """) + @UserMessage(""" + Your task is to detect whether a fraud was committed for the customer. + + Answer with a **single** JSON document containing: + - the customer name in the 'customer-name' key + - the transaction limit in the 'transaction-limit' key + - the computed sum of all transactions committed during the last 15 minutes in the 'total' key + - the 'fraud' key set to true if the computed sum of all transactions is greater than the transaction limit + - the 'transactions' key containing an array of JSON objects. Each object must have transaction 'amount', 'city' and formatted 'time' keys. + - the 'explanation' key containing an explanation of your answer. + - the 'email' key containing the customer email if the fraud was detected. + + Your response must be just the raw JSON document, without ```json, ``` or anything else. Do not use null JSON properties. + """) +@Timeout(value = 2, unit = ChronoUnit.MINUTES) +String detectAmountFraudForCustomer(); +``` + +_Note:_ You can also use fault tolerance annotations in combination with the prompt annotations. + +### 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 io.quarkus.security.Authenticated; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/fraud") +@Authenticated +public class FraudDetectionResource { + + private final FraudDetectionAi service; + + public FraudDetectionResource(FraudDetectionAi service) { + this.service = service; + } + + @GET + @Path("/amount") + public String detectBaseOnAmount() { + return service.detectAmountFraudForCustomer(); + } +} +``` + +`FraudDetectionResource` can only be accessed by authenticated users. + +## Google Authentication + +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. + +## Running the Demo + +To run the demo, use the following command: + +```shell +mvn quarkus:dev -Dname="Firstname Familyname" -Demail=someuser@gmail.com +``` + +Then, access `http://localhost:8080`, login to Google, and follow a provided application link to check the fraud. + diff --git a/samples/secure-fraud-detection/pom.xml b/samples/secure-fraud-detection/pom.xml new file mode 100644 index 000000000..fc976c2fc --- /dev/null +++ b/samples/secure-fraud-detection/pom.xml @@ -0,0 +1,134 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-sample-secure-fraud-detection + Quarkus LangChain4j - Sample - Secure Fraud Detection + 1.0-SNAPSHOT + + + 3.13.0 + true + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 3.9.4 + true + 3.2.5 + 0.15.1 + + + + + + ${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-openai + ${quarkus-langchain4j.version} + + + io.quarkus + quarkus-smallrye-fault-tolerance + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-hibernate-orm-panache + + + 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-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Customer.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Customer.java new file mode 100644 index 000000000..d67c3f02e --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Customer.java @@ -0,0 +1,16 @@ +package io.quarkiverse.langchain4j.sample; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Customer { + + @Id + @GeneratedValue + public Long id; + public String name; + public String email; + public int transactionLimit; +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/CustomerConfig.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/CustomerConfig.java new file mode 100644 index 000000000..fdcef80bf --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/CustomerConfig.java @@ -0,0 +1,10 @@ +package io.quarkiverse.langchain4j.sample; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "customer") +public interface CustomerConfig { + + String name(); + String email(); +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/CustomerRepository.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/CustomerRepository.java new file mode 100644 index 000000000..7e096fdf7 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/CustomerRepository.java @@ -0,0 +1,22 @@ +package io.quarkiverse.langchain4j.sample; + +import org.jboss.logging.Logger; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class CustomerRepository implements PanacheRepository { + private static final Logger log = Logger.getLogger(CustomerRepository.class); + /* + * Transaction limit for the customer. + */ + public int getTransactionLimit(String customerName, String customerEmail) { + Customer customer = + find("name = ?1 and email = ?2", customerName, customerEmail).firstResult(); + if (customer == null) { + throw new MissingCustomerException(); + } + return customer.transactionLimit; + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionAi.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionAi.java new file mode 100644 index 000000000..0b4173768 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionAi.java @@ -0,0 +1,33 @@ +package io.quarkiverse.langchain4j.sample; + +import java.time.temporal.ChronoUnit; + +import org.eclipse.microprofile.faulttolerance.Timeout; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; + +@RegisterAiService(retrievalAugmentor = FraudDetectionRetrievalAugmentor.class) +public interface FraudDetectionAi { + + @SystemMessage(""" + You are a bank account fraud detection AI. You have to detect frauds in transactions. + """) + @UserMessage(""" + Your task is to detect whether a fraud was committed for the customer. + + Answer with a **single** JSON document containing: + - the customer name in the 'customer-name' key + - the transaction limit in the 'transaction-limit' key + - the computed sum of all transactions committed during the last 15 minutes in the 'total' key + - the 'fraud' key set to true if the computed sum of all transactions is greater than the transaction limit + - the 'transactions' key containing an array of JSON objects. Each object must have transaction 'amount', 'city' and formatted 'time' keys. + - the 'explanation' key containing an explanation of your answer. + - the 'email' key containing the customer email if the fraud was detected. + + Your response must be just the raw JSON document, without ```json, ``` or anything else. Do not use null JSON properties. + """) + @Timeout(value = 2, unit = ChronoUnit.MINUTES) + String detectAmountFraudForCustomer(); +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionContentRetriever.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionContentRetriever.java new file mode 100644 index 000000000..47480cd8f --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionContentRetriever.java @@ -0,0 +1,53 @@ +package io.quarkiverse.langchain4j.sample; + +import java.time.ZoneOffset; +import java.util.List; + +import org.jboss.logging.Logger; + +import dev.langchain4j.rag.content.Content; +import dev.langchain4j.rag.content.retriever.ContentRetriever; +import dev.langchain4j.rag.query.Query; +import io.quarkiverse.langchain4j.sample.SecureMemoryIdProvider.UserNameAndEmail; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; + +@ApplicationScoped +public class FraudDetectionContentRetriever implements ContentRetriever { + private static final Logger log = Logger.getLogger(FraudDetectionContentRetriever.class); + + @Inject + TransactionRepository transactionRepository; + + @Inject + CustomerRepository customerRepository; + + @ActivateRequestContext + @Override + public List retrieve(Query query) { + UserNameAndEmail userNameAndEmail = UserNameAndEmail.fromString((String)query.metadata().chatMemoryId()); + log.infof("Use customer name %s and email %s to retrieve content", userNameAndEmail.getName(), userNameAndEmail.getEmail()); + + int transactionLimit = customerRepository.getTransactionLimit(userNameAndEmail.getName(), userNameAndEmail.getEmail()); + + List transactions = transactionRepository.getTransactionsForCustomer(userNameAndEmail.getName(), userNameAndEmail.getEmail()); + + JsonArray jsonTransactions = new JsonArray(); + for (Transaction t : transactions) { + jsonTransactions.add(JsonObject.of( + "customer-name", t.customerName, + "customer-email", t.customerEmail, + "transaction-amount", t.amount, + "transaction-city", t.city, + "transaction-time-in-seconds-from-the-epoch", t.time.toEpochSecond(ZoneOffset.UTC))); + } + + JsonObject json = JsonObject.of( + "transaction-limit", transactionLimit, + "transactions", jsonTransactions); + return List.of(Content.from(json.toString())); + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionResource.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionResource.java new file mode 100644 index 000000000..3ef02263a --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionResource.java @@ -0,0 +1,26 @@ +package io.quarkiverse.langchain4j.sample; + +import io.quarkus.security.Authenticated; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/fraud") +@Authenticated +public class FraudDetectionResource { + + private final FraudDetectionAi service; + + public FraudDetectionResource(FraudDetectionAi service) { + this.service = service; + } + + @GET + @Path("/amount") + public String detectBaseOnAmount() { + try { + return service.detectAmountFraudForCustomer(); + } catch (RuntimeException ex) { + throw (ex.getCause() instanceof MissingCustomerException) ? (MissingCustomerException)ex.getCause() : ex; + } + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionRetrievalAugmentor.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionRetrievalAugmentor.java new file mode 100644 index 000000000..062bc1740 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/FraudDetectionRetrievalAugmentor.java @@ -0,0 +1,22 @@ +package io.quarkiverse.langchain4j.sample; + +import java.util.function.Supplier; + +import dev.langchain4j.rag.DefaultRetrievalAugmentor; +import dev.langchain4j.rag.RetrievalAugmentor; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class FraudDetectionRetrievalAugmentor implements Supplier { + + @Inject + FraudDetectionContentRetriever contentRetriever; + + @Override + public RetrievalAugmentor get() { + return DefaultRetrievalAugmentor.builder() + .contentRetriever(contentRetriever) + .build(); + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/LoginResource.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/LoginResource.java new file mode 100644 index 000000000..baf1e6d0c --- /dev/null +++ b/samples/secure-fraud-detection/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 fraud detection page to the authenticated user + */ +@Path("/login") +@Authenticated +public class LoginResource { + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + Template fraudDetection; + + @GET + @Produces("text/html") + public TemplateInstance fraudDetection() { + return fraudDetection.data("name", idToken.getName()); + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/LogoutResource.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/LogoutResource.java new file mode 100644 index 000000000..e2635772e --- /dev/null +++ b/samples/secure-fraud-detection/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-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerException.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerException.java new file mode 100644 index 000000000..039d5e164 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerException.java @@ -0,0 +1,4 @@ +package io.quarkiverse.langchain4j.sample; + +public class MissingCustomerException extends RuntimeException { +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerExceptionMapper.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerExceptionMapper.java new file mode 100644 index 000000000..51693c85e --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerExceptionMapper.java @@ -0,0 +1,18 @@ +package io.quarkiverse.langchain4j.sample; + +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class MissingCustomerExceptionMapper implements ExceptionMapper { + @Context + UriInfo uriInfo; + + @Override + public Response toResponse(MissingCustomerException ex) { + return Response.seeOther(uriInfo.getBaseUriBuilder().path("missingCustomer").build()).build(); + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerResource.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerResource.java new file mode 100644 index 000000000..f38f0d7ae --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/MissingCustomerResource.java @@ -0,0 +1,33 @@ +package io.quarkiverse.langchain4j.sample; + +import org.eclipse.microprofile.jwt.Claims; +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; + +@Path("/missingCustomer") +@Authenticated +public class MissingCustomerResource { + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + Template missingCustomer; + + @GET + @Produces("text/html") + public TemplateInstance missingCustomer() { + return missingCustomer.data("given_name", idToken.getClaim("given_name")) + .data("name", idToken.getName()) + .data("email", idToken.getClaim(Claims.email)); + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/SecureMemoryIdProvider.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/SecureMemoryIdProvider.java new file mode 100644 index 000000000..33c9098d8 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/SecureMemoryIdProvider.java @@ -0,0 +1,75 @@ +package io.quarkiverse.langchain4j.sample; + +import java.util.Objects; + +import org.eclipse.microprofile.jwt.Claims; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.jboss.logging.Logger; + +import io.quarkiverse.langchain4j.spi.DefaultMemoryIdProvider; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.enterprise.context.ContextNotActiveException; + +public class SecureMemoryIdProvider implements DefaultMemoryIdProvider { + + private static final Logger log = Logger.getLogger(SecureMemoryIdProvider.class); + + @Override + public Object getMemoryId() { + InstanceHandle instance = Arc.container().instance(SecurityIdentity.class); + if (instance.isAvailable()) { + try { + JsonWebToken jwt = (JsonWebToken)instance.get().getPrincipal(); + return new UserNameAndEmail(jwt.getName(), jwt.getClaim(Claims.email)); + } catch (ContextNotActiveException ignored) { + log.info("CDI context is null"); + // this means that the request scope was not active, so we can't provide a value + return null; + } + } + log.info("SecurityIdentity is unavailable"); + return null; + } + + public static class UserNameAndEmail { + private final String name; + private final String email; + UserNameAndEmail(String name, String email) { + this.name = Objects.requireNonNull(name); + this.email = Objects.requireNonNull(email); + } + public String getName() { + return name; + } + public String getEmail() { + return email; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof UserNameAndEmail)) { + return false; + } + UserNameAndEmail otherMemoryId = (UserNameAndEmail)other; + return name.equals(otherMemoryId.name) && email.equals(otherMemoryId.email); + } + + @Override + public int hashCode() { + return Objects.hash(name, email); + } + + @Override + public String toString() { + return name + ":" + email; + } + + public static UserNameAndEmail fromString(String chatMemoryId) { + // name:email#suffix + String[] nameAndEmail = chatMemoryId.substring(0, chatMemoryId.indexOf('#')).split(":"); + return new UserNameAndEmail(nameAndEmail[0], nameAndEmail[1]); + } + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Setup.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Setup.java new file mode 100644 index 000000000..4dc038fd3 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Setup.java @@ -0,0 +1,51 @@ +package io.quarkiverse.langchain4j.sample; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Random; + +import io.quarkus.runtime.StartupEvent; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +@ApplicationScoped +public class Setup { + + public static List CITIES = List.of("Paris", "Lyon", "Marseille", "Bordeaux", "Toulouse", "Nantes", "Brest", + "Clermont-Ferrand", "La Rochelle", "Lille", "Metz", "Strasbourg", "Nancy", "Valence", "Avignon", "Montpellier", + "Nime", "Arles", "Nice", "Cannes"); + + public static String getARandomCity() { + return CITIES.get(new Random().nextInt(CITIES.size())); + } + + @Inject + CustomerConfig config; + + @Transactional + public void init(@Observes StartupEvent ev, CustomerRepository customers, TransactionRepository transactions) { + customers.deleteAll(); + Random random = new Random(); + + var customer = new Customer(); + customer.name = config.name(); + customer.email = config.email(); + customer.transactionLimit = 1000; + customers.persist(customer); + + transactions.deleteAll(); // Delete all transactions + for (int i = 0; i < 5; i++) { + var transaction = new Transaction(); + transaction.customerName = customer.name; + transaction.customerEmail = customer.email; + transaction.amount = random.nextInt(1000) + 1; + transaction.time = LocalDateTime.now().minusMinutes(random.nextInt(20)); + transaction.city = getARandomCity(); + transactions.persist(transaction); + } + + System.out.println("Customer: " + customer.name + " - " + customer.email); + } +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Transaction.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Transaction.java new file mode 100644 index 000000000..1a4c554ad --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/Transaction.java @@ -0,0 +1,26 @@ +package io.quarkiverse.langchain4j.sample; + +import java.time.LocalDateTime; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Transaction { + + @Id + @GeneratedValue + public Long id; + + public double amount; + + public String customerName; + + public String customerEmail; + + public String city; + + public LocalDateTime time; + +} diff --git a/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/TransactionRepository.java b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/TransactionRepository.java new file mode 100644 index 000000000..76d3a90f7 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/java/io/quarkiverse/langchain4j/sample/TransactionRepository.java @@ -0,0 +1,21 @@ +package io.quarkiverse.langchain4j.sample; + +import java.time.LocalDateTime; +import java.util.List; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TransactionRepository implements PanacheRepository { + + /* + * List of transactions during the last 30 minutes. + */ + public List getTransactionsForCustomer(String customerName, String customerEmail) { + return find("customerName = ?1 and customerEmail = ?2 and time > ?3", + customerName, + customerEmail, + LocalDateTime.now().minusMinutes(30)).list(); + } +} diff --git a/samples/secure-fraud-detection/src/main/resources/META-INF/resources/images/google.png b/samples/secure-fraud-detection/src/main/resources/META-INF/resources/images/google.png new file mode 100644 index 000000000..e2503df4e Binary files /dev/null and b/samples/secure-fraud-detection/src/main/resources/META-INF/resources/images/google.png differ diff --git a/samples/secure-fraud-detection/src/main/resources/META-INF/resources/index.html b/samples/secure-fraud-detection/src/main/resources/META-INF/resources/index.html new file mode 100644 index 000000000..6fd3636ed --- /dev/null +++ b/samples/secure-fraud-detection/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,133 @@ + + + + + Secure Fraud Detection + + + + + + +
+
+
+

Login

+ + + + +
Login with Google
+ +
+
+
+ +
+ +
+ + + + diff --git a/samples/secure-fraud-detection/src/main/resources/META-INF/services/io.quarkiverse.langchain4j.spi.DefaultMemoryIdProvider b/samples/secure-fraud-detection/src/main/resources/META-INF/services/io.quarkiverse.langchain4j.spi.DefaultMemoryIdProvider new file mode 100644 index 000000000..4a8d26572 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/resources/META-INF/services/io.quarkiverse.langchain4j.spi.DefaultMemoryIdProvider @@ -0,0 +1 @@ +io.quarkiverse.langchain4j.sample.SecureMemoryIdProvider \ No newline at end of file diff --git a/samples/secure-fraud-detection/src/main/resources/application.properties b/samples/secure-fraud-detection/src/main/resources/application.properties new file mode 100644 index 000000000..6c50ae0d2 --- /dev/null +++ b/samples/secure-fraud-detection/src/main/resources/application.properties @@ -0,0 +1,14 @@ +quarkus.langchain4j.openai.timeout=60s +quarkus.langchain4j.openai.chat-model.temperature=0 +quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY} + +quarkus.oidc.provider=google +quarkus.oidc.client-id=${GOOGLE_CLIENT_ID} +quarkus.oidc.credentials.secret=${GOOGLE_CLIENT_SECRET} +quarkus.oidc.authentication.redirect-path=/login + +quarkus.langchain4j.openai.log-requests=true +quarkus.langchain4j.openai.log-responses=true + +customer.name=${name} +customer.email=${email} diff --git a/samples/secure-fraud-detection/src/main/resources/templates/fraudDetection.html b/samples/secure-fraud-detection/src/main/resources/templates/fraudDetection.html new file mode 100644 index 000000000..080a487af --- /dev/null +++ b/samples/secure-fraud-detection/src/main/resources/templates/fraudDetection.html @@ -0,0 +1,18 @@ + + + + +Secure Fraud Detection + + +

Hello {name}, please check fraud occurrences by amount:

+ + + + +
+ Detect fraud +
+ Logout + + diff --git a/samples/secure-fraud-detection/src/main/resources/templates/missingCustomer.html b/samples/secure-fraud-detection/src/main/resources/templates/missingCustomer.html new file mode 100644 index 000000000..12b1e4a8b --- /dev/null +++ b/samples/secure-fraud-detection/src/main/resources/templates/missingCustomer.html @@ -0,0 +1,18 @@ + + + + +Missing Customer + + + {given_name}, please make sure your Google account's full name and email are correctly registered at the startup using -Dname="{name}" and -Demail={email} system properties +

+ + + + +
+ Back to the main page +
+ +