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 client function tools, registered name-only custom-tool selectors, standard web_search, their 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. Server execution returns native custom_tool_call and custom_tool_call_output pairs or a web_search_call in the same response; complete output items can also be replayed as history. LGOS executes registered custom tools inside that response. This intentionally differs from OpenAI's ordinary custom-tool flow, where caller code executes the tool and supplies its output to a later model request; the wire items remain standard Responses types. The public web_search shape does not prescribe the graph's search backend; the bundled demo chooses an HTTP or upstream provider backend.

LGOS does not persist completed Responses for retrieve or deletion. Omitted, null, and false store values are accepted, and the returned Response reports store=false; 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 unregistered custom tools, client-supplied custom descriptions or formats, other built-in tools, structured output, image/audio input, URL or inline file input, function 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.

Chat Completions Request

Chat messages accept string content, explicit text content parts, and native file parts containing only file.file_id. The route supports modern function tools, tool_choice, assistant tool_calls, matching tool messages, streaming, and stream_options.include_usage. Image and audio parts, inline file data or filenames, prompt-cache fields, deprecated function fields, generation controls, and other unknown fields are rejected.

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 copies its initial mapping and must contain at least one graph. It rejects empty model IDs, ., .., and IDs containing /. The public registry.registry mapping is an insertion-ordered, read-only view; use registry.register(model_id, config) to add or replace a graph. Replacing an existing ID preserves its position.

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.
  • features: GraphFeature values that enable optional server behavior or advertise graph input and client-tool capabilities.
  • client_settings: explicit public ClientSettings model class advertised by model retrieval.
  • server_tools: internal allowlist of server-executed tool names. A registered custom tool is selected with the Responses custom type and name; web_search uses its built-in type. The graph owns tool definitions and execution. Model retrieval does not advertise tools. See Server Tools.
  • 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.

GraphConfig is immutable after construction. Pydantic snapshots features and server_tools as frozen sets, so later mutations of the input collections cannot change a registered model. To change a declaration, construct a replacement and pass it to registry.register(). Freezing the declaration does not make a caller-owned callback handler or callback manager internally immutable.

Streaming forwards non-empty text from every AIMessageChunk emitted by the graph's messages stream. Configure private ChatOpenAI calls with disable_streaming=True; LangChain then uses the complete invocation path and does not emit model stream chunks for that call.

A directly supplied compiled graph is reused. A sync or async graph factory is called for every request and is never cached; LGOS validates each resolved value as a compiled state graph and rechecks its context schema and interrupt checkpointer capabilities before execution. Static configuration relationships, including the requirement that run_coordinator appear exactly when GraphFeature.INTERRUPTS is enabled, fail during GraphConfig construction.

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 the shared model, metadata, user, normalized client function tools, selected server-tool names in server_tools, tool_choice, and parallel_tool_calls values. NamedFunctionToolChoice identifies a required client function, while NamedCustomToolChoice identifies a required registered custom tool. A single web_search declaration with tool_choice="required" requires search. Named built-in choices are outside the supported subset. 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 features set is returned in the versioned lgos.features extension and enables server behavior where applicable. GraphFeature.CLIENT_EVENTS enables and advertises public status commentary in streaming Responses. Chat Completions ignores custom stream events and does not emit commentary. GraphFeature.MCP_TOOLS advertises that a client may attach and execute tools from its configured MCP gateway; it does not publish tool definitions or grant access to them. 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 them and create 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 isinstance(self.graph, CompiledStateGraph):
        graph = self.graph
    else:
        graph = await _maybe_await(self.graph())
    return _validate_resolved_graph(graph, self)

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

validate_interrupt_configuration

validate_interrupt_configuration()

Validate feature relationships that do not depend on a resolved graph.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@model_validator(mode="after")
def validate_interrupt_configuration(self) -> Self:
    """Validate feature relationships that do not depend on a resolved graph."""
    interrupt_enabled = self.supports(GraphFeature.INTERRUPTS)
    if self.run_coordinator is not None and not interrupt_enabled:
        msg = "run_coordinator is only supported by interrupt-enabled graphs."
        raise ValueError(msg)
    if interrupt_enabled and self.run_coordinator is None:
        msg = "Interrupt-enabled graphs must configure a run_coordinator."
        raise ValueError(msg)
    return self

GraphFeature

Bases: StrEnum

Features supported by a registered graph.

GraphRegistry

GraphRegistry(*, registry)

Registry of graphs.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def __init__(self, *, registry: Mapping[str, GraphConfig]) -> None:
    if not registry:
        msg = "GraphRegistry must contain at least one graph."
        raise ValueError(msg)

    entries = {
        _validate_model_id(model_id): _validate_graph_config(config)
        for model_id, config in registry.items()
    }
    self._entries = entries
    self._registry = MappingProxyType(entries)

registry property

registry

