Skip to content

Reference

OpenAI-Compatible API

Default prefix: /v1. Change it with LGOS_OPENAI_API_PREFIX or bind_openai_api(prefix=...). Generic access logs are emitted by the deployment's ASGI server or ingress proxy.

Method Path Purpose
GET /v1/models List registered graph models with LGOS descriptions.
GET /v1/models/{model} Retrieve one model with the required LGOS metadata extension.
POST /v1/chat/completions Run a graph through OpenAI chat completions.
GET /v1/health Health check.

FastAPI docs for the mounted OpenAI app are disabled by default. Set LGOS_OPENAI_API_DOCS_ENABLED=true to expose {prefix}/docs, {prefix}/redoc, and {prefix}/openapi.json.

Settings

Package settings:

Setting Default Notes
LGOS_OPENAI_API_PREFIX /v1 Must start with /; trailing slash is normalized.
LGOS_OPENAI_API_DOCS_ENABLED false Enables docs only for the mounted OpenAI app.
LGOS_ENABLE_LANGFUSE false Lazily adds the package Langfuse callback to every graph run.

Settings prefixed with DEMO_ belong to the independent example applications and are documented under Demo Settings and Commands.

Public API

Use LanggraphOpenaiServe to bind OpenAI-compatible routes to a FastAPI app. After binding, server.openai_app exposes the mounted FastAPI application for host integrations such as manual middleware or telemetry instrumentation. Use GraphRegistry to map OpenAI model names to GraphConfig values. The registry must contain at least one graph. Pydantic rejects empty registries and model IDs that cannot be addressed as one URL path segment. Registry keys are read-only after validation; use registry.register(model_id, config) to add or replace a graph.

LanggraphOpenaiServe(..., checkpoint_scope=resolver) accepts an optional sync or async callable from FastAPI Request to a non-empty, server-trusted string. Interrupt checkpoint keys include this scope before model and run identity. Use an authenticated tenant or principal identifier when caller-chosen run UUIDs must be isolated between security domains; do not derive the scope from untrusted Chat Completions metadata or the Chat Completions user field. The default "default" scope is suitable only for a single-tenant or shared-trust deployment. The resolver must return the same scope for the initial request and its resume; changing tenant identity makes the other scope's checkpoint deliberately unreachable.

GraphConfig accepts:

  • graph: compiled graph, sync factory, or async factory.
  • description: required human-readable model description advertised by model listing and retrieval.
  • streamable_node_names: node names whose streamed AIMessageChunk values are forwarded as assistant text. If several nodes contribute, the graph's output adapter must render the same ordered content for complete responses.
  • features: GraphFeature values that enable optional server behavior.
  • client_settings: explicit public ClientSettings model class advertised by model retrieval.
  • runtime_callbacks: callbacks included in the LangGraph RunnableConfig. When Langfuse tracing is enabled, LGOS adds its callback without mutating this collection or manager.
  • run_coordinator: asynchronous single-flight coordination for interrupt runs. It rejects an occupied LGOS checkpoint key instead of queueing it and returns an async context manager.
  • request_to_input(request, messages): custom OpenAI request to graph input.
  • context_factory(request, client_settings): compose the final typed LangGraph runtime context from server-owned values and optional validated public settings.
  • output_to_message(output): custom graph output to a durable AIMessage.

When both are configured, LGOS validates the public settings first and passes them to context_factory. Without a factory, the validated settings instance is the runtime context, so the graph must use that settings model as its context_schema. A factory may return None; every non-null result requires a graph context schema. LGOS passes server-owned factory results to LangGraph without rebuilding them. LangGraph's native runtime-context handling constructs mapping values through dataclass and Pydantic context schemas and trusts existing instances. The factory owns the validity of instances it creates. Graphs should access context from an injected Runtime[Context]. Runtime context is separate from RunnableConfig:

Value LGOS/LangGraph path Intended use
Graph input graph.ainvoke(input, ...) or graph.astream(input, ...) Messages and mutable workflow state.
Runtime context public settings → optional context_factorycontext=Runtime.context Immutable per-run application values and dependencies.
Runnable config config= Callbacks, tags, tracing, and other execution controls.
Interrupt run server scope + model + optional metadata.langgraph_run_id UUID → internal checkpoint key Isolate, retry, interrupt, and resume one operation.

LGOS assembles runnable config from runtime_callbacks and, for an interrupt-enabled run, a fixed-length SHA-256 checkpoint key derived from the server-trusted scope, registered model, and operation UUID. This is deliberately not a UI chat or thread ID. There is intentionally no adapter for placing arbitrary OpenAI request fields into config["configurable"]; use typed runtime context for values consumed by nodes.

Langfuse is a first-class optional integration. Install it and enable the default callback through process environment settings:

uv add "langgraph-openai-serve[tracing]"
export LGOS_ENABLE_LANGFUSE=true
export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...

LANGFUSE_BASE_URL is optional; Langfuse Cloud is the default. Set it only for a different cloud region or a self-hosted instance. Langfuse's CallbackHandler owns its standard SDK configuration and error behavior. LGOS constructs it on the first graph run that needs runnable configuration, then reuses that process-wide handler. When enabled, the deployment-level toggle is authoritative: LGOS adds Langfuse alongside empty, list, or manager callbacks without altering the registered GraphConfig or caller-owned collection. To provide a custom Langfuse handler, leave the toggle off and pass that handler through runtime_callbacks.

For explicit construction, import langgraph_openai_serve.integrations.langfuse.get_langfuse_callback or pass an application-created vendor handler through runtime_callbacks.

When a callback is present, LGOS gives the graph run the stable name lgos.chat_completion and adds RunnableConfig.metadata fields for the request ID, registered graph model, (for interrupt runs) operation ID, and (when the request supplies metadata.session_id) the Langfuse-recognized langfuse_session_id. LangGraph also propagates primitive configurable values during execution, so callbacks on interrupt runs receive the derived checkpoint thread_id. LGOS does not set LangChain's native tracer run_id or force a custom Langfuse trace ID. See Production Logging and Request Correlation.

The same features set drives runtime behavior and the versioned langgraph_openai_serve.features extension returned by GET /v1/models/{model}. GraphFeature.CLIENT_EVENTS enables and advertises public client-event chunks. GraphFeature.INTERRUPTS enables and advertises the interrupt/resume flow.

Runtime Settings

Subclass ClientSettings to publish only fields deliberately selected by the server author. LGOS never inspects or publishes the LangGraph context schema:

Public settings model
from pydantic import Field

from langgraph_openai_serve import ClientSettings


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

Pass this model as GraphConfig.client_settings and use it as the graph's context schema when it is the complete runtime context. Every public field must have a default. Registration rejects subclasses that change the inherited strict, frozen, extra-forbid, or default-validation behavior, as well as fields excluded from Pydantic serialization.

All public fields travel together as compact JSON text in the metadata.langgraph_runtime_settings string. Clients omit values equal to the advertised defaults. System instructions remain ordinary OpenAI messages and are independent of ClientSettings; native Chat Completions fields keep their standard request semantics.

LGOS validates defaults and generates the discovery JSON Schema when the graph is registered, then validates settings on every request. Without context_factory, the settings become Runtime.context. A factory can instead combine them with server-derived identity, authorization, database clients, and other dependencies.

The serialized descriptor appears only on model retrieval as langgraph_openai_serve.client_settings, with independent schema_version, json_schema, and defaults fields. All client settings use the fixed metadata.langgraph_runtime_settings envelope. Clients use the descriptor's validated defaults object as the baseline; default keywords within the generated JSON Schema are annotations, not the runtime baseline.

See Configure LangGraph Runtime Settings for the runtime settings flow, and Runtime Settings for the request lifecycle.

Interrupt-enabled graphs have additional registration requirements:

  • compile the graph with an asynchronous checkpointer that supports aget_tuple(), alist(), aput(), aput_writes(), and adelete_thread();
  • configure an asynchronous run_coordinator; and
  • use a durable checkpointer and cross-process coordinator in production.

The initial request does not require metadata. LGOS generates a UUID operation ID and returns it in every interrupt tool call. A caller that needs deterministic initial-request retries can instead supply a non-nil UUID in metadata.langgraph_run_id. InMemoryRunCoordinator is suitable only for tests and a single-process development server; it cannot serialize requests across workers or hosts.

Pending checkpoints exist only to resume an interrupt batch returned to the client. LGOS deletes isolated checkpoint state after terminal completion or when execution fails or is cancelled before producing that batch. Operators must separately define an expiry policy for runs abandoned after a batch is returned.

PostgreSQL Coordination

Install langgraph-openai-serve[postgres] to use the public langgraph_openai_serve.integrations.postgres.PostgresRunCoordinator. Use LangGraph's official AsyncPostgresSaver for checkpoints and AsyncPostgresStore for application data. The LGOS adapter supplies only the cross-worker interrupt-run lease; it does not replace either storage primitive. Run each configured storage adapter's setup() once before API workers start. A shared pool must follow the upstream connection requirements: autocommit=True, prepare_threshold=0, and mapping rows.

PostgresRunCoordinator(pool, max_concurrent_leases=...) accepts an existing psycopg_pool.AsyncConnectionPool configured with mapping rows and the default close_returns=False; physical session closure is the safety fallback for an indeterminate lock operation. When persistence adapters share that pool, set the lease limit below the pool maximum so at least one connection remains available for persistence I/O. Create one coordinator per process-owned pool so that this capacity limit is not accidentally multiplied. Session advisory locks require direct PostgreSQL connections or session-mode pooling; transaction-mode poolers cannot preserve the lease. Lock contention itself fails immediately through PostgreSQL's pg_try_advisory_lock; connection checkout still follows the pool's configured timeout. The demo deployment uses one pool for both storage adapters and interrupt coordination, plus a separate one-shot schema setup process. Busy interrupt leases fail before streaming begins with HTTP 409 and code: "run_busy".

Client Stream Events

Declare the feature on every graph that publishes client events:

from langgraph_openai_serve import GraphConfig, GraphFeature

config = GraphConfig(
    graph=graph,
    description="Graph that reports media-generation status.",
    features={GraphFeature.CLIENT_EVENTS},
)

Inside a long-running graph node or tool, publish user-facing status with status_event():

from langgraph.config import get_stream_writer
from langgraph_openai_serve import status_event

writer = get_stream_writer()
writer(status_event("Generating audio", namespace=("media",)))

# Perform the long-running work.

writer(
    status_event(
        "Audio ready",
        done=True,
        namespace=("media",),
    )
)

The portable status data matches native UI status concepts:

{
  "type": "status",
  "namespace": ["media"],
  "data": {
    "description": "Generating audio",
    "done": false,
    "hidden": false
  }
}

done=False displays ongoing work; always finish a visible status sequence with done=True. Set hidden=True on the final update when clients should remove the status after completion. Status text is deliberately authored by the graph: LGOS does not infer it from internal node names or state.

For other passive notifications, use client_event():

from langgraph.config import get_stream_writer
from langgraph_openai_serve import client_event

get_stream_writer()(
    client_event(
        "progress",
        {
            "stage": "retrieval",
            "completed": 2,
            "total": 5,
            "message": "Searching documents",
        },
        namespace=("research",),
    )
)

The v1 vocabulary is status, progress, and artifact. Event data must be JSON-safe, and every namespace segment must be a string. Keep payloads small and represent large artifacts by an ID or URL. The namespace is a stable, author-defined path; LGOS does not expose LangGraph's dynamic execution namespace.

Events are streaming-only and require both the graph feature and client opt-in. Clients request them with metadata={"langgraph_stream_events": "v1"} and receive a versioned langgraph_openai_serve property on an otherwise standard Chat Completions chunk. Missing and unsupported versions produce the ordinary strict stream. Unknown custom events remain available only to direct runner consumers.

See Client stream events for the wire contract and OpenAI clients for consumption.

Citations

Put citations on the final LangChain AIMessage:

from langchain_core.messages import AIMessage
from langchain_core.messages.content import create_citation, create_text_block

message = AIMessage(
    content=[
        create_text_block(
            text="Read the source [1].",
            annotations=[
                create_citation(
                    url="https://example.com/source",
                    title="Example source",
                    start_index=9,
                    end_index=14,
                    cited_text="source",
                )
            ],
        )
    ]
)

Put visible inline citations in the assistant text. Structured annotations add machine-readable provenance; clients are not required to invent marker text from annotation indices.

LangChain citation indices refer to their containing text block. LGOS offsets them into the final response text and preserves OpenAI's inclusive end_index. Use citation_slice(annotation, text) to validate received indices and convert them to a Python slice. LGOS maps native LangChain citations to completed message.annotations; the streaming compatibility extension is added to the final delta.

See Citation ownership for transport and client behavior.

The streaming graph runner preserves LangGraph's native CustomStreamPart values, including their execution namespace. Non-streaming invocation does not subscribe to or replay custom events.

langgraph-openai-serve package.

ClientSettings

Bases: BaseModel

Base class for settings that clients may configure for a graph.

Subclasses define the complete public contract. Every field must have a valid JSON-serializable default so model discovery can advertise a usable settings object without maintaining a second defaults mapping.

defaults classmethod

defaults()

Return a deep copy of the registration-validated defaults.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def defaults(cls) -> Self:
    """Return a deep copy of the registration-validated defaults."""
    return cast(
        "Self",
        _validated_contract(cls).defaults.model_copy(deep=True),
    )

validate_request classmethod

validate_request(request)

Read and validate this model's values from an OpenAI request.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def validate_request(cls, request: ChatCompletionRequest) -> Self:
    """Read and validate this model's values from an OpenAI request."""
    parameter = f"metadata.{RUNTIME_SETTINGS_METADATA_KEY}"
    encoded = (request.metadata or {}).get(RUNTIME_SETTINGS_METADATA_KEY, "{}")

    try:
        changes = _validate_json_object(encoded)
    except ValidationError as exc:
        raise ClientSettingsValidationError(
            _validation_message(exc, label="runtime settings"),
            param=parameter,
        ) from exc

    values = client_settings_default_values(cls)
    values.update(changes)

    try:
        settings = cls.model_validate_json(
            _SETTINGS_OBJECT_ADAPTER.dump_json(values),
            strict=True,
            by_alias=False,
            by_name=True,
        )
    except ValidationError as exc:
        field = _first_error_field(exc)
        raise ClientSettingsValidationError(
            _validation_message(exc, label=field or "runtime settings"),
            param=parameter,
        ) from exc
    else:
        _validated_settings_json(settings)
        return settings

