Skip to main content
Complete reference for all public classes and methods exported by graphrag_sdk.

Table of Contents


GraphRAG (Facade)

The main entry point. Three primary operations: ingest(), retrieve(), and completion(). Tables go through the same ingest(); see Structured Ingestion.

Constructor

Public attributes: llm, embedder, schema, graph_store, vector_store

ingest()

Build a knowledge graph from one or more sources. Auto-detects loader from file extension. When a list of sources is provided, documents are ingested in parallel with bounded concurrency. A .csv, .tsv, .psv or .tab is read as records, not prose: its mapping is looked up in the ontology by filename (see Structured Ingestion), no model is called, and a StructuredIngestionResult is returned. A table may not appear in a list — each is written on its own — and chunker/extractor/resolver are rejected for one; identity across sources is judged by finalize(). Pass loader=TextLoader() to read a CSV as prose anyway. Returns: IngestionResult for a single document, StructuredIngestionResult for a table, list[IngestionResult | Exception] (aligned by index) for multiple sources.

retrieve()

Retrieve context from the knowledge graph without generating an answer. Use this to inspect retrieved context or pass it to your own LLM. Returns: RetrieverResult

completion()

Full RAG pipeline: retrieve context and generate an answer. When history is provided, messages are passed natively to the LLM provider’s multi-turn chat API. Returns: RagResult Conversation history: History accepts a list of ChatMessage objects or plain dicts with role and content keys. Supported roles: "system", "user", "assistant". Invalid roles raise ValueError.
When history is provided, completion() builds a native messages list: [system_prompt, *history, user_question] and calls LLMInterface.ainvoke_messages(). Without history, it uses the single-turn ainvoke() path.

query()

Run a Cypher query against your own graph and return its rows, each a list of column values. The way to check what an ingest wrote or to aggregate over declared columns yourself. Runs exactly the Cypher you pass with no rewriting and no read-only enforcement — use params ($name) for values, never string formatting.

drop_table()

Take a table out of the graph: its Document and record chunks (and any entity only those rows mentioned), every property it signed on entities that survive, the identity it gave them (entity_key, is_stub — unless another table still keys the node), and its stored mapping, so the next ingest() of that filename is proposed a mapping afresh. The label stays in the ontology; drop_entity() removes a label nothing uses. source is matched on its basename, like ingest(). Raises: ValueError when no table of that name is in the ontology.

deduplicate_entities()

Post-ingestion entity deduplication across all documents. Every run has a judge: judge is any DedupJudge, and None is LLMJudge with the instance’s llm. Anything else — judge=False among them — raises TypeError before any work. It is incremental: only the pending entities are checked — those without a name vector (ingest writes entities without one, and marks nothing on them) and those a pending system record lists — against the whole graph, through the __Entity__ vector index, never by reading every entity. A run lists an entity before it embeds it and clears it once its check completes, so a second call with nothing pending does no work. An entity whose check did not complete — a failed lookup or judge call, an answer that was not a clean partition, a group held for one, a merge an update() changed — stays pending for the next run; after 3 judged runs without a usable verdict it is unresolved: reported, left out of every set, never merged automatically. dedup_all=True checks every entity instead, and retries the unresolved ones. It is crash-safe: every merge is decided before any is written, written as a sealed plan, and applied one atomic query per merge, each ticking itself off, so an interrupted call is finished by the next one. One call per graph at a time (a second raises DedupRunActiveError); update() and deletes may run meanwhile. Like every SDK connection, it refuses a FalkorDB server older than 4.20 at its first query (FalkorDBVersionError).
  • Phase 1 (always): Canonical name match between the pending entities and their nearest names from the vector index — case, accents, punctuation, inner dots (A.I. = AI), a leading English article, Surname, Given inversion, common abbreviations and one trailing legal form (Ltd, Inc) are normalised; word order is not. An acronym joins its unique same-label long form. A group holding a table row merges here, by rule; a group of extracted mentions only goes to the judge (Phase 2): the same spelling is evidence, not proof. Keeps the survivor ranked by declared key, real-over-placeholder, long form over acronym, degree, description length, then name length; merges the duplicate’s description (" | "-joined, members kept in descriptions), aliases, source_chunk_ids and any property the survivor lacks into it, and moves every relationship of every type to it, in the same query that deletes the duplicate.
  • Phase 2: judged cross-document dedup — each pending entity’s 5 nearest names from the vector index (cosine ≥ 0.5) and Phase 1’s same-spelling mention pairs and ambiguous acronym/long-form pairs nominate candidates, dense sets of ≤ 8 (more than half of a set’s pairs must be nominated, so a chain A–B–C–D cannot form one set; agreements in separate sets still chain, see below), and the judge partitions each set once, reading names, labels, existing aliases, descriptions and the values of the attributes the ontology declares, and names each merged group. Agreed pairs merge by the same rules as Phase 1 (the survivor additionally gains every member’s label, recorded in merged_labels, and takes the judge’s name when it is one of the members’ names or aliases, except a table row, which keeps its declared name); agreements chain across sets, but never into a group holding a pair the judge kept apart. A “different” answer writes nothing. Every judge’s answer is checked; a set without a usable answer is asked again (up to 2 more times; LLMJudge asks it on its own); a group such a set could still change is held, not merged. Two keyed rows of a table, and a mention two such rows could own, are never asked. Descriptions are not embedded. For LLMJudge a gpt-4.1-class model is recommended. A run with nothing pending asks the judge nothing. Stats in _deduplicator.last_judge_stats.