The read-only, insertion-ordered registry view.

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."""
    validated_model_id = _validate_model_id(model_id)
    validated_config = _validate_graph_config(config)
    self._entries[validated_model_id] = validated_config

GraphRequest dataclass

GraphRequest(
    model, metadata, user, tools, tool_choice, parallel_tool_calls, server_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

NamedCustomToolChoice dataclass

NamedCustomToolChoice(name)

Require one named custom tool.

NamedFunctionToolChoice dataclass

NamedFunctionToolChoice(name)

Require one named 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.

UnsupportedChatRequestError
UnsupportedChatRequestError(message, *, param)

Bases: ValueError

Raised when a valid Chat field has unsupported LGOS semantics.

Source code in src/langgraph_openai_serve/api/chat/request.py
def __init__(self, message: str, *, param: str) -> None:
    super().__init__(message)
    self.param = param
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.

ChatCompletionFileContentPart

Bases: _ChatRequestModel

One native Chat Completions file-ID content part.

ChatCompletionFileReference

Bases: _ChatRequestModel

One uploaded file selected by its opaque Files API ID.

ChatCompletionRequest

Bases: _ChatRequestModel

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: _ChatRequestModel

Model for a chat completion request message.

ChatCompletionStreamOptions

Bases: _ChatRequestModel

Options that affect Chat Completions streaming.

ChatCompletionTextContentPart

Bases: _ChatRequestModel

One text part in a Chat Completions message.

FunctionDefinition

Bases: _ChatRequestModel

Model for a function definition.

NamedToolChoice

Bases: _ChatRequestModel

Named function tool choice accepted by Chat Completions.

NamedToolChoiceFunction

Bases: _ChatRequestModel

Function selected by a named Chat Completions tool choice.

Role

Bases: StrEnum

Role options for chat messages.

Tool

Bases: _ChatRequestModel

Model for a tool.

ToolCall

Bases: _ChatRequestModel

Model for a tool call.

ToolCallFunction

Bases: _ChatRequestModel

Model for a tool call function.

service

Prepare and execute graph runs for OpenAI 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."""
    async with run:
        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,
        )
prepare_completion_run async
prepare_completion_run(request, graph_registry)

Validate a Chat request and prepare its graph run.

Source code in src/langgraph_openai_serve/api/chat/service.py
async def prepare_completion_run(
    request: ChatCompletionRequest,
    graph_registry: GraphRegistry,
) -> GraphRun:
    """Validate a Chat request and prepare its graph run."""
    graph_request, messages = decode_chat_request(request)
    graph_config = graph_registry.get_graph(request.model)
    if graph_config.supports(GraphFeature.INTERRUPTS):
        message = (
            f"Model '{request.model}' requires interrupts, which is only "
            "supported via the Responses API (/v1/responses)."
        )
        raise UnsupportedChatRequestError(message, param="model")
    return await prepare_run(graph_request, messages, graph_registry)
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)]

The graph registry dependency.

required
stream_owner Annotated[StreamOwner, Depends(get_stream_owner, 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)],
    stream_owner: Annotated[
        StreamOwner,
        Depends(get_stream_owner, 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:
            run = await chat_service.prepare_completion_run(
                chat_request,
                graph_registry,
            )
        except (InvalidChatMessageError, UnsupportedChatRequestError) 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, UnsupportedChatRequestError)
                        else "messages"
                    ),
                ),
            ) from exc

        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.

get_graph_registry

get_graph_registry(request)

Get the graph registry from application state.

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

get_stream_owner async

get_stream_owner()

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 get_stream_owner() -> AsyncIterator[StreamOwner]:
    """
    Manage the streaming producer owned by one request.

    Yields:
        The request-scoped stream owner.

    """
    async with StreamOwner() as owner:
        yield owner

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

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

deps

FastAPI dependencies local to the Responses route.

get_checkpoint_scope async
get_checkpoint_scope(request)

Resolve the server-trusted checkpoint scope for one request.

