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, features, and tool names.
GET /v1/models/{model} Retrieve one model with the required LGOS metadata extension.
POST /v1/responses Run a graph now or accept an opted-in polling-only background Response.
GET /v1/responses/{response_id} Retrieve an authorized background Response.
POST /v1/responses/{response_id}/cancel Cancel an active background Response or return a finished one.
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, safety_identifier and its deprecated alias 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.

Foreground LGOS Responses are not persisted for retrieval or deletion. Omitted, null, and false store values are accepted, and the returned foreground Response reports store=false; foreground store=true is rejected. For a model declaring GraphFeature.BACKGROUND, background=true selects the polling-only path and permits either store=false or store=true; the background engine keeps the result either way, and Hatchet keeps it for its data retention period (30 days by default when self-hosted). Background streaming and cursor/event replay are rejected. conversation is unsupported in both modes. 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.

The lgos command adds the server settings.

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(graphs={...}, run_coordinator=...) to map OpenAI model names to GraphConfig values; registry.graphs is a plain dict. The registry must contain at least one graph. Model IDs must be non-empty, must not contain /, and must not be . or ... registry.get_graph(model) returns a GraphConfig or raises a 404 model_not_found error. run_coordinator is shared by every interrupt-enabled graph and is required only when a graph declares GraphFeature.INTERRUPTS.

LanggraphOpenaiServe(registry=registry, app=app) serves the registry. LanggraphOpenaiServe(..., background=backend) accepts an optional BackgroundBackend. LanggraphOpenaiServe(..., checkpoint_scope=resolver) accepts an optional sync or async callable from FastAPI Request to a non-empty, server-trusted string. Interrupt checkpoint keys and background Response authorization include this scope before model and run identity. Use an authenticated tenant or principal identifier when runs must be isolated between security domains; do not derive the scope from untrusted OpenAI metadata or the end-user safety_identifier and user fields. 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.

RequestContextFilter adds the active request's LGOS fields, such as request_id, to log records; install it on a host handler to enrich every record. See Production Logging.

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: allowlist of server-executed tool names, advertised as lgos.server_tools. 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. See Server Tools.
  • client_tools: names of client-owned functions the graph can call, such as display_file, advertised as lgos.client_tools so clients offer only the functions a graph uses. LGOS does not enforce the list, and a name cannot also be a server tool.
  • runtime_callbacks: callbacks included in the LangGraph RunnableConfig. When Langfuse tracing is enabled, LGOS adds its callback without mutating this collection or 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, server_tools, and client_tools as frozen sets, so later mutations of the input collections cannot change a registered model. 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. To keep a private model call out of the stream, tag it with LangGraph's nostream tag (langgraph.constants.TAG_NOSTREAM), for example ChatOpenAI(..., tags=[TAG_NOSTREAM]); the call still runs and returns its output, but LangGraph emits none of its tokens. disable_streaming=True is for models that cannot stream: it makes the provider request non-streaming, and LangGraph emits the call's complete message instead. With either option, LangGraph also emits a complete message that a node returns into state. LGOS forwards neither complete message, but other consumers of the graph's stream see them.

A directly supplied compiled graph is reused and validated when its GraphConfig is built. 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 it before execution. Validation checks the context schema and interrupt checkpointer capabilities, rejects static breakpoints (interrupt_before or interrupt_after) in the graph or its subgraph nodes, since LGOS pauses only at interrupt(), and rejects a checkpointer, including checkpointer=True, on a graph without GraphFeature.INTERRUPTS, since clients send the full conversation with every other request. A registry with an interrupt-enabled graph and no run_coordinator fails during GraphRegistry construction. A graph may declare both interrupts and background; a background run that reaches an interrupt completes with lgos_interrupt function calls, and an answer in either mode continues it.

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 (the end user from safety_identifier, or the deprecated user field), 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.astream(input, ...) Messages and mutable workflow state.
Runtime context public settings → optional context_factory → context= → Runtime.context Immutable per-run application values and dependencies.
Runnable config config= Callbacks, tags, tracing, and other execution controls.
Interrupt run server scope + model + server-generated run UUID → internal checkpoint key Isolate, 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 run 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. A self-hosted Langfuse must be a release that Langfuse's compatibility matrix lists for the Python SDK version that the extra installs. 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. Before that, LGOS creates the Langfuse client with a tracer provider of its own, as Langfuse recommends beside another tracing backend. Langfuse's observations, which carry prompts and outputs, then never reach the application's OpenTelemetry exporters, and the application's spans never reach Langfuse; both share each trace ID. An application that creates its own Langfuse client first keeps its own tracing setup. 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.

LGOS gives every 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 it names an end user with safety_identifier or the deprecated user) that end user as lgos.user. Graph code can read them with LangGraph's get_config(), with or without Langfuse. When the toggle is on, LGOS also sets the end user, metadata.conversation_id, and the registered model as Langfuse's native user, session, and trace name on every observation of the run; a custom handler passed with the toggle off does not receive them. 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 lgos.features extension and enables server behavior where applicable. 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. GraphFeature.BACKGROUND enables and advertises polling-only background Responses.

OpenTelemetry

LGOS reports every graph execution through the OpenTelemetry API, following the GenAI workflow span and workflow metric conventions. The API records nothing until the host application configures an SDK, for example with opentelemetry-instrument.

HTTP request telemetry belongs to the host app. The mounted OpenAI app turns off FastAPI's native OpenTelemetry, so the host's telemetry settings, including turning it off, cover LGOS routes, and http.route includes the mount, such as /v1/responses.

Signal Name Attributes
Span, kind INTERNAL invoke_workflow {model} gen_ai.operation.name=invoke_workflow, gen_ai.workflow.name, gen_ai.conversation.id when the request supplies metadata.conversation_id, and error.type on failure
Histogram, unit s gen_ai.invoke_workflow.duration gen_ai.workflow.name, and error.type on failure

gen_ai.workflow.name is the registered model. Both signals cover graph execution through the final output; they exclude lease waits, background queue time, and checkpoint cleanup. While LGOS advances the graph, the span's context is current: spans the graph creates, such as model calls, are its children, while code that consumes a stream keeps its own parent span between events. The span itself is not current, so code in the graph cannot change it, and it carries only the attributes above; add attributes to spans of your own. A run that ends before its output exists fails: one that raises an exception, one cancelled by a disconnecting client or a cancelled background Response, and one whose stream the consumer closes mid-run. The span status is then ERROR with the exception message, if any, and error.type is the exception type, qualified by its module unless it is built in, such as asyncio.exceptions.CancelledError or GeneratorExit. Closing a stream after its final output is not a failure. Design Choices lists the tracers whose cancellation handling this follows. A raised Exception propagates to its handler: LGOS routes and the background engines log it with its traceback, and direct Python callers receive it; cancellations and closed streams are not logged. The span does not repeat an exception as an event. The histogram uses the conventions' bucket boundaries, which start at one second; configure an SDK View for finer boundaries.

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 deterministic default, because clients omit values equal to the advertised defaults; a default_factory must return the same value on every call. Registration fails when the model cannot build its defaults or schema.

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 the settings with the model's strict JSON validation 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 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.

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(), aput(), aput_writes(), and adelete_thread();
  • configure an asynchronous GraphRegistry.run_coordinator; and
  • use a durable checkpointer and cross-process coordinator in production.

The coordinator provides single-flight leases for interrupt runs: it returns an async context manager and rejects an occupied checkpoint key instead of queueing it. Exiting that context manager must release the lease even when the exit is cancelled.

The initial request does not require metadata. LGOS generates a UUID run ID for every new run and embeds it in the paused Response ID. InMemoryRunCoordinator is suitable only for tests and a single-process development server; it cannot serialize requests across workers or hosts.

Expire Paused Runs

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. A run abandoned after its batch is returned stays until deleted: call delete_expired_interrupt_runs(checkpointer, run_coordinator, older_than=...) from langgraph_openai_serve.graph.interrupt on a schedule, as LangGraph Agent Server's checkpointer TTL does. In production, prefer one scheduled job over a loop in every replica, for example a Kubernetes CronJob with concurrencyPolicy: Forbid or your task queue's scheduler. It deletes the runs whose latest pause is older than older_than and returns their count. It reads every checkpoint through alist(), leaves threads LGOS did not create alone, and skips runs whose lease is held. Choose a TTL longer than the longest time a user may take to answer. To write your own cleanup, select threads whose checkpoint metadata contains OPERATION_ID_METADATA_KEY from the same module, then hold each run's lease, confirm that its latest checkpoint in any namespace is still older than your TTL, and delete it through the checkpointer. lgos serve runs this sweep in each API process, configured by LGOS_INTERRUPT_TTL_MINUTES and LGOS_INTERRUPT_SWEEP_INTERVAL_MINUTES; set the interval to 0 when one scheduled job sweeps instead.

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() before serving requests, serializing migration attempts when workers can start together. Their migrations use CREATE INDEX CONCURRENTLY, which deadlocks with a worker blocked in pg_advisory_lock; retry pg_try_advisory_lock instead. 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. Each process applies pending LangGraph migrations during startup under a separate schema advisory lock. Busy interrupt leases fail before streaming begins with HTTP 409 and code: "run_busy".

Background Execution

LanggraphOpenaiServe(background=...) accepts any BackgroundBackend: an engine that runs BackgroundJobs and stores their status and result.

Method Contract
submit(job) Start the job, or return the run already holding job.idempotency_key.
get(run_id) Return the run's job, status, and executed Response, or None.
cancel(run_id) Stop the run; a finished run keeps its outcome.

The engine's worker calls execute_background_job(job, run_id, registry). It runs the job through the foreground Responses path and returns the Response JSON the engine stores. Run IDs must be UUIDs because the public Response ID embeds the run ID, so reading a Response needs no lookup table. InMemoryBackgroundBackend(registry) runs jobs as tasks of one process for development and tests; enter its lifespan in the application's lifespan.

Install langgraph-openai-serve[hatchet] for langgraph_openai_serve.integrations.hatchet. create_hatchet_task(hatchet) registers the task in the API and worker processes, with a 30-minute schedule_timeout and a one-hour execution_timeout by default. HatchetBackgroundBackend(task, hatchet.runs) submits, reads, and cancels its runs. The worker's Hatchet lifespan yields the GraphRegistry, including its run_coordinator, that the task executes. The task has no retries, and its 24-hour Hatchet idempotency key is the scoped Idempotency-Key digest. The adapter is never imported by the core package.

Background create accepts an optional Idempotency-Key header of 1 to 255 characters. A retry returns the original Response without starting a second run; reuse with different content returns 422 with code="idempotency_key_reused". A background interrupt run chooses its run ID at submission.

See Run Responses In The Background for the client contract, graph requirements, and deployment wiring.

Server

Install langgraph-openai-serve[server] for the lgos command and langgraph_openai_serve.server. lgos serve module:attribute and lgos worker module:attribute run a RegistryFactory: a callable that takes ServerResources (checkpointer, store, run_coordinator) and returns a GraphRegistry, or an async context manager that yields one. lgos serve also accepts every Uvicorn option and UVICORN_* variable. create_app(factory, settings=...) returns the FastAPI application that lgos serve runs; its lifespan opens the resources and mounts the OpenAI routes. ServerSettings reads the LGOS_* server settings. See Run The LGOS Server.

Streaming Status

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

# Perform the long-running work.

writer(status_event("Audio ready"))

The helper returns this custom stream value:

{
  "type": "lgos.status",
  "description": "Generating audio"
}

Status text is deliberately authored by the graph; LGOS does not infer it from internal node names or state. Graphs need no feature declaration to publish status.

Status is streaming-only. Streaming Responses needs no metadata opt-in and emits each non-empty description as a standard phase="commentary" message. Non-streaming Responses and Chat Completions ignore statuses. Both APIs ignore any other custom stream data. Use standard Responses function calls plus the Files API for portable durable rich output.

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.

BackgroundBackend

Bases: Protocol

An engine that runs background jobs and stores their status and result.

cancel async

cancel(run_id)

Stop one run; a finished run keeps its outcome.

