Knowledge Graph Ontology
Control which entities and relations Knowledge Graph extracts, and auto-discover a project-specific ontology from sample files.
An ontology is the schema of a project's knowledge graph: the entity types to look for, the relation types that connect them, and which entity types each relation is allowed to connect. Ingestion only extracts entities and relations that fit the project's ontology, so the ontology decides what the graph contains and which questions it answers well.
Every project starts with a generic default ontology. To tailor extraction to your own documents, for example to find Contract, Party, and Clause entities in legal documents, let the server draft a project-specific ontology from a few sample files, review and edit the draft, and approve it.
Ontology Structure
GET /v1/graph/ontology returns, and PUT /v1/graph/ontology/draft accepts, ontologies in GraphRAG-SDK's own Ontology JSON shape:
{
"entities": [
{
"label": "Person",
"description": "A named individual (author, employee, participant, contact).",
"properties": [
{ "name": "role", "type": "STRING", "description": "Job title or role." }
]
},
{
"label": "Organization",
"description": "A company, agency, team, or other named group entity.",
"properties": []
}
],
"relations": [
{
"label": "WORKS_FOR",
"description": "Person is employed by or affiliated with an Organization.",
"patterns": [["Person", "Organization"]],
"properties": [
{ "name": "start_date", "type": "DATE", "description": "When the employment started." }
]
}
]
}| Field | Description |
|---|---|
entities[].label | Name of the entity type, for example Person. GLiNER receives these labels as the entity types to look for. |
entities[].description | What counts as this entity type. The extraction prompts include it, so a precise description improves extraction. |
relations[].label | Name of the relation type, for example WORKS_FOR. |
relations[].description | What the relation means. |
relations[].patterns | Allowed [source label, target label] pairs. The extractor drops relations whose entity types match none of the pairs, and reports them in the ingestion job's warnings. An empty list, as on RELATED_TO, allows any pair. |
properties | Typed attributes the extractor captures on an entity or a relation. |
properties[].name | Attribute name in snake_case, such as role or founded_year. See Draft Sanitization. |
properties[].type | One of STRING, INTEGER, FLOAT, BOOLEAN, DATE, or LIST. Defaults to STRING. |
Every entity also carries a name attribute that GraphRAG-SDK manages itself, whether or not the ontology declares it. Do not use Document or Chunk as a label: GraphRAG-SDK uses them for its own nodes.
Default Ontology
A project uses the default ontology until it saves or approves its own. The default declares no properties.
| Entity Type | Description |
|---|---|
Person | A named individual: author, employee, participant, or contact. |
Organization | A company, agency, team, or other named group. |
Technology | A named technology, platform, framework, or standard. |
Product | A named product, service, or deliverable. |
Location | A named place: city, country, region, address, or venue. |
Date | A specific date, time, or date range. |
Event | A named occurrence: meeting, release, incident, milestone, or transaction. |
Concept | An abstract idea, topic, category, or named process. |
Law | A named law, regulation, standard, contract clause, or policy. |
Dataset | A named collection of data. |
Method | A named technique, algorithm, or procedure. |
| Relation Type | Allowed Patterns |
|---|---|
WORKS_FOR | Person → Organization |
LOCATED_IN | Organization, Person, Event, Product, or Location → Location |
PART_OF | Organization → Organization, Product → Organization, Technology → Product, Concept → Concept, Location → Location |
CREATED_BY | Product, Technology, or Dataset → Organization or Person |
USES | Organization → Technology or Product, Method → Technology, Product → Technology |
PARTICIPATED_IN | Person or Organization → Event |
OCCURRED_ON | Event → Date |
GOVERNED_BY | Organization, Product, Event, or Concept → Law |
COLLABORATES_WITH | Organization → Organization, Person → Person |
RELATED_TO | Any pair. A fallback for relevant associations that fit no other relation. |
Ontology Lifecycle
Each project has at most one ontology record, which holds two ontologies:
ontologyis the current draft. Discovery writes it, and you edit it.approved_ontologyis the active ontology, which ingestion, search, and completion use. It staysnull, and the project keeps using the default ontology, until you save or approve a draft.
The record's state is one of the following:
| State | Meaning | Next Steps |
|---|---|---|
discovering | Discovery is running. | Poll GET /v1/graph/ontology, or cancel. |
draft | Discovery finished, or you saved an edited draft. | Edit, approve, or propose again. |
approved | You approved the draft. | Propose again to change the ontology. |
failed | Discovery failed. last_error says why. | Edit the previous draft, or propose again. |
cancelled | You cancelled discovery. | Propose again. |
The typical flow is:
Upload a few representative files with POST /v1/files.
Start discovery with POST /v1/graph/ontology/propose.
Poll GET /v1/graph/ontology until state is draft.
Review ontology, and edit it with PUT /v1/graph/ontology/draft if needed.
Approve the draft with POST /v1/graph/ontology/approve.
Ingest your files. To apply the new ontology to files you ingested earlier, ingest them again with ?force=true.
Changing the ontology never changes what the graph already contains. Entities and relations
extracted under the previous ontology stay in the graph until you ingest their files again with
POST /v1/graph/files/{file_id}/ingest?force=true. Removing an entity or relation type from the
ontology only stops future extraction of that type.
Discover an Ontology
Pass 1 to 10 file IDs of already uploaded files, and an optional boundaries hint:
curl -X 'POST' \
'https://deepfellow-server-host/v1/graph/ontology/propose' \
-H "Authorization: Bearer DEEPFELLOW_PROJECT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"file_ids": ["FILE_ID_1", "FILE_ID_2"],
"boundaries": "legal contracts"
}'import requests
response = requests.post(
"https://deepfellow-server-host/v1/graph/ontology/propose",
headers={"Authorization": "Bearer DEEPFELLOW_PROJECT_API_KEY"},
json={
"file_ids": ["FILE_ID_1", "FILE_ID_2"],
"boundaries": "legal contracts",
},
)
print(response.json())const response = await fetch('https://deepfellow-server-host/v1/graph/ontology/propose', {
method: 'POST',
headers: {
Authorization: 'Bearer DEEPFELLOW_PROJECT_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_ids: ['FILE_ID_1', 'FILE_ID_2'],
boundaries: 'legal contracts'
})
});
const data = await response.json();
console.log(data);The endpoint returns 202 Accepted:
{
"status": "discovering",
"ontology_id": "68da445c5186deb8bca2bdeb"
}| Field | Description |
|---|---|
file_ids | 1 to graph.ontology_discovery_max_sample_files file IDs. The limit defaults to, and never exceeds, 10. A longer list returns 422 Unprocessable Entity. |
boundaries | Optional free-text hint that narrows the scope of the ontology, such as a document type or a domain. |
Discovery runs in the background:
- The server converts each sample file with Doc Chunker, through the same pipeline ingestion uses, so discovery reads the same text that ingestion will.
- The server picks
graph.ontology_discovery_sample_chunks_per_docchunks from each file, spread evenly from its beginning to its end. graph.ontology_discovery_llm_modelproposes entity and relation types from each sampled chunk, running up tograph.ontology_discovery_concurrencycalls in parallel. The prompts use the starting ontology as a vocabulary to build on.- GraphRAG-SDK merges the proposals across files, collapses synonyms, and merges the result into the starting ontology.
- The server cleans up the result and stores it as the draft.
The starting ontology is the project's approved ontology, or the default ontology if the project has none. The draft therefore always contains every type the project already uses, plus the types discovery added, so proposing again never drops a type you approved earlier. The same files always yield the same sampled chunks. The LLM's answers still vary between runs, so two runs over the same files produce similar but not always identical drafts.
propose overwrites the current draft once discovery finishes, and returns 409 Conflict while discovery is already running for the project. The active ontology stays untouched until you save or approve the new draft.
Choose sample files that cover the kinds of documents the project will hold. Since discovery reads
only a few chunks per file, several short, varied files give it more to work with than one long
file. To draft ontologies faster, or at a lower cost, set graph.ontology_discovery_llm_model to
a different model than graph.llm_model.
Check Discovery Progress
curl -X 'GET' \
'https://deepfellow-server-host/v1/graph/ontology' \
-H "Authorization: Bearer DEEPFELLOW_PROJECT_API_KEY"Response:
{
"id": "68da445c5186deb8bca2bdeb",
"project_id": "68da445c5186deb8bca2bdec",
"state": "draft",
"ontology": {
"entities": [
{ "label": "Person", "description": "A named individual (author, employee, participant, contact).", "properties": [] },
{ "label": "Contract", "description": "A legally binding agreement between parties.", "properties": [
{ "name": "effective_date", "type": "DATE", "description": "Date the contract takes effect." }
] }
],
"relations": [
{ "label": "PARTY_TO", "description": "An organization or person that signed a contract.", "patterns": [["Organization", "Contract"], ["Person", "Contract"]], "properties": [] }
]
},
"approved_ontology": null,
"source_file_ids": ["FILE_ID_1", "FILE_ID_2"],
"boundaries": "legal contracts",
"last_error": null,
"warnings": [],
"created_at": "2026-08-03T10:00:00Z",
"updated_at": "2026-08-03T10:05:00Z"
}The example shortens ontology for readability. A real draft also lists every type of the default ontology.
GET /v1/graph/ontology returns 404 Not Found until the project runs discovery for the first time. While state is discovering, ontology still holds the previous draft, or null on a first run.
warnings lists the discovery steps that failed without failing the whole discovery, for example a sampled chunk the LLM returned no valid proposal for, or a skipped synonym-merging pass. The draft is still usable, but it reflects less of your files. Each new propose clears the list.
Cancel Discovery
curl -X 'POST' \
'https://deepfellow-server-host/v1/graph/ontology/cancel' \
-H "Authorization: Bearer DEEPFELLOW_PROJECT_API_KEY"Cancelling sets state to cancelled and returns the ontology record. It returns 409 Conflict if state is not discovering. A queued discovery that has not started yet is skipped. A discovery that is already running is not interrupted: once it finishes, it still stores its draft and sets state to draft. To start over, propose again.
Discovery Failures
The server retries each LLM call that fails, for example on a rate limit or a temporary DeepFellow Infra error, up to graph.ontology_discovery_llm_call_attempts times, waiting 1, 2, 4, and more seconds between attempts.
A discovery fails in the following cases:
- A sample file does not belong to the project or cannot be converted.
- An LLM call still fails after its last attempt.
- No text could be sampled from the sample files.
- Every sampled chunk failed to produce a proposal. Discovery then has nothing to add to the starting ontology.
state becomes failed, and last_error holds the error. Nightshift first retries discovery with backoff until it reaches graph.ontology_discovery_max_attempts deliveries. After that, propose again to retry.
Edit the Draft
Replace the draft with an edited ontology:
curl -X 'PUT' \
'https://deepfellow-server-host/v1/graph/ontology/draft' \
-H "Authorization: Bearer DEEPFELLOW_PROJECT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"ontology": {
"entities": [
{ "label": "Contract", "description": "A legally binding agreement between parties.", "properties": [] },
{ "label": "Organization", "description": "A company, agency, team, or other named group.", "properties": [] }
],
"relations": [
{ "label": "PARTY_TO", "description": "An organization that signed a contract.", "patterns": [["Organization", "Contract"]], "properties": [] }
]
}
}'Send the complete ontology every time. The request replaces the draft as a whole, so any type you leave out is removed from it.
Saving a draft makes it the active ontology at once: the server writes it to both ontology and
approved_ontology, and the next ingested document already uses it. Approval is not required for
an edit to take effect.
Before the server stores the draft, it runs the same compatibility check as approval, so an edit never breaks ingestion or search. The endpoint returns the updated ontology record with state set to draft, or one of these errors:
| Status | Cause |
|---|---|
404 Not Found | The project never ran discovery. Propose first. |
409 Conflict | state is discovering, approved, or cancelled. Only a draft or failed ontology accepts edits. |
409 Conflict | The draft changes the type of a property FalkorDB already holds for the project. |
422 Unprocessable Entity | The body is not a valid Ontology, for example a property with an unknown type. |
To change an approved ontology, propose again, then edit and approve the new draft.
Approve the Draft
curl -X 'POST' \
'https://deepfellow-server-host/v1/graph/ontology/approve' \
-H "Authorization: Bearer DEEPFELLOW_PROJECT_API_KEY"Approval runs the compatibility check, copies the draft to approved_ontology, sets state to approved, and returns the ontology record. It returns 404 Not Found if the project never ran discovery, and 409 Conflict if state is not draft or if the check fails.
Since a saved draft is already active, approval mostly marks the ontology as final. After approval, the draft no longer accepts edits until the next propose.
Compatibility with Ingested Data
FalkorDB keeps a copy of each project's ontology, which grows as the project ingests documents. Saving or approving a draft compares the draft against that copy:
- Additive changes are applied. A new entity type, relation type, property, or relation pattern is written to FalkorDB before the draft becomes active.
- Removals are allowed. A type that the draft leaves out stays in FalkorDB and in the existing graph, but is no longer extracted.
- Retyped properties are rejected. A property that keeps its name but changes its
typeon an existing entity or relation type, for examplefounded_yearfromINTEGERtoSTRING, would leave the data already in the graph inconsistent with the new schema. The request returns409 Conflict, and nothing changes.
A project that has not ingested anything yet passes the check with any draft. The 409 Conflict error message lists every conflict, for example:
{
"error": {
"message": "Ontology conflicts with the already-registered ontology: Entity 'Organization': property 'founded_year' type change INTEGER -> STRING not allowed.",
"type": "invalid_request_error",
"param": null,
"code": null
}
}To resolve a conflict, keep the property's original type, or give the property a new name.
Draft Cleanup
When discovery finishes, the server cleans up the draft before it stores it.
Draft Sanitization
Discovery LLMs occasionally propose a value they saw in the text, such as John Smith or 1976, as a property name. The server drops every property whose name does not match ^[a-z][a-z0-9_]*$: a lowercase letter followed by lowercase letters, digits, or underscores. It sanitizes the draft when discovery finishes, when you save a draft, and when you approve one, and it drops such properties silently, without an error.
Near-Duplicate Types
A new type whose name differs from an existing one only by letter case, punctuation, or a plural ending, such as Organizations or organization next to Organization, is merged into the existing type. Its properties and relation patterns move to the existing type. The types of the starting ontology always keep their names and are never merged into each other, even if two of them differ only this way. Between two new types, the first one discovery proposed keeps its name. The check covers regular English plurals only, so People is not merged into Person.
Definitions of Known Types
GraphRAG-SDK's synonym-merging pass sometimes rewrites the descriptions of types the starting ontology already has, or changes the type of their properties. The server restores the original descriptions, since the extraction prompts rely on them, and the original property types and descriptions, so approving the draft never fails the compatibility check on a property you did not change. New properties that discovery adds to a known type stay in the draft.
Limits on New Types
If graph.ontology_discovery_max_new_entities or graph.ontology_discovery_max_new_relations is above 0, the draft keeps at most that many new entity or relation types. Types of the starting ontology never count toward the limit. The server keeps the new entity types that the most relation patterns reference, and the new relation types with the most patterns. Relation patterns that reference a dropped entity type are removed, and a new relation type left with no patterns is dropped too.
Use the Web UI
Open the project details page and click Ontology in the Toolboxes section header. The page has three parts:
- Choose sample files. Select up to 10 of the project's files, enter optional Boundaries, and click Propose. While discovery runs, the page refreshes by itself and shows a Cancel button. If you have unsaved edits, the page asks for confirmation first, since the new draft will overwrite them.
- Review & edit draft. Add, edit, and remove entities, relations, patterns, and properties. Save draft saves your edits and makes them active at once. Approve is enabled only for a saved draft in the
draftstate. - Approved ontology. Lists the labels of the active ontology, and notes when a newer draft exists.
Configuration
These are dynamic settings. See Knowledge Graph Settings for the full list.
| Setting | Description | Default |
|---|---|---|
graph.ontology_discovery_llm_model | Model on DeepFellow Infra that drafts ontologies. Independent of graph.llm_model. | gpt-4o-mini |
graph.ontology_discovery_sample_chunks_per_doc | Number of chunks sampled from each sample file. | 3 |
graph.ontology_discovery_concurrency | Number of LLM calls discovery runs in parallel. | 4 |
graph.ontology_discovery_max_sample_files | Maximum number of sample files per discovery. Values above 10 have no effect. | 10 |
graph.ontology_discovery_max_attempts | Nightshift deliveries a discovery gets before it is marked failed. | 3 |
graph.ontology_discovery_llm_call_attempts | Attempts each discovery LLM call gets before it fails. At least 1. | 4 |
graph.ontology_discovery_max_new_entities | Maximum number of new entity types one discovery adds. 0 means no limit. | 0 |
graph.ontology_discovery_max_new_relations | Maximum number of new relation types one discovery adds. 0 means no limit. | 0 |
We use cookies on our website. We use them to ensure proper functioning of the site and, if you agree, for purposes such as analytics, marketing, and targeting ads.