# Open Notebook Context Builder Utility: How RAG Retrieval Works

> Discover how the Context Builder utility assembles sources and notes for retrieval augmented generation RAG feeding retrieved knowledge into LLM prompts.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: tutorial
- Published: 2026-06-22

---

**The Context Builder utility is a flexible orchestration class that assembles relevant sources, notes, and insights into a token-budgeted context payload, enabling retrieval-augmented generation (RAG) by feeding retrieved knowledge into LLM prompts.**

Open Notebook implements a sophisticated RAG retrieval pipeline that transforms vector search results into LLM-ready context windows. At the heart of this system lies the **Context Builder utility** located in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py), a generic framework that fetches, deduplicates, and formats knowledge entities while enforcing strict token constraints. This article examines how the utility assembles context and powers the end-to-end retrieval workflow.

## What Is the Context Builder Utility?

The **Context Builder** is a generic utility class designed to standardize how knowledge objects are prepared for LLM consumption. It accepts flexible parameters through its constructor, retrieves entities from the domain layer, and produces a structured payload that respects configurable token budgets.

### Parameter Parsing and Initialization

The `ContextBuilder.__init__` method receives keyword arguments such as `source_id`, `notebook_id`, `include_insights`, and `max_tokens`. These parameters are stored in `self.params`, with common values extracted into instance attributes for quick access. Developers can also inject a custom `ContextConfig` object to tune behavior, and the `_process_custom_params` hook allows future extensions without modifying core logic.

### Data Gathering from Domain Models

Depending on the supplied IDs, the builder executes specific retrieval methods:

- **`_add_source_context`** – Fetches `Source` objects via `Source.get`, then calls `source.get_context()` and optionally `source.get_insights()` to extract textual content.
- **`_add_notebook_context`** – Retrieves `Notebook` objects via `Notebook.get`, then gathers all linked sources and notes through `notebook.get_sources()` and `notebook.get_notes()`.
- **`_add_note_context`** – Loads individual `Note` objects via `Note.get` and invokes `note.get_context()` for content extraction.

These methods reside in the domain layer under `open_notebook/domain/` and return raw content that the builder will package into structured items.

### ContextItem Creation and Metadata Calculation

For each fetched entity, the builder instantiates a `ContextItem` dataclass containing `id`, `type`, `content`, `priority`, and `token_count`. The `ContextItem.__post_init__` method automatically calculates token counts using `token_utils.token_count`, ensuring accurate budget tracking. Items are registered via the `add_item` method, which populates the internal collection for further processing.

### Deduplication and Prioritization

Before assembly, the builder calls `remove_duplicates` to eliminate redundant IDs from the collection. It then executes `prioritize`, which sorts items by the `priority` field. Priority values can be tuned via `ContextConfig.priority_weights`, allowing certain content types (such as insights or specific sources) to rank higher in the final context.

### Token Budget Enforcement

When `max_tokens` is specified, the `truncate_to_fit` method iteratively drops the lowest-priority items until the cumulative `token_count` falls within the budget. This ensures the LLM receives the most relevant information without exceeding model context limits.

### Response Formatting

Finally, `_format_response` groups items by type into `sources`, `notes`, and `insights`, returning a dictionary that includes the formatted content, total token counts, item tallies, and the original configuration. This standardized output integrates seamlessly with downstream LLM calls.

## How RAG Retrieval Works in Open Notebook

The RAG retrieval pipeline leverages the Context Builder to bridge vector search and LLM generation. The workflow follows five distinct stages:

