CollectionInfo icon
Get icon
Request icon
Script icon
Process icon
Loop icon
Query icon
Log icon
If icon
Assert icon
Pause icon
Upsert icon
SlackIncomingWebhook icon
Schedule icon

Detect Qdrant embedding drift and verify bounded re-embedding

Detect changed embedding spaces in Qdrant, approve a bounded repair and verify held-out recall before closing the incident.

Categories
AIData

Safety

An Upsert replaces a point's vector and payload. Re-embedding the wrong batch can mix spaces further or overwrite newer ingestion. Use this flow only during an exclusive maintenance window: stop other writers and model-tag updates first. The stored payload.text must be the authoritative source for each selected point.

Guard What it stops
One physical COSINE collection and 1 to 100 explicit IDs Unbounded discovery or an unsupported vector layout
reembed_allowed: true and exact embedding_batch Rewriting protected points or another ingestion batch
Distance strictly above its ceiling plus recall strictly below its floor Rewriting vectors when only one signal suggests a problem
Expected model digest Approving one model then writing vectors from another tag revision
Safe defaults, maintenance acknowledgement and Pause An unattended write or an unanswered approval
Batch snapshot after approval and point snapshot before each Upsert Acting on a point changed while the plan waited
Fresh embeddings, native reads and held-out queries after repair Treating an accepted Upsert as proof of retrieval recovery

Qdrant plugin 1.0.0 exposes no conditional version predicate on Upsert. The recheck is not a lock. maintenance_confirmed records the operator's acknowledgement; it cannot exclude another application. Flow concurrency serializes this flow only. Do not use live repair if you cannot exclude concurrent writers.

How it works

  1. collection_info and stored_points (io.kestra.plugin.qdrant.CollectionInfo, Get) read the selected collection and points. prepare checks IDs, dense vectors, source text, held-out probes and the digest returned by Ollama's tags API.
  2. embed_source (core.http.Request) calls the live Ollama embedding API. plan computes cosine distance against each stored vector, selects only opted-in points from source_batch and saves original-points.json for a separately reviewed restore.
  3. embed_queries and recall_before (Loop, qdrant.Query) measure mean Recall@k over probes. Declared Loop outputs carry the hit lists to measure_before; they are not inferred from iteration counts.
  4. repair_needed continues only below the recall floor with auto_remediate: true and dry_run: false. It requires eligible drift and a maintenance acknowledgement, then approval waits up to one hour. A missing or false approval changes nothing.
  5. approval_still_current compares the entire supplied snapshot after the Pause. replace_vectors (Loop, concurrencyLimit: 1) rechecks each point and the model digest immediately before native qdrant.Upsert. IDs and payloads are preserved except for embedding_model_digest.
  6. embed_source_after, embed_queries_after and points_after obtain fresh evidence. verify_vectors checks replacements and preserved records. recall_after queries the repaired collection again; recovered fails while recall remains below the floor. The errors block sends a Slack failure alert. The disabled hourly Schedule pins observation mode.

What you get

Per-point drift distances, protected IDs, an original-point artifact and held-out recall before and after a bounded repair. A retry reads actual vectors and skips completed points. Refusal, stale approval and unresolved retrieval remain visible in the execution history.

Who it's for

RAG platform teams diagnosing a small ingestion batch after a same-dimension embedding model change. This is suitable when the source text is retained in Qdrant and a maintenance window is available.

Why orchestrate this with Kestra

The useful boundary is between detecting a mismatch, approving its exact point snapshot and deciding whether retrieval recovered. Kestra records that boundary, resumes the approved execution and keeps a failed or interrupted pass available for reconciliation. Qdrant performs the reads and writes; Ollama produces the embeddings.

Tested end to end

Kestra 2.0.5 with Qdrant plugin 1.0.0 installed separately from Maven Central, Python script plugin 1.14.0 and Slack 2.1.0. Services: Qdrant 1.15.5 and Ollama 0.12.3. Both all-minilm:22m and snowflake-arctic-embed:22m produced 384-dimensional embeddings. Model digests were recorded and checked. Validation had no constraints, warnings or deprecations.

An independent Qdrant REST probe measured eight real-model points and four held-out questions. Switching the query model left Recall@1 at 2/4. Repair replaced six eligible vectors and restored 4/4. All eight IDs, source texts and tenant metadata survived. Point 7 had opted out; point 8 belonged to another batch. Their vectors and payloads stayed unchanged despite measurable drift.

Case Execution Result
Healthy 6OoPg2ZVeVzkFMgMXZmcAH SUCCESS, 4/4 recall and no write
Degraded dry run 6gnBLzBrHQrayXbbOJ8JvJ SUCCESS, six candidates, still 2/4, no write
Declined approval atkG1HAdBfflsFEeAkWc6 SUCCESS, all records unchanged
Repair 4Xl3MomrkpJlyVpBr3bpQ7 SUCCESS, six replacements, 2/4 to 4/4
Repeat 35DdbZ3oqRDRX6XsUutQK4 SUCCESS, zero candidates and no write
Read-only credential c90okrADNBhsUCskqLRU2 FAILED at native Upsert, all records unchanged
Point protected during approval 1Yzmfb60Yio25gV3a1uYaF FAILED before the write loop, operator change retained

