DEV Community

shashank ms
shashank ms

Posted on

LLM for Entity Disambiguation

Entity disambiguation turns ambiguous strings like "Apple" or "Washington" into specific knowledge-base entries using surrounding context. We will build a lightweight Python resolver that detects surface forms, retrieves candidates from a local index, and uses an LLM to pick the correct entity. I run this on Oxlo.ai because flat per-request pricing keeps costs stable even when candidate descriptions make the prompt long, and the OpenAI-compatible client drops in without extra dependencies.

What you'll need

Step 1: Set up the Oxlo.ai client

I always start with a smoke test. Point the OpenAI SDK at Oxlo.ai and verify the connection returns a response.

from openai import OpenAI

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "user", "content": "Say OK"},
    ],
)
print(response.choices[0].message.content)

Step 2: Build a candidate entity index

A production system might query Wikidata or an internal graph. Here we keep a hard-coded dictionary mapping lowercase surface forms to candidate lists. Each candidate carries an ID, canonical name, type, and description.

CANDIDATE_INDEX = {
    "apple": [
        {"id": "Q312", "name": "Apple Inc.", "type": "ORG", "description": "Technology company headquartered in Cupertino, California"},
        {"id": "Q89", "name": "Apple", "type": "PLANT", "description": "Edible fruit of the apple tree"}
    ],
    "washington": [
        {"id": "Q1223", "name": "Washington", "type": "GPE", "description": "State in the Pacific Northwest of the United States"},
        {"id": "Q9834", "name": "George Washington", "type": "PERSON", "description": "First president of the United States"}
    ],
    "python": [
        {"id": "Q28865", "name": "Python", "type": "TECH", "description": "General-purpose programming language"},
        {"id": "Q245462", "name": "Python", "type": "ANIMAL", "description": "Family of nonvenomous snakes"}
    ]
}

Step 3: Define the disambiguation system prompt

The prompt is the contract. It tells the model to return strict JSON with one entry per ambiguous mention, including confidence and a one-sentence rationale.

SYSTEM_PROMPT = """You are an entity disambiguation engine. Your task is to map ambiguous mentions in a user-provided text to the correct candidate entity from a supplied list.

For each mention, output an object with:
- mention: the exact text as it appeared in the input
- entity_id: the id of the chosen candidate
- entity_name: the canonical name of the chosen candidate
- confidence: HIGH, MEDIUM, or LOW
- reasoning: one sentence explaining why the candidate fits the context

Return ONLY a JSON object with a single key "entities" containing the list of results. If a mention has no suitable candidate, set entity_id to null."""

Step 4: Detect mentions and query the LLM

We scan the input text for any surface form in our index, build a structured prompt with the candidates, and ask the model to resolve them. I use llama-3.3-70b here because it follows JSON instructions reliably and runs without cold starts on Oxlo.ai.

import json
import re

def detect_mentions(text, index):
    found = []
    text_lower = text.lower()
    for surface in index:
        pattern = r'\b' + re.escape(surface) + r'\b'
        if re.search(pattern, text_lower):
            found.append(surface)
    return found

def resolve(text, index, client):
    mentions = detect_mentions(text, index)
    if not mentions:
        return {"entities": []}
    
    candidate_block = ""
    for m in mentions:
        candidate_block += f"\nMention '{m}':\n"
        for c in index[m]:
            candidate_block += f"  - {c['id']} ({c['type']}): {c['name']} - {c['description']}\n"
    
    user_message = f"""Text: {text}
Candidates:{candidate_block}
Return the JSON disambiguation results."""
    
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
    )
    
    raw = response.choices[0].message.content.strip()
    if raw.startswith("

```"):
        raw = raw.split("```

")[1].strip()
        if raw.startswith("json"):
            raw = raw[4:].strip()
    return json.loads(raw)

Step 5: Connect the pipeline

Now we wire the pieces together. I added a small helper to pretty-print results so console output stays readable during development.

def disambiguate(text, index, client):
    result = resolve(text, index, client)
    print(json.dumps(result, indent=2))
    return result

Run it

Pass a sentence containing multiple ambiguous entities. The model receives the full candidate descriptions and resolves each based on context.

if __name__ == "__main__":
    client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
    
    text = "Apple is opening a new engineering office in Washington, and Python developers are invited to the launch event."
    
    disambiguate(text, CANDIDATE_INDEX, client)

Example output:

{
  "entities": [
    {
      "mention": "Apple",
      "entity_id": "Q312",
      "entity_name": "Apple Inc.",
      "confidence": "HIGH",
      "reasoning": "Opening an engineering office indicates a technology organization."
    },
    {
      "mention": "Washington",
      "entity_id": "Q1223",
      "entity_name": "Washington",
      "confidence": "HIGH",
      "reasoning": "An office location refers to the U.S. state."
    },
    {
      "mention": "Python",
      "entity_id": "Q28865",
      "entity_name": "Python",
      "confidence": "HIGH",
      "reasoning": "Developers attending a technical event refers to the programming language."
    }
  ]
}

Wrap up and next steps

This pipeline gives you a working baseline. Two concrete ways to push it further: replace the hard-coded index with a vector search over an embedding store so you can scale to thousands of candidates, or add a caching layer in front of the Oxlo.ai client to avoid re-resolving frequent mentions.

Top comments (0)