# Chat, source_chat, and ask Graphs in LangGraph: Open Notebook's Three Conversation Patterns

> Explore Open Notebook's chat source_chat and ask graphs in LangGraph. Understand their distinctions for notebook chat research driven Q&A and vector search applications.

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

---

**Open Notebook implements three distinct LangGraph graphs—`chat`, `source_chat`, and `ask`—that serve different conversational purposes ranging from simple notebook chat to research-driven Q&A with vector search.**

Open Notebook leverages LangGraph to orchestrate LLM interactions through specialized state machines. Each graph targets a specific interaction pattern: generic conversation, source-grounded dialogue, or multi-step research synthesis.

## The Three Graphs at a Glance

Open Notebook defines three distinct LangGraph graphs that drive the conversational back-ends:

| Graph | Primary Purpose | State Structure | When It Is Used |
|-------|---------------|----------------|-----------------|
| **chat** | Simple one-turn chat with a notebook | `ThreadState` – holds messages, optional `Notebook`, raw `context` | Generic chat interactions without source-specific context |
| **source_chat** | Source-aware conversation | `SourceChatState` – contains `source_id`, `SourceInsight`s, built `context` | When user refers to a specific source (e.g., "Explain Source 123") |
| **ask** | Multi-step research workflow | `ThreadState` with `Strategy`, intermediate answers | Research questions requiring vector search and synthesis |

## The Chat Graph: Simple Conversational State

The `chat` graph handles basic conversational interactions with minimal overhead.

### State Structure and Nodes

