Skip to main content
Version: 0.13.0

Topic drift ingest processor

The topic_drift ingest processor keeps LLM and VLM extraction on the rails during ingest.

Given a mission goal and the text an analysis step produced for a document — say a vision model's description of a satellite chip — it scores how far that output has drifted from the mission and annotates the document with the verdict. Off-mission or hallucinated extractions are flagged for review or re-run instead of silently polluting the searchable index.

A scene analyzed for "catalog maritime vessels in port imagery" that comes back describing the weather, or hallucinating a vehicle, scores high drift and needs_intervention: true, with the goal returned as corrective context to re-inject on a re-run.

It costs a handful of vector operations, not a second model call

Drift scoring embeds the goal and the analysis text using the same embedding provider the rest of ingest uses, then compares them. There is no second LLM call, which is what makes it affordable to run on every ingested item rather than on a sample.

Request fields

FieldTypeDescriptionRequired
goalStringThe mission statement to score against.Yes
fieldStringDocument field holding the analysis text. Defaults to analysis.No
target_fieldStringField that receives the verdict. Defaults to drift.No
providerStringEmbedding provider used to embed the goal and the text.No
model_idStringEmbedding model on that provider.No
dimensionsIntegerEmbedding dimensions. Defaults to 768.No
nudge_atFloatScore above which the result is worth a nudge.No
correct_atFloatScore above which correction is called for.No
block_atFloatScore above which the extraction should be blocked.No
info

The embedding provider and model must match the ones used elsewhere in your pipeline. Two embeddings from different models are not comparable, so a mismatch produces a drift score that means nothing.

What it writes

{
"drift": {
"score": 0.71,
"verdict": "correct",
"needs_intervention": true,
"goal_distance": 0.68,
"novelty": 0.24,
"corrective": ["catalog maritime vessels in port imagery"]
}
}
FieldDescription
scoreOverall drift score.
verdictThe threshold band the score fell into, lowercased.
needs_interventionWhether the extraction should be reviewed or re-run.
goal_distanceHow far the analysis sits from the mission goal.
noveltyHow much new material the analysis introduced.
correctiveThe goal, returned as context to re-inject on a re-run. Present only when intervention is needed.

Example

PUT /_ingest/pipeline/vessel-analysis
{
"description": "Score VLM output against the mission goal",
"processors": [
{
"topic_drift": {
"goal": "catalog maritime vessels in port imagery",
"field": "analysis",
"target_field": "drift",
"provider": "my-embedding-provider",
"model_id": "text-embedding-v1"
}
}
]
}

Because the verdict is an ordinary field, you can route on it — index everything and filter the clean extractions at query time:

GET /imagery/_search
{
"query": {
"bool": {
"must": [ { "match": { "analysis": "container vessel" } } ],
"filter": [ { "term": { "drift.needs_intervention": false } } ]
}
}
}