Call once after all documents are ingested. Returns: Number of duplicate entities merged.

finalize()

Run all post-ingestion steps after all documents and tables are ingested. Bundles:
  1. Remove NULL-name stub entities left by legacy writes (only nodes no mapping wrote)
  2. deduplicate_entities(judge=..., dedup_all=...) — incremental and crash-safe: the pending entities (added since the last dedup run, or left pending by it) get canonical-name and acronym dedup, then one judge pass over their nearest names. Dedup writes every entity’s name embedding, listing the entity first; entities_embedded counts them
  3. embed_relationships() — fact text embeddings on RELATES edges
  4. ensure_indices() — all indexes
  5. A consistency report over the whole graph
Returns: FinalizeResult — counts entities_deduplicated, judge_calls, judge_stats, dedup_pending (entities still owed a check), dedup_unresolved (entities no longer retried, with the reason), dedup_merges_changed (planned merges skipped because an update() changed their entities), dedup_collisions (merges whose entities held different values for one property: which value stayed, by the judge’s choice or the survivor’s, and which went to <property>__dropped), descriptions_summarised (entities whose descriptions were replaced by one LLM summary), description_summary_failures (entities whose summary failed, with the error, or the failed pass; tried again by the next run), entities_embedded, relationships_embedded, null_stubs_removed, indexes, and the report: probable_duplicates (spellings the canonical rule declined to merge and the judge did not, plus mentions two keyed rows could own, with the reason; pairs involving the entities this run checked), unmerged_name_collisions (a name under more than one label), property_conflicts (a property two tables both supply, with how many entities hold differing values; every value is kept, none resolved for you), unresolved_references (placeholders whose owning table has not arrived), entities_without_a_name, proposed_mappings (tables loaded on a proposed, not declared, mapping), mapping_changed, stale_signed_properties. Everything non-empty is also logged at WARNING.

Sync Wrappers

Convenience methods that run the async versions in asyncio.run().

Connection

ConnectionConfig

FalkorDBConnection


Providers

LLMInterface (ABC)

ainvoke_messages() is used by completion() when conversation history is provided. The default implementation concatenates messages into a single prompt string and calls ainvoke(), so custom providers work without changes. LiteLLM and OpenRouterLLM override this with native multi-turn implementations.

Embedder (ABC)

LLMBatchItem

LiteLLM

LiteLLMEmbedder

OpenRouterLLM

OpenRouterEmbedder


Data Models

All models extend DataModel (Pydantic BaseModel with extra="allow").

GraphNode

GraphRelationship

GraphData

TextChunk

TextChunks

DocumentInfo

DocumentOutput

IngestionResult

RagResult

RetrieverResult

RetrieverResultItem

ResolutionResult

ChatMessage

Validated message type for multi-turn conversations. Used by completion(history=...) and LLMInterface.ainvoke_messages(). Invalid roles raise a validation error on construction. LLMMessage is a backward-compatible alias for ChatMessage.

LLMResponse

SearchType

Extraction Models

compute_entity_id()

Deterministic entity ID from normalized name and optional type. When entity_type is provided, appends a __type suffix to prevent cross-type collisions (e.g. paris__person vs paris__location). Without entity_type, returns just the normalized name for backwards compatibility.

Schema

EntityType

RelationType

PropertyType

GraphSchema


Ingestion Strategies

LoaderStrategy (ABC)

Built-in: TextLoader(encoding="utf-8"), PdfLoader()

ChunkingStrategy (ABC)

Built-in: FixedSizeChunking(chunk_size=1000, chunk_overlap=100)

ExtractionStrategy (ABC)

Built-in:
  • GraphExtraction(llm, *, entity_extractor=None, coref_resolver=None, entity_types=None, relation_types=None, max_concurrency=None) — entity_types=None selects DEFAULT_ENTITY_TYPES; relation_types=None selects DEFAULT_RELATION_TYPES, a 31-label UPPER_SNAKE_CASE vocabulary (LOCATED_IN, PART_OF, EMPLOYED_AT, AUTHORED, …) the step-2 prompt asks the LLM to prefer; off-list labels are still kept. Pass relation_types=[] for open-vocabulary labels. Both are overridden by a declared Ontology, whose relations are enforced.
