Initial commit
This commit is contained in:
446
skills/langchain4j/qdrant/SKILL.md
Normal file
446
skills/langchain4j/qdrant/SKILL.md
Normal file
@@ -0,0 +1,446 @@
|
||||
---
|
||||
name: qdrant-vector-database-integration
|
||||
description: Qdrant vector database integration patterns with LangChain4j. Store embeddings, similarity search, and vector management for Java applications. Use when implementing vector-based retrieval for RAG systems, semantic search, or recommendation engines.
|
||||
category: backend
|
||||
tags: [qdrant, java, spring-boot, langchain4j, vector-search, ai, machine-learning]
|
||||
version: 1.2.0
|
||||
allowed-tools: Read, Write, Bash
|
||||
---
|
||||
|
||||
# Qdrant Vector Database Integration
|
||||
|
||||
## Overview
|
||||
|
||||
Qdrant is an AI-native vector database for semantic search and similarity retrieval. This skill provides patterns for integrating Qdrant with Java applications, focusing on Spring Boot integration and LangChain4j framework support. Enable efficient vector search capabilities for RAG systems, recommendation engines, and semantic search applications.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when implementing:
|
||||
- Semantic search or recommendation systems in Spring Boot applications
|
||||
- Retrieval-Augmented Generation (RAG) pipelines with Java and LangChain4j
|
||||
- Vector database integration for AI and machine learning applications
|
||||
- High-performance similarity search with filtered queries
|
||||
- Embedding storage and retrieval for context-aware applications
|
||||
|
||||
## Getting Started: Qdrant Setup
|
||||
|
||||
To begin integration, first deploy a Qdrant instance.
|
||||
|
||||
### Local Development with Docker
|
||||
|
||||
```bash
|
||||
# Pull the latest Qdrant image
|
||||
docker pull qdrant/qdrant
|
||||
|
||||
# Run the Qdrant container
|
||||
docker run -p 6333:6333 -p 6334:6334 \
|
||||
-v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
|
||||
qdrant/qdrant
|
||||
```
|
||||
|
||||
Access Qdrant via:
|
||||
- **REST API**: `http://localhost:6333`
|
||||
- **gRPC API**: `http://localhost:6334` (used by Java client)
|
||||
|
||||
## Core Java Client Integration
|
||||
|
||||
Add dependencies to your build configuration and initialize the client for programmatic access.
|
||||
|
||||
### Dependency Configuration
|
||||
|
||||
**Maven:**
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.qdrant</groupId>
|
||||
<artifactId>client</artifactId>
|
||||
<version>1.15.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle:**
|
||||
```gradle
|
||||
implementation 'io.qdrant:client:1.15.0'
|
||||
```
|
||||
|
||||
### Client Initialization
|
||||
|
||||
Create and configure the Qdrant client for application use:
|
||||
|
||||
```java
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
|
||||
// Basic local connection
|
||||
QdrantClient client = new QdrantClient(
|
||||
QdrantGrpcClient.newBuilder("localhost").build());
|
||||
|
||||
// Secure connection with API key
|
||||
QdrantClient secureClient = new QdrantClient(
|
||||
QdrantGrpcClient.newBuilder("localhost", 6334, false)
|
||||
.withApiKey("YOUR_API_KEY")
|
||||
.build());
|
||||
|
||||
// Managed connection with TLS
|
||||
QdrantClient tlsClient = new QdrantClient(
|
||||
QdrantGrpcClient.newBuilder(channel)
|
||||
.withApiKey("YOUR_API_KEY")
|
||||
.build());
|
||||
```
|
||||
|
||||
## Collection Management
|
||||
|
||||
Create and configure vector collections with appropriate distance metrics and dimensions.
|
||||
|
||||
### Create Collections
|
||||
|
||||
```java
|
||||
import io.qdrant.client.grpc.Collections.Distance;
|
||||
import io.qdrant.client.grpc.Collections.VectorParams;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
// Create a collection with cosine distance
|
||||
client.createCollectionAsync("search-collection",
|
||||
VectorParams.newBuilder()
|
||||
.setDistance(Distance.Cosine)
|
||||
.setSize(384)
|
||||
.build()).get();
|
||||
|
||||
// Create collection with configuration
|
||||
client.createCollectionAsync("recommendation-engine",
|
||||
VectorParams.newBuilder()
|
||||
.setDistance(Distance.Euclidean)
|
||||
.setSize(512)
|
||||
.build()).get();
|
||||
```
|
||||
|
||||
## Vector Operations
|
||||
|
||||
Perform common vector operations including upsert, search, and filtering.
|
||||
|
||||
### Upsert Points
|
||||
|
||||
```java
|
||||
import io.qdrant.client.grpc.Points.PointStruct;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static io.qdrant.client.PointIdFactory.id;
|
||||
import static io.qdrant.client.ValueFactory.value;
|
||||
import static io.qdrant.client.VectorsFactory.vectors;
|
||||
|
||||
// Batch upsert vector points
|
||||
List<PointStruct> points = List.of(
|
||||
PointStruct.newBuilder()
|
||||
.setId(id(1))
|
||||
.setVectors(vectors(0.05f, 0.61f, 0.76f, 0.74f))
|
||||
.putAllPayload(Map.of(
|
||||
"title", value("Spring Boot Documentation"),
|
||||
"content", value("Spring Boot framework documentation")
|
||||
))
|
||||
.build(),
|
||||
PointStruct.newBuilder()
|
||||
.setId(id(2))
|
||||
.setVectors(vectors(0.19f, 0.81f, 0.75f, 0.11f))
|
||||
.putAllPayload(Map.of(
|
||||
"title", value("Qdrant Vector Database"),
|
||||
"content", value("Vector database for AI applications")
|
||||
))
|
||||
.build()
|
||||
);
|
||||
|
||||
client.upsertAsync("search-collection", points).get();
|
||||
```
|
||||
|
||||
### Vector Search
|
||||
|
||||
```java
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import io.qdrant.client.grpc.Points.ScoredPoint;
|
||||
import static io.qdrant.client.QueryFactory.nearest;
|
||||
import java.util.List;
|
||||
|
||||
// Basic similarity search
|
||||
List<ScoredPoint> results = client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName("search-collection")
|
||||
.setLimit(5)
|
||||
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
|
||||
.build()
|
||||
).get();
|
||||
|
||||
// Search with filters
|
||||
List<ScoredPoint> filteredResults = client.searchAsync(
|
||||
SearchPoints.newBuilder()
|
||||
.setCollectionName("search-collection")
|
||||
.addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
|
||||
.setFilter(Filter.newBuilder()
|
||||
.addMust(range("rand_number",
|
||||
Range.newBuilder().setGte(3).build()))
|
||||
.build())
|
||||
.setLimit(5)
|
||||
.build()).get();
|
||||
```
|
||||
|
||||
## Spring Boot Integration
|
||||
|
||||
Integrate Qdrant with Spring Boot using dependency injection and proper configuration.
|
||||
|
||||
### Configuration Class
|
||||
|
||||
```java
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service Layer Implementation
|
||||
|
||||
```java
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Service
|
||||
public class VectorSearchService {
|
||||
|
||||
private final QdrantClient qdrantClient;
|
||||
|
||||
public VectorSearchService(QdrantClient qdrantClient) {
|
||||
this.qdrantClient = qdrantClient;
|
||||
}
|
||||
|
||||
public List<ScoredPoint> search(String collectionName, List<Float> queryVector) {
|
||||
try {
|
||||
return qdrantClient.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName(collectionName)
|
||||
.setLimit(5)
|
||||
.setQuery(nearest(queryVector))
|
||||
.build()
|
||||
).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException("Qdrant search failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void upsertPoints(String collectionName, List<PointStruct> points) {
|
||||
try {
|
||||
qdrantClient.upsertAsync(collectionName, points).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException("Qdrant upsert failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LangChain4j Integration
|
||||
|
||||
Leverage LangChain4j for high-level vector store abstractions and RAG implementations.
|
||||
|
||||
### Dependency Setup
|
||||
|
||||
**Maven:**
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-qdrant</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### QdrantEmbeddingStore Configuration
|
||||
|
||||
```java
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.embedding.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
|
||||
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class Langchain4jConfig {
|
||||
|
||||
@Bean
|
||||
public EmbeddingStore<TextSegment> embeddingStore() {
|
||||
return QdrantEmbeddingStore.builder()
|
||||
.collectionName("rag-collection")
|
||||
.host("localhost")
|
||||
.port(6334)
|
||||
.apiKey("YOUR_API_KEY")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EmbeddingModel embeddingModel() {
|
||||
return new AllMiniLmL6V2EmbeddingModel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EmbeddingStoreIngestor embeddingStoreIngestor(
|
||||
EmbeddingStore<TextSegment> embeddingStore,
|
||||
EmbeddingModel embeddingModel) {
|
||||
return EmbeddingStoreIngestor.builder()
|
||||
.embeddingStore(embeddingStore)
|
||||
.embeddingModel(embeddingModel)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### RAG Service Implementation
|
||||
|
||||
```java
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.embedding.EmbeddingModel;
|
||||
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 {
|
||||
|
||||
private final EmbeddingStoreIngestor ingestor;
|
||||
|
||||
public RagService(EmbeddingStoreIngestor ingestor) {
|
||||
this.ingestor = ingestor;
|
||||
}
|
||||
|
||||
public void ingestDocument(String text) {
|
||||
TextSegment segment = TextSegment.from(text);
|
||||
ingestor.ingest(segment);
|
||||
}
|
||||
|
||||
public List<TextSegment> findRelevant(String query) {
|
||||
EmbeddingStore<TextSegment> embeddingStore = ingestor.getEmbeddingStore();
|
||||
return embeddingStore.findRelevant(
|
||||
ingestor.getEmbeddingModel().embed(query).content(),
|
||||
5,
|
||||
0.7
|
||||
).stream()
|
||||
.map(match -> match.embedded())
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Search Implementation
|
||||
|
||||
```java
|
||||
// Create simple search endpoint
|
||||
@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) {
|
||||
// Convert query to embedding (requires embedding model)
|
||||
List<Float> queryVector = embeddingModel.embed(query).content().vectorAsList();
|
||||
return searchService.search("documents", queryVector);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Vector Database Configuration
|
||||
- Use appropriate distance metrics: Cosine for text, Euclidean for numerical data
|
||||
- Optimize vector dimensions based on embedding model specifications
|
||||
- Configure proper collection naming conventions
|
||||
- Monitor performance and optimize search parameters
|
||||
|
||||
### Spring Boot Integration
|
||||
- Always use constructor injection for dependency injection
|
||||
- Handle async operations with proper exception handling
|
||||
- Configure connection timeouts and retry policies
|
||||
- Use proper bean configuration for production environments
|
||||
|
||||
### Security Considerations
|
||||
- Never hardcode API keys in code
|
||||
- Use environment variables or Spring configuration properties
|
||||
- Implement proper authentication and authorization
|
||||
- Use TLS for production connections
|
||||
|
||||
### Performance Optimization
|
||||
- Batch operations for bulk upserts
|
||||
- Use appropriate limits and filters
|
||||
- Monitor memory usage and connection pooling
|
||||
- Consider sharding for large datasets
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Multi-tenant Vector Storage
|
||||
```java
|
||||
// Implement collection-based multi-tenancy
|
||||
public class MultiTenantVectorService {
|
||||
private final QdrantClient client;
|
||||
|
||||
public void upsertForTenant(String tenantId, List<PointStruct> points) {
|
||||
String collectionName = "tenant_" + tenantId + "_documents";
|
||||
client.upsertAsync(collectionName, points).get();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hybrid Search with Filters
|
||||
```java
|
||||
// Combine vector similarity with metadata filtering
|
||||
public List<ScoredPoint> hybridSearch(String collectionName, List<Float> queryVector,
|
||||
String category, Date dateRange) {
|
||||
Filter filter = Filter.newBuilder()
|
||||
.addMust(range("created_at",
|
||||
Range.newBuilder().setGte(dateRange.getTime()).build()))
|
||||
.addMust(exactMatch("category", category))
|
||||
.build();
|
||||
|
||||
return client.searchAsync(
|
||||
SearchPoints.newBuilder()
|
||||
.setCollectionName(collectionName)
|
||||
.addAllVector(queryVector)
|
||||
.setFilter(filter)
|
||||
.build()
|
||||
).get();
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
For comprehensive technical details and advanced patterns, see:
|
||||
- [Qdrant API Reference](references/references.md) - Complete client API documentation
|
||||
- [Complete Spring Boot Examples](references/examples.md) - Full application implementations
|
||||
- [Official Qdrant Documentation](https://qdrant.tech/documentation/) - Core documentation
|
||||
- [LangChain4j Documentation](https://langchain4j.dev/) - Framework-specific patterns
|
||||
584
skills/langchain4j/qdrant/references/examples.md
Normal file
584
skills/langchain4j/qdrant/references/examples.md
Normal file
@@ -0,0 +1,584 @@
|
||||
# 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)
|
||||
```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)
|
||||
```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
|
||||
```java
|
||||
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
|
||||
```java
|
||||
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
|
||||
```java
|
||||
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
|
||||
```java
|
||||
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
|
||||
```java
|
||||
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
|
||||
```java
|
||||
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
|
||||
```java
|
||||
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
|
||||
```yaml
|
||||
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
|
||||
```properties
|
||||
# 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
|
||||
```java
|
||||
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.
|
||||
141
skills/langchain4j/qdrant/references/references.md
Normal file
141
skills/langchain4j/qdrant/references/references.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Qdrant for Java: References
|
||||
|
||||
This file contains key technical details and code patterns for integrating Qdrant with Java applications.
|
||||
|
||||
## Qdrant Java Client API Reference
|
||||
|
||||
### Core Setup
|
||||
|
||||
**Maven:**
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.qdrant</groupId>
|
||||
<artifactId>client</artifactId>
|
||||
<version>1.15.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle:**
|
||||
```gradle
|
||||
implementation 'io.qdrant:client:1.15.0'
|
||||
```
|
||||
|
||||
### Client Initialization
|
||||
|
||||
```java
|
||||
// Basic client
|
||||
QdrantClient client = new QdrantClient(
|
||||
QdrantGrpcClient.newBuilder("localhost").build());
|
||||
|
||||
// Advanced client with TLS and API key
|
||||
ManagedChannel channel = Grpc.newChannelBuilder(
|
||||
"localhost:6334",
|
||||
TlsChannelCredentials.newBuilder()
|
||||
.trustManager(new File("ssl/ca.crt"))
|
||||
.build()).build();
|
||||
|
||||
QdrantClient client = new QdrantClient(
|
||||
QdrantGrpcClient.newBuilder(channel)
|
||||
.withApiKey("<apikey>")
|
||||
.build());
|
||||
```
|
||||
|
||||
### Collection Management
|
||||
|
||||
```java
|
||||
// Create collection
|
||||
client.createCollectionAsync("my_collection",
|
||||
VectorParams.newBuilder()
|
||||
.setDistance(Distance.Cosine)
|
||||
.setSize(4)
|
||||
.build()).get();
|
||||
|
||||
// Create collection with configuration
|
||||
client.createCollectionAsync("my_collection",
|
||||
VectorParams.newBuilder()
|
||||
.setDistance(Distance.Cosine)
|
||||
.setSize(384)
|
||||
.build())
|
||||
.get();
|
||||
```
|
||||
|
||||
### Point Operations
|
||||
|
||||
```java
|
||||
// Insert points
|
||||
List<PointStruct> points = List.of(
|
||||
PointStruct.newBuilder()
|
||||
.setId(id(1))
|
||||
.setVectors(vectors(0.32f, 0.52f, 0.21f, 0.52f))
|
||||
.putAllPayload(Map.of("color", value("red")))
|
||||
.build()
|
||||
);
|
||||
|
||||
UpdateResult result = client.upsertAsync("my_collection", points).get();
|
||||
```
|
||||
|
||||
### Search Operations
|
||||
|
||||
```java
|
||||
// Simple search
|
||||
List<ScoredPoint> results = client.searchAsync(
|
||||
SearchPoints.newBuilder()
|
||||
.setCollectionName("my_collection")
|
||||
.addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
|
||||
.setLimit(5)
|
||||
.build()).get();
|
||||
|
||||
// Filtered search
|
||||
List<ScoredPoint> filteredResults = client.searchAsync(
|
||||
SearchPoints.newBuilder()
|
||||
.setCollectionName("my_collection")
|
||||
.addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
|
||||
.setFilter(Filter.newBuilder()
|
||||
.addMust(range("rand_number",
|
||||
Range.newBuilder().setGte(3).build()))
|
||||
.build())
|
||||
.setLimit(5)
|
||||
.build()).get();
|
||||
```
|
||||
|
||||
## LangChain4j Integration Patterns
|
||||
|
||||
### QdrantEmbeddingStore Setup
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-qdrant</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```java
|
||||
EmbeddingStore<TextSegment> embeddingStore = QdrantEmbeddingStore.builder()
|
||||
.collectionName("YOUR_COLLECTION_NAME")
|
||||
.host("YOUR_HOST_URL")
|
||||
.port(6334)
|
||||
.apiKey("YOUR_API_KEY")
|
||||
.build();
|
||||
|
||||
// Or with HTTPS
|
||||
EmbeddingStore<TextSegment> embeddingStore = QdrantEmbeddingStore.builder()
|
||||
.collectionName("YOUR_COLLECTION_NAME")
|
||||
.host("YOUR_HOST_URL")
|
||||
.port(443)
|
||||
.useHttps(true)
|
||||
.apiKey("YOUR_API_KEY")
|
||||
.build();
|
||||
```
|
||||
|
||||
## Official Documentation Resources
|
||||
|
||||
- **[Qdrant Documentation](https://qdrant.tech/documentation/)**: Main documentation portal
|
||||
- **[Qdrant Java Client GitHub](https://github.com/qdrant/java-client)**: Source code and issues
|
||||
- **[Java Client Javadoc](https://qdrant.github.io/java-client/)**: Complete API documentation
|
||||
- **[API & SDKs](https://qdrant.tech/documentation/interfaces/)**: All supported clients
|
||||
- **[Quickstart Guide](https://qdrant.tech/documentation/quickstart/)**: Local setup guide
|
||||
- **[LangChain4j Official Site](https://langchain4j.dev/)**: Framework documentation
|
||||
- **[LangChain4j Examples](https://github.com/langchain4j/langchain4j-examples)**: Comprehensive examples
|
||||
Reference in New Issue
Block a user