Skip to content

Getting Started

LinkingTK unifies four text-linking tasks behind one common interface:

  • Entity Alignment (EA) — matching entities between two knowledge graphs.
  • Entity Linking (EL) — linking mentions in text to a knowledge base.
  • Word Sense Disambiguation (WSD) — linking mentions in text to dictionary senses.
  • Word Sense Alignment (WSA) — matching senses between two dictionaries.

Every task is modeled as matching entities from one dataset to another, so the same Entity, BlockingStrategy and Evaluator types work across all four. This page walks through that interface end-to-end; see Key Concepts for how the pieces fit together.

Installation

pip install linkingtk

The core install covers the shared interface and the transformer-based linkers. Optional extras add support for specific algorithms and data sources — pull them in as you need them:

Extra Adds
graph NetworkX / RDFLib graph helpers
nlp spaCy
kge PyKEEN and the knowledge-graph-embedding EA linkers
wn WordNet via the wn package
wikipedia Wikipedia / Wikidata HTTP sources
vector-index FAISS + sentence-transformers vector indexes
llm OpenAI / Anthropic clients for LLM-based linkers
server FastAPI / Uvicorn REST server mode
all everything above
pip install "linkingtk[graph,nlp]"

To work on LinkingTK itself, clone the repository and use uv:

git clone https://github.com/teangaNLP/linkingtk.git
cd linkingtk
uv sync --extra graph --extra nlp --group dev

The core interface

Every entity — a KG node, a text mention, a dictionary sense — is represented the same way:

from linkingtk.core import Entity

Entity(id="s1", labels=["cat"], description="A small domesticated feline.")

A linking algorithm implements BaseLinker.link, taking two datasets of entities and returning a list of AlignmentResult — one predicted source_id → target_id link per match, each with a confidence score.

Before scoring, a BlockingStrategy cuts the full dataset1 × dataset2 cross-product down to a smaller set of plausible candidate pairs. The simplest is ExactMatch, which pairs entities that share a label:

"""Minimal end-to-end example: block candidate pairs and evaluate them.

Run with: `uv run python examples/basic_exact_match.py`
"""

from linkingtk.blocking import ExactMatch
from linkingtk.core import Entity
from linkingtk.eval import Evaluator


def main() -> None:
    dataset1 = [
        Entity(id="s1", labels=["cat"]),
        Entity(id="s2", labels=["dog"]),
    ]
    dataset2 = [
        Entity(id="t1", labels=[("cat", "en")]),
        Entity(id="t2", labels=["fish"]),
    ]

    pairs = ExactMatch().candidate_pairs(dataset1, dataset2)
    predictions = [(e1.id, e2.id) for e1, e2 in pairs]
    print("Candidate pairs:", predictions)

    ground_truth = [("s1", "t1"), ("s2", "t2")]
    report = Evaluator.evaluate(predictions=predictions, ground_truth=ground_truth)
    print("Metrics:", report.metrics)

    blocking_report = Evaluator.evaluate_blocking(
        candidate_pairs=predictions,
        ground_truth=ground_truth,
        dataset1_size=len(dataset1),
        dataset2_size=len(dataset2),
    )
    print("Blocking metrics:", blocking_report.metrics)


if __name__ == "__main__":
    main()

Run with:

uv run python examples/basic_exact_match.py
Candidate pairs: [('s1', 't1')]
Metrics: {'precision@1': 1.0, 'recall': 0.5, 'f1': 0.6666666666666666}
Blocking metrics: {'pair_completeness': 0.5, 'reduction_ratio': 0.75}

Evaluator.evaluate scores predictions against known ground truth; the same call is used for every task, and Evaluator.evaluate_blocking scores the blocking step on its own via Pair Completeness (the fraction of true matches blocking kept) and Reduction Ratio (the fraction of the full cross-product it eliminated).

Putting it together across all four tasks

ExactMatch only works when source and target already share a label. A real linker like StringSimilarityLinker scores pairs by similarity instead, so it can match "cat" against a differently-worded description. The same linker class, and the same Evaluator.evaluate call, works unchanged across EA, EL, WSD and WSA — only the dataset and the fields being compared change per task:

"""String-similarity baselines on all four tasks.

Runs the same StringSimilarityLinker baseline (LeskLinker for WSD is a
preconfigured instance of it) against a bundled toy dataset for each of Entity
Alignment (EA), Entity Linking (EL), Word Sense Disambiguation (WSD) and Word
Sense Alignment (WSA), scoring each with Evaluator.evaluate.

Run with: `uv run python examples/mvp_benchmark.py`
"""

from __future__ import annotations

from linkingtk.algorithms.string_similarity import StringSimilarityLinker
from linkingtk.algorithms.wsd import LeskLinker
from linkingtk.blocking import LabelOverlap
from linkingtk.core import AlignmentResult
from linkingtk.datasets import ToyEADataset, ToyELDataset, ToyWSADataset, ToyWSDDataset
from linkingtk.eval import Evaluator


def _score(results: list[AlignmentResult], ground_truth: list[tuple[str, str]]) -> dict[str, float]:
    predictions = [(r.source_id, r.target_id) for r in results]
    return Evaluator.evaluate(predictions=predictions, ground_truth=ground_truth).metrics


def run_ea() -> dict[str, float]:
    kg1, kg2, ground_truth = ToyEADataset().load()
    linker = StringSimilarityLinker(source_field="label", target_field="label", metric="jaccard")
    results = linker.link(kg1, kg2, blocking=LabelOverlap(max_matches=3))
    return _score(results, ground_truth)


def run_el() -> dict[str, float]:
    mentions, kb, ground_truth = ToyELDataset().load()
    linker = StringSimilarityLinker(
        source_field="context", target_field="description", metric="word_overlap"
    )
    results = linker.link(mentions, kb)
    return _score(results, ground_truth)


def run_wsd() -> dict[str, float]:
    mentions, senses, ground_truth = ToyWSDDataset().load()
    results = LeskLinker().link(mentions, senses)
    return _score(results, ground_truth)


def run_wsa() -> dict[str, float]:
    dict1, dict2, ground_truth = ToyWSADataset().load()
    linker = StringSimilarityLinker(
        source_field="description", target_field="description", metric="word_overlap"
    )
    results = linker.link(dict1, dict2)
    return _score(results, ground_truth)


def main() -> None:
    tasks = {"EA": run_ea, "EL": run_el, "WSD": run_wsd, "WSA": run_wsa}
    for name, task in tasks.items():
        print(f"{name}: {task()}")


if __name__ == "__main__":
    main()

Run with:

uv run python examples/mvp_benchmark.py
EA: {'precision@1': 1.0, 'recall': 1.0, 'f1': 1.0}
EL: {'precision@1': 1.0, 'recall': 1.0, 'f1': 1.0}
WSD: {'precision@1': 1.0, 'recall': 1.0, 'f1': 1.0}
WSA: {'precision@1': 1.0, 'recall': 1.0, 'f1': 1.0}

LeskLinker used for WSD above is just a preconfigured StringSimilarityLinker. All four calls load a bundled toy dataset — small, synthetic, and included with the package so this runs offline with no downloads.

Where to go next

  • Datasets — swap the toy datasets above for real ones (Wikidata, WordNet, OpenEA, UFSAC, ...).
  • Examples — task-specific algorithms, from simple baselines to full reproductions of published methods.
  • API Reference — full docs for every public class and function.