GraphConfig

Bases: BaseModel

Graph configuration.

build_context async

build_context(request, graph)

Build the LangGraph runtime context for a request.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def build_context(
    self,
    request: ChatCompletionRequest,
    graph: CompiledStateGraph,
) -> Any:
    """Build the LangGraph runtime context for a request."""
    settings = (
        self.client_settings.validate_request(request)
        if self.client_settings is not None
        else None
    )
    if self.context_factory is not None:
        context = await _maybe_await(self.context_factory(request, settings))
    else:
        context = settings

    if context is None:
        return None
    if graph.context_schema is None:
        msg = "A graph that produces runtime context must declare context_schema."
        raise GraphConfigurationError(msg)

    # Preserve server-owned context objects; LangGraph applies context_schema
    # coercion when it invokes the graph.
    return context

build_input async

build_input(request, messages)

Build the native graph input for a chat completion request.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def build_input(
    self,
    request: ChatCompletionRequest,
    messages: list[BaseMessage],
) -> Any:
    """Build the native graph input for a chat completion request."""
    if self.request_to_input is None:
        return {"messages": messages}
    return await _maybe_await(self.request_to_input(request, messages))

render_output async

render_output(output)

Convert native graph output into the durable assistant message.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def render_output(self, output: Any) -> AIMessage:
    """Convert native graph output into the durable assistant message."""
    if self.output_to_message is not None:
        return await _maybe_await(self.output_to_message(output))

    messages = (
        output["messages"]
        if isinstance(output, Mapping)
        else getattr(output, "messages", None)
    )
    if messages is None:
        msg = "Graph output must expose a messages field."
        raise GraphConfigurationError(msg)
    if not messages:
        return AIMessage(content="")
    message = messages[-1]
    if not isinstance(message, AIMessage):
        msg = "The final graph message must be an AIMessage."
        raise GraphConfigurationError(msg)
    return message

resolve_graph async

resolve_graph()

Get the graph instance, resolving callable graph factories.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def resolve_graph(self) -> CompiledStateGraph:
    """Get the graph instance, resolving callable graph factories."""
    if self.run_coordinator is not None and not self.supports(
        GraphFeature.INTERRUPTS
    ):
        msg = "run_coordinator is only supported by interrupt-enabled graphs."
        raise GraphConfigurationError(msg)

    if isinstance(self.graph, CompiledStateGraph):
        graph = self.graph
    else:
        graph = await _maybe_await(self.graph())

    if (
        self.client_settings is not None
        and self.context_factory is None
        and graph.context_schema is not self.client_settings
    ):
        msg = (
            "Graphs using client_settings directly must use that settings model "
            "as context_schema."
        )
        raise GraphConfigurationError(msg)

    if self.supports(GraphFeature.INTERRUPTS):
        checkpointer = graph.checkpointer
        if checkpointer is None or any(
            not _overrides_checkpointer_method(checkpointer, method_name)
            for method_name in _INTERRUPT_CHECKPOINTER_METHODS
        ):
            msg = (
                "Interrupt-enabled graphs must use a fully asynchronous "
                "checkpointer with thread deletion."
            )
            raise GraphConfigurationError(msg)
        if self.run_coordinator is None:
            msg = "Interrupt-enabled graphs must configure a run_coordinator."
            raise GraphConfigurationError(msg)

    return graph

supports

supports(feature)

Return whether this graph supports a feature.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def supports(self, feature: GraphFeature) -> bool:
    """Return whether this graph supports a feature."""
    return feature in self.features

validate_client_settings classmethod

validate_client_settings(value)

Validate a public settings model when its graph is registered.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@field_validator("client_settings")
@classmethod
def validate_client_settings(
    cls,
    value: type[ClientSettings] | None,
) -> type[ClientSettings] | None:
    """Validate a public settings model when its graph is registered."""
    return validate_client_settings_model(value) if value is not None else None

GraphFeature

Bases: StrEnum

Features supported by a registered graph.

GraphRegistry

Bases: BaseModel

Registry of graphs.

get_graph

get_graph(name)

Get a graph by its name.

Parameters:

Name Type Description Default
name str

The name of the graph to retrieve.

required

Returns:

Type Description
GraphConfig

The graph configuration associated with the given name.

Raises:

Type Description
GraphNotFoundError

If the graph name is not found in the registry.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def get_graph(self, name: str) -> GraphConfig:
    """
    Get a graph by its name.

    Args:
        name: The name of the graph to retrieve.

    Returns:
        The graph configuration associated with the given name.

    Raises:
        GraphNotFoundError: If the graph name is not found in the registry.

    """
    try:
        return self.registry[name]
    except KeyError as exc:
        msg = f"Graph '{name}' not found in registry."
        raise GraphNotFoundError(msg) from exc

get_graph_names

get_graph_names()

Get the names of all registered graphs.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def get_graph_names(self) -> list[str]:
    """Get the names of all registered graphs."""
    return list(self.registry.keys())

register

register(model_id, config)

Add or replace one graph through the validated registry boundary.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def register(self, model_id: str, config: GraphConfig) -> None:
    """Add or replace one graph through the validated registry boundary."""
    self.registry = {**self.registry, model_id: config}

LanggraphOpenaiServe

LanggraphOpenaiServe(graphs, app=None, checkpoint_scope=None)

Server class to connect LangGraph instances with an OpenAI-compatible API.

This class serves as a bridge between LangGraph instances and an OpenAI-compatible API. It allows users to register their LangGraph instances and expose them through an OpenAI-compatible sub-application mounted on a FastAPI host app.

Attributes:

Name Type Description
app FastAPI

The host FastAPI application to mount the OpenAI API on.

graph_registry

The populated GraphRegistry containing the graphs to serve.

openai_app FastAPI

The mounted OpenAI-compatible FastAPI application.

Initialize the server with a FastAPI app and a populated graph registry.

Parameters:

Name Type Description Default
app FastAPI | None

The host FastAPI application to mount the OpenAI API on. If None, a new FastAPI app will be created.

None
graphs GraphRegistry

A GraphRegistry instance containing the graphs to serve.

required
checkpoint_scope Callable[[Request], str | Awaitable[str]] | None

Optional server-trusted resolver used to isolate interrupt checkpoints by deployment or authenticated principal.

None

Raises:

Type Description
TypeError

If graphs is not a GraphRegistry instance.

Source code in src/langgraph_openai_serve/openai_server.py
def __init__(
    self,
    graphs: GraphRegistry,
    app: FastAPI | None = None,
    checkpoint_scope: Callable[[Request], str | Awaitable[str]] | None = None,
) -> None:
    """
    Initialize the server with a FastAPI app and a populated graph registry.

    Args:
        app: The host FastAPI application to mount the OpenAI API on. If None,
            a new FastAPI app will be created.
        graphs: A GraphRegistry instance containing the graphs to serve.
        checkpoint_scope: Optional server-trusted resolver used to isolate
            interrupt checkpoints by deployment or authenticated principal.

    Raises:
        TypeError: If graphs is not a GraphRegistry instance.

    """
    if not isinstance(graphs, GraphRegistry):
        msg = "Invalid type for graphs parameter. Expected GraphRegistry."
        raise TypeError(msg)

    if app is None:
        app = FastAPI(
            title="LangGraph OpenAI Compatible API",
            description="An OpenAI-compatible API for LangGraph",
            version=get_version(),
        )
    self.app: FastAPI = app
    self._openai_app: FastAPI | None = None
    self.checkpoint_scope = checkpoint_scope or (lambda _request: "default")

    self.graph_registry = graphs

    # Host integrations can inspect registered graphs without traversing the
    # mounted OpenAI sub-application.
    self.app.state.graph_registry = self.graph_registry
    self.app.state.checkpoint_scope = self.checkpoint_scope

    logger.info(
        "server.initialized",
        extra={"graph_count": len(self.graph_registry.registry)},
    )

openai_app property

openai_app

The mounted OpenAI-compatible FastAPI application.

bind_openai_api

bind_openai_api(prefix=None)

Mount OpenAI-compatible endpoints on the host FastAPI app.

Parameters:

Name Type Description Default
prefix str | None

Optional; The URL prefix for the OpenAI-compatible endpoints. Defaults to settings.OPENAI_API_PREFIX.

None
Source code in src/langgraph_openai_serve/openai_server.py
def bind_openai_api(self, prefix: str | None = None) -> "LanggraphOpenaiServe":
    """
    Mount OpenAI-compatible endpoints on the host FastAPI app.

    Args:
        prefix: Optional; The URL prefix for the OpenAI-compatible endpoints.
            Defaults to settings.OPENAI_API_PREFIX.

    """
    prefix = (
        normalize_openai_api_prefix(prefix)
        if prefix is not None
        else settings.OPENAI_API_PREFIX
    )

    openai_app = FastAPI(
        title="LangGraph OpenAI Compatible API",
        description="An OpenAI-compatible API for LangGraph",
        version=get_version(),
        **settings.fastapi_docs_kwargs,
    )
    # Dependencies in mounted routes resolve against the mounted app.
    openai_app.state.graph_registry = self.graph_registry
    openai_app.state.checkpoint_scope = self.checkpoint_scope
    configure_openai_error_handlers(openai_app)
    openai_app.include_router(chat_views.router)
    openai_app.include_router(health_views.router)
    openai_app.include_router(models_views.router)

    self.app.router.routes.append(
        Mount(
            prefix,
            app=openai_app,
            name="openai",
            middleware=[Middleware(RequestContextMiddleware)],
        )
    )
    self._openai_app = openai_app

    logger.info("server.api_bound", extra={"prefix": prefix})

    return self

citation_slice

citation_slice(annotation, content)

Convert an OpenAI inclusive citation span to a validated Python slice.

Source code in src/langgraph_openai_serve/graph/events.py
def citation_slice(annotation: Annotation, content: str) -> slice:
    """Convert an OpenAI inclusive citation span to a validated Python slice."""
    citation = annotation.url_citation
    start = citation.start_index
    stop = citation.end_index + 1
    if not 0 <= start < stop <= len(content):
        msg = "citation indices must refer to the final assistant text"
        raise ValueError(msg)
    return slice(start, stop)

client_event

client_event(event_type, data, *, namespace=())

Build an explicitly public, JSON-safe client stream event.

Source code in src/langgraph_openai_serve/graph/events.py
def client_event(
    event_type: ClientEventType,
    data: JsonValue,
    *,
    namespace: tuple[str, ...] = (),
) -> dict[str, object]:
    """Build an explicitly public, JSON-safe client stream event."""
    envelope = _ClientEventEnvelope(
        type=_CLIENT_EVENT_ENVELOPE_TYPE,
        schema_version=CLIENT_EVENT_SCHEMA_VERSION,
        event=_ClientEventData(
            type=event_type,
            namespace=namespace,
            data=data,
        ),
    )
    return envelope.model_dump(mode="json")

status_event

status_event(description, *, done=False, hidden=False, namespace=())

Build a portable status update for native client UI.

Source code in src/langgraph_openai_serve/graph/events.py
def status_event(
    description: str,
    *,
    done: bool = False,
    hidden: bool = False,
    namespace: tuple[str, ...] = (),
) -> dict[str, object]:
    """Build a portable status update for native client UI."""
    data = _StatusEventData(
        description=description,
        done=done,
        hidden=hidden,
    )
    return client_event(
        "status",
        data.model_dump(mode="json"),
        namespace=namespace,
    )

api

chat

deps

Dependencies for chat completion routes.

checkpoint_scope_dependency async
checkpoint_scope_dependency(request)

Resolve checkpoint scope.

Source code in src/langgraph_openai_serve/api/chat/deps.py
async def checkpoint_scope_dependency(request: Request) -> str:
    """Resolve checkpoint scope."""
    value = request.app.state.checkpoint_scope(request)
    if inspect.isawaitable(value):
        value = await value
    return value
stream_owner_dependency async
stream_owner_dependency()

Manage stream ownership.

Yields:

Type Description
AsyncIterator[_StreamOwner]

The stream owner dependency instance.

Source code in src/langgraph_openai_serve/api/chat/deps.py
async def stream_owner_dependency() -> AsyncIterator[_StreamOwner]:
    """
    Manage stream ownership.

    Yields:
        The stream owner dependency instance.

    """
    owner = _StreamOwner()
    try:
        yield owner
    finally:
        await owner.aclose()

schemas

Pydantic models for the OpenAI API.

This module defines Pydantic models that match the OpenAI API request and response formats.

ChatCompletionRequest

Bases: BaseModel

Model for a chat completion request.

reject_legacy_fields classmethod
reject_legacy_fields(data)

Reject deprecated Chat Completions function parameters.

Source code in src/langgraph_openai_serve/api/chat/schemas.py
@model_validator(mode="before")
@classmethod
def reject_legacy_fields(cls, data: Any) -> Any:
    """Reject deprecated Chat Completions function parameters."""
    return _reject_legacy_fields(
        data,
        {
            "function_call": "tool_choice",
            "functions": "tools",
        },
        title=cls.__name__,
    )
validate_stream_options
validate_stream_options()

Allow stream options only for streaming requests.

Source code in src/langgraph_openai_serve/api/chat/schemas.py
@model_validator(mode="after")
def validate_stream_options(self) -> "ChatCompletionRequest":
    """Allow stream options only for streaming requests."""
    if self.stream_options is not None and not self.stream:
        msg = "stream_options may only be set when stream is true"
        raise ValueError(msg)
    return self
ChatCompletionRequestMessage

Bases: BaseModel

Model for a chat completion request message.

reject_legacy_fields classmethod
reject_legacy_fields(data)

Reject the deprecated singular Chat Completions function call.

Source code in src/langgraph_openai_serve/api/chat/schemas.py
@model_validator(mode="before")
@classmethod
def reject_legacy_fields(cls, data: Any) -> Any:
    """Reject the deprecated singular Chat Completions function call."""
    return _reject_legacy_fields(
        data,
        {"function_call": "tool_calls"},
        title=cls.__name__,
    )
ChatCompletionResponse

Bases: BaseModel

Model for a chat completion response.

ChatCompletionResponseChoice

Bases: BaseModel

