Metadata-Version: 2.4
Name: littlebigbrain
Version: 0.24.0
Summary: Python client for little big brain: search, graph queries, and data import
Author: little big brain
License-Expression: Apache-2.0
Project-URL: Documentation, https://docs.littlebigbrain.com/sdks/python/
Project-URL: Issues, https://github.com/littlebigbrains/lbb-python/issues
Project-URL: Repository, https://github.com/littlebigbrains/lbb-python
Keywords: little-big-brain,graph,search,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2
Provides-Extra: dev
Requires-Dist: build==1.5.1; extra == "dev"
Requires-Dist: black==26.5.1; extra == "dev"
Requires-Dist: datamodel-code-generator==0.63.0; extra == "dev"
Requires-Dist: isort==8.0.1; extra == "dev"
Requires-Dist: mypy>=1.15; extra == "dev"
Requires-Dist: packaging==26.2; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=6; extra == "dev"
Requires-Dist: ruff>=0.11; extra == "dev"
Requires-Dist: twine==6.2.0; extra == "dev"
Dynamic: license-file

# littlebigbrain Python SDK

Python client for [little big brain](https://littlebigbrain.com), a search
platform for AI applications such as chatbots, search tools, and agents.
Load facts, query their relationships, and keep the data version behind an answer
so you can check it later.

The package supports Python 3.10+ and provides synchronous and asynchronous
clients built on `httpx`. Generated Pydantic models are available in `lbb.models`.

[Documentation](https://docs.littlebigbrain.com/sdks/python/) ·
[Quickstart](https://docs.littlebigbrain.com/start/quickstart/) ·
[Issues](https://github.com/littlebigbrains/lbb-python/issues)

## Install

```sh
pip install littlebigbrain
```

The package is installed as `littlebigbrain` and imported as `lbb`.

## Load facts and run a query

Create a stack in the [console](https://cloud.littlebigbrain.com) and open
**Connect**. Copy its complete endpoint and a stack API key:

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

This example creates a graph named `quickstart` on its first write. It stores
three facts: a service writes to a database, and each has a label. The data uses
Resource Description Framework (RDF), where each line names a subject, a
relationship, and a value or another record. SPARQL is the query language for
those facts.

Save as `quickstart.py`, then run `python3 quickstart.py`:

```python
import os

from lbb import LbbClient

facts = """
<https://example.org/auth-service> <https://example.org/writesTo> <https://example.org/user-db> .
<https://example.org/auth-service> <http://www.w3.org/2000/01/rdf-schema#label> "Auth Service" .
<https://example.org/user-db> <http://www.w3.org/2000/01/rdf-schema#label> "User Database" .
"""

query = """
    SELECT ?service ?database WHERE {
        ?s <https://example.org/writesTo> ?db .
        ?s <http://www.w3.org/2000/01/rdf-schema#label> ?service .
        ?db <http://www.w3.org/2000/01/rdf-schema#label> ?database .
    } ORDER BY ?service ?database LIMIT 10
"""

with LbbClient(
    os.environ["LBB_URL"],
    api_key=os.environ["LBB_API_KEY"],
    graph="quickstart",
) as lbb:
    imported = lbb.graph("quickstart").facts.import_rdf(
        facts, format="ntriples", idempotency_key="sdk-quickstart-v1"
    )

    results = lbb.sparql(
        query,
        consistency="strong",
        min_indexed_seq=imported["committed_commit_seq"],
    )
    for row in results:
        print(f"{row['service']} -> {row['database']}")
```

On a fresh graph, this prints:

```text
Auth Service -> User Database
```

The query follows the stored relationship between the service and database.
`consistency="strong"` makes the new facts available to this read without
waiting for a background index job. Reads default to eventual consistency, so
omit this option only when an earlier version is acceptable.

The idempotency key makes repeating the same import safe. Use a new key if you
change the data.

## Async client

`AsyncLbbClient` provides the same methods with `await`. After running the
quickstart, this script reads its data:

```python
import asyncio
import os

from lbb import AsyncLbbClient


async def main():
    async with AsyncLbbClient(
        os.environ["LBB_URL"],
        api_key=os.environ["LBB_API_KEY"],
        graph="quickstart",
    ) as lbb:
        result = await lbb.sparql(
            "ASK { ?s <https://example.org/writesTo> ?db }",
            consistency="strong",
        )
        print(result.boolean)  # True


asyncio.run(main())
```

## Ask a question in plain words

`query.ask` answers a question about the graph in plain words. The server's
model runs a short loop of tool calls. It runs SPARQL queries, looks up names,
reads the graph description, finds the commit of a date, and compares two
points. Then it answers.

```python
result = lbb.query.ask(
    "Which services write to the user database?",
    context="Services and databases of the platform team.",
)

print(result.answer)
print(result.citations)
for row in result.rows:
    print(row)
```

`answer` holds the text. `citations` holds the IRIs the answer names. Each one
is in the rows the model read. `steps` lists the tool calls. `query` and `rows`
hold the query whose rows hold the answer. `chart` says how to draw `rows`, or
is `None`: `kind` (`bar`, `line`, `scatter` or `table`) and the columns `x`,
`y` and `series`. The server checked that the columns exist and that `y`
holds numbers for `bar` and `line`. The loop runs at most 4 rounds of
tool calls, and a question takes about 10 s. When the loop stops before it
answers, `answer` is `None` and `error` says why. `rows` then hold the best
rows it read.

`mode="route"` returns only the kind of question, in `result.route["kind"]`.
The router model answers alone, so the call is fast. `answer`, `query` and
`rows` are then empty.

`result.trace_id` names the eval trace of the rows, so you can label them.
Each call uses model tokens, so the client does not retry a failed call. Pass
`options={"retry": True}` to retry it.

The graph can keep notes and worked examples that the model reads for every
question. Store them with `query.set_rewrite_profile(notes=..., examples=...,
expected_version=...)` and read them with `query.rewrite_profile()`. A call's
`context` still adds notes for that call.

The server finds the names in the question ("Quelmann", "TU Dresden") in the
graph, and the loop uses the IRIs it found. `result.linked` lists them, so you
can show "Did you mean …?". When the user has a record open, pass its IRI in
`anchor` (`anchor=[iri]`, at most 10).

A question about a date ("Which findings were open on 18 June?") reads the
last commit written by the end of that day. `result.history` names the commit
and how the loop found it. When your commits stand for other dates (a demo's
milestones, an import of old records), pass `timeline`:
`timeline=[{"date": "2026-06-18", "as_of_commit_seq": 5, "label": "Addendum"}]`.
A question that asks what changed compares two points. Then
`result.history["added"]` and `result.history["removed"]` hold the rows that
differ, and `rows` is empty. When the loop pairs the rows by a variable
(`key`), `result.history["changed"]` holds the entities whose values changed.
`result.history["totals"]` counts each list.

### Show progress

`query.ask_stream` sends the same request and yields an event for each step.
The last event, `done`, holds the same response as `query.ask`.

```python
from lbb import QueryAskResult

for event in lbb.query.ask_stream("Which services write to the user database?"):
    if event.event == "route":
        print("route", event.data["kind"])
    elif event.event == "step":
        print(event.data["tool"], event.data["input"])
    elif event.event == "done":
        result = QueryAskResult.from_response(event.data)
```

The events are `grounding`, `route`, a `step` per tool call, `answer` and
`done`. A second `route` comes before `answer` when the loop chose another
route. `mode="route"` sends `grounding`, `route` and `done`. An error event
raises the same `LbbError` as `query.ask`. Leave the loop, or call `close()`,
to close the response and stop the server's work. On `AsyncLbbClient`, use
`async for` and `aclose()`. The client skips event names it does not know.

### Tools for your own agent

When your app runs its own agent loop, four calls give it what only the
server knows. None calls a model. `query.ask` runs the same kind of loop on
the server.

```python
# The IRI of a name, the person before a document with the name.
found = lbb.query.names("Summarize David Korn's deals")
# The classes and properties a question needs: how many instances hold each
# property, and the values of small classes such as stages.
described = lbb.query.describe(question="Which deals moved stage?")
# The commit of a date.
at = lbb.query.commit_at(date="2026-06-18")
# What changed between two points, paired by entity.
diff = lbb.query.compare(
    "SELECT ?deal ?stage WHERE { ?deal <https://x.test/p/stage> ?stage }",
    before={"date": "2026-06-01"},
    key=["deal"],
)
print(diff["totals"], diff["changed"], diff.get("next_cursor"))
```

`compare` reads up to 20,000 rows per point and pages each list: pass
`next_cursor` back as `cursor=` with the same arguments. Run your own queries
with `query.sparql(..., as_of_commit_seq=...)`.

## Model calls for your own pipeline

`reason_activity`, `decide_activity` and `embed_activity` call LBB's own
models: the reasoning model answers a prompt with JSON that follows a
schema, the decision model answers choice questions about a state, and the
embedding model embeds texts. The calls count against the stack's model
budget for workflows and appear in the call log under `label`. Pass the
same `effect_id` when a step repeats, and the server returns the first
answer (`cached: True`) at no cost.

```python
answer = lbb.reason_activity(
    'Which code does this row define? "P3 Waschtisch 55x45"',
    {
        "type": "object",
        "properties": {"code": {"type": "string"}, "description": {"type": "string"}},
        "required": ["code", "description"],
    },
    effect_id="tender-import/define-p3",
    label="define P3",
)
print(answer["value"]["code"], answer["cost_micro_usd"])
```

A request too large for the model raises `LbbError` with the code
`activity_request_refused` (422): split it and send each half.

## Next steps

- [Search by meaning](https://docs.littlebigbrain.com/guides/search-by-meaning/): choose which facts to embed, find records from a text description, and search inside a SPARQL query with `search:similarTo`.
- [Load your own RDF](https://docs.littlebigbrain.com/guides/load-rdf/): import Turtle, N-Triples, N-Quads, or TriG.
- [Work with JSON records](https://docs.littlebigbrain.com/guides/without-rdf/): define a schema and write records without writing RDF.
- [Validate writes](https://docs.littlebigbrain.com/guides/sparql-and-shacl/): define constraints with the Shapes Constraint Language (SHACL).
- [Read the same version again](https://docs.littlebigbrain.com/guides/time-travel-audit/): save a query and its commit sequence, then replay it through the HTTP API.

The RDF and JSON guides use different write workflows. Choose one when creating
a graph; a graph first written through RDF import does not accept
`facts.create` or JSON record imports.

## Integrations for your customers

`client.integrations` connects each of your customers' HubSpot, Linear,
SharePoint or Google Drive to a graph of their own. Its routes are on the
integrations API, `https://api.littlebigbrain.com` by default
(`integrations_url`). They take the
same stack API key. An operator turns on developer access per stack; ask us
first. `AsyncLbbClient` has the same methods, with `await`.

```python
graph = "c-5f1c9a0e3b7d2c4a8e6f1b0d"  # one opaque graph per customer

lbb.integrations.create(
    graph=graph, id="hubspot", kind="hubspot", credentials={"HUBSPOT_TOKEN": token}
)
connections = lbb.integrations.list(graph=graph)["connections"]
open_ = lbb.integrations.suggestions("hubspot", graph=graph, status="open")
lbb.integrations.accept(open_.suggestions[0].suggestion_id, graph=graph, sync=True)
lbb.integrations.erase(graph, confirm=graph)
```

Google Drive connects through your own Google app. `lbb.google_drive` builds
the consent URL and turns the callback's code into the connection's
credentials (`exchange_code_async` for async code):

```python
from lbb import google_drive

url = google_drive.authorize_url(client_id=client_id, redirect_uri=redirect_uri, state=state)
# On the callback, after `state` matches:
grant = google_drive.exchange_code(
    client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, code=code
)
lbb.integrations.create(
    graph=graph, id="google-drive", kind="google_drive", credentials=grant.credentials
)
```

See [Integrations for your customers](https://docs.littlebigbrain.com/guides/integrations-for-your-customers/).

## Errors and retries

Failed HTTP requests raise `LbbError`, with a status, error code, message, and
request ID. Use `raw_request()` when you also need response headers or timing.

Safe reads and writes with an idempotency key retry rate limits, retryable server
errors, and network failures. Retries respect `Retry-After` and use a 60-second
budget by default. See the [client reference](https://docs.littlebigbrain.com/sdks/python/)
for timeout and retry options.

## Development

From a clone of this repository:

```sh
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m ruff check lbb tests
.venv/bin/python -m mypy lbb
.venv/bin/python -m pytest tests
```

`lbb/models.py` is generated from the API contract. See
[CONTRIBUTING.md](CONTRIBUTING.md) for changes to generated models.

## License

[Apache-2.0](LICENSE).