A fresh Qdrant container repeated healthy (13MTiYTIISAOnA4kRDKRKI), repair (6f5qoj3zVm0V0MmuWfarbP) and stale approval (2BHdYD9ImeqDy3JQTN5oac). Boundary cases spared distance equal to the ceiling and recall equal to the floor. Low recall without eligible vector drift failed before approval. A valid query set that remained at 3/4 after replacement failed recovered (4Z37nIIFLEFtlXYnPxKtGw). Failure alerts reached a local HTTP Slack sink.

Execution 24g86zawOlRM6RMbtxjThE was killed after two replacements committed. Reconciliation 70kxXX7jpAGC29h39GrYyG preserved those two records exactly, selected only the remaining four points and restored 4/4 recall.

Prerequisites

Kestra 2.0.5 with separately installed io.kestra.plugin:plugin-qdrant:1.0.0, the Python script and Slack plugins, plus Python 3.12 on the worker. Qdrant gRPC and Ollama HTTP must be reachable from that worker. The Qdrant credential needs read/query access and permission to Upsert the selected collection; collection creation and deletion are not used. Dry observation needs read access only.

Use an actual collection name, an immutable ingestion batch identifier and authoritative source text in every supplied point. Named vectors, sparse vectors and dimension changes are refused. Keep other ingestion and model-tag updates excluded until the execution has finished.

Secrets

  • QDRANT_API_KEY: the default Qdrant credential.
  • SLACK_WEBHOOK_URL: failure notification destination.
  • QDRANT_HOST, OLLAMA_URL and EMBEDDING_MODEL_DIGEST: used by the disabled Schedule. Configure its collection, IDs, batch and probe list before enabling it.

Inputs

Input Default Purpose
qdrant_host, qdrant_port, qdrant_tls required, 6334, true gRPC connection
qdrant_api_key secret('QDRANT_API_KEY') Credential override for restricted observations
collection, point_ids, source_batch required Physical collection, explicit IDs and exact batch opt-in
ollama_url, embedding_model, model_digest required Application's embedding endpoint, installed tag and expected digest
probes required Objects with query and nonempty expected_ids arrays, up to 20
top_k 3 Retrieval depth, 1 to 20
max_cosine_distance 0.05 Strict drift ceiling, 0 to 2
min_recall 0.9 Strict lower bound before repair, 0 to 1
maintenance_confirmed false Acknowledge exclusion of other writers and tag updates
auto_remediate, dry_run false, true Both must be changed before approval is offered

Each point needs payload.text, payload.embedding_batch and an explicit boolean payload.reembed_allowed. Missing opt-in or a different batch protects the point. A probe can be {"query":"What is the PostgreSQL backup schedule?","expected_ids":["2"]}. Use held-out questions with reviewed relevant IDs, not copies of the source text.

Quick start

  1. Install the Qdrant plugin on a Kestra 2.0.5 worker, configure the two connection secrets and confirm gRPC connectivity. Pull the chosen embedding model in Ollama and record its digest with ollama list or /api/tags.
  2. Select a bounded batch, retain its authoritative text and mark only repairable points with reembed_allowed: true and the exact embedding_batch. Supply its IDs plus reviewed held-out probes. Run with the safe defaults.
  3. Review plan, measure_before and the original-point artifact. Diagnose missing source updates or a genuinely weaker model before approving a rewrite.
  4. Exclude other ingestion and tag changes. Set maintenance_confirmed: true, auto_remediate: true and dry_run: false. Approve the Pause only after checking its snapshot.
  5. Keep the maintenance window until verification finishes. If the run fails or is interrupted, inspect actual state and rerun with the same scope. Completed vectors are skipped. Restore original vectors only through a separate reviewed procedure.
  6. Restrict access to execution artifacts and set retention: they contain source text, vectors and payloads. Configure the disabled Schedule's sample scope before enabling observation.

How to extend

Add reviewed probes for more application intents or invoke this flow once per independently owned batch. For large corpora or uninterrupted ingestion, design a versioned-collection migration with its own cutover and rollback protocol.

Limits: the result proves recovery only for the supplied probes and eligible batch. Other protected vectors may remain in an old space. Cosine mismatch does not distinguish a model change from source text edited without refreshing its vector. Rechecks cannot prevent a write between the final read and Upsert; external writer exclusion is mandatory. Reads and scripts time out after 60 seconds, embedding calls after 120 seconds and approval after one hour. There are no automatic write retries. The tested scope was eight 384-dimensional points; the 100-ID input ceiling is not a capacity guarantee. Large vectors or payloads can exceed worker environment limits because scripts receive JSON through environment variables. Production TLS, a real Slack workspace and other server/plugin versions were not exercised.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.