Source code in src/langgraph_openai_serve/api/responses/deps.py
async def get_checkpoint_scope(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

events

Build SDK-typed OpenAI Responses events and named SSE frames.

ResponsesEventBuilder
ResponsesEventBuilder(request, *, run_id=None, server_tools=())

Own stable state for one Responses event lifecycle.

Source code in src/langgraph_openai_serve/api/responses/events.py
def __init__(
    self,
    request: ResponseCreateRequest,
    *,
    run_id: str | None = None,
    server_tools: Collection[str] = (),
) -> None:
    self._context = ResponseContext.for_run(request, run_id=run_id)
    self._sequence_number = 0
    self._output: list[ResponseOutputItem] = []
    self._server_tool_tracker = ServerToolTracker(server_tools)
    self._final_item: _TextItem | None = None
    self._terminal_emitted = False
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/events.py
def commentary(self, text: str) -> Iterator[ResponseStreamEvent]:
    """
    Emit one complete commentary message lifecycle.

    Yields:
        Typed events for the message lifecycle.

    """
    self._ensure_active()
    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/events.py
def created(self) -> ResponseCreatedEvent:
    """Create the initial response event."""
    self._ensure_active()
    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/events.py
def failure(self, message: str) -> Iterator[ResponseStreamEvent]:
    """
    Emit the normative terminal failure sequence.

    Yields:
        The error and failed Response events.

    """
    self._ensure_active()
    # 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 getattr(self._output[item.output_index], "status", None)
        == "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 getattr(item, "status", None) == "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 self._terminal(
        ResponseFailedEvent(
            type="response.failed",
            sequence_number=self._sequence(),
            response=self._response(
                status="failed",
                error=ResponseError.model_validate(
                    {
                        "code": "server_error",
                        "message": message,
                        "misalignment": None,
                    }
                ),
            ),
        )
    )
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/events.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.

    """
    self._ensure_active()
    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/events.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.

    """
    self._ensure_active()
    calls, item_status, incomplete_details = self._completion(message)
    yield from self._finish_answer(message, status=item_status)
    for call in calls:
        yield from self._tool_item(call.model_copy(update={"status": item_status}))
    response = self._response(
        status=item_status,
        usage=response_usage(message.usage_metadata),
        incomplete_details=incomplete_details,
    )
    if incomplete_details is not None:
        yield self._terminal(
            ResponseIncompleteEvent(
                type="response.incomplete",
                sequence_number=self._sequence(),
                response=response,
            )
        )
    else:
        yield self._terminal(
            ResponseCompletedEvent(
                type="response.completed",
                sequence_number=self._sequence(),
                response=response,
            )
        )
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/events.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.

    """
    self._ensure_active()
    self._server_tool_tracker.ensure_complete()
    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._tool_item(call)
    yield self._terminal(
        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/events.py
def in_progress(self) -> ResponseInProgressEvent:
    """Create the response in-progress event."""
    self._ensure_active()
    return ResponseInProgressEvent(
        type="response.in_progress",
        sequence_number=self._sequence(),
        response=self._response(status="in_progress"),
    )
server_tools
server_tools(event)

Expose selected tool activity from one root graph update.

Yields:

Type Description
ResponseStreamEvent

Native application-tool item lifecycle events.

Source code in src/langgraph_openai_serve/api/responses/events.py
def server_tools(self, event: UpdatesStreamPart) -> Iterator[ResponseStreamEvent]:
    """
    Expose selected tool activity from one root graph update.

    Yields:
        Native application-tool item lifecycle events.

    """
    self._ensure_active()
    for item in self._server_tool_tracker.items(event):
        yield from self._tool_item(item)
encode_event
encode_event(event)

Encode one Responses event using the official named SSE framing.

Source code in src/langgraph_openai_serve/api/responses/events.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(by_alias=True)}\n\n"

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, generation_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, generation_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 _GENERATION_TOKEN_PATTERN.fullmatch(generation_token) is None:
        msg = "Interrupt generation tokens must be SHA-256 hex digests."
        raise ValueError(msg)
    response_nonce = response_id.rsplit("_", 1)[-1]
    return f"{_INTERRUPT_CALL_PREFIX}{generation_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)
    generation_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_generation, interrupt_id = _parse_interrupt_tool_call_id(
            item.call_id, previous_response_id
        )
        if generation_token is None:
            generation_token = output_generation
        elif output_generation != generation_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 generation_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,
        generation_token=generation_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)
    calls: list[ResponseFunctionCallInput | ResponseCustomToolCallInput] = []
    for item in input_value:
        if isinstance(item, (ResponseFunctionCallInput, ResponseCustomToolCallInput)):
            calls.append(item)
            continue
        if calls:
            messages.append(_tool_call_message(calls))
            calls = []
        messages.append(_message_from_item(item))
    if calls:
        messages.append(_tool_call_message(calls))
    return messages

output

Convert graph output into OpenAI Response models.

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/output.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))
response
response(*, status, output, error=None, usage=None, incomplete_details=None)

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

Source code in src/langgraph_openai_serve/api/responses/output.py
def response(
    self,
    *,
    status: Literal["in_progress", "completed", "failed", "incomplete"],
    output: Sequence[ResponseOutputItem],
    error: ResponseError | None = None,
    usage: ResponseUsage | None = None,
    incomplete_details: IncompleteDetails | None = None,
) -> Response:
    """Build one SDK-typed Response with the route's stable defaults."""
    request = self.request
    return Response.model_validate(
        {
            "id": self.id,
            "object": "response",
            "created_at": self.created_at,
            "status": status,
            "background": False,
            "completed_at": time.time() if status == "completed" else None,
            "error": error,
            "incomplete_details": incomplete_details,
            "instructions": request.instructions,
            "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,
            # OpenAI v3 added this nullable response field. Supplying it to
            # v2 is safe because SDK response models allow extra fields.
            "prompt_cache_diagnostics": None,
            "service_tier": "default",
            "store": False,
            "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", by_alias=True)
                for tool in request.tools or ()
            ],
            "top_logprobs": 0,
            "truncation": "disabled",
            "usage": usage,
            "user": request.user,
        }
    )
UnsupportedResponsesOutputError

Bases: RuntimeError

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

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/output.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,
                generation_token=batch.generation_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/output.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/output.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 and response_incomplete_details(message) is None:
        msg = "The final assistant message contains invalid tool calls."
        raise UnsupportedResponsesOutputError(msg)

    calls = [response_function_call(call) for call in message.tool_calls]
    calls.extend(_incomplete_function_call(call) for call in message.invalid_tool_calls)
    seen_call_ids: set[str] = set()
    for output in calls:
        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)
    return calls
response_incomplete_details
response_incomplete_details(message)

Keep the final provider's truncation or filtering outcome visible.

Source code in src/langgraph_openai_serve/api/responses/output.py
def response_incomplete_details(message: AIMessage) -> IncompleteDetails | None:
    """Keep the final provider's truncation or filtering outcome visible."""
    metadata = message.response_metadata
    if metadata.get("status") == "incomplete":
        return IncompleteDetails.model_validate(
            metadata.get("incomplete_details") or {}
        )
    reason = metadata.get("finish_reason")
    if reason == "length":
        return IncompleteDetails(reason="max_output_tokens")
    if reason == "content_filter":
        return IncompleteDetails(reason="content_filter")
    return None
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/output.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_refusals
response_refusals(message)

