Skip to main content
The 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).
Takeaway: the returned 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:
1

limit: cap the response at the engine level

Cheaper than asking for 100 chunks and trimming to 5 in Python.
2

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

mode: choose the retrieval channels

SearchMode.{VECTOR, GRAPH, HYBRID, ALL, KEYWORD}. HYBRID (the default) fuses vector + graph; the BM25 keyword channel joins only when enable_bm25_channel is on. KEYWORD is BM25-only lexical match (always runs BM25, regardless of that flag).
Why this matters: VectorCypher fuses the vector and graph channels by default, so mode lets you narrow to a single channel. Add the keyword channel with enable_bm25_channel, or use SearchMode.KEYWORD for pure lexical match.

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:
Takeaway: same model, same text. But the domain-specific run yields an entity-type histogram you can query against. Picking the ontology is the single highest-leverage extraction decision you make. This is the groundwork for the iterative-ontology workflow planned in Tier 40 (open extraction → inspect → refine → re-extract). When you need the system prompt, dedup, and inference rules versioned alongside the types, package them as an ExpertiseConfig. See 06 below and the Expertise & ontologies guide.

Reading entities & relationships back

VectorCypher builds a graph at write time (one LLM call per remember produces entities + relationships keyed back to the source chunks). This example reads that graph back three ways:
Takeaway: 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:
  1. list_entities(namespace): enumerate everything
  2. list_entities(entity_type="PERSON"): filter to one type
  3. search_entities(query, namespace): semantic lookup by name/description
  4. get_entity(entity_id, namespace): fetch one node by id
  5. find_related_entities(entity_id, max_depth=…): walk the edges outward
  6. recall(query, mode=SearchMode.GRAPH): graph-channel retrieval
  7. An ASCII tree built from (1) + (5)
Takeaway: 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:
A short hiring note, driven by the ontology object. Entities come back typed the way the ontology declares (one run, LLM output varies). The example also turns event extraction off (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:
Takeaway: bare lists are a per-call hint. An 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

Workloads

End-to-end scenarios that compose these APIs into real applications.

VectorCypher

The engine behind these examples: hybrid vector + graph retrieval, with an opt-in BM25 keyword channel.