# Suggest ontology changes from text (experimental)

Source: https://docs.littlebigbrain.com/guides/suggest-ontology-changes-from-text/

Your text already describes your domain. Interview transcripts name the
tools people use, the documents they pass on, and who approves what. Fit
from text reads that text and proposes the ontology changes it supports:
new classes, new properties, new relations. Each proposal quotes the
sentence that supports it. The proposals wait in the suggestion inbox until
a person accepts or dismisses them, so nothing changes your ontology on its
own.

Fit from text works like [search by meaning](https://docs.littlebigbrain.com/guides/search-by-meaning/).
You declare on a class which properties hold the text, and the platform
keeps the proposals in step with your graph. A new or changed instance is
read after the next commit is published.

The fit proposes ontology changes only. Extracting the facts of each text
(the people, tools and dates) stays with you.

## 1. Commit the text

Commit each text as an instance of a class, with the text in a string
property. A transcript of 40,000 characters fits in one property. This
example loads four short interviews in Turtle:

```turtle
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix c: <https://example.org/class/> .
@prefix e: <https://example.org/interview/> .
@prefix p: <https://example.org/p/> .

e:jonas-berg a c:interview ;
    rdfs:label "Interview with Jonas Berg" ;
    p:transcript """Interviewer: How does a customer request reach your team?
Jonas: Every request becomes a ticket in Deskflow, our ticket system. ...""" .
```

```sh
curl -X POST "$LBB/v1/graph/import/rdf?format=turtle" \
  -H "Authorization: Bearer $LBB_API_KEY" \
  --data-binary @interviews.ttl
```

JSON records work the same way: commit an `Interview` with a `transcript`
property. Documents from the SharePoint and Google Drive integrations
already carry a `text` property.

## 2. Preview what the fit reads

The preview shows the fields the fit would read and the text of a few
instances. It stores nothing and calls no model.

```sh
curl -X POST "$LBB/v1/ontology/fit-sources/preview" \
  -H "Authorization: Bearer $LBB_API_KEY" -H 'Content-Type: application/json' \
  --data '{"class": "https://example.org/class/interview", "from": ["transcript"]}'
```

`from` takes the same paths as an embedding: a property name
(`transcript`), `label`, a prefixed name, a full IRI, or a path with one
link. Without `from`, the long text facts of the class are chosen.

Add `"propose": true` for a dry run. The models read the first part of up
to three instances, and every proposal comes back with the quote, the
support score and what the checks made of it. Nothing is filed. The dry run
waits at most 60 seconds for the models; an instance that has not finished
by then is left out, and `truncated` says so.

## 3. Declare a fit source

In the console, open **Review** and click **Suggest
from text** (also under **Models → Ontology fit → From text**). Pick the class
and its text fields, write one sentence about the text, try it on 3
instances, and click **Start reading**. With the API:

```sh
curl -X PUT "$LBB/v1/ontology/fit-sources" \
  -H "Authorization: Bearer $LBB_API_KEY" -H 'Content-Type: application/json' \
  --data '{
    "class": "https://example.org/class/interview",
    "from": ["transcript"],
    "context": "Interviews with employees about their work processes and the tools they use"
  }'
```

`context` is one sentence about the text. The model reads it with every
part of every text.

With the TypeScript client:

```ts
import { LbbClient } from "@littlebigbrain/client";

const client = new LbbClient({
  baseUrl: process.env.LBB_URL!,
  apiKey: process.env.LBB_API_KEY!,
});

const dryRun = await client.ontology.fitSources.preview({
  class: "https://example.org/class/interview",
  from: ["transcript"],
  propose: true,
});
await client.ontology.fitSources.declare({
  class: "https://example.org/class/interview",
  from: ["transcript"],
  context: "Interviews with employees about their work processes and the tools they use",
});
```

With Python, `client.ontology.fit_sources.declare(class_, from_=[...],
context=...)` does the same. Agents use the MCP tools `lbb_fit_sources`
and `lbb_fit_sources_manage`.

The platform now reads every interview. For each one:

1. A reasoning model proposes changes for each part of about 40,000
   characters, each with a quote from the text.
2. Every quote is checked against the text. A proposal whose quote is not
   in the text is dropped.
3. Every change is checked against your current ontology. Invalid changes
   and changes you have already are dropped.
4. A decision model scores how well each quote supports its change. Weak
   support (below 0.5) is dropped.
5. The kept proposals become suggestions. The same change from many
   interviews is one suggestion, with the number of interviews that
   support it and up to 20 quotes.

Check the progress:

```sh
curl "$LBB/v1/ontology/fit-sources?name=interview" -H "Authorization: Bearer $LBB_API_KEY"
```

The status shows the instances read, the changed instances still to read,
the proposals kept and dropped per check, the suggestions filed, this
month's spend and the last error. `POST
/v1/ontology/fit-sources/refresh?name=interview` asks the platform to read
new instances now and returns at once; check the status again for the
progress.

## 4. Review the suggestions

In the console, open **Review**. With the API, list
the suggestions of the source:

```sh
curl "$LBB/v1/ontology/suggestions?origin_id=fit:interview" \
  -H "Authorization: Bearer $LBB_API_KEY"
```

Each suggestion shows the change, the number of interviews that support it,
and the quotes. Accept the changes you want, edit one before you accept
it, or dismiss it with a reason.

A dismissed change is not filed again. It reopens only when the interviews
that support it reach twice the count it had when you dismissed it, and at
least three more.

## 5. Accept

```sh
curl -X POST "$LBB/v1/ontology/suggestions/accept?suggestion_id=sg_…" \
  -H "Authorization: Bearer $LBB_API_KEY"
```

Accepting applies the change to the ontology as it is now. New interviews
keep adding support to the open suggestions.

## Read one text or document

To try the fit on one text without a class, use **Suggest from text** in
the console, or call `POST /v1/ontology/fit-text` with pasted text:

```sh
curl -X POST "$LBB/v1/ontology/fit-text" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "meeting-notes", "text": "We track every support ticket on a shared board. …",
       "context": "Notes from a team meeting", "dry_run": true}'
```

Or with an uploaded PDF. Parse it first with the Parse documents workflow,
then give its SHA-256 and, if you like, the pages to read:

```sh
curl -X POST "$LBB/v1/ontology/fit-text" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "parts-catalogue", "document": "<sha256>", "pages": {"from": 1, "to": 20},
       "context": "A parts catalogue with one sheet per part."}'
```

The call runs the same checks as a fit source and returns every proposal
with its outcome. With `dry_run: true` it files nothing. Otherwise the kept
proposals go to the inbox under `fit:<name>`, each with its quote; for a
PDF also the page and the line the quote is on. One call reads at most
about 60,000 characters (16 parts) and takes about a minute and a half. A
longer text is cut after the first pages that fit, and the answer says
`truncated: true` and which pages it read. A file without parsed pages
gives `409 document_not_parsed`.

## Cost and limits

Each part of a text is one call to the reasoning model, and each text one
call to the decision model. The calls count against a monthly budget for
fit from text per stack (10 US dollars by default). At the limit the fit
stops with `429 fit_budget_exceeded` until the next month. Fit from text
needs a Cloud plan.

Every model call is in the call log under **Models**, so you can check the
proposals and the scores.