In [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the graph uses `ThreadState` to persist:

- A message list
- An optional `Notebook` object
- Optional raw `context` and `context_config`
- An optional `model_override`

The primary node is `call_model_with_messages`, which:

1. Builds a system prompt from the `chat/system` Jinja template
2. Appends user messages
3. Provisions a LangChain chat model via `provision_langchain_model`
4. Returns the cleaned AI reply

No additional data fetching occurs—sources and insights are not retrieved unless explicitly provided in the initial state.

### When to Use Chat

Use this graph for generic chat interactions where the user wants a conversational reply or wishes to continue a notebook-wide thread without needing source-specific context.

## The Source Chat Graph: Context-Aware Source Interactions

The `source_chat` graph enables conversations grounded in specific documents, PDFs, or notebook entries.

### State Structure and Context Building

Defined in [`open_notebook/graphs/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/source_chat.py), `SourceChatState` contains:

- The message list
- Target `source_id` and optional `Source` object
- A list of `SourceInsight`s
- The built `context` string
- `context_indicators` mapping usage

The graph leverages `ContextBuilder` (from [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py)) to construct rich source-specific context before invoking the model.

### Execution Flow

The main node `call_model_with_source_context`:

1. Builds source-specific context using `ContextBuilder` (including insights, notes, and metadata)
2. Renders the context into a system prompt via the `source_chat/system` template
3. Provisions a chat-type model (max 8192 tokens)
4. Enriches state with `source`, `insights`, and formatted context indicators

### When to Use Source Chat

Use `source_chat` when the user explicitly refers to a particular source (e.g., "Explain the findings from Source 123") and the system should ground the reply in that source's content and derived insights.

## The Ask Graph: Multi-Step Research Workflows

The `ask` graph implements a complex, multi-step "question-answer" workflow involving strategy generation, parallel vector searches, and answer synthesis.

### State Structure

The graph uses two state types:

- `ThreadState`: Stores the original question, a `Strategy` (JSON-encoded), intermediate answers, and final answer
- `SubGraphState`: Used by the `provide_answer` sub-graph for search terms, instructions, raw results, and retrieved IDs

### Workflow Nodes

1. **Strategy Generation**: `call_model_with_messages` prompts the model with `ask/entry` to produce a JSON-encoded `Strategy` containing search queries
2. **Parallel Execution**: `trigger_queries` creates **Send** edges to the `provide_answer` sub-graph for each search in the strategy
3. **Answer Synthesis**: `provide_answer` runs vector search (`vector_search`), then feeds results to a second LLM using the `ask/query_process` template
4. **Final Assembly**: `write_final_answer` stitches intermediate answers using the `ask/final_answer` template

### Model Specialization

Unlike `chat` and `source_chat`, the `ask` graph provisions three different models:

- `strategy_model` for query generation
- `answer_model` for processing search results
- `final_answer_model` for synthesis

### When to Use Ask

Use `ask` for "research" questions where the system must retrieve relevant documents, synthesize information, and present a concise answer.

## Key Architectural Differences

### State Definitions

Each graph declares a `TypedDict` that tells LangGraph which fields persist between steps. The `chat` graph's state is minimal, while `ask` and `source_chat` carry richer, domain-specific data structures.

### Prompt Templates

- `chat/system` (in [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py)): Generic conversational prompt
- `ask/entry`, `ask/query_process`, `ask/final_answer`: Chain of prompts for research workflows
- `source_chat/system`: Template receiving fully-built source context from `ContextBuilder`

### Execution Model

**Chat and Source Chat**: Executed from sync entry points; they spin up temporary event loops to run the async provisioner.

**Ask**: Fully async because it must `await` vector search results and multiple LLM calls across sub-graphs.

### Context Building

Only `source_chat` builds complex context via `ContextBuilder`. The `ask` graph builds context dynamically through `vector_search` operations, retrieving relevant chunks across the corpus rather than focusing on a single pre-identified source.

## Implementation Examples

### Simple Chat Invocation

```python

# From api/chat_service.py

from open_notebook.graphs.chat import graph as chat_graph

async def chat_endpoint(state: dict, model_id: str):
    # State conforms to ThreadState (messages, notebook, …)

    result = await chat_graph.ainvoke(
        state, 
        config={"configurable": {"model_id": model_id}}
    )
    return result["messages"]  # AIMessage for client

```

### Research Ask Workflow

```python

# From api/ask_service.py

from open_notebook.graphs.ask import graph as ask_graph

async def ask_endpoint(question: str):
    initial_state = {"question": question}
    # Orchestrates strategy, vector searches, and final answer

    result = await ask_graph.ainvoke(initial_state)
    return result["final_answer"]

```

### Source-Aware Chat

```python

# From api/source_chat_service.py

from open_notebook.graphs.source_chat import source_chat_graph

async def source_chat_endpoint(source_id: str, messages: List[dict]):
    state = {
        "source_id": source_id,
        "messages": messages,
    }
    result = await source_chat_graph.ainvoke(state)
    return result["messages"]  # Reply enriched with source context

```

## Summary

- **chat**: Use for quick conversational replies with minimal state; relies on `ThreadState` and generic system prompts
- **source_chat**: Use when grounding responses in specific sources; leverages `ContextBuilder` and `SourceChatState` to include insights and metadata
- **ask**: Use for research workflows requiring vector search; implements a multi-step strategy with parallel sub-graph execution and specialized model provisioning

## Frequently Asked Questions

### What is the difference between ThreadState and SourceChatState?

`ThreadState` serves as the base state for general conversation in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), holding messages and optional notebook context. `SourceChatState` extends this pattern to include source-specific fields like `source_id`, `SourceInsight`s, and `context_indicators`, enabling the graph to track which source materials informed the response.

### Can I use the ask graph for simple chat queries?

While technically possible, the `ask` graph introduces unnecessary overhead for simple queries. It provisions three separate models and executes parallel vector searches through sub-graphs. For straightforward conversation, use the `chat` graph to minimize latency and token consumption.

### How does source_chat build context differently from ask?

The `source_chat` graph uses `ContextBuilder` (in [`open_notebook/utils/context_builder.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/context_builder.py)) to construct a rich textual representation of the source, including extracted insights and notes. The `ask` graph instead builds context dynamically through `vector_search` operations, retrieving relevant chunks across the corpus rather than focusing on a single pre-identified source.

### Are all three graphs async by default?

Only the `ask` graph is fully async from the entry point. Both `chat` and `source_chat` are designed to run from synchronous contexts, spinning up temporary event loops internally to handle async model provisioning via `provision_langchain_model`.