An embeddable C# library for building temporally-aware knowledge graphs for AI agents, with
behavior derived from Graphiti's Python graphiti_core. It
ingests episodes (chat messages, JSON, free text), extracts entities and facts with an LLM,
deduplicates them against what it already knows, and answers questions with hybrid search over the
resulting graph.
This is our own library, maintained by us and consumed in-process as a subsystem of a larger system — not a native binding and not a redistribution of the Python project. The Python library is the functional contract: the C# wire shape (snake_case JSON), prompt identity, ranking semantics, and cache keys mirror Python's behavior, and that parity is enforced by tests. Beyond that, the code is idiomatic modern C#.
Status: functionally complete and validated end to end against a real OpenAI provider. It is used as an embedded library (referenced from source / consumed in-process), not distributed on NuGet — that is a deliberate choice, not a gap, and the assembly name may change.
Graphiti.Coreincludes the deterministic InMemory reference driver and the first-class LadybugDB driver; restores that include LadybugDB use thesergey-v9/ladybug-dotnetGitHub Packages feed. See Reference / consume. The active development focus is idiomatic-C# and allocation/GC modernization of the code, not release packaging.
- Why Graphiti
- Reference / consume
- Quickstart
- Using a real provider (OpenAI)
- Dependency injection
- Observability
- Drivers
- Search
- Custom entity & edge types
- The temporal model
- Samples & evaluation
- Building & verifying
- Project layout
- Relationship to Python Graphiti
- License
- Bi-temporal facts. Every fact (an
EntityEdge) tracks when it became true (valid_at), when it stopped being true (invalid_at), and when it was superseded in the graph (expired_at). New information that contradicts an existing fact invalidates it instead of overwriting it, so the graph keeps an auditable history. See The temporal model. - Hybrid search. Retrieval combines semantic similarity (embeddings), keyword search (BM25), and graph traversal, then fuses and reranks the candidates (reciprocal rank fusion, MMR, cross-encoder, node-distance, episode-mentions). See Search.
- Custom ontologies. Supply your own entity and edge types so extraction populates typed attributes you define. See Custom entity & edge types.
- Communities & sagas. Build communities (clusters of related entities with summaries) and sagas (ordered chains of episodes) on top of the base graph.
- Incremental. Each
AddEpisodeAsyncintegrates new data without recomputing the whole graph.
This is an embedded library, consumed in-process — not distributed on NuGet (a deliberate choice for an
internal subsystem). Reference it from source by adding a project reference to
src/Graphiti.Core/Graphiti.Core.csproj, exactly as the samples do:
<ItemGroup>
<ProjectReference Include="path/to/csharp/src/Graphiti.Core/Graphiti.Core.csproj" />
</ItemGroup>The library targets net10.0.
Graphiti.Core carries the graph-driver contract, the deterministic InMemory reference/test driver,
and the first-class LadybugDB backend. The LadybugDB code lives under
src/Graphiti.Core/Drivers/Ladybug/, and the same package exposes LadybugDbOptions,
AddLadybugDbGraphDriver, and LadybugDbGraphDriverFactory.
Graphiti.Core depends on the LadybugDB / LadybugDB.Native packages. On this branch those
packages are restored from the sergey-v9/ladybug-dotnet GitHub Packages feed configured in
NuGet.config:
<add key="github_ladybug" value="https://nuget.pkg.github.com/sergey-v9/index.json" />GitHub Packages requires credentials with read:packages for this feed. A plain dotnet restore
without credentials will fail to resolve LadybugDB. Keep credentials out of the repository and
supply them through user-level NuGet config or the NuGet environment variable for the source name:
$env:NuGetPackageSourceCredentials_github_ladybug = "Username=sergey-v9;Password=<PAT_WITH_read:packages>"Even InMemory-only consumers restore these native packages, although they do not load the LadybugDB runtime unless a Ladybug driver is constructed. The current pinned Ladybug package family is:
LadybugDB 0.17.1-dev.2.1.g53e5ab5
LadybugDB.Native 0.17.1-dev.2.1.g53e5ab5
Those packages were published by the fork's dev branch workflow at
sergey-v9/ladybug-dotnet; future binding repairs can be pushed there and consumed by bumping the
central versions in Directory.Packages.props.
Graphiti Core validates the LadybugDB driver on win-x64 through the full verifier and on linux-x64
through the gated fts + vector extension smoke. The LadybugDB native package family also ships
linux-arm64 and macOS assets, but those RIDs are not Graphiti-validated yet.
The smallest end-to-end example: construct Graphiti with the deterministic in-memory driver,
build indices, ingest an episode, and search. This uses the built-in default clients (a no-op LLM, a
deterministic hash embedder, and an identity cross-encoder), so it runs with no API key — but
because there is no real LLM, extraction produces no entities or facts. It is the right shape to start
from; swap in a real provider (next section) to get real extraction.
For a runnable no-key sample that creates a useful fact directly, see
samples/Graphiti.Sample.Quickstart.
using Graphiti.Core;
using Graphiti.Core.Drivers;
using Graphiti.Core.Models;
await using var graphiti = new Graphiti(
graphDriver: new InMemoryGraphDriver("quickstart"),
maxCoroutines: 2);
// Create the indices/constraints once on a fresh database.
await graphiti.BuildIndicesAndConstraintsAsync(deleteExisting: true);
// Ingest an episode.
await graphiti.AddEpisodeAsync(
name: "Intro",
episodeBody: "User: My name is Maya Patel. I manage the Atlas migration project at Nimbus Health.",
sourceDescription: "chat transcript",
referenceTime: DateTime.UtcNow,
source: EpisodeType.Message,
groupId: "quickstart");
// Retrieve the most relevant facts for a query.
var facts = await graphiti.SearchAsync(
"Who manages Atlas?",
groupIds: new[] { "quickstart" },
numResults: 5);
foreach (var edge in facts)
{
Console.WriteLine(edge.Fact);
}The public constructor (from src/Graphiti.Core/Graphiti.cs) accepts the clients you want to supply
and defaults the rest:
public Graphiti(
ILlmClient? llmClient = null, // defaults to a no-op client
IEmbedderClient? embedder = null, // defaults to a deterministic hash embedder
ICrossEncoderClient? crossEncoder = null, // defaults to an identity reranker
bool storeRawEpisodeContent = true,
IGraphDriver? graphDriver = null, // see driver selection below
int? maxCoroutines = null, // optional cap on concurrent operations
TimeProvider? timeProvider = null,
ILogger<Graphiti>? logger = null,
string database = "");Driver selection by precedence: an explicit graphDriver is used as-is (recommended); otherwise it
defaults to an in-memory InMemoryGraphDriver. So new Graphiti() and
new Graphiti(llmClient: x, embedder: y) work out of the box on the deterministic reference driver.
Pass a LadybugDB driver (via AddLadybugDbGraphDriver() / LadybugDbGraphDriverFactory) for
first-class persistent backend work, or an InMemory driver for tests and ephemeral graphs.
Real entity/fact extraction needs a real LLM, embedder, and (for cross-encoder reranking) a chat model.
Graphiti's client contracts (ILlmClient, IEmbedderClient, ICrossEncoderClient) are adapted over
Microsoft.Extensions.AI. The wiring below
is copied from the canonical sample at samples/Graphiti.Sample.OpenAI/Program.cs.
Add the OpenAI integration package to your project:
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.6.0" />using Graphiti.Core;
using Graphiti.Core.CrossEncoder;
using Graphiti.Core.Drivers;
using Graphiti.Core.Embedding;
using Graphiti.Core.LlmClients;
using Microsoft.Extensions.AI;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException("Set OPENAI_API_KEY.");
var chatModel = "gpt-4.1-mini";
var embeddingModel = "text-embedding-3-small";
var embeddingDimensions = 1536;
// Microsoft.Extensions.AI adapters over the OpenAI SDK.
var chatClient = new OpenAI.Chat.ChatClient(chatModel, apiKey).AsIChatClient();
var embeddingGenerator = new OpenAI.Embeddings.EmbeddingClient(embeddingModel, apiKey)
.AsIEmbeddingGenerator(embeddingDimensions);
// Graphiti-facing clients. Temperature 0 keeps extraction deterministic.
var llmClient = new MicrosoftExtensionsAIChatClient(
chatClient,
new LlmConfig
{
Model = chatModel,
SmallModel = chatModel,
Temperature = 0
});
var embedder = new MicrosoftExtensionsAIEmbedderClient(
embeddingGenerator,
embeddingDimensions,
modelId: embeddingModel,
batchSize: 16,
batchConcurrency: 2);
var crossEncoder = new MicrosoftExtensionsAICrossEncoderClient(
chatClient,
new LlmConfig
{
Model = MicrosoftExtensionsAICrossEncoderClient.DefaultModel, // "gpt-4.1-nano"
SmallModel = MicrosoftExtensionsAICrossEncoderClient.DefaultModel,
Temperature = 0,
MaxTokens = 64
});
await using var graphiti = new Graphiti(
llmClient: llmClient,
embedder: embedder,
crossEncoder: crossEncoder,
graphDriver: new InMemoryGraphDriver("sample-openai"),
maxCoroutines: 2);
await graphiti.BuildIndicesAndConstraintsAsync(deleteExisting: true);
// ... AddEpisodeAsync / SearchAsync as in the Quickstart.The sample reads optional model overrides from environment variables: OPENAI_CHAT_MODEL,
OPENAI_SMALL_MODEL, OPENAI_RERANKER_MODEL, OPENAI_EMBEDDING_MODEL, and
OPENAI_EMBEDDING_DIMENSIONS. gpt-5-family reasoning models require temperature = 0; the client
handles that automatically.
For ASP.NET Core / generic-host apps, register Graphiti with the extensions in
Graphiti.Core.Configuration. Register your own Microsoft.Extensions.AI chat client and embedding
generator in the container and Graphiti picks them up; if none are registered it falls back to the
built-in no-op/deterministic clients.
using Graphiti.Core.Configuration;
using Graphiti.Core.Drivers;
using Microsoft.Extensions.AI;
// Your provider clients (e.g. OpenAI), registered as M.E.AI abstractions:
services.AddSingleton<IChatClient>(_ =>
new OpenAI.Chat.ChatClient("gpt-4.1-mini", apiKey).AsIChatClient());
services.AddSingleton<IEmbeddingGenerator<string, Embedding<float>>>(_ =>
new OpenAI.Embeddings.EmbeddingClient("text-embedding-3-small", apiKey)
.AsIEmbeddingGenerator(1536));
// Graphiti itself:
services.AddGraphiti(options =>
{
options.Provider = GraphProvider.InMemory; // or .LadybugDb for product backend work
options.EmbeddingDimension = 1536;
options.MaxCoroutines = 4;
});
// To use the LadybugDB driver with a persistent file:
services.AddLadybugDbGraphDriver(o => o.DatabasePath = "graphiti.db");Then resolve Graphiti from the provider (it is registered as a scoped service).
AddGraphiti also has an overload that binds from IConfiguration — it reads the Llm,
Embedding, ContentChunking, Cache, and Resilience sections in addition to GraphitiOptions:
services.AddGraphiti(configuration);(AddGraphitiCore remains as an [Obsolete] alias of AddGraphiti.)
Key option types (all under Graphiti.Core.Configuration / Graphiti.Core.LlmClients):
| Type | Purpose | Notable members |
|---|---|---|
GraphitiOptions |
Which backend and instance behavior | Provider, Database, EmbeddingDimension (default 1024), MaxCoroutines, StoreRawEpisodeContent, GraphDriverFactory |
LadybugDbOptions |
LadybugDB driver | DatabasePath (empty or :memory: = in-memory) |
LlmConfig |
Bound from the Llm section |
Model, SmallModel, Temperature, MaxTokens, ApiKey, BaseUrl |
EmbeddingConfig |
Bound from the Embedding section |
EmbeddingDimension, ModelId, BatchSize, BatchConcurrency |
GraphitiCacheOptions |
LLM response cache (HybridCache) | LlmResponseExpiration, LlmResponseLocalCacheExpiration, LlmResponseTags |
GraphitiResilienceOptions |
Polly retry/timeout/concurrency for provider calls | MaxRetryAttempts (default 3), RetryDelay, MaxRetryDelay, AttemptTimeout, ProviderConcurrencyLimit |
Use AddLadybugDbGraphDriver(...) to configure a LadybugDB database path and point the driver factory
at LadybugDB. AddLadybugDbGraphDriver(configuration) binds LadybugDbOptions from a configuration
section. Selecting GraphProvider.LadybugDb or the obsolete GraphProvider.Kuzu alias also resolves
the built-in LadybugDB driver directly, using the default in-memory LadybugDB path unless configured.
Graphiti Core emits traces through GraphitiTelemetry.ActivitySourceName and metrics through
GraphitiTelemetry.MeterName (both Graphiti.Core). Core has no exporter dependency; hosts subscribe
with OpenTelemetry and choose their exporter.
Metrics include episodes ingested, ingestion duration/result counts, search duration/result counts,
LLM token usage, and LLM cache hit/miss lookups. The OTLP wiring sample is in
samples/Graphiti.Sample.Observability, and the full signal list is in
docs/observability.md.
A graph driver implements IGraphDriver (Graphiti.Core.Drivers). Backends are identified by the
GraphProvider enum.
| Driver | GraphProvider |
Status | Notes |
|---|---|---|---|
| InMemory | InMemory |
Deterministic reference/test driver | new InMemoryGraphDriver(database). In-process, fully featured (persistence + search), ideal for tests, samples, and ephemeral graphs. Used by both samples. |
| LadybugDB | LadybugDb |
Primary provider target (built in) | The C# port's investment backend (a Kuzu-lineage embedded graph DB). Build via LadybugDbGraphDriverFactory.Create(databasePath) / .CreateInMemory(), or wire through DI with AddLadybugDbGraphDriver. See the GitHub Packages feed. |
| FalkorDB / Neptune | FalkorDb / Neptune |
Compatibility surface only | Present on the GraphProvider enum for wire compatibility with Python; not implemented as configured C# providers. |
The driver-facing provider value is GraphProvider.LadybugDb. GraphProvider.Kuzu remains as an
[Obsolete] alias (the Python-parity compatibility name) that still resolves to the LadybugDB driver.
LadybugDbGraphDriverFactory.Create(databasePath) (and the DI LadybugDbOptions.DatabasePath) take a
file path for a persistent database. An empty string or the Kuzu :memory: sentinel selects an
in-memory database. Example:
using Graphiti.Core.Drivers.Ladybug;
var driver = LadybugDbGraphDriverFactory.Create("graphiti.db"); // persisted to disk
await using var graphiti = new Graphiti(graphDriver: driver, /* clients... */);There are two SearchAsync overloads plus SearchAdvancedAsync on Graphiti
(src/Graphiti.Core/Graphiti.Search.cs).
Convenience overload — returns the most relevant facts (EntityEdges):
public Task<IReadOnlyList<EntityEdge>> SearchAsync(
string query,
string? centerNodeUuid = null,
IReadOnlyList<string>? groupIds = null,
int numResults = SearchConfiguration.DefaultSearchLimit,
SearchFilters? searchFilter = null,
IGraphDriver? driver = null,
CancellationToken cancellationToken = default);Pass groupIds to scope the search to graph partitions and numResults to cap results. When
centerNodeUuid is supplied the results are reranked by graph distance to that node; otherwise
reciprocal rank fusion is used.
var facts = await graphiti.SearchAsync(
"What is the current Atlas rollout date?",
groupIds: new[] { "sample-openai" },
numResults: 5);Config-driven overload — returns a combined SearchResults (edges, nodes, episodes,
communities) according to an explicit SearchConfig:
using Graphiti.Core.Search;
var results = await graphiti.SearchAsync(
"Who owns Atlas?",
config: SearchConfigRecipes.CombinedHybridSearchCrossEncoder,
groupIds: new[] { "sample-openai" });
// results.Edges, results.Nodes, results.Episodes, results.CommunitiesSearchConfigRecipes (Graphiti.Core.Search) provides ready-made presets matching the Python
implementation. Each is a fresh SearchConfig you can further tune (Limit, RerankerMinScore).
| Recipe family | What it searches | Reranker |
|---|---|---|
CombinedHybridSearchRrf |
edges + nodes + episodes + communities | RRF (reciprocal rank fusion) |
CombinedHybridSearchMmr |
edges + nodes + episodes + communities | MMR (diversity) |
CombinedHybridSearchCrossEncoder |
edges + nodes + episodes + communities (adds BFS) | cross-encoder (default for SearchAdvancedAsync) |
EdgeHybridSearch{Rrf,Mmr,NodeDistance,EpisodeMentions,CrossEncoder} |
facts only | as named |
NodeHybridSearch{Rrf,Mmr,NodeDistance,EpisodeMentions,CrossEncoder} |
entities only | as named |
CommunityHybridSearch{Rrf,Mmr,CrossEncoder} |
communities only | as named |
Rerankers at a glance: RRF fuses multiple ranked lists; MMR trades relevance for diversity;
cross-encoder reranks with the chat model (requires a real cross-encoder client); node-distance
biases toward a centerNodeUuid; episode-mentions favors facts mentioned in more episodes.
SearchFilters (Graphiti.Core.Search) further constrains candidates — by NodeLabels, EdgeTypes,
and temporal predicates (ValidAt, InvalidAt, CreatedAt, ExpiredAt), each an OR-of-AND-groups of
DateFilters.
EdgeTypes = [] and EdgeUuids = [] are active empty predicates that match no edges. Empty
NodeLabels and empty temporal groups are treated as no predicate; C# deliberately avoids Python's
malformed empty-fragment queries for those shapes.
See docs/search.md for more detail on methods, rerankers, and filters.
Define an ontology with EntityTypeDefinition and EntityAttributeDefinition (Graphiti.Core.Models)
and pass it to AddEpisodeAsync via the entityTypes / edgeTypes parameters (both are
IReadOnlyDictionary<string, EntityTypeDefinition> keyed by type name). Extraction is then guided by
your types and the LLM fills the typed attributes you declared.
using Graphiti.Core.Models;
var entityTypes = new Dictionary<string, EntityTypeDefinition>
{
["Person"] = new EntityTypeDefinition(
name: "Person",
description: "A named individual.",
attributes: new Dictionary<string, EntityAttributeDefinition>
{
["role"] = new EntityAttributeDefinition(
description: "The person's job title or role.",
maxLength: 120,
required: true),
["bio"] = new EntityAttributeDefinition(
description: "Short profile summary when explicitly stated.",
maxLength: 2_000),
}),
["Project"] = new EntityTypeDefinition(
name: "Project",
description: "A software or migration project."),
};
await graphiti.AddEpisodeAsync(
name: "Intro",
episodeBody: "User: Maya Patel manages the Atlas migration at Nimbus Health.",
sourceDescription: "chat transcript",
referenceTime: DateTime.UtcNow,
source: EpisodeType.Message,
groupId: "ontology-demo",
entityTypes: entityTypes);AddEpisodeAsync also accepts excludedEntityTypes, an edgeTypeMap (allowed edge types per
source/target type pair), and customExtractionInstructions (extra natural-language guidance appended
to the extraction prompt). The same entityTypes / edgeTypes parameters exist on
AddEpisodeBulkAsync. Extracted attributes land in EntityNode.Attributes /
EntityEdge.Attributes.
EntityAttributeDefinition.MaxLength overrides the default string/list length guard for that field.
Required fields (required: true) are included in the structured response schema and retained if a
provider emits an over-cap value. Optional fields are omitted from the schema's required set and are
dropped/restored according to the normal attribute merge mode when over cap.
Every fact is an EntityEdge (Graphiti.Core.Models.Edges) with three temporal markers:
valid_at(ValidAt) — the event time from which the fact is considered true.invalid_at(InvalidAt) — the event time at which the fact stopped being true, if known.expired_at(ExpiredAt) — the transaction time at which the fact was superseded in the graph.
When a newly ingested episode states something that contradicts an existing fact, Graphiti does not
delete the old fact — it sets the old fact's expired_at (and, where applicable, invalid_at) and
adds the new one, preserving an auditable history. The OpenAI sample demonstrates this: it ingests a
March 15 rollout date, then a "rollout moved to March 29" episode, and the original date fact is
invalidated while the new one becomes current. You can inspect these markers directly:
foreach (var edge in await EntityEdge.GetByGroupIdsAsync(graphiti.Driver, new[] { groupId }))
{
Console.WriteLine($"{edge.Fact}");
Console.WriteLine($" valid_at={edge.ValidAt} invalid_at={edge.InvalidAt} expired_at={edge.ExpiredAt}");
}AddTripletAsync adds a single source → edge → target fact directly (bypassing LLM extraction) and
runs the same deduplication and invalidation. RemoveEpisodeAsync(episodeUuid) removes an episode and
cleans up the graph elements only it produced (data still referenced by other episodes is retained).
-
samples/Graphiti.Sample.Quickstart— no-key "hello graph" using InMemory and directAddTripletAsync.dotnet run --project samples/Graphiti.Sample.Quickstart
-
samples/Graphiti.Sample.GenericProvider— offline non-OpenAI provider sample using localMicrosoft.Extensions.AIchat and embedding implementations behind Graphiti's adapters.dotnet run --project samples/Graphiti.Sample.GenericProvider
-
samples/Graphiti.Sample.Observability— OTLP host wiring sample for Graphiti traces and metrics. SetOTEL_EXPORTER_OTLP_ENDPOINTwhen sending to a collector.dotnet run --project samples/Graphiti.Sample.Observability
-
samples/Graphiti.Sample.OpenAI— ingests a small fixture (the "Atlas rollout" story), prints the extracted entities and facts with their temporal markers, and runs a few searches.$env:OPENAI_API_KEY = "..." dotnet run --project samples/Graphiti.Sample.OpenAI
-
samples/Graphiti.Eval— an evaluation harness with two modes:- default — graph-building regression eval. Establishes a persisted baseline on first run, then judges (via an LLM) whether a later candidate extraction is worse than the baseline.
--qa— retrieval-QA eval. For each gold (question, answer) pair it retrieves facts, forms an answer from the top-1 fact, and scores it with an LLM judge. Includes a distractor question whose answer is absent from the fixture, to surface retrieval/judge leaks.
$env:OPENAI_API_KEY = "..." dotnet run --project samples/Graphiti.Eval # graph-building regression dotnet run --project samples/Graphiti.Eval -- --qa # retrieval QA
Set
GRAPHITI_EVAL_FAIL_LOUD=1when running the eval as a CI canary. In that mode, graph-building returns nonzero if any episode is judged worse than the baseline, and retrieval-QA returns nonzero on a distractor leak or if it falls belowGRAPHITI_EVAL_MIN_QA_CORRECT(default3). CI can pointGRAPHITI_EVAL_BASELINE_PATHatsamples/Graphiti.Eval/baselines/baseline_graph_results.jsonand setGRAPHITI_EVAL_REQUIRE_BASELINE=1to avoid bootstrapping a same-run baseline.
To run the full live-provider validation loop (restore, build, OpenAI integration tests, then the
OpenAI sample) in one command — it also loads a local, gitignored .env for OPENAI_API_KEY:
$env:OPENAI_API_KEY = "..."
.\eng\Run-OpenAIProviderValidation.ps1The .github/workflows/live-provider.yml workflow runs that live-provider loop plus fail-loud evals
on workflow_dispatch and a weekly schedule. It intentionally lives outside normal PR CI and fails if
the OPENAI_API_KEY secret is not configured.
There is nothing to build for this README (markdown only), but to build and verify the library run the
full verifier from the csharp folder:
.\eng\Verify-GraphitiCore.ps1It runs restore, formatting checks, build, tests, package creation for Graphiti.Core, and a
package-consumption smoke check. The smoke check creates a fresh temporary net10.0 console project
with a strict NuGet.config (<clear />) and isolated NUGET_PACKAGES; it restores the packed
Graphiti.Core output plus the fork GitHub Packages feed, runs an InMemory triplet/search workflow,
then runs the same workflow through a LadybugDB driver from the same package. Use -SkipPackageSmoke
only when iterating on non-packaging changes. For a quick local test-only loop:
dotnet test Graphiti.Core.CSharp.slnxOpenAI provider integration tests live in tests/Graphiti.Core.Tests and skip unless OPENAI_API_KEY
is set. To run just those with real providers:
$env:OPENAI_API_KEY = "..."
dotnet test Graphiti.Core.CSharp.slnx --filter "FullyQualifiedName~OpenAIProviderIntegrationTests"
Graphiti.Coreand the LadybugDB tests restoreLadybugDB/LadybugDB.Nativefrom thesergey-v9/ladybug-dotnetGitHub Packages feed inNuGet.config. Configureread:packagescredentials for sourcegithub_ladybugbefore running the verifier.
src/Graphiti.Core— core library: models, theGraphitiorchestrator, the graph-driver contract, the deterministic InMemory reference/test driver, the built-in LadybugDB driver, search, maintenance helpers, and LLM/embedder/reranker contracts.tests/Graphiti.Core.Tests— parity-oriented xUnit tests for ingestion, search/ranking, text utilities, provider infrastructure, serialization/cache behavior, and graph-driver contracts.samples/Graphiti.Sample.Quickstart— no-key hello graph sample.samples/Graphiti.Sample.GenericProvider— non-OpenAIMicrosoft.Extensions.AIprovider sample.samples/Graphiti.Sample.Observability— OpenTelemetry/OTLP wiring sample.samples/Graphiti.Sample.OpenAI— console host wiring the core to real OpenAI chat, embedding, and reranking providers viaMicrosoft.Extensions.AI.OpenAI.samples/Graphiti.Eval— graph-building and retrieval-QA evaluation harness.
Public types are organized into feature sub-namespaces under Graphiti.Core. The primary entry point
(Graphiti) and the exception hierarchy stay in the root Graphiti.Core namespace; everything else
lives under a matching sub-namespace:
| Sub-namespace | Contents |
|---|---|
Graphiti.Core |
Graphiti orchestrator, GraphitiException and the exception hierarchy |
Graphiti.Core.Models |
EpisodeType, EntityTypeDefinition, EntityAttributeDefinition |
Graphiti.Core.Models.Nodes |
Node, EntityNode, EpisodicNode, CommunityNode, SagaNode |
Graphiti.Core.Models.Edges |
Edge, EntityEdge, EpisodicEdge, CommunityEdge, HasEpisodeEdge, NextEpisodeEdge |
Graphiti.Core.Models.Results |
AddEpisodeResults, AddBulkEpisodeResults, AddTripletResults, RawEpisode, GraphitiClients |
Graphiti.Core.Drivers |
IGraphDriver, GraphDriverBase, InMemoryGraphDriver, GraphProvider, SagaEpisodeContent |
Graphiti.Core.Drivers.Ladybug |
LadybugDbGraphDriverFactory and the LadybugDB driver internals |
Graphiti.Core.Search |
search engine, configuration, filters, and reranking |
Graphiti.Core.LlmClients |
ILlmClient, LlmClient, LlmConfig, response caches, token usage |
Graphiti.Core.Embedding |
IEmbedderClient, EmbedderClient, HashEmbedder |
Graphiti.Core.CrossEncoder |
ICrossEncoderClient, CrossEncoderClient and rerankers |
Graphiti.Core.Maintenance |
dedup and community clustering |
Graphiti.Core.Prompts |
LLM prompt builders ported from Python graphiti_core/prompts/ |
Graphiti.Core.Text |
content chunking, token counting, text helpers |
Graphiti.Core.Namespaces |
node/edge namespace facades |
Graphiti.Core.Telemetry |
ActivitySource, Meter, and logging |
Graphiti.Core.Configuration |
options and DI registration |
Graphiti.Core.Serialization |
System.Text.Json context |
Earlier prototypes exposed every type from the single flat Graphiti.Core namespace. In the 2.0.0
line those types moved into the sub-namespaces above. This is a source-breaking change for external
consumers: add the sub-namespace using directives for the types you reference (for example
using Graphiti.Core.Models.Nodes; for EntityNode). LLM client types were also renamed from the
LLM prefix to Llm (ILLMClient → ILlmClient, LLMConfig → LlmConfig, LLMResponseCache →
LlmResponseCache, etc.). For consistency, the configuration section bound to LlmConfig is now Llm
(was LLM), and the chat telemetry span is now Graphiti.Llm.GenerateResponse (was
Graphiti.LLM.GenerateResponse).
This library's behavior is derived from Python graphiti_core,
but it is our own code — not a native binding, not a redistribution, and not a contribution back to
the upstream repo. It is maintained independently and is likely to be renamed. Python is the
functional contract: the C# keeps the JSON/wire shape (snake_case properties, enum wire values such
as fact_triple), prompt and response-format identity, ranking/search semantics (RRF, MMR,
cross-encoder, node-distance, episode-mentions), and LLM cache-key inputs behavior-compatible with
Python, and that parity is enforced by tests. Everything else is idiomatic modern C#, which is where the
active development effort goes. The library targets LadybugDB as the first-class backend and InMemory as
the reference/test backend, while FalkorDB/Neptune are compatibility surfaces only.
Apache-2.0. See LICENSE and the PackageLicenseExpression in
src/Graphiti.Core/Graphiti.Core.csproj, matching upstream Graphiti.