# Context Builder Mechanism for RAG Conversations in Open-Notebook

> Explore the ContextBuilder mechanism for RAG conversations in Open-Notebook. Learn how it orchestrates prompt context assembly, source retrieval, deduplication, and prioritization to optimize RAG performance.

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

---

**The ContextBuilder is an async-aware orchestrator that assembles retrieval-augmented generation (RAG) prompt contexts by fetching sources, notes, and insights from the graph database, deduplicating and prioritizing them by relevance, and truncating results to a configurable token budget before returning a structured JSON payload.**

Open-Notebook implements a sophisticated context builder mechanism for RAG conversations through the `ContextBuilder` utility located in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py). This system acts as a configurable pipeline that transforms raw graph data into optimized LLM-ready context windows. Understanding this architecture is essential for developers extending the RAG capabilities or integrating custom knowledge sources into the conversation flow.

## Core Data Structures

The context builder relies on three primary dataclasses that define how knowledge is packaged and prioritized before reaching the language model.

### ContextItem Dataclass

The **`ContextItem`** class represents a single atomic unit of context within the RAG pipeline. Defined at lines 21-30 of [`context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/context_builder.py), this dataclass encapsulates an ID, content type (source, note, or insight), raw content, priority score, and token count. Each item lazily computes its token count via `open_notebook/utils/token_utils`, ensuring the builder can enforce budget constraints without requiring pre-computed sizes from callers.

### ContextConfig Dataclass

**`ContextConfig`** (lines 38-48) centralizes the inclusion rules and ranking logic for the context assembly process. This configuration object stores boolean flags for including insights and notes, defines per-type priority weights, and sets the maximum token limit. When instantiated, it translates high-level parameters like `include_insights=True` into concrete filtering rules that the orchestrator applies during the build phase.

### ContextBuilder Orchestrator

The **`ContextBuilder`** class (lines 59-65) serves as the main entry point and pipeline controller. Its constructor accepts flexible keyword arguments—including `source_id`, `notebook_id`, `max_tokens`, and custom configuration objects—and stores them in `self.params` for extensibility. If no explicit `ContextConfig` is provided, the builder automatically constructs one from the extracted boolean flags and token limits.

## The Context Building Pipeline

The `ContextBuilder.build()` method executes a seven-step pipeline that transforms database records into a formatted response dictionary.

### Step 1: Initialization and Reset

The build process begins by clearing the internal `self.items` list to ensure clean state between invocations. This reset prevents context leakage when the same builder instance processes multiple consecutive RAG queries.

### Step 2: Source Context Retrieval

When a `source_id` is supplied, the **`_add_source_context`** method (lines 42-84) fetches the corresponding `Source` record from the graph database. It extracts either short or long-form context via `Source.get_context()`, creates a `ContextItem`, and optionally attaches related insights if `include_insights` is enabled.

### Step 3: Notebook Context Expansion

If processing a `notebook_id`, the **`_add_notebook_context`** method (lines 10-27) loads the `Notebook` model and iterates over its associated sources and notes. This method delegates to specialized helpers that convert `Notebook.get_sources()` and `Notebook.get_notes()` results into individual `ContextItem` instances, allowing entire research notebooks to be compressed into single context windows.

### Step 4: Custom Parameter Processing

The pipeline includes a hook method **`_process_custom_params`** (lines 96-104) designed for subclassing. Developers can override this to inject domain-specific filtering logic—such as date-range constraints or semantic similarity thresholds—without modifying the core deduplication and truncation logic.

### Step 5: Deduplication and Prioritization

The **`_remove_duplicates`** method (lines 51-63) eliminates `ContextItem` instances with duplicate IDs that may arise when notebooks contain overlapping sources. Subsequently, **`_prioritize`** (lines 15-18) sorts the remaining items by their numeric priority score in descending order, ensuring the most relevant content appears first in the final context window.

### Step 6: Token Budget Enforcement

The **`truncate_to_fit`** method (lines 20-47) enforces the `max_tokens` constraint by calculating the cumulative `token_count` of all items and dropping the lowest-priority entries until the total fits within budget. This greedy algorithm preserves high-priority context while gracefully degrading when source material exceeds LLM context limits.

### Step 7: Response Formatting

Finally, **`_format_response`** (lines 67-107) groups items by type, computes aggregate statistics, and returns a plain dictionary with optional notebook metadata. If any step raises an exception, the builder wraps it in `DatabaseOperationError`, enabling the API layer in [`api/context_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/context_service.py) to translate failures into appropriate 5xx HTTP responses.

