kgagent — A Temporal Knowledge Graph Agent
A faithful Python re-implementation of the Graphiti architecture, with first-class support for Ollama and vLLM as LLM providers.
What is a temporal knowledge graph? Every entity and relationship carries two timestamps: when it became true (
valid_at) and when it stopped being true (expired_at). This lets the agent answer both "what is true now?" and "what was true on 2024-03-15?" — and handle real-time updates that invalidate stale facts.
Why kgagent?
| Feature | kgagent | Graphiti |
|---|---|---|
| Temporal bi-indexing | ✅ | ✅ |
| Episode provenance | ✅ | ✅ |
| Custom ontology (Pydantic) | ✅ | ✅ |
| Hybrid search (vector + BM25 + graph) | ✅ | ✅ |
| RRF / MMR / node-distance reranking | ✅ | ✅ |
| Ollama support | ✅ first-class | ✅ |
| vLLM support | ✅ first-class | ✅ |
| Zero-server default (in-memory) | ✅ | ❌ (needs Neo4j) |
| Embedded DB option (Kùzu) | ✅ | ✅ |
| Pure Python CLI | ✅ | ✅ (Python) |
Quick Start
1. Install
git clone <this-repo> && cd kgagent
python -m venv .venv && source .venv/bin/activate
pip install -e .
# Optional backends:
pip install -e ".[kuzu]" # embedded graph DB
pip install -e ".[neo4j]" # Neo4j server backend
pip install -e ".[anthropic]" # Claude support
2. Configure
Create a .env file (or export env vars):
# --- LLM ---
# Option A: OpenAI
KGAGENT_LLM_PROVIDER=openai_compatible
KGAGENT_LLM_MODEL=gpt-4o-mini
KGAGENT_LLM_API_KEY=sk-...
# Option B: Ollama (local, free)
KGAGENT_LLM_PROVIDER=openai_compatible
KGAGENT_LLM_MODEL=llama3.1:8b
KGAGENT_LLM_BASE_URL=http://localhost:11434/v1
KGAGENT_LLM_API_KEY=ollama
KGAGENT_LLM_SMALL_MODEL=llama3.1:8b
# Option C: vLLM (local, GPU)
KGAGENT_LLM_PROVIDER=openai_compatible
KGAGENT_LLM_MODEL=Qwen/Qwen2.5-7B-Instruct
KGAGENT_LLM_BASE_URL=http://localhost:8000/v1
KGAGENT_LLM_API_KEY=dummy
# --- Embedder ---
# Option A: local sentence-transformers (free, ~130MB)
KGAGENT_EMBEDDER_PROVIDER=sentence_transformers
KGAGENT_EMBEDDER_MODEL=BAAI/bge-small-en-v1.5
KGAGENT_EMBEDDER_DIM=384
# Option B: Ollama embeddings
KGAGENT_EMBEDDER_PROVIDER=ollama
KGAGENT_EMBEDDER_MODEL=nomic-embed-text
KGAGENT_EMBEDDER_BASE_URL=http://localhost:11434
KGAGENT_EMBEDDER_DIM=768
# --- Graph ---
KGAGENT_GRAPH_BACKEND=memory # or kuzu / neo4j
KGAGENT_GROUP_ID=default
3. Use the CLI
# Initialize the graph backend (creates indexes)
kgagent migrate
# Ingest a text file as an episode
kgagent ingest --file notes.txt --source-description "my notes"
# Ingest from stdin
echo "Alice joined Acme as CTO in March 2024." | kgagent ingest --stdin
# Ingest a JSON record
kgagent ingest --json '{"name":"Acme","industry":"tech"}' --source json
# Search the graph
kgagent search "Who is the CTO of Acme?"
# Search with a specific recipe
kgagent search "Alice's role" --recipe mmr_diverse
# Ask the ReAct agent
kgagent agent "Who is the CTO of Acme and when did they join?"
# Show graph stats
kgagent count
# Check connectivity
kgagent health
# Print the resolved config
kgagent config
4. Use the Python API
import asyncio
from kgagent import Graphiti, Settings, Episode, EpisodeType
async def main():
settings = Settings() # reads from env / .env
kg = await Graphiti.create(settings)
# Ingest
await kg.add_episode(Episode(
name="hiring-note",
content="Alice joined Acme as CTO in March 2024.",
source=EpisodeType.TEXT,
source_description="hr-notes",
))
# Search
results = await kg.search("Who is the CTO of Acme?")
for nr in results.nodes:
print(f" node: {nr.node.name} (score={nr.score:.3f})")
for er in results.edges:
print(f" edge: {er.edge.fact} (score={er.score:.3f})")
# Agent
answer = await kg.agent_ask("Who is the CTO of Acme?")
print("Agent:", answer)
await kg.close()
asyncio.run(main())
Architecture
┌─────────────────────────────────────────────────────────┐ │ CLI (Typer) │ │ ingest · search · agent · schema · migrate · health │ ├─────────────────────────────────────────────────────────┤ │ Graphiti (facade) │ ├──────────┬──────────┬──────────┬──────────┬─────────────┤ │ Ingest │ Search │ Update │ Agent │ Semantic │ │ Pipeline │ Engine │ Engine │ (ReAct) │ Layer │ ├──────────┴──────────┴──────────┴──────────┴─────────────┤ │ LLM Client │ Embedder Client │ │ OpenAI/vLLM/Ollama │ ST/OpenAI/Ollama │ ├──────────────────────────┴──────────────────────────────┤ │ Graph Client (pluggable) │ │ Memory (NetworkX) · Kùzu · Neo4j · FalkorDB │ └──────────────────────────────────────────────────────────┘
Semantic Layer
The semantic layer is the type system that constrains the LLM extractor. Define custom entity and edge types as Pydantic models:
from kgagent.semantic.ontology import Ontology, Entity, Edge
class Person(Entity):
name: str
age: int | None = None
role: str | None = None
class Company(Entity):
name: str
industry: str | None = None
class Employment(Edge):
source_type: type[Entity] = Person
target_type: type[Entity] = Company
title: str | None = None
since: str | None = None
ontology = Ontology(entities=[Person, Company], edges=[Employment])
kg = await Graphiti.create(settings, ontology=ontology)
The extractor generates a JSON schema from these models and passes it to the LLM via function-calling (or response_format=json_schema for vLLM with --guided-decoding-backend outlines).
Temporal Bi-Indexing
Every EntityEdge carries:
| Field | Meaning |
|---|---|
valid_at |
When the fact became true in the real world |
expired_at |
When the fact stopped being true (None = still true) |
created_at |
When the edge was first written to the graph |
episodes |
List of episode UUIDs that established this fact (provenance) |
When a new episode contradicts an existing fact, the update engine sets expired_at on the old edge and writes a new one — the old fact is never deleted, just invalidated.
Incremental Update Engine
The ingestion pipeline runs this sequence for every episode:
-
Chunk the episode if it exceeds
ingest_chunk_size. -
Persist an
EpisodicNodefor provenance. -
Extract entities + edges via LLM (constrained by ontology).
-
Embed entity names and edge facts.
-
Deduplicate extracted entities against existing graph entities (embedding similarity → LLM confirmation).
-
Detect contradictions between new edges and existing edges (LLM judges).
-
Invalidate stale edges (
expired_at = now). -
Save new entities, edges, and MENTIONS links.
Hybrid Search
Three retrievers run in parallel:
-
Semantic — cosine similarity over
name_embedding/fact_embedding. -
BM25 — full-text search over node names + summaries / edge facts.
-
Graph traversal — BFS from a focal node (optional).
Results are fused with one of:
-
RRF (Reciprocal Rank Fusion) — default, robust.
-
MMR (Maximal Marginal Relevance) — diversity-aware.
-
Node distance — boost nodes close to a focal entity.
-
Episode mentions — boost nodes corroborated by more episodes.
Prebuilt recipes: combined, node, edge, community, mmr_diverse, node_distance, episode_mentions.
Ollama Setup
# 1. Install Ollama: https://ollama.com
# 2. Pull models
ollama pull llama3.1:8b # LLM
ollama pull nomic-embed-text # embedder
# 3. Configure kgagent
export KGAGENT_LLM_PROVIDER=openai_compatible
export KGAGENT_LLM_MODEL=llama3.1:8b
export KGAGENT_LLM_BASE_URL=http://localhost:11434/v1
export KGAGENT_LLM_API_KEY=ollama
export KGAGENT_EMBEDDER_PROVIDER=ollama
export KGAGENT_EMBEDDER_MODEL=nomic-embed-text
export KGAGENT_EMBEDDER_BASE_URL=http://localhost:11434
export KGAGENT_EMBEDDER_DIM=768
# 4. Run
kgagent migrate
kgagent ingest --stdin <<< "Alice joined Acme as CTO in March 2024."
kgagent search "Who is the CTO of Acme?"
vLLM Setup
# 1. Start vLLM with guided decoding for strict JSON output
vllm serve Qwen/Qwen2.5-7B-Instruct \
--guided-decoding-backend outlines \
--port 8000
# 2. Configure kgagent
export KGAGENT_LLM_PROVIDER=openai_compatible
export KGAGENT_LLM_MODEL=Qwen/Qwen2.5-7B-Instruct
export KGAGENT_LLM_BASE_URL=http://localhost:8000/v1
export KGAGENT_LLM_API_KEY=dummy
export KGAGENT_EMBEDDER_PROVIDER=sentence_transformers
export KGAGENT_EMBEDDER_MODEL=BAAI/bge-small-en-v1.5
export KGAGENT_EMBEDDER_DIM=384
# 3. Run
kgagent migrate
kgagent ingest --file data.txt
kgagent agent "What entities are in the graph?"
Graph Backends
| Backend | Setup | Best for |
|---|---|---|
memory |
None | Tests, small graphs (<100k nodes) |
kuzu |
pip install kgagent[kuzu] |
Single-process apps, file-backed persistence |
neo4j |
Run Neo4j server | Production, multi-process, large graphs |
falkordb |
Run FalkorDB | Redis-based, high-throughput |
Project Structure
kgagent/
├── pyproject.toml
├── README.md
├── kgagent/
│ ├── config.py # Settings (env-driven)
│ ├── core.py # Graphiti facade
│ ├── semantic/ # Semantic layer (nodes, edges, episodes, ontology)
│ ├── llm/ # LLM clients (OpenAI/vLLM/Ollama/Anthropic)
│ ├── embedder/ # Embedder clients (ST/OpenAI/Ollama)
│ ├── graph/ # Graph backends (Memory/Kuzu/Neo4j)
│ ├── ingest/ # Episode ingestion pipeline
│ ├── update/ # Incremental update engine
│ ├── search/ # Hybrid search + rerankers + recipes
│ ├── agent/ # ReAct agent
│ └── cli/ # Typer CLI
├── scripts/
│ └── smoke_test.py # End-to-end test (mock LLM/embedder)
└── examples/
└── demo.py # Usage demo
License
MIT.
https://cdn.neusncp.com/public/file/20260731150742_fVpqb5UY.zip