Source code in src/langgraph_openai_serve/background.py
async def cancel(self, run_id: str) -> None:
    """Stop one run; a finished run keeps its outcome."""
    ...

get async

get(run_id)

Read one run, or return None when the engine does not have it.

Source code in src/langgraph_openai_serve/background.py
async def get(self, run_id: str) -> BackgroundRun | None:
    """Read one run, or return None when the engine does not have it."""
    ...

submit async

submit(job)

Start job, or return the run already holding its idempotency key.

Source code in src/langgraph_openai_serve/background.py
async def submit(self, job: BackgroundJob) -> BackgroundRun:
    """Start ``job``, or return the run already holding its idempotency key."""
    ...

BackgroundJob

Bases: BaseModel

A validated request and the server-owned context that executes it.

BackgroundRun

Bases: BaseModel

An engine's current view of one submitted job.

response_id property

response_id

Public ID that locates this run and continues its interrupts.

snapshot

snapshot()

Return the public Response for the run's current state.

Source code in src/langgraph_openai_serve/background.py
def snapshot(self) -> Response:
    """Return the public Response for the run's current state."""
    if self.response is not None:
        return Response.model_validate(self.response)
    response = _queued_response(self.job, self.response_id)
    if self.status in {"completed", "failed"}:
        return _failed_response(response, "The background response failed.")
    return response.model_copy(update={"status": self.status})

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 needs a deterministic default so model discovery can advertise a complete, stable settings object.

default_values classmethod

default_values()

Return the JSON defaults advertised by model discovery.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def default_values(cls) -> dict[str, JsonValue]:
    """Return the JSON defaults advertised by model discovery."""
    # Aliases are the public names in the schema and request validation.
    return cls().model_dump(mode="json", by_alias=True)

json_schema classmethod

json_schema()

Return the JSON Schema advertised by model discovery.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def json_schema(cls) -> dict[str, JsonValue]:
    """Return the JSON Schema advertised by model discovery."""
    return {"$schema": JSON_SCHEMA_DIALECT, **cls.model_json_schema()}

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."""
    try:
        return cls.model_validate_json(
            request.metadata.get(SETTINGS_METADATA_KEY, "{}")
        )
    except ValidationError as exc:
        error = exc.errors()[0]
        field = ".".join(str(part) for part in error["loc"])
        label = f"runtime setting for {field}" if field else "runtime settings"
        msg = f"Invalid {label}: {error['msg']}"
        raise InvalidRequestError(
            msg,
            param=f"metadata.{SETTINGS_METADATA_KEY}",
        ) from exc

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 GraphError(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 GraphError(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 GraphError(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):
        return self.graph
    return _validate_resolved_graph(await _maybe_await(self.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)

Fail at registration when a settings model cannot be advertised.

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:
    """Fail at registration when a settings model cannot be advertised."""
    if value is not None:
        value.json_schema()
        value.default_values()
    return value

validate_compiled_graph

validate_compiled_graph()

Fail at registration when a directly supplied graph cannot be served.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@model_validator(mode="after")
def validate_compiled_graph(self) -> Self:
    """Fail at registration when a directly supplied graph cannot be served."""
    if isinstance(self.graph, CompiledStateGraph):
        _validate_resolved_graph(self.graph, self)
    return self

validate_tool_ownership

validate_tool_ownership()

Keep the side that executes each advertised tool unambiguous.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@model_validator(mode="after")
def validate_tool_ownership(self) -> Self:
    """Keep the side that executes each advertised tool unambiguous."""
    if shared := self.server_tools & self.client_tools:
        names = ", ".join(sorted(shared))
        msg = f"Tools cannot be both server and client tools: {names}"
        raise ValueError(msg)
    return self

GraphError

Bases: RuntimeError

Raised when a graph's configuration or output cannot be served.

GraphFeature

Bases: StrEnum

Features supported by a registered graph.

GraphRegistry dataclass

GraphRegistry(graphs, run_coordinator=None)

The graphs served as OpenAI models, keyed by model ID.

run_coordinator leases the interrupt runs of every interrupt-enabled graph; use a shared coordinator when several processes serve these graphs.

__post_init__

__post_init__()

Fail at startup for a registry the OpenAI routes cannot serve.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def __post_init__(self) -> None:
    """Fail at startup for a registry the OpenAI routes cannot serve."""
    if not self.graphs:
        msg = "GraphRegistry must contain at least one graph."
        raise ValueError(msg)
    for model_id in self.graphs:
        # Each model must be addressable as GET /models/{model}.
        if not model_id or "/" in model_id or model_id in {".", ".."}:
            msg = f"Model ID {model_id!r} is not addressable."
            raise ValueError(msg)
    if self.run_coordinator is None and any(
        config.supports(GraphFeature.INTERRUPTS) for config in self.graphs.values()
    ):
        msg = "Interrupt-enabled graphs need a GraphRegistry run_coordinator."
        raise ValueError(msg)

get_graph

get_graph(model)

Return the graph served as model.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def get_graph(self, model: str) -> GraphConfig:
    """Return the graph served as ``model``."""
    try:
        return self.graphs[model]
    except KeyError as exc:
        msg = f"The model '{model}' does not exist."
        raise InvalidRequestError(
            msg,
            param="model",
            code="model_not_found",
            status_code=404,
        ) from exc

GraphRequest dataclass

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

Request data shared by protocol decoders and graph execution.

InMemoryBackgroundBackend

InMemoryBackgroundBackend(registry)

Run background jobs as tasks of this application process.

For development and tests: runs stay in memory until the process exits.

Source code in src/langgraph_openai_serve/background.py
def __init__(self, registry: GraphRegistry) -> None:
    self._registry = registry
    self._runs: dict[str, BackgroundRun] = {}
    self._keys: dict[str, str] = {}
    self._scopes: dict[str, CancelScope] = {}
    self._tasks: TaskGroup | None = None

cancel async

cancel(run_id)

Mark an active run cancelled and cancel its task.

Source code in src/langgraph_openai_serve/background.py
async def cancel(self, run_id: str) -> None:
    """Mark an active run cancelled and cancel its task."""
    if run_id in self._runs:
        self._transition(run_id, "cancelled")
    if (scope := self._scopes.get(run_id)) is not None:
        scope.cancel()

get async

get(run_id)

Read one run.

Source code in src/langgraph_openai_serve/background.py
async def get(self, run_id: str) -> BackgroundRun | None:
    """Read one run."""
    return self._runs.get(run_id)

lifespan async

lifespan(_app)

Own the execution tasks for one ASGI application lifespan.

Source code in src/langgraph_openai_serve/background.py
@asynccontextmanager
async def lifespan(self, _app: object) -> AsyncGenerator[None, None]:
    """Own the execution tasks for one ASGI application lifespan."""
    async with create_task_group() as tasks:
        self._tasks = tasks
        try:
            yield
        finally:
            self._tasks = None
            tasks.cancel_scope.cancel()

submit async

submit(job)

Start one task, or return the run holding the job's key.

Source code in src/langgraph_openai_serve/background.py
async def submit(self, job: BackgroundJob) -> BackgroundRun:
    """Start one task, or return the run holding the job's key."""
    if self._tasks is None:
        msg = "The in-memory background backend lifespan is not running."
        raise RuntimeError(msg)
    if (run_id := self._keys.get(job.idempotency_key)) is not None:
        return self._runs[run_id]
    run = BackgroundRun(id=str(uuid.uuid4()), job=job, status="queued")
    self._runs[run.id] = run
    self._keys[job.idempotency_key] = run.id
    self._scopes[run.id] = CancelScope()
    self._tasks.start_soon(self._execute, run)
    return run

InvalidRequestError

InvalidRequestError(
    message, *, param=None, code=None, status_code=status.HTTP_400_BAD_REQUEST
)

Bases: Exception

A request rejected with an OpenAI invalid_request_error envelope.

Source code in src/langgraph_openai_serve/core/errors.py
def __init__(
    self,
    message: str,
    *,
    param: str | None = None,
    code: str | None = None,
    status_code: int = status.HTTP_400_BAD_REQUEST,
) -> None:
    super().__init__(message)
    self.param = param
    self.code = code
    self.status_code = status_code

LanggraphOpenaiServe