Read refusals through LangChain's normalized content boundary.

Source code in src/langgraph_openai_serve/api/responses/output.py
def response_refusals(message: AIMessage) -> list[ResponseOutputRefusal]:
    """Read refusals through LangChain's normalized content boundary."""
    refusals = []
    for block in message.content_blocks:
        value = block.get("value")
        if block["type"] != "non_standard" or not isinstance(value, dict):
            continue
        refusal = value.get("refusal")
        if value.get("type") == "refusal" and isinstance(refusal, str):
            refusals.append(ResponseOutputRefusal(type="refusal", refusal=refusal))
    fallback = message.additional_kwargs.get("refusal")
    if not refusals and isinstance(fallback, str):
        refusals.append(ResponseOutputRefusal(type="refusal", refusal=fallback))
    return refusals
response_usage
response_usage(usage)

Map provider-reported LangChain usage to Responses token details.

Source code in src/langgraph_openai_serve/api/responses/output.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"],
    )

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, server_tools)

Normalize one supported, stateless Responses request.

Source code in src/langgraph_openai_serve/api/responses/request.py
def decode_responses_request(
    request: ResponseCreateRequest,
    server_tools: AbstractSet[str],
) -> 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)
            ),
            server_tools=selected_server_tools(request, server_tools),
            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,
    )
selected_server_tools
selected_server_tools(request, server_tools)

Return the registered server tools selected for this response.

Source code in src/langgraph_openai_serve/api/responses/request.py
def selected_server_tools(
    request: ResponseCreateRequest, server_tools: AbstractSet[str]
) -> tuple[str, ...]:
    """Return the registered server tools selected for this response."""
    if request.tool_choice == "none":
        return ()
    return tuple(
        _tool_name(tool)
        for tool in request.tools or ()
        if isinstance(tool, (ResponseCustomTool, ResponseWebSearchTool))
        and _tool_name(tool) in server_tools
    )
validate_tools
validate_tools(request, server_tools)

Reject unknown server-tool selectors before execution or SSE starts.

Source code in src/langgraph_openai_serve/api/responses/request.py
def validate_tools(
    request: ResponseCreateRequest,
    server_tools: AbstractSet[str],
) -> None:
    """Reject unknown server-tool selectors before execution or SSE starts."""
    declarations: dict[str, str] = {}
    for index, tool in enumerate(request.tools or ()):
        name = _tool_name(tool)
        if isinstance(tool, ResponseFunctionTool) and name in server_tools:
            expected_type = "web_search" if name == "web_search" else "custom"
            msg = f"Registered server tool '{name}' must use type '{expected_type}'."
            raise UnsupportedResponsesRequestError(msg, param=f"tools.{index}.type")
        if isinstance(tool, ResponseCustomTool) and name == "web_search":
            msg = "The standard web_search tool must use type 'web_search'."
            raise UnsupportedResponsesRequestError(msg, param=f"tools.{index}.type")
        if isinstance(tool, (ResponseCustomTool, ResponseWebSearchTool)) and (
            name not in server_tools
        ):
            msg = f"Tool '{name}' is not registered by model '{request.model}'."
            raise UnsupportedResponsesRequestError(
                msg,
                param=(
                    f"tools.{index}.name"
                    if isinstance(tool, ResponseCustomTool)
                    else f"tools.{index}.type"
                ),
            )
        if name in declarations:
            msg = f"Tool '{name}' is declared more than once."
            raise UnsupportedResponsesRequestError(msg, param="tools")
        declarations[name] = tool.type
    choice = request.tool_choice
    if choice is not None and not isinstance(choice, str):
        if declarations.get(choice.name) != choice.type:
            msg = "The named tool_choice must be declared in tools."
            raise UnsupportedResponsesRequestError(msg, param="tool_choice")
    elif choice == "required" and not declarations:
        msg = "tool_choice='required' needs at least one tool."
        raise UnsupportedResponsesRequestError(msg, param="tool_choice")

schemas

Validated request models for the supported Responses API subset.

ResponseCreateRequest

Bases: _ResponsesRequestModel

The stateless Responses request accepted by LGOS.

ResponseCustomTool

Bases: _ResponsesRequestModel

Select one registered server tool with the Responses custom-tool shape.

ResponseCustomToolCallInput

Bases: _ResponsesRequestModel

A custom-tool call replayed from a previous Response.

ResponseCustomToolCallOutputInput

Bases: _ResponsesRequestModel

A string result replayed for a preceding custom-tool call.

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.

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 or custom tool.

ResponseOutputMessageInput

Bases: _ResponsesRequestModel

A terminal assistant output message replayed as input.

ResponseOutputTextInput

Bases: _ResponsesRequestModel

Plain output text replayed from a previous assistant message.

ResponseRefusalInput

Bases: _ResponsesRequestModel

A model refusal replayed from an assistant message.

ResponseTextConfig

Bases: _ResponsesRequestModel

Plain-text response configuration.

ResponseTextFormat

Bases: _ResponsesRequestModel

The supported plain-text output format.

ResponseURLCitationInput

Bases: _ResponsesRequestModel

A URL citation replayed with assistant output text.

ResponseWebSearchActionInput

