graphrag_sdk.
Table of Contents
- GraphRAG (Facade)
- Connection
- Providers
- Data Models
- Schema
- Ingestion Strategies
- Ingestion Pipeline
- Structured Ingestion
- Retrieval Strategies
- Reranking Strategies
- Storage
- Context
- Exceptions
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()
.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(resolver=...). 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()
Returns:
RetrieverResult
completion()
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.
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()
params ($name) for values, never string formatting.
drop_table()
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()
judge=False); finalize() runs it by default (judge=True).
- Phase 1 (always): Canonical name match — case, accents, punctuation, inner dots (
A.I.=AI), a leading English article,Surname, Giveninversion, common abbreviations and one trailing legal form (Ltd,Inc) are normalised; word order is not. An acronym joins its unique same-label long form. Keeps the survivor ranked by declared key, real-over-placeholder, long form over acronym, degree, then description length; merges the duplicate’s description (" | "-joined, members kept indescriptions),aliases,source_chunk_idsand any property the survivor lacks into it; remaps RELATES and MENTIONED_IN edges; deletes duplicates. - Phase 2 (optional,
fuzzy=True): Embedding-based — embeds entity names, finds near-duplicates by cosine similarity. - Phase 3 (optional,
resolver=): AResolutionStrategyjudges the remaining pairs across documents and tables.finalize()passes one by default. - Phase 4 (opt-in here with
judge=True; on by default infinalize()): LLM-judged cross-document dedup — name/description embeddings and name-in-description nominate candidate pairs, dense sets of ≤ 8 (more than half of a set’s pairs must be nominated, so a chain A–B–C–D cannot form), the judge partitions each set; withjudge_vote=True(default) a second shuffled pass must agree, and pairs only one pass grouped becomeSAME_ASedges (agreement=1); withjudge_vote=Falsethe single pass decides and nothing is linked for disagreement — only a pair that reached across two sets is linked rather than merged, stamped with the number of passes that saw it (agreement=2with the vote,1without). Agreed pairs merge by the same rules as every other phase (the survivor additionally gains every member’s label, recorded inmerged_labels); pairs a resolver remembered asDISTINCT_FROM, two keyed rows of a table, and a mention two such rows could own are never asked. Agreements are unioned within one set only, so two sets sharing a member never chain into one merge. Description vectors are cached on the node (description_embedding, keyed bydescription_embedding_hashon the text, the embedder’smodel_nameand the vector’s dimension), so a later run embeds only descriptions that are new, changed, or embedded by another model; a cached vector whose dimension differs from the ones embedded now is re-embedded, with a warning. If theDISTINCT_FROMpairs cannot be read, the phase is skipped with a warning andlast_judge_statsholdsskipped_reasoninstead of counts.judge_llmdefaults to the GraphRAG LLM (gpt-4.1-class recommended); given withoutjudge=Trueit is ignored, with a warning. Without aresolverand withjudgeoff — the defaults of this method — no model is called. Stats in_deduplicator.last_judge_stats.
finalize()
deduplicate_entities(resolver=..., judge=..., judge_llm=..., judge_vote=...)— canonical-name dedup, then the resolver across sources, then the LLM-judged cross-document phase (which embeds and stores the names it reads)backfill_entity_embeddings()— name embeddings on every entity still missing one, after dedup so a duplicate about to be removed is not embedded first;entities_embeddedcounts this step’s vectors plus the judge’sembed_relationships()— fact text embeddings on RELATES edgesensure_indices()— all indexes- A consistency report over the whole graph
Returns:
FinalizeResult — counts entities_deduplicated, entities_linked (judge SAME_AS edges), judge_llm_calls, judge_stats, entities_embedded, relationships_embedded, null_stubs_removed, indexes, and the report: resolved_duplicates, rejected_duplicates, probable_duplicates (spellings the canonical rule declined to merge, with the reason), unmerged_name_collisions (one name under two declared labels), 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
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 extendDataModel (Pydantic BaseModel with extra="allow").
GraphNode
GraphRelationship
GraphData
TextChunk
TextChunks
DocumentInfo
DocumentOutput
IngestionResult
RagResult
RetrieverResult
RetrieverResultItem
ResolutionResult
ChatMessage
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()
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)
TextLoader(encoding="utf-8"), PdfLoader()
ChunkingStrategy (ABC)
FixedSizeChunking(chunk_size=1000, chunk_overlap=100)
ExtractionStrategy (ABC)
GraphExtraction(llm, *, entity_extractor=None, coref_resolver=None, entity_types=None, relation_types=None, max_concurrency=None)—entity_types=NoneselectsDEFAULT_ENTITY_TYPES;relation_types=NoneselectsDEFAULT_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. Passrelation_types=[]for open-vocabulary labels. Both are overridden by a declaredOntology, whose relations are enforced.
GraphExtraction):
GLiNERExtractor(threshold=None, model_name=None, window_tokens=None, window_overlap=48, candidate_threshold=<25 % below threshold>)— default, local NER.model_name=Noneselectsurchade/gliner_medium-v2.1;threshold=Noneselects the measured per-model value (0.75 for that model). Spans scoring betweencandidate_thresholdandthresholdare returned as"Unknown"; passcandidate_threshold=Noneto disable. Chunks longer thanwindow_tokens(derived from the model’smax_lenwhenNone) are processed as overlapping windows.LLMExtractor(llm, threshold=0.75)— LLM-based NER- Subclass
EntityExtractorfor custom backends
ResolutionStrategy (ABC)
ExactMatchResolution(llm=None, resolve_property="name", cross_label_merge=True)—ingest()’s default isExactMatchResolution(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 atingest();finalize()’s default cross-source resolver
Ingestion Pipeline
Structured Ingestion
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.
Link
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 byingest() 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()anddrop_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=Trueand reported byfinalize().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 aTableMappingto replace it, ordrop_table()to refuse it. - Ontology registration is additive. Declared types reach the ontology so text-to-Cypher sees
ageas anINTEGER; 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.