Entity disambiguation turns vague mentions into precise knowledge graph nodes. In this tutorial, I will build a small disambiguation agent that reads a sentence, fetches candidate entities from a mock knowledge base, and uses an LLM to pick the right one. It is useful for anyone cleaning datasets, enriching search indexes, or wiring up a RAG pipeline that needs exact entity references.
What you'll need
- An Oxlo.ai API key from https://portal.oxlo.ai
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai
Step 1: Set Up the Client
I start by configuring the OpenAI SDK to point at Oxlo.ai. Because Oxlo.ai is fully OpenAI compatible, this is a single line change to the base URL.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
# Verify connectivity
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": "Confirm the client is working."}],
)
print(response.choices[0].message.content)
Step 2: Build a Mock Candidate Retriever
In a real system, candidates come from a vector database or an elastic index over a knowledge base like Wikidata. Here, I will hard-code a tiny lookup table so the tutorial is self-contained and runnable without external infra.
import json
KNOWLEDGE_BASE = {
"Apple": [
{"id": "Q312", "name": "Apple Inc.", "description": "American multinational technology company headquartered in Cupertino, California."},
{"id": "Q89", "name": "Apple", "description": "Edible fruit produced by an apple tree (Malus domestica)."}
],
"Jordan": [
{"id": "Q30", "name": "Michael Jordan", "description": "American former professional basketball player and businessman."},
{"id": "Q810", "name": "Jordan", "description": "Country in the Middle East bordered by Saudi Arabia, Iraq, Syria, Israel and Palestine."},
{"id": "Q622649", "name": "Jordan River", "description": "River in Western Asia flowing to the Dead Sea."}
],
"Python": [
{"id": "Q28865", "name": "Python (programming language)", "description": "High-level, general-purpose programming language."},
{"id": "Q201306", "name": "Python (genus)", "description": "Genus of constricting snakes in the Pythonidae family."}
]
}
def get_candidates(mention: str):
return KNOWLEDGE_BASE.get(mention, [])
Step 3: Write the Disambiguation Prompt
The system prompt is the contract. I tell the model exactly what inputs to expect, what JSON shape to return, and how to handle misses. Keeping the instructions tight reduces parsing failures.
SYSTEM_PROMPT = """You are an entity disambiguation system. Your job is to pick the single best candidate for a given mention based on the surrounding context.
You will receive:
1. A mention to disambiguate.
2. A context sentence or paragraph containing the mention.
3. A JSON list of candidate entities, each with an id, name, and description.
Respond with a JSON object containing exactly two keys:
- "entity_id": the id of the best matching candidate.
- "reasoning": a one-sentence explanation of why this candidate fits the context.
If none of the candidates fit, use "entity_id": null. Output raw JSON only, with no markdown fences."""
Step 4: Assemble the Pipeline
Now I wire the retriever and the LLM together. The disambiguate function builds the user message, calls Oxlo.ai, and parses the JSON response. I use llama-3.3-70b here because it follows structured instructions reliably, though Oxlo.ai carries several alternatives like qwen-3-32b or kimi-k2.6 if you need multilingual or vision support later.
def disambiguate(mention: str, context: str) -> dict:
candidates = get_candidates(mention)
if not candidates:
return {"entity_id": None, "reasoning": "No candidates found."}
user_message = f"""Mention: {mention}
Context: {context}
Candidates: {json.dumps(candidates)}"""
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()
# Strip accidental markdown fences
if raw.startswith("
```json"):
raw = raw[7:]
elif raw.startswith("```
"):
raw = raw[3:]
if raw.endswith("
```
"):
raw = raw[:-3]
raw = raw.strip()
return json.loads(raw)
Run It
I will feed the pipeline six ambiguous mentions and print the results.
tests = [
("Apple", "I love baking pies with cinnamon and Apple every autumn."),
("Apple", "Apple reported record quarterly revenue for its Mac division."),
("Jordan", "Jordan averages over 30 points per game in the playoffs."),
("Jordan", "We crossed into Jordan from Israel at the Allenby Bridge."),
("Python", "My data pipeline is written entirely in Python."),
("Python", "The zookeeper fed a large rat to the hungry Python.")
]
for mention, context in tests:
result = disambiguate(mention, context)
print(f"Mention: {mention}")
print(f"Context: {context}")
print(f"Result: {result}")
print()
Example output:
Mention: Apple
Context: I love baking pies with cinnamon and Apple every autumn.
Result: {'entity_id': 'Q89', 'reasoning': 'The context mentions baking pies, which refers to the edible fruit rather than the technology company.'}
Mention: Apple
Context: Apple reported record quarterly revenue for its Mac division.
Result: {'entity_id': 'Q312', 'reasoning': 'Quarterly revenue and the Mac division indicate the technology company Apple Inc.'}
Mention: Jordan
Context: Jordan averages over 30 points per game in the playoffs.
Result: {'entity_id': 'Q30', 'reasoning': 'Averaging points in the playoffs refers to the basketball player Michael Jordan.'}
Mention: Jordan
Context: We crossed into Jordan from Israel at the Allenby Bridge.
Result: {'entity_id': 'Q810', 'reasoning': 'Crossing a border from Israel indicates the country Jordan.'}
Mention: Python
Context: My data pipeline is written entirely in Python.
Result: {'entity_id': 'Q28865', 'reasoning': 'A data pipeline being written in Python refers to the programming language.'}
Mention: Python
Context: The zookeeper fed a large rat to the hungry Python.
Result: {'entity_id': 'Q201306', 'reasoning': 'A zookeeper feeding a rat to a hungry Python indicates the snake genus.'}
Wrap-Up and Next Steps
That is the core of a working entity disambiguation agent. To productionize it, swap the mock KNOWLEDGE_BASE for a vector search step against a real knowledge base like Wikidata or your internal entity catalog. If latency becomes critical, Oxlo.ai offers request-based pricing that stays flat regardless of how long your context snippets grow, which helps when you start passing in full paragraphs or multi-turn conversation history to improve accuracy. See https://oxlo.ai/pricing for details.
Top comments (0)