# How source_chat.py Differs from chat.py in Context Building and Tool Usage

> Discover how source_chat.py builds richer document context and uses `ContextBuilder` for LLM insights, improving upon chat.py's generic notebook conversations. Learn the key differences.

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

---

**While [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) provides a lightweight wrapper for generic notebook conversations using direct prompt rendering, [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) implements a source-centric workflow that leverages `ContextBuilder` to assemble rich document context, insights, and metadata before LLM invocation.**

The `open-notebook` repository provides two distinct LangGraph workflows for AI interactions. Understanding how [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) differs from [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) in context building and tool usage is essential for developers implementing document-centric chat features versus general conversational interfaces.

## State Definitions and Architectural Purpose

The fundamental divergence begins with how each module defines its state graph and intended use case.

### Generic Chat State in chat.py

The [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) module operates on a `ThreadState` designed for flexibility. This state includes a `messages` list, an optional `Notebook` object, and generic `context` and `context_config` fields. According to the source code in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), this structure supports lightweight, notebook-wide conversations without requiring specific document attachments.

### Source-Centric State in source_chat.py

In contrast, [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) defines a `SourceChatState` that mandates a `source_id` field. As implemented in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py), this state includes the required source identifier, optional `Source` and `SourceInsight` objects, and a `context_indicators` map that explicitly records which source and insight IDs were referenced during context assembly. This architecture enables deep document analysis by binding the conversation to specific source materials.

## Context Building Methodologies

The two modules employ radically different strategies for preparing context windows before LLM invocation.

### Direct System Prompt Rendering

The [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) workflow skips explicit context building. In lines 32-33 of [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the system prompt renders directly from the supplied state:

```python
system_prompt = Prompter(prompt_template="chat/system").render(data=state)

```

This approach passes the raw state data to the `"chat/system"` template without intermediate processing or token management.

### ContextBuilder Integration

Conversely, [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) utilizes the `ContextBuilder` helper class to construct a token-bounded context string. Lines 66-73 of [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py) demonstrate this heavyweight approach:

```python
context_builder = ContextBuilder(
    source_id=source_id,
    include_insights=True,
    include_notes=False,
    max_tokens=50000
)
context_data = new_loop.run_until_complete(context_builder.build())

```

The `ContextBuilder`, defined in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py), fetches the full source text, associated insights, and optional notes, then formats them into a structured string that respects the `max_tokens` limit. This enriched context feeds into the `"source_chat/system"` prompt template, which expects `source`, `insights`, and formatted `context` variables.

## Tool Usage and Async Processing

Beyond context construction, the modules differ significantly in auxiliary tooling and asynchronous execution patterns.

### Minimal Tooling in Generic Chat

The [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) workflow relies solely on `provision_langchain_model` from [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py) to instantiate the language model. It handles async operations through a simple helper that creates a new event loop when needed, streamlining the execution path for standard conversational use cases.

### Enhanced Tooling in Source Chat

The [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) implementation incorporates several specialized utilities:

- **ContextBuilder** – Gathers source-specific data asynchronously
- **classify_error** – Maps raw exceptions to domain-specific error types
- **extract_text_content** and **clean_thinking_content** – Post-process the LLM's response to extract clean text

Additionally, [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) manages **two distinct async steps** (context building and model provisioning). Lines 80-90 and 152-162 implement a sophisticated fallback mechanism: each step first checks for an existing event loop, and if one exists, offloads the work to a thread-pool executor with a fresh loop to prevent async context conflicts.

## Prompt Templates and Response Structures

### Template Differences

The prompt templates reflect the architectural divergence. [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) uses the generic `"chat/system"` template, while [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) employs `"source_chat/system"`, which is designed to render structured source metadata and insight summaries alongside the conversation history.

### Output Payload Variations

The return structures differ substantially. [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) returns only the cleaned `messages` list. However, [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) returns a comprehensive payload including:

- Cleaned `messages`
- The resolved `source` object
- A list of `insights`
- The formatted `context` string
- `context_indicators` tracking which IDs were referenced

Both workflows compile into LangGraph objects (`graph` and `source_chat_graph` respectively) and utilize an SQLite checkpoint file (`LANGGRAPH_CHECKPOINT_FILE`) for state persistence.

## Practical Implementation Examples

To invoke the generic chat workflow:

```python
from open_notebook.graphs.chat import graph as chat_graph

state = {
    "messages": [],
    "notebook": None,
    "context": None,
    "model_override": "gpt-4o-mini",
}

result = chat_graph.ainvoke(
    state, 
    config={"configurable": {"model_id": "gpt-4o-mini"}}
)
print(result["messages"].content)

```

To utilize the source-centric workflow:

```python
from open_notebook.graphs.source_chat import source_chat_graph

state = {
    "messages": [],
    "source_id": "src_12345",
    "model_override": "claude-3-5-sonnet",
}

result = source_chat_graph.ainvoke(
    state,
    config={"configurable": {"model_id": "claude-3-5-sonnet"}}
)

print("AI reply:", result["messages"].content)
print("Source title:", result["source"]["title"])
print("Referenced insight IDs:", result["context_indicators"]["insights"])

```

## Summary

- **[`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py)** implements a lightweight, generic conversation wrapper using direct prompt rendering from `ThreadState` and minimal tooling.
- **[`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py)** enforces a source-centric workflow via `SourceChatState`, utilizing `ContextBuilder` to assemble rich document context with token management.
- **Context construction** differs between direct template rendering (chat.py) and async `ContextBuilder` invocation with insights integration (source_chat.py).
- **Tool usage** expands in [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) to include error classification, content extraction, and sophisticated async handling with thread-pool executors.
- **Output structures** vary from simple message lists to enriched payloads containing source metadata, insights, and context indicators.

## Frequently Asked Questions

### What is the primary architectural difference between chat.py and source_chat.py?

The primary difference lies in state coupling: [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) uses a flexible `ThreadState` for general notebook conversations, while [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) requires a `source_id` in its `SourceChatState` to bind the conversation to a specific document and its associated insights.

### How does ContextBuilder affect token management in source_chat.py?

The `ContextBuilder` accepts a `max_tokens` parameter (set to 50000 by default in the source code) and selectively includes content based on `include_insights` and `include_notes` flags, ensuring the assembled context fits within model context windows while preserving the most relevant source material.

### Can source_chat.py operate without source insights?

Yes, you can initialize `ContextBuilder` with `include_insights=False`, but the `source_id` remains mandatory in the `SourceChatState`. The workflow will still build context around the source text alone, though it will lack derived insight summaries.

### Why does source_chat.py use a thread-pool executor for async operations?

Because [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py) performs two separate async steps (context building and model provisioning), it checks for existing event loops at lines 80-90 and 152-162. When an existing loop is detected, it offloads work to a thread-pool executor with a fresh loop to prevent event loop conflicts in environments like Jupyter notebooks or existing async applications.