examples/10_core_apis/
tier picks up where the Basics leave off. Each example
isolates one API surface and shows the knobs that matter. So when you reach for
remember_batch, recall(...) filters, or the entity-graph reads, you already
know which argument does what.
Most examples here run on the zero-infra embedded backend (
pip install "khora[sqlite-lance]" + OPENAI_API_KEY), exactly like the basics. Reading entities & relationships back and Exploring the graph default to PostgreSQL + Neo4j. Bring the stack up with make dev
from the Khora repo first. They read the entity graph back, and on the embedded
backend entity vectors aren’t written to LanceDB yet, so two of their steps
would come back empty. Both still accept --config examples/khora.embedded.yaml
if you want to run degraded.Batch ingestion with remember_batch
A kb.remember() loop is fine for a handful of records but pays three avoidable
taxes at scale: the embedder cache re-warms per call, entity dedup runs
per-document with no cross-doc scope, and concurrency is whatever your caller
wired up. remember_batch fixes all three: one shared embedder cache,
cross-document dedup via EntityIndex, and a max_concurrent ceiling on
in-flight LLM calls (≈5–10 on a laptop, higher on the standard stack).
BatchResult carries processed / skipped /
failed counts plus rolled-up chunks / entities / relationships totals.
Recall with filters
recall() exposes three arguments that change the result set itself, not just
its ordering. Knowing them removes most application-side post-filtering:
limit: cap the response at the engine level
Cheaper than asking for 100 chunks and trimming to 5 in Python.
min_similarity: a real semantic-quality cutoff
Drops chunks whose raw cosine is below the threshold. It operates before
score normalization, so it’s a genuine quality gate, unlike thresholding the
post-normalize
chunk.score.mode actually
differentiates between vector, graph, and keyword retrieval.
Ontology config
entity_types and relationship_types are required kwargs on every remember(),
but they’re guidance, not a hard schema. The extractor treats your list as a strong
hint (it can still emit types outside it), and passing empty lists doesn’t turn
extraction off. It removes the guidance entirely, so Khora falls back to unbounded
extraction and infers its own taxonomy from the content, with loosely-cased model-chosen
labels (you’ll see Person and EVENT side by side). So pass them deliberately. This
example runs the same paragraph through two ontologies and compares the resulting
entity-type histograms:
ExpertiseConfig. See 06 below
and the Expertise & ontologies guide.
Reading entities & relationships back
VectorCypher builds a graph at write time (one LLM call perremember produces
entities + relationships keyed back to the source chunks). This example reads that
graph back three ways:
recall().entities / .relationships give you the graph inline
with retrieval. list_entities and find_related_entities are the explicit
“show me the graph” reads. On embedded sqlite_lance the inline lists come back
empty. The example detects
this and falls through to the list/traverse APIs, which is why it defaults to
PostgreSQL + Neo4j.
Exploring the graph
The widest tour in the tier, seven reads against the same Curie-family graph, so you can pick the right call for the task:list_entities(namespace): enumerate everythinglist_entities(entity_type="PERSON"): filter to one typesearch_entities(query, namespace): semantic lookup by name/descriptionget_entity(entity_id, namespace): fetch one node by idfind_related_entities(entity_id, max_depth=…): walk the edges outwardrecall(query, mode=SearchMode.GRAPH): graph-channel retrieval- An ASCII tree built from (1) + (5)
list_entities / get_entity / find_related_entities read from
the graph store and work on every backend. search_entities and
recall(mode=GRAPH) need entity vectors, so they degrade to empty on embedded,
hence the PostgreSQL + Neo4j default. max_depth is how you control how far a traversal fans out.
Ontology as an ExpertiseConfig
Example 03 used bare entity_types / relationship_types lists. When you outgrow that,
an ExpertiseConfig packages the whole recruiting domain
into one reusable, versioned object: a system prompt, typed entities with identifiers
(for cross-source dedup), typed relationships, a correlation rule (merge the same
candidate seen in two notes), and an inference rule that derives edges the text never
stated:
VectorCypherConfig(store_events=False)), so no EVENT /
PARTICIPATED_IN appear. The ASSOCIATED_WITH edges are co-occurrence links Khora adds
and aren’t configurable on VectorCypher. See Ingestion for
both knobs:
ExpertiseConfig is the ontology as a
first-class artifact: define the prompt, identifiers (dedup), correlation rules, and
inference rules once, version them, and reuse them across every remember(). The
APPLIED_TO + ROLE_AT ⇒ TARGETS inference rule is the kind expansion uses to derive
candidate→employer edges no chunk states. Full guide:
Expertise & ontologies.
Next steps
workspaces
Workloads
End-to-end scenarios that compose these APIs into real applications.
settings_input_component
VectorCypher
The engine behind these examples: hybrid vector + graph + keyword retrieval.