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 and features.
GET /v1/models/{model} Retrieve one model with the required LGOS metadata extension.
POST /v1/responses Run a graph through the stateless OpenAI Responses subset.
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.

Responses Request

The route accepts string or ordered message input, instructions, plain input_text, input_file.file_id, string-valued metadata, user, flat function tools and choices, parallel_tool_calls, plain text output, and streaming. Replayed assistant output messages preserve phase; complete function_call items and matching string-valued function_call_output items support ordinary client-tool continuation. Interrupt continuation sends only matching function_call_output items with previous_response_id.

LGOS does not persist completed Responses for retrieve or deletion. Omitted store and store=false are accepted; store=true, conversation, and background mode are rejected. previous_response_id is supported for interruptible graphs to resume execution (and rejected for non-interruptible graphs); new instructions are rejected on those resumes. The route also rejects OpenAI-hosted tools, structured output, image/audio input, URL or inline file input, result-content lists, reasoning and generation controls, include, stream options, service tiers, reusable prompts, prompt-cache controls, and truncation. Unknown fields are not silently ignored. See the supported Responses subset for the complete behavior and continuation rules.

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 OpenAI metadata or the OpenAI 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.

Responses input_file.file_id content and native Chat file parts normalize to the same LangChain file block, so graphs receive native file_id values and decide whether to download, parse, or forward them. File upload and storage belong to an external OpenAI Files API, not the LGOS package. See Accept And Display Files.

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 or advertise a graph input capability.
  • hosted_tools: allowlisted lgos_... tool identifiers accepted by Responses; the graph owns their schemas and execution. See hosted tools.
  • 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 normalized request and LangChain messages to graph input.
  • context_factory(request, client_settings): compose the final typed LangGraph runtime context from normalized request values, 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].

Graph adapters receive an immutable, protocol-neutral GraphRequest from either API's decoder. It exposes only the shared model, metadata, user, normalized function tools, tool_choice, parallel_tool_calls, and hosted_tools values. hosted_tools is a tuple of selected LGOS identifiers, separate from function tools; Chat requests leave it empty. Raw OpenAI transport models are not part of the graph-adapter interface.

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.lgos_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 Tracing

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.graph_run for both endpoints and adds RunnableConfig.metadata fields for the request ID, registered graph model, (for interrupt runs) operation ID, and (when the request supplies metadata.conversation_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 lgos.features extension returned by model listing and retrieval. GraphFeature.CLIENT_EVENTS enables and advertises public status commentary in streaming Responses. Chat Completions ignores custom stream events and does not emit commentary. GraphFeature.FILE_INPUTS advertises that the graph resolves native file content parts. 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.lgos_settings string. Clients omit values equal to the advertised defaults. System instructions remain ordinary OpenAI messages and are independent of ClientSettings; native OpenAI 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 lgos.client_settings, with independent schema_version, json_schema, and defaults fields. All client settings use the fixed metadata.lgos_settings key. Clients use the descriptor's validated defaults object as the baseline; default keywords within the generated JSON Schema are annotations, not the runtime baseline. The schema's $schema keyword declares the JSON Schema 2020-12 dialect independently of the LGOS descriptor version.

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 embeds it in the paused Response ID. A caller that needs deterministic initial-request retries can instead supply a non-nil UUID in metadata.lgos_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".

Streaming Status

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 helper writes this versioned graph-to-LGOS envelope:

{
  "type": "lgos.client_event",
  "schema_version": 1,
  "event": {
    "type": "status",
    "namespace": ["media"],
    "data": {
      "description": "Generating audio",
      "done": false,
      "hidden": false
    }
  }
}

Status text is deliberately authored by the graph; LGOS does not infer it from internal node names or state. Responses exposes the description as commentary and suppresses hidden updates; the namespace, done, and hidden fields do not become nonstandard Response fields.

The event envelope has its own schema version, independent of model discovery and client settings. The v1 event vocabulary is status, progress, and artifact. client_event("status", data) remains the lower-level equivalent when an application already has validated status data; prefer status_event() for its typed fields. Event data must be JSON-safe, and every namespace segment must be a string. The namespace is a stable, author-defined path; LGOS does not expose LangGraph's dynamic execution namespace.

Status is streaming-only and always requires the graph feature. Responses needs no metadata opt-in and emits each visible update as a standard phase="commentary" message. The Chat Completions API is strictly for simple graphs and plain text streaming; it ignores custom stream events and does not emit commentary. Responses ignores progress and artifact. Use standard Responses function calls plus the Files API for portable durable rich output. Unknown custom events remain available only to direct runner consumers.

See Streaming status for the wire contract and Stream final text and commentary 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(start_index, end_index, text) to validate received indices and convert them to a Python slice. Responses maps citations to output_text.annotations and emits the typed annotation event while streaming. Chat maps them to completed message.annotations; its final streaming delta uses the compatibility extension.

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.

ClientFunctionTool dataclass

ClientFunctionTool(name, description, parameters, strict)

A client-supplied function available to the graph.

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: GraphRequest) -> Self:
    """Read and validate this model's values from an OpenAI request."""
    parameter = f"metadata.{SETTINGS_METADATA_KEY}"
    encoded = request.metadata.get(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: GraphRequest,
    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 normalized request.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def build_input(
    self,
    request: GraphRequest,
    messages: list[BaseMessage],
) -> Any:
    """Build the native graph input for a normalized 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}

GraphRequest dataclass

GraphRequest(
    model, metadata, user, tools, tool_choice, parallel_tool_calls, hosted_tools=()
)

Request data shared by protocol decoders and graph execution.

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)
    openai_app.include_router(responses_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

NamedFunctionToolChoice dataclass

NamedFunctionToolChoice(name)

Require one named client-supplied function.

citation_slice

citation_slice(start_index, end_index, content)

Convert an inclusive citation span to a validated Python slice.

Source code in src/langgraph_openai_serve/graph/citations.py
def citation_slice(start_index: int, end_index: int, content: str) -> slice:
    """Convert an inclusive citation span to a validated Python slice."""
    stop = end_index + 1
    if not 0 <= start_index < stop <= len(content):
        msg = "citation indices must refer to the final assistant text"
        raise ValueError(msg)
    return slice(start_index, 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_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

messages

Convert Chat Completions messages into LangChain messages.

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/api/chat/messages.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=_langchain_content(m.content), name=m.name)
                )
            case Role.USER:
                lc_messages.append(
                    HumanMessage(content=_langchain_content(m.content), 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=_langchain_content(m.content),
                        name=m.name,
                        tool_call_id=m.tool_call_id,
                    )
                )
    return lc_messages

request

Decode Chat Completions requests for protocol-neutral graph execution.

decode_chat_request
decode_chat_request(request)

Normalize one Chat Completions request for graph execution.

Source code in src/langgraph_openai_serve/api/chat/request.py
def decode_chat_request(
    request: ChatCompletionRequest,
) -> tuple[GraphRequest, list[BaseMessage]]:
    """Normalize one Chat Completions request for graph execution."""
    graph_request = GraphRequest(
        model=request.model,
        metadata=dict(request.metadata or {}),
        user=request.user,
        tools=tuple(
            ClientFunctionTool(
                name=tool.function.name,
                description=tool.function.description,
                parameters=(
                    dict(tool.function.parameters)
                    if tool.function.parameters is not None
                    else None
                ),
                strict=tool.function.strict,
            )
            for tool in request.tools or ()
        ),
        tool_choice=_decode_tool_choice(request.tool_choice),
        parallel_tool_calls=request.parallel_tool_calls,
    )
    return (
        graph_request,
        convert_to_lc_messages(request.messages),
    )

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/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
done staticmethod
done()

Stream done.

Source code in src/langgraph_openai_serve/api/chat/responses.py
@staticmethod
def done() -> str:
    """Stream done."""
    return "data: [DONE]\n\n"
error
error(message)

Stream error.

Source code in src/langgraph_openai_serve/api/chat/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/responses.py
def finish(
    self,
    finish_reason: Literal["stop", "tool_calls"],
    *,
    annotations: list[Annotation] | None = None,
) -> str:
    """Stream finish."""
    return self._chunk(
        ChoiceDelta(),
        finish_reason=finish_reason,
        annotations=annotations,
    )
role
role()

Stream role.

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

Stream text content.

Source code in src/langgraph_openai_serve/api/chat/responses.py
def text(self, content: str) -> str:
    """Stream text content."""
    return self._chunk(ChoiceDelta(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/responses.py
def tool_calls(self, message: AIMessage) -> str:
    """Stream complete final-message tool calls as one delta."""
    return self._chunk(
        ChoiceDelta(
            tool_calls=[
                ChoiceDeltaToolCall(
                    index=index,
                    id=tool_call.id,
                    type=tool_call.type,
                    function=ChoiceDeltaToolCallFunction(
                        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/responses.py
def usage(self, usage: UsageMetadata) -> str:
    """Stream the optional final usage-only chunk."""
    response = ChatCompletionChunk(
        id=self.response_id,
        object="chat.completion.chunk",
        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 validated LangChain citations to Chat URL annotations.

Source code in src/langgraph_openai_serve/api/chat/responses.py
def annotations_from_message(message: AIMessage) -> list[Annotation]:
    """Convert validated LangChain citations to Chat URL annotations."""
    return [
        Annotation.model_validate(
            {
                "type": "url_citation",
                "url_citation": {
                    key: citation[key]
                    for key in ("url", "title", "start_index", "end_index")
                },
            }
        )
        for citation in citations_from_message(message)
    ]
chat_completion_response
chat_completion_response(*, model, message)

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

Source code in src/langgraph_openai_serve/api/chat/responses.py
def chat_completion_response(
    *,
    model: str,
    message: AIMessage,
) -> ChatCompletion:
    """Build a non-streaming OpenAI-compatible chat completion response."""
    resp_message, finish_reason = response_message(message)
    return ChatCompletion(
        id=f"chatcmpl-{uuid.uuid4()}",
        object="chat.completion",
        created=int(time.time()),
        model=model,
        choices=[
            ChatChoice(
                index=0,
                message=resp_message,
                finish_reason=finish_reason,
            )
        ],
        usage=usage_info(message.usage_metadata),
    )
response_message
response_message(message)

Format response message.

Source code in src/langgraph_openai_serve/api/chat/responses.py
def response_message(
    message: AIMessage,
) -> tuple[ChatCompletionMessage, Literal["stop", "tool_calls"]]:
    """Format response message."""
    tool_calls = tool_calls_from_message(message)
    return (
        ChatCompletionMessage(
            role="assistant",
            content=message.text or None,
            annotations=annotations_from_message(message) 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/responses.py
def tool_calls_from_message(
    message: AIMessage,
) -> list[ChatCompletionMessageFunctionToolCall]:
    """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(
            ChatCompletionMessageFunctionToolCall(
                id=tool_call_id,
                type="function",
                function=Function(
                    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/responses.py
def usage_info(usage: UsageMetadata | None) -> CompletionUsage | None:
    """Map LangChain's provider-reported usage to Chat Completions usage."""
    if usage is None:
        return None
    return CompletionUsage(
        prompt_tokens=usage["input_tokens"],
        completion_tokens=usage["output_tokens"],
        total_tokens=usage["total_tokens"],
    )

schemas

Request models for the supported Chat Completions subset.

ChatCompletionRequest

Bases: BaseModel

Model for a chat completion request.

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.

ChatCompletionStreamOptions

Bases: BaseModel

Options that affect Chat Completions streaming.

FunctionDefinition

Bases: BaseModel

Model for a function definition.

NamedToolChoice

Bases: BaseModel

Named function tool choice accepted by Chat Completions.

NamedToolChoiceFunction

Bases: BaseModel

Function selected by a named Chat Completions tool choice.

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.

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
) -> ChatCompletion:
    """Generate a chat completion."""
    output = await invoke_run(run)
    if not isinstance(output, AIMessage):
        msg = "The graph returned an unsupported Chat Completions output."
        raise TypeError(msg)
    return chat_completion_response(
        model=chat_request.model,
        message=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,
    )
    chunks = _generate_stream_chunks(response_builder, run, include_usage=include_usage)
    try:
        async with aclosing(chunks):
            async for chunk in chunks:
                yield chunk
    except Exception:
        logger.exception("chat_completion.stream_failed")
        yield response_builder.error("Internal server error")
        yield response_builder.done()

views

OpenAI-compatible Chat Completions router.

create_chat_completion async
create_chat_completion(chat_request, graph_registry, 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
stream_owner Annotated[_StreamOwner, Depends(stream_owner_dependency, scope=request)]

The request-scoped streaming task owner.

required

Returns:

Type Description
StreamingResponse | ChatCompletion

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=ChatCompletion,
    response_model_exclude_none=True,
)
async def create_chat_completion(
    chat_request: ChatCompletionRequest,
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry_dependency)],
    stream_owner: Annotated[
        _StreamOwner,
        Depends(stream_owner_dependency, scope="request"),
    ],
) -> StreamingResponse | ChatCompletion:
    """
    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.
        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,
    )

    with graph_errors(input_param="messages"):
        try:
            graph_request, messages = decode_chat_request(chat_request)
        except InvalidChatMessageError as exc:
            raise OpenAIHTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                error=ErrorObject(
                    message=str(exc), type="invalid_request_error", param="messages"
                ),
            ) from exc

        graph_config = graph_registry.get_graph(chat_request.model)
        if graph_config.supports(GraphFeature.INTERRUPTS):
            raise OpenAIHTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                error=ErrorObject(
                    message=(
                        f"Model '{chat_request.model}' requires interrupts, which is only "
                        "supported via the Responses API (/v1/responses)."
                    ),
                    type="invalid_request_error",
                    param="model",
                ),
            )

        run = await prepare_run(
            graph_request,
            messages,
            graph_registry,
        )

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

        return await chat_service.generate_completion(chat_request, run)

deps

Dependencies shared by OpenAI-compatible API routes.

checkpoint_scope_dependency async

checkpoint_scope_dependency(request)

Resolve the server-trusted checkpoint scope for one request.

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

stream_owner_dependency async

stream_owner_dependency()

Manage the streaming producer owned by one request.

Yields:

Type Description
AsyncIterator[_StreamOwner]

The request-scoped stream owner.

Source code in src/langgraph_openai_serve/api/deps.py
async def stream_owner_dependency() -> AsyncIterator[_StreamOwner]:
    """
    Manage the streaming producer owned by one request.

    Yields:
        The request-scoped stream owner.

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

errors

Translate shared graph failures at either OpenAI inference boundary.

graph_errors

graph_errors(*, input_param)

Map graph errors to OpenAI errors using the endpoint's input field.

Yields:

Type Description
None

Control to request decoding, preparation, and non-streaming execution.

Source code in src/langgraph_openai_serve/api/errors.py
@contextmanager
def graph_errors(*, input_param: Literal["input", "messages"]) -> Iterator[None]:
    """
    Map graph errors to OpenAI errors using the endpoint's input field.

    Yields:
        Control to request decoding, preparation, and non-streaming execution.

    """
    try:
        yield
    except (RunBusyError, InterruptStateConflictError) as exc:
        busy = isinstance(exc, RunBusyError)
        raise OpenAIHTTPException(
            status_code=status.HTTP_409_CONFLICT,
            error=ErrorObject(
                message=str(exc),
                type="invalid_request_error",
                param=None if busy else input_param,
                code="run_busy" if busy else "interrupt_state_conflict",
            ),
        ) from exc
    except (
        InvalidRunIDError,
        InvalidResumeRequestError,
        GraphNotFoundError,
        ClientSettingsValidationError,
    ) as exc:
        match exc:
            case InvalidRunIDError():
                param = f"metadata.{RUN_METADATA_KEY}"
            case GraphNotFoundError():
                param = "model"
            case ClientSettingsValidationError():
                param = exc.param
            case InvalidResumeRequestError():
                param = exc.param or input_param
        raise OpenAIHTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            error=ErrorObject(
                message=str(exc), type="invalid_request_error", param=param
            ),
        ) from exc
    except (GraphConfigurationError, InvalidInterruptPayloadError) as exc:
        raise OpenAIHTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error=ErrorObject(message=str(exc), type="server_error"),
        ) from exc

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()}

