❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️

Memory with SQL (pgvector) and Qdrant β€” Simple SK patterns

Memory with SQL (pgvector) and Qdrant β€” Simple SK patterns

Not every app needs Cosmos DB. If you already run Postgres or prefer a light, dedicated vector DB, Postgres with pgvector and Qdrant are both excellent choices for app memory. Below are minimal patterns using Semantic Kernel to store and recall user memory.

When I pick which store

  • Qdrant
    • Simple to run (Docker), high‑quality HNSW vector search, great defaults.
    • Perfect for feature teams that want a focused vector DB without managing a full RDBMS.
  • Postgres + pgvector
    • Fits where Postgres is already the backbone; benefits from SQL joins, migrations, backups.
    • Great for β€œmemory + metadata” scenarios and unified infra.

Tip: start with what your team can operate confidently. For small scopes, both perform well.

SK quickstart: Qdrant user memory

Install packages: Microsoft.SemanticKernel, Microsoft.SemanticKernel.Connectors.Qdrant, and an embeddings provider (OpenAI/Azure OpenAI).

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Connectors.Qdrant;

string openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
int vectorSize = 1536; // must match your embedding model

// 1) Build a standalone memory service
var memory = new MemoryBuilder()
    .WithOpenAITextEmbeddingGeneration(
        modelId: "text-embedding-3-small",
        apiKey: openAiKey)
    .WithQdrantMemoryStore(
        endpoint: "http://localhost:6333",
        vectorSize: vectorSize,
        collectionNamePrefix: "sk_") // optional
    .Build();

// 2) Save some user memory
await memory.SaveInformationAsync(
    collection: "user-profile",
    text: "Prefers concise answers",
    id: "user:42:pref-1",
    description: "style");

// 3) Recall by semantic similarity
await foreach (var m in memory.SearchAsync(
    collection: "user-profile",
    query: "concise replies",
    limit: 3,
    minRelevanceScore: 0.7))
{
    Console.WriteLine($"{m.Metadata.Id} | {m.Metadata.Text} | score {m.Relevance}");
}

What’s happening:

  • Embeddings are created automatically for SaveInformationAsync.
  • Qdrant collections are created on demand (configurable), with HNSW indexing.
  • SearchAsync retrieves top‑K by similarity with a relevance threshold.

SK quickstart: Postgres (pgvector)

Install packages: Microsoft.SemanticKernel, Microsoft.SemanticKernel.Connectors.Postgres, and your embeddings provider. Ensure the pgvector extension is enabled on your database.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Connectors.Postgres;

string openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
string conn = "Host=localhost;Username=pg;Password=pg;Database=sk;";
int vectorSize = 1536;

// 1) Create a Postgres memory store (requires pgvector extension)
var store = await PostgresMemoryStore.ConnectAsync(
    connectionString: conn,
    vectorSize: vectorSize,
    schema: "public");

// 2) Build memory with Postgres store
var memory = new MemoryBuilder()
    .WithOpenAITextEmbeddingGeneration(
        modelId: "text-embedding-3-small",
        apiKey: openAiKey)
    .WithMemoryStore(store)
    .Build();

// 3) Save & search user memory
await memory.SaveInformationAsync(
    collection: "user-profile",
    text: "Enjoys biking and TypeScript",
    id: "user:42:pref-2");

await foreach (var m in memory.SearchAsync(
    collection: "user-profile",
    query: "What hobbies?",
    limit: 3,
    minRelevanceScore: 0.6))
{
    Console.WriteLine($"{m.Metadata.Id} | {m.Metadata.Text} | score {m.Relevance}");
}

Notes:

  • ConnectAsync initializes tables if missing; vector size must match your embedding model.
  • You can share the same store across collections (user/team/company memories).
  • Use SQL for additional filtering (e.g., userId/teamId) before similarity search when feasible.

Kernel‑integrated pattern (optional)

If you prefer everything via a single Kernel, register embeddings and the memory store with the DI container, then use kernel.Memory:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Connectors.Qdrant;

var builder = Kernel.CreateBuilder();

// embeddings (OpenAI/Azure OpenAI β€” pick one)
builder.AddOpenAITextEmbeddingGeneration(
    modelId: "text-embedding-3-small",
    apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);

// register the memory store (Qdrant example)
var qdrant = new QdrantMemoryStore(
    endpoint: "http://localhost:6333",
    vectorSize: 1536);

builder.Services.AddSingleton<IMemoryStore>(qdrant);
var kernel = builder.Build();

await kernel.Memory.SaveInformationAsync(
    collection: "user-profile",
    text: "Works in M365 ecosystem",
    id: "user:42:role-1");

await foreach (var m in kernel.Memory.SearchAsync(
    collection: "user-profile",
    query: "Microsoft 365",
    limit: 2,
    minRelevanceScore: 0.5))
{
    Console.WriteLine($"{m.Metadata.Id} | {m.Metadata.Text} | score {m.Relevance}");
}

Minimal flow

App

Semantic Kernel Memory

Embedding Model

Qdrant

Postgres pgvector

Why this is easy for user memory

  • You can start with a single collection per scope (e.g., user-profile), a handful of strings, and built‑in embeddings.
  • Saving is one line; searching is a few more. The operational model is small.
  • You can add ACLs and scope partitioning later (store metadata like userId in the record and filter before similarity).

Wrap‑up

Cosmos DB, Qdrant, and Postgres + pgvector are all viable for memory. I lean Qdrant for quick starts and Postgres when I want to live inside SQL. With Semantic Kernel, the integration is straightforward β€” pick your store, wire embeddings, and you have user memory in minutes.