How `source_chat.py` LangGraph Enables Chatting with Specific Data Sources in Open Notebook
TLDR: The source_chat.py graph in lfnovo/open-notebook retrieves a single source’s text and insights, renders them into a dedicated system prompt, provisions an LLM, and cleans the output so every conversation turn stays grounded in that specific document, web page, or video.
The Open Notebook backend is designed to isolate knowledge and let users interrogate it directly. One of its core features is chatting with specific data sources through the LangGraph defined in open_notebook/graphs/source_chat.py. By funneling a single source_id through a pipeline of context assembly, prompt rendering, and model invocation, the graph ensures the LLM never drifts from the material it is meant to reference.
Gathering Source Context with ContextBuilder
The graph node call_model_with_source_context starts by instantiating ContextBuilder from open_notebook/utils/context_builder.py (lines 59–73). The builder is initialized with the target source_id and performs three critical lookups:
- It loads the source record itself.
- It pulls the source’s full text or a shortened representation.
- It fetches any linked
SourceInsightobjects viaSource.getandsource.get_insights.
Once the raw pieces are collected, the builder’s internal _format_response method (lines 90–108) assembles them into a structured dictionary. This payload includes metadata such as token counts and item counts, giving downstream nodes an accurate picture of how much context is being carried into the chat.
from open_notebook.models.source import Source
from open_notebook.utils.context_builder import ContextBuilder
# Conceptual flow mirrored by ContextBuilder (lines 59-73)
source = Source.get("source-id")
insights = source.get_insights()
# The builder encapsulates the lookups above and produces the
# final payload via _format_response (lines 90-108)
builder = ContextBuilder(source_id="source-id")
Rendering the System Prompt for Source-Aware Chat
The structured context is passed to a Prompter using the template key source_chat/system (lines 28–30 in source_chat.py). This Jinja2 template injects the source details, insights, and a formatted context string produced by _format_source_context (lines 90–125). The resulting system prompt explicitly tells the LLM which information it may reference and defines its behavioral boundaries for the conversation.
from open_notebook.ai.prompter import Prompter
# Lines 28-30 of open_notebook/graphs/source_chat.py
prompter = Prompter(prompt_template="source_chat/system")
# The prompter loads the Jinja2 template and injects:
# - source metadata
# - linked SourceInsight objects
# - the formatted context block from _format_source_context (lines 90-125)
Provisioning the LLM in a Sync-Friendly Node
Because LangGraph nodes run synchronously, the graph cannot directly await the async provision_langchain_model helper imported from open_notebook.ai.provision. Instead, the node spins up a fresh event loop (lines 33–71) to run the coroutine. This wrapper also respects any per-request model_override or model ID supplied through the LangGraph RunnableConfig.
import asyncio
from open_notebook.ai.provision import provision_langchain_model
# Lines 33-71: bridge async provisioning into the sync graph node
loop = asyncio.new_event_loop()
try:
# The call respects model_override or model_id from RunnableConfig
model = loop.run_until_complete(provision_langchain_model())
finally:
loop.close()
Invoking the Model and Post-Processing the Response
With the model ready, the node calls model.invoke(payload) at line 73 to produce the chat response. The raw output then passes through two cleaning stages before it reaches the user:
extract_text_contentstrips out any markup tags from the LLM response.clean_thinking_contentremoves internal reasoning artifacts or "thinking" sections.
# Line 73: generate the chat response
response = model.invoke(payload)
# Post-processing sanitizes the raw output
cleaned = extract_text_content(response)
final_answer = clean_thinking_content(cleaned)
This final sequence guarantees that the user receives a clean, strictly source-grounded reply rather than raw model noise.
Summary
open_notebook/graphs/source_chat.pydefines a LangGraph that isolates a single source for focused conversation.- The
call_model_with_source_contextnode usesContextBuilder(lines 59–73) and_format_response(lines 90–108) to assemble source text and insights. - A
Prompterwith thesource_chat/systemtemplate (lines 28–30) renders a system prompt that anchors the LLM to the source material. - Async model provisioning is bridged into the sync graph via a new event loop (lines 33–71), honoring
RunnableConfigoverrides. model.invoke(payload)(line 73) generates the answer, which is then sanitized byextract_text_contentandclean_thinking_content.
Frequently Asked Questions
How does source_chat.py keep the LLM focused on one source?
The graph node call_model_with_source_context creates a ContextBuilder that loads only the specified source_id, its full text, and linked SourceInsight objects. This material is rendered into a dedicated system prompt, so the model receives no outside context.
Why does the graph use a new event loop to provision the model?
provision_langchain_model is an async function, but LangGraph nodes execute synchronously. The node wraps the call in asyncio.new_event_loop() (lines 33–71) so it can run the coroutine without blocking the main application loop.
What kind of metadata does ContextBuilder include in the context payload?
The builder’s _format_response method (lines 90–108 of context_builder.py) appends metadata such as token counts and item counts, letting the system track exactly how much source material is injected into the prompt.
How is the raw LLM output cleaned before reaching the user?
After model.invoke(payload) (line 73), the response is passed through extract_text_content to remove markup tags, and then clean_thinking_content to strip internal reasoning artifacts, yielding a clean final answer.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →