Skip to content

Quickstart: find it by meaning, prove it by path

View Markdown

This quickstart answers one question: which release fixed the login bug?

You write a few support tickets, bugs and releases as JSON. Search by meaning finds the right ticket, although the question uses other words.

A graph query then follows the ticket’s links to the release. Both reads name the same commit, so you can check the answer later.

"users cannot sign in after the upgrade"
│ search by meaning
▼
ticket 4821: login fails after 3.1
└─ REPORTED_AS → bug AUTH-112: session token expiry
└─ FIXED_IN → Release 3.2

It takes about ten minutes. You need no RDF or graph database experience.

To start from your own data, see Load your data. It loads a database and documents, with examples for five use cases.

Open the console. If you are signed out, it opens the WorkOS sign-in page automatically. Choose Sign up there if you need an account.

Once signed in, create a stack and open Connect. Copy the complete endpoint and a stack API key. Then set them in your terminal:

Terminal window
export LBB_URL="https://<your-complete-stack-host>"
export LBB_API_KEY="<your-stack-api-key>"

Use the exact endpoint from Connect. It looks like https://<tenant-short-id>--<stack-slug>.db.eu.littlebigbrain.com. Keep the key on your server or your machine. Never put it in a browser bundle.

Install a client, or use cURL. The cURL steps also use jq to read fields from JSON responses.

Terminal window
npm install @littlebigbrain/client

Create quickstart.mts. Add the snippet of each step to the end of the file. Run it with npx tsx quickstart.mts after any step. Each call is safe to repeat.

A graph accepts the record types, links and fields you declare. This works like a table schema. A write that does not fit is refused, so bad data does not get in.

Name Kind Meaning
Ticket, Bug, Release Record types The three kinds of records.
REPORTED_AS Link A ticket is reported as a bug.
FIXED_IN Link A bug is fixed in a release.
description Text field Free text on a record.

The graph support is created by the first call. A graph is one dataset in your stack, like a database or an index. Load your data explains the schema and the other ways to load data.

import { LbbClient } from "@littlebigbrain/client";
const lbb = new LbbClient({
baseUrl: process.env.LBB_URL!,
apiKey: process.env.LBB_API_KEY!,
graph: "support",
});
await lbb.ontology.define({
format: "spec",
merge_default: false,
source: JSON.stringify({
entity_types: ["Ticket", "Bug", "Release"],
relation_types: [
{ name: "REPORTED_AS", source: "Ticket", target: "Bug" },
{ name: "FIXED_IN", source: "Bug", target: "Release" },
],
}),
});
await lbb.ontology.evolve({
ops: [{ op: "add_property", name: "description", value_type: "text" }],
});

A write is a commit. It holds links between records (triplets) and field values (entity_properties). A record is its type plus a key, and it has a name. Each link can carry evidence, the reason it was recorded.

After the write, wait until it is published. Search reads published data. This usually takes a second or two.

const login = { type: "Ticket", key: "4821", name: "login fails after 3.1" };
const invoice = { type: "Ticket", key: "4822", name: "invoice PDF is blank" };
const csvExport = { type: "Ticket", key: "4823", name: "CSV export times out" };
const auth = { type: "Bug", key: "AUTH-112", name: "session token expiry" };
const pdf = { type: "Bug", key: "BILL-7", name: "PDF fonts missing" };
const slowExport = { type: "Bug", key: "DATA-58", name: "export runs on one thread" };
const r32 = { type: "Release", key: "3.2", name: "Release 3.2" };
const r311 = { type: "Release", key: "3.1.1", name: "Release 3.1.1" };
const written = await lbb.graph("support").facts.create(
{
triplets: [
{ source: login, relation: "REPORTED_AS", target: auth, evidence: "Support triage, 2 Sep" },
{ source: invoice, relation: "REPORTED_AS", target: pdf, evidence: "Support triage, 3 Sep" },
{ source: csvExport, relation: "REPORTED_AS", target: slowExport, evidence: "Support triage, 4 Sep" },
{ source: auth, relation: "FIXED_IN", target: r32, evidence: "Release notes 3.2" },
{ source: pdf, relation: "FIXED_IN", target: r311, evidence: "Release notes 3.1.1" },
],
entity_properties: [
{ ...login, properties: { description: "Since 3.1, people are sent back to the login screen right after they type their password." } },
{ ...invoice, properties: { description: "Downloaded invoices open as empty pages." } },
{ ...csvExport, properties: { description: "Exports with more than 10,000 rows stop after 30 seconds." } },
],
},
{ idempotencyKey: "quickstart-support-1" },
);
await lbb.waitForPublished(written.commit_seq);
console.log("commit", written.commit_seq);

The response reports commit_seq, the number of this commit. On a new graph it is 1.

If you change the records, change the idempotency key too. A reused key returns the first write with replayed: true and stores nothing new.

Declare an embedding on the Ticket type and name the fields to embed. The platform builds one text per ticket, embeds it and stores the vectors. When a ticket changes, its vector is updated. You run no embedding model and no vector store.

https://littlebigbrain.com/class/ticket is the global name of the Ticket type. Each type you declare gets one: its name in lowercase after https://littlebigbrain.com/class/. label is the record’s name.

The first build runs in the background. Wait until the embedding reports serving.

const TICKET = "https://littlebigbrain.com/class/ticket";
await lbb.embeddings.declare({ class: TICKET, from: ["label", "description"] });
while (!(await lbb.embeddings.get("ticket")).serving) {
await new Promise((resolve) => setTimeout(resolve, 2_000));
}

Ask the question in your own words. The question says “sign in” and “upgrade”. The ticket says “login” and “3.1”.

const found = await lbb.embeddings.search({
text: "users cannot sign in after the upgrade",
top_k: 3,
});
for (const hit of found.hits) console.log(hit.label);

The login ticket comes first:

login fails after 3.1

The other two tickets follow, in an order that depends on the model. Each hit also has a score and an iri, the global name of that record. The response field served_at_seq is the commit the search read.

Similarity found a starting point. It does not prove which release fixed the bug. The stored links do. This query starts at the ticket the search found and follows two links.

The query language is SPARQL, the standard for graph queries. Each line in the braces is a pattern: a record, a link, then another record.

Names that start with ? are variables. The query returns the values that fit all patterns at once. In Cypher, the same path reads (t)-[:REPORTED_AS]->(b)-[:FIXED_IN]->(r).

  • dr:reported_as is your REPORTED_AS link. dr: is short for https://littlebigbrain.com/r/, where the links you declare live.
  • rdfs:label is the name of a record.
  • as_of_commit_seq runs the query at the commit the search read. Search and path then agree on one version of the data.
const ticket = found.hits[0].iri;
const query = `
PREFIX dr: <https://littlebigbrain.com/r/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?bug ?release WHERE {
<${ticket}> dr:reported_as ?b .
?b rdfs:label ?bug ; dr:fixed_in ?r .
?r rdfs:label ?release .
}`;
const { rows, snapshot } = await lbb.sparqlRows({ query, as_of_commit_seq: found.served_at_seq });
console.log(rows[0], "read at commit", snapshot?.served_at_seq);

The TypeScript example prints this on a new graph:

{ bug: 'session token expiry', release: 'Release 3.2' } read at commit 1

Python prints the same row. cURL returns it in SPARQL Results JSON, under results.bindings.

Your agent can now answer “Release 3.2 fixed it” and show why. Store these with the answer:

  • the question and the search hits,
  • the query and the rows it returned,
  • the commit number, served_at_seq.

Later writes do not change that commit. Run the same query with the same as_of_commit_seq later. It returns the same rows while the commit is retained. Replay an answer covers retention and history.

What you see What to do
401 The API key is missing, wrong or revoked. Export a current key in the same terminal.
stack_endpoint_mismatch or stack_endpoint_required Copy the complete endpoint from the same stack’s Connect view.
400 with no predicate named description when you declare the embedding The write is not published yet. Wait for it, then declare again.
409 embedding_not_ready The first build is still running. Wait until the embedding reports serving.
Edited records do not appear, and the commit reports replayed: true The idempotency key was reused. Use a new key for the changed records.
A retryable 429 Wait for the time in Retry-After, then retry.