LanggraphOpenaiServe(registry, app=None, checkpoint_scope=None, background=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 graphs to serve and their interrupt run coordinator.

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
registry GraphRegistry

The graphs to serve and their interrupt run coordinator.

required
app FastAPI | None

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

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

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

None
background BackgroundBackend | None

Optional polling-only background backend.

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

    Args:
        registry: The graphs to serve and their interrupt run coordinator.
        app: The host FastAPI application to mount the OpenAI API on. If None,
            a new FastAPI app will be created.
        checkpoint_scope: Optional server-trusted resolver used to isolate
            interrupt checkpoints by deployment or authenticated principal.
        background: Optional polling-only background backend.

    """
    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.background = background
    self.graph_registry = registry

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

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

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(),
        # FastAPI's native telemetry belongs to the host app, so the host's
        # settings, including turning it off, also cover the mounted routes.
        telemetry={"tracing": False, "metrics": False, "logs": False},
        **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
    openai_app.state.background_backend = self.background
    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.

RequestContextFilter

RequestContextFilter()

Bases: Filter

Add the active LGOS request fields to log records.

LGOS installs it on its own loggers. A host that installs it on a handler also enriches records from graph nodes, dependencies, and its own code.

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

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 GraphError(msg)
    return slice(start_index, stop)

execute_background_job async

execute_background_job(job, engine_run_id, registry)

Execute one job through the foreground Responses path.

Source code in src/langgraph_openai_serve/background.py
async def execute_background_job(
    job: BackgroundJob,
    engine_run_id: str,
    registry: GraphRegistry,
) -> dict[str, JsonValue]:
    """Execute one job through the foreground Responses path."""
    request = ResponseCreateRequest.model_validate(job.request)
    response_id = interrupt_response_id(job.run_id, engine_run_id)
    try:
        run = await prepare_response_run(
            request,
            registry,
            checkpoint_scope=job.owner_scope,
            run_id=job.run_id,
        )
        response = await collect_response(
            request,
            run,
            response_id=response_id,
            created_at=job.created_at,
        )
    except InvalidRequestError as exc:
        # The foreground path reports these as 4xx; a background Response
        # reports them as its own failure. An interrupt answer, for example,
        # is validated only once its run holds the lease, so a stale answer
        # fails here.
        response = _failed_response(_queued_response(job, response_id), str(exc))
    return response.model_dump(mode="json", by_alias=True)

status_event

status_event(description)

Build a status update for a node to write with get_stream_writer().

Source code in src/langgraph_openai_serve/graph/events.py
6
7
8
def status_event(description: str) -> dict[str, str]:
    """Build a status update for a node to write with ``get_stream_writer()``."""
    return {"type": STATUS_EVENT_TYPE, "description": description}

api

chat

messages

Convert Chat Completions messages into LangChain messages.

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 InvalidRequestError(msg, param="messages")
                lc_messages.append(
                    ToolMessage(
                        content=_langchain_content(m.content),
                        name=m.name,
                        tool_call_id=m.tool_call_id,
                    )
                )
    return lc_messages

request

Decode Chat Completions requests for protocol-neutral graph execution.

decode_chat_request
decode_chat_request(request)

Normalize one Chat Completions request for graph execution.

Source code in src/langgraph_openai_serve/api/chat/request.py
def decode_chat_request(
    request: ChatCompletionRequest,
) -> tuple[GraphRequest, list[BaseMessage]]:
    """Normalize one Chat Completions request for graph execution."""
    graph_request = GraphRequest(
        model=request.model,
        metadata=dict(request.metadata or {}),
        user=request.safety_identifier or 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 GraphError(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 collect_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_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 InvalidRequestError(message, param="model")
    graph_request, messages = decode_chat_request(request)
    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 one chat completion, as JSON or as an SSE 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 one chat completion, as JSON or as an SSE stream."""
    bind_log_context(model=chat_request.model, stream=chat_request.stream)
    run = await chat_service.prepare_completion_run(chat_request, graph_registry)
    if chat_request.stream:
        return stream_owner.start(
            chat_service.stream_completion(chat_request, run),
            run,
        )
    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()

Provide one request-scoped stream owner.

Yields:

Type Description
AsyncIterator[StreamOwner]

The stream owner, closed after the response finishes.

Source code in src/langgraph_openai_serve/api/deps.py
async def get_stream_owner() -> AsyncIterator[StreamOwner]:
    """
    Provide one request-scoped stream owner.

    Yields:
        The stream owner, closed after the response finishes.

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

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 and end-user 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

LangGraph OpenAI Serve model-detail extension.

LangGraphModelSummaryExtension

Bases: BaseModel

LGOS fields safe to include in a model list.

Model

Bases: BaseModel

Individual model information.

ModelClientSettings

Bases: BaseModel

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(),
            defaults=client_settings.default_values(),
        )

    return ModelDetails(
        id=model,
        created=MODEL_CREATED,
        owned_by=MODEL_OWNER,
        lgos=LangGraphModelExtension(
            **dict(_summary_extension(graph_config)),
            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=_summary_extension(graph_config),
        )
        for name, graph_config in graph_registry.graphs.items()
    ]

    return ModelList(data=models)

views

OpenAI-compatible model listing and retrieval.

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."""
    return models_service.get_model(model, graph_registry)

responses

OpenAI-compatible Responses API.

background

Create, retrieve, and cancel polling-only background Responses.

cancel_background_response async
cancel_background_response(response_id, background, *, checkpoint_scope)

Cancel an active Response; a finished one is returned unchanged.

Source code in src/langgraph_openai_serve/api/responses/background.py
async def cancel_background_response(
    response_id: str,
    background: BackgroundBackend | None,
    *,
    checkpoint_scope: str,
) -> Response:
    """Cancel an active Response; a finished one is returned unchanged."""
    run = await _visible_run(response_id, background, checkpoint_scope)
    if background is not None and run.status in {"queued", "in_progress"}:
        await background.cancel(run.id)
        # A run that finished before the cancel keeps its outcome; one that
        # still reads as active is being cancelled.
        run = await background.get(run.id) or run
        if run.status in {"queued", "in_progress"}:
            run = run.model_copy(update={"status": "cancelled"})
    return run.snapshot()
create_background_response async
create_background_response(
    request, graph_registry, background, *, checkpoint_scope, idempotency_key=None
)

Validate one request and submit it to the background engine.

Proxies and SDKs retry creates after timeouts and 5xx responses. With an Idempotency-Key, a retry returns the original Response instead of starting a second run.

Source code in src/langgraph_openai_serve/api/responses/background.py
async def create_background_response(
    request: ResponseCreateRequest,
    graph_registry: GraphRegistry,
    background: BackgroundBackend | None,
    *,
    checkpoint_scope: str,
    idempotency_key: str | None = None,
) -> Response:
    """
    Validate one request and submit it to the background engine.

    Proxies and SDKs retry creates after timeouts and 5xx responses. With an
    ``Idempotency-Key``, a retry returns the original Response instead of
    starting a second run.
    """
    if background is None:
        message = "Background Responses are not configured for this server."
        raise InvalidRequestError(message, param="background")
    job = _job(
        request,
        graph_registry,
        owner_scope=checkpoint_scope,
        idempotency_key=idempotency_key,
    )
    run = await background.submit(job)
    if run.job.request != job.request:
        # 422 follows the IETF Idempotency-Key draft; SDKs do not retry it.
        message = "This Idempotency-Key was already used with a different request."
        raise InvalidRequestError(
            message,
            param="Idempotency-Key",
            code="idempotency_key_reused",
            status_code=422,
        )
    return run.snapshot()
retrieve_background_response async
retrieve_background_response(response_id, background, *, checkpoint_scope)

Read one authorized Response snapshot.

Source code in src/langgraph_openai_serve/api/responses/background.py
async def retrieve_background_response(
    response_id: str,
    background: BackgroundBackend | None,
    *,
    checkpoint_scope: str,
) -> Response:
    """Read one authorized Response snapshot."""
    run = await _visible_run(response_id, background, checkpoint_scope)
    return run.snapshot()

deps

FastAPI dependencies local to the Responses route.

get_background_backend
get_background_backend(request)

Return the optional server-injected background backend.

Source code in src/langgraph_openai_serve/api/responses/deps.py
def get_background_backend(request: Request) -> BackgroundBackend | None:
    """Return the optional server-injected background backend."""
    return request.app.state.background_backend
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
    if not value:
        msg = "checkpoint_scope must resolve to a non-empty server-trusted string."
        raise GraphError(msg)
    return value
validate_background_retrieval
validate_background_retrieval(*, stream=None, starting_after=None)

Reject streaming and cursors before reading the background engine.

Source code in src/langgraph_openai_serve/api/responses/deps.py
def validate_background_retrieval(
    *,
    stream: bool | None = None,
    starting_after: str | None = None,
) -> None:
    """Reject streaming and cursors before reading the background engine."""
    for param, value in (("stream", stream), ("starting_after", starting_after)):
        if value:
            msg = f"Background response retrieval does not support {param}."
            raise InvalidRequestError(msg, param=param)

events

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

ResponsesEventBuilder
ResponsesEventBuilder(
    request, *, run_id=None, response_id=None, created_at=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,
    response_id: str | None = None,
    created_at: int | None = None,
    server_tools: Collection[str] = (),
) -> None:
    self._context = ResponseContext.for_run(
        request,
        run_id=run_id,
        response_id=response_id,
        created_at=created_at,
    )
    self._sequence_number = 0
    self._output: list[ResponseOutputItem] = []
    self.selected_server_tools = frozenset(server_tools)
    self._server_tool_tracker = ServerToolTracker(self.selected_server_tools)
    self._final_item: _TextItem | None = None
commentary
commentary(text)

Emit one complete commentary message lifecycle.

Yields:

Type Description
ResponseStreamEvent

Typed events for the message lifecycle.

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

    Yields:
        Typed events for the message lifecycle.

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

Create the initial response event.

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

Emit the normative terminal failure sequence.

Yields:

Type Description
ResponseStreamEvent

The error and failed Response events.

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

    Yields:
        The error and failed Response events.

    """
    # Keep the items already exposed to the client in the terminal snapshot.
    # The SDK replaces its accumulated Response with response.failed.
    item = self._final_item
    if (
        item is not None
        and 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 ResponseFailedEvent(
        type="response.failed",
        sequence_number=self._sequence(),
        response=self._response(
            status="failed",
            error=ResponseError(code="server_error", message=message),
        ),
    )
final_delta
final_delta(delta)

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

Yields:

Type Description
ResponseStreamEvent

Typed events that open or update the final message.

Source code in src/langgraph_openai_serve/api/responses/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.

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

    """
    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 ResponseIncompleteEvent(
            type="response.incomplete",
            sequence_number=self._sequence(),
            response=response,
        )
    else:
        yield 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._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):
        yield from self._tool_item(call)
    yield ResponseCompletedEvent(
        type="response.completed",
        sequence_number=self._sequence(),
        response=self._response(status="completed", usage=usage),
    )
in_progress
in_progress()

Create the response in-progress event.

Source code in src/langgraph_openai_serve/api/responses/events.py
def in_progress(self) -> ResponseInProgressEvent:
    """Create the response in-progress event."""
    return ResponseInProgressEvent(
        type="response.in_progress",
        sequence_number=self._sequence(),
        response=self._response(status="in_progress"),
    )
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.

    """
    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, nonce=None)

Create a Response ID that carries its interrupt run identity.

A background Response passes its engine run UUID as nonce, so the ID alone locates that run.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def interrupt_response_id(run_id: str, nonce: str | None = None) -> str:
    """
    Create a Response ID that carries its interrupt run identity.

    A background Response passes its engine run UUID as ``nonce``, so the ID
    alone locates that run.
    """
    suffix = uuid.UUID(nonce).hex if nonce is not None else uuid.uuid4().hex
    return f"{_INTERRUPT_RESPONSE_PREFIX}{uuid.UUID(run_id).hex}_{suffix}"
interrupt_response_nonce
interrupt_response_nonce(response_id)

Return the nonce UUID of an interrupt-style Response ID.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def interrupt_response_nonce(response_id: str) -> str | None:
    """Return the nonce UUID of an interrupt-style Response ID."""
    match = _RESPONSE_ID_PATTERN.fullmatch(response_id)
    return str(uuid.UUID(hex=match.group("nonce"))) if match else None
interrupt_run_id
interrupt_run_id(response_id)

Return the run UUID carried by an interrupt-style Response ID.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def interrupt_run_id(response_id: str) -> str:
    """Return the run UUID carried by an interrupt-style Response ID."""
    match = _RESPONSE_ID_PATTERN.fullmatch(response_id)
    if match is None or uuid.UUID(hex=match.group("run")).int == 0:
        msg = "previous_response_id is not an LGOS interrupt Response ID."
        raise InvalidRequestError(msg, param="previous_response_id")
    return str(uuid.UUID(hex=match.group("run")))
interrupt_tool_call_id
interrupt_tool_call_id(interrupt_id)

Expose one native LangGraph interrupt ID as an OpenAI call ID.

Source code in src/langgraph_openai_serve/api/responses/interrupts.py
def interrupt_tool_call_id(interrupt_id: str) -> str:
    """Expose one native LangGraph interrupt ID as an OpenAI call ID."""
    if not interrupt_id:
        msg = "LangGraph interrupt IDs must be non-empty strings."
        raise ValueError(msg)
    return f"{_INTERRUPT_CALL_PREFIX}{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_resume_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 InvalidRequestError(msg, param="input")

    run_id = interrupt_run_id(previous_response_id)
    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 InvalidRequestError(msg, param="input")
        interrupt_id = _parse_interrupt_tool_call_id(item.call_id)
        if interrupt_id in values:
            msg = "Interrupt function_call_output call_id values must be unique."
            raise InvalidRequestError(msg, param="input")
        values[interrupt_id] = item.output

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

messages

Convert Responses message input into LangChain messages.

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

Stable identity and request fields shared by one response lifecycle.

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

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

A background Response passes the response_id and created_at it was accepted with, so every snapshot keeps one identity.

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

    A background Response passes the ``response_id`` and ``created_at`` it
    was accepted with, so every snapshot keeps one identity.
    """
    if response_id is None:
        response_id = (
            interrupt_response_id(run_id)
            if run_id is not None
            else f"resp_{uuid.uuid4().hex}"
        )
    return cls(
        request=request,
        id=response_id,
        # OpenAI sends whole Unix seconds; strict clients reject fractions.
        created_at=int(time.time()) if created_at is None else created_at,
    )
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["queued", "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": bool(request.background),
            "completed_at": int(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,
            "safety_identifier": request.safety_identifier,
            "service_tier": "default",
            "store": bool(request.store),
            "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,
        }
    )