1. **Query Embedding** – User queries are converted into embeddings by the service defined in [`api/embedding_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/embedding_service.py).
2. **Vector Search** – The embedding is compared against stored vectors in SurrealDB, returning the most similar source IDs and optional note IDs.
3. **Context Construction** – Retrieved IDs are passed to convenience helpers like `build_source_context` or `build_notebook_context`, which instantiate `ContextBuilder`, pull full text, include insights, and enforce token limits.
4. **LLM Integration** – The constructed context dictionary is concatenated with the user prompt and sent to the selected LLM through the Esperanto-based AI layer.
5. **Grounded Response** – The LLM generates a response based on the retrieved knowledge, achieving true RAG behavior.

### Integration with LangGraph

A concrete implementation of this flow appears in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py), where a graph node creates a `ContextBuilder` instance for a specific source. The node configures the builder with `include_insights=True` and a `max_tokens` limit, then forwards the built context to the LLM node for completion.

## Context Builder Implementation Examples

### Building Context for a Single Source

Use the `build_source_context` helper to assemble context from one source, including its insights and respecting a token budget:

```python
from open_notebook.utils.context_builder import build_source_context

async def get_source_context(source_id: str):
    # Limit the final context to 1500 tokens

    ctx = await build_source_context(
        source_id=source_id, 
        include_insights=True, 
        max_tokens=1500
    )
    return ctx

```

### Building Mixed Context from Multiple Entities

For queries spanning multiple sources and notes, use `build_mixed_context`:

```python
from open_notebook.utils.context_builder import build_mixed_context

async def mixed_context(source_ids, note_ids, notebook_id=None):
    ctx = await build_mixed_context(
        source_ids=source_ids,
        note_ids=note_ids,
        notebook_id=notebook_id,
        max_tokens=2000,
    )
    return ctx

```

### Direct Instantiation in LangGraph Nodes

For custom graph implementations, instantiate `ContextBuilder` directly:

```python
from open_notebook.utils.context_builder import ContextBuilder

async def build_context_for_source(source_id: str):
    builder = ContextBuilder(
        source_id=source_id,
        include_insights=True,
        max_tokens=1200,
    )
    return await builder.build()

```

All three patterns utilize the same core class, ensuring consistent token counting, deduplication, and formatting across the application.

## Key Source Files

The RAG retrieval pipeline and Context Builder utility are implemented across the following files:

- **[`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py)** – Contains the generic `ContextBuilder` class, `ContextConfig`, `ContextItem` dataclass, and helper functions (`build_source_context`, `build_mixed_context`).
- **[`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py)** – Demonstrates graph-node integration, showing how context is built for a source and passed to the LLM.
- **[`api/embedding_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/embedding_service.py)** – Generates query embeddings and performs vector similarity search against SurrealDB.
- **[`api/routers/context.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/context.py)** – Exposes the `/context` HTTP endpoint that returns ready-made RAG payloads to clients.
- **`open_notebook/domain/`** – Houses `Source`, `Notebook`, and `Note` models with methods like `get_context()` and `get_insights()` consumed by the builder.

## Summary

- The **Context Builder utility** standardizes the assembly of knowledge objects into LLM-ready payloads through the `ContextBuilder` class in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py).
- It supports **token-budget enforcement** via `truncate_to_fit`, ensuring contexts never exceed specified limits by dropping low-priority items first.
- The utility performs automatic **deduplication** and **prioritization** using configurable weights, maximizing the relevance of included content.
- **RAG retrieval** integrates vector search (SurrealDB via [`api/embedding_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/embedding_service.py)) with context construction, feeding retrieved IDs into the builder to ground LLM responses.
- Helper functions like `build_source_context` and `build_mixed_context` provide convenient async interfaces for common use cases.

## Frequently Asked Questions

### How does the Context Builder enforce token limits?

The Context Builder uses the `truncate_to_fit` method, which calculates the total token count of all assembled `ContextItem` objects and iteratively removes the lowest-priority items until the sum fits within the `max_tokens` parameter specified during initialization.

### What types of content can the Context Builder assemble?

The utility can process **Source** objects (with optional insights), **Notebook** objects (including all linked sources and notes), and individual **Note** objects. Each type is handled by dedicated private methods (`_add_source_context`, `_add_notebook_context`, `_add_note_context`) that fetch content from the domain models.

### How does RAG retrieval integrate with the vector database?

When a user submits a query, the system generates an embedding via [`api/embedding_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/embedding_service.py) and performs a similarity search against vectors stored in SurrealDB. The returned source and note IDs are passed to the Context Builder, which retrieves the full text and formats it for the LLM, completing the retrieval-augmented generation loop.

### Can developers customize the Context Builder behavior?

Yes, developers can inject a custom `ContextConfig` object to adjust `priority_weights` for different content types, or use the `_process_custom_params` extension hook to handle additional keyword arguments without modifying the core `ContextBuilder` class.