Bases: _ResponsesRequestModel

The query action produced by LGOS's supported web-search tool.

ResponseWebSearchCallInput

Bases: _ResponsesRequestModel

A web-search call replayed from a previous Response.

ResponseWebSearchTool

Bases: _ResponsesRequestModel

Select the graph's OpenAI-compatible web-search capability.

server_tools

Translate LGOS-executed tools into native Responses output items.

ServerToolTracker
ServerToolTracker(selected)

Correlate root-graph server-tool calls with their ToolMessages.

Source code in src/langgraph_openai_serve/api/responses/server_tools.py
def __init__(self, selected: Collection[str]) -> None:
    self._selected = frozenset(selected)
    self._pending: dict[str, _ServerCall] = {}
    self._completed: set[str] = set()
client_function_calls
client_function_calls(message)

Return final function calls that belong to the client.

Source code in src/langgraph_openai_serve/api/responses/server_tools.py
def client_function_calls(
    self, message: AIMessage
) -> list[ResponseFunctionToolCall]:
    """Return final function calls that belong to the client."""
    self.ensure_complete()
    calls = response_function_calls(message)
    if any(call.name in self._selected for call in calls):
        msg = "Server tool output contains a call without its executed result."
        raise UnsupportedResponsesOutputError(msg)
    return calls
ensure_complete
ensure_complete()

Reject a response whose selected call has no graph-produced result.

Source code in src/langgraph_openai_serve/api/responses/server_tools.py
def ensure_complete(self) -> None:
    """Reject a response whose selected call has no graph-produced result."""
    if self._pending:
        msg = "Server tool output contains a call without its executed result."
        raise UnsupportedResponsesOutputError(msg)
items
items(event)

Yield public tool items represented by one root graph update.

Yields:

Type Description
ServerToolItem

Selected calls and graph-produced results.

Source code in src/langgraph_openai_serve/api/responses/server_tools.py
def items(self, event: UpdatesStreamPart) -> Iterator[ServerToolItem]:
    """
    Yield public tool items represented by one root graph update.

    Yields:
        Selected calls and graph-produced results.

    """
    if event["ns"]:
        return
    for update in event["data"].values():
        for message in _update_messages(update):
            if isinstance(message, AIMessage):
                yield from self._tool_calls(message)
            elif isinstance(message, ToolMessage):
                item = self._tool_result(message)
                if item is not None:
                    yield item

service

Prepare and execute graph runs for OpenAI Responses.

collect_response async
collect_response(request, run)

Build one non-streaming Response from the graph's durable output.

Source code in src/langgraph_openai_serve/api/responses/service.py
async def collect_response(request: ResponseCreateRequest, run: GraphRun) -> Response:
    """Build one non-streaming Response from the graph's durable output."""
    try:
        server_tools = selected_server_tools(request, run.config.server_tools)
        builder = ResponsesEventBuilder(
            request,
            run_id=run.run_id,
            server_tools=server_tools,
        )
    except BaseException as exc:
        run.record_failure(exc)
        await run.aclose()
        raise

    if not server_tools:
        async with run:
            output = await invoke_run(run)
            for event in _terminal_events(builder, output, run):
                if isinstance(event, (ResponseCompletedEvent, ResponseIncompleteEvent)):
                    return event.response
    else:
        events = _successful_response_events(
            builder,
            run,
            stream_updates=True,
            streaming=False,
        )
        async with aclosing(events):
            async for event in events:
                if isinstance(event, (ResponseCompletedEvent, ResponseIncompleteEvent)):
                    return event.response
    msg = "Graph execution completed without a final Response."
    raise UnsupportedResponsesOutputError(msg)
prepare_response_run async
prepare_response_run(request, graph_registry, *, checkpoint_scope)

Validate a Responses request and prepare its graph run.

Source code in src/langgraph_openai_serve/api/responses/service.py
async def prepare_response_run(
    request: ResponseCreateRequest,
    graph_registry: GraphRegistry,
    *,
    checkpoint_scope: str,
) -> GraphRun:
    """Validate a Responses request and prepare its graph run."""
    graph_config = graph_registry.get_graph(request.model)
    validate_tools(request, graph_config.server_tools)
    if request.previous_response_id is not None and not graph_config.supports(
        GraphFeature.INTERRUPTS
    ):
        message = (
            "Previous response state is not supported for model "
            f"'{request.model}'; only interruptible graphs support "
            "'previous_response_id'."
        )
        raise UnsupportedResponsesRequestError(
            message,
            param="previous_response_id",
        )
    graph_request, messages, resume = decode_responses_request(
        request,
        graph_config.server_tools,
    )
    return await prepare_run(
        graph_request,
        messages,
        graph_registry,
        resume=resume,
        checkpoint_scope=checkpoint_scope,
    )
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/service.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.

    """
    server_tools = selected_server_tools(request, run.config.server_tools)
    builder = ResponsesEventBuilder(
        request,
        run_id=run.run_id,
        server_tools=server_tools,
    )
    events = _successful_response_events(
        builder,
        run,
        stream_updates=bool(server_tools),
    )
    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)],
    checkpoint_scope: Annotated[str, Depends(get_checkpoint_scope)],
    stream_owner: Annotated[
        StreamOwner,
        Depends(get_stream_owner, 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:
            run = await responses_service.prepare_response_run(
                response_request,
                graph_registry,
                checkpoint_scope=checkpoint_scope,
            )
        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
        if response_request.stream:
            body = stream_owner.start(
                responses_service.stream_response(response_request, run),
                run,
            )
            return StreamingResponse(body, media_type="text/event-stream")
        try:
            return await responses_service.collect_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.

StreamOwner

StreamOwner()

Own the producer and resources for one streaming graph run.

Source code in src/langgraph_openai_serve/api/streaming.py
def __init__(self) -> None:
    self._started = False
    self._producer: asyncio.Task[None] | None = None
    self._run: GraphRun | None = None
    self._send_stream: MemoryObjectSendStream[str] | None = None
    self._receive_stream: MemoryObjectReceiveStream[str] | None = None
__aenter__ async
__aenter__()

Enter this stream owner's request-scoped lifetime.

Source code in src/langgraph_openai_serve/api/streaming.py
async def __aenter__(self) -> Self:
    """Enter this stream owner's request-scoped lifetime."""
    return self
__aexit__ async
__aexit__(_exc_type, exc, _traceback)

Close the producer and prepared run when the request scope exits.

Source code in src/langgraph_openai_serve/api/streaming.py
async def __aexit__(
    self,
    _exc_type: type[BaseException] | None,
    exc: BaseException | None,
    _traceback: TracebackType | None,
) -> None:
    """Close the producer and prepared run when the request scope exits."""
    if exc is not None and self._run is not None:
        self._run.record_failure(exc)
    try:
        await self.aclose()
    except BaseException:
        if exc is None:
            raise
        logger.exception("openai.stream_cleanup_failed")
aclose async
aclose()

Stop production and close the prepared run exactly once.

Source code in src/langgraph_openai_serve/api/streaming.py
async def aclose(self) -> None:
    """Stop production and close the prepared run exactly once."""
    producer = self._producer
    run = self._run
    if run is None:
        return

    # Cleanup may run inside the request's cancelled scope, so shield nested
    # stream finalizers long enough to finish.
    with CancelScope(shield=True):
        primary_error: BaseException | None = None
        try:
            await self._stop_producer(producer)
        except BaseException as exc:
            primary_error = exc
            raise
        finally:
            try:
                await self._close_run(run, primary_error)
            finally:
                self._reset()
start
start(source, run)

Start the producer and take fallback ownership of its prepared run.

Source code in src/langgraph_openai_serve/api/streaming.py
def start(
    self,
    source: AsyncGenerator[str, None],
    run: GraphRun,
) -> MemoryObjectReceiveStream[str]:
    """Start the producer and take fallback ownership of its prepared run."""
    if self._started:
        msg = "A stream owner can only start one producer."
        raise RuntimeError(msg)

    # An unbuffered handoff propagates response backpressure into graph
    # execution.
    send_stream, receive_stream = create_memory_object_stream[str](
        max_buffer_size=0
    )

    async def produce() -> None:
        async with aclosing(source), send_stream:
            async for chunk in source:
                await send_stream.send(chunk)

    self._started = True
    self._run = run
    self._send_stream = send_stream
    self._receive_stream = receive_stream
    self._producer = asyncio.create_task(produce(), name="openai-response-stream")
    return receive_stream

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."""
    payload = error.model_dump(mode="json")
    # OpenAI v3 added this nullable field. Include it under v2 as well so the
    # public error envelope does not depend on the installed SDK generation.
    payload.setdefault("misalignment", None)
    return {"error": payload}

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."""
    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()
            cited_text = citation.get("cited_text")
            span = citation_slice(
                citation["start_index"], citation["end_index"], block["text"]
            )
            if cited_text is not None and block["text"][span] != cited_text:
                msg = "citation indices must match cited_text"
                raise ValueError(msg)
            citation["start_index"] += text_offset
            citation["end_index"] += text_offset
            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 isinstance(self.graph, CompiledStateGraph):
        graph = self.graph
    else:
        graph = await _maybe_await(self.graph())
    return _validate_resolved_graph(graph, self)
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
validate_interrupt_configuration
validate_interrupt_configuration()

Validate feature relationships that do not depend on a resolved graph.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@model_validator(mode="after")
def validate_interrupt_configuration(self) -> Self:
    """Validate feature relationships that do not depend on a resolved graph."""
    interrupt_enabled = self.supports(GraphFeature.INTERRUPTS)
    if self.run_coordinator is not None and not interrupt_enabled:
        msg = "run_coordinator is only supported by interrupt-enabled graphs."
        raise ValueError(msg)
    if interrupt_enabled and self.run_coordinator is None:
        msg = "Interrupt-enabled graphs must configure a run_coordinator."
        raise ValueError(msg)
    return self

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

GraphRegistry(*, registry)

Registry of graphs.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def __init__(self, *, registry: Mapping[str, GraphConfig]) -> None:
    if not registry:
        msg = "GraphRegistry must contain at least one graph."
        raise ValueError(msg)

    entries = {
        _validate_model_id(model_id): _validate_graph_config(config)
        for model_id, config in registry.items()
    }
    self._entries = entries
    self._registry = MappingProxyType(entries)
registry property
registry