metadata

Validation constraints shared by OpenAI request metadata fields.

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,
        lgos=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,
            lgos=LangGraphModelSummaryExtension(
                description=graph_config.description,
                features=sorted(
                    graph_config.features,
                    key=lambda feature: feature.value,
                ),
            ),
        )
        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

responses

OpenAI-compatible Responses API.

interrupts

OpenAI Responses encoding for LangGraph interrupt continuations.

interrupt_response_id
interrupt_response_id(run_id)

Create a unique Response ID that carries its interrupt run identity.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def interrupt_response_id(run_id: str) -> str:
    """Create a unique Response ID that carries its interrupt run identity."""
    return f"{_INTERRUPT_RESPONSE_PREFIX}{uuid.UUID(run_id).hex}_{uuid.uuid4().hex}"
interrupt_tool_call_id
interrupt_tool_call_id(interrupt_id, state_token, *, response_id)

Bind one interrupt to its Response and durable checkpoint generation.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def interrupt_tool_call_id(
    interrupt_id: str, state_token: str, *, response_id: str
) -> str:
    """Bind one interrupt to its Response and durable checkpoint generation."""
    if not interrupt_id:
        msg = "LangGraph interrupt IDs must be non-empty strings."
        raise ValueError(msg)
    if _STATE_TOKEN_PATTERN.fullmatch(state_token) is None:
        msg = "LangGraph interrupt state tokens must be SHA-256 hex digests."
        raise ValueError(msg)
    response_nonce = response_id.rsplit("_", 1)[-1]
    return f"{_INTERRUPT_CALL_PREFIX}{state_token}_{response_nonce}_{interrupt_id}"
parse_responses_resume
parse_responses_resume(input_value, *, previous_response_id=None)

Parse the sole supported interrupt continuation form.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def parse_responses_resume(
    input_value: str | list[ResponseInputItem],
    *,
    previous_response_id: str | None = None,
) -> InterruptResume | None:
    """Parse the sole supported interrupt continuation form."""
    if previous_response_id is None:
        _reject_interrupt_items_without_response_id(input_value)
        return None
    if isinstance(input_value, str):
        msg = (
            "Interrupt resumes require only function_call_output input items for "
            "the previous Response."
        )
        raise InvalidResumeRequestError(msg)

    run_id = _parse_interrupt_response_id(previous_response_id)
    state_token: str | None = None
    values: dict[str, str] = {}
    for item in input_value:
        if not isinstance(item, ResponseFunctionCallOutputInput):
            msg = (
                "Interrupt resumes require only function_call_output input items for "
                "the previous Response."
            )
            raise InvalidResumeRequestError(msg)
        output_token, interrupt_id = _parse_interrupt_tool_call_id(
            item.call_id, previous_response_id
        )
        if state_token is None:
            state_token = output_token
        elif output_token != state_token:
            msg = "Interrupt outputs must belong to one checkpoint generation."
            raise InvalidResumeRequestError(msg)
        if interrupt_id in values:
            msg = "Interrupt function_call_output call_id values must be unique."
            raise InvalidResumeRequestError(msg)
        values[interrupt_id] = item.output

    if state_token is None:  # Response input lists are non-empty by schema.
        msg = "Interrupt resumes require at least one function_call_output item."
        raise InvalidResumeRequestError(msg)
    return InterruptResume(
        run_id=run_id,
        state_token=state_token,
        values=values,
    )

