How Model Overrides Work on a Per-Request Basis Using RunnableConfig in Open Notebook
Open Notebook’s LangGraph workflows accept a model_id inside the configurable dictionary of a RunnableConfig to override the default language model for individual requests, falling back to thread state or system defaults only when omitted.
This mechanism allows callers to dynamically select which AI model processes a specific interaction without altering global configuration. When an API request arrives, the system extracts any per-request model preference and injects it into the RunnableConfig before invoking the graph, ensuring the selected model is provisioned for that invocation only.
The Three-Tier Resolution Hierarchy
Open Notebook resolves which language model to use through a strict priority chain. This design ensures explicit per-request overrides take precedence over stored preferences while maintaining sensible defaults.
1. Explicit RunnableConfig Override
The highest priority source is the configurable dictionary passed within the RunnableConfig object at invocation time. Graph nodes check config.get("configurable", {}).get("model_id") first. If present, this value is sent directly to the provisioning layer.
2. Thread State Fallback
If the RunnableConfig lacks a model_id, the system checks state.get("model_override"). This allows a model selection to persist across turns in a conversation when set earlier in the thread lifecycle.
3. Default Model Provisioning
When neither the config nor the state specifies a model, the provision_langchain_model helper selects the system default based on the requested model type (e.g., large-context or standard chat).
Implementation in the Source Code
Chat Graph Node
In open_notebook/graphs/chat.py, the call_model_with_messages node implements the resolution logic:
model_id = config.get("configurable", {}).get("model_id") or state.get("model_override")
This line (lines 30-36 in the source) demonstrates the short-circuit evaluation that prioritizes the RunnableConfig over thread state. The resolved model_id is then passed to the provisioning helper.
Source-Chat Graph Node
The source-chat workflow in open_notebook/graphs/source_chat.py follows an identical pattern. Within _call_model_with_source_context_inner, the same configuration check occurs (lines 41-45), ensuring consistency across different graph types.
Model Provisioning Helper
The provision_langchain_model function in open_notebook/ai/provision.py receives the resolved identifier. When model_id is explicitly provided (lines 28-34), it invokes model_manager.get_model(model_id, **kwargs) to return a LangChain-compatible model instance. If the ID is invalid, it raises a configuration error before the node attempts generation.
API Router Integration
The streaming endpoints in api/routers/source_chat.py construct the RunnableConfig explicitly. For example, when handling a request, the router builds:
RunnableConfig(configurable={"thread_id": session_id, "model_id": model_override})
This pattern (lines 44-48) bridges the HTTP API layer with the graph, translating the JSON payload's model_override field into the configuration structure the nodes expect.
Practical Code Examples
Calling the Chat Endpoint with an Override
Send a per-request model selection via the REST API:
import httpx
payload = {
"message": "Explain the difference between GPT-4 and Claude-3.",
"model_override": "gpt-4o-mini" # Per-request model ID
}
resp = httpx.post(
"http://localhost:5055/api/chat/sessions/12345/messages",
json=payload,
)
print(resp.json())
The router converts the model_override field into a RunnableConfig with {"model_id": "gpt-4o-mini"}, which the chat graph node reads during execution.
Direct Graph Invocation
Programmatically override the model when invoking the graph directly:
from langchain_core.runnables import RunnableConfig
from open_notebook.graphs.chat import graph, ThreadState
state: ThreadState = {"messages": [], "model_override": None}
config = RunnableConfig(
configurable={"model_id": "anthropic/claude-3-opus-20240229"}
)
result = graph.invoke(input=state, config=config)
print(result["messages"])
Here, the RunnableConfig explicitly sets the model, bypassing any value stored in state["model_override"].
Streaming Source-Chat with Override
For streaming responses, the override flows through the async generator:
import asyncio
from api.routers.source_chat import stream_source_chat_response
async def demo():
async for chunk in stream_source_chat_response(
session_id="chat_session:abc",
source_id="source:xyz",
message="Summarize the key points.",
model_override="gpt-4o-mini", # Per-request override
):
print(chunk)
asyncio.run(demo())
The stream_source_chat_response function forwards the override into the RunnableConfig, which the source-chat graph consumes exactly as the standard chat graph does.
Summary
- RunnableConfig is the primary vehicle for per-request model overrides in Open Notebook, using the
configurable["model_id"]key. - Resolution follows a strict hierarchy: explicit config override → thread state fallback → system default.
- Both
chat.pyandsource_chat.pygraph nodes implement identical logic to checkconfig.get("configurable", {}).get("model_id")before falling back to state. - The
provision_langchain_modelhelper inai/provision.pyinstantiates the specific model instance based on the resolved identifier. - API routers bridge HTTP requests to graph invocations by constructing RunnableConfig objects that carry the
model_overridefrom the request payload.
Frequently Asked Questions
What happens if I provide both a RunnableConfig model_id and a state model_override?
The RunnableConfig value takes precedence. In open_notebook/graphs/chat.py, the node uses config.get("configurable", {}).get("model_id") or state.get("model_override"), meaning the explicit config is evaluated first and the state is only checked if the config value is absent or evaluates to False.
Can I use any model ID available in the model manager?
Yes, provided the ID exists in the model manager's registry. The provision_langchain_model function calls model_manager.get_model(model_id, **kwargs), which will raise a configuration error if the ID is unrecognized. Valid IDs depend on your Open Notebook installation's configured providers (e.g., OpenAI, Anthropic, or local models).
Does the model override affect the entire thread or just the current request?
The override affects only the current request invocation. However, if your application logic saves the model_id back into state["model_override"] after processing, subsequent turns in the same thread will inherit that selection via the state fallback mechanism until a new RunnableConfig override is provided or the state is cleared.
Is the RunnableConfig approach specific to certain graph types?
No, this is a consistent pattern across Open Notebook's LangGraph implementations. Both the standard chat graph (open_notebook/graphs/chat.py) and the source-chat graph (open_notebook/graphs/source_chat.py) use identical configuration checking logic, as do their respective API routers, ensuring uniform behavior across the application.
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 →