Skip to content

Custom Graphs

Each GraphRegistry key becomes an OpenAI model name.

Default Message Graph

Default message graph
GraphConfig(graph=my_graph, streamable_node_names=["generate"])

Without adapters, graph input is {"messages": langchain_messages} and output text is read from result["messages"][-1].content.

Custom Schemas

Use adapters when your graph has native LangGraph input, output, or context schemas:

Custom graph adapters
GraphConfig(
    graph=custom_io_graph,  # (1)!
    request_to_input=request_to_input,  # (2)!
    context_factory=context_factory,  # (3)!
    output_to_text=output_to_text,  # (4)!
)
  1. Keep the graph's native LangGraph schema.
  2. Build graph input from the validated OpenAI request and converted messages.
  3. Build optional LangGraph runtime context from the request and public settings.
  4. Render the graph's native output as OpenAI assistant text.

See demo/api/graphs/custom_io.py for the runnable version.

Runtime Context

Use LangGraph runtime context for immutable, per-invocation application values that nodes need but that do not belong in graph state. Define a context schema, declare it on StateGraph, and read it from the injected Runtime object:

Typed runtime context
from dataclasses import dataclass

from langgraph.graph import END, START, StateGraph
from langgraph.runtime import Runtime
from typing_extensions import TypedDict


@dataclass(frozen=True)
class AppContext:
    user_id: str


class State(TypedDict, total=False):
    question: str
    answer: str


async def generate(state: State, runtime: Runtime[AppContext]) -> dict[str, str]:
    return {
        "answer": f"{runtime.context.user_id} asked: {state['question']}"
    }


custom_graph = (
    StateGraph(State, context_schema=AppContext)
    .add_node("generate", generate)
    .add_edge(START, "generate")
    .add_edge("generate", END)
    .compile()
)

Build that context from the validated OpenAI request at the adapter boundary:

Request to runtime context
from langchain_core.messages import BaseMessage

from langgraph_openai_serve import ClientSettings, GraphConfig
from langgraph_openai_serve.api.chat.schemas import ChatCompletionRequest


def request_to_input(
    request: ChatCompletionRequest,
    messages: list[BaseMessage],
) -> State:
    return {"question": str(messages[-1].content or "")}


def context_factory(
    request: ChatCompletionRequest,
    _client_settings: ClientSettings | None,
) -> AppContext:
    return AppContext(user_id=request.user or "anonymous")


def output_to_text(output: State) -> str:
    return output["answer"]


custom_graph_config = GraphConfig(
    graph=custom_graph,
    request_to_input=request_to_input,
    context_factory=context_factory,
    output_to_text=output_to_text,
)

LGOS passes the returned value through LangGraph's context argument. Do not put application values such as user_id, model selection, or prompt options in config["configurable"].

Keep context and config separate

See Context versus config for execution settings, checkpoint identity, and async config propagation. For a checkpointed workflow, continue to Interrupts.

Runtime Settings

Expose only a safe, explicit subset of runtime context when an ordinary OpenAI client should configure a graph. Define that public subset as a ClientSettings model. All fields automatically share one JSON metadata envelope:

Public runtime settings model
from pydantic import Field

from langgraph_openai_serve import ClientSettings


class PublicRuntimeSettings(ClientSettings):
    use_history: bool = Field(
        default=True,
        title="Use conversation history",
    )

When public settings are the complete runtime context, declare the same class as the graph's context_schema. In every case, set client_settings=PublicRuntimeSettings on the compiled graph's GraphConfig. LGOS validates the request directly from JSON and, without a context_factory, passes the resulting PublicRuntimeSettings instance as LangGraph runtime context. Graph authors must keep the inherited strict, frozen, extra-forbid, and default-validation behavior; LGOS rejects a settings model that changes it.

Do not publish internal context automatically

Keep user IDs, tenant identity, authorization state, database clients, secrets, and resource handles server-derived. Combine client_settings with context_factory(request, settings) when the final runtime context also needs server-owned values; declare that final composite type as the graph's context_schema. A factory may return None, but LGOS rejects any non-null result when the resolved graph has no context schema. LangGraph constructs a mapping through a dataclass or Pydantic context schema; it trusts an existing instance, so the server-owned factory is responsible for constructing valid instances. Do not expose server-owned values as runtime settings.

Follow Configure LangGraph Runtime Settings for discovery, request transport, and per-request behavior. The runnable repository version is in demo/api/graphs/simple.py.

Async Factories

GraphConfig.graph may be a compiled graph, sync factory, or async factory:

Async graph factory
async def advanced_graph():
    tools = await mcp_client.get_tools()
    return create_agent(model=model, tools=tools)

GraphConfig(graph=advanced_graph)

See demo/api/graphs/advanced_mcp.py for a mock MCP-style example.

Register And Bind

Application registration
from langgraph_openai_serve import GraphConfig, GraphRegistry, LanggraphOpenaiServe

graphs = GraphRegistry(
    registry={
        "my-graph": GraphConfig(graph=my_graph, streamable_node_names=["generate"]),
        "advanced-mcp-tools": GraphConfig(graph=advanced_graph),
    }
)

LanggraphOpenaiServe(graphs=graphs).bind_openai_api()

Streaming

When an OpenAI request sets stream=True, LGOS forwards only streamed AIMessageChunk values from streamable_node_names. Deterministic graphs that return a final dictionary should be called without stream=True.

Choose streamable nodes deliberately

List only nodes whose model chunks should reach the client. This prevents internal graph work from appearing as assistant output.

Interrupts

Enable the interrupt feature for checkpointed human-in-the-loop graphs:

from langgraph_openai_serve import GraphConfig, GraphFeature

GraphConfig(
    graph=interruptible_graph,
    features={GraphFeature.INTERRUPTS},
)

Clients must pass metadata.langgraph_thread_id so follow-up tool messages resume the same LangGraph thread. The interrupt is represented as an OpenAI tool call named langgraph_interrupt.

A checkpointer is required

Interrupt-enabled graphs must be compiled with a checkpointer. Production deployments should use durable storage so a pending thread can resume after a process restart.

Next Steps