messages

Convert Responses message input into LangChain messages.

InvalidResponsesInputError

Bases: ValueError

Raised when Responses input items cannot be replayed unambiguously.

convert_responses_input
convert_responses_input(input_value, *, instructions)

Normalize supported Responses text and message input.

Source code in src/langgraph_openai_serve/api/responses/messages.py
def convert_responses_input(
    input_value: str | list[ResponseInputItem],
    *,
    instructions: str | None,
) -> list[BaseMessage]:
    """Normalize supported Responses text and message input."""
    messages: list[BaseMessage] = []
    if instructions is not None:
        messages.append(SystemMessage(content=instructions))

    if isinstance(input_value, str):
        messages.append(HumanMessage(content=input_value))
        return messages

    _validate_replay_ids(input_value)
    index = 0
    while index < len(input_value):
        item = input_value[index]
        if isinstance(item, ResponseFunctionCallInput):
            calls: list[ResponseFunctionCallInput] = []
            while index < len(input_value) and isinstance(
                input_value[index],
                ResponseFunctionCallInput,
            ):
                calls.append(cast("ResponseFunctionCallInput", input_value[index]))
                index += 1
            messages.append(_function_call_message(calls))
            continue
        messages.append(_message_from_item(item))
        index += 1
    return messages

request

Decode Responses requests for protocol-neutral graph execution.

UnsupportedResponsesRequestError
UnsupportedResponsesRequestError(message, *, param)

Bases: ValueError

Raised when a valid OpenAI field has unsupported LGOS semantics.

Source code in src/langgraph_openai_serve/api/responses/request.py
def __init__(self, message: str, *, param: str) -> None:
    super().__init__(message)
    self.param = param
decode_responses_request
decode_responses_request(request)

Normalize one supported, stateless Responses request.

Source code in src/langgraph_openai_serve/api/responses/request.py
def decode_responses_request(
    request: ResponseCreateRequest,
) -> tuple[GraphRequest, list[BaseMessage], InterruptResume | None]:
    """Normalize one supported, stateless Responses request."""
    _validate_supported_semantics(request)
    resume = parse_responses_resume(
        request.input,
        previous_response_id=request.previous_response_id,
    )
    return (
        GraphRequest(
            model=request.model,
            metadata=dict(request.metadata or {}),
            user=request.user,
            tools=tuple(
                ClientFunctionTool(
                    name=tool.name,
                    description=tool.description,
                    parameters=(
                        dict(tool.parameters) if tool.parameters is not None else None
                    ),
                    strict=tool.strict,
                )
                for tool in request.tools or ()
                if isinstance(tool, ResponseFunctionTool)
            ),
            hosted_tools=tuple(
                tool.name
                for tool in request.tools or ()
                if isinstance(tool, ResponseHostedTool)
            ),
            tool_choice=_decode_tool_choice(request.tool_choice),
            parallel_tool_calls=request.parallel_tool_calls,
        ),
        (
            []
            if resume is not None
            else convert_responses_input(
                request.input,
                instructions=request.instructions,
            )
        ),
        resume,
    )
validate_hosted_tools
validate_hosted_tools(request, supported)

Reject unavailable hosted tools before graph execution or SSE starts.

Source code in src/langgraph_openai_serve/api/responses/request.py
def validate_hosted_tools(request: ResponseCreateRequest, supported: set[str]) -> None:
    """Reject unavailable hosted tools before graph execution or SSE starts."""
    for index, tool in enumerate(request.tools or ()):
        if isinstance(tool, ResponseHostedTool) and tool.name not in supported:
            message = f"Hosted tool '{tool.name}' is not supported by model '{request.model}'."
            raise UnsupportedResponsesRequestError(message, param=f"tools.{index}.name")

schemas

Validated request models for the supported Responses API subset.

ResponseCreateRequest

Bases: _ResponsesRequestModel

The stateless Responses request accepted by LGOS.

ResponseFunctionCallInput

Bases: _ResponsesRequestModel

A function call replayed from a previous Response.

ResponseFunctionCallOutputInput

Bases: _ResponsesRequestModel

Client output for a preceding function call.

ResponseFunctionTool

Bases: _ResponsesRequestModel

A client-supplied function available to the graph.

ResponseHostedTool

Bases: _ResponsesRequestModel

Select a graph-owned LGOS tool without supplying its function schema.

ResponseInputFile

Bases: _ResponsesRequestModel

One file stored in the configured OpenAI Files service.

ResponseInputMessage

Bases: _ResponsesRequestModel

A standard OpenAI role message provided as input.

phase is accepted for every role and used only for assistant messages. See https://developers.openai.com/api/reference/resources/responses.

ResponseInputText

Bases: _ResponsesRequestModel

One plain-text input content part.

ResponseNamedToolChoice

Bases: _ResponsesRequestModel

Require one named function tool.

ResponseOutputMessageInput

Bases: _ResponsesRequestModel

A completed assistant output message replayed as input.

ResponseOutputTextInput

Bases: _ResponsesRequestModel

Plain output text replayed from a previous assistant message.

ResponseTextConfig

Bases: _ResponsesRequestModel

Plain-text response configuration.

ResponseTextFormat

Bases: _ResponsesRequestModel

The supported plain-text output format.

service

Execute and assemble OpenAI Response objects.

ResponseContext dataclass
ResponseContext(
    request, id=(lambda: f"resp_{uuid.uuid4().hex}")(), created_at=time.time()
)

Stable identity and request fields shared by one response lifecycle.

for_run classmethod
for_run(request, *, run_id=None)

Build context, binding an interrupt response ID when run_id is present.

Source code in src/langgraph_openai_serve/api/responses/service.py
@classmethod
def for_run(
    cls,
    request: ResponseCreateRequest,
    *,
    run_id: str | None = None,
) -> "ResponseContext":
    """Build context, binding an interrupt response ID when run_id is present."""
    if run_id is None:
        return cls(request=request)
    return cls(request=request, id=interrupt_response_id(run_id))
UnsupportedResponsesOutputError

Bases: RuntimeError

Raised when graph output cannot be serialized as supported Responses items.

generate_response async
generate_response(request, run)

Invoke a graph and serialize its durable Responses output.