Model for a chat completion response choice.

ChatCompletionResponseMessage

Bases: BaseModel

Model for a chat completion response message.

ChatCompletionStreamOptions

Bases: BaseModel

Options that affect Chat Completions streaming.

ChatCompletionStreamResponse

Bases: BaseModel

Model for a chat completion stream response.

ChatCompletionStreamResponseChoice

Bases: BaseModel

Model for a chat completion stream response choice.

ChatCompletionStreamResponseDelta

Bases: BaseModel

Model for a chat completion stream response delta.

ChatCompletionStreamToolCall

Bases: BaseModel

Model for a streaming tool call delta.

ChatCompletionStreamToolCallFunction

Bases: BaseModel

Model for a streaming tool call function delta.

FunctionDefinition

Bases: BaseModel

Model for a function definition.

Role

Bases: StrEnum

Role options for chat messages.

Tool

Bases: BaseModel

Model for a tool.

ToolCall

Bases: BaseModel

Model for a tool call.

ToolCallFunction

Bases: BaseModel

Model for a tool call function.

ToolFunction

Bases: BaseModel

Model for a tool function.

UsageInfo

Bases: BaseModel

Model for usage information.

service

Functions for generating chat completions.

generate_completion async
generate_completion(chat_request, run)

Generate a chat completion.

Source code in src/langgraph_openai_serve/api/chat/service.py
async def generate_completion(
    chat_request: ChatCompletionRequest, run: GraphRun
) -> ChatCompletionResponse:
    """Generate a chat completion."""
    invocation = await invoke_run(run)
    return chat_completion_response(
        model=chat_request.model,
        completion=invocation.output,
    )
stream_completion async
stream_completion(chat_request, run)

Stream a chat completion response.

Yields:

Type Description
AsyncGenerator[str, None]

String chunks representing Server-Sent Events.

Source code in src/langgraph_openai_serve/api/chat/service.py
async def stream_completion(
    chat_request: ChatCompletionRequest, run: GraphRun
) -> AsyncGenerator[str, None]:
    """
    Stream a chat completion response.

    Yields:
        String chunks representing Server-Sent Events.

    """
    include_usage = bool(
        chat_request.stream_options is not None
        and chat_request.stream_options.include_usage
    )
    response_builder = ChatCompletionStreamResponseBuilder(
        chat_request.model,
        include_usage=include_usage,
    )
    final_message: AIMessage | None = None
    text_parts: list[str] = []
    include_client_events = run.config.supports(
        GraphFeature.CLIENT_EVENTS
    ) and stream_events_requested(chat_request.metadata)

    try:  # ruff: ignore[too-many-nested-blocks, too-many-statements-in-try-clause]
        yield response_builder.role()

        run_stream = stream_run(run)
        # Closing the HTTP response must also close the nested graph stream.
        async with aclosing(run_stream):
            async for event in run_stream:
                if isinstance(event, LangGraphInterruptBatch):
                    yield response_builder.interrupt(event)
                    yield response_builder.finish("tool_calls")
                    yield response_builder.done()
                    return

                if isinstance(event, AIMessage):
                    final_message = event
                    continue

                if not isinstance(event, str):
                    if include_client_events:
                        extension = client_event_extension_from_custom_event(event)
                        if extension is not None:
                            yield response_builder.client_event(extension)
                    continue

                text_parts.append(event)
                yield response_builder.text(event)

        final_message = _require_final_message(final_message)
        for chunk in _final_chunks(
            response_builder,
            final_message,
            streamed_text="".join(text_parts) if text_parts else None,
            include_usage=include_usage,
        ):
            yield chunk

    except Exception:
        logger.exception("chat_completion.stream_failed")
        yield response_builder.error("Internal server error")
        yield response_builder.done()

utils

events

Adapt generic LangGraph custom events to OpenAI chat fields.

client_event_extension_from_custom_event
client_event_extension_from_custom_event(event)

Validate an explicitly public event and build its stream extension.

Source code in src/langgraph_openai_serve/api/chat/utils/events.py
def client_event_extension_from_custom_event(
    event: CustomStreamPart,
) -> dict[str, object] | None:
    """Validate an explicitly public event and build its stream extension."""
    # Ignore LangGraph's execution namespace, which contains dynamic task IDs.
    # The public namespace is authored explicitly inside the validated event.
    return client_event_extension(event["data"])
stream_events_requested
stream_events_requested(metadata)

Return whether a request opted into the supported event stream version.

Source code in src/langgraph_openai_serve/api/chat/utils/events.py
def stream_events_requested(metadata: dict[str, str] | None) -> bool:
    """Return whether a request opted into the supported event stream version."""
    return (metadata or {}).get(STREAM_EVENTS_METADATA_KEY) == (
        STREAM_EVENTS_METADATA_VALUE
    )
interrupts

OpenAI Chat Completions codec for LangGraph interrupts.

InterruptResume dataclass
InterruptResume(run_id, state_token, values)

A complete, causally bound set of interrupt answers.

InvalidInterruptPayloadError

Bases: ValueError

Raised when graph-authored interrupt data cannot cross the JSON API.

InvalidResumeRequestError

Bases: ValueError

Raised when an OpenAI tool exchange is not a valid interrupt resume.

interrupt_arguments
interrupt_arguments(*, run_id, state_token, payload)

Encode one interrupt without coercing unsupported graph values.

Source code in src/langgraph_openai_serve/api/chat/utils/interrupts.py
def interrupt_arguments(
    *,
    run_id: str,
    state_token: str,
    payload: Any,
) -> str:
    """Encode one interrupt without coercing unsupported graph values."""
    return _dump_json(
        {
            "run_id": run_id,
            "state_token": state_token,
            "payload": payload,
        }
    )
interrupt_tool_call_id
interrupt_tool_call_id(interrupt_id)

Format interrupt tool call ID.

Source code in src/langgraph_openai_serve/api/chat/utils/interrupts.py
def interrupt_tool_call_id(interrupt_id: str) -> str:
    """Format interrupt tool call ID."""
    return f"{INTERRUPT_TOOL_CALL_ID_PREFIX}{interrupt_id}"
parse_resume_request
parse_resume_request(messages)

Parse the trailing canonical assistant/tool interrupt exchange.

Ordinary tool messages remain ordinary graph input. A LangGraph resume is recognized only when the tool results answer a preceding assistant message whose function calls are all langgraph_interrupt calls.

Source code in src/langgraph_openai_serve/api/chat/utils/interrupts.py
def parse_resume_request(
    messages: list[ChatCompletionRequestMessage],
) -> InterruptResume | None:
    """
    Parse the trailing canonical assistant/tool interrupt exchange.

    Ordinary tool messages remain ordinary graph input. A LangGraph resume is
    recognized only when the tool results answer a preceding assistant message
    whose function calls are all ``langgraph_interrupt`` calls.
    """
    tool_start = _trailing_tool_start(messages)
    if tool_start is None:
        return None

    tool_messages = messages[tool_start:]
    assistant = messages[tool_start - 1] if tool_start > 0 else None

    if assistant is None or assistant.role != Role.ASSISTANT:
        if any(_is_interrupt_tool_result(message) for message in tool_messages):
            msg = "Interrupt tool results must follow their assistant tool calls."
            raise InvalidResumeRequestError(msg)
        return None

    calls = assistant.tool_calls or []
    interrupt_calls = [
        call for call in calls if call.function.name == INTERRUPT_TOOL_NAME
    ]

    if not interrupt_calls:
        if any(_is_interrupt_tool_result(message) for message in tool_messages):
            msg = "Interrupt tool results must follow their assistant tool calls."
            raise InvalidResumeRequestError(msg)
        return None

    if len(interrupt_calls) != len(calls):
        msg = "Interrupt and ordinary tool calls cannot be resumed in one exchange."
        raise InvalidResumeRequestError(msg)

    parsed_calls = _parse_interrupt_calls(interrupt_calls)
    values = _parse_tool_results(tool_messages, parsed_calls)

    run_ids = {call.run_id for call in parsed_calls.values()}
    state_tokens = {call.state_token for call in parsed_calls.values()}
    if len(run_ids) != 1 or len(state_tokens) != 1:
        msg = (
            "All interrupt tool calls in one exchange must belong to the same run "
            "and interrupt generation."
        )
        raise InvalidResumeRequestError(msg)

    return InterruptResume(
        run_id=run_ids.pop(),
        state_token=state_tokens.pop(),
        values=values,
    )
validate_interrupt_payload
validate_interrupt_payload(payload)

Reject graph values that cannot cross the OpenAI JSON boundary.

Source code in src/langgraph_openai_serve/api/chat/utils/interrupts.py
def validate_interrupt_payload(payload: Any) -> None:
    """Reject graph values that cannot cross the OpenAI JSON boundary."""
    _dump_json(payload)
responses

OpenAI chat response builders.

ChatCompletionStreamResponseBuilder
ChatCompletionStreamResponseBuilder(model, *, include_usage=False)

Build OpenAI-compatible chat completion SSE chunks.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def __init__(self, model: str, *, include_usage: bool = False) -> None:
    self.response_id = f"chatcmpl-{uuid.uuid4()}"
    self.created = int(time.time())
    self.model = model
    self.include_usage = include_usage
client_event
client_event(extension)

Build an empty-delta chunk carrying the opt-in event extension.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def client_event(self, extension: dict[str, object]) -> str:
    """Build an empty-delta chunk carrying the opt-in event extension."""
    return self._chunk(
        ChatCompletionStreamResponseDelta(),
        client_event_extension=extension,
    )
done
done()

Stream done.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def done(self) -> str:  # ruff: ignore[no-self-use]
    """Stream done."""
    return "data: [DONE]\n\n"
error
error(message)

Stream error.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def error(self, message: str) -> str:
    """Stream error."""
    return self._format_data(
        openai_error_payload(ErrorObject(message=message, type="server_error"))
    )
finish
finish(finish_reason, *, annotations=None)

Stream finish.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def finish(
    self,
    finish_reason: str,
    *,
    annotations: list[Annotation] | None = None,
) -> str:
    """Stream finish."""
    return self._chunk(
        ChatCompletionStreamResponseDelta(),
        finish_reason=finish_reason,
        annotations=annotations,
    )
interrupt
interrupt(batch)

Stream interrupt.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def interrupt(self, batch: LangGraphInterruptBatch) -> str:
    """Stream interrupt."""
    return self._chunk(
        ChatCompletionStreamResponseDelta(
            tool_calls=[
                ChatCompletionStreamToolCall(
                    index=index,
                    id=interrupt_tool_call_id(interrupt.id),
                    type="function",
                    function=ChatCompletionStreamToolCallFunction(
                        name=INTERRUPT_TOOL_NAME,
                        arguments=interrupt_tool_arguments(batch, interrupt),
                    ),
                )
                for index, interrupt in enumerate(batch.interrupts)
            ],
        ),
    )
role
role()

Stream role.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def role(self) -> str:
    """Stream role."""
    return self._chunk(ChatCompletionStreamResponseDelta(role=Role.ASSISTANT))
text
text(content)

Stream text content.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def text(self, content: str) -> str:
    """Stream text content."""
    return self._chunk(ChatCompletionStreamResponseDelta(content=content))
tool_calls
tool_calls(message)

Stream complete final-message tool calls as one delta.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def tool_calls(self, message: AIMessage) -> str:
    """Stream complete final-message tool calls as one delta."""
    return self._chunk(
        ChatCompletionStreamResponseDelta(
            tool_calls=[
                ChatCompletionStreamToolCall(
                    index=index,
                    id=tool_call.id,
                    type=tool_call.type,
                    function=ChatCompletionStreamToolCallFunction(
                        name=tool_call.function.name,
                        arguments=tool_call.function.arguments,
                    ),
                )
                for index, tool_call in enumerate(tool_calls_from_message(message))
            ]
        )
    )
usage
usage(usage)

Stream the optional final usage-only chunk.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def usage(self, usage: UsageMetadata) -> str:
    """Stream the optional final usage-only chunk."""
    response = ChatCompletionStreamResponse(
        id=self.response_id,
        created=self.created,
        model=self.model,
        choices=[],
        usage=usage_info(usage),
    )
    return self._format_data(response.model_dump(mode="json", exclude_none=True))
annotations_from_message
annotations_from_message(message)

Convert native LangChain citations to OpenAI URL annotations.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def annotations_from_message(message: AIMessage) -> list[Annotation]:
    """Convert native LangChain citations to OpenAI URL annotations."""
    annotations = []
    text_offset = 0
    for block in message.content_blocks:
        if block["type"] != "text":
            continue
        for citation in block.get("annotations", []):
            if citation.get("type") != "citation":
                continue
            citation = cast("Citation", citation)
            required = {"url", "title", "start_index", "end_index"}
            if not required.issubset(citation):
                continue
            annotation = Annotation.model_validate(
                {
                    "type": "url_citation",
                    "url_citation": {
                        "url": citation["url"],
                        "title": citation["title"],
                        "start_index": citation["start_index"] + text_offset,
                        "end_index": citation["end_index"] + text_offset,
                    },
                }
            )
            span = citation_slice(annotation, message.text)
            cited_text = citation.get("cited_text")
            if cited_text is not None and message.text[span] != cited_text:
                msg = "citation indices must match cited_text"
                raise ValueError(msg)
            annotations.append(annotation)
        text_offset += len(block["text"])
    return annotations
chat_completion_response
chat_completion_response(*, model, completion)

Build a non-streaming OpenAI-compatible chat completion response.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def chat_completion_response(
    *,
    model: str,
    completion: LangGraphOutput,
) -> ChatCompletionResponse:
    """Build a non-streaming OpenAI-compatible chat completion response."""
    message, finish_reason = response_message(completion)
    usage = completion.usage_metadata if isinstance(completion, AIMessage) else None
    return ChatCompletionResponse(
        id=f"chatcmpl-{uuid.uuid4()}",
        created=int(time.time()),
        model=model,
        choices=[
            ChatCompletionResponseChoice(
                index=0,
                message=message,
                finish_reason=finish_reason,
            )
        ],
        usage=usage_info(usage),
    )
interrupt_tool_arguments
interrupt_tool_arguments(batch, interrupt)

