n4nAI

Connecting Semantic Kernel to Qdrant for vector memory

Step-by-step semantic kernel qdrant vector memory tutorial: wire SK memory to Qdrant, configure embeddings, store and query vectors with runnable code.

n4n Team3 min read615 words

Audio narration

Coming soon — every post will get a voice note here.

Most teams hit a wall when they try to give Semantic Kernel long-term memory without coupling to a specific vector DB. This semantic kernel qdrant vector memory tutorial shows you how to plug Qdrant into SK’s memory stack using the official connectors and a minimal console setup. You’ll end up with a working pipeline that embeds text, persists it to Qdrant, and retrieves it by similarity.

Step 1: Start a Qdrant instance

Qdrant ships a single Docker image that is sufficient for local dev. Run it and expose both the gRPC and REST ports:

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

Verify it is up before writing code:

curl http://localhost:6333/healthz
# {"status":"ok"}

The REST API listens on 6333; the dashboard is at http://localhost:6333/dashboard. You do not need to pre-create collections—the SDK will do that.

Step 2: Add the NuGet packages

Create a .NET 8 console project and pull in the core SDK plus the Qdrant and OpenAI connectors:

dotnet new console -n SkQdrantDemo
cd SkQdrantDemo
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI

Pin to a recent 1.x version if you need reproducibility. The vector-store abstraction used here stabilized in SK 1.0+; do not use the legacy IMemoryStore for new work.

Step 3: Define the record schema

Semantic Kernel maps POCOs to vector records via attributes. The key must be a string or numeric, the vector must be ReadOnlyMemory<float>, and the dimension must match your embedding model exactly.

using Microsoft.SemanticKernel.Data;

public class MemoryRecord
{
    [VectorStoreRecordKey]
    public string Id { get; set; } = string.Empty;

    [VectorStoreRecordData]
    public string Text { get; set; } = string.Empty;

    [VectorStoreRecordVector(1536)] // text-embedding-3-small
    public ReadOnlyMemory<float> Embedding { get; set; }
}

If you swap to all-MiniLM-L6-v2 (384 dims) or text-embedding-3-large (3072 dims), change the attribute and the model ID together. Mismatched dimensions fail at upsert with a gRPC error from Qdrant.

Step 4: Configure Semantic Kernel

Build the kernel with an embedding generator and register the Qdrant vector store. The example uses OpenAI’s embedding API, but any ITextEmbeddingGenerationService works.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Connectors.Qdrant;

var builder = Kernel.CreateBuilder();
builder.AddOpenAITextEmbeddingGeneration(
    modelId: "text-embedding-3-small",
    apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);

builder.Services.AddQdrantVectorStore("http://localhost:6333");
var kernel = builder.Build();

Choosing an embedding model

text-embedding-3-small is cheap and good enough for most RAG demos. For on-prem, use Microsoft.SemanticKernel.Connectors.HuggingFace or ONNX local inference; just implement the same interface.

Pointing at a gateway (optional)

If you don’t want to juggle multiple provider API keys, point the embedding client at an OpenAI-compatible gateway like n4n.ai, which exposes one endpoint for 240+ models and applies automatic fallback when a provider is rate-limited. The SK OpenAI connector accepts a custom endpoint:

builder.AddOpenAITextEmbeddingGeneration(
    modelId: "text-embedding-3-small",
    apiKey: "your-gateway-key",
    endpoint: "https://api.n4n.ai/v1");

Step 5: Create the collection and upsert documents

Resolve the store and collection, ensure it exists, then embed and write records. Batch upserts are preferred for volume; here we show single writes for clarity.

var vectorStore = kernel.Services.GetRequiredService<IVectorStore>();
var collection = vectorStore.GetCollection<string, MemoryRecord>("sk_memory");
await collection.CreateCollectionIfNotExistsAsync();

var embedder = kernel.Services.GetRequiredService<ITextEmbeddingGenerationService>();

var docs = new[]
{
    "Semantic Kernel simplifies orchestration of LLM calls.",
    "Qdrant is a vector database written in Rust.",
    "Vector search retrieves by cosine similarity, not keywords."
};

for (var i = 0; i < docs.Length; i++)
{
    var vec = await embedder.GenerateEmbeddingAsync(docs[i]);
    await collection.UpsertAsync(new MemoryRecord
    {
        Id = $"doc-{i}",
        Text = docs[i],
        Embedding = vec
    });
}

Qdrant creates the collection with the distance metric defaulting to Cosine when driven by the SK connector. You can confirm via the dashboard: the sk_memory collection appears with 3 points.

Generate a query embedding and call VectorizedSearchAsync. The returned type is an IAsyncEnumerable of records with scores.

var queryVec = await embedder.GenerateEmbeddingAsync("How do I orchestrate LLMs?");
var search = await collection.VectorizedSearchAsync(queryVec, top: 2);

await foreach (var result in search.Results)
{
    Console.WriteLine($"Score: {result.Score:F3} | {result.Record.Text}");
}

Expected output ranks the “Semantic Kernel simplifies orchestration” line first. Scores are cosine similarities in [0,1]; higher is closer.

Step 7: Verify the pipeline works

A working semantic kernel qdrant vector memory tutorial implementation passes three checks:

  1. Health: curl localhost:6333/healthz returns ok after the app ran.
  2. Persistence: Restart the console app, re-run only the search block (skip upsert), and you still get results. Qdrant stored the points on disk.
  3. Dimension integrity: No DimensionMismatch errors in the Qdrant logs (docker logs <container>).

If search returns nothing, print search.Results.Count() after buffering with ToListAsync()—an empty collection usually means the embedding service returned zeros or the collection name differs.

Production notes

  • Use collection.UpsertAsync(batch) with List<MemoryRecord> to cut round-trips.
  • Add a VectorStoreRecordData field for timestamps or tenant IDs and filter with Filter clauses in VectorizedSearchAsync.
  • Set Qdrant API keys and TLS before exposing the port beyond localhost.
  • The SK Qdrant connector uses gRPC by default for upserts; the REST port is fine for health checks.

The pattern above is the minimum viable link between Semantic Kernel and Qdrant. From here, wrap the upsert in a KernelFunction or a background worker, and you have durable memory for any SK agent.

Tagssemantic-kernelqdrantmemoryvector-store

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All semantic kernel memory & vector stores posts →