Source code in src/langgraph_openai_serve/api/responses/service.py
async def generate_response(
    request: ResponseCreateRequest,
    run: GraphRun,
) -> Response:
    """Invoke a graph and serialize its durable Responses output."""
    context = ResponseContext.for_run(request, run_id=run.run_id)
    output = await invoke_run(run)
    if isinstance(output, AIMessage):
        items = response_output_items(output)
        usage = output.usage_metadata
    else:
        items = interrupt_output_items(output, response_id=context.id)
        usage = run.usage_metadata()
    return response_object(
        context,
        status="completed",
        output=items,
        usage=response_usage(usage),
    )
interrupt_output_items
interrupt_output_items(batch, *, response_id)

Serialize one durable interrupt batch as function-call items.

Source code in src/langgraph_openai_serve/api/responses/service.py
def interrupt_output_items(
    batch: LangGraphInterruptBatch,
    *,
    response_id: str,
) -> list[ResponseFunctionToolCall]:
    """Serialize one durable interrupt batch as function-call items."""
    return [
        _function_call_item(
            call_id=interrupt_tool_call_id(
                interrupt.id,
                state_token=batch.state_token,
                response_id=response_id,
            ),
            name=INTERRUPT_TOOL_NAME,
            arguments=_dump_arguments(interrupt.value),
        )
        for interrupt in batch.interrupts
    ]
response_function_call
response_function_call(call)

Serialize one LangChain client tool call.

Source code in src/langgraph_openai_serve/api/responses/service.py
def response_function_call(call: ToolCall) -> ResponseFunctionToolCall:
    """Serialize one LangChain client tool call."""
    call_id = call.get("id")
    name = call.get("name")
    arguments = call.get("args")
    if not isinstance(call_id, str) or not call_id:
        msg = "The final assistant tool call must include a non-empty id."
        raise UnsupportedResponsesOutputError(msg)
    if not isinstance(name, str) or not name:
        msg = "The final assistant tool call must include a non-empty name."
        raise UnsupportedResponsesOutputError(msg)
    if not isinstance(arguments, dict):
        msg = "The final assistant tool call arguments must be a JSON object."
        raise UnsupportedResponsesOutputError(msg)
    return _function_call_item(
        call_id=call_id,
        name=name,
        arguments=_dump_arguments(arguments),
    )
response_function_calls
response_function_calls(message)

Serialize and validate all client tool calls from an assistant message.

Source code in src/langgraph_openai_serve/api/responses/service.py
def response_function_calls(message: AIMessage) -> list[ResponseFunctionToolCall]:
    """Serialize and validate all client tool calls from an assistant message."""
    if message.invalid_tool_calls:
        msg = "The final assistant message contains invalid tool calls."
        raise UnsupportedResponsesOutputError(msg)

    calls: list[ResponseFunctionToolCall] = []
    seen_call_ids: set[str] = set()
    for call in message.tool_calls:
        output = response_function_call(call)
        if output.call_id in seen_call_ids:
            msg = f"The final assistant message repeats call id '{output.call_id}'."
            raise UnsupportedResponsesOutputError(msg)
        seen_call_ids.add(output.call_id)
        calls.append(output)
    return calls
response_object
response_object(context, *, status, output, error=None, usage=None)

Build one SDK-typed Response with the route's stable defaults.

Source code in src/langgraph_openai_serve/api/responses/service.py
def response_object(
    context: ResponseContext,
    *,
    status: Literal["in_progress", "completed", "failed"],
    output: Sequence[ResponseOutputItem],
    error: ResponseError | None = None,
    usage: ResponseUsage | None = None,
) -> Response:
    """Build one SDK-typed Response with the route's stable defaults."""
    request = context.request
    return Response.model_validate(
        {
            "id": context.id,
            "object": "response",
            "created_at": context.created_at,
            "status": status,
            "background": False,
            "completed_at": time.time() if status == "completed" else None,
            "error": error,
            "incomplete_details": None,
            "instructions": request.instructions,
            "max_output_tokens": None,
            "max_tool_calls": None,
            "metadata": dict(request.metadata or {}),
            "model": request.model,
            "output": list(output),
            "parallel_tool_calls": (
                request.parallel_tool_calls
                if request.parallel_tool_calls is not None
                else True
            ),
            "previous_response_id": request.previous_response_id,
            "prompt_cache_key": None,
            "reasoning": None,
            "safety_identifier": None,
            "service_tier": "default",
            "store": False,
            "temperature": None,
            "text": {"format": {"type": "text"}},
            "tool_choice": (
                request.tool_choice.model_dump(mode="json")
                if request.tool_choice is not None
                and not isinstance(request.tool_choice, str)
                else request.tool_choice or "auto"
            ),
            "tools": [tool.model_dump(mode="json") for tool in request.tools or ()],
            "top_logprobs": 0,
            "top_p": None,
            "truncation": "disabled",
            "usage": usage,
            "user": request.user,
        }
    )
response_output_items
response_output_items(message)

Serialize one assistant message into ordered Responses output items.

Source code in src/langgraph_openai_serve/api/responses/service.py
def response_output_items(message: AIMessage) -> list[ResponseOutputItem]:
    """Serialize one assistant message into ordered Responses output items."""
    calls = response_function_calls(message)
    output: list[ResponseOutputItem] = []
    if message.text or not message.tool_calls:
        output.append(
            ResponseOutputMessage(
                id=f"msg_{uuid.uuid4().hex}",
                content=[response_output_text(message)],
                role="assistant",
                status="completed",
                type="message",
                phase="final_answer",
            )
        )
    output.extend(calls)
    return output
response_output_text
response_output_text(message)

Build final Responses text and validated native URL annotations.

Source code in src/langgraph_openai_serve/api/responses/service.py
def response_output_text(message: AIMessage) -> ResponseOutputText:
    """Build final Responses text and validated native URL annotations."""
    return ResponseOutputText(
        annotations=[
            AnnotationURLCitation(
                type="url_citation",
                url=citation["url"],
                title=citation["title"],
                start_index=citation["start_index"],
                end_index=citation["end_index"],
            )
            for citation in citations_from_message(message)
        ],
        logprobs=[],
        text=str(message.text),
        type="output_text",
    )
response_usage
response_usage(usage)

Map provider-reported LangChain usage to Responses token details.