Format interrupt tool arguments.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def interrupt_tool_arguments(
    batch: LangGraphInterruptBatch,
    interrupt: Interrupt,
) -> str:
    """Format interrupt tool arguments."""
    return interrupt_arguments(
        run_id=batch.run_id,
        state_token=batch.state_token,
        payload=interrupt.value,
    )
interrupt_tool_call
interrupt_tool_call(batch, interrupt)

Format interrupt tool call.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def interrupt_tool_call(
    batch: LangGraphInterruptBatch,
    interrupt: Interrupt,
) -> ToolCall:
    """Format interrupt tool call."""
    return ToolCall(
        id=interrupt_tool_call_id(interrupt.id),
        type="function",
        function=ToolCallFunction(
            name=INTERRUPT_TOOL_NAME,
            arguments=interrupt_tool_arguments(batch, interrupt),
        ),
    )
response_message
response_message(completion)

Format response message.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def response_message(
    completion: LangGraphOutput,
) -> tuple[ChatCompletionResponseMessage, str]:
    """Format response message."""
    if isinstance(completion, LangGraphInterruptBatch):
        return (
            ChatCompletionResponseMessage(
                role=Role.ASSISTANT,
                content=None,
                tool_calls=[
                    interrupt_tool_call(completion, interrupt)
                    for interrupt in completion.interrupts
                ],
            ),
            "tool_calls",
        )

    tool_calls = tool_calls_from_message(completion)
    return (
        ChatCompletionResponseMessage(
            role=Role.ASSISTANT,
            content=completion.text or None,
            annotations=annotations_from_message(completion) or None,
            tool_calls=tool_calls or None,
        ),
        "tool_calls" if tool_calls else "stop",
    )
tool_calls_from_message
tool_calls_from_message(message)

Convert native LangChain tool calls to Chat Completions tool calls.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def tool_calls_from_message(message: AIMessage) -> list[ToolCall]:
    """Convert native LangChain tool calls to Chat Completions tool calls."""
    tool_calls = []
    for tool_call in message.tool_calls:
        tool_call_id = tool_call.get("id")
        if not tool_call_id:
            msg = "Final AIMessage tool calls must have an id."
            raise ValueError(msg)
        tool_calls.append(
            ToolCall(
                id=tool_call_id,
                function=ToolCallFunction(
                    name=tool_call["name"],
                    arguments=json.dumps(tool_call["args"]),
                ),
            )
        )
    return tool_calls
usage_info
usage_info(usage)

Map LangChain's provider-reported usage to Chat Completions usage.

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def usage_info(usage: UsageMetadata | None) -> UsageInfo | None:
    """Map LangChain's provider-reported usage to Chat Completions usage."""
    if usage is None:
        return None
    return UsageInfo(
        prompt_tokens=usage["input_tokens"],
        completion_tokens=usage["output_tokens"],
        total_tokens=usage["total_tokens"],
    )
streaming

Tie LangGraph stream production to a FastAPI request's lifetime.

Starlette owns response consumption, not the nested graph producer, so a client disconnect may leave graph and provider work running. The request dependency creates a _StreamOwner; the route passes start()'s receive stream to StreamingResponse, and dependency cleanup cancels the producer and releases its GraphRun.

AnyIO still provides the channel and cleanup shield, but its task-group level cancellation can repeatedly interrupt LangGraph's asyncio-native teardown. The producer therefore remains an asyncio.Task so cancellation is delivered once at the stream boundary.

views

Chat completion router.

This module provides the FastAPI router for the chat completion endpoint, implementing an OpenAI-compatible interface.

client_error_param
client_error_param(error)

Get client error param.

Source code in src/langgraph_openai_serve/api/chat/views.py
def client_error_param(error: Exception) -> str | None:
    """Get client error param."""
    match error:
        case GraphNotFoundError():
            return "model"
        case InvalidRunIDError():
            return f"metadata.{RUN_METADATA_KEY}"
        case InvalidResumeRequestError() | InvalidChatMessageError():
            return "messages"
        case ClientSettingsValidationError():
            return error.param
        case _:
            return None
create_chat_completion async
create_chat_completion(chat_request, graph_registry, checkpoint_scope, stream_owner)

Create a chat completion.

This endpoint is compatible with OpenAI's chat completion API.

Parameters:

Name Type Description Default
chat_request ChatCompletionRequest

The parsed chat completion request.

required
graph_registry Annotated[GraphRegistry, Depends(get_graph_registry_dependency)]

The graph registry dependency.

required
checkpoint_scope Annotated[str, Depends(checkpoint_scope_dependency)]

The checkpoint scope boundary.

required
stream_owner Annotated[_StreamOwner, Depends(stream_owner_dependency, scope=request)]

The request-scoped streaming task owner.

required

Returns:

Type Description
StreamingResponse | ChatCompletionResponse

A chat completion response, either as a complete response or as a stream.

Source code in src/langgraph_openai_serve/api/chat/views.py
@router.post(
    "/chat/completions",
    response_model=ChatCompletionResponse,
    response_model_exclude_none=True,
)
async def create_chat_completion(
    chat_request: ChatCompletionRequest,
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry_dependency)],
    checkpoint_scope: Annotated[str, Depends(checkpoint_scope_dependency)],
    stream_owner: Annotated[
        _StreamOwner,
        Depends(stream_owner_dependency, scope="request"),
    ],
) -> StreamingResponse | ChatCompletionResponse:
    """
    Create a chat completion.

    This endpoint is compatible with OpenAI's chat completion API.

    Args:
        chat_request: The parsed chat completion request.
        graph_registry: The graph registry dependency.
        checkpoint_scope: The checkpoint scope boundary.
        stream_owner: The request-scoped streaming task owner.

    Returns:
        A chat completion response, either as a complete response or as a stream.

    """
    bind_log_context(
        model=chat_request.model,
        stream=chat_request.stream,
    )

    try:
        run = await prepare_run(
            chat_request.model,
            chat_request.messages,
            graph_registry,
            chat_request,
            checkpoint_scope=checkpoint_scope,
        )

        if chat_request.stream:
            body = stream_owner.start(
                chat_service.stream_completion(chat_request, run),
                run,
            )
            return StreamingResponse(
                body,
                media_type="text/event-stream",
            )

        response = await chat_service.generate_completion(chat_request, run)
    except RunBusyError as e:
        raise OpenAIHTTPException(
            status_code=status.HTTP_409_CONFLICT,
            error=ErrorObject(
                message=str(e),
                type="invalid_request_error",
                code="run_busy",
            ),
        ) from e
    except InterruptStateConflictError as e:
        raise OpenAIHTTPException(
            status_code=status.HTTP_409_CONFLICT,
            error=ErrorObject(
                message=str(e),
                type="invalid_request_error",
                param="messages",
                code="interrupt_state_conflict",
            ),
        ) from e
    except _CLIENT_ERROR_TYPES as e:
        raise OpenAIHTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            error=ErrorObject(
                message=str(e),
                type="invalid_request_error",
                param=client_error_param(e),
            ),
        ) from e
    except (GraphConfigurationError, InvalidInterruptPayloadError) as e:
        raise OpenAIHTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error=ErrorObject(
                message=str(e),
                type="server_error",
            ),
        ) from e
    return response

health

views

health_check
health_check()

Check the health of a project.

It returns 200 if the project is healthy.

Source code in src/langgraph_openai_serve/api/health/views.py
@router.get("/health")
def health_check() -> None:
    """
    Check the health of a project.

    It returns 200 if the project is healthy.
    """
version
version()

Return API version.

Source code in src/langgraph_openai_serve/api/health/views.py
@router.get("/version")
def version() -> dict[str, str]:
    """Return API version."""
    return {"version": get_version()}

middleware

Pure ASGI middleware for request correlation.

RequestContextMiddleware

RequestContextMiddleware(app)

Attach a request ID and request context to the mounted LGOS app.

Source code in src/langgraph_openai_serve/api/middleware.py
def __init__(self, app: ASGIApp) -> None:
    self.app = app
__call__ async
__call__(scope, receive, send)

Handle HTTP requests and pass non-HTTP scopes through unchanged.

Source code in src/langgraph_openai_serve/api/middleware.py
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
    """Handle HTTP requests and pass non-HTTP scopes through unchanged."""
    if scope["type"] != "http":
        await self.app(scope, receive, send)
        return

    request_id = _request_id_from_scope(scope)
    token = begin_log_context(request_id)

    async def send_wrapper(message: Message) -> None:
        if message["type"] == "http.response.start":
            MutableHeaders(scope=message)["X-Request-ID"] = request_id
        await send(message)

    try:
        await self.app(scope, receive, send_wrapper)
    finally:
        reset_log_context(token)

models

deps

Dependencies for model routes.

get_graph_registry_dependency
get_graph_registry_dependency(request)

Get the graph registry from application state.

Source code in src/langgraph_openai_serve/api/models/deps.py
def get_graph_registry_dependency(request: Request) -> GraphRegistry:
    """Get the graph registry from application state."""
    return request.app.state.graph_registry

schemas

LangGraphModelExtension

Bases: LangGraphModelSummaryExtension

Versioned LangGraph OpenAI Serve model-detail extension.

LangGraphModelSummaryExtension

Bases: BaseModel

Versioned LGOS fields safe to include in a model list.

Model

Bases: BaseModel

Individual model information.

ModelClientSettings

Bases: BaseModel

Versioned public runtime settings for one registered graph.

ModelDetails

Bases: Model

Retrieved model with required LGOS capability metadata.

ModelList

Bases: BaseModel

List of available models.

service

Functions for building OpenAI model information.

get_model
get_model(model, graph_registry)

Get one registered graph as an OpenAI model with LGOS metadata.

Source code in src/langgraph_openai_serve/api/models/service.py
def get_model(model: str, graph_registry: GraphRegistry) -> ModelDetails:
    """Get one registered graph as an OpenAI model with LGOS metadata."""
    graph_config = graph_registry.get_graph(model)
    client_settings = graph_config.client_settings
    client_settings_details = None
    if client_settings is not None:
        client_settings_details = ModelClientSettings(
            json_schema=client_settings_json_schema(client_settings),
            defaults=client_settings_default_values(client_settings),
        )

    return ModelDetails(
        id=model,
        created=MODEL_CREATED,
        owned_by=MODEL_OWNER,
        langgraph_openai_serve=LangGraphModelExtension(
            description=graph_config.description,
            features=sorted(
                graph_config.features,
                key=lambda feature: feature.value,
            ),
            client_settings=client_settings_details,
        ),
    )
get_models
get_models(graph_registry)

Get a list of available models.

Parameters:

Name Type Description Default
graph_registry GraphRegistry

The GraphRegistry containing registered graphs.

required

Returns:

Type Description
ModelList

A list of models in OpenAI compatible format.

Source code in src/langgraph_openai_serve/api/models/service.py
def get_models(graph_registry: GraphRegistry) -> ModelList:
    """
    Get a list of available models.

    Args:
        graph_registry: The GraphRegistry containing registered graphs.

    Returns:
        A list of models in OpenAI compatible format.

    """
    models = [
        Model(
            id=name,
            created=MODEL_CREATED,
            owned_by=MODEL_OWNER,
            langgraph_openai_serve=LangGraphModelSummaryExtension(
                description=graph_config.description
            ),
        )
        for name, graph_config in graph_registry.registry.items()
    ]

    return ModelList(data=models)

views

Models router.

This module provides the FastAPI router for the models endpoint, implementing an OpenAI-compatible interface for model listing.

list_models
list_models(graph_registry)

Get a list of available models.

Source code in src/langgraph_openai_serve/api/models/views.py
@router.get("")
def list_models(
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry_dependency)],
) -> ModelList:
    """Get a list of available models."""
    return models_service.get_models(graph_registry)
retrieve_model
retrieve_model(model, graph_registry)

Retrieve one registered graph as an OpenAI model.

Source code in src/langgraph_openai_serve/api/models/views.py
@router.get(
    "/{model}",
    response_model_exclude_none=True,
)
def retrieve_model(
    model: str,
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry_dependency)],
) -> ModelDetails:
    """Retrieve one registered graph as an OpenAI model."""
    try:
        return models_service.get_model(model, graph_registry)
    except GraphNotFoundError as exc:
        raise OpenAIHTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            error=ErrorObject(
                message=str(exc),
                type="invalid_request_error",
                param="model",
                code="model_not_found",
            ),
        ) from exc

core

errors

OpenAI-compatible error response helpers.

OpenAIHTTPException

OpenAIHTTPException(*, status_code, error, headers=None)

Bases: HTTPException

HTTP exception that carries OpenAI error object metadata.

Source code in src/langgraph_openai_serve/core/errors.py
def __init__(
    self,
    *,
    status_code: int,
    error: ErrorObject,
    headers: dict[str, str] | None = None,
) -> None:
    super().__init__(
        status_code=status_code,
        detail=error.message,
        headers=headers,
    )
    self.error = error

configure_openai_error_handlers

configure_openai_error_handlers(app)

Install OpenAI-compatible JSON error handlers on a FastAPI app.

Source code in src/langgraph_openai_serve/core/errors.py
def configure_openai_error_handlers(app: FastAPI) -> None:
    """Install OpenAI-compatible JSON error handlers on a FastAPI app."""
    # Starlette dispatches each handler only for its registered exception class.
    http_handler = cast("HTTPExceptionHandler", openai_http_exception_handler)
    validation_handler = cast(
        "HTTPExceptionHandler",
        openai_request_validation_exception_handler,
    )

    app.add_exception_handler(OpenAIHTTPException, http_handler)
    app.add_exception_handler(
        StarletteHTTPException,
        http_handler,
    )
    app.add_exception_handler(
        RequestValidationError,
        validation_handler,
    )
    app.add_exception_handler(Exception, openai_unhandled_exception_handler)

openai_error_payload

openai_error_payload(error)

Create OpenAI error payload.

Source code in src/langgraph_openai_serve/core/errors.py
def openai_error_payload(error: ErrorObject) -> dict[str, Any]:
    """Create OpenAI error payload."""
    return {"error": error.model_dump(mode="json")}

openai_http_exception_handler async

openai_http_exception_handler(request, exc)

Handle HTTP exceptions.