The read-only, insertion-ordered registry view.

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."""
    validated_model_id = _validate_model_id(model_id)
    validated_config = _validate_graph_config(config)
    self._entries[validated_model_id] = validated_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, generation_token, values)

A complete, causally bound set of interrupt answers.

LangGraphInterruptBatch dataclass

LangGraphInterruptBatch(run_id, generation_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, generation_token, values)

A complete, causally bound set of interrupt answers.

LangGraphInterruptBatch dataclass
LangGraphInterruptBatch(run_id, generation_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()
continuation_generation_token async
continuation_generation_token(graph, runnable_config)

Fingerprint the durable continuation generation across all namespaces.

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 continuation_generation_token(
    graph: CompiledStateGraph,
    runnable_config: RunnableConfig,
) -> str | None:
    """
    Fingerprint the durable continuation generation across all namespaces.

    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

        # Locked LangGraph 1.2.9 can reuse both its interrupt ID and checkpoint
        # ID for a later pause in one task. Only the durable RESUME-write count
        # distinguishes that continuation generation without storing answers.
        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, interrupts, runnable_config, run_id)

Bind native execution interrupts to the durable checkpoint head.

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

    pending_interrupts = _native_interrupts_by_id(interrupts)
    if not pending_interrupts:
        return None

    if run_id is None:
        msg = "run_id cannot be None"
        raise RuntimeError(msg)
    generation_token = await continuation_generation_token(graph, runnable_config)
    if generation_token is None:
        msg = "Interrupted LangGraph state has no checkpoint tuple."
        raise RuntimeError(msg)
    return LangGraphInterruptBatch(
        run_id=run_id,
        generation_token=generation_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, tuple[Interrupt, ...]]:
    """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:
        return await _prepare_new_or_retry_input(
            graph_config,
            request,
            messages,
            checkpoint_id=checkpoint_id,
            pending_interrupts=pending_interrupts,
        )

    _require_pending_resume_state(checkpoint_id, pending_interrupts)

    generation_token = await continuation_generation_token(graph, snapshot.config)
    if generation_token is None:
        msg = "No durable interrupt state exists for this run."
        raise InterruptStateConflictError(msg)
    return (
        _resume_interrupt_inputs(
            generation_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, server_tools=()
)

Request data shared by protocol decoders and graph execution.

NamedCustomToolChoice dataclass

NamedCustomToolChoice(name)

Require one named custom tool.

NamedFunctionToolChoice dataclass

NamedFunctionToolChoice(name)

Require one named function.

runner

Run LangGraph workflows from protocol-neutral requests and messages.

invoke_run async

invoke_run(run)

Invoke a graph already owned by an active GraphRun context.

Source code in src/langgraph_openai_serve/graph/runner.py
async def invoke_run(run: GraphRun) -> LangGraphOutput:
    """Invoke a graph already owned by an active ``GraphRun`` context."""
    run.require_owner()
    if not run.should_execute:
        interrupt_batch = await _durable_interrupt_batch(
            run,
            run.pending_interrupts,
        )
        if interrupt_batch is None:
            msg = "Pending interrupt state disappeared before use."
            raise RuntimeError(msg)
        run.commit_interrupts()
        return interrupt_batch

    run.begin_execution()
    result = await run.graph.ainvoke(
        run.inputs,
        config=run.runnable_config,
        context=run.context,
        output_keys=run.graph.output_channels,
        durability=_durability(run),
        version="v2",
    )

    if run.config.supports(GraphFeature.INTERRUPTS):
        interrupt_batch = await _durable_interrupt_batch(run, result.interrupts)
        if interrupt_batch is not None:
            run.commit_interrupts()
            return interrupt_batch

    return _with_usage(
        await run.config.render_output(result.value),
        run,
    )

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

    async with run:
        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,
    )
    async with run:
        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_messages=True, stream_updates=False)

Stream a graph already owned by an active GraphRun context.

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,
    *,
    stream_messages: bool = True,
    stream_updates: bool = False,
) -> AsyncGenerator[LangGraphStreamEvent, None]:
    """
    Stream a graph already owned by an active ``GraphRun`` context.

    Yields:
        LangGraph stream events.

    """
    run.require_owner()
    if not run.should_execute:
        interrupt_batch = await _durable_interrupt_batch(
            run,
            run.pending_interrupts,
        )
        if interrupt_batch is None:
            msg = "Pending interrupt state disappeared before use."
            raise RuntimeError(msg)
        run.commit_interrupts()
        yield interrupt_batch
        return

    run.begin_execution()
    final_output: Any = _MISSING
    interrupts: list[Interrupt] = []

    # LangGraph implements this as an async generator, while its overload
    # returns AsyncIterator. Keep the concrete type so cancellation closes it.
    graph_stream = cast(
        "AsyncGenerator[StreamPart[Any, Any], None]",
        run.graph.astream(
            run.inputs,
            config=run.runnable_config,
            context=run.context,
            stream_mode=_stream_modes(
                stream_messages=stream_messages,
                stream_updates=stream_updates,
            ),
            subgraphs=True,
            output_keys=run.graph.output_channels,
            durability=_durability(run),
            version="v2",
        ),
    )
    async with aclosing(graph_stream):
        async for part in graph_stream:
            if part["type"] == "values":
                if not part["ns"]:
                    final_output = part["data"]
                    interrupts.extend(part["interrupts"])
                continue
            visible_part = _visible_stream_part(part, stream_updates=stream_updates)
            if visible_part is not None:
                yield visible_part

    if run.config.supports(GraphFeature.INTERRUPTS):
        interrupt_batch = await _durable_interrupt_batch(run, tuple(interrupts))
        if interrupt_batch is not None:
            run.commit_interrupts()
            yield interrupt_batch
            return

    yield await _render_stream_output(final_output, run)

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,
    pending_interrupts=(),
    usage_callback=UsageMetadataCallbackHandler(),
    _resources=AsyncExitStack(),
)

Own one prepared graph run and its cleanup resources.

__aenter__ async
__aenter__()

Claim ownership of this prepared run.

Source code in src/langgraph_openai_serve/graph/utils.py
async def __aenter__(self) -> Self:
    """Claim ownership of this prepared run."""
    if self._closed:
        msg = "A closed graph run cannot be reused."
        raise RuntimeError(msg)
    if self._entered:
        msg = "A graph run can have only one active owner."
        raise RuntimeError(msg)
    self._entered = True
    return self
__aexit__ async
__aexit__(_exc_type, exc, _traceback)

Finalize this run without suppressing its primary failure.

Source code in src/langgraph_openai_serve/graph/utils.py
async def __aexit__(
    self,
    _exc_type: type[BaseException] | None,
    exc: BaseException | None,
    _traceback: TracebackType | None,
) -> None:
    """Finalize this run without suppressing its primary failure."""
    if exc is not None:
        self.record_failure(exc)
    try:
        await self.aclose()
    finally:
        self._entered = False
aclose async
aclose()

Apply checkpoint disposition and release resources exactly once.

Source code in src/langgraph_openai_serve/graph/utils.py
async def aclose(self) -> None:
    """Apply checkpoint disposition and release resources exactly once."""
    if self._closed:
        return
    self._closed = True

    with CancelScope(shield=True):
        cleanup_error: BaseException | None = None
        if self._checkpoint_disposition == "delete":
            try:
                await self._delete_checkpoint_thread()
            except BaseException as exc:
                if self._primary_error is None:
                    cleanup_error = exc
                else:
                    logger.exception("graph_run.checkpoint_cleanup_failed")

        active_error = self._primary_error or cleanup_error
        try:
            await self._resources.__aexit__(
                type(active_error) if active_error is not None else None,
                active_error,
                active_error.__traceback__ if active_error is not None else None,
            )
        except BaseException:
            if active_error is None:
                raise
            logger.exception("graph_run.lease_release_failed")

        if cleanup_error is not None:
            raise cleanup_error
begin_execution
begin_execution()

Mark checkpoint state as incomplete immediately before execution.

Source code in src/langgraph_openai_serve/graph/utils.py
def begin_execution(self) -> None:
    """Mark checkpoint state as incomplete immediately before execution."""
    self.require_owner()
    if self.config.supports(GraphFeature.INTERRUPTS):
        self._checkpoint_disposition = "delete"
commit_interrupts
commit_interrupts()

Preserve a validated interrupt batch committed by the runner.

Source code in src/langgraph_openai_serve/graph/utils.py
def commit_interrupts(self) -> None:
    """Preserve a validated interrupt batch committed by the runner."""
    self.require_owner()
    if not self.config.supports(GraphFeature.INTERRUPTS):
        msg = "Only interrupt-enabled runs can commit an interrupt batch."
        raise RuntimeError(msg)
    self._checkpoint_disposition = "preserve"
record_failure
record_failure(error)

Retain the first failure so later cleanup cannot replace it.

Source code in src/langgraph_openai_serve/graph/utils.py
def record_failure(self, error: BaseException) -> None:
    """Retain the first failure so later cleanup cannot replace it."""
    if self._primary_error is None:
        self._primary_error = error
require_owner
require_owner()

Require the caller to own this run through its async context.

Source code in src/langgraph_openai_serve/graph/utils.py
def require_owner(self) -> None:
    """Require the caller to own this run through its async context."""
    if not self._entered:
        msg = "Graph execution requires an active GraphRun context."
        raise RuntimeError(msg)
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()
    identity = _resolve_run_identity(
        request,
        graph_config,
        resume,
        checkpoint_scope=checkpoint_scope,
    )
    runnable_config = build_runnable_config(
        graph_config.runtime_callbacks,
        configurable=identity.configurable(),
        metadata=_runnable_metadata(request, identity.run_id),
        extra_callbacks=[usage_callback],
    )
    if identity.checkpoint_thread_id is not None and runnable_config is None:
        msg = "Interrupt run has no runnable configuration."
        raise RuntimeError(msg)

    resources = AsyncExitStack()
    try:
        values = await _prepare_run_values(
            request=request,
            messages=messages,
            graph_config=graph_config,
            graph=graph,
            runnable_config=runnable_config,
            identity=identity,
            resources=resources,
            resume=resume,
        )
    except BaseException:
        error_info = sys.exc_info()
        with CancelScope(shield=True):
            try:
                await resources.__aexit__(*error_info)
            except BaseException:
                logger.exception("graph_run.preparation_cleanup_failed")
        raise

    return GraphRun(
        config=graph_config,
        graph=graph,
        inputs=values.inputs,
        context=values.context,
        runnable_config=runnable_config,
        run_id=identity.run_id,
        checkpoint_thread_id=identity.checkpoint_thread_id,
        should_execute=values.should_execute,
        pending_interrupts=values.pending_interrupts,
        usage_callback=usage_callback,
        _resources=resources,
    )

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