Skip to main content

Installation

Install via go get:
go get github.com/qdrant/go-client

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/qdrant/go-client/qdrant"
)

func main() {
	client, err := qdrant.NewClient(&qdrant.Config{
		Host: "localhost",
		Port: 6334,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	ctx := context.Background()

	// Create collection
	err = client.CreateCollection(ctx, &qdrant.CreateCollection{
		CollectionName: "my_collection",
		VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
			Size:     384,
			Distance: qdrant.Distance_Cosine,
		}),
	})
	if err != nil {
		log.Fatal(err)
	}

	// Upsert points
	err = client.Upsert(ctx, &qdrant.UpsertPoints{
		CollectionName: "my_collection",
		Points: []*qdrant.PointStruct{
			{
				Id:      qdrant.NewIDNum(1),
				Vectors: qdrant.NewVectors(0.05, 0.61, 0.76, 0.74),
				Payload: qdrant.NewValueMap(map[string]any{
					"city":    "Berlin",
					"country": "Germany",
				}),
			},
			{
				Id:      qdrant.NewIDNum(2),
				Vectors: qdrant.NewVectors(0.19, 0.81, 0.75, 0.11),
				Payload: qdrant.NewValueMap(map[string]any{
					"city":    "London",
					"country": "UK",
				}),
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	// Search
	searchResult, err := client.Search(ctx, &qdrant.SearchPoints{
		CollectionName: "my_collection",
		Vector:         []float32{0.2, 0.1, 0.9, 0.7},
		Limit:          3,
		WithPayload:    qdrant.NewWithPayload(true),
	})
	if err != nil {
		log.Fatal(err)
	}

	for _, point := range searchResult {
		fmt.Printf("ID: %v, Score: %f\n", point.Id, point.Score)
		fmt.Printf("Payload: %v\n", point.Payload)
	}
}

Repository

GitHub Repository

Official Go client: qdrant/go-client

Client Initialization

Basic Connection

import "github.com/qdrant/go-client/qdrant"

// Connect to local instance
client, err := qdrant.NewClient(&qdrant.Config{
	Host: "localhost",
	Port: 6334, // gRPC port
})
if err != nil {
	log.Fatal(err)
}
defer client.Close()

With API Key

client, err := qdrant.NewClient(&qdrant.Config{
	Host:   "your-cluster.cloud.qdrant.io",
	Port:   6334,
	APIKey: "your-api-key",
	UseTLS: true,
})

With Custom Options

import (
	"time"
	"google.golang.org/grpc"
	"github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
	Host:    "localhost",
	Port:    6334,
	Timeout: 30 * time.Second,
	GrpcOptions: []grpc.DialOption{
		grpc.WithMaxMsgSize(100 * 1024 * 1024), // 100MB
	},
})

Collection Management

Create Collection

err := client.CreateCollection(ctx, &qdrant.CreateCollection{
	CollectionName: "my_collection",
	VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
		Size:     768,
		Distance: qdrant.Distance_Cosine,
		OnDisk:   qdrant.PtrOf(false),
	}),
	HnswConfig: &qdrant.HnswConfigDiff{
		M:              qdrant.PtrOf(uint64(16)),
		EfConstruct:    qdrant.PtrOf(uint64(100)),
		FullScanThreshold: qdrant.PtrOf(uint64(10000)),
	},
	ReplicationFactor:       qdrant.PtrOf(uint32(2)),
	WriteConsistencyFactor: qdrant.PtrOf(uint32(1)),
})

Create with Multiple Vectors

err := client.CreateCollection(ctx, &qdrant.CreateCollection{
	CollectionName: "multi_vector",
	VectorsConfig: qdrant.NewVectorsConfigMap(map[string]*qdrant.VectorParams{
		"text": {
			Size:     768,
			Distance: qdrant.Distance_Cosine,
		},
		"image": {
			Size:     512,
			Distance: qdrant.Distance_Euclid,
		},
	}),
})

Get Collection Info

info, err := client.GetCollectionInfo(ctx, "my_collection")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Status: %v\n", info.Status)
fmt.Printf("Vectors count: %v\n", info.VectorsCount)
fmt.Printf("Points count: %v\n", info.PointsCount)

List Collections

collections, err := client.ListCollections(ctx)
if err != nil {
	log.Fatal(err)
}

for _, collection := range collections.GetCollections() {
	fmt.Println(collection.Name)
}

Delete Collection

err := client.DeleteCollection(ctx, "my_collection")

Point Operations

Upsert Points

err := client.Upsert(ctx, &qdrant.UpsertPoints{
	CollectionName: "my_collection",
	Wait:           qdrant.PtrOf(true),
	Points: []*qdrant.PointStruct{
		{
			Id:      qdrant.NewIDNum(1),
			Vectors: qdrant.NewVectors(0.05, 0.61, 0.76, 0.74),
			Payload: qdrant.NewValueMap(map[string]any{
				"title":    "Document 1",
				"category": "technology",
				"views":    1500,
				"tags":     []string{"ai", "ml", "vector-db"},
			}),
		},
	},
})

Upsert with UUID

import "github.com/google/uuid"

id := uuid.New()
err := client.Upsert(ctx, &qdrant.UpsertPoints{
	CollectionName: "my_collection",
	Points: []*qdrant.PointStruct{
		{
			Id:      qdrant.NewID(id.String()),
			Vectors: qdrant.NewVectors(0.1, 0.2, 0.3),
			Payload: qdrant.NewValueMap(map[string]any{"key": "value"}),
		},
	},
})

Batch Upsert

batchSize := 100
totalPoints := 10000

for i := 0; i < totalPoints; i += batchSize {
	points := make([]*qdrant.PointStruct, 0, batchSize)
	
	for j := i; j < i+batchSize && j < totalPoints; j++ {
		points = append(points, &qdrant.PointStruct{
			Id:      qdrant.NewIDNum(uint64(j)),
			Vectors: qdrant.NewVectors(0.1, 0.2, 0.3, 0.4), // Random in production
			Payload: qdrant.NewValueMap(map[string]any{"index": j}),
		})
	}
	
	err := client.Upsert(ctx, &qdrant.UpsertPoints{
		CollectionName: "my_collection",
		Wait:           qdrant.PtrOf(false),
		Points:         points,
	})
	if err != nil {
		log.Printf("Batch %d failed: %v", i/batchSize, err)
	}
}

Get Points

points, err := client.Get(ctx, &qdrant.GetPoints{
	CollectionName: "my_collection",
	Ids: []*qdrant.PointId{
		qdrant.NewIDNum(1),
		qdrant.NewIDNum(2),
		qdrant.NewIDNum(3),
	},
	WithPayload: qdrant.NewWithPayload(true),
	WithVectors: qdrant.NewWithVectors(true),
})

for _, point := range points {
	fmt.Printf("ID: %v\n", point.Id)
	fmt.Printf("Vectors: %v\n", point.Vectors)
	fmt.Printf("Payload: %v\n", point.Payload)
}

Delete Points

// Delete by IDs
err := client.Delete(ctx, &qdrant.DeletePoints{
	CollectionName: "my_collection",
	Points: &qdrant.PointsSelector{
		PointsSelectorOneOf: &qdrant.PointsSelector_Points{
			Points: &qdrant.PointsIdsList{
				Ids: []*qdrant.PointId{
					qdrant.NewIDNum(1),
					qdrant.NewIDNum(2),
				},
			},
		},
	},
})

// Delete by filter
err = client.Delete(ctx, &qdrant.DeletePoints{
	CollectionName: "my_collection",
	Points: &qdrant.PointsSelector{
		PointsSelectorOneOf: &qdrant.PointsSelector_Filter{
			Filter: &qdrant.Filter{
				Must: []*qdrant.Condition{
					qdrant.NewMatch("category", "outdated"),
				},
			},
		},
	},
})

Search Operations

searchResult, err := client.Search(ctx, &qdrant.SearchPoints{
	CollectionName: "my_collection",
	Vector:         []float32{0.2, 0.1, 0.9, 0.7},
	Limit:          10,
	WithPayload:    qdrant.NewWithPayload(true),
	WithVectors:    qdrant.NewWithVectors(false),
})

for _, point := range searchResult {
	fmt.Printf("ID: %v, Score: %f\n", point.Id, point.Score)
	fmt.Printf("Payload: %v\n", point.Payload)
}

Search with Filtering

searchResult, err := client.Search(ctx, &qdrant.SearchPoints{
	CollectionName: "my_collection",
	Vector:         []float32{0.2, 0.1, 0.9, 0.7},
	Filter: &qdrant.Filter{
		Must: []*qdrant.Condition{
			qdrant.NewMatch("category", "technology"),
			qdrant.NewRange("views", &qdrant.Range{
				Gte: qdrant.PtrOf(1000.0),
				Lt:  qdrant.PtrOf(10000.0),
			}),
		},
	},
	Limit:          5,
	ScoreThreshold: qdrant.PtrOf(float32(0.7)),
})

Search with Parameters

searchResult, err := client.Search(ctx, &qdrant.SearchPoints{
	CollectionName: "my_collection",
	Vector:         []float32{0.2, 0.1, 0.9, 0.7},
	Limit:          20,
	Params: &qdrant.SearchParams{
		HnswEf: qdrant.PtrOf(uint64(128)),
		Exact:  qdrant.PtrOf(false),
		Quantization: &qdrant.QuantizationSearchParams{
			Ignore:       qdrant.PtrOf(false),
			Rescore:      qdrant.PtrOf(true),
			Oversampling: qdrant.PtrOf(2.0),
		},
	},
})
searchResult, err := client.SearchBatch(ctx, &qdrant.SearchBatchPoints{
	CollectionName: "my_collection",
	SearchPoints: []*qdrant.SearchPoints{
		{
			Vector: []float32{0.2, 0.1, 0.9},
			Limit:  5,
		},
		{
			Vector: []float32{0.5, 0.3, 0.2},
			Limit:  10,
		},
	},
})

for i, batch := range searchResult {
	fmt.Printf("Results for query %d:\n", i)
	for _, point := range batch.Result {
		fmt.Printf("  ID: %v, Score: %f\n", point.Id, point.Score)
	}
}

Recommendations

recommendResult, err := client.Recommend(ctx, &qdrant.RecommendPoints{
	CollectionName: "my_collection",
	Positive: []*qdrant.PointId{
		qdrant.NewIDNum(1),
		qdrant.NewIDNum(2),
		qdrant.NewIDNum(3),
	},
	Negative: []*qdrant.PointId{
		qdrant.NewIDNum(10),
	},
	Limit:       10,
	WithPayload: qdrant.NewWithPayload(true),
})

for _, point := range recommendResult {
	fmt.Printf("ID: %v, Score: %f\n", point.Id, point.Score)
}

Query API (Universal)

queryResult, err := client.Query(ctx, &qdrant.QueryPoints{
	CollectionName: "my_collection",
	Query: qdrant.NewQuery([]float32{0.1, 0.2, 0.3, 0.4}),
	Limit:       qdrant.PtrOf(uint64(10)),
	WithPayload: qdrant.NewWithPayload(true),
})

Payload Operations

Set Payload

err := client.SetPayload(ctx, &qdrant.SetPayloadPoints{
	CollectionName: "my_collection",
	Payload: qdrant.NewValueMap(map[string]any{
		"new_field":  "value",
		"updated_at": "2024-01-01",
	}),
	PointsSelector: &qdrant.PointsSelector{
		PointsSelectorOneOf: &qdrant.PointsSelector_Points{
			Points: &qdrant.PointsIdsList{
				Ids: []*qdrant.PointId{
					qdrant.NewIDNum(1),
					qdrant.NewIDNum(2),
				},
			},
		},
	},
})

Delete Payload Keys

err := client.DeletePayload(ctx, &qdrant.DeletePayloadPoints{
	CollectionName: "my_collection",
	Keys:           []string{"old_field", "deprecated"},
	PointsSelector: &qdrant.PointsSelector{
		PointsSelectorOneOf: &qdrant.PointsSelector_Points{
			Points: &qdrant.PointsIdsList{
				Ids: []*qdrant.PointId{qdrant.NewIDNum(1)},
			},
		},
	},
})

Scroll (Pagination)

var offset *qdrant.PointId
var allPoints []*qdrant.RetrievedPoint

for {
	result, err := client.Scroll(ctx, &qdrant.ScrollPoints{
		CollectionName: "my_collection",
		Limit:          qdrant.PtrOf(uint32(100)),
		Offset:         offset,
		WithPayload:    qdrant.NewWithPayload(true),
	})
	if err != nil {
		log.Fatal(err)
	}

	allPoints = append(allPoints, result.Result...)
	offset = result.NextPageOffset

	if offset == nil {
		break
	}
}

fmt.Printf("Total points: %d\n", len(allPoints))

Payload Indexing

// Create keyword index
err := client.CreateFieldIndex(ctx, &qdrant.CreateFieldIndexCollection{
	CollectionName: "my_collection",
	FieldName:      "category",
	FieldType:      qdrant.FieldType_FieldTypeKeyword.Enum(),
})

// Create text index
err = client.CreateFieldIndex(ctx, &qdrant.CreateFieldIndexCollection{
	CollectionName: "my_collection",
	FieldName:      "description",
	FieldType:      qdrant.FieldType_FieldTypeText.Enum(),
	FieldIndexParams: &qdrant.PayloadIndexParams{
		IndexParams: &qdrant.PayloadIndexParams_TextIndexParams{
			TextIndexParams: &qdrant.TextIndexParams{
				Tokenizer:   qdrant.TokenizerType_Word,
				Lowercase:   qdrant.PtrOf(true),
				MinTokenLen: qdrant.PtrOf(uint64(2)),
				MaxTokenLen: qdrant.PtrOf(uint64(20)),
			},
		},
	},
})

// Delete index
err = client.DeleteFieldIndex(ctx, "my_collection", "category")

Count Points

count, err := client.Count(ctx, &qdrant.CountPoints{
	CollectionName: "my_collection",
	Exact:          qdrant.PtrOf(true),
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Total points: %d\n", count.GetResult().GetCount())

Error Handling

import (
	"errors"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

info, err := client.GetCollectionInfo(ctx, "nonexistent")
if err != nil {
	if st, ok := status.FromError(err); ok {
		switch st.Code() {
		case codes.NotFound:
			fmt.Println("Collection not found")
		case codes.InvalidArgument:
			fmt.Println("Invalid argument")
		default:
			fmt.Printf("Error: %v\n", st.Message())
		}
	} else {
		log.Fatal(err)
	}
}

Concurrency

import "sync"

func uploadConcurrent(client *qdrant.Client, points [][]*qdrant.PointStruct) error {
	var wg sync.WaitGroup
	errorChan := make(chan error, len(points))

	for _, batch := range points {
		wg.Add(1)
		go func(batch []*qdrant.PointStruct) {
			defer wg.Done()
			err := client.Upsert(context.Background(), &qdrant.UpsertPoints{
				CollectionName: "my_collection",
				Points:         batch,
			})
			if err != nil {
				errorChan <- err
			}
		}(batch)
	}

	wg.Wait()
	close(errorChan)

	for err := range errorChan {
		if err != nil {
			return err
		}
	}

	return nil
}

Context with Timeout

import (
	"context"
	"time"
)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

searchResult, err := client.Search(ctx, &qdrant.SearchPoints{
	CollectionName: "my_collection",
	Vector:         []float32{0.2, 0.1, 0.9, 0.7},
	Limit:          10,
})
if err != nil {
	if ctx.Err() == context.DeadlineExceeded {
		fmt.Println("Search timed out")
	}
	log.Fatal(err)
}

Best Practices

Performance tips:
  • Reuse client instances across requests
  • Use batch operations for bulk inserts
  • Enable connection pooling with gRPC options
  • Use goroutines for concurrent operations
  • Always defer client.Close()
Common pitfalls:
  • Always check error returns
  • Use context.Context for cancellation and timeouts
  • Handle gRPC status codes appropriately
  • Don’t forget to use qdrant.PtrOf() for optional fields

Next Steps

.NET Client

.NET client documentation

gRPC API

Learn about gRPC protocol