Source code in src/langgraph_openai_serve/core/errors.py
async def openai_http_exception_handler(  # ruff: ignore[unused-async]
    request: Request,
    exc: StarletteHTTPException,
) -> JSONResponse:
    """Handle HTTP exceptions."""
    if exc.status_code >= status.HTTP_500_INTERNAL_SERVER_ERROR:
        _log_server_error(request, exc.status_code, exc.__cause__ or exc)

    if isinstance(exc, OpenAIHTTPException):
        error = exc.error
    else:
        message = exc.detail if isinstance(exc.detail, str) else str(exc.detail)
        error_type = (
            "server_error"
            if exc.status_code >= status.HTTP_500_INTERNAL_SERVER_ERROR
            else "invalid_request_error"
        )
        error = ErrorObject(message=message, type=error_type)

    return JSONResponse(
        status_code=exc.status_code,
        content=openai_error_payload(error),
        headers=getattr(exc, "headers", None),
    )

openai_request_validation_exception_handler async

openai_request_validation_exception_handler(_request, exc)

Handle validation exceptions.

Source code in src/langgraph_openai_serve/core/errors.py
async def openai_request_validation_exception_handler(  # ruff: ignore[unused-async]
    _request: Request,
    exc: RequestValidationError,
) -> JSONResponse:
    """Handle validation exceptions."""
    first_error = exc.errors()[0] if exc.errors() else {}
    location = first_error.get("loc", ())
    if not isinstance(location, (tuple, list)):
        location = ()

    parts = [str(part) for part in location if part not in {"body", "query", "path"}]
    param = ".".join(parts) or None
    message = str(first_error.get("msg") or "Invalid request")
    if param:
        message = f"{param}: {message}"

    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content=openai_error_payload(
            ErrorObject(
                message=message,
                type="invalid_request_error",
                param=param,
            )
        ),
    )

openai_unhandled_exception_handler async

openai_unhandled_exception_handler(request, exc)

Handle unhandled exceptions.

Source code in src/langgraph_openai_serve/core/errors.py
async def openai_unhandled_exception_handler(  # ruff: ignore[unused-async]
    request: Request,
    exc: Exception,
) -> JSONResponse:
    """Handle unhandled exceptions."""
    _log_server_error(request, status.HTTP_500_INTERNAL_SERVER_ERROR, exc)
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content=openai_error_payload(
            ErrorObject(
                message="Internal server error",
                type="server_error",
            )
        ),
    )

logging

Request-scoped context for standard-library log records.

RequestContextFilter

RequestContextFilter()

Bases: Filter

Add active LGOS request fields to records emitted by LGOS loggers.

Source code in src/langgraph_openai_serve/core/logging.py
def __init__(self) -> None:
    super().__init__()
    self._log_context = _log_context
filter
filter(record)

Enrich a record while preserving fields supplied by the caller.

Source code in src/langgraph_openai_serve/core/logging.py
def filter(self, record: logging.LogRecord) -> bool:
    """Enrich a record while preserving fields supplied by the caller."""
    for name, value in (self._log_context.get() or {}).items():
        if name not in record.__dict__:
            setattr(record, name, value)
    return True

begin_log_context

begin_log_context(request_id)

Start a request context and return a token for restoring its parent.

Source code in src/langgraph_openai_serve/core/logging.py
def begin_log_context(request_id: str) -> Token[_LogContext | None]:
    """Start a request context and return a token for restoring its parent."""
    return _log_context.set({"request_id": request_id})

bind_log_context

bind_log_context(*, model=None, stream=None, operation_id=None)

Add fields to the active request context without mutating it.

Source code in src/langgraph_openai_serve/core/logging.py
def bind_log_context(
    *,
    model: str | None = None,
    stream: bool | None = None,
    operation_id: str | None = None,
) -> None:
    """Add fields to the active request context without mutating it."""
    current = _log_context.get()
    if current is None:
        return

    fields: _LogContext = {}
    if model is not None:
        fields["model"] = model
    if stream is not None:
        fields["stream"] = stream
    if operation_id is not None:
        fields["operation_id"] = operation_id
    if fields:
        _log_context.set({**current, **fields})

exception_type_name

exception_type_name(exc)

Return the canonical OpenTelemetry error type for an exception.

Source code in src/langgraph_openai_serve/core/logging.py
def exception_type_name(exc: BaseException) -> str:
    """Return the canonical OpenTelemetry error type for an exception."""
    cls = type(exc)
    if cls.__module__ == "builtins":
        return cls.__qualname__
    return f"{cls.__module__}.{cls.__qualname__}"

get_log_context

get_log_context()

Return a copy of the active request context.

Source code in src/langgraph_openai_serve/core/logging.py
def get_log_context() -> _LogContext:
    """Return a copy of the active request context."""
    return dict(_log_context.get() or {})

get_logger

get_logger(name)

Return a normal logger with the LGOS context filter installed once.

Source code in src/langgraph_openai_serve/core/logging.py
def get_logger(name: str) -> logging.Logger:
    """Return a normal logger with the LGOS context filter installed once."""
    logger = logging.getLogger(name)
    if not any(isinstance(item, RequestContextFilter) for item in logger.filters):
        logger.addFilter(RequestContextFilter())
    return logger

reset_log_context

reset_log_context(token)

Restore the context that was active before a request started.

Source code in src/langgraph_openai_serve/core/logging.py
def reset_log_context(token: Token[_LogContext | None]) -> None:
    """Restore the context that was active before a request started."""
    _log_context.reset(token)

settings

Settings

Bases: BaseSettings

Package settings read from explicit values and the process environment.

fastapi_docs_kwargs property
fastapi_docs_kwargs

Kwargs to configure FastAPI docs visibility.

check_langfuse_settings classmethod
check_langfuse_settings(v)

Validate Langfuse settings if enabled.

Source code in src/langgraph_openai_serve/core/settings.py
@field_validator("ENABLE_LANGFUSE")
@classmethod
def check_langfuse_settings(cls, v: bool) -> bool:
    """Validate Langfuse settings if enabled."""
    if v is False:
        return v

    if importlib.util.find_spec("langfuse") is None:
        msg = (
            "Langfuse is enabled but the 'langfuse' package is not installed. "
            "Please install it, e.g., with `uv add langgraph-openai-serve[tracing]`."
        )
        raise RuntimeError(msg)

    required_env_vars = [
        "LANGFUSE_PUBLIC_KEY",
        "LANGFUSE_SECRET_KEY",
    ]
    missing_vars = [
        var for var in required_env_vars if not os.getenv(var, "").strip()
    ]

    if missing_vars:
        msg = (
            "Langfuse is enabled but the following environment variables are not set: "
            f"{', '.join(missing_vars)}. Please set these variables."
        )
        raise RuntimeError(msg)

    return v
validate_openai_api_prefix classmethod
validate_openai_api_prefix(v)

Validate the mount prefix for OpenAI-compatible endpoints.

Source code in src/langgraph_openai_serve/core/settings.py
@field_validator("OPENAI_API_PREFIX")
@classmethod
def validate_openai_api_prefix(cls, v: str) -> str:
    """Validate the mount prefix for OpenAI-compatible endpoints."""
    return normalize_openai_api_prefix(v)

normalize_openai_api_prefix

normalize_openai_api_prefix(v)

Normalize and validate the OpenAI-compatible API mount prefix.

Source code in src/langgraph_openai_serve/core/settings.py
def normalize_openai_api_prefix(v: str) -> str:
    """Normalize and validate the OpenAI-compatible API mount prefix."""
    if not v.startswith("/"):
        msg = "OPENAI_API_PREFIX must start with '/'."
        raise ValueError(msg)
    if len(v) > 1:
        normalized = v.rstrip("/")
        if not normalized:
            msg = "OPENAI_API_PREFIX must not contain only slashes."
            raise ValueError(msg)
        return normalized
    return v

version

get_version cached

get_version()

Return installed package version.

Source code in src/langgraph_openai_serve/core/version.py
5
6
7
8
@lru_cache
def get_version() -> str:
    """Return installed package version."""
    return metadata_version("langgraph_openai_serve")

graph

Service package for the LangGraph OpenAI compatible API.

client_settings

Public graph settings transported through standard OpenAI requests.

ClientSettings

Bases: BaseModel

Base class for settings that clients may configure for a graph.

Subclasses define the complete public contract. Every field must have a valid JSON-serializable default so model discovery can advertise a usable settings object without maintaining a second defaults mapping.

defaults classmethod
defaults()

Return a deep copy of the registration-validated defaults.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def defaults(cls) -> Self:
    """Return a deep copy of the registration-validated defaults."""
    return cast(
        "Self",
        _validated_contract(cls).defaults.model_copy(deep=True),
    )
validate_request classmethod
validate_request(request)

Read and validate this model's values from an OpenAI request.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def validate_request(cls, request: ChatCompletionRequest) -> Self:
    """Read and validate this model's values from an OpenAI request."""
    parameter = f"metadata.{RUNTIME_SETTINGS_METADATA_KEY}"
    encoded = (request.metadata or {}).get(RUNTIME_SETTINGS_METADATA_KEY, "{}")

    try:
        changes = _validate_json_object(encoded)
    except ValidationError as exc:
        raise ClientSettingsValidationError(
            _validation_message(exc, label="runtime settings"),
            param=parameter,
        ) from exc

    values = client_settings_default_values(cls)
    values.update(changes)

    try:
        settings = cls.model_validate_json(
            _SETTINGS_OBJECT_ADAPTER.dump_json(values),
            strict=True,
            by_alias=False,
            by_name=True,
        )
    except ValidationError as exc:
        field = _first_error_field(exc)
        raise ClientSettingsValidationError(
            _validation_message(exc, label=field or "runtime settings"),
            param=parameter,
        ) from exc
    else:
        _validated_settings_json(settings)
        return settings

ClientSettingsValidationError

ClientSettingsValidationError(message, *, param=None)

Bases: ValueError

Raised when client-controlled graph settings are invalid.

Source code in src/langgraph_openai_serve/graph/client_settings.py
def __init__(self, message: str, *, param: str | None = None) -> None:
    super().__init__(message)
    self.param = param

client_settings_default_values

client_settings_default_values(settings_model)

Return a fresh copy of the registration-validated JSON defaults.

Source code in src/langgraph_openai_serve/graph/client_settings.py
def client_settings_default_values(
    settings_model: type[ClientSettings],
) -> dict[str, JsonValue]:
    """Return a fresh copy of the registration-validated JSON defaults."""
    return _validate_json_object(_validated_contract(settings_model).defaults_json)

client_settings_json_schema

client_settings_json_schema(settings_model)

Return a fresh copy of the registration-validated discovery schema.

Source code in src/langgraph_openai_serve/graph/client_settings.py
def client_settings_json_schema(
    settings_model: type[ClientSettings],
) -> dict[str, JsonValue]:
    """Return a fresh copy of the registration-validated discovery schema."""
    return _validate_json_object(_validated_contract(settings_model).json_schema_json)

validate_client_settings_model

validate_client_settings_model(settings_model)

Validate the registration-time contract of a settings model.

Source code in src/langgraph_openai_serve/graph/client_settings.py
def validate_client_settings_model(
    settings_model: type[ClientSettings],
) -> type[ClientSettings]:
    """Validate the registration-time contract of a settings model."""
    if any(
        settings_model.model_config.get(key) != expected
        for key, expected in ClientSettings.model_config.items()
    ):
        msg = "ClientSettings subclasses must preserve the inherited model config."
        raise ValueError(msg)

    if any(
        field.exclude or getattr(field, "exclude_if", None) is not None
        for field in settings_model.model_fields.values()
    ):
        msg = "ClientSettings fields cannot be excluded from defaults."
        raise ValueError(msg)

    client_settings_json_schema(settings_model)
    return settings_model

events

Public events emitted by LangGraph nodes and tools.

citation_slice

citation_slice(annotation, content)

Convert an OpenAI inclusive citation span to a validated Python slice.

Source code in src/langgraph_openai_serve/graph/events.py
def citation_slice(annotation: Annotation, content: str) -> slice:
    """Convert an OpenAI inclusive citation span to a validated Python slice."""
    citation = annotation.url_citation
    start = citation.start_index
    stop = citation.end_index + 1
    if not 0 <= start < stop <= len(content):
        msg = "citation indices must refer to the final assistant text"
        raise ValueError(msg)
    return slice(start, stop)

client_event

client_event(event_type, data, *, namespace=())

Build an explicitly public, JSON-safe client stream event.

Source code in src/langgraph_openai_serve/graph/events.py
def client_event(
    event_type: ClientEventType,
    data: JsonValue,
    *,
    namespace: tuple[str, ...] = (),
) -> dict[str, object]:
    """Build an explicitly public, JSON-safe client stream event."""
    envelope = _ClientEventEnvelope(
        type=_CLIENT_EVENT_ENVELOPE_TYPE,
        schema_version=CLIENT_EVENT_SCHEMA_VERSION,
        event=_ClientEventData(
            type=event_type,
            namespace=namespace,
            data=data,
        ),
    )
    return envelope.model_dump(mode="json")

client_event_extension

client_event_extension(value)

Build a stream extension from validated public custom stream data.

Source code in src/langgraph_openai_serve/graph/events.py
def client_event_extension(value: object) -> dict[str, object] | None:
    """Build a stream extension from validated public custom stream data."""
    if not isinstance(value, dict) or value.get("type") != _CLIENT_EVENT_ENVELOPE_TYPE:
        return None

    try:
        envelope = _ClientEventEnvelope.model_validate(value)
    except ValidationError:
        return None
    return envelope.model_dump(mode="json", exclude={"type"})

status_event

status_event(description, *, done=False, hidden=False, namespace=())

Build a portable status update for native client UI.

Source code in src/langgraph_openai_serve/graph/events.py
def status_event(
    description: str,
    *,
    done: bool = False,
    hidden: bool = False,
    namespace: tuple[str, ...] = (),
) -> dict[str, object]:
    """Build a portable status update for native client UI."""
    data = _StatusEventData(
        description=description,
        done=done,
        hidden=hidden,
    )
    return client_event(
        "status",
        data.model_dump(mode="json"),
        namespace=namespace,
    )

features

GraphFeature

Bases: StrEnum

Features supported by a registered graph.

graph_registry

GraphConfig

Bases: BaseModel

Graph configuration.

build_context async
build_context(request, graph)