Source code in src/langgraph_openai_serve/api/responses/service.py
def response_usage(usage: UsageMetadata | None) -> ResponseUsage | None:
    """Map provider-reported LangChain usage to Responses token details."""
    if usage is None:
        return None
    input_details = usage.get("input_token_details", {})
    output_details = usage.get("output_token_details", {})
    return ResponseUsage(
        input_tokens=usage["input_tokens"],
        input_tokens_details=InputTokensDetails(
            cached_tokens=input_details.get("cache_read", 0),
            cache_write_tokens=input_details.get("cache_creation", 0),
        ),
        output_tokens=usage["output_tokens"],
        output_tokens_details=OutputTokensDetails(
            reasoning_tokens=output_details.get("reasoning", 0),
        ),
        total_tokens=usage["total_tokens"],
    )

streaming

Assemble SDK-typed OpenAI Responses streaming events.

ResponsesStreamBuilder
ResponsesStreamBuilder(request, *, run_id=None)

Own stable state for one Responses SSE lifecycle.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def __init__(
    self,
    request: ResponseCreateRequest,
    *,
    run_id: str | None = None,
) -> None:
    self._context = ResponseContext.for_run(request, run_id=run_id)
    self._sequence_number = 0
    self._output: list[ResponseOutputMessage | ResponseFunctionToolCall] = []
    self._final_item: _TextItem | None = None
commentary
commentary(text)

Emit one complete commentary message lifecycle.

Yields:

Type Description
ResponseStreamEvent

Typed events for the message lifecycle.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def commentary(self, text: str) -> Iterator[ResponseStreamEvent]:
    """
    Emit one complete commentary message lifecycle.

    Yields:
        Typed events for the message lifecycle.

    """
    item = self._new_text_item("commentary")
    yield from self._start_text_item(item)
    item.text_parts.append(text)
    yield self._text_delta(item, text)
    part = ResponseOutputText(
        annotations=[],
        logprobs=[],
        text=item.text,
        type="output_text",
    )
    yield from self._finish_text_item(item, part)
created
created()

Create the initial response event.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def created(self) -> ResponseCreatedEvent:
    """Create the initial response event."""
    return ResponseCreatedEvent(
        type="response.created",
        sequence_number=self._sequence(),
        response=self._response(status="in_progress"),
    )
failure
failure(message)

Emit the normative terminal failure sequence.

Yields:

Type Description
ResponseStreamEvent

The error and failed Response events.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def failure(self, message: str) -> Iterator[ResponseStreamEvent]:
    """
    Emit the normative terminal failure sequence.

    Yields:
        The error and failed Response events.

    """
    # Keep the items already exposed to the client in the terminal snapshot.
    # The SDK replaces its accumulated Response with response.failed.
    item = self._final_item
    if item is not None and self._output[item.output_index].status == "in_progress":
        self._output[item.output_index] = self._output[
            item.output_index
        ].model_copy(
            update={"content": [response_output_text(AIMessage(content=item.text))]}
        )
    self._output = [
        item.model_copy(update={"status": "incomplete"})
        if item.status == "in_progress"
        else item
        for item in self._output
    ]
    yield ResponseErrorEvent(
        type="error",
        sequence_number=self._sequence(),
        code="server_error",
        message=message,
        param=None,
    )
    yield ResponseFailedEvent(
        type="response.failed",
        sequence_number=self._sequence(),
        response=self._response(
            status="failed",
            error=ResponseError(code="server_error", message=message),
        ),
    )
final_delta
final_delta(delta)

Emit one final-answer delta, opening its item if needed.

Yields:

Type Description
ResponseStreamEvent

Typed events that open or update the final message.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def final_delta(self, delta: str) -> Iterator[ResponseStreamEvent]:
    """
    Emit one final-answer delta, opening its item if needed.

    Yields:
        Typed events that open or update the final message.

    """
    item = self._final_item
    if item is None:
        item = self._new_text_item("final_answer")
        self._final_item = item
        yield from self._start_text_item(item)
    item.text_parts.append(delta)
    yield self._text_delta(item, delta)
finish
finish(message)

Reconcile final text, finish its item, and complete the Response.

Yields:

Type Description
ResponseStreamEvent

Typed terminal events for a successful Response.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def finish(self, message: AIMessage) -> Iterator[ResponseStreamEvent]:
    """
    Reconcile final text, finish its item, and complete the Response.

    Yields:
        Typed terminal events for a successful Response.

    """
    calls = response_function_calls(message)
    item = self._final_item
    if item is None and (message.text or not message.tool_calls):
        item = self._new_text_item("final_answer")
        self._final_item = item
        yield from self._start_text_item(item)
        if message.text:
            text = str(message.text)
            item.text_parts.append(text)
            yield self._text_delta(item, text)
    elif item is not None and item.text != str(message.text):
        msg = "Streamed assistant text did not match the final assistant message."
        raise RuntimeError(msg)

    if item is not None:
        part = response_output_text(message)
        yield from self._finish_text_item(item, part)
    for call in calls:
        yield from self._function_call(call)
    yield ResponseCompletedEvent(
        type="response.completed",
        sequence_number=self._sequence(),
        response=self._response(
            status="completed",
            usage=response_usage(message.usage_metadata),
        ),
    )
finish_interrupt
finish_interrupt(batch, *, usage)

Emit a durable interrupt batch and complete the Response.

Yields:

Type Description
ResponseStreamEvent

Typed function-call and terminal events.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def finish_interrupt(
    self,
    batch: LangGraphInterruptBatch,
    *,
    usage: ResponseUsage | None,
) -> Iterator[ResponseStreamEvent]:
    """
    Emit a durable interrupt batch and complete the Response.

    Yields:
        Typed function-call and terminal events.

    """
    if self._final_item is not None:
        yield from self._finish_text_item(
            self._final_item,
            response_output_text(AIMessage(content=self._final_item.text)),
        )
    for call in interrupt_output_items(batch, response_id=self._context.id):
        yield from self._function_call(call)
    yield ResponseCompletedEvent(
        type="response.completed",
        sequence_number=self._sequence(),
        response=self._response(status="completed", usage=usage),
    )
in_progress
in_progress()

Create the response in-progress event.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def in_progress(self) -> ResponseInProgressEvent:
    """Create the response in-progress event."""
    return ResponseInProgressEvent(
        type="response.in_progress",
        sequence_number=self._sequence(),
        response=self._response(status="in_progress"),
    )
encode_event
encode_event(event)

Encode one Responses event using the official named SSE framing.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
def encode_event(event: ResponseStreamEvent) -> str:
    """Encode one Responses event using the official named SSE framing."""
    return f"event: {event.type}\ndata: {event.model_dump_json()}\n\n"
stream_response async
stream_response(request, run)

Stream one prepared graph run as a typed Responses lifecycle.

Yields:

Type Description
AsyncGenerator[str, None]

Named, compact Responses SSE frames.

