diff --git a/pom.xml b/pom.xml index 473cba4f..9c29cc78 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,7 @@ helidon-examples payara-micro-example watsonx-ai-examples + yugabytedb-example diff --git a/yugabytedb-example/pom.xml b/yugabytedb-example/pom.xml new file mode 100644 index 00000000..07eae667 --- /dev/null +++ b/yugabytedb-example/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + dev.langchain4j + yugabytedb-example + 1.8.0-beta15-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + + dev.langchain4j + langchain4j-community-yugabytedb + 1.8.0-beta15-SNAPSHOT + + + + org.testcontainers + testcontainers + 1.19.7 + + + + dev.langchain4j + langchain4j-embeddings-all-minilm-l6-v2 + 1.7.1-beta14 + + + + org.slf4j + slf4j-simple + 2.0.12 + + + + + + diff --git a/yugabytedb-example/run-example.sh b/yugabytedb-example/run-example.sh new file mode 100755 index 00000000..aba5353f --- /dev/null +++ b/yugabytedb-example/run-example.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Run YugabyteDB examples as standalone applications +# This avoids exec:java classloader issues with Testcontainers + +set -e + +EXAMPLE_CLASS=${1:-YugabyteDBEmbeddingStoreExample} + +echo "๐Ÿš€ Building project..." +../mvnw clean compile -q + +echo "๐Ÿ“ฆ Running example: $EXAMPLE_CLASS" +echo "" + +# Run with proper classpath +../mvnw exec:exec -Dexec.executable="java" \ + -Dexec.args="-cp %classpath $EXAMPLE_CLASS" \ + -q + +echo "" +echo "โœ… Example execution completed!" + diff --git a/yugabytedb-example/src/main/java/YugabyteDBEmbeddingStoreExample.java b/yugabytedb-example/src/main/java/YugabyteDBEmbeddingStoreExample.java new file mode 100644 index 00000000..3fe7abf3 --- /dev/null +++ b/yugabytedb-example/src/main/java/YugabyteDBEmbeddingStoreExample.java @@ -0,0 +1,104 @@ +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEmbeddingStore; +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEngine; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; +import dev.langchain4j.store.embedding.EmbeddingMatch; +import dev.langchain4j.store.embedding.EmbeddingSearchRequest; +import dev.langchain4j.store.embedding.EmbeddingStore; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.List; + +public class YugabyteDBEmbeddingStoreExample { + + public static void main(String[] args) { + GenericContainer yugabyteContainer = null; + YugabyteDBEngine engine = null; + + try { + DockerImageName dockerImageName = DockerImageName.parse("yugabytedb/yugabyte:2025.1.0.1-b3"); + yugabyteContainer = new GenericContainer<>(dockerImageName) + .withExposedPorts(5433, 7000, 9000, 15433, 9042) + .withCommand("bin/yugabyted", "start", "--background=false") + .waitingFor(Wait.forListeningPorts(5433).withStartupTimeout(Duration.ofMinutes(5))); + + yugabyteContainer.start(); + + EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); + + // Create YugabyteDB engine with PostgreSQL driver + engine = YugabyteDBEngine.builder() + .host(yugabyteContainer.getHost()) + .port(yugabyteContainer.getMappedPort(5433)) + .database("yugabyte") + .username("yugabyte") + .password("yugabyte") + .usePostgreSQLDriver(true) // Use PostgreSQL JDBC driver + .build(); + + EmbeddingStore embeddingStore = YugabyteDBEmbeddingStore.builder() + .engine(engine) + .tableName("test_embeddings") + .dimension(embeddingModel.dimension()) + .createTableIfNotExists(true) + .build(); + + TextSegment segment1 = TextSegment.from("I like football."); + Embedding embedding1 = embeddingModel.embed(segment1).content(); + embeddingStore.add(embedding1, segment1); + + TextSegment segment2 = TextSegment.from("The weather is good today."); + Embedding embedding2 = embeddingModel.embed(segment2).content(); + embeddingStore.add(embedding2, segment2); + + Embedding queryEmbedding = embeddingModel.embed("What is your favourite sport?").content(); + + EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder() + .queryEmbedding(queryEmbedding) + .maxResults(1) + .build(); + + List> relevant = embeddingStore.search(embeddingSearchRequest).matches(); + + EmbeddingMatch embeddingMatch = relevant.get(0); + + System.out.println(embeddingMatch.score()); // ~0.8144 + System.out.println(embeddingMatch.embedded().text()); // I like football. + + System.out.println("\nโœ… Example completed successfully!"); + + } catch (Exception e) { + System.err.println("โŒ Error running example: " + e.getMessage()); + e.printStackTrace(); + } finally { + // Give Testcontainers time to cleanup gracefully + try { + Thread.sleep(2000); + } catch (InterruptedException ignored) { + } + + // Cleanup resources + System.out.println("๐Ÿงน Cleaning up resources..."); + if (engine != null) { + try { + engine.close(); + } catch (Exception e) { + System.err.println("Error closing engine: " + e.getMessage()); + } + } + if (yugabyteContainer != null) { + try { + yugabyteContainer.stop(); + } catch (Exception e) { + System.err.println("Error stopping container: " + e.getMessage()); + } + } + } + } +} + diff --git a/yugabytedb-example/src/main/java/YugabyteDBEmbeddingStoreWithMetadataExample.java b/yugabytedb-example/src/main/java/YugabyteDBEmbeddingStoreWithMetadataExample.java new file mode 100644 index 00000000..b4fe517b --- /dev/null +++ b/yugabytedb-example/src/main/java/YugabyteDBEmbeddingStoreWithMetadataExample.java @@ -0,0 +1,131 @@ +import dev.langchain4j.community.store.embedding.yugabytedb.DefaultMetadataStorageConfig; +import dev.langchain4j.community.store.embedding.yugabytedb.MetadataStorageConfig; +import dev.langchain4j.community.store.embedding.yugabytedb.MetadataStorageMode; +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEmbeddingStore; +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEngine; +import dev.langchain4j.data.document.Metadata; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; +import dev.langchain4j.store.embedding.EmbeddingMatch; +import dev.langchain4j.store.embedding.EmbeddingSearchRequest; +import dev.langchain4j.store.embedding.EmbeddingStore; +import dev.langchain4j.store.embedding.filter.Filter; +import dev.langchain4j.store.embedding.filter.comparison.IsEqualTo; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.List; + +public class YugabyteDBEmbeddingStoreWithMetadataExample { + + public static void main(String[] args) { + GenericContainer yugabyteContainer = null; + YugabyteDBEngine engine = null; + + try { + DockerImageName dockerImageName = DockerImageName.parse("yugabytedb/yugabyte:2025.1.0.1-b3"); + yugabyteContainer = new GenericContainer<>(dockerImageName) + .withExposedPorts(5433, 7000, 9000, 15433, 9042) + .withCommand("bin/yugabyted", "start", "--background=false") + .waitingFor(Wait.forListeningPorts(5433).withStartupTimeout(Duration.ofMinutes(5))); + + yugabyteContainer.start(); + + EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); + + // Create YugabyteDB engine with PostgreSQL driver + engine = YugabyteDBEngine.builder() + .host(yugabyteContainer.getHost()) + .port(yugabyteContainer.getMappedPort(5433)) + .database("yugabyte") + .username("yugabyte") + .password("yugabyte") + .usePostgreSQLDriver(true) + .build(); + + // Configure metadata storage (JSONB format) + MetadataStorageConfig metadataConfig = DefaultMetadataStorageConfig.builder() + .storageMode(MetadataStorageMode.COMBINED_JSONB) + .build(); + + EmbeddingStore embeddingStore = YugabyteDBEmbeddingStore.builder() + .engine(engine) + .tableName("test_embeddings_with_metadata") + .dimension(embeddingModel.dimension()) + .metadataStorageConfig(metadataConfig) + .createTableIfNotExists(true) + .build(); + + // Add embeddings with metadata + TextSegment segment1 = TextSegment.from("I like football.", + Metadata.from("category", "sports").put("user", "john")); + Embedding embedding1 = embeddingModel.embed(segment1).content(); + embeddingStore.add(embedding1, segment1); + + TextSegment segment2 = TextSegment.from("The weather is good today.", + Metadata.from("category", "weather").put("user", "alice")); + Embedding embedding2 = embeddingModel.embed(segment2).content(); + embeddingStore.add(embedding2, segment2); + + TextSegment segment3 = TextSegment.from("I love basketball.", + Metadata.from("category", "sports").put("user", "bob")); + Embedding embedding3 = embeddingModel.embed(segment3).content(); + embeddingStore.add(embedding3, segment3); + + // Search with metadata filter + Embedding queryEmbedding = embeddingModel.embed("What sport do you like?").content(); + + Filter categoryFilter = new IsEqualTo("category", "sports"); + + EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() + .queryEmbedding(queryEmbedding) + .maxResults(5) + .filter(categoryFilter) + .build(); + + List> relevant = embeddingStore.search(searchRequest).matches(); + + System.out.println("Found " + relevant.size() + " sports-related results:"); + for (EmbeddingMatch match : relevant) { + System.out.println("Score: " + match.score()); + System.out.println("Text: " + match.embedded().text()); + System.out.println("Metadata: " + match.embedded().metadata()); + System.out.println("---"); + } + + System.out.println("\nโœ… Example completed successfully!"); + + } catch (Exception e) { + System.err.println("โŒ Error running example: " + e.getMessage()); + e.printStackTrace(); + } finally { + // Give Testcontainers time to cleanup gracefully + try { + Thread.sleep(2000); + } catch (InterruptedException ignored) { + } + + // Cleanup resources + System.out.println("๐Ÿงน Cleaning up resources..."); + if (engine != null) { + try { + engine.close(); + } catch (Exception e) { + System.err.println("Error closing engine: " + e.getMessage()); + } + } + if (yugabyteContainer != null) { + try { + yugabyteContainer.stop(); + } catch (Exception e) { + System.err.println("Error stopping container: " + e.getMessage()); + } + } + } + } +} + diff --git a/yugabytedb-example/src/main/java/YugabyteDBWithPostgreSQLDriverExample.java b/yugabytedb-example/src/main/java/YugabyteDBWithPostgreSQLDriverExample.java new file mode 100644 index 00000000..17007c61 --- /dev/null +++ b/yugabytedb-example/src/main/java/YugabyteDBWithPostgreSQLDriverExample.java @@ -0,0 +1,126 @@ +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEmbeddingStore; +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEngine; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; +import dev.langchain4j.store.embedding.EmbeddingMatch; +import dev.langchain4j.store.embedding.EmbeddingSearchRequest; +import dev.langchain4j.store.embedding.EmbeddingStore; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.List; + +/** + * This example demonstrates using YugabyteDB with the PostgreSQL JDBC driver. + * + * PostgreSQL JDBC driver is recommended for: + * - Standard SQL operations + * - Maximum PostgreSQL compatibility + * - Simple single-node or replicated deployments + */ +public class YugabyteDBWithPostgreSQLDriverExample { + + public static void main(String[] args) { + GenericContainer yugabyteContainer = null; + YugabyteDBEngine engine = null; + + try { + DockerImageName dockerImageName = DockerImageName.parse("yugabytedb/yugabyte:2025.1.0.1-b3"); + yugabyteContainer = new GenericContainer<>(dockerImageName) + .withExposedPorts(5433, 7000, 9000, 15433, 9042) + .withCommand("bin/yugabyted", "start", "--background=false") + .waitingFor(Wait.forListeningPorts(5433).withStartupTimeout(Duration.ofMinutes(5))); + + yugabyteContainer.start(); + + EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); + + System.out.println("=== Using PostgreSQL JDBC Driver ==="); + System.out.println("Driver: org.postgresql.Driver"); + System.out.println("Best for: Standard SQL operations and PostgreSQL compatibility"); + System.out.println(); + + // Create YugabyteDB engine with PostgreSQL JDBC driver + engine = YugabyteDBEngine.builder() + .host(yugabyteContainer.getHost()) + .port(yugabyteContainer.getMappedPort(5433)) + .database("yugabyte") + .username("yugabyte") + .password("yugabyte") + .usePostgreSQLDriver(true) // โ† Use PostgreSQL JDBC driver + .maxPoolSize(10) + .build(); + + EmbeddingStore embeddingStore = YugabyteDBEmbeddingStore.builder() + .engine(engine) + .tableName("postgres_driver_embeddings") + .dimension(embeddingModel.dimension()) + .createTableIfNotExists(true) + .build(); + + // Add some sample data + TextSegment segment1 = TextSegment.from("PostgreSQL driver provides excellent compatibility."); + Embedding embedding1 = embeddingModel.embed(segment1).content(); + embeddingStore.add(embedding1, segment1); + + TextSegment segment2 = TextSegment.from("YugabyteDB is PostgreSQL compatible."); + Embedding embedding2 = embeddingModel.embed(segment2).content(); + embeddingStore.add(embedding2, segment2); + + TextSegment segment3 = TextSegment.from("Vector search works seamlessly with pgvector."); + Embedding embedding3 = embeddingModel.embed(segment3).content(); + embeddingStore.add(embedding3, segment3); + + // Search for similar embeddings + Embedding queryEmbedding = embeddingModel.embed("Tell me about PostgreSQL compatibility").content(); + + EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() + .queryEmbedding(queryEmbedding) + .maxResults(2) + .build(); + + List> relevant = embeddingStore.search(searchRequest).matches(); + + System.out.println("Search Results:"); + for (EmbeddingMatch match : relevant) { + System.out.println(" Score: " + String.format("%.4f", match.score())); + System.out.println(" Text: " + match.embedded().text()); + System.out.println(); + } + + System.out.println("\nโœ… Example completed successfully!"); + + } catch (Exception e) { + System.err.println("โŒ Error running example: " + e.getMessage()); + e.printStackTrace(); + } finally { + // Give Testcontainers time to cleanup gracefully + try { + Thread.sleep(2000); + } catch (InterruptedException ignored) { + } + + // Cleanup resources + System.out.println("๐Ÿงน Cleaning up resources..."); + if (engine != null) { + try { + engine.close(); + } catch (Exception e) { + System.err.println("Error closing engine: " + e.getMessage()); + } + } + if (yugabyteContainer != null) { + try { + yugabyteContainer.stop(); + } catch (Exception e) { + System.err.println("Error stopping container: " + e.getMessage()); + } + } + } + } +} + diff --git a/yugabytedb-example/src/main/java/YugabyteDBWithSmartDriverExample.java b/yugabytedb-example/src/main/java/YugabyteDBWithSmartDriverExample.java new file mode 100644 index 00000000..02c20abe --- /dev/null +++ b/yugabytedb-example/src/main/java/YugabyteDBWithSmartDriverExample.java @@ -0,0 +1,132 @@ +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEmbeddingStore; +import dev.langchain4j.community.store.embedding.yugabytedb.YugabyteDBEngine; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; +import dev.langchain4j.store.embedding.EmbeddingMatch; +import dev.langchain4j.store.embedding.EmbeddingSearchRequest; +import dev.langchain4j.store.embedding.EmbeddingStore; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.List; + +/** + * This example demonstrates using YugabyteDB with the YugabyteDB Smart Driver. + * + * YugabyteDB Smart Driver is recommended for: + * - Advanced distributed database features + * - Topology-aware load balancing + * - Node-aware connection management + * - Multi-region deployments + */ +public class YugabyteDBWithSmartDriverExample { + + public static void main(String[] args) { + GenericContainer yugabyteContainer = null; + YugabyteDBEngine engine = null; + + try { + DockerImageName dockerImageName = DockerImageName.parse("yugabytedb/yugabyte:2025.1.0.1-b3"); + yugabyteContainer = new GenericContainer<>(dockerImageName) + .withExposedPorts(5433, 7000, 9000, 15433, 9042) + .withCommand("bin/yugabyted", "start", "--background=false") + .waitingFor(Wait.forListeningPorts(5433).withStartupTimeout(Duration.ofMinutes(5))); + + yugabyteContainer.start(); + + EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); + + System.out.println("=== Using YugabyteDB Smart Driver ==="); + System.out.println("Driver: com.yugabyte.Driver"); + System.out.println("Best for: Distributed deployments with topology-aware load balancing"); + System.out.println(); + + // Create YugabyteDB engine with Smart Driver + engine = YugabyteDBEngine.builder() + .host(yugabyteContainer.getHost()) + .port(yugabyteContainer.getMappedPort(5433)) + .database("yugabyte") + .username("yugabyte") + .password("yugabyte") + .usePostgreSQLDriver(false) // โ† Use YugabyteDB Smart Driver (default) + .maxPoolSize(10) + .build(); + + EmbeddingStore embeddingStore = YugabyteDBEmbeddingStore.builder() + .engine(engine) + .tableName("smart_driver_embeddings") + .dimension(embeddingModel.dimension()) + .createTableIfNotExists(true) + .build(); + + // Add some sample data + TextSegment segment1 = TextSegment.from("Smart Driver provides topology-aware load balancing."); + Embedding embedding1 = embeddingModel.embed(segment1).content(); + embeddingStore.add(embedding1, segment1); + + TextSegment segment2 = TextSegment.from("Distributed databases benefit from cluster-aware drivers."); + Embedding embedding2 = embeddingModel.embed(segment2).content(); + embeddingStore.add(embedding2, segment2); + + TextSegment segment3 = TextSegment.from("Multi-region deployments require smart connection management."); + Embedding embedding3 = embeddingModel.embed(segment3).content(); + embeddingStore.add(embedding3, segment3); + + // Search for similar embeddings + Embedding queryEmbedding = embeddingModel.embed("How do distributed databases handle connections?").content(); + + EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() + .queryEmbedding(queryEmbedding) + .maxResults(2) + .build(); + + List> relevant = embeddingStore.search(searchRequest).matches(); + + System.out.println("Search Results:"); + for (EmbeddingMatch match : relevant) { + System.out.println(" Score: " + String.format("%.4f", match.score())); + System.out.println(" Text: " + match.embedded().text()); + System.out.println(); + } + + System.out.println("\nSmart Driver Features:"); + System.out.println(" โœ“ Topology-aware load balancing"); + System.out.println(" โœ“ Automatic failover"); + System.out.println(" โœ“ Connection pooling per node"); + System.out.println(" โœ“ Preferred region support"); + + System.out.println("\nโœ… Example completed successfully!"); + + } catch (Exception e) { + System.err.println("โŒ Error running example: " + e.getMessage()); + e.printStackTrace(); + } finally { + // Give Testcontainers time to cleanup gracefully + try { + Thread.sleep(2000); + } catch (InterruptedException ignored) { + } + + // Cleanup resources + System.out.println("๐Ÿงน Cleaning up resources..."); + if (engine != null) { + try { + engine.close(); + } catch (Exception e) { + System.err.println("Error closing engine: " + e.getMessage()); + } + } + if (yugabyteContainer != null) { + try { + yugabyteContainer.stop(); + } catch (Exception e) { + System.err.println("Error stopping container: " + e.getMessage()); + } + } + } + } +}