Build the LangGraph runtime context for a request.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def build_context(
    self,
    request: ChatCompletionRequest,
    graph: CompiledStateGraph,
) -> Any:
    """Build the LangGraph runtime context for a request."""
    settings = (
        self.client_settings.validate_request(request)
        if self.client_settings is not None
        else None
    )
    if self.context_factory is not None:
        context = await _maybe_await(self.context_factory(request, settings))
    else:
        context = settings

    if context is None:
        return None
    if graph.context_schema is None:
        msg = "A graph that produces runtime context must declare context_schema."
        raise GraphConfigurationError(msg)

    # Preserve server-owned context objects; LangGraph applies context_schema
    # coercion when it invokes the graph.
    return context
build_input async
build_input(request, messages)

Build the native graph input for a chat completion request.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def build_input(
    self,
    request: ChatCompletionRequest,
    messages: list[BaseMessage],
) -> Any:
    """Build the native graph input for a chat completion request."""
    if self.request_to_input is None:
        return {"messages": messages}
    return await _maybe_await(self.request_to_input(request, messages))
render_output async
render_output(output)

Convert native graph output into the durable assistant message.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def render_output(self, output: Any) -> AIMessage:
    """Convert native graph output into the durable assistant message."""
    if self.output_to_message is not None:
        return await _maybe_await(self.output_to_message(output))

    messages = (
        output["messages"]
        if isinstance(output, Mapping)
        else getattr(output, "messages", None)
    )
    if messages is None:
        msg = "Graph output must expose a messages field."
        raise GraphConfigurationError(msg)
    if not messages:
        return AIMessage(content="")
    message = messages[-1]
    if not isinstance(message, AIMessage):
        msg = "The final graph message must be an AIMessage."
        raise GraphConfigurationError(msg)
    return message
resolve_graph async
resolve_graph()

Get the graph instance, resolving callable graph factories.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def resolve_graph(self) -> CompiledStateGraph:
    """Get the graph instance, resolving callable graph factories."""
    if self.run_coordinator is not None and not self.supports(
        GraphFeature.INTERRUPTS
    ):
        msg = "run_coordinator is only supported by interrupt-enabled graphs."
        raise GraphConfigurationError(msg)

    if isinstance(self.graph, CompiledStateGraph):
        graph = self.graph
    else:
        graph = await _maybe_await(self.graph())

    if (
        self.client_settings is not None
        and self.context_factory is None
        and graph.context_schema is not self.client_settings
    ):
        msg = (
            "Graphs using client_settings directly must use that settings model "
            "as context_schema."
        )
        raise GraphConfigurationError(msg)

    if self.supports(GraphFeature.INTERRUPTS):
        checkpointer = graph.checkpointer
        if checkpointer is None or any(
            not _overrides_checkpointer_method(checkpointer, method_name)
            for method_name in _INTERRUPT_CHECKPOINTER_METHODS
        ):
            msg = (
                "Interrupt-enabled graphs must use a fully asynchronous "
                "checkpointer with thread deletion."
            )
            raise GraphConfigurationError(msg)
        if self.run_coordinator is None:
            msg = "Interrupt-enabled graphs must configure a run_coordinator."
            raise GraphConfigurationError(msg)

    return graph
supports
supports(feature)

Return whether this graph supports a feature.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def supports(self, feature: GraphFeature) -> bool:
    """Return whether this graph supports a feature."""
    return feature in self.features
validate_client_settings classmethod
validate_client_settings(value)

Validate a public settings model when its graph is registered.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@field_validator("client_settings")
@classmethod
def validate_client_settings(
    cls,
    value: type[ClientSettings] | None,
) -> type[ClientSettings] | None:
    """Validate a public settings model when its graph is registered."""
    return validate_client_settings_model(value) if value is not None else None

GraphConfigurationError

Bases: RuntimeError

Raised when a registered graph cannot satisfy its declared config.

GraphNotFoundError

Bases: ValueError

Raised when a requested graph is not registered.

GraphRegistry

Bases: BaseModel

Registry of graphs.

get_graph
get_graph(name)

Get a graph by its name.

Parameters:

Name Type Description Default
name str

The name of the graph to retrieve.

required

Returns:

Type Description
GraphConfig

The graph configuration associated with the given name.

Raises:

Type Description
GraphNotFoundError

If the graph name is not found in the registry.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def get_graph(self, name: str) -> GraphConfig:
    """
    Get a graph by its name.

    Args:
        name: The name of the graph to retrieve.

    Returns:
        The graph configuration associated with the given name.

    Raises:
        GraphNotFoundError: If the graph name is not found in the registry.

    """
    try:
        return self.registry[name]
    except KeyError as exc:
        msg = f"Graph '{name}' not found in registry."
        raise GraphNotFoundError(msg) from exc
get_graph_names
get_graph_names()

Get the names of all registered graphs.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def get_graph_names(self) -> list[str]:
    """Get the names of all registered graphs."""
    return list(self.registry.keys())
register
register(model_id, config)

Add or replace one graph through the validated registry boundary.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def register(self, model_id: str, config: GraphConfig) -> None:
    """Add or replace one graph through the validated registry boundary."""
    self.registry = {**self.registry, model_id: config}

interrupt

Durable interrupt support for LangGraph runs.

InMemoryRunCoordinator

InMemoryRunCoordinator()

Coordinate interrupt runs within one process without waiting.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
def __init__(self) -> None:
    self._active_keys: set[str] = set()
    self._guard = Lock()
__call__ async
__call__(key)

Acquire lease asynchronously.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
@asynccontextmanager
async def __call__(self, key: str, /) -> AsyncIterator[None]:
    """Acquire lease asynchronously."""
    self._acquire(key)
    try:
        yield
    finally:
        self._release(key)

LangGraphInterruptBatch dataclass

LangGraphInterruptBatch(run_id, state_token, interrupts)

The durable interrupts awaiting answers for one graph run.

RunBusyError

RunBusyError(key)

Bases: RuntimeError

Raised when an interrupt run cannot acquire its coordination lease.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
def __init__(self, key: str) -> None:
    self.key = key
    super().__init__("This interrupt run cannot acquire its coordination lease.")

RunCoordinator

Bases: Protocol

Acquire a lease that rejects rather than queues an occupied interrupt run.

__call__
__call__(key)

Acquire lease synchronously.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
def __call__(
    self,
    key: str,
    /,
) -> AbstractAsyncContextManager[None]:
    """Acquire lease synchronously."""
    ...

coordination

Nonblocking coordination for interrupt-enabled graph runs.

InMemoryRunCoordinator
InMemoryRunCoordinator()

Coordinate interrupt runs within one process without waiting.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
def __init__(self) -> None:
    self._active_keys: set[str] = set()
    self._guard = Lock()
__call__ async
__call__(key)

Acquire lease asynchronously.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
@asynccontextmanager
async def __call__(self, key: str, /) -> AsyncIterator[None]:
    """Acquire lease asynchronously."""
    self._acquire(key)
    try:
        yield
    finally:
        self._release(key)
RunBusyError
RunBusyError(key)

Bases: RuntimeError

Raised when an interrupt run cannot acquire its coordination lease.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
def __init__(self, key: str) -> None:
    self.key = key
    super().__init__("This interrupt run cannot acquire its coordination lease.")
RunCoordinator

Bases: Protocol

Acquire a lease that rejects rather than queues an occupied interrupt run.

__call__
__call__(key)

Acquire lease synchronously.

Source code in src/langgraph_openai_serve/graph/interrupt/coordination.py
def __call__(
    self,
    key: str,
    /,
) -> AbstractAsyncContextManager[None]:
    """Acquire lease synchronously."""
    ...

models

Result models produced by interrupt-enabled graph runs.

LangGraphInterruptBatch dataclass
LangGraphInterruptBatch(run_id, state_token, interrupts)

The durable interrupts awaiting answers for one graph run.

state

Durable state and resume handling for interrupt-enabled graph runs.

InterruptStateConflictError

Bases: RuntimeError

Raised when a resume does not match durable pending state.

InvalidRunIDError

Bases: ValueError

Raised when a caller-supplied run id is not a UUID.

checkpoint_key
checkpoint_key(model, run_id, *, scope='default')

Derive a fixed-length storage key scoped to this protocol and model.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def checkpoint_key(model: str, run_id: str, *, scope: str = "default") -> str:
    """Derive a fixed-length storage key scoped to this protocol and model."""
    identity = json.dumps(
        ["langgraph-openai-serve.interrupt.v2", scope, model, run_id],
        ensure_ascii=False,
        separators=(",", ":"),
    )
    return hashlib.sha256(identity.encode()).hexdigest()
checkpoint_state_token async
checkpoint_state_token(graph, runnable_config)

Fingerprint the latest checkpoint in every namespace.

Nested resumes may not advance the root checkpoint, and indirectly invoked subgraphs are not exposed through state snapshots. Scanning the checkpointer keeps stale-resume detection generic without introducing separate state.

Performance impact: Local PostgreSQL measurements were 0.5-0.7 ms for the current 1-2 tuple runs, scaling linearly to about 5 ms at 100 and 45 ms at 1,000 small tuples.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
async def checkpoint_state_token(
    graph: CompiledStateGraph,
    runnable_config: RunnableConfig,
) -> str | None:
    """
    Fingerprint the latest checkpoint in every namespace.

    Nested resumes may not advance the root checkpoint, and indirectly invoked
    subgraphs are not exposed through state snapshots. Scanning the checkpointer
    keeps stale-resume detection generic without introducing separate state.

    Performance impact: Local PostgreSQL measurements were 0.5-0.7 ms for the
    current 1-2 tuple runs, scaling linearly to about 5 ms at 100 and 45 ms at
    1,000 small tuples.
    """
    checkpointer = cast("BaseCheckpointSaver", graph.checkpointer)
    thread_id = runnable_config["configurable"]["thread_id"]
    heads: dict[str, tuple[str, list[tuple[str, int]]]] = {}

    async for checkpoint_tuple in checkpointer.alist(
        {"configurable": {"thread_id": thread_id}}
    ):
        namespace = checkpoint_tuple.config["configurable"].get("checkpoint_ns", "")
        checkpoint_id = require_checkpoint_id(checkpoint_tuple.config)
        head = heads.get(namespace)
        if head is not None and checkpoint_id <= head[0]:
            continue

        heads[namespace] = (
            checkpoint_id,
            sorted(
                (
                    task_id,
                    len(value) if isinstance(value, (list, tuple)) else 1,
                )
                for task_id, channel, value in checkpoint_tuple.pending_writes or ()
                if channel == RESUME
            ),
        )

    if not heads:
        return None

    identity = json.dumps(
        [
            "langgraph-openai-serve.interrupt-state.v2",
            sorted((namespace, *head) for namespace, head in heads.items()),
        ],
        separators=(",", ":"),
    )
    return hashlib.sha256(identity.encode()).hexdigest()
durable_interrupt_batch async
durable_interrupt_batch(graph, runnable_config, run_id)

Read the durable checkpoint head after graph execution has quiesced.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
async def durable_interrupt_batch(
    graph: CompiledStateGraph,
    runnable_config: RunnableConfig | None,
    run_id: str | None,
) -> LangGraphInterruptBatch | None:
    """Read the durable checkpoint head after graph execution has quiesced."""
    if runnable_config is None:
        msg = "Interrupt-enabled runs require runnable configuration."
        raise RuntimeError(msg)

    snapshot = await graph.aget_state(runnable_config, subgraphs=True)
    if not snapshot.interrupts:
        return None

    pending_interrupts = interrupts_by_id(snapshot)
    for interrupt in pending_interrupts.values():
        validate_interrupt_payload(interrupt.value)

    if run_id is None:
        msg = "run_id cannot be None"
        raise RuntimeError(msg)
    state_token = await checkpoint_state_token(graph, snapshot.config)
    if state_token is None:
        msg = "Interrupted LangGraph state has no checkpoint tuple."
        raise RuntimeError(msg)
    return LangGraphInterruptBatch(
        run_id=run_id,
        state_token=state_token,
        interrupts=tuple(pending_interrupts.values()),
    )
get_run_id
get_run_id(request)

Read the optional interrupt run id from OpenAI request metadata.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def get_run_id(request: ChatCompletionRequest) -> str | None:
    """Read the optional interrupt run id from OpenAI request metadata."""
    return (request.metadata or {}).get(RUN_METADATA_KEY)
interrupts_by_id
interrupts_by_id(snapshot)

Validate and index the interrupts exposed by a state snapshot.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def interrupts_by_id(snapshot: StateSnapshot) -> dict[str, Interrupt]:
    """Validate and index the interrupts exposed by a state snapshot."""
    pending: dict[str, Interrupt] = {}
    for interrupt in snapshot.interrupts:
        interrupt_id = interrupt.id
        if not isinstance(interrupt_id, str) or not interrupt_id:
            msg = "Durable interrupt state has an invalid interrupt id."
            raise RuntimeError(msg)
        if interrupt_id in pending:
            msg = "Durable interrupt state has duplicate interrupt ids."
            raise RuntimeError(msg)
        pending[interrupt_id] = interrupt
    return pending
normalize_checkpoint_scope
normalize_checkpoint_scope(value)

Validate a server-owned checkpoint isolation scope.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def normalize_checkpoint_scope(value: str) -> str:
    """Validate a server-owned checkpoint isolation scope."""
    if not isinstance(value, str) or not value.strip():
        msg = "checkpoint_scope must resolve to a non-empty server-trusted string."
        raise GraphConfigurationError(msg)
    return value.strip()
normalize_run_id
normalize_run_id(value)

Return the canonical form of a valid, non-nil UUID run id.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def normalize_run_id(value: str) -> str:
    """Return the canonical form of a valid, non-nil UUID run id."""
    try:
        parsed = uuid.UUID(value)
    except (AttributeError, TypeError, ValueError) as exc:
        msg = f"metadata.{RUN_METADATA_KEY} must be a UUID when provided."
        raise InvalidRunIDError(msg) from exc
    if parsed.int == 0:
        msg = f"metadata.{RUN_METADATA_KEY} must not be the nil UUID."
        raise InvalidRunIDError(msg)
    return str(parsed)
prepare_interrupt_input async
prepare_interrupt_input(graph_config, graph, request, snapshot, resume)