Source code in src/langgraph_openai_serve/api/responses/streaming.py
async def stream_response(
    request: ResponseCreateRequest,
    run: GraphRun,
) -> AsyncGenerator[str, None]:
    """
    Stream one prepared graph run as a typed Responses lifecycle.

    Yields:
        Named, compact Responses SSE frames.

    """
    builder = ResponsesStreamBuilder(request, run_id=run.run_id)
    events = _successful_events(builder, run)
    try:
        async with aclosing(events):
            async for event in events:
                yield encode_event(event)
    except Exception:
        logger.exception("responses.stream_failed")
        for response_event in builder.failure("Internal server error"):
            yield encode_event(response_event)

views

OpenAI-compatible Responses router.

create_response async
create_response(response_request, graph_registry, checkpoint_scope, stream_owner)

Create one stateless OpenAI Response, optionally as an SSE stream.

Source code in src/langgraph_openai_serve/api/responses/views.py
@router.post("/responses", response_model=Response)
async def create_response(
    response_request: ResponseCreateRequest,
    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 | Response:
    """Create one stateless OpenAI Response, optionally as an SSE stream."""
    bind_log_context(model=response_request.model, stream=response_request.stream)

    with graph_errors(input_param="input"):
        try:
            graph_request, messages, resume = _validate_responses_request(
                response_request,
                graph_registry,
            )
        except (UnsupportedResponsesRequestError, InvalidResponsesInputError) as exc:
            raise OpenAIHTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                error=ErrorObject(
                    message=str(exc),
                    type="invalid_request_error",
                    param=(
                        exc.param
                        if isinstance(exc, UnsupportedResponsesRequestError)
                        else "input"
                    ),
                ),
            ) from exc
        run = await prepare_run(
            graph_request,
            messages,
            graph_registry,
            resume=resume,
            checkpoint_scope=checkpoint_scope,
        )
        if response_request.stream:
            body = stream_owner.start(
                stream_response(response_request, run),
                run,
            )
            return StreamingResponse(body, media_type="text/event-stream")
        try:
            return await generate_response(response_request, run)
        except UnsupportedResponsesOutputError as exc:
            raise OpenAIHTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                error=ErrorObject(message=str(exc), type="server_error"),
            ) from exc

streaming

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

Starlette owns response consumption, not the nested graph producer, so a client disconnect may otherwise leave graph and provider work running. Chat and Responses use this shared request owner with separate protocol generators.

AnyIO provides the backpressured channel and cleanup shield. The producer stays an asyncio.Task so cancellation reaches LangGraph's asyncio-native teardown once at the stream boundary.

tools

Shared function-call decoding for the OpenAI protocol adapters.

decode_function_call

decode_function_call(*, name, arguments, call_id)

Parse once, preserving malformed arguments for the graph to handle.

Source code in src/langgraph_openai_serve/api/tools.py
def decode_function_call(
    *, name: str, arguments: str, call_id: str
) -> ToolCall | InvalidToolCall:
    """Parse once, preserving malformed arguments for the graph to handle."""
    error: str | None = None
    try:
        parsed = json.loads(arguments)
        # json.loads accepts NaN, Infinity, and numeric overflow such as 1e999.
        # None of those may enter an otherwise valid JSON argument object.
        json.dumps(parsed, allow_nan=False)
    except ValueError as exc:
        error = f"Function arguments are not valid JSON: {exc}"
    else:
        if isinstance(parsed, dict):
            return tool_call(name=name, args=parsed, id=call_id)
        error = "Function arguments must decode to a JSON object."
    return invalid_tool_call(name=name, args=arguments, id=call_id, error=error)

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.

citations

Validate native LangChain citations independently of the HTTP protocol.

citation_slice

citation_slice(start_index, end_index, content)

Convert an inclusive citation span to a validated Python slice.

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

citations_from_message

citations_from_message(message)

Extract URL citations with validated offsets into the complete text.

Source code in src/langgraph_openai_serve/graph/citations.py
def citations_from_message(message: AIMessage) -> list[Citation]:
    """Extract URL citations with validated offsets into the complete text."""
    text = str(message.text)
    citations: list[Citation] = []
    text_offset = 0
    for block in message.content_blocks:
        if block["type"] != "text":
            continue
        for raw_citation in block.get("annotations", []):
            if raw_citation.get("type") != "citation":
                continue
            required = {"url", "title", "start_index", "end_index"}
            if not required.issubset(raw_citation):
                continue
            citation = cast("Citation", raw_citation).copy()
            citation["start_index"] += text_offset
            citation["end_index"] += text_offset
            span = citation_slice(citation["start_index"], citation["end_index"], text)
            cited_text = citation.get("cited_text")
            if cited_text is not None and text[span] != cited_text:
                msg = "citation indices must match cited_text"
                raise ValueError(msg)
            citations.append(citation)
        text_offset += len(block["text"])
    return citations

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: GraphRequest) -> Self:
    """Read and validate this model's values from an OpenAI request."""
    parameter = f"metadata.{SETTINGS_METADATA_KEY}"
    encoded = request.metadata.get(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.

StatusEventData

Bases: BaseModel

Validated graph status used to render Responses commentary.

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_TYPE,
        schema_version=CLIENT_EVENT_SCHEMA_VERSION,
        event=_ClientEventData(
            type=event_type,
            namespace=namespace,
            data=data,
        ),
    )
    return envelope.model_dump(mode="json")

parse_status_event

parse_status_event(value)

Read a public graph status, ignoring private or diagnostic custom data.

Source code in src/langgraph_openai_serve/graph/events.py
def parse_status_event(value: object) -> StatusEventData | None:
    """Read a public graph status, ignoring private or diagnostic custom data."""
    if not isinstance(value, dict) or value.get("type") != CLIENT_EVENT_TYPE:
        return None

    try:
        envelope = _ClientEventEnvelope.model_validate(value)
        if envelope.event.type != "status":
            return None
        return StatusEventData.model_validate(envelope.event.data)
    except ValidationError:
        return None

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: GraphRequest,
    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 normalized request.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def build_input(
    self,
    request: GraphRequest,
    messages: list[BaseMessage],
) -> Any:
    """Build the native graph input for a normalized 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)

InterruptResume dataclass

InterruptResume(run_id, state_token, values)

A complete, causally bound set of interrupt answers.

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."""
    ...

errors

Errors shared by Responses adaptation and interrupt execution.

InvalidInterruptPayloadError

Bases: ValueError

Raised when graph-authored interrupt data is not JSON-compatible.

InvalidResumeRequestError
InvalidResumeRequestError(message, *, param=None)

