Using with AI Agents¶
KGLite is designed to work as a self-contained knowledge layer for AI agents. No external database, no server process, no network — just a Python object with a Cypher interface that an agent can query directly.
The Idea¶
Load or build a graph from your data (DataFrames, CSVs, APIs)
Give the agent
describe()— a progressive-disclosure XML schema that scales from tiny to massive graphsThe agent writes Cypher queries using
graph.cypher()— no other API to learnSemantic search works natively —
text_score()in Cypher, backed by any embedding model you wrap
No vector database, no graph database, no infrastructure. The graph lives in memory and persists to a single .kgl file.
Quick Setup¶
xml = graph.describe() # inventory overview — types, connections, Cypher extensions
prompt = f"You have a knowledge graph:\n{xml}\nAnswer the user's question using graph.cypher()."
MCP Server¶
Expose the graph to any MCP-compatible agent (Claude, etc.) with a thin server. See the MCP Servers guide for a complete walkthrough — server setup, tool patterns, FORMAT CSV export, security, and a copy-paste template.
Adding Semantic Search (5-Minute Setup)¶
Semantic search lets agents find nodes by meaning, not just exact property matches:
# 1. Wrap any embedding model (local or remote)
class Embedder:
dimension = 384
def embed(self, texts: list[str]) -> list[list[float]]:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
return model.encode(texts).tolist()
# 2. Register it on the graph
graph.set_embedder(Embedder())
# 3. Embed a text column (one-time, incremental on re-run)
graph.embed_texts("Article", "summary")
# 4. Now agents can search by meaning in Cypher — no extra API
graph.cypher("""
MATCH (a:Article)
WHERE text_score(a, 'summary', 'climate policy') > 0.5
RETURN a.title, text_score(a, 'summary', 'climate policy') AS score
ORDER BY score DESC LIMIT 10
""")
The model wrapper works with any provider — OpenAI, Cohere, local sentence-transformers, Ollama. See Semantic Search for the full API.
Tips for Agent Prompts¶
Start with
describe()— gives the agent an inventory of types with capability flags, connection map, and non-standard Cypher extensionsDrill into types with
describe(types=['Field'])— shows properties, connections, timeseries/spatial config, supporting children, and sample nodesUse
properties(type)for deeper column discovery — shows types, nullability, unique counts, and sample valuesUse
sample(type, n=3)before writing queries — lets the agent see real data shapesPrefer Cypher over the fluent API in agent contexts — closer to natural language, easier for LLMs to generate
Use parameters (
params={'x': val}) to prevent injection when passing user input to queriesResultView is lazy — agents can call
len(result)to check row count without converting all rows
Structural Validators¶
Six native Cypher procedures surface data-integrity gaps without the agent having to write the underlying WHERE NOT EXISTS patterns. They appear in describe() (in the <rules hint="..."/> extension and in describe(cypher=True)) so the agent can discover them inline with the schema.
Procedure |
What it finds |
|---|---|
|
nodes with zero edges in any direction |
|
self-loops via the given edge |
|
reciprocal pairs |
|
direction-validated outbound check |
|
direction-validated inbound check |
|
nodes whose title is shared with another node of the same type |
Each binds node (or node_a, node_b for cycle_2step), so the agent can compose with WHERE / ORDER BY / aggregation in a single Cypher pass — including the cross-reference workflow where flagged IDs are checked against another query’s results:
graph.cypher("""
MATCH (l:Licence {title: '057'})<-[:IN_LICENCE]-(w:Wellbore)
WITH collect(w.id) AS pl057
CALL missing_required_edge({type: 'Wellbore', edge: 'DRILLED_BY'}) YIELD node
WHERE node.id IN pl057
RETURN count(*) AS pl057_missing_drilled_by
""")
missing_required_edge and missing_inbound_edge validate the (type, edge) direction against the graph’s actual schema and raise DirectionMismatch with a fix-suggesting message when the agent picks the wrong one (e.g. asking for inbound IN_LICENCE on a Wellbore when the edge flows outward). See Cypher → Structural-validator CALL procedures for the full surface and per-procedure docs via describe(cypher=['orphan_node']).
What describe() Returns¶
Inventory mode (
describe()): node types as compact descriptorsTypeName[size,complexity,flags]sorted by count, connection map, Cypher extensions. Core/supporting type tiers hide child types behind+Nsuffixes. For small graphs (≤15 types), full detail is inlined automatically. The<extensions>block carries<algorithms>and<rules>hint lines pointing the agent at the availableCALLprocedures (graph algorithms + structural validators).Focused mode (
describe(types=['Field'])): detailed properties with types, connection topology, timeseries/spatial config, supporting children, and sample nodes.Cypher reference (
describe(cypher=True)): full language reference including all supported clauses, operators, built-in functions, predicates, and procedures (including the six structural validators). Drill into a single procedure withdescribe(cypher=['orphan_node']).
Reading the XML¶
A few attributes worth knowing when you paste describe() into a prompt:
kglite_version="…"on the root<graph>element (0.9.37+). The KGLite version that produced the XML, sourced at compile time from the running binary. Useful when the localpip install kgliteis on a different version from the MCP-server-side binary — the schema you’re reading is from the server’s binary, not your local one. Surface this if you see a schema/query mismatch.id_alias="…"/title_alias="…"on a<type>element. Set whenadd_nodes(...)was called with aunique_id_fieldother than"id"(e.g."npdid") or anode_title_fieldother than"title"(e.g."prospect_name"). The alias tells the agent thatn.npdidandn.idresolve to the same field, so it can use whichever name appears in the source data without wondering which one Cypher will accept. Both forms work inMATCH/WHERE. Result rows always come back keyed under the canonicalid/title.revs="…"on the<active_graph …/>header (MCP code-graph sessions). Present only when the server built a multi-revision code graph —repo_management(name, revs=N|[list])(github) orset_root_dir(path, revs=…)(local) — and names the loaded rev-set, e.g.revs="v1.0,v2.0,HEAD". It is the signal that one graph holds every listed revision, so an unscoped query counts the union across revs and over-counts —MATCH (n:Function) RETURN count(n)is not “functions at HEAD” but “functions across all revs”. Scope a query to a single rev with list membership on the per-noderevsproperty (MATCH (n:Function) WHERE 'v2.0' IN n.revs RETURN n), and reach forCALL rev_diff({from, to})for added / removed / changed deltas between two revs. Whenrevsis absent the graph is single-revision and plain unscoped queries are exact.describe()also lists the loaded revs and teaches the sameWHERE '<rev>' IN n.revsidiom.sample_truncate(call-site knob). Sample values, sample node titles, and sample edge attributes get truncated at 40 chars by default to keep prompts compact. Passdescribe(sample_truncate=None)to emit them in full when you have the context budget, or e.g.sample_truncate=120for a middle ground. The knob only affects rendering; stored data is always full-precision and accessible via Cypher.