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 computed each run and never written to the graph (any vectors an earlier build left on entity nodes are removed). 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.