CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-milvus

Milvus embedding store integration for LangChain4j

Overview
Eval results
Files

patterns.mddocs/

Common Patterns

Frequently used patterns and best practices for coding agents.

Batch Processing

Pattern: Document Collection Ingestion

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);

Pattern: Large Batch with Batching

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());
}

Pattern: Batch with Custom IDs

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);

Metadata Patterns

Pattern: Rich Metadata Storage

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);

Pattern: Metadata for Filtering

// 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();

Search Patterns

Pattern: Top-K with Threshold

EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryEmbedding)
    .maxResults(10)
    .minScore(0.75)  // Only high-quality matches
    .build();

EmbeddingSearchResult<TextSegment> results = store.search(request);

Pattern: Multi-Stage Search

// 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());

Pattern: Search with Multiple Filters

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();

Deletion Patterns

Pattern: Remove Processed Items

List<String> processedIds = new ArrayList<>();

for (EmbeddingMatch<TextSegment> match : searchResults.matches()) {
    processItem(match);
    processedIds.add(match.embeddingId());
}

if (!processedIds.isEmpty()) {
    store.removeAll(processedIds);
}

Pattern: Cleanup Old Data

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);

Pattern: Conditional Removal

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));

Collection Lifecycle Patterns

Pattern: Temporary Collection for Testing

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);
}

Pattern: Collection Versioning

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);
}

Error Handling Patterns

Pattern: Robust Add with Retry

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
    }
}

Pattern: Safe Batch Processing

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());

Pattern: Validation Before Removal

// 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);
}

Performance Patterns

Pattern: Batch Insert with No Auto-Flush

MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
    .host("localhost")
    .collectionName("bulk_insert")
    .dimension(384)
    .autoFlushOnInsert(false)  // Better batch performance
    .build();

// Add large batch
store.addAll(largeEmbeddingList);

Pattern: Search Without Embedding Retrieval

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);

Pattern: Eventually Consistent for High Throughput

import io.milvus.common.clientenum.ConsistencyLevelEnum;

MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
    .host("localhost")
    .collectionName("high_throughput")
    .dimension(384)
    .consistencyLevel(ConsistencyLevelEnum.EVENTUALLY)
    .autoFlushOnInsert(false)
    .build();

Update Patterns

Pattern: Update by Replace

// 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);

Pattern: Incremental Updates

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");

Duplicate Detection Pattern

// 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");
}

Multi-Collection Pattern

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);

Best Practices Summary

  1. Batch operations: Use addAll() instead of loops with add()
  2. Metadata: Store searchable attributes as metadata
  3. Filters: Use metadata filters to narrow search space
  4. IDs: Use custom IDs for external system integration
  5. Error handling: Wrap operations in try-catch for production
  6. Consistency: Choose appropriate consistency level for use case
  7. Performance: Disable auto-flush and embedding retrieval unless needed
  8. Cleanup: Drop temporary collections in finally blocks
  9. Validation: Preview before bulk deletions
  10. Updates: Implement as remove + add pattern

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-milvus@1.11.0

docs

advanced.md

api-reference.md

configuration.md

index.md

patterns.md

quickstart.md

troubleshooting.md

tile.json