Skip to main content
Version: 0.13.0

Concept link processor

Introduced 0.13.0

The concept_link processor reads a document's text, finds the names of concepts from a vocabulary in it, and writes the concept identifiers onto the document. It is what connects concept expansion to your actual content: without it, the vocabulary knows that a beta-blocker is a cardiac drug but nothing knows which of your documents are about one.

It also records what it could not link, which turns out to matter more than it sounds.

Why a dictionary, and not a model​

A dictionary linker is exhaustive over what it knows, needs no model, runs air-gapped, and — above all — can explain itself. A document is about dogs because the word "hound" appears at character 412 and that is a label of the concept. A model that scores 0.83 cannot say that, and a linker whose decisions cannot be argued with is one nobody will trust with a compliance question.

Parameters​

ParameterRequiredDefaultDescription
fieldYesThe text field to read.
vocabularyYesThe index holding the vocabulary's concepts and their labels.
target_fieldNoconceptThe field the matched concept identifiers are written to. This is the field graph_traversal uses as its node_field.
coverage_fieldNolinkedThe boolean field written to every document, saying whether anything was linked.
include_mentionsNofalseAlso write _mentions, recording each match with its text, start, end and concept.
ignore_missingNofalseSkip documents that do not have field instead of failing them.

Example​

PUT _ingest/pipeline/link-concepts
{
"processors": [
{
"concept_link": {
"field": "abstract",
"vocabulary": "mesh",
"target_field": "concept",
"include_mentions": true
}
}
]
}

A document whose abstract mentions a hound comes out carrying the concept identifier for that concept, linked: true, and — because include_mentions was set — the evidence:

{
"abstract": "The hound was examined ...",
"concept": [ 4285301 ],
"linked": true,
"_mentions": [
{ "text": "hound", "start": 4, "end": 9, "concept": 4285301 }
]
}

How matching works​

By whole tokens, and this is not a detail​

Substring matching would link every document containing "category" to the concept cat. The failure is silent and it compounds: those documents then answer questions about animals, and a recall claim becomes a precision embarrassment. A mention must begin and end where a word does.

Longest match wins, overlaps are kept apart​

Where "New York City" and "New York" both match at the same position, the longer one is what the author meant. But a shorter name starting elsewhere is a separate mention and is kept — text saying "New York City and York" names two things.

Ambiguous names report every candidate​

When one name belongs to several concepts, all of them are recorded. The processor does not guess which sense was meant; disambiguation is a separate decision, and silently picking one would be unexplainable.

The linked field, and why it exists​

The processor writes linked to every document it sees — false as readily as true.

That field exists so that coverage is an ordinary aggregation. You can ask what share of an index is reachable by concept search with a plain query, and the FindByMeaning tool reads exactly this field to state its coverage in every answer.

GET articles/_search
{
"size": 0,
"aggs": { "coverage": { "terms": { "field": "linked" } } }
}
warning

Documents that are not linked cannot be found by concept search at all. This is the honest boundary of the completeness claim: concept expansion returns every linked document about a concept, not every document. Measure your coverage before you rely on it.

Order in the pipeline​

concept_link reads text, so it belongs after anything that produces text — content extraction, for example — and before indexing. It does not need embeddings and does not call any external service.

Next​