Bases: ValueError

Raised when a protocol request is not a valid interrupt resume.

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

models

Protocol-neutral models for interrupt-enabled graph runs.

InterruptResume dataclass
InterruptResume(run_id, state_token, values)

A complete, causally bound set of interrupt answers.

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 normalized request metadata.

Source code in src/langgraph_openai_serve/graph/interrupt/state.py
def get_run_id(request: GraphRequest) -> str | None:
    """Read the optional interrupt run id from normalized request metadata."""
    return request.metadata.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, *, messages)

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(  # ruff: ignore[too-many-arguments]
    graph_config: GraphConfig,
    graph: CompiledStateGraph,
    request: GraphRequest,
    snapshot: StateSnapshot,
    resume: InterruptResume | None,
    *,
    messages: list[BaseMessage],
) -> 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:
            return await graph_config.build_input(request, 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 "
                    "Response."
                )
                raise InvalidResumeRequestError(
                    msg,
                    param=f"metadata.{RUN_METADATA_KEY}",
                )
        return resume_run_id

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

validation

Validate graph-authored LangGraph interrupt payloads.

validate_interrupt_payload
validate_interrupt_payload(payload)

Require function-call arguments containing valid JSON object values.

Source code in src/langgraph_openai_serve/graph/interrupt/validation.py
def validate_interrupt_payload(payload: Any) -> None:
    """Require function-call arguments containing valid JSON object values."""
    if not isinstance(payload, dict):
        msg = "LangGraph interrupt payloads must be JSON objects."
        raise InvalidInterruptPayloadError(msg)
    try:
        json.dumps(payload, allow_nan=False)
    except (TypeError, ValueError) as exc:
        msg = "LangGraph interrupt payloads must be valid JSON values."
        raise InvalidInterruptPayloadError(msg) from exc

request

Protocol-neutral inputs exposed to graph adapters.

ClientFunctionTool dataclass

ClientFunctionTool(name, description, parameters, strict)

A client-supplied function available to the graph.

GraphRequest dataclass

GraphRequest(
    model, metadata, user, tools, tool_choice, parallel_tool_calls, hosted_tools=()
)

Request data shared by protocol decoders and graph execution.

NamedFunctionToolChoice dataclass

NamedFunctionToolChoice(name)

Require one named client-supplied function.

runner

Run LangGraph workflows from protocol-neutral requests and messages.

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) -> LangGraphOutput:
    """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 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 interrupt_batch

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

        return rendered_output
    finally:
        await finalize_run(run, checkpoint_disposition)

run_langgraph async

run_langgraph(
    request, messages, graph_registry, *, resume=None, checkpoint_scope="default"
)

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:

>>> output = await run_langgraph(request, messages, registry)
>>> print(output)

Parameters:

Name Type Description Default
request GraphRequest

Normalized graph selection, metadata, user, and client tools.

required
messages list[BaseMessage]

Decoded LangChain messages to process through the graph.

required
graph_registry GraphRegistry

The GraphRegistry instance containing registered graphs.

required
resume InterruptResume | None

A decoded, complete interrupt answer batch, when resuming.

None
checkpoint_scope str

Server-trusted scope used to isolate checkpoint state.

'default'

Returns:

Type Description
LangGraphOutput

The durable graph output.

Source code in src/langgraph_openai_serve/graph/runner.py
async def run_langgraph(
    request: GraphRequest,
    messages: list[BaseMessage],
    graph_registry: GraphRegistry,
    *,
    resume: interrupt_models.InterruptResume | None = None,
    checkpoint_scope: str = "default",
) -> LangGraphOutput:
    """
    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:
        >>> output = await run_langgraph(request, messages, registry)
        >>> print(output)

    Args:
        request: Normalized graph selection, metadata, user, and client tools.
        messages: Decoded LangChain messages to process through the graph.
        graph_registry: The GraphRegistry instance containing registered graphs.
        resume: A decoded, complete interrupt answer batch, when resuming.
        checkpoint_scope: Server-trusted scope used to isolate checkpoint state.

    Returns:
        The durable graph output.

    """
    run = await prepare_run(
        request,
        messages,
        graph_registry,
        resume=resume,
        checkpoint_scope=checkpoint_scope,
    )

    return await invoke_run(run)

run_langgraph_stream async

run_langgraph_stream(
    request, messages, graph_registry, *, resume=None, checkpoint_scope="default"
)

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
request GraphRequest

Normalized graph selection, metadata, user, and client tools.

required
messages list[BaseMessage]

Decoded LangChain messages to process through the graph.

required
graph_registry GraphRegistry

The registry containing the graph configurations.

required
resume InterruptResume | None

A decoded, complete interrupt answer batch, when resuming.

None
checkpoint_scope str

Server-trusted scope used to isolate checkpoint state.

'default'

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(
    request: GraphRequest,
    messages: list[BaseMessage],
    graph_registry: GraphRegistry,
    *,
    resume: interrupt_models.InterruptResume | None = None,
    checkpoint_scope: str = "default",
) -> 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:
        request: Normalized graph selection, metadata, user, and client tools.
        messages: Decoded LangChain messages to process through the graph.
        graph_registry: The registry containing the graph configurations.
        resume: A decoded, complete interrupt answer batch, when resuming.
        checkpoint_scope: Server-trusted scope used to isolate checkpoint state.

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

    """
    run = await prepare_run(
        request,
        messages,
        graph_registry,
        resume=resume,
        checkpoint_scope=checkpoint_scope,
    )
    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."""
    total = None
    for usage in self.usage_callback.usage_metadata.values():
        total = add_usage(total, usage)
    return total

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(
    request, messages, graph_registry, *, resume=None, checkpoint_scope="default"
)

Prepare a graph run.

Source code in src/langgraph_openai_serve/graph/utils.py
async def prepare_run(
    request: GraphRequest,
    messages: list[BaseMessage],
    graph_registry: GraphRegistry,
    *,
    resume: InterruptResume | None = None,
    checkpoint_scope: str = "default",
) -> GraphRun:
    """Prepare a graph run."""
    graph_config = graph_registry.get_graph(request.model)

    graph = await graph_config.resolve_graph()
    usage_callback = UsageMetadataCallbackHandler()

    if not graph_config.supports(GraphFeature.INTERRUPTS):
        inputs = await graph_config.build_input(request, 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,
        )

    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(
        request.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,
            messages=messages,
        )
        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)
    openai_app.include_router(responses_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

protocol

Stable names used by the public LGOS protocol extensions.

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)])
        ]
    )