Skip to main content

The Deduplication Challenge

When extracting entities from multiple documents, the same real-world entity often appears under different names:
Without deduplication, your knowledge graph contains duplicate nodes for the same entity, fragmenting relationships and making analysis difficult. sift-kg solves this with a 4-layer deduplication approach that combines deterministic rules, semantic similarity, LLM reasoning, and human review.

Layer 1: Automatic Pre-Deduplication

Before entities become graph nodes, sift-kg runs automatic deduplication during sift build to catch obvious duplicates.

Phase 1: Deterministic Merging

Entities are normalized and grouped by: Unicode Normalization
Singularization
Title Stripping Common title prefixes are removed:
Full list of recognized titles:
See /home/daytona/workspace/source/src/sift_kg/graph/prededup.py:30 for the complete list. Canonical Selection When multiple variants normalize to the same form, the canonical is chosen by:
  1. Frequency: Most common variant wins
  2. Length: Longest variant (likely more complete)
  3. Alphabetical: First alphabetically (tiebreaker)
Example:
Implementation at /home/daytona/workspace/source/src/sift_kg/graph/prededup.py:177:

Phase 2: Fuzzy Semantic Matching

After deterministic grouping, remaining unique forms are compared using SemHash semantic similarity. How SemHash Works
  1. Convert entity names to embeddings using Model2Vec (lightweight, no GPU needed)
  2. Compute pairwise cosine similarities
  3. Merge entities with similarity ≥ 0.95 threshold
What It Catches
Implementation From /home/daytona/workspace/source/src/sift_kg/graph/prededup.py:153:

Results

Pre-dedup typically reduces entity counts by 10-30%:
Saved entities don’t waste LLM tokens in the next resolution stage.

Layer 2: LLM-Based Entity Resolution

After pre-dedup, sift resolve finds duplicates that require semantic understanding.

Why LLM Resolution?

Pre-dedup catches mechanical duplicates. LLM resolution catches:
  • Partial names: “Joseph Recarey” vs “Joe Recarey”
  • Nicknames: “William” vs “Bill”
  • Context-dependent equivalence: “the Transformer architecture” vs “Transformers”
  • Professional vs personal names: “Dr. Alice Smith” vs “Alice”

Batching Strategy

Entities are processed in type-specific batches to improve accuracy. Sorting Entities are sorted before batching:
  • PERSON entities: Sorted by surname so name variants cluster
  • Other types: Alphabetically by name
Implementation at /home/daytona/workspace/source/src/sift_kg/resolve/resolver.py:40:
Overlapping Windows Large entity sets are split into batches with overlap:
  • Batch size: 100 entities max
  • Overlap: 20 entities between consecutive batches
This prevents duplicates from being missed at batch boundaries.

LLM Resolution Prompt

For each batch, the LLM receives entity data:
The prompt asks the LLM to identify:
  1. Duplicates: Same entity with different names → merge proposals
  2. Variants: Parent-child relationships → EXTENDS relations
From /home/daytona/workspace/source/src/sift_kg/resolve/resolver.py:393:

Domain Context

When a domain provides system_context, it’s prepended to the prompt:
This helps the LLM make domain-appropriate decisions.

Cross-Type Deduplication

After per-type resolution, sift-kg finds entities with identical names but different types:
These are merged automatically (no LLM call needed):
  • Canonical type: The one with more connections (higher degree)
  • Reason: “Same name across types (CONCEPT vs PHENOMENON). Relations will be combined.”
From /home/daytona/workspace/source/src/sift_kg/resolve/resolver.py:190:

Output

sift resolve produces: merge_proposals.yaml
relation_review.yaml (updated with variants)

Layer 3: Human Review

LLMs make mistakes. Human review validates proposals before applying changes.

Interactive Review Process

The sift review command presents each proposal:
User decisions:
  • Approve: Status changes to CONFIRMED, will be applied
  • Reject: Status changes to REJECTED, ignored
  • Skip: Stays DRAFT, can review later
  • Quit: Saves progress, remaining proposals stay DRAFT