interrupt_output_items
interrupt_output_items(batch)

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,
) -> list[ResponseFunctionToolCall]:
    """Serialize one durable interrupt batch as function-call items."""
    return [
        _function_call_item(
            call_id=interrupt_tool_call_id(interrupt.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 GraphError(msg)
    if not isinstance(name, str) or not name:
        msg = "The final assistant tool call must include a non-empty name."
        raise GraphError(msg)
    if not isinstance(arguments, dict):
        msg = "The final assistant tool call arguments must be a JSON object."
        raise GraphError(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 GraphError(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 GraphError(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.

decode_graph_request
decode_graph_request(request, graph_config)

Validate and normalize one Responses request for its registered graph.

Source code in src/langgraph_openai_serve/api/responses/request.py
def decode_graph_request(
    request: ResponseCreateRequest,
    graph_config: GraphConfig,
) -> tuple[GraphRequest, list[BaseMessage], InterruptResume | None]:
    """Validate and normalize one Responses request for its registered graph."""
    _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 InvalidRequestError(message, param="previous_response_id")
    return decode_responses_request(request, graph_config.server_tools)
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.safety_identifier or 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
    )

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._call_ids: 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 GraphError(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 GraphError(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, *, response_id=None, created_at=None)

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_id: str | None = None,
    created_at: int | None = None,
) -> Response:
    """Build one non-streaming Response from the graph's durable output."""
    async with run:
        events = _response_events(
            _builder(request, run, response_id=response_id, created_at=created_at),
            run,
            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 RuntimeError(msg)
prepare_response_run async
prepare_response_run(request, graph_registry, *, checkpoint_scope, run_id=None)

Validate a Responses request and prepare its graph run.

A background run passes the interrupt run_id it chose at submission.

Source code in src/langgraph_openai_serve/api/responses/service.py
async def prepare_response_run(
    request: ResponseCreateRequest,
    graph_registry: GraphRegistry,
    *,
    checkpoint_scope: str,
    run_id: str | None = None,
) -> GraphRun:
    """
    Validate a Responses request and prepare its graph run.

    A background run passes the interrupt ``run_id`` it chose at submission.
    """
    graph_request, messages, resume = decode_graph_request(
        request,
        graph_registry.get_graph(request.model),
    )
    return await prepare_run(
        graph_request,
        messages,
        graph_registry,
        resume=resume,
        run_id=run_id,
        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.

    """
    builder = _builder(request, run)
    events = _response_events(builder, run, streaming=True)
    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.

cancel_response async
cancel_response(response_id, checkpoint_scope, background)

Cancel one authorized active background Response.

Source code in src/langgraph_openai_serve/api/responses/views.py
@router.post("/responses/{response_id}/cancel")
async def cancel_response(
    response_id: str,
    checkpoint_scope: Annotated[str, Depends(get_checkpoint_scope)],
    background: Annotated[
        BackgroundBackend | None,
        Depends(get_background_backend),
    ],
) -> Response:
    """Cancel one authorized active background Response."""
    return await responses_background.cancel_background_response(
        response_id,
        background,
        checkpoint_scope=checkpoint_scope,
    )
create_response async
create_response(
    response_request,
    graph_registry,
    checkpoint_scope,
    background,
    stream_owner,
    idempotency_key=None,
)

Create one OpenAI Response, as JSON, an SSE stream, or a background run.

Source code in src/langgraph_openai_serve/api/responses/views.py
@router.post("/responses", response_model=Response)
async def create_response(  # ruff: ignore[too-many-arguments, too-many-positional-arguments] - Explicit FastAPI dependencies.
    response_request: ResponseCreateRequest,
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry)],
    checkpoint_scope: Annotated[str, Depends(get_checkpoint_scope)],
    background: Annotated[
        BackgroundBackend | None,
        Depends(get_background_backend),
    ],
    stream_owner: Annotated[
        StreamOwner,
        Depends(get_stream_owner, scope="request"),
    ],
    idempotency_key: Annotated[
        str | None,
        Header(alias="Idempotency-Key"),
    ] = None,
) -> StreamingResponse | Response:
    """Create one OpenAI Response, as JSON, an SSE stream, or a background run."""
    bind_log_context(model=response_request.model, stream=response_request.stream)
    if response_request.background:
        return await responses_background.create_background_response(
            response_request,
            graph_registry,
            background,
            checkpoint_scope=checkpoint_scope,
            idempotency_key=idempotency_key,
        )
    run = await responses_service.prepare_response_run(
        response_request,
        graph_registry,
        checkpoint_scope=checkpoint_scope,
    )
    if response_request.stream:
        return stream_owner.start(
            responses_service.stream_response(response_request, run),
            run,
        )
    return await responses_service.collect_response(response_request, run)
retrieve_response async
retrieve_response(response_id, checkpoint_scope, background)

Retrieve one authorized background Response snapshot.

Source code in src/langgraph_openai_serve/api/responses/views.py
@router.get(
    "/responses/{response_id}",
    dependencies=[Depends(validate_background_retrieval)],
)
async def retrieve_response(
    response_id: str,
    checkpoint_scope: Annotated[str, Depends(get_checkpoint_scope)],
    background: Annotated[
        BackgroundBackend | None,
        Depends(get_background_backend),
    ],
) -> Response:
    """Retrieve one authorized background Response snapshot."""
    return await responses_background.retrieve_background_response(
        response_id,
        background,
        checkpoint_scope=checkpoint_scope,
    )

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. The producer runs as a TaskStream, which request cleanup stops whether or not Starlette is still consuming it.

StreamOwner

StreamOwner(keepalive_interval=_KEEPALIVE_INTERVAL)

Own the producer task and prepared run of one streaming request.

Source code in src/langgraph_openai_serve/api/streaming.py
def __init__(self, keepalive_interval: float = _KEEPALIVE_INTERVAL) -> None:
    self._keepalive_interval = keepalive_interval
    self._stream: TaskStream[str] | None = None
    self._run: GraphRun | None = None
aclose async
aclose()

Stop the producer, then close the run it may not have reached.

Source code in src/langgraph_openai_serve/api/streaming.py
async def aclose(self) -> None:
    """Stop the producer, then close the run it may not have reached."""
    with CancelScope(shield=True):
        try:
            if self._stream is not None:
                await self._stream.aclose()
        finally:
            if self._run is not None:
                await self._run.aclose()
start
start(source, run)

Stream source as SSE and close run when the request ends.

Source code in src/langgraph_openai_serve/api/streaming.py
def start(
    self,
    source: AsyncGenerator[str, None],
    run: GraphRun,
) -> StreamingResponse:
    """Stream ``source`` as SSE and close ``run`` when the request ends."""
    self._run = run
    self._stream = TaskStream(source, name="openai-response-stream")
    return StreamingResponse(
        self._with_keepalive(self._stream.receive),
        media_type="text/event-stream",
        headers=_SSE_HEADERS,
    )

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)

background

Polling-only background Responses executed by a pluggable engine.

BackgroundBackend

Bases: Protocol

An engine that runs background jobs and stores their status and result.

cancel async

cancel(run_id)

Stop one run; a finished run keeps its outcome.

Source code in src/langgraph_openai_serve/background.py
async def cancel(self, run_id: str) -> None:
    """Stop one run; a finished run keeps its outcome."""
    ...

get async

get(run_id)

Read one run, or return None when the engine does not have it.

Source code in src/langgraph_openai_serve/background.py
async def get(self, run_id: str) -> BackgroundRun | None:
    """Read one run, or return None when the engine does not have it."""
    ...

submit async

submit(job)

Start job, or return the run already holding its idempotency key.

Source code in src/langgraph_openai_serve/background.py
async def submit(self, job: BackgroundJob) -> BackgroundRun:
    """Start ``job``, or return the run already holding its idempotency key."""
    ...

BackgroundJob

Bases: BaseModel

A validated request and the server-owned context that executes it.

BackgroundRun

Bases: BaseModel

An engine's current view of one submitted job.

response_id property

response_id

Public ID that locates this run and continues its interrupts.

snapshot

snapshot()

Return the public Response for the run's current state.

Source code in src/langgraph_openai_serve/background.py
def snapshot(self) -> Response:
    """Return the public Response for the run's current state."""
    if self.response is not None:
        return Response.model_validate(self.response)
    response = _queued_response(self.job, self.response_id)
    if self.status in {"completed", "failed"}:
        return _failed_response(response, "The background response failed.")
    return response.model_copy(update={"status": self.status})

InMemoryBackgroundBackend

InMemoryBackgroundBackend(registry)

Run background jobs as tasks of this application process.

For development and tests: runs stay in memory until the process exits.

Source code in src/langgraph_openai_serve/background.py
def __init__(self, registry: GraphRegistry) -> None:
    self._registry = registry
    self._runs: dict[str, BackgroundRun] = {}
    self._keys: dict[str, str] = {}
    self._scopes: dict[str, CancelScope] = {}
    self._tasks: TaskGroup | None = None

cancel async

cancel(run_id)

Mark an active run cancelled and cancel its task.

Source code in src/langgraph_openai_serve/background.py
async def cancel(self, run_id: str) -> None:
    """Mark an active run cancelled and cancel its task."""
    if run_id in self._runs:
        self._transition(run_id, "cancelled")
    if (scope := self._scopes.get(run_id)) is not None:
        scope.cancel()

get async

get(run_id)

Read one run.

Source code in src/langgraph_openai_serve/background.py
async def get(self, run_id: str) -> BackgroundRun | None:
    """Read one run."""
    return self._runs.get(run_id)

lifespan async

lifespan(_app)

Own the execution tasks for one ASGI application lifespan.

Source code in src/langgraph_openai_serve/background.py
@asynccontextmanager
async def lifespan(self, _app: object) -> AsyncGenerator[None, None]:
    """Own the execution tasks for one ASGI application lifespan."""
    async with create_task_group() as tasks:
        self._tasks = tasks
        try:
            yield
        finally:
            self._tasks = None
            tasks.cancel_scope.cancel()

submit async

submit(job)

Start one task, or return the run holding the job's key.

Source code in src/langgraph_openai_serve/background.py
async def submit(self, job: BackgroundJob) -> BackgroundRun:
    """Start one task, or return the run holding the job's key."""
    if self._tasks is None:
        msg = "The in-memory background backend lifespan is not running."
        raise RuntimeError(msg)
    if (run_id := self._keys.get(job.idempotency_key)) is not None:
        return self._runs[run_id]
    run = BackgroundRun(id=str(uuid.uuid4()), job=job, status="queued")
    self._runs[run.id] = run
    self._keys[job.idempotency_key] = run.id
    self._scopes[run.id] = CancelScope()
    self._tasks.start_soon(self._execute, run)
    return run

execute_background_job async

execute_background_job(job, engine_run_id, registry)

Execute one job through the foreground Responses path.

Source code in src/langgraph_openai_serve/background.py
async def execute_background_job(
    job: BackgroundJob,
    engine_run_id: str,
    registry: GraphRegistry,
) -> dict[str, JsonValue]:
    """Execute one job through the foreground Responses path."""
    request = ResponseCreateRequest.model_validate(job.request)
    response_id = interrupt_response_id(job.run_id, engine_run_id)
    try:
        run = await prepare_response_run(
            request,
            registry,
            checkpoint_scope=job.owner_scope,
            run_id=job.run_id,
        )
        response = await collect_response(
            request,
            run,
            response_id=response_id,
            created_at=job.created_at,
        )
    except InvalidRequestError as exc:
        # The foreground path reports these as 4xx; a background Response
        # reports them as its own failure. An interrupt answer, for example,
        # is validated only once its run holds the lease, so a stale answer
        # fails here.
        response = _failed_response(_queued_response(job, response_id), str(exc))
    return response.model_dump(mode="json", by_alias=True)

cli

The lgos command: serve a graph registry or run its Hatchet worker.

main

main()

Serve a graph registry or run its Hatchet worker.

Source code in src/langgraph_openai_serve/cli.py
@click.group()
@click.version_option(package_name="langgraph_openai_serve")
def main() -> None:
    """Serve a graph registry or run its Hatchet worker."""

serve

serve(registry, log_config, **options)

Serve REGISTRY ($LGOS_REGISTRY), a module:attribute factory, with Uvicorn.

Source code in src/langgraph_openai_serve/cli.py
@main.command(
    params=[
        _REGISTRY,
        *(
            param
            for param in uvicorn.main.params
            if param.name not in {"app", "factory"}
        ),
    ],
    context_settings={
        "auto_envvar_prefix": "UVICORN",
        # Flags and UVICORN_* variables override these LGOS defaults.
        "default_map": {"access_log": False},
    },
)
def serve(registry: str, log_config: str | None, **options: Any) -> None:
    """Serve REGISTRY ($LGOS_REGISTRY), a module:attribute factory, with Uvicorn."""
    try:
        from langgraph_openai_serve.server.logging import logging_config
        from langgraph_openai_serve.server.settings import ServerSettings
    except ModuleNotFoundError as exc:
        raise _missing_server_extra(exc) from exc
    # Uvicorn's worker count: the option, else WEB_CONCURRENCY, else one.
    workers = options["workers"] or int(os.getenv("WEB_CONCURRENCY", "1"))
    # Uvicorn loads --env-file only once it starts, so read it here as well.
    settings = ServerSettings(_env_file=options["env_file"])
    if workers > 1 and settings.BACKGROUND == "memory":
        msg = (
            "LGOS_BACKGROUND=memory keeps background Responses in one process; "
            "run one worker or set LGOS_BACKGROUND=hatchet."
        )
        raise click.UsageError(msg)
    # Uvicorn imports the app again in its worker and reload processes, so the
    # registry travels through the environment they inherit.
    os.environ["LGOS_REGISTRY"] = registry
    click.get_current_context().invoke(
        uvicorn.main,
        app=f"{__name__}:{_create_app.__name__}",
        factory=True,
        log_config=log_config
        or logging_config(_application(registry), root_level=logging.WARNING),
        **options,
    )

worker

worker(registry)

Run background Responses for REGISTRY ($LGOS_REGISTRY) on Hatchet.

Source code in src/langgraph_openai_serve/cli.py
@main.command(params=[_REGISTRY])
def worker(registry: str) -> None:
    """Run background Responses for REGISTRY ($LGOS_REGISTRY) on Hatchet."""
    try:
        from langgraph_openai_serve.server.hatchet import run_worker
        from langgraph_openai_serve.server.logging import configure_logging
        from langgraph_openai_serve.server.settings import ServerSettings
    except ModuleNotFoundError as exc:
        raise _missing_server_extra(exc) from exc
    # Like Uvicorn, resolve modules from the working directory first.
    sys.path.insert(0, "")
    factory = pkgutil.resolve_name(registry)
    # Hatchet forwards task logs at the root logger's level.
    configure_logging(_application(registry), root_level=logging.INFO)
    run_worker(factory, ServerSettings())

core

errors

OpenAI-compatible error responses.

GraphError

Bases: RuntimeError

Raised when a graph's configuration or output cannot be served.

InvalidRequestError

InvalidRequestError(
    message, *, param=None, code=None, status_code=status.HTTP_400_BAD_REQUEST
)

Bases: Exception

A request rejected with an OpenAI invalid_request_error envelope.

Source code in src/langgraph_openai_serve/core/errors.py
def __init__(
    self,
    message: str,
    *,
    param: str | None = None,
    code: str | None = None,
    status_code: int = status.HTTP_400_BAD_REQUEST,
) -> None:
    super().__init__(message)
    self.param = param
    self.code = code
    self.status_code = status_code

configure_openai_error_handlers

configure_openai_error_handlers(app)

Return every error from app as an OpenAI-compatible JSON envelope.

Source code in src/langgraph_openai_serve/core/errors.py
def configure_openai_error_handlers(app: FastAPI) -> None:
    """Return every error from ``app`` as an OpenAI-compatible JSON envelope."""
    for exc_class in (
        InvalidRequestError,
        GraphError,
        ContextOverflowError,
        HTTPException,
        RequestValidationError,
        Exception,
    ):
        app.add_exception_handler(exc_class, _openai_error_response)

openai_error_payload

openai_error_payload(error)

Create OpenAI error payload.

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

logging

Request-scoped context for standard-library log records.

RequestContextFilter

RequestContextFilter()

Bases: Filter

Add the active LGOS request fields to log records.

LGOS installs it on its own loggers. A host that installs it on a handler also enriches records from graph nodes, dependencies, and its own code.

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 GraphError(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 GraphError(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 needs a deterministic default so model discovery can advertise a complete, stable settings object.

default_values classmethod
default_values()

Return the JSON defaults advertised by model discovery.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def default_values(cls) -> dict[str, JsonValue]:
    """Return the JSON defaults advertised by model discovery."""
    # Aliases are the public names in the schema and request validation.
    return cls().model_dump(mode="json", by_alias=True)
json_schema classmethod
json_schema()

Return the JSON Schema advertised by model discovery.

Source code in src/langgraph_openai_serve/graph/client_settings.py
@classmethod
def json_schema(cls) -> dict[str, JsonValue]:
    """Return the JSON Schema advertised by model discovery."""
    return {"$schema": JSON_SCHEMA_DIALECT, **cls.model_json_schema()}
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."""
    try:
        return cls.model_validate_json(
            request.metadata.get(SETTINGS_METADATA_KEY, "{}")
        )
    except ValidationError as exc:
        error = exc.errors()[0]
        field = ".".join(str(part) for part in error["loc"])
        label = f"runtime setting for {field}" if field else "runtime settings"
        msg = f"Invalid {label}: {error['msg']}"
        raise InvalidRequestError(
            msg,
            param=f"metadata.{SETTINGS_METADATA_KEY}",
        ) from exc

events

Status updates that graph nodes publish as Responses commentary.

status_description

status_description(value)

Return a status update's description, ignoring other custom stream data.

Source code in src/langgraph_openai_serve/graph/events.py
def status_description(value: object) -> str | None:
    """Return a status update's description, ignoring other custom stream data."""
    if not isinstance(value, dict) or value.get("type") != STATUS_EVENT_TYPE:
        return None
    description = value.get("description")
    return description if isinstance(description, str) and description else None

status_event

status_event(description)

Build a status update for a node to write with get_stream_writer().

Source code in src/langgraph_openai_serve/graph/events.py
6
7
8
def status_event(description: str) -> dict[str, str]:
    """Build a status update for a node to write with ``get_stream_writer()``."""
    return {"type": STATUS_EVENT_TYPE, "description": description}

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 GraphError(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 GraphError(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 GraphError(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):
        return self.graph
    return _validate_resolved_graph(await _maybe_await(self.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)

Fail at registration when a settings model cannot be advertised.

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:
    """Fail at registration when a settings model cannot be advertised."""
    if value is not None:
        value.json_schema()
        value.default_values()
    return value
validate_compiled_graph
validate_compiled_graph()

Fail at registration when a directly supplied graph cannot be served.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@model_validator(mode="after")
def validate_compiled_graph(self) -> Self:
    """Fail at registration when a directly supplied graph cannot be served."""
    if isinstance(self.graph, CompiledStateGraph):
        _validate_resolved_graph(self.graph, self)
    return self
validate_tool_ownership
validate_tool_ownership()

Keep the side that executes each advertised tool unambiguous.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
@model_validator(mode="after")
def validate_tool_ownership(self) -> Self:
    """Keep the side that executes each advertised tool unambiguous."""
    if shared := self.server_tools & self.client_tools:
        names = ", ".join(sorted(shared))
        msg = f"Tools cannot be both server and client tools: {names}"
        raise ValueError(msg)
    return self

GraphRegistry dataclass

GraphRegistry(graphs, run_coordinator=None)

The graphs served as OpenAI models, keyed by model ID.

run_coordinator leases the interrupt runs of every interrupt-enabled graph; use a shared coordinator when several processes serve these graphs.

__post_init__
__post_init__()

Fail at startup for a registry the OpenAI routes cannot serve.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def __post_init__(self) -> None:
    """Fail at startup for a registry the OpenAI routes cannot serve."""
    if not self.graphs:
        msg = "GraphRegistry must contain at least one graph."
        raise ValueError(msg)
    for model_id in self.graphs:
        # Each model must be addressable as GET /models/{model}.
        if not model_id or "/" in model_id or model_id in {".", ".."}:
            msg = f"Model ID {model_id!r} is not addressable."
            raise ValueError(msg)
    if self.run_coordinator is None and any(
        config.supports(GraphFeature.INTERRUPTS) for config in self.graphs.values()
    ):
        msg = "Interrupt-enabled graphs need a GraphRegistry run_coordinator."
        raise ValueError(msg)
get_graph
get_graph(model)

Return the graph served as model.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
def get_graph(self, model: str) -> GraphConfig:
    """Return the graph served as ``model``."""
    try:
        return self.graphs[model]
    except KeyError as exc:
        msg = f"The model '{model}' does not exist."
        raise InvalidRequestError(
            msg,
            param="model",
            code="model_not_found",
            status_code=404,
        ) from exc

interrupt

Durable LangGraph interrupts: run leases, run identity, and resume state.

InMemoryRunCoordinator

InMemoryRunCoordinator()

Coordinate interrupt runs within one event loop.

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

Hold the lease for key or raise RunBusyError.

Source code in src/langgraph_openai_serve/graph/interrupt.py
@asynccontextmanager
async def __call__(self, key: str, /) -> AsyncGenerator[None, None]:
    """Hold the lease for ``key`` or raise ``RunBusyError``."""
    if key in self._active:
        raise RunBusyError
    self._active.add(key)
    try:
        yield
    finally:
        self._active.discard(key)

InterruptResume dataclass

InterruptResume(run_id, values)

A complete set of interrupt answers for one run.

LangGraphInterruptBatch dataclass

LangGraphInterruptBatch(run_id, interrupts)

The durable interrupts awaiting answers for one graph run.

RunBusyError

RunBusyError()

Bases: InvalidRequestError

Raised when another request holds an interrupt run's lease.

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

RunCoordinator

Bases: Protocol

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

Exiting the lease must release it even when the exit is cancelled, because run cleanup is abandoned after a deadline; shield an asynchronous release.

__call__
__call__(key)

Hold the lease for key or raise RunBusyError.

Source code in src/langgraph_openai_serve/graph/interrupt.py
def __call__(self, key: str, /) -> AbstractAsyncContextManager[None]:
    """Hold the lease for ``key`` or raise ``RunBusyError``."""
    ...

checkpoint_key

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

Derive a fixed-length checkpoint thread ID for one scoped model run.

Source code in src/langgraph_openai_serve/graph/interrupt.py
def checkpoint_key(model: str, run_id: str, *, scope: str = "default") -> str:
    """Derive a fixed-length checkpoint thread ID for one scoped model run."""
    identity = json.dumps(
        ["langgraph-openai-serve.interrupt", scope, model, run_id],
        ensure_ascii=False,
        separators=(",", ":"),
    )
    return hashlib.sha256(identity.encode()).hexdigest()

delete_expired_interrupt_runs async

delete_expired_interrupt_runs(checkpointer, run_coordinator, *, older_than)

Delete paused interrupt runs whose latest pause is older than older_than.

Other threads in checkpointer are left alone, and a run whose lease is held, such as one being resumed, is skipped until the next call.

Returns:

Type Description
int

The number of deleted runs.

Source code in src/langgraph_openai_serve/graph/interrupt.py
async def delete_expired_interrupt_runs(
    checkpointer: BaseCheckpointSaver,
    run_coordinator: RunCoordinator,
    *,
    older_than: timedelta,
) -> int:
    """
    Delete paused interrupt runs whose latest pause is older than ``older_than``.

    Other threads in ``checkpointer`` are left alone, and a run whose lease is
    held, such as one being resumed, is skipped until the next call.

    Returns:
        The number of deleted runs.

    """
    cutoff = datetime.now(UTC) - older_than
    paused_at = await _latest_pauses(checkpointer.alist(None))

    deleted = 0
    for thread_id, timestamp in paused_at.items():
        if timestamp >= cutoff:
            continue
        try:
            async with run_coordinator(thread_id):
                # The run may have been resumed, or deleted, since it was listed.
                latest = await _latest_pauses(
                    checkpointer.alist({"configurable": {"thread_id": thread_id}})
                )
                if latest.get(thread_id, cutoff) >= cutoff:
                    continue
                await checkpointer.adelete_thread(thread_id)
        except RunBusyError:
            continue
        deleted += 1
    return deleted

interrupt_batch

interrupt_batch(interrupts, run_id)

Validate pending interrupts as JSON function-call arguments.

Source code in src/langgraph_openai_serve/graph/interrupt.py
def interrupt_batch(
    interrupts: Iterable[Interrupt],
    run_id: str,
) -> LangGraphInterruptBatch:
    """Validate pending interrupts as JSON function-call arguments."""
    pending = tuple(interrupts)
    for item in pending:
        if not isinstance(item.value, dict):
            msg = "LangGraph interrupt payloads must be JSON objects."
            raise GraphError(msg)
        try:
            json.dumps(item.value, allow_nan=False)
        except (TypeError, ValueError) as exc:
            msg = "LangGraph interrupt payloads must be valid JSON values."
            raise GraphError(msg) from exc
    return LangGraphInterruptBatch(run_id=run_id, interrupts=pending)

resume_command async

resume_command(graph, config, resume)

Validate answers against the run's durable pending interrupts.

Source code in src/langgraph_openai_serve/graph/interrupt.py
async def resume_command(
    graph: CompiledStateGraph,
    config: RunnableConfig,
    resume: InterruptResume,
) -> Command:
    """Validate answers against the run's durable pending interrupts."""
    snapshot = await graph.aget_state(config, subgraphs=True)
    if not snapshot.interrupts:
        msg = "This run has no pending interrupts."
        raise _state_conflict(msg)
    if set(resume.values) != {item.id for item in snapshot.interrupts}:
        msg = "Interrupt results do not match the complete pending interrupt set."
        raise _state_conflict(msg)
    # Native ID/value resumes answer parallel interrupts without replaying input.
    return Command(resume=resume.values)

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.

run

Prepare one isolated LangGraph execution for the OpenAI API.

GraphRun dataclass

GraphRun(
    request,
    config,
    graph,
    inputs,
    context,
    runnable_config,
    usage_callback,
    interrupt=None,
    _resources=AsyncExitStack(),
)

One prepared graph run and the resources it holds until closed.

An interrupt-enabled run holds its coordinator lease from preparation until aclose(). Closing deletes the checkpoint thread of a run that executed without pausing, so durable state exists only for pending interrupts.

__aenter__ async
__aenter__()

Return this run; exiting closes it.

Source code in src/langgraph_openai_serve/graph/run.py
async def __aenter__(self) -> Self:
    """Return this run; exiting closes it."""
    return self
__aexit__ async
__aexit__(_exc_type, exc, _traceback)

Close this run.

Source code in src/langgraph_openai_serve/graph/run.py
async def __aexit__(
    self,
    _exc_type: type[BaseException] | None,
    exc: BaseException | None,
    _traceback: TracebackType | None,
) -> None:
    """Close this run."""
    await self.aclose(error=exc)
aclose async
aclose(*, error=None)

Apply checkpoint cleanup and release the lease; later calls do nothing.

When error ended the run, a cleanup failure is logged instead of replacing it.

Source code in src/langgraph_openai_serve/graph/run.py
async def aclose(self, *, error: BaseException | None = None) -> None:
    """
    Apply checkpoint cleanup and release the lease; later calls do nothing.

    When ``error`` ended the run, a cleanup failure is logged instead of
    replacing it.
    """
    try:
        await self._cleanup()
    except Exception:
        if error is None:
            raise
        logger.exception("graph_run.cleanup_failed")
begin_execution
begin_execution()

Mark checkpoint state for deletion unless the run later pauses.

Source code in src/langgraph_openai_serve/graph/run.py
def begin_execution(self) -> None:
    """Mark checkpoint state for deletion unless the run later pauses."""
    self._delete_checkpoint = self.interrupt is not None
hold async
hold(lease)

Hold lease until this run closes.

Source code in src/langgraph_openai_serve/graph/run.py
async def hold(self, lease: AbstractAsyncContextManager[None]) -> None:
    """Hold ``lease`` until this run closes."""
    await self._resources.enter_async_context(lease)
keep_checkpoint
keep_checkpoint()

Preserve the checkpoint of a run that paused on interrupts.

Source code in src/langgraph_openai_serve/graph/run.py
def keep_checkpoint(self) -> None:
    """Preserve the checkpoint of a run that paused on interrupts."""
    self._delete_checkpoint = False
langfuse_attributes
langfuse_attributes()

Name this run's end user, session, and graph on its Langfuse observations.

Enter it where the graph's task starts. LangChain runs Langfuse's handler on worker threads with a copy of the caller's context, so the handler's own propagation reaches only the root observation, and the model calls carrying the run's cost would have no user (https://github.com/langfuse/langfuse/issues/16177).

Source code in src/langgraph_openai_serve/graph/run.py
def langfuse_attributes(self) -> AbstractContextManager[object]:
    """
    Name this run's end user, session, and graph on its Langfuse observations.

    Enter it where the graph's task starts. LangChain runs Langfuse's
    handler on worker threads with a copy of the caller's context, so the
    handler's own propagation reaches only the root observation, and the
    model calls carrying the run's cost would have no user
    (https://github.com/langfuse/langfuse/issues/16177).
    """
    if not settings.ENABLE_LANGFUSE:
        return nullcontext()
    return trace_attributes(
        user_id=self.request.user or None,
        session_id=self.request.metadata.get(CONVERSATION_METADATA_KEY) or None,
        trace_name=self.request.model,
    )
usage_metadata
usage_metadata()

Return provider-reported usage aggregated across the graph run.

Source code in src/langgraph_openai_serve/graph/run.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

InterruptRun dataclass

InterruptRun(run_id, thread_id)

The durable identity of one interrupt-enabled run.

prepare_run async

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

Resolve, lease, and build the inputs of one graph run.

An interrupt-enabled run continues resume.run_id, uses a server-chosen run_id, or starts a new run.

Source code in src/langgraph_openai_serve/graph/run.py
async def prepare_run(  # ruff: ignore[too-many-arguments] - Background runs choose their run_id before execution.
    request: GraphRequest,
    messages: list[BaseMessage],
    graph_registry: GraphRegistry,
    *,
    resume: InterruptResume | None = None,
    run_id: str | None = None,
    checkpoint_scope: str = "default",
) -> GraphRun:
    """
    Resolve, lease, and build the inputs of one graph run.

    An interrupt-enabled run continues ``resume.run_id``, uses a server-chosen
    ``run_id``, or starts a new run.
    """
    config = graph_registry.get_graph(request.model)
    graph = await config.resolve_graph()
    # A server-chosen run without a resume is a background job. When its worker
    # dies, the engine runs the job again, and it starts over.
    restart = resume is None and run_id is not None
    interrupt_run = None
    if config.supports(GraphFeature.INTERRUPTS):
        if resume is not None:
            run_id = resume.run_id
        run_id = run_id or str(uuid.uuid4())
        bind_log_context(operation_id=run_id)
        interrupt_run = InterruptRun(
            run_id=run_id,
            thread_id=checkpoint_key(request.model, run_id, scope=checkpoint_scope),
        )

    usage_callback = UsageMetadataCallbackHandler()
    runnable_config = _runnable_config(
        request,
        config.runtime_callbacks,
        usage_callback,
        interrupt_run,
    )
    run = GraphRun(
        request=request,
        config=config,
        graph=graph,
        inputs=None,
        context=None,
        runnable_config=runnable_config,
        usage_callback=usage_callback,
        interrupt=interrupt_run,
    )
    try:
        await _prepare_inputs(
            run, request, messages, resume, graph_registry.run_coordinator
        )
        if restart and run.interrupt is not None:
            checkpointer = cast("BaseCheckpointSaver", run.graph.checkpointer)
            if await checkpointer.aget_tuple(run.runnable_config) is not None:
                await checkpointer.adelete_thread(run.interrupt.thread_id)
    except BaseException as exc:
        await run.aclose(error=exc)
        raise
    return run

runner

Run LangGraph workflows from protocol-neutral requests and messages.

TaskStream

TaskStream(source, *, name)

Bases: Generic[_Item]

Produce an async generator's items from a dedicated asyncio task.

AnyIO cancels a scope again at every await, and LangGraph then leaves node tasks running once it runs them in the background: in parallel steps, or while a stream reads message or custom events or subgraphs. The producer task instead receives exactly one asyncio cancellation, however its consumer is cancelled.

Source code in src/langgraph_openai_serve/graph/runner.py
def __init__(self, source: AsyncGenerator[_Item, None], *, name: str) -> None:
    # An unbuffered handoff propagates consumer backpressure into the source.
    self._send, self.receive = create_memory_object_stream[_Item](max_buffer_size=0)

    async def produce() -> None:
        async with self._send, aclosing(source):
            async for item in source:
                await self._send.send(item)

    self._producer = asyncio.create_task(produce(), name=name)
aclose async
aclose()

Stop the producer, then raise the exception that ended it, if any.

Source code in src/langgraph_openai_serve/graph/runner.py
async def aclose(self) -> None:
    """Stop the producer, then raise the exception that ended it, if any."""
    with CancelScope(shield=True):
        try:
            self._producer.cancel()
            # Like asyncio.TaskGroup, also ignore a producer that cancelled
            # itself: re-raised, that would read as the consumer's cancellation.
            # LangGraph raises NodeCancelledError for a node that cancels itself.
            with suppress(asyncio.CancelledError):
                await self._producer
        finally:
            self._send.close()
            self.receive.close()

collect_run async

collect_run(run)

Execute a prepared run and return only its final output.

Source code in src/langgraph_openai_serve/graph/runner.py
async def collect_run(run: GraphRun) -> LangGraphOutput:
    """Execute a prepared run and return only its final output."""
    events = stream_run(run, streaming=False)
    async with aclosing(events):
        async for event in events:
            if isinstance(event, (AIMessage, interrupt.LangGraphInterruptBatch)):
                return event
    msg = "LangGraph run completed without a final output."
    raise RuntimeError(msg)

run_langgraph async

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

Prepare, execute, and close one graph run for direct Python callers.

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 final assistant message or the pending interrupt batch.

Source code in src/langgraph_openai_serve/graph/runner.py
async def run_langgraph(
    request: GraphRequest,
    messages: list[BaseMessage],
    graph_registry: GraphRegistry,
    *,
    resume: interrupt.InterruptResume | None = None,
    checkpoint_scope: str = "default",
) -> LangGraphOutput:
    """
    Prepare, execute, and close one graph run for direct Python callers.

    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 final assistant message or the pending interrupt batch.

    """
    run = await prepare_run(
        request,
        messages,
        graph_registry,
        resume=resume,
        checkpoint_scope=checkpoint_scope,
    )
    async with run:
        return await collect_run(run)

run_langgraph_stream async

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

Prepare, stream, and close one graph run for direct Python callers.

Yields:

Type Description
AsyncGenerator[LangGraphStreamEvent, None]

Assistant text chunks, custom events, then the final output.

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.InterruptResume | None = None,
    checkpoint_scope: str = "default",
) -> AsyncGenerator[LangGraphStreamEvent, None]:
    """
    Prepare, stream, and close one graph run for direct Python callers.

    Yields:
        Assistant text chunks, custom events, then the final output.

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

stream_run async

stream_run(run, *, streaming=True, stream_updates=False)

Execute a prepared run, ending with its final output.

Parameters:

Name Type Description Default
run GraphRun

The prepared run.

required
streaming bool

Yield assistant text and custom events, including those of nested subgraphs.

True
stream_updates bool

Yield node updates; with streaming, nested subgraph updates are yielded too.

False

Yields:

Type Description
AsyncGenerator[LangGraphStreamEvent, None]

The requested intermediate events, then the final AIMessage or

AsyncGenerator[LangGraphStreamEvent, None]

LangGraphInterruptBatch.

Source code in src/langgraph_openai_serve/graph/runner.py
async def stream_run(
    run: GraphRun,
    *,
    streaming: bool = True,
    stream_updates: bool = False,
) -> AsyncGenerator[LangGraphStreamEvent, None]:
    """
    Execute a prepared run, ending with its final output.

    Args:
        run: The prepared run.
        streaming: Yield assistant text and custom events, including those of
            nested subgraphs.
        stream_updates: Yield node updates; with ``streaming``, nested subgraph
            updates are yielded too.

    Yields:
        The requested intermediate events, then the final ``AIMessage`` or
        ``LangGraphInterruptBatch``.

    """
    run.begin_execution()
    # Without streaming, request only root values, exactly like ainvoke().
    stream_mode: list[StreamMode] = ["values"]
    if streaming:
        stream_mode += ["messages", "custom"]
    if stream_updates:
        stream_mode.append("updates")

    with invoke_workflow(run.request) as span:
        final_output: Any = _MISSING
        interrupts: dict[str, Interrupt] = {}
        # LangGraph implements astream 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_mode,
                subgraphs=streaming,
                output_keys=run.graph.output_channels,
                # Persist interrupt runs only when they pause or exit.
                durability="exit" if run.interrupt is not None else None,
                version="v2",
            ),
        )
        # Graph work runs under the span's context, not the span itself: its spans
        # are children, while code inside the graph, such as Langfuse's handler,
        # cannot write onto the span. asyncio copies this context into the
        # producer task, so the consumer's context does not change.
        graph_parent = NonRecordingSpan(span.get_span_context())
        with trace.use_span(graph_parent), run.langfuse_attributes():
            parts = TaskStream(graph_stream, name="langgraph-astream")
        try:
            async for part in parts.receive:
                if part["type"] == "values":
                    if not part["ns"]:
                        final_output = part["data"]
                        interrupts.update(
                            (item.id, item) for item in part["interrupts"]
                        )
                elif (event := _visible_event(part)) is not None:
                    yield event
        finally:
            await parts.aclose()

        output: LangGraphOutput
        if interrupts:
            output = _interrupt_batch(run, interrupts.values())
        elif final_output is _MISSING:
            msg = "LangGraph stream completed without a final value."
            raise RuntimeError(msg)
        else:
            with trace.use_span(graph_parent):
                message = await run.config.render_output(final_output)
            usage = run.usage_metadata()
            output = (
                message.model_copy(update={"usage_metadata": usage})
                if usage
                else message
            )
    # The run ends with its output; the consumer's handling of it is not part of
    # the workflow.
    yield output

telemetry

Report graph runs with the OpenTelemetry GenAI workflow conventions.

LGOS uses only the OpenTelemetry API, so nothing is recorded until the application configures an SDK.

invoke_workflow

invoke_workflow(request)

Record one graph execution as an invoke_workflow span and duration.

A run that ends before its output exists fails with its error.type: an exception, a cancellation from a disconnecting client or a cancel request, or a consumer closing the stream mid-run.

Yields:

Type Description
Span

The run's span, which callers make current only around graph work.

Source code in src/langgraph_openai_serve/graph/telemetry.py
@contextmanager
def invoke_workflow(request: GraphRequest) -> Generator[Span, None, None]:
    """
    Record one graph execution as an ``invoke_workflow`` span and duration.

    A run that ends before its output exists fails with its ``error.type``:
    an exception, a cancellation from a disconnecting client or a cancel
    request, or a consumer closing the stream mid-run.

    Yields:
        The run's span, which callers make current only around graph work.

    """
    attributes = {
        "gen_ai.operation.name": "invoke_workflow",
        "gen_ai.workflow.name": request.model,
    }
    if conversation_id := request.metadata.get(CONVERSATION_METADATA_KEY):
        attributes["gen_ai.conversation.id"] = conversation_id
    measurement = {"gen_ai.workflow.name": request.model}
    span = _tracer.start_span(f"invoke_workflow {request.model}", attributes=attributes)
    started = perf_counter()
    try:
        yield span
    except BaseException as exc:
        # An exception should be recorded once, as a log where it is handled.
        # LGOS routes, background engines, and direct callers handle it, so the
        # span adds no exception event.
        error_type = exception_type_name(exc)
        span.set_status(StatusCode.ERROR, str(exc) or None)
        span.set_attribute("error.type", error_type)
        measurement["error.type"] = error_type
        raise
    finally:
        # The span is not current here, so pass it for exemplars to link the
        # measurement to this run's trace.
        _duration.record(
            perf_counter() - started,
            measurement,
            context=trace.set_span_in_context(span),
        )
        span.end()

integrations

Optional infrastructure integrations for LangGraph OpenAI Serve.

hatchet

Hatchet engine for background Responses.

HatchetBackgroundBackend

HatchetBackgroundBackend(task, runs)

Submit, read, and cancel background Responses as Hatchet task runs.

Source code in src/langgraph_openai_serve/integrations/hatchet.py
def __init__(self, task: HatchetBackgroundTask, runs: RunsClient) -> None:
    self._task = task
    self._runs = runs
cancel async
cancel(run_id)

Cancel the Hatchet run.

Source code in src/langgraph_openai_serve/integrations/hatchet.py
async def cancel(self, run_id: str) -> None:
    """Cancel the Hatchet run."""
    await self._runs.aio_cancel(run_id)
get async
get(run_id)

Read the run's input, status, and output from Hatchet.

Source code in src/langgraph_openai_serve/integrations/hatchet.py
async def get(self, run_id: str) -> BackgroundRun | None:
    """Read the run's input, status, and output from Hatchet."""
    try:
        details = await self._runs.aio_get_details(run_id)
    except grpc.RpcError as exc:
        if (
            isinstance(exc, (grpc.Call, grpc.aio.AioRpcError))
            and exc.code() == grpc.StatusCode.NOT_FOUND
        ):
            return None
        raise
    try:
        job = BackgroundJob.model_validate(details.input)
    except ValidationError:
        # A Response ID can name another run in the same Hatchet tenant.
        return None
    task_run = next(iter(details.task_runs.values()), None)
    completed = details.status is RunStatus.COMPLETED
    return BackgroundRun(
        id=run_id,
        job=job,
        status=_STATUSES[details.status],
        response=task_run.output if completed and task_run else None,
    )
submit async
submit(job)

Trigger the task, or return the run holding the job's key.

Source code in src/langgraph_openai_serve/integrations/hatchet.py
async def submit(self, job: BackgroundJob) -> BackgroundRun:
    """Trigger the task, or return the run holding the job's key."""
    try:
        ref = await self._task.aio_run(job, wait_for_result=False)
    except IdempotencyCollisionError as exc:
        existing = await self.get(exc.existing_run_external_id)
        if existing is None:
            msg = "Hatchet reported an idempotency collision with no run."
            raise RuntimeError(msg) from exc
        return existing
    return BackgroundRun(id=ref.workflow_run_id, job=job, status="queued")

create_hatchet_task

create_hatchet_task(
    hatchet,
    *,
    name="lgos-background-response",
    schedule_timeout=timedelta(minutes=30),
    execution_timeout=timedelta(hours=1),
)

Register the background Response task in the API and worker processes.

The worker lifespan must yield the GraphRegistry the task executes. The task is not retried: a failed Response is final, and the client may create a new one.

Source code in src/langgraph_openai_serve/integrations/hatchet.py
def create_hatchet_task(
    hatchet: Hatchet,
    *,
    name: str = "lgos-background-response",
    schedule_timeout: timedelta = timedelta(minutes=30),
    execution_timeout: timedelta = timedelta(hours=1),
) -> HatchetBackgroundTask:
    """
    Register the background Response task in the API and worker processes.

    The worker lifespan must yield the ``GraphRegistry`` the task executes.
    The task is not retried: a failed Response is final, and the client may
    create a new one.
    """

    @hatchet.task(
        name=name,
        input_validator=BackgroundJob,
        schedule_timeout=schedule_timeout,
        execution_timeout=execution_timeout,
        idempotency=TTLBasedIdempotencyConfig(
            key_expression="input.idempotency_key",
            ttl=_IDEMPOTENCY_TTL,
        ),
    )
    async def run_background_response(
        job: BackgroundJob,
        context: Context,
    ) -> dict[str, JsonValue]:
        registry = context.lifespan
        if not isinstance(registry, GraphRegistry):
            msg = "The Hatchet worker lifespan must yield a GraphRegistry."
            raise TypeError(msg)
        return await execute_background_job(job, context.workflow_run_id, registry)

    return run_background_response

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 import Langfuse
    from langfuse.langchain import CallbackHandler
    from opentelemetry.sdk.trace import TracerProvider

    # A tracer provider of its own keeps Langfuse's observations, which carry
    # prompts and outputs, out of the application's trace exporters, and the
    # application's spans out of Langfuse; both still share each trace ID. The
    # first client of a Langfuse project configures its tracing, so an
    # application that already created one keeps its own.
    Langfuse(tracer_provider=TracerProvider())
    return CallbackHandler()

trace_attributes

trace_attributes(*, user_id, session_id, trace_name)

Return Langfuse's context naming the end user, session, and trace.

Source code in src/langgraph_openai_serve/integrations/langfuse.py
def trace_attributes(
    *, user_id: str | None, session_id: str | None, trace_name: str
) -> AbstractContextManager[object]:
    """Return Langfuse's context naming the end user, session, and trace."""
    from langfuse import propagate_attributes

    return propagate_attributes(
        user_id=user_id, session_id=session_id, trace_name=trace_name
    )

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:
    # With close_returns, close() hands the session back to the pool, so a
    # discarded session would keep holding its advisory lock.
    if getattr(pool, "close_returns", False):
        msg = "PostgresRunCoordinator requires a pool with close_returns=False."
        raise ValueError(msg)
    if 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)

Hold a PostgreSQL advisory lock for one interrupt run.

Source code in src/langgraph_openai_serve/integrations/postgres.py
@asynccontextmanager
async def __call__(self, key: str, /) -> AsyncGenerator[None, None]:
    """Hold a PostgreSQL advisory lock for one interrupt run."""
    if not self._capacity.acquire(blocking=False):
        raise RunBusyError
    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
            try:
                yield
            finally:
                await _release_advisory_lock(connection, lock_key)
    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")
>>> registry = GraphRegistry(
...     graphs={
...         "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(registry=registry, app=app)
>>> graph_serve.bind_openai_api()

LanggraphOpenaiServe

LanggraphOpenaiServe(registry, app=None, checkpoint_scope=None, background=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 graphs to serve and their interrupt run coordinator.

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
registry GraphRegistry

The graphs to serve and their interrupt run coordinator.

required
app FastAPI | None

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

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

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

None
background BackgroundBackend | None

Optional polling-only background backend.

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

    Args:
        registry: The graphs to serve and their interrupt run coordinator.
        app: The host FastAPI application to mount the OpenAI API on. If None,
            a new FastAPI app will be created.
        checkpoint_scope: Optional server-trusted resolver used to isolate
            interrupt checkpoints by deployment or authenticated principal.
        background: Optional polling-only background backend.

    """
    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.background = background
    self.graph_registry = registry

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

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

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(),
        # FastAPI's native telemetry belongs to the host app, so the host's
        # settings, including turning it off, also cover the mounted routes.
        telemetry={"tracing": False, "metrics": False, "logs": False},
        **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
    openai_app.state.background_backend = self.background
    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.

server

Serve a graph registry as a standalone application: lgos serve and lgos worker.

RegistryFactory module-attribute

RegistryFactory = Callable[
    [ServerResources], GraphRegistry | AbstractAsyncContextManager[GraphRegistry]
]

Build the served graphs, or open them for the process lifetime.

ServerResources dataclass

ServerResources(checkpointer, store, run_coordinator)

Persistence shared by every graph one server process runs.

ServerSettings

Bases: BaseSettings

Server settings read from explicit values and the process environment.

create_app

create_app(registry, *, settings=None)

Serve the factory's registry under the OpenAI prefix once the app starts.

Source code in src/langgraph_openai_serve/server/app.py
def create_app(
    registry: RegistryFactory,
    *,
    settings: ServerSettings | None = None,
) -> FastAPI:
    """Serve the factory's registry under the OpenAI prefix once the app starts."""
    settings = settings or ServerSettings()

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
        async with AsyncExitStack() as stack:
            resources = await stack.enter_async_context(open_resources(settings))
            graphs = await stack.enter_async_context(open_registry(registry, resources))
            background: BackgroundBackend | None = None
            if settings.BACKGROUND == "memory":
                memory = InMemoryBackgroundBackend(graphs)
                await stack.enter_async_context(memory.lifespan(app))
                background = memory
            elif settings.BACKGROUND == "hatchet":
                # The Hatchet SDK and gRPC take about a second to import, so
                # only processes that submit background runs load them.
                from langgraph_openai_serve.server.hatchet import (
                    create_hatchet_backend,
                )

                background = create_hatchet_backend()
            tasks = await stack.enter_async_context(anyio.create_task_group())
            # Registered after the task group, this runs before the group exits.
            stack.callback(tasks.cancel_scope.cancel)
            if settings.INTERRUPT_SWEEP_INTERVAL_MINUTES:
                tasks.start_soon(expire_paused_runs, resources, settings)
            # The registry exists only after its resources open, so the OpenAI
            # routes are mounted at startup and removed at shutdown; otherwise a
            # restarted app would keep serving the closed registry first.
            LanggraphOpenaiServe(
                registry=graphs, app=app, background=background
            ).bind_openai_api()
            stack.callback(app.router.routes.remove, app.router.routes[-1])
            yield

    app = FastAPI(
        title="LangGraph OpenAI Compatible API",
        lifespan=lifespan,
        openapi_url=None,
        # FastAPI records requests through the global providers; export belongs
        # to `opentelemetry-instrument`, so FastAPI must not add its own.
        telemetry={
            "auto_configure": False,
            "exclude": lambda scope: scope["path"].endswith("/health"),
        },
    )
    if settings.CORS_ORIGINS:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=settings.CORS_ORIGINS,
            allow_methods=["GET", "POST"],
            allow_headers=["*"],
            expose_headers=["X-Request-ID"],
        )
    return app

app

The FastAPI application that lgos serve runs.

create_app

create_app(registry, *, settings=None)

Serve the factory's registry under the OpenAI prefix once the app starts.

Source code in src/langgraph_openai_serve/server/app.py
def create_app(
    registry: RegistryFactory,
    *,
    settings: ServerSettings | None = None,
) -> FastAPI:
    """Serve the factory's registry under the OpenAI prefix once the app starts."""
    settings = settings or ServerSettings()

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
        async with AsyncExitStack() as stack:
            resources = await stack.enter_async_context(open_resources(settings))
            graphs = await stack.enter_async_context(open_registry(registry, resources))
            background: BackgroundBackend | None = None
            if settings.BACKGROUND == "memory":
                memory = InMemoryBackgroundBackend(graphs)
                await stack.enter_async_context(memory.lifespan(app))
                background = memory
            elif settings.BACKGROUND == "hatchet":
                # The Hatchet SDK and gRPC take about a second to import, so
                # only processes that submit background runs load them.
                from langgraph_openai_serve.server.hatchet import (
                    create_hatchet_backend,
                )

                background = create_hatchet_backend()
            tasks = await stack.enter_async_context(anyio.create_task_group())
            # Registered after the task group, this runs before the group exits.
            stack.callback(tasks.cancel_scope.cancel)
            if settings.INTERRUPT_SWEEP_INTERVAL_MINUTES:
                tasks.start_soon(expire_paused_runs, resources, settings)
            # The registry exists only after its resources open, so the OpenAI
            # routes are mounted at startup and removed at shutdown; otherwise a
            # restarted app would keep serving the closed registry first.
            LanggraphOpenaiServe(
                registry=graphs, app=app, background=background
            ).bind_openai_api()
            stack.callback(app.router.routes.remove, app.router.routes[-1])
            yield

    app = FastAPI(
        title="LangGraph OpenAI Compatible API",
        lifespan=lifespan,
        openapi_url=None,
        # FastAPI records requests through the global providers; export belongs
        # to `opentelemetry-instrument`, so FastAPI must not add its own.
        telemetry={
            "auto_configure": False,
            "exclude": lambda scope: scope["path"].endswith("/health"),
        },
    )
    if settings.CORS_ORIGINS:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=settings.CORS_ORIGINS,
            allow_methods=["GET", "POST"],
            allow_headers=["*"],
            expose_headers=["X-Request-ID"],
        )
    return app

hatchet

Hatchet background execution for the server and its worker process.

create_hatchet

create_hatchet()

Create a client configured by the environment and traced when exporting.

Source code in src/langgraph_openai_serve/server/hatchet.py
def create_hatchet() -> Hatchet:
    """Create a client configured by the environment and traced when exporting."""
    # The SDK reads its token, namespace, endpoints, and TLS from HATCHET_CLIENT_*.
    hatchet = Hatchet()
    if os.getenv("OTEL_TRACES_EXPORTER", "").strip().lower() not in {"", "none"}:
        from hatchet_sdk.opentelemetry.instrumentor import HatchetInstrumentor

        # opentelemetry-instrument owns export and shutdown.
        instrumentor = HatchetInstrumentor(
            config=hatchet.config, enable_hatchet_otel_collector=False
        )
        if not instrumentor.is_instrumented_by_opentelemetry:
            instrumentor.instrument()
    return hatchet

create_hatchet_backend

create_hatchet_backend()

Build the API-side backend that submits, reads, and cancels runs.

Source code in src/langgraph_openai_serve/server/hatchet.py
def create_hatchet_backend() -> HatchetBackgroundBackend:
    """Build the API-side backend that submits, reads, and cancels runs."""
    hatchet = create_hatchet()
    return HatchetBackgroundBackend(create_hatchet_task(hatchet), hatchet.runs)

run_worker

run_worker(factory, settings)

Run background Responses for the same registry the API serves.

Source code in src/langgraph_openai_serve/server/hatchet.py
def run_worker(factory: RegistryFactory, settings: ServerSettings) -> None:
    """Run background Responses for the same registry the API serves."""
    if settings.BACKGROUND != "hatchet":
        msg = "Set LGOS_BACKGROUND=hatchet before starting the worker."
        raise ValueError(msg)
    if (
        settings.POSTGRES_URI is not None
        and settings.HATCHET_WORKER_SLOTS >= settings.POSTGRES_POOL_SIZE
    ):
        msg = (
            "LGOS_HATCHET_WORKER_SLOTS must be below LGOS_POSTGRES_POOL_SIZE: "
            "each running interrupt holds one pool connection."
        )
        raise ValueError(msg)

    async def lifespan() -> AsyncGenerator[GraphRegistry, None]:
        async with (
            open_resources(settings) as resources,
            open_registry(factory, resources) as registry,
        ):
            yield registry

    hatchet = create_hatchet()
    hatchet.worker(
        name="lgos-worker",
        slots=settings.HATCHET_WORKER_SLOTS,
        workflows=[create_hatchet_task(hatchet)],
        lifespan=lifespan,
    ).start()

logging

Structured logging on stdout for the server and worker processes.

configure_logging

configure_logging(application, *, root_level)

Install the logging configuration for a process that Uvicorn does not run.

Source code in src/langgraph_openai_serve/server/logging.py
def configure_logging(application: str, *, root_level: int) -> None:
    """Install the logging configuration for a process that Uvicorn does not run."""
    logging.config.dictConfig(logging_config(application, root_level=root_level))

logging_config

logging_config(application, *, root_level)

Return a dictConfig that logs INFO from LGOS, Uvicorn, and the app.

Source code in src/langgraph_openai_serve/server/logging.py
def logging_config(application: str, *, root_level: int) -> dict[str, Any]:
    """Return a ``dictConfig`` that logs INFO from LGOS, Uvicorn, and the app."""
    info = {"level": "INFO", "propagate": True}
    return {
        "version": 1,
        "disable_existing_loggers": False,
        "filters": {
            "drop_uvicorn_color_message": {"()": _DropUvicornColorMessage},
            "request_context": {"()": RequestContextFilter},
        },
        "formatters": {
            "structlog": {
                "()": structlog.stdlib.ProcessorFormatter,
                "foreign_pre_chain": [
                    structlog.stdlib.add_log_level,
                    structlog.stdlib.add_logger_name,
                    structlog.stdlib.ExtraAdder(),
                    structlog.processors.TimeStamper(fmt="iso", utc=True),
                ],
                "processors": [
                    structlog.processors.StackInfoRenderer(),
                    structlog.stdlib.ProcessorFormatter.remove_processors_meta,
                    *_renderers(),
                ],
            }
        },
        "handlers": {
            "stdout": {
                "class": "logging.StreamHandler",
                "formatter": "structlog",
                # Records from graph nodes and dependencies also get request IDs.
                "filters": ["request_context"],
                "level": "INFO",
                "stream": "ext://sys.stdout",
            }
        },
        "root": {"handlers": ["stdout"], "level": root_level},
        "loggers": {
            application: {**info},
            "hatchet": {**info, "handlers": []},
            "langgraph_openai_serve": {**info},
            # Uvicorn records reach the root handler, so OpenTelemetry's root
            # LoggingHandler exports them too when the launcher is used.
            "uvicorn": {**info, "handlers": []},
            "uvicorn.error": {
                **info,
                "handlers": [],
                "filters": ["drop_uvicorn_color_message"],
            },
        },
    }

runtime

Persistence the server owns and passes to the application's registry.

RegistryFactory module-attribute

RegistryFactory = Callable[
    [ServerResources], GraphRegistry | AbstractAsyncContextManager[GraphRegistry]
]

Build the served graphs, or open them for the process lifetime.

ServerResources dataclass

ServerResources(checkpointer, store, run_coordinator)

Persistence shared by every graph one server process runs.

expire_paused_runs async

expire_paused_runs(resources, settings)

Delete abandoned interrupt runs on an interval, like Agent Server's TTL.

Source code in src/langgraph_openai_serve/server/runtime.py
async def expire_paused_runs(
    resources: ServerResources,
    settings: ServerSettings,
) -> None:
    """Delete abandoned interrupt runs on an interval, like Agent Server's TTL."""
    ttl = timedelta(minutes=settings.INTERRUPT_TTL_MINUTES)
    while True:
        try:
            deleted = await delete_expired_interrupt_runs(
                resources.checkpointer, resources.run_coordinator, older_than=ttl
            )
        except Exception:
            logger.exception("server.interrupt_expiry.failed")
        else:
            if deleted:
                logger.info("server.interrupt_expiry.deleted", extra={"runs": deleted})
        await anyio.sleep(settings.INTERRUPT_SWEEP_INTERVAL_MINUTES * 60)

open_registry async

open_registry(factory, resources)

Build the registry, entering it when the factory owns resources.

Yields:

Type Description
AsyncGenerator[GraphRegistry, None]

The registry to serve until the process stops.

Source code in src/langgraph_openai_serve/server/runtime.py
@asynccontextmanager
async def open_registry(
    factory: RegistryFactory,
    resources: ServerResources,
) -> AsyncGenerator[GraphRegistry, None]:
    """
    Build the registry, entering it when the factory owns resources.

    Yields:
        The registry to serve until the process stops.

    """
    registry = factory(resources)
    if isinstance(registry, GraphRegistry):
        yield registry
        return
    async with registry as opened:
        yield opened

open_resources async

open_resources(settings)

Open PostgreSQL persistence, or process memory when no database is set.

Yields:

Type Description
AsyncGenerator[ServerResources, None]

The checkpointer, store, and run coordinator for this process.

Source code in src/langgraph_openai_serve/server/runtime.py
@asynccontextmanager
async def open_resources(
    settings: ServerSettings,
) -> AsyncGenerator[ServerResources, None]:
    """
    Open PostgreSQL persistence, or process memory when no database is set.

    Yields:
        The checkpointer, store, and run coordinator for this process.

    """
    if settings.POSTGRES_URI is None:
        logger.warning("server.persistence.in_memory")
        yield ServerResources(
            checkpointer=InMemorySaver(),
            store=InMemoryStore(),
            run_coordinator=InMemoryRunCoordinator(),
        )
        return

    postgres_uri = settings.POSTGRES_URI.get_secret_value()
    await _setup_schema(postgres_uri)
    pool_context = cast(
        "PostgresPool",
        AsyncConnectionPool(
            conninfo=postgres_uri,
            kwargs={
                "autocommit": True,
                "prepare_threshold": 0,
                "row_factory": dict_row,
            },
            min_size=1,
            max_size=settings.POSTGRES_POOL_SIZE,
            open=False,
        ),
    )
    async with pool_context as pool:
        await pool.wait()
        yield ServerResources(
            checkpointer=AsyncPostgresSaver(pool),
            store=AsyncPostgresStore(pool),
            # Reserve one connection for checkpoints while leases hold the others.
            run_coordinator=PostgresRunCoordinator(
                pool, max_concurrent_leases=settings.POSTGRES_POOL_SIZE - 1
            ),
        )

settings

Settings for lgos serve and lgos worker.

ServerSettings

Bases: BaseSettings

Server settings read from explicit values and the process environment.