Skip to content

sergey-v9/Graphiti.Core

Repository files navigation

Graphiti Core for .NET

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.Core includes the deterministic InMemory reference driver and the first-class LadybugDB driver; restores that include LadybugDB use the sergey-v9/ladybug-dotnet GitHub Packages feed. See Reference / consume. The active development focus is idiomatic-C# and allocation/GC modernization of the code, not release packaging.

Contents

Why Graphiti

  • 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 AddEpisodeAsync integrates new data without recomputing the whole graph.

Reference / consume

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.

LadybugDB GitHub Packages feed

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.

Quickstart

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.

Using a real provider (OpenAI)

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.

Dependency injection

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.

Observability

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.

Drivers

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.

LadybugDB persistence

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... */);

Search

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.Communities

Recipes

SearchConfigRecipes (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.

Custom entity & edge types

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.

The temporal model

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 & evaluation

  • samples/Graphiti.Sample.Quickstart — no-key "hello graph" using InMemory and direct AddTripletAsync.

    dotnet run --project samples/Graphiti.Sample.Quickstart
  • samples/Graphiti.Sample.GenericProvider — offline non-OpenAI provider sample using local Microsoft.Extensions.AI chat 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. Set OTEL_EXPORTER_OTLP_ENDPOINT when 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=1 when 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 below GRAPHITI_EVAL_MIN_QA_CORRECT (default 3). CI can point GRAPHITI_EVAL_BASELINE_PATH at samples/Graphiti.Eval/baselines/baseline_graph_results.json and set GRAPHITI_EVAL_REQUIRE_BASELINE=1 to 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.ps1

The .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.

Building & verifying

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.ps1

It 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.slnx

OpenAI 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.Core and the LadybugDB tests restore LadybugDB / LadybugDB.Native from the sergey-v9/ladybug-dotnet GitHub Packages feed in NuGet.config. Configure read:packages credentials for source github_ladybug before running the verifier.

Project layout

  • src/Graphiti.Core — core library: models, the Graphiti orchestrator, 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-OpenAI Microsoft.Extensions.AI provider 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 via Microsoft.Extensions.AI.OpenAI.
  • samples/Graphiti.Eval — graph-building and retrieval-QA evaluation harness.

Namespace layout

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

Migrating from the flat Graphiti.Core namespace

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 (ILLMClientILlmClient, LLMConfigLlmConfig, LLMResponseCacheLlmResponseCache, 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).

Relationship to Python Graphiti

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.

License

Apache-2.0. See LICENSE and the PackageLicenseExpression in src/Graphiti.Core/Graphiti.Core.csproj, matching upstream Graphiti.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors