# Keeping Match Confidence on the Graph Edge: Why Throwing Away Splink Scores Hurts Graph RAG

> Source: <https://dev.to/hannune/keeping-match-confidence-on-the-graph-edge-why-throwing-away-splink-scores-hurts-graph-rag-140n>
> Published: 2026-07-17 02:40:18+00:00

Most entity resolution pipelines throw away the most useful number they produce.

You run a probabilistic linker — Splink, Dedupe, or a custom model — and it produces a match probability for every candidate pair. 0.95 for two records you are very confident share the same real-world entity. 0.71 for a pair that probably matches but has enough ambiguity to give you pause.

Then you apply a threshold, classify each pair as a match or non-match, and discard the probability. The downstream system — a knowledge graph, a database table, an analytics pipeline — gets a binary signal: same entity, or different entity.

This is a mistake when those resolved entities feed a knowledge graph.

The match probability is information about how certain the resolution was. Once you cross the threshold gate, that information is gone. The knowledge graph treats a 0.95-confidence merge and a 0.71-confidence merge identically. Both become unconditional identity claims: "this is the same entity."

For low-stakes applications, this is fine. For a knowledge graph that feeds retrieval systems, this is a quiet defect.

In [er-api](https://er-api.hannune.ai), a multilingual entity resolution service for Korean, Japanese, Chinese, and English corporate data, I kept the match probability from Splink as an edge property in the graph rather than discarding it at the resolution boundary.

The insert looks like this:

``` python
def create_identity_edge(
    source_node_id: str,
    target_node_id: str,
    match_probability: float,
    source_record_ids: list[str],
) -> dict:
    return {
        "source": source_node_id,
        "target": target_node_id,
        "relation": "SAME_AS",
        "properties": {
            "match_probability": match_probability,
            "source_record_ids": source_record_ids,
            "resolved_at": datetime.utcnow().isoformat(),
            "resolver": "splink-3",
            "review_status": "auto" if match_probability >= 0.90 else "pending",
        }
    }
```

A 0.95-confidence match between Samsung Electronics records gets `review_status: "auto"`

. A 0.71-confidence match gets `review_status: "pending"`

and is queued for human review.

The impact shows up most clearly at retrieval time.

When a Graph RAG query traverses the graph, it may cross one or more `SAME_AS`

edges to connect records from different source systems. If those edges have unknown confidence, the traversal has no way to signal how certain the identity claims it relied on actually are.

``` python
def traverse_with_confidence(
    start_node_id: str,
    relation: str,
    min_confidence: float = 0.80,
) -> list[dict]:
    results = []
    for path in graph.find_paths(start=start_node_id, edge_type=relation):
        identity_edges = [e for e in path.edges if e["relation"] == "SAME_AS"]
        if not identity_edges:
            results.append({"path": path, "path_confidence": 1.0})
            continue

        worst_case = min(e["properties"]["match_probability"] for e in identity_edges)
        if worst_case >= min_confidence:
            results.append({"path": path, "path_confidence": worst_case})

    return sorted(results, key=lambda x: x["path_confidence"], reverse=True)
```

Three things change when confidence propagates through the graph:

**Per-use-case threshold filtering.** A due-diligence query for legal compliance can set `min_confidence=0.95`

. An exploratory search for business intelligence can use `min_confidence=0.70`

. The same graph serves both use cases because the confidence data is available to filter on.

**Worst-case confidence on retrieved paths.** If a traversal crosses a 0.68-confidence merge, the retrieval result carries that number. The LLM prompt can surface it explicitly: "Note: this answer relies on an entity match with 68% confidence." The LLM cannot author this signal — it can only surface it if the retrieval layer passes it through.

**Low-confidence merges become visible.** Instead of being invisible assumptions baked permanently into graph structure, uncertain merges are flagged and queryable. You can find all `SAME_AS`

edges below a threshold and present them to domain experts for review without touching graph structure.

In practice, running entity resolution on East Asian corporate data means dealing with cases where the right answer requires domain knowledge — knowing that "삼성SDI" and "Samsung SDI" are the same entity requires understanding the Korean naming convention, not just string similarity.

The `review_status: "pending"`

flag on low-confidence edges gives you a clean queue:

``` php
def get_pending_review_queue(threshold: float = 0.90) -> list[dict]:
    return graph.find_edges(
        relation="SAME_AS",
        filters={"review_status": "pending", "match_probability": {"lt": threshold}},
        order_by="match_probability",
        order="asc",
    )
```

Reviewers see the lowest-confidence merges first. They can confirm, reject, or flag for escalation. Confirmed merges update `review_status`

to `"verified"`

. Rejected merges get split back into separate nodes.

The graph stays queryable during review. Downstream systems that need only high-confidence data can filter to `match_probability >= 0.90`

. Systems that can tolerate more uncertainty can include pending merges. Neither has to wait for the review queue to clear.

The common alternative to confidence-aware edges is running resolution at higher thresholds and treating everything as certain. This suppresses the 0.71-confidence merges entirely — they never enter the graph.

The problem is that some of those 0.71-confidence pairs are correct. Multilingual corporate data has noise that systematically pushes correct matches below the high-confidence threshold: inconsistent romanization, abbreviated legal suffixes, different subsidiary naming conventions across source databases. A threshold high enough to eliminate incorrect merges also eliminates a meaningful fraction of correct ones.

The confidence-on-edge approach lets you include them with explicit uncertainty rather than excluding them. Users and downstream systems can decide what confidence level they need. The probability on the edge is the mechanism that makes that decision possible.

The match probability is information. Throwing it away at the resolution gate means the knowledge graph permanently forgets something it knew about the quality of its own structure.

*Building multilingual entity resolution and knowledge graph pipelines at er-api.hannune.ai and 2asy.ai. More at hannune.ai.*

*Cover: Piret Ilver on Unsplash*
