How Named Entity Disambiguation Works Using Open LLMs, Neo4j, and Medical Ontologies
Resolving Medical term ambiguity with AI and Ontologies
How Named Entity Disambiguation Works Using Open LLMs, Neo4j, and Medical Ontologies
Resolving Medical term ambiguity with AI and Ontologies

Imagine a doctor reading patient notes and encountering the word “diabetes”. Does the patient have Type 1, Type 2, or gestational diabetes? Without context, even an experienced clinician would hesitate. Now imagine the doctor can instantly look up a medical encyclopedia, trace every relationship between diseases, and reason through the answer in seconds. That is exactly what this medium article builds — and it does it entirely on your laptop, with no cloud APIs, no patient data leaving the machine.
If you’re not a Medium member, you can read the full story using the MEDIUM FRIEND LINK
The Problem: Ambiguity in Medical Text
Natural language is messy. When a medical note says “the patient has poorly controlled diabetes”, a computer doesn’t automatically know that this maps to SNOMED CT concept 44054006: Diabetes mellitus type 2. There are dozens of diabetes-related concepts in any clinical terminology. Picking the right one — Named Entity Disambiguation (NED) — is the challenge at the heart of this Blog.
SNOMED CT (Systematized Nomenclature of Medicine — Clinical Terms) is the world’s most comprehensive clinical terminology system. It contains hundreds of thousands of medical concepts — diseases, symptoms, procedures, body structures, substances — and crucially, it defines relationships between them.
For example, SNOMED doesn’t just know that “Type 2 Diabetes” exists. It also knows that:
- Type 2 Diabetes is a subtype of Diabetes Mellitus
- Diabetes Mellitus is a risk factor for Renal Failure
- Hypertension is also a risk factor for Renal Failure
These relationships form a graph. And when you store that graph in Neo4j, you can traverse it — finding connections between concepts that a simple keyword search would never reveal.
The Big Picture: A Four-Stage Pipeline
Before diving into each stage, here is the entire pipeline from start to finish.

End-to-End Example
Input sentence:
“The patient has poorly controlled diabetes and hypertension, increasing risk of renal failure.”
Stage 1 — NER with LLM
The first stage is about finding the medical terms in the text before we can identify them.
Traditional NER tools are trained on fixed categories like “Person”, “Location”, “Organization”. Medical NER needs far richer categories — “Disease”, “Procedure”, “Body Structure”, “Substance”, and many more. These categories come directly from the SNOMED CT ontology stored in Neo4j. At startup, the system queries Neo4j to retrieve the full list of SNOMED concept type labels and passes them to the LLM as a menu of valid annotations.
The LLM reads the input sentence and returns a structured list of every medical mention it found, along with its SNOMED category label and its position in the text. This is far more flexible than a statistical model, because the same LLM can handle unusual phrasings, abbreviations, and clinical shorthand without retraining.

The LLM is prompted with the full list of SNOMED category labels retrieved from Neo4j (e.g., Disease, Substance, Procedure, Body structure, …) and must annotate the sentence.
{
"sentence": "The patient has poorly controlled diabetes and hypertension, increasing risk of renal failure.",
"entities": [
{"id": 0, "mention": "poorly controlled diabetes", "label": "Disease", "start": 16, "end": 41},
{"id": 1, "mention": "hypertension", "label": "Disease", "start": 47, "end": 59},
{"id": 2, "mention": "renal failure", "label": "Disease", "start": 76, "end": 89}
]
}
The key insight here is that the LLM is not guessing categories blindly. It receives the exact list of categories that exist in the SNOMED ontology, so its output is already aligned with the structure of the knowledge graph.
Stage 2 — Candidate Selection (Neo4j full-text search)
Once we know which terms to look up, the next step is finding the possible SNOMED concepts they could refer to. This is called Candidate Selection.
Rather than using embedding similarity (which is computationally heavy and requires a separate embedding model), the blog uses Neo4j’s built-in full-text search index. This works similarly to how a search engine finds results — it accepts fuzzy queries, handles partial matches, and ranks results by relevance.
For the mention “poorly controlled diabetes”, the full-text search might return three or four SNOMED concepts with similar names. This small set of candidates is exactly what the system needs: not the final answer, but a shortlist that will be narrowed down using graph reasoning in the next stage.

For "poorly controlled diabetes", the fuzzy full-text query returns:
candidates = [
{"snomed_id": "44054006", "name": "Diabetes mellitus type 2"},
{"snomed_id": "73211009", "name": "Diabetes mellitus"},
{"snomed_id": "290371000000106", "name": "Poorly controlled type 1 diabetes mellitus"}
]
Similarly for “hypertension” and “renal failure”.
This stage is deliberately fast and broad. The goal is not precision yet — it is to generate a reasonable shortlist of 3–5 candidates per mention that can then be reasoned about in context
Stage 3 — Graph Context Generation
This is where things become genuinely novel. Instead of making the disambiguation decision immediately, the system first consults the knowledge graph to understand how the candidate concepts relate to each other. It does this in three sub-steps.

