New to Kestra?
Use blueprints to kickstart your first workflows.
Detect changed embedding spaces in Qdrant, approve a bounded repair and verify held-out recall before closing the incident.
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.
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.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.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.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.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.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.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.
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.
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.
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.
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.
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.| 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.
ollama list or /api/tags.reembed_allowed: true and the exact embedding_batch. Supply its IDs plus reviewed held-out probes. Run with the safe defaults.plan, measure_before and the original-point artifact. Diagnose missing source updates or a genuinely weaker model before approving a rewrite.maintenance_confirmed: true, auto_remediate: true and dry_run: false. Approve the Pause only after checking its snapshot.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.
This blueprint was created by zkasuran.
