# How the Open Notebook Context Builder Constructs Prompts for AI Models

> Discover how the Open Notebook context builder constructs AI prompts by aggregating, deduplicating, and formatting notes and insights for efficient model token use.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-29

---

**The Open Notebook context builder constructs prompts for AI models by aggregating sources, notes, and insights into prioritized, token-aware context items that are deduplicated, truncated to fit within model limits, and formatted into a dictionary ready for prompt template interpolation.**

The **context builder** is the core utility in the `lfnovo/open-notebook` repository that transforms raw notebook data into LLM-ready prompt content. Located in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py), this asynchronous pipeline gathers data from multiple domain models, applies intelligent filtering, and ensures the final output respects token budgets before injection into AI prompts.

## How the Context Builder Works

The `ContextBuilder` class follows a modular, asynchronous workflow designed to handle complex data aggregation while maintaining strict token constraints.

### Initializing the Builder

When instantiating `ContextBuilder(**kwargs)`, the `__init__` method (lines 65‑98) stores all input parameters—including `source_id`, `notebook_id`, `include_insights`, and `max_tokens`—in `self.params`. If no `ContextConfig` is provided, the builder creates a default configuration with predefined priority weights for different content types.

### Orchestrating the Build Process

The `await builder.build()` method (lines 105‑138) serves as the main orchestration entry point. It first clears any previous context items, then sequentially invokes private helper methods to load data for the requested entities based on the initialization parameters.

### Adding Source Context

For individual source retrieval, `_add_source_context` (lines 142‑202) loads the `Source` record from [`open_notebook/domain/source.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/source.py) and fetches either short or long content snippets via `source.get_context`. When `include_insights` is enabled, it also retrieves processed insights through `source.get_insights`. Each retrieved piece is wrapped in a `ContextItem` object with a priority value derived from `self.context_config.priority_weights`.

### Adding Notebook Context

When processing entire notebooks, `_add_notebook_context` (lines 210‑250) loads the `Notebook` record and iterates over its associated sources and notes. This method respects custom configuration maps in `ContextConfig` that specify whether to include "insights", "full content", or exclude specific items entirely.

### Adding Note Context

The `_add_note_context` method (lines 254‑288) handles individual note IDs by fetching the `Note` record from [`open_notebook/domain/note.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/note.py), determining the appropriate content length, wrapping it in a `ContextItem`, and adding it to the builder's internal collection.

### Processing Custom Parameters

The `_process_custom_params` hook (lines 296‑304) allows subclasses to extend the builder's behavior. By default, it only logs any `custom_*` kwargs passed during initialization, providing a clean extension point for specialized context processing.

### Deduplicating and Prioritizing Content

Before finalization, the builder calls `remove_duplicates` (lines 351‑363) to eliminate any `ContextItem` instances sharing the same `id`, ensuring no source or note appears twice in the context. Then `prioritize` (lines 315‑319) sorts the remaining items by their priority value in descending order (highest first). The default `ContextConfig` assigns weights of **source:100**, **insight:75**, and **note:50**.

### Enforcing Token Limits

If `max_tokens` is specified, `truncate_to_fit` (lines 320‑349) walks the sorted item list from the end (lowest priority) and removes items until the total token count—computed via `token_count` from [`open_notebook/utils/token_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/token_utils.py)—falls within the specified limit. This guarantees the final prompt will not exceed the model's context window.

### Formatting the Final Response

The `_format_response` method (lines 367‑416) groups the processed items by type (`sources`, `notes`, `insights`), calculates the total token count, and returns a structured dictionary. This output format allows downstream LangGraph workflows to directly interpolate the content into Jinja2 prompt templates without additional processing.

## Using the Context Builder in Practice

The repository provides three convenience functions that wrap the `ContextBuilder` class for common use cases (lines 421‑495).

### Building Context for a Notebook

To construct context for an entire notebook with token ceiling enforcement:

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

async def get_notebook_prompt(notebook_id: str):
    ctx = await build_notebook_context(
        notebook_id=notebook_id,
        max_tokens=2048,
    )
    # Returns: {"sources": [...], "notes": [...], "insights": [...], ...}

    return ctx

```

### Building Context for Individual Sources

For single-source context generation including AI-generated insights:

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

async def source_prompt(source_id: str):
    ctx = await build_source_context(
        source_id=source_id,
        include_insights=True,
        max_tokens=1500,
    )
    return ctx

```

### Mixing Sources and Notes

To combine arbitrary sources and notes into a single context:

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

async def mixed_prompt(source_ids, note_ids):
    ctx = await build_mixed_context(
        source_ids=source_ids,
        note_ids=note_ids,
        max_tokens=1800,
    )
    return ctx

```

## Key Implementation Details

The context builder leverages **asynchronous I/O** throughout the pipeline, utilizing FastAPI's async stack and the SurrealDB driver to efficiently handle large notebooks. Key architectural components include:

- **Domain models**: `Source` ([`open_notebook/domain/source.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/source.py)), `Notebook` ([`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py)), and `Note` ([`open_notebook/domain/note.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/note.py)) provide context retrieval methods
- **Token management**: [`open_notebook/utils/token_utils.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/token_utils.py) handles LLM token counting for truncation decisions
- **Consumer integration**: The `search_and_synthesize` LangGraph workflow consumes the builder's output and interpolates it into prompt templates without additional trimming logic

## Summary

- The `ContextBuilder` class in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py) aggregates data from notebooks, sources, and notes into unified context objects.
- Content is wrapped in `ContextItem` instances with configurable priority weights (sources:100, insights:75, notes:50 by default).
- **Deduplication** ensures unique content, while **token-aware truncation** enforces model limits by removing lowest-priority items first.
- Three convenience functions—`build_notebook_context`, `build_source_context`, and `build_mixed_context`—provide simple async interfaces for common use cases.
- The final output is a dictionary formatted for direct insertion into Jinja2 prompt templates, used by downstream AI workflows.

## Frequently Asked Questions

### How does the context builder handle token limits?

The context builder enforces token limits through the `truncate_to_fit` method, which removes lowest-priority items until the total token count falls within the specified `max_tokens` parameter. This ensures the final prompt never exceeds the model's context window while preserving the most important content.

### Can I customize which content types are included in the context?

Yes, the `ContextConfig` class allows customization of content inclusion through `sources` and `notes` maps that specify whether to include "insights", "full content", or exclude items entirely. You can also override the default priority weights to change how different content types are ranked during truncation.

### Is the context builder compatible with async workflows?

Absolutely. The entire `ContextBuilder` pipeline is implemented asynchronously using `async`/`await` patterns, making it compatible with FastAPI endpoints and LangGraph workflows. All database operations through SurrealDB use async drivers to maintain efficiency when processing large notebooks.

### What is the difference between `build_notebook_context` and `build_source_context`?

`build_notebook_context` aggregates all sources and notes belonging to a specific notebook ID, while `build_source_context` focuses on a single source and optionally includes its AI-generated insights. The former is ideal for comprehensive queries across entire notebooks, while the latter is optimized for deep-dives into specific source materials.