a. PathExtraction — Neo4j GDS finds shortest SNOMED paths (max depth 2) between every combination of candidate SNOMED IDs, filtering out high-degree hub nodes:
(Diabetes mellitus type 2)-[:RISK_FACTOR_FOR]->(Renal failure)
(Hypertension)-[:RISK_FACTOR_FOR]->(Renal failure)
b. PathTranslation — The LLM converts each path to a sentence:
{"sentence": "Diabetes mellitus type 2 is a risk factor for renal failure."}
{"sentence": "Hypertension is also a risk factor for renal failure, suggesting a shared mechanism."}
c. PathSummarization — The LLM merges all translated sentences:
{
"context": "Diabetes mellitus type 2 and hypertension are both risk factors for renal failure,
indicating that patients with these conditions are at heightened risk of developing
kidney complications."
}
Stage 4 — Candidate Disambiguation
With the context paragraph in hand, the final stage is straightforward. The LLM receives three things:
- The original sentence from the patient notes
- The list of candidates for each entity is mentioned
- The graph-derived context paragraph
Armed with this information, the LLM can make a confident, reasoned selection. It knows that the text mentions three conditions that are known to be related (thanks to the graph context), and it can choose the most specific and appropriate SNOMED concept for each one.

The LLM receives the original sentence, the candidate list, and the context summary, then selects:
{
"entities": [
{"id": 0, "disambiguation": {"snomed_id": "44054006", "name": "Diabetes mellitus type 2"}},
{"id": 1, "disambiguation": {"snomed_id": "38341003", "name": "Hypertensive disorder"}},
{"id": 2, "disambiguation": {"snomed_id": "42399005", "name": "Renal failure syndrome"}}
]
]
The graph-derived context (“Diabetes mellitus type 2 and hypertension are risk factors for renal failure”) gives the LLM enough evidence to confidently pick the most specific SNOMED concept rather than a generic one.
Notice how the graph context is doing real work here. Without it, the LLM might choose the generic “Diabetes mellitus” concept instead of the more specific “Diabetes mellitus type 2”. The relational evidence — the fact that type 2 diabetes specifically has a documented RISK_FACTOR_FOR link to renal failure in SNOMED — tips the balance toward the more precise concept.
A Complete Walkthrough: One Sentence, Four Stages
Let’s trace the entire pipeline with one example to see how everything connects.

Input sentence: “The patient has poorly controlled diabetes and hypertension, increasing risk of renal failure.”
At each stage, the system becomes more certain. The NER stage narrows the field from “all text” to “medical mentions”. The candidate selection stage narrows it from “all of SNOMED” to “a small shortlist”. The graph context stage provides the relational evidence. And the disambiguation stage makes the final call.
What Makes Graph Paths Such Good Context?
The most elegant idea in this blog is the path-to-text translation. Let’s think about why it works so well.
An LLM is trained on human-written text. Graph edges like [:RISK_FACTOR_FOR] are not human-written text — they are structured database relationships. If you tried to pass a raw graph path to an LLM, it would struggle to integrate it with the natural language context of the patient note.
But the moment you translate (Diabetes type 2)-[:RISK_FACTOR_FOR]->(Renal failure) into "Diabetes mellitus type 2 is a known risk factor for renal failure", you have created a sentence that the LLM is perfectly comfortable reasoning with. It can weight this evidence, compare it to the patient note, and make an informed decision — all because you bridged the semantic gap between graph structure and natural language.

This translation trick is a general pattern that goes beyond medical NLP. Any time you have structured data that you want an LLM to reason about, converting it to natural language first dramatically improves the LLM’s ability to use it effectively.
Conclusion
This blog demonstrates that you do not need massive compute, cloud APIs, or proprietary biomedical models to do high-quality medical entity disambiguation. By combining three simple ideas , a locally running LLM, a graph database with a medical ontology, and a pipeline that converts graph paths into natural language, the blog builds a system that is explainable, privacy-safe, and contextually aware.
The key breakthrough is using graph traversal as a reasoning aid rather than as a lookup tool. The SNOMED graph doesn’t just store concepts — it stores the relationships between concepts, and those relationships become the evidence that guides the LLM’s final decision.

The blog is ultimately a proof of concept for a broader principle: knowledge graphs and LLMs are not competing technologies — they are complementary ones. The graph provides the structured, verifiable domain knowledge that the LLM lacks. The LLM provides the flexible, language-level reasoning that the graph cannot do. Together, they are more capable than either is alone.
메타데이터
- post_id
- 583138ff8b3d
- slug
- how-named-entity-disambiguation-works-using-open-llms-neo4j-and-medical-ontologies-583138ff8b3d
- url
- https://medium.com/@aiwithakashgoyal/how-named-entity-disambiguation-works-using-open-llms-neo4j-and-medical-ontologies-583138ff8b3d
- canonical_url
- https://medium.com/@aiwithakashgoyal/how-named-entity-disambiguation-works-using-open-llms-neo4j-and-medical-ontologies-583138ff8b3d
- author_url
- https://medium.com/@aiwithakashgoyal
- status
- ok
- fetched_at
- 2026-06-09 15:37:30