17 KiB
17 KiB
Qdrant for Java: Complete Examples
This file provides comprehensive code examples for integrating Qdrant with Java and Spring Boot applications.
1. Complete Spring Boot Application with Qdrant
This example demonstrates a full Spring Boot application with Qdrant integration for vector search.
Project Structure
/src/main/java/com/example/qdrantdemo/
├── QdrantDemoApplication.java
├── config/
│ ├── QdrantConfig.java
│ └── Langchain4jConfig.java
├── controller/
│ ├── SearchController.java
│ └── RagController.java
├── service/
│ ├── VectorSearchService.java
│ └── RagService.java
└── Application.properties
Dependencies (pom.xml)
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Qdrant Java Client -->
<dependency>
<groupId>io.qdrant</groupId>
<artifactId>client</artifactId>
<version>1.15.0</version>
</dependency>
<!-- LangChain4j -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qdrant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-all-minilm-l6-v2</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
Application Configuration (application.properties)
# Qdrant Configuration
qdrant.host=localhost
qdrant.port=6334
qdrant.api-key=
# OpenAI Configuration (for RAG)
openai.api-key=YOUR_OPENAI_API_KEY
Qdrant Configuration
package com.example.qdrantdemo.config;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QdrantConfig {
@Value("${qdrant.host:localhost}")
private String host;
@Value("${qdrant.port:6334}")
private int port;
@Value("${qdrant.api-key:}")
private String apiKey;
@Bean
public QdrantClient qdrantClient() {
QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(host, port, false)
.withApiKey(apiKey)
.build();
return new QdrantClient(grpcClient);
}
}
Vector Search Service
package com.example.qdrantdemo.service;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.ScoredPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import static io.qdrant.client.QueryFactory.nearest;
@Service
public class VectorSearchService {
private final QdrantClient client;
@Autowired
private EmbeddingService embeddingService; // Helper service for embeddings
public static final String COLLECTION_NAME = "document-search";
public static final int VECTOR_SIZE = 384; // For AllMiniLM-L6-v2
public VectorSearchService(QdrantClient client) {
this.client = client;
}
@PostConstruct
public void initializeCollection() throws ExecutionException, InterruptedException {
// Create collection if it doesn't exist
client.createCollectionAsync(COLLECTION_NAME,
VectorParams.newBuilder()
.setDistance(Distance.Cosine)
.setSize(VECTOR_SIZE)
.build()
).get();
}
public List<ScoredPoint> search(String query, int limit) {
try {
List<Float> queryVector = embeddingService.embedQuery(query);
return client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName(COLLECTION_NAME)
.setLimit(limit)
.setQuery(nearest(queryVector))
.setWithPayload(true)
.build()
).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Qdrant search failed", e);
}
}
public void addDocument(String documentId, String title, String content) {
try {
List<Float> contentVector = embeddingService.embedText(content);
PointStruct point = PointStruct.newBuilder()
.setId(id(documentId))
.setVectors(vectors(contentVector))
.putAllPayload(Map.of(
"title", value(title),
"content", value(content),
"created_at", value(System.currentTimeMillis())
))
.build();
client.upsertAsync(COLLECTION_NAME, List.of(point)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Qdrant document insertion failed", e);
}
}
}
Search Controller
package com.example.qdrantdemo.controller;
import com.example.qdrantdemo.service.VectorSearchService;
import io.qdrant.client.grpc.Points.ScoredPoint;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/search")
public class SearchController {
private final VectorSearchService searchService;
public SearchController(VectorSearchService searchService) {
this.searchService = searchService;
}
@GetMapping
public List<ScoredPoint> search(@RequestParam String query,
@RequestParam(defaultValue = "5") int limit) {
return searchService.search(query, limit);
}
@PostMapping("/document")
public String addDocument(@RequestBody AddDocumentRequest request) {
searchService.addDocument(request.getDocumentId(), request.getTitle(), request.getContent());
return "Document added successfully";
}
public static class AddDocumentRequest {
private String documentId;
private String title;
private String content;
// Getters and setters
public String getDocumentId() { return documentId; }
public void setDocumentId(String documentId) { this.documentId = documentId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
}
2. Advanced RAG with LangChain4j
This example demonstrates a complete RAG system with Qdrant and LLM integration.
LangChain4j Configuration
package com.example.qdrantdemo.config;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.embedding.EmbeddingModel;
import dev.langchain4j.embedding.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Langchain4jConfig {
@Value("${qdrant.host:localhost}")
private String host;
@Value("${qdrant.port:6334}")
private int port;
@Value("${qdrant.api-key:}")
private String apiKey;
@Value("${openai.api-key}")
private String openaiApiKey;
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return QdrantEmbeddingStore.builder()
.collectionName("rag-collection")
.host(host)
.port(port)
.apiKey(apiKey)
.build();
}
@Bean
public EmbeddingModel embeddingModel() {
return new AllMiniLmL6V2EmbeddingModel();
}
@Bean
public ChatLanguageModel chatLanguageModel() {
return OpenAiChatModel.builder()
.apiKey(openaiApiKey)
.modelName("gpt-3.5-turbo")
.build();
}
@Bean
public EmbeddingStoreIngestor embeddingStoreIngestor(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
return EmbeddingStoreIngestor.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.build();
}
}
RAG Service with Assistant
package com.example.qdrantdemo.service;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RagService {
// Define the AI assistant interface
interface Assistant {
String chat(String userMessage);
}
private final EmbeddingStoreIngestor ingestor;
private final Assistant assistant;
public RagService(EmbeddingStore<TextSegment> embeddingStore,
EmbeddingStoreIngestor ingestor,
ChatLanguageModel chatModel) {
this.ingestor = ingestor;
// Create content retriever for RAG
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.maxResults(3)
.minScore(0.7)
.build();
// Build the AI assistant with RAG capabilities
this.assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatModel)
.contentRetriever(contentRetriever)
.build();
}
public void ingestDocument(String text) {
TextSegment segment = TextSegment.from(text);
ingestor.ingest(segment);
}
public String query(String userQuery) {
return assistant.chat(userQuery);
}
public List<TextSegment> findRelevantDocuments(String query, int maxResults) {
EmbeddingStore<TextSegment> embeddingStore = ingestor.getEmbeddingStore();
return embeddingStore.findRelevant(
ingestor.getEmbeddingModel().embed(query).content(),
maxResults,
0.7
).stream()
.map(match -> match.embedded())
.toList();
}
}
RAG Controller
package com.example.qdrantdemo.controller;
import com.example.qdrantdemo.service.RagService;
import dev.langchain4j.data.segment.TextSegment;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/rag")
public class RagController {
private final RagService ragService;
public RagController(RagService ragService) {
this.ragService = ragService;
}
@PostMapping("/ingest")
public String ingestDocument(@RequestBody String document) {
ragService.ingestDocument(document);
return "Document ingested successfully.";
}
@PostMapping("/query")
public String query(@RequestBody QueryRequest request) {
return ragService.query(request.getQuery());
}
@GetMapping("/documents")
public List<TextSegment> findDocuments(@RequestParam String query,
@RequestParam(defaultValue = "3") int maxResults) {
return ragService.findRelevantDocuments(query, maxResults);
}
public static class QueryRequest {
private String query;
public String getQuery() { return query; }
public void setQuery(String query) { this.query = query; }
}
}
3. Multi-tenant Vector Search Application
This example demonstrates advanced patterns for multi-tenant applications.
Multi-Tenant Vector Service
package com.example.qdrantdemo.service;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.ScoredPoint;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Service
public class MultiTenantVectorService {
private final QdrantClient client;
public MultiTenantVectorService(QdrantClient client) {
this.client = client;
}
// Collection-based multi-tenancy
public List<ScoredPoint> searchByTenant(String tenantId, List<Float> queryVector, int limit) {
try {
String collectionName = "tenant_" + tenantId + "_documents";
return client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName(collectionName)
.setLimit(limit)
.addAllVector(queryVector)
.setWithPayload(true)
.build()
).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Multi-tenant search failed", e);
}
}
public void upsertForTenant(String tenantId, List<PointStruct> points) {
try {
String collectionName = "tenant_" + tenantId + "_documents";
client.upsertAsync(collectionName, points).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Multi-tenant upsert failed", e);
}
}
// Hybrid search with tenant-specific filters
public List<ScoredPoint> hybridSearch(String tenantId, List<Float> queryVector,
String category, int limit) {
try {
String collectionName = "tenant_" + tenantId + "_documents";
QueryPoints.Builder queryBuilder = QueryPoints.newBuilder()
.setCollectionName(collectionName)
.setLimit(limit)
.addAllVector(queryVector);
// Add category filter if provided
if (category != null && !category.isEmpty()) {
queryBuilder.setFilter(Filter.newBuilder()
.addMust(exactMatch("category", category))
.build());
}
return client.queryAsync(queryBuilder.build()).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Hybrid search failed", e);
}
}
}
Deployment and Configuration
Docker Compose Setup
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:v1.7.0
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_storage:/qdrant/storage
environment:
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
volumes:
qdrant_storage:
Production Configuration
# application-prod.properties
qdrant.host=qdrant-service
qdrant.port=6334
qdrant.api-key=${QDRANT_API_KEY}
# Enable HTTPS for production
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=${SSL_KEYSTORE_PASSWORD}
# OpenAI Configuration
openai.api-key=${OPENAI_API_KEY}
# Logging
logging.level.com.example.qdrantdemo=INFO
logging.level.io.qdrant=INFO
Testing Strategy
Unit Tests for Vector Service
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class VectorSearchServiceTest {
@Autowired
private VectorSearchService vectorSearchService;
@Test
public void testCollectionInitialization() {
// Test that collection is created properly
// This could involve checking collection metadata
}
@Test
public void testDocumentUpsert() {
// Test document insertion and retrieval
}
@Test
public void testSearchFunctionality() {
// Test vector search functionality
}
}
This comprehensive example provides a complete foundation for building Qdrant-powered applications with Spring Boot and LangChain4j.