Entity Extractors (step 1 backends for GraphExtraction):
  • GLiNERExtractor(threshold=None, model_name=None, window_tokens=None, window_overlap=48, candidate_threshold=<25 % below threshold>) — default, local NER. model_name=None selects urchade/gliner_medium-v2.1; threshold=None selects the measured per-model value (0.75 for that model). Spans scoring between candidate_threshold and threshold are returned as "Unknown"; pass candidate_threshold=None to disable. Chunks longer than window_tokens (derived from the model’s max_len when None) are processed as overlapping windows.
  • LLMExtractor(llm, threshold=0.75) — LLM-based NER
  • Subclass EntityExtractor for custom backends

ResolutionStrategy (ABC)

Built-in:
  • ExactMatchResolution(llm=None, resolve_property="name", cross_label_merge=True) — ingest()’s default is ExactMatchResolution(llm=None, cross_label_merge=False)
  • LLMVerifiedResolution(llm=None, embedder=None, *, hard_threshold=0.95, soft_threshold=0.65, max_llm_pairs=500, max_llm_concurrency=None, force_summary_threshold=3, max_summary_tokens=500, ann_top_k=50, batch_verification=True, verification_token_budget=4000, verification_max_pairs_per_call=5, cross_label_merge=True, cross_label_vector_door=0.60, cross_label_rank_floor=0.55, cross_label_max_pairs=200, cross_label_min_descriptions=3, unified_stage=True, unified_threshold=0.65, label_family_gate=True, cross_label_vote=True) — opt-in at ingest(), per document; cross-document dedup is finalize()’s judge

Ingestion Pipeline


Structured Ingestion

A table is declared on the ontology, next to the entity types, and then ingested through the ordinary ingest(). No model is involved in writing it: identity comes from a declared key, every property type is declared, and the same file always produces the same graph. A row and a prose mention of the same thing share one node from the first write. The full guide is Structured Ingestion; the runnable version is examples/11_structured_ingestion.py.

TableMapping

MappingError is raised for a declaration that does not fit — an unknown column, a cell that does not parse as its type, a label the sanitiser would rewrite — and the graph is left untouched.

Column

COLUMN_TYPES: STRING, INTEGER, FLOAT, BOOLEAN, DATE, LIST. Types are declared, never sniffed. INTEGER and FLOAT accept 1,234.56 and 1.234,56; FLOAT refuses nan/inf; LIST is parsed as a CSV row. A blank cell writes no property.
Written as a RELATES edge with rel_type=type, like every data edge. The target is keyed, never named: a missing target is created as a placeholder (is_stub: true) that is renamed in place — edges intact — when the owning table arrives; an existing target, another table’s row or an entity a document mentioned, only gains the key. A link can therefore never overwrite what the owning source wrote.

Record loaders

CsvRecordLoader is the default; delimiter=None sniffs comma, semicolon, tab or pipe from the first kilobyte, and the default encoding reads UTF-8 with or without the byte-order mark Excel writes. The file is read once before anything is written: a row with more fields than the header — an unquoted delimiter inside a cell — is refused with its row number rather than loaded shifted. Implement RecordLoaderStrategy for another format and pass it as ingest(..., record_loader=...).

StructuredIngestionResult

Returned by ingest() for a table (graphrag_sdk.ingestion.structured_pipeline.StructuredIngestionResult).

How a table is written

  • Re-ingesting is a re-sync. A table is a snapshot: a deleted row is removed, a blanked cell loses its column, a moved foreign key leaves its old target, and a node another source still mentions survives with the table’s signed columns and identity retracted. The content hash covers the mapping, so re-declaring a type rewrites the source.
  • A Document records how it was written, so update() cannot re-read a table as prose or a document as records. update(), delete_document() and drop_table() all take the table’s name.
  • No mapping declared? With a model, one is proposed from the measured columns, the first rows and the current ontology, held to the data (unique key, existing columns, known link targets), stored as derived=True and reported by finalize().proposed_mappings. Without a model the file is read as-is: the label from the filename, the leftmost unique-and-complete column as key, no name column. Declare a TableMapping to replace it, or drop_table() to refuse it.
  • Ontology registration is additive. Declared types reach the ontology so text-to-Cypher sees age as an INTEGER; an existing label is extended, not redeclared, and a type contradiction raises.

Retrieval Strategies

RetrievalStrategy (ABC)

Uses the Template Method pattern.
set_ontology() is called by the facade whenever the working ontology changes — an evolution call, a table declaring typed columns — so a strategy that generates Cypher always sees the current schema. A no-op by default; MultiPathRetrieval overrides it.

LocalRetrieval

MultiPathRetrieval


Reranking Strategies

RerankingStrategy (ABC)

CosineReranker


Storage

GraphStore

VectorStore


Context

Execution context for logging and budget tracking.

Exceptions