## Token Counting Strategy

Each `ContextItem` computes its token footprint lazily through the `token_count` property, which delegates to utilities in `open_notebook/utils/token_utils`. This design decouples tokenization from data retrieval, allowing the builder to make truncation decisions based on actual encoder counts rather than heuristic character estimates. The lazy evaluation ensures that tokenization only occurs for items that survive the deduplication and prioritization phases, optimizing performance when processing large notebooks.

## Convenience Functions for Common Use Cases

Open-Notebook exposes three async helper functions that wrap the `ContextBuilder` for typical RAG scenarios:

- **`build_notebook_context`** – Generates comprehensive context for an entire notebook, including all sources, notes, and insights, truncated to the specified token limit.
- **`build_source_context`** – Creates focused context from a single source document with optional insight inclusion, ideal for deep-dive questions on specific materials.
- **`build_mixed_context`** – Assembles arbitrary combinations of source IDs and note IDs, supporting ad-hoc RAG queries that span multiple disconnected resources.

All three functions instantiate `ContextBuilder` with appropriate arguments and forward to `builder.build()`, as implemented at lines 119-146 of [`context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/context_builder.py).

## Integration with RAG Workflows

The context builder integrates directly into Open-Notebook's LangGraph workflows through [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py), where it serves as a node in the retrieval pipeline. During conversation execution, the graph invokes the builder to assemble historical context before passing the formatted dictionary to the language model. HTTP clients can also trigger context generation via [`api/context_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/context_service.py), which exposes the builder's capabilities through REST endpoints.

## Summary

- The **ContextBuilder** in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py) orchestrates RAG context assembly through a configurable, async-aware pipeline.
- **ContextItem** and **ContextConfig** dataclasses standardize how knowledge units are packaged, prioritized, and filtered.
- The build pipeline executes seven distinct phases: initialization, source retrieval, notebook expansion, custom processing, deduplication, prioritization, and token-budget truncation.
- **Token counting** occurs lazily via `open_notebook/utils/token_utils`, ensuring accurate budget enforcement without unnecessary computation.
- Convenience functions (`build_notebook_context`, `build_source_context`, `build_mixed_context`) provide simplified interfaces for common retrieval patterns.

## Frequently Asked Questions

### How does the ContextBuilder handle duplicate content across multiple sources?

The builder calls **`_remove_duplicates`** during the pipeline execution, which scans the collected `ContextItem` instances and removes any with duplicate IDs. This prevents the same source document from consuming token budget multiple times when it appears in both a direct source query and a notebook collection.

### What happens when the context exceeds the maximum token limit?

The **`truncate_to_fit`** method implements a priority-based truncation algorithm. It sorts items by relevance score (highest first), calculates cumulative token counts, and removes the lowest-priority items until the total fits within the `max_tokens` budget. This ensures the most important context survives while gracefully degrading less relevant material.

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

Yes, through the **`ContextConfig`** dataclass or constructor flags like `include_insights` and `include_notes`. Developers can also subclass `ContextBuilder` and override **`_process_custom_params`** to implement domain-specific filtering logic—such as semantic similarity thresholds or date-range constraints—without modifying the core truncation and formatting logic.

### Where is the ContextBuilder invoked during actual chat conversations?

The builder is integrated into the LangGraph workflow defined in **[`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py)**, where it serves as a retrieval node that assembles historical context before LLM invocation. Additionally, HTTP-based RAG requests are handled by **[`api/context_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/context_service.py)**, which instantiates the builder and returns formatted context through REST endpoints.