Build a new input or causally validate an interrupt resume.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
async def prepare_interrupt_input(
    graph_config: GraphConfig,
    graph: CompiledStateGraph,
    request: ChatCompletionRequest,
    snapshot: StateSnapshot,
    resume: InterruptResume | None,
) -> tuple[Any, bool]:
    """Build a new input or causally validate an interrupt resume."""
    pending_interrupts = interrupts_by_id(snapshot)
    checkpoint_id = get_checkpoint_id(snapshot.config)

    if resume is None:
        if checkpoint_id is None:
            lc_messages = convert_to_lc_messages(request.messages)
            return await graph_config.build_input(request, lc_messages), True
        if pending_interrupts:
            # Re-emit persisted tool calls without rerunning graph nodes.
            return None, False
        msg = "This run_id has already been used."
        raise InterruptStateConflictError(msg)

    if checkpoint_id is None:
        msg = "No durable interrupt state exists for this run."
        raise InterruptStateConflictError(msg)
    if not pending_interrupts:
        msg = "This run no longer has pending interrupts."
        raise InterruptStateConflictError(msg)

    state_token = await checkpoint_state_token(graph, snapshot.config)
    if state_token is None:
        msg = "No durable interrupt state exists for this run."
        raise InterruptStateConflictError(msg)
    return _resume_interrupt_inputs(
        state_token,
        set(pending_interrupts),
        resume,
    ), True
require_checkpoint_id
require_checkpoint_id(config)

Return the checkpoint id from a validated LangGraph config.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def require_checkpoint_id(config: RunnableConfig) -> str:
    """Return the checkpoint id from a validated LangGraph config."""
    try:
        checkpoint_id = get_checkpoint_id(config)
    except (AttributeError, KeyError, TypeError):
        checkpoint_id = None
    if not isinstance(checkpoint_id, str) or not checkpoint_id:
        msg = "Durable interrupt state has no checkpoint_id."
        raise RuntimeError(msg)
    return checkpoint_id
resolve_run_id
resolve_run_id(requested_run_id, resume)

Resolve and validate the durable run identity for a request.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def resolve_run_id(
    requested_run_id: str | None,
    resume: InterruptResume | None,
) -> str:
    """Resolve and validate the durable run identity for a request."""
    if resume is not None:
        resume_run_id = normalize_run_id(resume.run_id)
        if requested_run_id is not None:
            requested_run_id = normalize_run_id(requested_run_id)
            if requested_run_id != resume_run_id:
                msg = (
                    f"metadata.{RUN_METADATA_KEY} does not match the interrupt "
                    "tool call."
                )
                raise InvalidResumeRequestError(msg)
        return resume_run_id

    if requested_run_id is not None:
        return normalize_run_id(requested_run_id)
    return str(uuid.uuid4())

runner

Run LangGraph workflows behind the OpenAI-compatible chat API.

LangGraphInvocation dataclass

LangGraphInvocation(output)

A durable graph result.

delete_checkpoint_thread async

delete_checkpoint_thread(run)

Delete terminal state retained only to support an active interrupt.

Source code in src/langgraph_openai_serve/graph/runner.py
async def delete_checkpoint_thread(run: GraphRun) -> None:
    """Delete terminal state retained only to support an active interrupt."""
    if run.checkpoint_thread_id is None:
        msg = "Interrupt-enabled run has no checkpoint thread id."
        raise RuntimeError(msg)

    checkpointer = cast("BaseCheckpointSaver", run.graph.checkpointer)
    await checkpointer.adelete_thread(run.checkpoint_thread_id)

finalize_run async

finalize_run(run, checkpoint_disposition)

Finalize checkpoint retention, then release any interrupt-run lease.

Only state exposed as a resumable interrupt is preserved. Cleanup for an unclassified run is best-effort so it cannot mask the failure that prevented classification.

Source code in src/langgraph_openai_serve/graph/runner.py
async def finalize_run(
    run: GraphRun,
    checkpoint_disposition: _CheckpointDisposition,
) -> None:
    """
    Finalize checkpoint retention, then release any interrupt-run lease.

    Only state exposed as a resumable interrupt is preserved. Cleanup for an
    unclassified run is best-effort so it cannot mask the failure that prevented
    classification.
    """
    with CancelScope(shield=True):
        try:
            if checkpoint_disposition == "delete" or (
                checkpoint_disposition == "unknown"
                and run.config.supports(GraphFeature.INTERRUPTS)
            ):
                await delete_checkpoint_thread(run)
        except Exception:
            if checkpoint_disposition != "unknown":
                raise
            logger.exception("graph_run.checkpoint_cleanup_failed")
        finally:
            try:
                await run.aclose()
            except Exception:
                if checkpoint_disposition != "unknown":
                    raise
                logger.exception("graph_run.lease_release_failed")

invoke_run async

invoke_run(run)

Invoke a graph and return only its durable result.

Source code in src/langgraph_openai_serve/graph/runner.py
async def invoke_run(run: GraphRun) -> LangGraphInvocation:
    """Invoke a graph and return only its durable result."""
    checkpoint_disposition: _CheckpointDisposition = "unknown"
    try:
        if not run.should_execute:
            interrupt_batch = await _durable_interrupt_batch(run)
            if interrupt_batch is None:
                msg = "Pending interrupt state disappeared before use."
                raise RuntimeError(msg)
            checkpoint_disposition = "preserve"
            return LangGraphInvocation(output=interrupt_batch)

        result = cast(
            "GraphOutput[Any]",
            await run.graph.ainvoke(
                run.inputs,
                config=run.runnable_config,
                context=run.context,
                output_keys=run.graph.output_channels,
                **_invoke_options(run),
            ),
        )

        if run.config.supports(GraphFeature.INTERRUPTS):
            interrupt_batch = await _durable_interrupt_batch(run)
            if interrupt_batch is not None:
                checkpoint_disposition = "preserve"
                return LangGraphInvocation(output=interrupt_batch)

        rendered_output = _with_usage(
            await run.config.render_output(result.value),
            run,
        )
        if run.config.supports(GraphFeature.INTERRUPTS):
            checkpoint_disposition = "delete"

        return LangGraphInvocation(output=rendered_output)
    finally:
        await finalize_run(run, checkpoint_disposition)

run_langgraph async

run_langgraph(model, messages, graph_registry, request=None)

Prepare and invoke a graph for direct runner callers.

This convenience wrapper combines :func:prepare_run and :func:invoke_run. The HTTP route prepares its run before creating a response so preparation errors can be returned as OpenAI-compatible HTTP errors; its service therefore calls invoke_run directly with that prepared run.

Examples:

>>> invocation = await run_langgraph("my-model", messages, registry)
>>> print(invocation.output)

Parameters:

Name Type Description Default
model str

The name of the model to use, which also determines which graph to use.

required
messages list[ChatCompletionRequestMessage]

A list of messages to process through the LangGraph.

required
graph_registry GraphRegistry

The GraphRegistry instance containing registered graphs.

required
request ChatCompletionRequest | None

The complete chat completion request passed to graph adapters.

None

Returns:

Type Description
LangGraphInvocation

The durable graph output.

Source code in src/langgraph_openai_serve/graph/runner.py
async def run_langgraph(
    model: str,
    messages: list[ChatCompletionRequestMessage],
    graph_registry: GraphRegistry,
    request: ChatCompletionRequest | None = None,
) -> LangGraphInvocation:
    """
    Prepare and invoke a graph for direct runner callers.

    This convenience wrapper combines :func:`prepare_run` and :func:`invoke_run`.
    The HTTP route prepares its run before creating a response so preparation
    errors can be returned as OpenAI-compatible HTTP errors; its service therefore
    calls ``invoke_run`` directly with that prepared run.

    Examples:
        >>> invocation = await run_langgraph("my-model", messages, registry)
        >>> print(invocation.output)

    Args:
        model: The name of the model to use, which also determines which graph to use.
        messages: A list of messages to process through the LangGraph.
        graph_registry: The GraphRegistry instance containing registered graphs.
        request: The complete chat completion request passed to graph adapters.

    Returns:
        The durable graph output.

    """
    run = await prepare_run(model, messages, graph_registry, request)

    return await invoke_run(run)

run_langgraph_stream async

run_langgraph_stream(model, messages, graph_registry, request=None)

Prepare and stream a graph for direct runner callers.

This convenience wrapper combines :func:prepare_run and :func:stream_run. The HTTP route prepares its run before starting the streaming response so preparation errors remain normal OpenAI-compatible HTTP errors; its service therefore calls stream_run directly with that prepared run.

Parameters:

Name Type Description Default
model str

The name of the model (graph) to run.

required
messages list[ChatCompletionRequestMessage]

A list of OpenAI-compatible messages.

required
graph_registry GraphRegistry

The registry containing the graph configurations.

required
request ChatCompletionRequest | None

The complete chat completion request passed to graph adapters.

None

Yields:

Type Description
AsyncGenerator[LangGraphStreamEvent, None]

Assistant text chunks, custom events, or LangGraph interrupts.

Source code in src/langgraph_openai_serve/graph/runner.py
async def run_langgraph_stream(
    model: str,
    messages: list[ChatCompletionRequestMessage],
    graph_registry: GraphRegistry,
    request: ChatCompletionRequest | None = None,
) -> AsyncGenerator[LangGraphStreamEvent, None]:
    """
    Prepare and stream a graph for direct runner callers.

    This convenience wrapper combines :func:`prepare_run` and :func:`stream_run`.
    The HTTP route prepares its run before starting the streaming response so
    preparation errors remain normal OpenAI-compatible HTTP errors; its service
    therefore calls ``stream_run`` directly with that prepared run.

    Args:
        model: The name of the model (graph) to run.
        messages: A list of OpenAI-compatible messages.
        graph_registry: The registry containing the graph configurations.
        request: The complete chat completion request passed to graph adapters.

    Yields:
        Assistant text chunks, custom events, or LangGraph interrupts.

    """
    run = await prepare_run(model, messages, graph_registry, request)
    run_stream = stream_run(run)
    async with aclosing(run_stream):
        async for event in run_stream:
            yield event

stream_run async

stream_run(run)

Stream an already prepared LangGraph invocation.

Yields:

Type Description
AsyncGenerator[LangGraphStreamEvent, None]

LangGraph stream events.

Source code in src/langgraph_openai_serve/graph/runner.py
async def stream_run(
    run: GraphRun,
) -> AsyncGenerator[LangGraphStreamEvent, None]:
    """
    Stream an already prepared LangGraph invocation.

    Yields:
        LangGraph stream events.

    """
    checkpoint_disposition: _CheckpointDisposition = "unknown"
    try:
        if not run.should_execute:
            interrupt_batch = await _durable_interrupt_batch(run)
            if interrupt_batch is None:
                msg = "Pending interrupt state disappeared before use."
                raise RuntimeError(msg)
            checkpoint_disposition = "preserve"
            yield interrupt_batch
            return

        stream_mode: list[StreamMode] = ["messages", "custom", "values"]
        final_output: Any = _MISSING

        graph_stream = cast(
            "AsyncGenerator[dict[str, Any], None]",
            run.graph.astream(
                run.inputs,
                config=run.runnable_config,
                context=run.context,
                stream_mode=stream_mode,
                **_astream_options(run),
            ),
        )
        async with aclosing(graph_stream):
            async for event in graph_stream:
                if event.get("type") == "custom":
                    yield cast("CustomStreamPart", event)
                    continue

                if event.get("type") == "values" and not event.get("ns"):
                    final_output = event.get("data")
                    continue

                if event.get("type") != "messages":
                    continue

                content = text_from_message_event(event, run)
                if content:
                    yield content

        if run.config.supports(GraphFeature.INTERRUPTS):
            interrupt_batch = await _durable_interrupt_batch(run)
            if interrupt_batch is not None:
                checkpoint_disposition = "preserve"
                yield interrupt_batch
                return
            else:
                checkpoint_disposition = "delete"

        yield await _render_stream_output(final_output, run)
    finally:
        await finalize_run(run, checkpoint_disposition)

text_from_message_event

text_from_message_event(event, run)

Extract visible text from a streamable LangGraph message event.

Source code in src/langgraph_openai_serve/graph/runner.py
def text_from_message_event(event: dict, run: GraphRun) -> str | None:
    """Extract visible text from a streamable LangGraph message event."""
    message, metadata = event["data"]
    if not isinstance(message, AIMessageChunk):
        return None
    if TAG_NOSTREAM in (metadata.get("tags") or []):
        return None
    if metadata.get("langgraph_node") not in run.config.streamable_node_names:
        return None

    content = str(message.text)
    return content or None

utils

Prepare one isolated LangGraph execution for the OpenAI API.

GraphRun dataclass

GraphRun(
    config,
    graph,
    inputs,
    context,
    runnable_config,
    run_id,
    checkpoint_thread_id=None,
    should_execute=True,
    usage_callback=UsageMetadataCallbackHandler(),
    _lease=None,
)

Context for a graph run.

aclose async
aclose()

Release this run's interrupt lease exactly once, if present.

Source code in src/langgraph_openai_serve/graph/utils.py
async def aclose(self) -> None:
    """Release this run's interrupt lease exactly once, if present."""
    lease, self._lease = self._lease, None
    if lease is not None:
        await lease.__aexit__(None, None, None)
usage_metadata
usage_metadata()

Return provider-reported usage aggregated across the graph run.

Source code in src/langgraph_openai_serve/graph/utils.py
def usage_metadata(self) -> UsageMetadata | None:
    """Return provider-reported usage aggregated across the graph run."""
    usages = self.usage_callback.usage_metadata.values()
    if not usages:
        return None
    return UsageMetadata(
        input_tokens=sum(usage["input_tokens"] for usage in usages),
        output_tokens=sum(usage["output_tokens"] for usage in usages),
        total_tokens=sum(usage["total_tokens"] for usage in usages),
    )

build_runnable_config

build_runnable_config(
    callbacks, configurable=None, *, metadata=None, extra_callbacks=()
)

Build runnable config.

