Milvus embedding store integration for LangChain4j
Frequently used patterns and best practices for coding agents.
import java.util.List;
import java.util.ArrayList;
List<String> documents = loadDocuments();
List<Embedding> embeddings = new ArrayList<>();
List<TextSegment> segments = new ArrayList<>();
for (String doc : documents) {
embeddings.add(model.embed(doc).content());
segments.add(TextSegment.from(doc));
}
// Batch insert (auto-generated IDs)
List<String> ids = store.addAll(embeddings);int batchSize = 500;
List<Embedding> allEmbeddings = /* large list */;
for (int i = 0; i < allEmbeddings.size(); i += batchSize) {
int end = Math.min(i + batchSize, allEmbeddings.size());
List<Embedding> batch = allEmbeddings.subList(i, end);
store.addAll(batch);
System.out.println("Processed " + end + "/" + allEmbeddings.size());
}List<String> ids = new ArrayList<>();
List<Embedding> embeddings = new ArrayList<>();
List<TextSegment> segments = new ArrayList<>();
for (Document doc : documents) {
ids.add(doc.getId());
embeddings.add(model.embed(doc.getText()).content());
segments.add(TextSegment.from(doc.getText(), doc.getMetadata()));
}
store.addAll(ids, embeddings, segments);import dev.langchain4j.data.document.Metadata;
import java.util.Map;
Metadata metadata = Metadata.from(Map.of(
"source", "documentation.pdf",
"page", 42,
"category", "technical",
"author", "John Doe",
"created_date", "2024-01-15",
"security_level", "public",
"version", 1.0
));
TextSegment segment = TextSegment.from("Document content", metadata);
store.add(embedding, segment);// Store with filterable metadata
Metadata metadata = Metadata.from(Map.of(
"department", "engineering",
"year", 2024,
"active", true
));
// Later search with filter
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
import static dev.langchain4j.store.embedding.filter.Filter.and;
Filter departmentFilter = metadataKey("department").isEqualTo("engineering");
Filter yearFilter = metadataKey("year").isGreaterThanOrEqualTo(2023);
Filter activeFilter = metadataKey("active").isEqualTo(true);
Filter combinedFilter = and(departmentFilter, yearFilter, activeFilter);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.filter(combinedFilter)
.build();EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.minScore(0.75) // Only high-quality matches
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);// Stage 1: Broad search
EmbeddingSearchRequest broadSearch = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(50)
.minScore(0.6)
.build();
List<EmbeddingMatch<TextSegment>> candidates = store.search(broadSearch).matches();
// Stage 2: Re-rank or filter locally
List<EmbeddingMatch<TextSegment>> filtered = candidates.stream()
.filter(match -> customFilter(match.embedded()))
.limit(10)
.collect(Collectors.toList());import static dev.langchain4j.store.embedding.filter.Filter.or;
import static dev.langchain4j.store.embedding.filter.Filter.and;
Filter techCategory = metadataKey("category").isEqualTo("technical");
Filter scienceCategory = metadataKey("category").isEqualTo("science");
Filter recentYear = metadataKey("year").isGreaterThan(2022);
// (technical OR science) AND recent
Filter filter = and(
or(techCategory, scienceCategory),
recentYear
);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(20)
.filter(filter)
.build();List<String> processedIds = new ArrayList<>();
for (EmbeddingMatch<TextSegment> match : searchResults.matches()) {
processItem(match);
processedIds.add(match.embeddingId());
}
if (!processedIds.isEmpty()) {
store.removeAll(processedIds);
}import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
import java.time.LocalDate;
int cutoffYear = LocalDate.now().getYear() - 2;
Filter oldFilter = metadataKey("year").isLessThan(cutoffYear);
store.removeAll(oldFilter);import static dev.langchain4j.store.embedding.filter.Filter.and;
Filter lowConfidence = metadataKey("confidence").isLessThan(0.5);
Filter temporary = metadataKey("type").isEqualTo("temporary");
store.removeAll(and(lowConfidence, temporary));String testCollection = "test_" + System.currentTimeMillis();
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
.host("localhost")
.collectionName(testCollection)
.dimension(384)
.build();
try {
// Run tests
runTests(store);
} finally {
// Cleanup
store.dropCollection(testCollection);
}String oldCollection = "embeddings_v1";
String newCollection = "embeddings_v2";
// Create new version
MilvusEmbeddingStore newStore = MilvusEmbeddingStore.builder()
.host("localhost")
.collectionName(newCollection)
.dimension(768) // Updated dimension
.indexType(IndexType.HNSW) // Updated index
.build();
// Migrate data
migrateData(oldCollection, newCollection);
// Verify and drop old
if (verifyMigration(newCollection)) {
newStore.dropCollection(oldCollection);
}int maxRetries = 3;
int attempt = 0;
boolean success = false;
while (attempt < maxRetries && !success) {
try {
String id = store.add(embedding, segment);
success = true;
System.out.println("Added: " + id);
} catch (Exception e) {
attempt++;
if (attempt >= maxRetries) {
System.err.println("Failed after " + maxRetries + " attempts");
throw e;
}
Thread.sleep(1000 * attempt); // Exponential backoff
}
}List<String> successIds = new ArrayList<>();
List<String> failedIds = new ArrayList<>();
for (int i = 0; i < embeddings.size(); i++) {
try {
String id = store.add(embeddings.get(i), segments.get(i));
successIds.add(id);
} catch (Exception e) {
failedIds.add("index_" + i);
System.err.println("Failed to add embedding " + i + ": " + e.getMessage());
}
}
System.out.println("Success: " + successIds.size() + ", Failed: " + failedIds.size());// Search to preview what will be deleted
Filter filter = metadataKey("status").isEqualTo("archived");
EmbeddingSearchRequest previewRequest = EmbeddingSearchRequest.builder()
.queryEmbedding(sampleEmbedding) // Any embedding for structure
.maxResults(1000)
.filter(filter)
.build();
int count = store.search(previewRequest).matches().size();
System.out.println("Will delete " + count + " embeddings");
// Confirm then delete
if (confirm("Delete " + count + " embeddings?")) {
store.removeAll(filter);
}MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
.host("localhost")
.collectionName("bulk_insert")
.dimension(384)
.autoFlushOnInsert(false) // Better batch performance
.build();
// Add large batch
store.addAll(largeEmbeddingList);MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
.host("localhost")
.collectionName("fast_search")
.dimension(384)
.retrieveEmbeddingsOnSearch(false) // Faster search
.build();
// Search only returns IDs and text, not embeddings
EmbeddingSearchResult<TextSegment> results = store.search(request);import io.milvus.common.clientenum.ConsistencyLevelEnum;
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
.host("localhost")
.collectionName("high_throughput")
.dimension(384)
.consistencyLevel(ConsistencyLevelEnum.EVENTUALLY)
.autoFlushOnInsert(false)
.build();// Milvus doesn't have update; use remove + add
String documentId = "doc-123";
// Remove old version
store.removeAll(Arrays.asList(documentId));
// Add new version
Embedding newEmbedding = model.embed(updatedText).content();
TextSegment newSegment = TextSegment.from(updatedText, updatedMetadata);
store.add(documentId, newEmbedding);List<String> updatedIds = new ArrayList<>();
for (Document doc : documentsToUpdate) {
store.removeAll(Arrays.asList(doc.getId()));
Embedding embedding = model.embed(doc.getText()).content();
TextSegment segment = TextSegment.from(doc.getText(), doc.getMetadata());
store.add(doc.getId(), embedding);
updatedIds.add(doc.getId());
}
System.out.println("Updated " + updatedIds.size() + " documents");// Find near-duplicates of a reference embedding
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(referenceEmbedding)
.maxResults(100)
.minScore(0.99) // Very high similarity
.build();
List<EmbeddingMatch<TextSegment>> results = store.search(request).matches();
// Remove duplicates (keep first)
List<String> duplicateIds = results.stream()
.skip(1) // Keep the first match
.map(EmbeddingMatch::embeddingId)
.collect(Collectors.toList());
if (!duplicateIds.isEmpty()) {
store.removeAll(duplicateIds);
System.out.println("Removed " + duplicateIds.size() + " duplicates");
}Map<String, MilvusEmbeddingStore> stores = new HashMap<>();
String[] collections = {"users", "documents", "products"};
for (String collection : collections) {
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
.host("localhost")
.collectionName(collection)
.dimension(384)
.build();
stores.put(collection, store);
}
// Use appropriate store
stores.get("documents").add(embedding, segment);addAll() instead of loops with add()Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-milvus@1.11.0