Implementation at /home/daytona/workspace/source/src/sift_kg/resolve/reviewer.py:39.

Auto-Approval

High-confidence proposals can be auto-confirmed:
All proposals where every member has confidence ≥ 0.85 are automatically confirmed without interactive review.
Remaining proposals are reviewed interactively.

Relation Review

Flagged relations are also reviewed:
Auto-reject low-confidence relations:
Relations with confidence < 0.5 are automatically rejected.

Review Files After Changes

After review, files are updated in place: merge_proposals.yaml
You can also manually edit these files before running sift apply-merges.

Layer 4: Apply Merges

The final stage executes confirmed changes to the graph.

Merge Operation

For each CONFIRMED proposal, sift-kg: 1. Merge Node Data Canonical entity accumulates data from merged members:
From /home/daytona/workspace/source/src/sift_kg/resolve/engine.py:95. 2. Rewrite Edges All relations pointing to/from merged members are redirected to the canonical:
3. Remove Merged Nodes
4. Remove Rejected Relations Relations marked REJECTED in relation_review.yaml are deleted:
From /home/daytona/workspace/source/src/sift_kg/resolve/engine.py:140.

Output Statistics

The updated graph is saved to graph_data.json.

Real-World Example

Let’s trace a person entity through all 4 layers:

Initial Extractions

Three documents extract variations of the same person:

Layer 1: Pre-Dedup

Normalization:
Grouping by normalized form:
Canonical selection:
SemHash clustering:
Result after pre-dedup:
Note: “edwards” and “bradley edwards” weren’t merged because semantic similarity was below the 0.95 threshold. This is where LLM resolution helps.

Layer 2: LLM Resolution

Entities sent to LLM:
LLM response:
Merge proposal created:

Layer 3: Human Review

User approves. Status changes to CONFIRMED.

Layer 4: Apply Merge

Before merge:
After merge:
The duplicate EMPLOYED_BY relation becomes a self-loop and is dropped. Final result: One consolidated entity with complete provenance and all name variations tracked.

Advanced Features

Semantic Clustering

For very large entity sets (1000+), use embedding-based clustering instead of alphabetical batching:
Requires installing the embeddings extra:
How it works:
  1. Generate embeddings for all entity names
  2. Cluster by cosine similarity
  3. Each cluster becomes a batch sent to the LLM
Benefits:
  • Semantically similar entities are batched together
  • Reduces false negatives (missed duplicates)
  • Better for multilingual entities
Tradeoff: Slower and requires more memory.

Domain-Specific Resolution Context

Provide domain context to help the LLM make better decisions:

Reviewing Previous Decisions

If you want to re-review previously confirmed/rejected proposals:
  1. Edit merge_proposals.yaml and change status back to DRAFT:
  2. Run sift review again:

Manual Merge Proposals

You can manually add merge proposals to merge_proposals.yaml:
Then run sift apply-merges to execute.

Performance Considerations

Cost Estimation

Entity resolution costs scale with entity count:
For 1,000 entities across 10 types with gpt-4o-mini:

Speed Optimization

Use concurrency to parallelize LLM calls:
Default is 4. Higher values speed up resolution but may hit rate limits.

Skipping Resolution

If you’re confident in your extractions or have very clean data:
You can always run sift resolve later if you discover duplicates.

Best Practices

1. Review High-Impact Merges First

Sort proposals by degree (connection count) to focus on central entities:
Merging highly-connected entities has bigger impact on graph structure.

2. Use Auto-Approval Conservatively

Start with a high threshold:
Review the auto-approved proposals in merge_proposals.yaml. If quality is good, lower the threshold:

3. Iterate on Domain Context

If the LLM makes systematic mistakes, add guidance to system_context:

4. Combine with Pre-Dedup Tuning

If pre-dedup misses obvious duplicates, you can adjust the SemHash threshold in code:
Default is 0.95 (conservative).

Next Steps

How It Works

Understand the full pipeline from extraction to visualization

Domains

Learn about bundled domains and creating custom schemas