Source code in src/langgraph_openai_serve/graph/utils.py
def build_runnable_config(
    callbacks: Callbacks,
    configurable: dict[str, Any] | None = None,
    *,
    metadata: dict[str, Any] | None = None,
    extra_callbacks: Sequence[BaseCallbackHandler] = (),
) -> RunnableConfig | None:
    """Build runnable config."""
    callbacks = _extend_callbacks(callbacks, extra_callbacks)
    if settings.ENABLE_LANGFUSE:
        # GraphConfig is shared across requests; add tracing without mutating its
        # callback collection or manager.
        langfuse_callback = get_langfuse_callback()
        if callbacks is None:
            callbacks = [langfuse_callback]
        elif isinstance(callbacks, list):
            callbacks = [
                *cast("list[BaseCallbackHandler]", callbacks),
                langfuse_callback,
            ]
        else:
            callbacks = callbacks.copy()
            callbacks.add_handler(langfuse_callback)

    kwargs: dict[str, Any] = {}
    if callbacks:
        kwargs["callbacks"] = callbacks
    if configurable:
        kwargs["configurable"] = configurable
    if kwargs:
        kwargs["run_name"] = _RUN_NAME
        if metadata:
            kwargs["metadata"] = metadata

    return RunnableConfig(**kwargs) if kwargs else None

prepare_run async

prepare_run(model, messages, graph_registry, request, *, checkpoint_scope='default')

Prepare a graph run.

Source code in src/langgraph_openai_serve/graph/utils.py
async def prepare_run(  # ruff: ignore[too-many-locals]
    model: str,
    messages: list[ChatCompletionRequestMessage],
    graph_registry: GraphRegistry,
    request: ChatCompletionRequest | None,
    *,
    checkpoint_scope: str = "default",
) -> GraphRun:
    """Prepare a graph run."""
    graph_config = graph_registry.get_graph(model)

    request = request or ChatCompletionRequest(model=model, messages=messages)
    graph = await graph_config.resolve_graph()
    usage_callback = UsageMetadataCallbackHandler()

    if not graph_config.supports(GraphFeature.INTERRUPTS):
        lc_messages = convert_to_lc_messages(messages)
        inputs = await graph_config.build_input(request, lc_messages)
        context = await graph_config.build_context(request, graph)
        runnable_config = build_runnable_config(
            graph_config.runtime_callbacks,
            metadata=_runnable_metadata(request),
            extra_callbacks=[usage_callback],
        )
        return GraphRun(
            config=graph_config,
            graph=graph,
            inputs=inputs,
            context=context,
            runnable_config=runnable_config,
            run_id=None,
            usage_callback=usage_callback,
        )

    resume = parse_resume_request(messages)
    requested_run_id = interrupt_state.get_run_id(request)
    run_id = interrupt_state.resolve_run_id(requested_run_id, resume)
    bind_log_context(operation_id=run_id)
    checkpoint_thread_id = interrupt_state.checkpoint_key(
        model,
        run_id,
        scope=interrupt_state.normalize_checkpoint_scope(checkpoint_scope),
    )
    runnable_config = build_runnable_config(
        graph_config.runtime_callbacks,
        configurable={"thread_id": checkpoint_thread_id},
        metadata=_runnable_metadata(request, run_id),
        extra_callbacks=[usage_callback],
    )
    if runnable_config is None:  # The configurable thread always creates one.
        msg = "Interrupt run has no runnable configuration."
        raise RuntimeError(msg)

    lease = await _acquire_lease(graph_config, checkpoint_thread_id)

    try:
        snapshot = await graph.aget_state(runnable_config, subgraphs=True)
        inputs, should_execute = await interrupt_state.prepare_interrupt_input(
            graph_config,
            graph,
            request,
            snapshot,
            resume,
        )
        context = (
            await graph_config.build_context(request, graph) if should_execute else None
        )
    except BaseException:
        error_info = sys.exc_info()
        with CancelScope(shield=True):
            try:
                await lease.__aexit__(*error_info)
            except Exception:
                logger.exception("graph_run.preparation_cleanup_failed")
        raise

    return GraphRun(
        config=graph_config,
        graph=graph,
        inputs=inputs,
        context=context,
        runnable_config=runnable_config,
        run_id=run_id,
        checkpoint_thread_id=checkpoint_thread_id,
        should_execute=should_execute,
        usage_callback=usage_callback,
        _lease=lease,
    )

integrations

Optional infrastructure integrations for LangGraph OpenAI Serve.

langfuse

Lazy construction for the optional Langfuse tracing integration.

get_langfuse_callback cached

get_langfuse_callback()

Return the process-wide Langfuse callback, constructing it lazily.

Source code in src/langgraph_openai_serve/integrations/langfuse.py
@cache
def get_langfuse_callback() -> BaseCallbackHandler:
    """Return the process-wide Langfuse callback, constructing it lazily."""
    from langfuse.langchain import CallbackHandler

    return CallbackHandler()

postgres

PostgreSQL coordination for interrupt-enabled graph runs.

PostgresRunCoordinator

PostgresRunCoordinator(pool, *, max_concurrent_leases)

Coordinate interrupt runs with PostgreSQL session advisory locks.

The pool must return mapping rows, as required by AsyncPostgresSaver when both components share one pool (for example, row_factory=dict_row). max_concurrent_leases limits how many pool connections coordination may hold at once. When the checkpointer shares this pool, reserve at least one connection for checkpoint I/O to avoid exhausting the pool with leases.

Source code in src/langgraph_openai_serve/integrations/postgres.py
def __init__(
    self,
    pool: _PostgresPool,
    *,
    max_concurrent_leases: int,
) -> None:
    if getattr(pool, "close_returns", False) is True:
        msg = "PostgresRunCoordinator requires a pool with close_returns=False."
        raise ValueError(msg)
    if (
        isinstance(max_concurrent_leases, bool)
        or not isinstance(max_concurrent_leases, int)
        or max_concurrent_leases < 1
    ):
        msg = "max_concurrent_leases must be a positive integer"
        raise ValueError(msg)
    self._pool = pool
    self._capacity = BoundedSemaphore(max_concurrent_leases)
__call__ async
__call__(key)

Acquire a PostgreSQL advisory lease for one interrupt run.

Source code in src/langgraph_openai_serve/integrations/postgres.py
@asynccontextmanager
async def __call__(self, key: str, /) -> AsyncIterator[None]:
    """Acquire a PostgreSQL advisory lease for one interrupt run."""
    if not self._capacity.acquire(blocking=False):
        raise RunBusyError(key)
    try:
        lock_key = _advisory_lock_key(key)
        async with self._pool.connection() as connection:
            if not await _try_acquire_advisory_lock(connection, lock_key):
                raise RunBusyError(key)

            body_error: BaseException | None = None
            try:
                yield
            except BaseException as exc:
                body_error = exc
                raise
            finally:
                try:
                    await _release_advisory_lock(connection, lock_key)
                except Exception:
                    if body_error is None:
                        raise
                    logger.exception("postgres.graph_run_lease_release_failed")
    finally:
        self._capacity.release()

openai_server

LangGraph OpenAI API Serve.

This module provides a server class that connects LangGraph instances to an OpenAI-compatible API. It allows users to register their LangGraph instances and expose them through a mounted FastAPI sub-application.

Examples:

>>> from langgraph_openai_serve import GraphConfig, GraphRegistry, LanggraphOpenaiServe
>>> from fastapi import FastAPI
>>> from your_graphs import simple_graph_1, simple_graph_2
>>>
>>> app = FastAPI(title="LangGraph OpenAI API")
>>> graphs = GraphRegistry(
...     registry={
...         "simple_graph_1": GraphConfig(
...             graph=simple_graph_1,
...             description="First simple graph.",
...         ),
...         "simple_graph_2": GraphConfig(
...             graph=simple_graph_2,
...             description="Second simple graph.",
...         ),
...     }
... )
>>> graph_serve = LanggraphOpenaiServe(
...     app=app,
...     graphs=graphs,
... )
>>> graph_serve.bind_openai_api()

LanggraphOpenaiServe

LanggraphOpenaiServe(graphs, app=None, checkpoint_scope=None)

Server class to connect LangGraph instances with an OpenAI-compatible API.

This class serves as a bridge between LangGraph instances and an OpenAI-compatible API. It allows users to register their LangGraph instances and expose them through an OpenAI-compatible sub-application mounted on a FastAPI host app.

Attributes:

Name Type Description
app FastAPI

The host FastAPI application to mount the OpenAI API on.

graph_registry

The populated GraphRegistry containing the graphs to serve.

openai_app FastAPI

The mounted OpenAI-compatible FastAPI application.

Initialize the server with a FastAPI app and a populated graph registry.

Parameters:

Name Type Description Default
app FastAPI | None

The host FastAPI application to mount the OpenAI API on. If None, a new FastAPI app will be created.

None
graphs GraphRegistry

A GraphRegistry instance containing the graphs to serve.

required
checkpoint_scope Callable[[Request], str | Awaitable[str]] | None

Optional server-trusted resolver used to isolate interrupt checkpoints by deployment or authenticated principal.

None

Raises:

Type Description
TypeError

If graphs is not a GraphRegistry instance.

Source code in src/langgraph_openai_serve/openai_server.py
def __init__(
    self,
    graphs: GraphRegistry,
    app: FastAPI | None = None,
    checkpoint_scope: Callable[[Request], str | Awaitable[str]] | None = None,
) -> None:
    """
    Initialize the server with a FastAPI app and a populated graph registry.

    Args:
        app: The host FastAPI application to mount the OpenAI API on. If None,
            a new FastAPI app will be created.
        graphs: A GraphRegistry instance containing the graphs to serve.
        checkpoint_scope: Optional server-trusted resolver used to isolate
            interrupt checkpoints by deployment or authenticated principal.

    Raises:
        TypeError: If graphs is not a GraphRegistry instance.

    """
    if not isinstance(graphs, GraphRegistry):
        msg = "Invalid type for graphs parameter. Expected GraphRegistry."
        raise TypeError(msg)

    if app is None:
        app = FastAPI(
            title="LangGraph OpenAI Compatible API",
            description="An OpenAI-compatible API for LangGraph",
            version=get_version(),
        )
    self.app: FastAPI = app
    self._openai_app: FastAPI | None = None
    self.checkpoint_scope = checkpoint_scope or (lambda _request: "default")

    self.graph_registry = graphs

    # Host integrations can inspect registered graphs without traversing the
    # mounted OpenAI sub-application.
    self.app.state.graph_registry = self.graph_registry
    self.app.state.checkpoint_scope = self.checkpoint_scope

    logger.info(
        "server.initialized",
        extra={"graph_count": len(self.graph_registry.registry)},
    )

openai_app property

openai_app

The mounted OpenAI-compatible FastAPI application.

bind_openai_api

bind_openai_api(prefix=None)

Mount OpenAI-compatible endpoints on the host FastAPI app.

Parameters:

Name Type Description Default
prefix str | None

Optional; The URL prefix for the OpenAI-compatible endpoints. Defaults to settings.OPENAI_API_PREFIX.

None
Source code in src/langgraph_openai_serve/openai_server.py
def bind_openai_api(self, prefix: str | None = None) -> "LanggraphOpenaiServe":
    """
    Mount OpenAI-compatible endpoints on the host FastAPI app.

    Args:
        prefix: Optional; The URL prefix for the OpenAI-compatible endpoints.
            Defaults to settings.OPENAI_API_PREFIX.

    """
    prefix = (
        normalize_openai_api_prefix(prefix)
        if prefix is not None
        else settings.OPENAI_API_PREFIX
    )

    openai_app = FastAPI(
        title="LangGraph OpenAI Compatible API",
        description="An OpenAI-compatible API for LangGraph",
        version=get_version(),
        **settings.fastapi_docs_kwargs,
    )
    # Dependencies in mounted routes resolve against the mounted app.
    openai_app.state.graph_registry = self.graph_registry
    openai_app.state.checkpoint_scope = self.checkpoint_scope
    configure_openai_error_handlers(openai_app)
    openai_app.include_router(chat_views.router)
    openai_app.include_router(health_views.router)
    openai_app.include_router(models_views.router)

    self.app.router.routes.append(
        Mount(
            prefix,
            app=openai_app,
            name="openai",
            middleware=[Middleware(RequestContextMiddleware)],
        )
    )
    self._openai_app = openai_app

    logger.info("server.api_bound", extra={"prefix": prefix})

    return self

schemas

Models package for the LangGraph OpenAI compatible API.

utils

Utility functions.

fake_llm

Shared fake streaming model helpers for demos and tests.

stream_fake_chat_response async

stream_fake_chat_response(response, prompt)

Stream a deterministic fake chat response and collect it for graph state.

Source code in src/langgraph_openai_serve/utils/fake_llm.py
async def stream_fake_chat_response(
    response: str,
    prompt: str,
) -> str:
    """Stream a deterministic fake chat response and collect it for graph state."""
    model = GenericFakeChatModel(messages=iter([response]))
    return "".join(
        [
            str(chunk.content)
            async for chunk in model.astream([HumanMessage(content=prompt)])
        ]
    )

message

InvalidChatMessageError

Bases: ValueError

Raised when a chat message is missing a role-specific required field.

convert_to_lc_messages

convert_to_lc_messages(messages)

Convert OpenAI messages to LangChain messages.

This function converts a list of OpenAI-compatible message objects to their LangChain equivalents for use with LangGraph.

Parameters:

Name Type Description Default
messages list[ChatCompletionRequestMessage]

A list of OpenAI chat completion request messages to convert.

required

Returns:

Type Description
list[BaseMessage]

A list of LangChain message objects.

Source code in src/langgraph_openai_serve/utils/message.py
def convert_to_lc_messages(
    messages: list[ChatCompletionRequestMessage],
) -> list[BaseMessage]:
    """
    Convert OpenAI messages to LangChain messages.

    This function converts a list of OpenAI-compatible message objects to their
    LangChain equivalents for use with LangGraph.

    Args:
        messages: A list of OpenAI chat completion request messages to convert.

    Returns:
        A list of LangChain message objects.

    """
    lc_messages: list[BaseMessage] = []
    for m in messages:
        match m.role:
            case Role.SYSTEM:
                lc_messages.append(SystemMessage(content=m.content or "", name=m.name))
            case Role.USER:
                lc_messages.append(HumanMessage(content=m.content or "", name=m.name))
            case Role.ASSISTANT:
                lc_messages.append(_assistant_message(m))
            case Role.TOOL:
                if m.tool_call_id is None:
                    msg = "Tool messages require the 'tool_call_id' field."
                    raise InvalidChatMessageError(msg)
                lc_messages.append(
                    ToolMessage(
                        content=m.content or "",
                        name=m.name,
                        tool_call_id=m.tool_call_id,
                    )
                )
    return lc_messages