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

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, SourceInsights, 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, 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, SourceChatState contains:

  • The message list
  • Target source_id and optional Source object
  • A list of SourceInsights
  • The built context string
  • context_indicators mapping usage

The graph leverages ContextBuilder (from 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): 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


# 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


# 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


# 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, holding messages and optional notebook context. SourceChatState extends this pattern to include source-specific fields like source_id, SourceInsights, 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) 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →