Skip to content

LangGraph Integration

A registered graph name becomes the OpenAI model value clients pass to /v1/chat/completions.

Registration

GraphRegistry stores model names and GraphConfig values:

GraphRegistry(
    registry={
        "chat": GraphConfig(
            graph=chat_graph,
            description="General-purpose chat graph.",
            streamable_node_names=["generate"],
        ),
        "advanced-mcp-tools": GraphConfig(
            graph=advanced_graph,
            description="Chat graph with MCP tools.",
        ),
    }
)

GraphConfig.graph can be a compiled graph, sync factory, or async factory. Async factories support setup such as MCP-style tool loading before creating a ReAct graph.

Adaptation

The default graph contract is:

  • input: {"messages": langchain_messages}
  • output: the last AIMessage in result["messages"]

Use request_to_input, context_factory, and output_to_message when the graph has custom input, output, or context schemas. Those adapters keep the public HTTP surface OpenAI-compatible while letting the graph stay idiomatic LangGraph.

Graphs that use LangGraph's MessagesState (or a messages channel reduced by add_messages) explicitly decide which completed messages belong to their conversation state. Shared-key subgraphs can update that channel directly; private subgraphs must map selected results back in their wrapper. This state choice does not, by itself, select live token streaming or concatenate several messages into one OpenAI response.

Context Versus Config

LGOS keeps LangGraph's invocation channels separate:

graph.ainvoke(  # use graph.astream(...) for streaming
    graph_input,
    context=runtime_context,
    config=runnable_config,
)
  • graph_input contains mutable workflow state, including converted messages.
  • runtime_context starts with optional validated GraphConfig.client_settings settings and can be composed with server-owned values by GraphConfig.context_factory(request, settings). Settings used directly must also be the graph's context_schema; every non-null factory result requires a context schema. LangGraph applies its native schema coercion when it runs the graph. Nodes receive the result as runtime.context on an injected Runtime[Context].
  • runnable_config contains callbacks and execution identity. For an interrupt-enabled graph, LGOS derives config["configurable"]["thread_id"] from the server's checkpoint scope, registered model, and operation UUID. The UUID is generated by the server unless the initial request supplies metadata.langgraph_run_id. When callbacks are configured, LGOS also adds a stable run name and request/model/operation correlation metadata for tracing. LangGraph also propagates the primitive configurable checkpoint thread_id to callbacks during interrupt-enabled execution.

Application settings that nodes consume belong in typed runtime context, not in the configurable section of RunnableConfig. The internal checkpoint key is different: the checkpointer needs it to restore state before node execution, so it remains execution configuration. It is intentionally scoped by a server-trusted value plus model and run; it is not derived from a UI conversation ID.

Because LGOS supports Python 3.11 and newer, callback/config context propagates automatically to nested async runnable calls. Node functions only need an injected RunnableConfig when they inspect or modify execution configuration; they do not need one solely to pass config to a nested model's ainvoke().

See Custom Graphs for a typed server-owned context example and Configure LangGraph Runtime Settings for public settings, discovery, and request handling. LangGraph's official runtime, streaming, and persistence documentation for the underlying conventions.

Runner Behavior

LGOS uses the LangGraph interface that matches the OpenAI response mode. Both paths use LangGraph's stable v2 output wrapper so interrupt handling remains durable.

When stream is omitted or false, the route awaits invoke_run(). The runner calls graph.ainvoke(version="v2"). It does not subscribe to custom events. After execution it reads durable pending state for interrupts; otherwise it renders the returned value as the final AIMessage.

When stream=true, the route returns an SSE response backed by stream_run(). The runner consumes messages, custom, and values. Only AIMessageChunk values from configured streamable nodes become text chunks; the list may include nodes in nested subgraphs. Returning a message through the graph's messages state is not a live-streaming signal. The chat service immediately maps explicitly public client_event() and status_event() values into namespaced chunks when the request opts into v1 events. The final root value supplies durable citations, tool calls, and provider-reported usage. After execution quiesces, it reads durable pending state and renders a complete interrupt batch when present. Unknown custom events stay private.

Internal model calls that must not reach delta.content use LangGraph's native nostream tag. streamable_node_names selects calls whose text is intended for the OpenAI assistant stream; the tag selects calls within those nodes. Graph authors must follow the assistant text parity contract because a graph cannot retract an intermediate draft after it has streamed it.

Interrupt runs use exit durability and drain graph execution before exposing a durable tool-call batch. A GraphConfig.run_coordinator lease covers state inspection, validation, and execution; busy leases fail with HTTP 409. LGOS retains only checkpoints it has exposed as an interrupt and otherwise performs best-effort terminal cleanup. The complete lifecycle is specified in OpenAI compatibility.

The graph therefore needs an async checkpointer implementing aget_tuple(), alist(), aput(), aput_writes(), and adelete_thread(), plus a coordinator shared by all workers. See PostgreSQL Coordination for the production adapter.

Test the interrupt contract before every LangGraph upgrade

Sequential interrupts can reuse their interrupt and checkpoint IDs, so the opaque state_token fingerprints every checkpoint namespace and its durable resume-channel generations. Keep the sequential, parallel, nested, stale-resume, and restart tests as an upgrade gate before widening the supported LangGraph range. See the official interrupt ordering rule.

Paused Runs Across Deployments

Paused state resumes against the graph implementation currently selected by the model. Drain, expire, migrate, or route pending operations before changing state schemas, pending nodes, or interrupt order. Keep the checkpoint scope stable when a release must resume old state; otherwise keep the old release routable. See LangGraph's official backward-compatibility contract.

Status updates use LangGraph's native custom stream mode. No middleware is needed. Graph code emits a status exactly where it has enough application context to describe the long-running work in user-facing language.

See Custom Graphs for runnable examples.

Request Cancellation

For streaming chat completions (stream=true), LGOS ties graph iteration to the HTTP response lifetime. A request-scoped FastAPI dependency owns the producer task and memory channel behind StreamingResponse. When the client disconnects, dependency cleanup cancels and awaits that producer, then closes the graph iterator. This uses the normal OpenAI streaming connection; LGOS adds no custom cancellation route, header, or SSE event.

Cancellation is cooperative

Asynchronous work stops at cancellation points. Blocking code may continue, and a proxy that keeps consuming the upstream response also keeps the graph alive. The upstream model provider decides whether closing its connection stops remote generation or billing. This path does not cover stream=false and does not create a durable, addressable cancellation record.