Skip to content

Reference

OpenAI-Compatible API

Default prefix: /v1. Change it with LGOS_OPENAI_API_PREFIX or bind_openai_api(prefix=...).

Method Path Purpose
GET /v1/models List standard summaries for registered graph models.
GET /v1/models/{model} Retrieve one model with optional LGOS discovery metadata.
POST /v1/chat/completions Run a graph through OpenAI chat completions.
GET /v1/health Health check.

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

Settings

Package settings:

Setting Default Notes
LGOS_OPENAI_API_PREFIX /v1 Must start with /; trailing slash is normalized.
LGOS_OPENAI_API_DOCS_ENABLED false Enables docs only for the mounted OpenAI app.

Demo API and graph settings:

Setting Default Notes
DEMO_OPENAI_BASE_URL https://api.openai.com/v1 Upstream OpenAI-compatible base URL for LLM-backed demo graphs.
DEMO_OPENAI_API_KEY DUMMY Upstream API key for LLM-backed demo graphs.
DEMO_OPENAI_MODEL gpt-5.4-mini Upstream generation model for LLM-backed demo graphs.
DEMO_OPENAI_EMBEDDING_MODEL text-embedding-3-small Embedding model used by the lgos-rag demo graph.
DEMO_POSTGRES_URI postgresql://lgos:lgos@localhost:5432/lgos Checkpoint database used by the interruptible demo graph.

Settings for optional clients and gateways are documented under Integrations.

Public API

Use LanggraphOpenaiServe to bind OpenAI-compatible routes to a FastAPI app. Use GraphRegistry to map OpenAI model names to GraphConfig values. The registry must contain at least one graph. Pydantic rejects empty registries and model IDs that cannot be addressed as one URL path segment. Registry keys are read-only after validation; use registry.register(model_id, config) to add or replace a graph.

GraphConfig accepts:

  • graph: compiled graph, sync factory, or async factory.
  • streamable_node_names: node names whose streamed AIMessageChunk values are forwarded to clients.
  • features: GraphFeature values that enable optional server behavior.
  • client_settings: explicit public ClientSettings model class advertised by model retrieval.
  • runtime_callbacks: callbacks included in the LangGraph RunnableConfig.
  • request_to_input(request, messages): custom OpenAI request to graph input.
  • context_factory(request, client_settings): compose the final typed LangGraph runtime context from server-owned values and optional validated public settings.
  • output_to_text(output): custom graph output to assistant text.

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

Value LGOS/LangGraph path Intended use
Graph input graph.astream(input, ...) Messages and mutable workflow state.
Runtime context public settings → optional context_factorycontext=Runtime.context Immutable per-run application values and dependencies.
Runnable config config= Callbacks, tags, tracing, and other execution controls.
Checkpoint thread metadata.langgraph_thread_idconfig["configurable"]["thread_id"] Load, save, interrupt, and resume checkpoint state.

LGOS assembles runnable config from runtime_callbacks and, when present, the checkpoint 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.

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

Runtime Settings

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

Public settings model
from pydantic import Field

from langgraph_openai_serve import ClientSettings


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

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

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

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

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

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

Interrupt-enabled graphs must be compiled with a LangGraph checkpointer and requests must include metadata={"langgraph_thread_id": "<client-chat-id>"}. Use a durable checkpointer in production.

Client Stream Events

Inside a graph node or tool, mark a passive client notification as explicitly public with client_event():

from langgraph.config import get_stream_writer
from langgraph_openai_serve import client_event

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

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

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

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

Citation Events

Inside a graph node or tool, emit a citation with LangGraph's stream writer:

from langgraph.config import get_stream_writer
from langgraph_openai_serve import citation_event

get_stream_writer()(
    citation_event(
        url="https://example.com/source",
        title="Example source",
        span=(10, 14),
    )
)

span uses Python's half-open convention, so text[10:14] returns the cited text. LGOS converts it to OpenAI's inclusive end_index at the event boundary. Use citation_slice(annotation, text) to validate received indices and convert them back to a Python slice. Citation events must refer to the final rendered assistant text.

See Citation ownership for transport and client behavior.

The graph runner preserves LangGraph's native CustomStreamPart values, including their execution namespace. Other event types remain available to direct runner consumers through langgraph_openai_serve.graph.runner.

Demo Models

make run-demo-api registers:

  • simple-graph (runtime settings for conversation history and intended audience)
  • citation-events (structured URL citations alongside portable Markdown)
  • lgos-rag
  • custom-input-output-context
  • advanced-mcp-tools
  • complex-subgraphs
  • custom-event-showcase (status, progress, and artifact stream events)
  • interruptible-approval

Local Commands

uv sync --frozen
make help
make run-demo-api

UI and gateway setup is documented under Integrations.

make -s test
make test-bifrost
uv run --module pytest tests/path/to/test_file.py
uv run --module pytest tests/path/to/test_file.py::test_name
make -s lint
make -s format
make doc-build
make doc-serve

make doc-serve serves the live preview on http://localhost:7999 and watches for documentation changes.

langgraph-openai-serve package.

ClientSettings

Bases: BaseModel

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

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

defaults classmethod

defaults()

Return a deep copy of the registration-validated defaults.

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

validate_request classmethod

validate_request(request)

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

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

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

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

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

GraphConfig

Bases: BaseModel

build_context async

build_context(request, graph)

Build the LangGraph runtime context for a request.

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

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

    # LangGraph owns context_schema coercion when the graph is invoked.
    return context

build_input async

build_input(request, messages)

Build the native graph input for a chat completion request.

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

render_output async

render_output(output)

Convert native graph output into assistant response text.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def render_output(self, output: Any) -> str:
    """Convert native graph output into assistant response text."""
    if self.output_to_text is None:
        messages = output["messages"]
        return messages[-1].content if messages else ""
    return await _maybe_await(self.output_to_text(output))

resolve_graph async

resolve_graph()

Get the graph instance, resolving callable graph factories.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def resolve_graph(self) -> CompiledStateGraph:
    """Get the graph instance, resolving callable graph factories."""
    if isinstance(self.graph, CompiledStateGraph):
        graph = self.graph
    else:
        graph = await _maybe_await(self.graph())

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

    if self.supports(GraphFeature.INTERRUPTS) and graph.checkpointer is None:
        raise GraphConfigurationError(
            "Interrupt-enabled graphs must be compiled with a checkpointer."
        )

    return graph

supports

supports(feature)

Return whether this graph supports a feature.

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

validate_client_settings classmethod

validate_client_settings(value)

Validate a public settings model when its graph is registered.

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

GraphFeature

Bases: StrEnum

Features supported by a registered graph.

GraphRegistry

Bases: BaseModel

get_graph

get_graph(name)

Get a graph by its name.

Parameters:

Name Type Description Default
name str

The name of the graph to retrieve.

required

Returns:

Type Description
GraphConfig

The graph configuration associated with the given name.

Raises:

Type Description
GraphNotFoundError

If the graph name is not found in the registry.

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

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

    Returns:
        The graph configuration associated with the given name.

    Raises:
        GraphNotFoundError: If the graph name is not found in the registry.
    """
    if name not in self.registry:
        raise GraphNotFoundError(f"Graph '{name}' not found in registry.")
    return self.registry[name]

get_graph_names

get_graph_names()

Get the names of all registered graphs.

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

register

register(model_id, config)

Add or replace one graph through the validated registry boundary.

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

LanggraphOpenaiServe

LanggraphOpenaiServe(graphs, app=None)

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

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

Attributes:

Name Type Description
app FastAPI

The host FastAPI application to mount the OpenAI API on.

graph_registry

The populated GraphRegistry containing the graphs to serve.

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

Parameters:

Name Type Description Default
app FastAPI | None

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

None
graphs GraphRegistry

A GraphRegistry instance containing the graphs to serve.

required

Raises:

Type Description
TypeError

If graphs is not a GraphRegistry instance.

Source code in src/langgraph_openai_serve/openai_server.py
def __init__(
    self,
    graphs: GraphRegistry,
    app: FastAPI | None = None,
):
    """Initialize the server with a FastAPI app and a populated graph registry.

    Args:
        app: The host FastAPI application to mount the OpenAI API on. If None,
            a new FastAPI app will be created.
        graphs: A GraphRegistry instance containing the graphs to serve.

    Raises:
        TypeError: If graphs is not a GraphRegistry instance.
    """
    if not isinstance(graphs, GraphRegistry):
        raise TypeError(
            "Invalid type for graphs parameter. Expected GraphRegistry."
        )

    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

    logger.info("Using provided GraphRegistry instance")
    self.graph_registry = graphs

    # Attach the registry to the host app for callers that inspect app state.
    self.app.state.graph_registry = self.graph_registry

    logger.info(
        f"Initialized LanggraphOpenaiServe with {len(self.graph_registry.registry)} graphs"
    )
    logger.info(
        f"Available graphs: {', '.join(self.graph_registry.get_graph_names())}"
    )

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):
    """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.
    """
    if prefix is None:
        prefix = settings.OPENAI_API_PREFIX
    else:
        prefix = normalize_openai_api_prefix(prefix)

    docs_url = "/docs" if settings.OPENAI_API_DOCS_ENABLED else None
    redoc_url = "/redoc" if settings.OPENAI_API_DOCS_ENABLED else None
    openapi_url = "/openapi.json" if settings.OPENAI_API_DOCS_ENABLED else None

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

    self.app.mount(prefix, openai_app, name="openai")

    logger.info(f"Bound OpenAI chat completion endpoints with prefix: {prefix}")

    return self

citation_event

citation_event(*, url, title, span)

Build an OpenAI URL citation from a Python-style half-open span.

Source code in src/langgraph_openai_serve/graph/events.py
def citation_event(
    *,
    url: str,
    title: str,
    span: tuple[int, int],
) -> dict[str, object]:
    """Build an OpenAI URL citation from a Python-style half-open span."""
    start, stop = span
    if not 0 <= start < stop:
        raise ValueError("citation span must define a valid non-empty text range")

    return Annotation(
        type="url_citation",
        url_citation=AnnotationURLCitation(
            url=url,
            title=title,
            start_index=start,
            end_index=stop - 1,
        ),
    ).model_dump(mode="json")

citation_slice

citation_slice(annotation, content)

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

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

client_event

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

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

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

api

chat

schemas

Pydantic models for the OpenAI API.

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

ChatCompletionRequest

Bases: BaseModel

Model for a chat completion request.

reject_legacy_fields classmethod
reject_legacy_fields(data)

Reject deprecated Chat Completions function parameters.

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

Bases: BaseModel

Model for a chat completion request message.

reject_legacy_fields classmethod
reject_legacy_fields(data)

Reject the deprecated singular Chat Completions function call.

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

Bases: BaseModel

Model for a chat completion response.

ChatCompletionResponseChoice

Bases: BaseModel

Model for a chat completion response choice.

ChatCompletionResponseMessage

Bases: BaseModel

Model for a chat completion response message.

ChatCompletionStreamResponse

Bases: BaseModel

Model for a chat completion stream response.

ChatCompletionStreamResponseChoice

Bases: BaseModel

Model for a chat completion stream response choice.

ChatCompletionStreamResponseDelta

Bases: BaseModel

Model for a chat completion stream response delta.

ChatCompletionStreamToolCall

Bases: BaseModel

Model for a streaming tool call delta.

ChatCompletionStreamToolCallFunction

Bases: BaseModel

Model for a streaming tool call function delta.

FunctionDefinition

Bases: BaseModel

Model for a function definition.

Role

Bases: str, Enum

Role options for chat messages.

Tool

Bases: BaseModel

Model for a tool.

ToolCall

Bases: BaseModel

Model for a tool call.

ToolCallFunction

Bases: BaseModel

Model for a tool call function.

ToolFunction

Bases: BaseModel

Model for a tool function.

UsageInfo

Bases: BaseModel

Model for usage information.

service

Chat completion service.

ChatCompletionService

Service for handling 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(
    self, chat_request: ChatCompletionRequest, run: GraphRun
) -> ChatCompletionResponse:
    """Generate a chat completion."""
    start_time = time.time()

    invocation = await invoke_run(run)
    completion = invocation.output
    tokens_used = usage_for(completion, chat_request.messages)
    annotations = (
        [
            annotation
            for event in invocation.custom_events
            if (annotation := annotation_from_custom_event(event, completion))
            is not None
        ]
        if isinstance(completion, str)
        else []
    )

    response = chat_completion_response(
        model=chat_request.model,
        completion=completion,
        annotations=annotations,
        usage=tokens_used,
    )

    logger.info(
        f"Chat completion finished in {time.time() - start_time:.2f}s. "
        f"Total tokens: {tokens_used['total_tokens']}"
    )

    return response
stream_completion async
stream_completion(chat_request, run)

Stream a chat completion response.

Source code in src/langgraph_openai_serve/api/chat/service.py
async def stream_completion(
    self, chat_request: ChatCompletionRequest, run: GraphRun
) -> AsyncGenerator[str, None]:
    """Stream a chat completion response."""
    start_time = time.time()
    response_builder = ChatCompletionStreamResponseBuilder(chat_request.model)
    custom_events: list[CustomStreamPart] = []
    content_parts: list[str] = []
    include_client_events = stream_events_requested(chat_request.metadata)

    try:
        yield response_builder.role()

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

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

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

        content = "".join(content_parts)
        annotations = [
            annotation
            for event in custom_events
            if (annotation := annotation_from_custom_event(event, content))
            is not None
        ]
        yield response_builder.finish("stop", annotations=annotations)
        yield response_builder.done()

        logger.info(
            f"Streamed chat completion finished in {time.time() - start_time:.2f}s"
        )

    except Exception as e:
        logger.exception("Error streaming chat completion")
        yield response_builder.error(str(e))
        yield response_builder.done()

utils

events

Adapt generic LangGraph custom events to OpenAI chat fields.

annotation_from_custom_event
annotation_from_custom_event(event, content)

Validate a recognized OpenAI annotation custom event.

Source code in src/langgraph_openai_serve/api/chat/utils/events.py
def annotation_from_custom_event(
    event: CustomStreamPart,
    content: str,
) -> Annotation | None:
    """Validate a recognized OpenAI annotation custom event."""
    payload = event["data"]
    if not isinstance(payload, dict) or payload.get("type") != "url_citation":
        return None

    annotation = Annotation.model_validate(payload)
    citation_slice(annotation, content)
    return annotation
client_event_extension_from_custom_event
client_event_extension_from_custom_event(event)

Validate an explicitly public event and build its stream extension.

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

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

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

OpenAI chat protocol helpers for LangGraph interrupts.

InvalidResumeRequestError

Bases: ValueError

Raised when a tool response cannot be used to resume an interrupt.

responses

OpenAI chat response builders.

ChatCompletionStreamResponseBuilder
ChatCompletionStreamResponseBuilder(model)

Build OpenAI-compatible chat completion SSE chunks.

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

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

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

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

Source code in src/langgraph_openai_serve/api/chat/utils/responses.py
def chat_completion_response(
    *,
    model: str,
    completion: LangGraphOutput,
    annotations: list[Annotation] | None = None,
    usage: dict[str, int],
) -> ChatCompletionResponse:
    """Build a non-streaming OpenAI-compatible chat completion response."""
    message, finish_reason = response_message(completion, annotations)
    return ChatCompletionResponse(
        id=f"chatcmpl-{uuid.uuid4()}",
        created=int(time.time()),
        model=model,
        choices=[
            ChatCompletionResponseChoice(
                index=0,
                message=message,
                finish_reason=finish_reason,
            )
        ],
        usage=UsageInfo(
            prompt_tokens=usage["prompt_tokens"],
            completion_tokens=usage["completion_tokens"],
            total_tokens=usage["total_tokens"],
        ),
    )

views

Chat completion router.

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

create_chat_completion async
create_chat_completion(chat_request, service, graph_registry, stream_owner)

Create a chat completion.

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

Parameters:

Name Type Description Default
chat_request ChatCompletionRequest

The parsed chat completion request.

required
graph_registry Annotated[GraphRegistry, Depends(get_graph_registry_dependency)]

The graph registry dependency.

required
service Annotated[ChatCompletionService, Depends(ChatCompletionService)]

The chat completion service dependency.

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

The request-scoped streaming task owner.

required

Returns:

Type Description
StreamingResponse | ChatCompletionResponse

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

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

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

    Args:
        chat_request: The parsed chat completion request.
        graph_registry: The graph registry dependency.
        service: The chat completion service dependency.
        stream_owner: The request-scoped streaming task owner.

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

    logger.info(
        f"Received chat completion request for model: {chat_request.model}, "
        f"stream: {chat_request.stream}"
    )

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

        if chat_request.stream:
            logger.info("Streaming chat completion response")
            body = stream_owner.start(service.stream_completion(chat_request, run))
            return StreamingResponse(
                body,
                media_type="text/event-stream",
            )

        logger.info("Generating non-streaming chat completion response")
        response = await service.generate_completion(chat_request, run)
    except CLIENT_ERROR_TYPES as e:
        raise OpenAIHTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            error=ErrorObject(
                message=str(e),
                type="invalid_request_error",
                param=client_error_param(e),
            ),
        ) from e
    except GraphConfigurationError as e:
        raise OpenAIHTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error=ErrorObject(
                message=str(e),
                type="server_error",
            ),
        ) from e
    logger.info("Returning non-streaming chat completion response")
    return response

health

views

health_check
health_check()

Checks 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:
    """Checks 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()}

models

schemas

LangGraphModelExtension

Bases: BaseModel

Versioned LangGraph OpenAI Serve model extension.

Model

Bases: BaseModel

Individual model information.

ModelClientSettings

Bases: BaseModel

Versioned public runtime settings for one registered graph.

ModelDetails

Bases: Model

Retrieved model with optional LGOS discovery metadata.

ModelList

Bases: BaseModel

List of available models.

service

Model service.

This module provides a service for handling OpenAI model information.

ModelService

Service for handling model operations.

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(self, model: str, graph_registry: GraphRegistry) -> ModelDetails:
    """Get one registered graph as an OpenAI model with LGOS metadata."""
    graph_config = graph_registry.get_graph(model)
    client_settings = graph_config.client_settings
    client_settings_details = None
    if client_settings is not None:
        client_settings_details = ModelClientSettings(
            json_schema=client_settings_json_schema(client_settings),
            defaults=client_settings_default_values(client_settings),
        )

    details = ModelDetails(
        id=model,
        created=MODEL_CREATED,
        owned_by=MODEL_OWNER,
        langgraph_openai_serve=LangGraphModelExtension(
            features=sorted(
                graph_config.features,
                key=lambda feature: feature.value,
            ),
            client_settings=client_settings_details,
        ),
    )
    logger.info(f"Retrieved model details for {model}")
    return 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(self, 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,
        )
        for name in graph_registry.registry
    ]

    logger.info(f"Retrieved {len(models)} available models")
    return ModelList(data=models)

views

Models router.

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

get_graph_registry_dependency
get_graph_registry_dependency(request)

Dependency to get the graph registry from the app state.

Source code in src/langgraph_openai_serve/api/models/views.py
def get_graph_registry_dependency(request: Request) -> GraphRegistry:
    """Dependency to get the graph registry from the app state."""
    return request.app.state.graph_registry
list_models
list_models(service, graph_registry)

Get a list of available models.

Source code in src/langgraph_openai_serve/api/models/views.py
@router.get("", response_model=ModelList)
def list_models(
    service: Annotated[ModelService, Depends(ModelService)],
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry_dependency)],
):
    """Get a list of available models."""
    logger.info("Received request to list models")
    models = service.get_models(graph_registry)
    logger.info(f"Returning {len(models.data)} models")
    return models
retrieve_model
retrieve_model(model, service, 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=ModelDetails,
    response_model_exclude_none=True,
)
def retrieve_model(
    model: str,
    service: Annotated[ModelService, Depends(ModelService)],
    graph_registry: Annotated[GraphRegistry, Depends(get_graph_registry_dependency)],
) -> ModelDetails:
    """Retrieve one registered graph as an OpenAI model."""
    logger.info(f"Received request to retrieve model: {model}")
    try:
        return service.get_model(model, graph_registry)
    except GraphNotFoundError as exc:
        raise OpenAIHTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            error=ErrorObject(
                message=str(exc),
                type="invalid_request_error",
                param="model",
                code="model_not_found",
            ),
        ) from exc

core

errors

OpenAI-compatible error response helpers.

OpenAIHTTPException

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

Bases: HTTPException

HTTP exception that carries OpenAI error object metadata.

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

configure_openai_error_handlers

configure_openai_error_handlers(app)

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

Source code in src/langgraph_openai_serve/core/errors.py
def configure_openai_error_handlers(app: FastAPI) -> None:
    """Install OpenAI-compatible JSON error handlers on a FastAPI app."""
    app.add_exception_handler(OpenAIHTTPException, openai_http_exception_handler)
    app.add_exception_handler(StarletteHTTPException, openai_http_exception_handler)
    app.add_exception_handler(
        RequestValidationError,
        openai_request_validation_exception_handler,
    )
    app.add_exception_handler(Exception, openai_unhandled_exception_handler)

settings

Settings

Bases: BaseSettings

This class is used to load environment variables either from environment or from a .env file and store them as class attributes. NOTE: - environment variables will always take priority over values loaded from a dotenv file - environment variable names are case-insensitive - environment variable type is inferred from the type hint of the class attribute - For environment variables that are not set, a default value should be provided

For more info, see the related pydantic docs: https://docs.pydantic.dev/latest/concepts/pydantic_settings

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

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

    # Check for required environment variables
    required_env_vars = [
        "LANGFUSE_BASE_URL",
        "LANGFUSE_PUBLIC_KEY",
        "LANGFUSE_SECRET_KEY",
    ]
    missing_vars = [var for var in required_env_vars if os.getenv(var) is None]

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

    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("/"):
        raise ValueError("OPENAI_API_PREFIX must start with '/'.")
    if len(v) > 1:
        normalized = v.rstrip("/")
        if not normalized:
            raise ValueError("OPENAI_API_PREFIX must not contain only slashes.")
        return normalized
    return v

version

get_version cached

get_version()

Return installed package version.

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

graph

Service package for the LangGraph OpenAI compatible API.

client_settings

Public graph settings transported through standard OpenAI requests.

ClientSettings

Bases: BaseModel

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

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

defaults classmethod
defaults()

Return a deep copy of the registration-validated defaults.

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

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

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

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

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

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

ClientSettingsValidationError

ClientSettingsValidationError(message, *, param=None)

Bases: ValueError

Raised when client-controlled graph settings are invalid.

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

client_settings_default_values

client_settings_default_values(settings_model)

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

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

client_settings_json_schema

client_settings_json_schema(settings_model)

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

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

validate_client_settings_model

validate_client_settings_model(settings_model)

Validate the registration-time contract of a settings model.

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

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

    client_settings_json_schema(settings_model)
    return settings_model

events

Public events emitted by LangGraph nodes and tools.

citation_event

citation_event(*, url, title, span)

Build an OpenAI URL citation from a Python-style half-open span.

Source code in src/langgraph_openai_serve/graph/events.py
def citation_event(
    *,
    url: str,
    title: str,
    span: tuple[int, int],
) -> dict[str, object]:
    """Build an OpenAI URL citation from a Python-style half-open span."""
    start, stop = span
    if not 0 <= start < stop:
        raise ValueError("citation span must define a valid non-empty text range")

    return Annotation(
        type="url_citation",
        url_citation=AnnotationURLCitation(
            url=url,
            title=title,
            start_index=start,
            end_index=stop - 1,
        ),
    ).model_dump(mode="json")

citation_slice

citation_slice(annotation, content)

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

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

client_event

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

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

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

client_event_extension

client_event_extension(value)

Build a stream extension from validated public custom stream data.

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

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

features

GraphFeature

Bases: StrEnum

Features supported by a registered graph.

graph_registry

GraphConfig

Bases: BaseModel

build_context async
build_context(request, graph)

Build the LangGraph runtime context for a request.

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

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

    # LangGraph owns context_schema coercion when the graph is invoked.
    return context
build_input async
build_input(request, messages)

Build the native graph input for a chat completion request.

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

Convert native graph output into assistant response text.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def render_output(self, output: Any) -> str:
    """Convert native graph output into assistant response text."""
    if self.output_to_text is None:
        messages = output["messages"]
        return messages[-1].content if messages else ""
    return await _maybe_await(self.output_to_text(output))
resolve_graph async
resolve_graph()

Get the graph instance, resolving callable graph factories.

Source code in src/langgraph_openai_serve/graph/graph_registry.py
async def resolve_graph(self) -> CompiledStateGraph:
    """Get the graph instance, resolving callable graph factories."""
    if isinstance(self.graph, CompiledStateGraph):
        graph = self.graph
    else:
        graph = await _maybe_await(self.graph())

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

    if self.supports(GraphFeature.INTERRUPTS) and graph.checkpointer is None:
        raise GraphConfigurationError(
            "Interrupt-enabled graphs must be compiled with a checkpointer."
        )

    return graph
supports
supports(feature)

Return whether this graph supports a feature.

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

Validate a public settings model when its graph is registered.

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

GraphConfigurationError

Bases: RuntimeError

Raised when a registered graph cannot satisfy its declared config.

GraphNotFoundError

Bases: ValueError

Raised when a requested graph is not registered.

GraphRegistry

Bases: BaseModel

get_graph
get_graph(name)

Get a graph by its name.

Parameters:

Name Type Description Default
name str

The name of the graph to retrieve.

required

Returns:

Type Description
GraphConfig

The graph configuration associated with the given name.

Raises:

Type Description
GraphNotFoundError

If the graph name is not found in the registry.

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

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

    Returns:
        The graph configuration associated with the given name.

    Raises:
        GraphNotFoundError: If the graph name is not found in the registry.
    """
    if name not in self.registry:
        raise GraphNotFoundError(f"Graph '{name}' not found in registry.")
    return self.registry[name]
get_graph_names
get_graph_names()

Get the names of all registered graphs.

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

Add or replace one graph through the validated registry boundary.

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

runner

Run LangGraph workflows behind the OpenAI-compatible chat API.

LangGraphInvocation dataclass

LangGraphInvocation(output, custom_events)

A graph result together with custom events emitted during its run.

extract_stream_interrupt

extract_stream_interrupt(event, thread_id)

Extract interrupt metadata from a LangGraph v2 values stream part.

Source code in src/langgraph_openai_serve/graph/runner.py
def extract_stream_interrupt(
    event: dict,
    thread_id: str | None,
) -> LangGraphInterrupt | None:
    """Extract interrupt metadata from a LangGraph v2 values stream part."""
    interrupts = event.get("interrupts") or ()
    if not interrupts:
        return None
    return interrupt_from_value(interrupts[0], thread_id)

invoke_run async

invoke_run(run)

Invoke a graph and collect its custom events.

Source code in src/langgraph_openai_serve/graph/runner.py
async def invoke_run(run: GraphRun) -> LangGraphInvocation:
    """Invoke a graph and collect its custom events."""
    stream_mode: list[StreamMode] = ["values", "custom"]

    final_output: Any = _MISSING
    custom_events: list[CustomStreamPart] = []
    graph_stream = cast(
        AsyncGenerator[dict[str, Any], None],
        run.graph.astream(
            run.inputs,
            config=run.runnable_config,
            context=run.context,
            stream_mode=stream_mode,
            output_keys=run.graph.output_channels,
            subgraphs=True,
            version="v2",
        ),
    )
    async with aclosing(graph_stream):
        async for event in graph_stream:
            if event.get("type") == "custom":
                custom_events.append(cast(CustomStreamPart, event))
                continue

            if event.get("type") == "values" and not event.get("ns"):
                if run.config.supports(GraphFeature.INTERRUPTS):
                    interrupt = extract_stream_interrupt(event, run.thread_id)
                    if interrupt is not None:
                        return LangGraphInvocation(
                            output=interrupt,
                            custom_events=tuple(custom_events),
                        )
                final_output = event.get("data")

    if final_output is _MISSING:
        raise RuntimeError("LangGraph invocation completed without a final value.")

    return LangGraphInvocation(
        output=await run.config.render_output(legacy_output(final_output)),
        custom_events=tuple(custom_events),
    )

legacy_output

legacy_output(output)

Match the plain output shape adapters received from LangGraph v1.

Source code in src/langgraph_openai_serve/graph/runner.py
def legacy_output(output: Any) -> Any:
    """Match the plain output shape adapters received from LangGraph v1."""
    if isinstance(output, BaseModel):
        return dict(output)
    if is_dataclass(output) and not isinstance(output, type):
        return {field.name: getattr(output, field.name) for field in fields(output)}
    return output

run_langgraph async

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

Prepare and invoke a graph for direct runner callers.

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

Examples:

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

Parameters:

Name Type Description Default
model str

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

required
messages list[ChatCompletionRequestMessage]

A list of messages to process through the LangGraph.

required
graph_registry GraphRegistry

The GraphRegistry instance containing registered graphs.

required
request ChatCompletionRequest | None

The complete chat completion request passed to graph adapters.

None

Returns:

Type Description
LangGraphInvocation

The graph output and custom events emitted during the invocation.

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

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

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

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

    Returns:
        The graph output and custom events emitted during the invocation.
    """
    logger.info(f"Running LangGraph model {model} with {len(messages)} messages")
    start_time = time.time()

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

    invocation = await invoke_run(run)

    logger.info(f"LangGraph completion generated in {time.time() - start_time:.2f}s")
    return invocation

run_langgraph_stream async

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

Prepare and stream a graph for direct runner callers.

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

Parameters:

Name Type Description Default
model str

The name of the model (graph) to run.

required
messages list[ChatCompletionRequestMessage]

A list of OpenAI-compatible messages.

required
graph_registry GraphRegistry

The registry containing the graph configurations.

required
request ChatCompletionRequest | None

The complete chat completion request passed to graph adapters.

None

Yields:

Type Description
AsyncGenerator[LangGraphStreamEvent, None]

Assistant text chunks, custom events, or LangGraph interrupts.

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

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

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

    Yields:
        Assistant text chunks, custom events, or LangGraph interrupts.
    """
    logger.info(f"Starting streaming LangGraph completion for model '{model}'")

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

stream_run async

stream_run(run)

Stream an already prepared LangGraph invocation.

Source code in src/langgraph_openai_serve/graph/runner.py
async def stream_run(
    run: GraphRun,
) -> AsyncGenerator[LangGraphStreamEvent, None]:
    """Stream an already prepared LangGraph invocation."""
    stream_mode: list[StreamMode] = ["messages", "custom"]
    if run.config.supports(GraphFeature.INTERRUPTS):
        stream_mode.append("updates")

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

            if event.get("type") == "updates":
                interrupt = extract_interrupt(event.get("data"), run.thread_id)
                if interrupt is not None:
                    yield interrupt
                continue

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

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

text_from_message_event

text_from_message_event(event, run)

Extract visible text from a streamable LangGraph message event.

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

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

utils

MissingThreadIDError

Bases: ValueError

Raised when an interrupt-enabled graph request has no thread id.

openai_server

LangGraph OpenAI API Serve.

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

Examples:

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

LanggraphOpenaiServe

LanggraphOpenaiServe(graphs, app=None)

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

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

Attributes:

Name Type Description
app FastAPI

The host FastAPI application to mount the OpenAI API on.

graph_registry

The populated GraphRegistry containing the graphs to serve.

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

Parameters:

Name Type Description Default
app FastAPI | None

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

None
graphs GraphRegistry

A GraphRegistry instance containing the graphs to serve.

required

Raises:

Type Description
TypeError

If graphs is not a GraphRegistry instance.

Source code in src/langgraph_openai_serve/openai_server.py
def __init__(
    self,
    graphs: GraphRegistry,
    app: FastAPI | None = None,
):
    """Initialize the server with a FastAPI app and a populated graph registry.

    Args:
        app: The host FastAPI application to mount the OpenAI API on. If None,
            a new FastAPI app will be created.
        graphs: A GraphRegistry instance containing the graphs to serve.

    Raises:
        TypeError: If graphs is not a GraphRegistry instance.
    """
    if not isinstance(graphs, GraphRegistry):
        raise TypeError(
            "Invalid type for graphs parameter. Expected GraphRegistry."
        )

    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

    logger.info("Using provided GraphRegistry instance")
    self.graph_registry = graphs

    # Attach the registry to the host app for callers that inspect app state.
    self.app.state.graph_registry = self.graph_registry

    logger.info(
        f"Initialized LanggraphOpenaiServe with {len(self.graph_registry.registry)} graphs"
    )
    logger.info(
        f"Available graphs: {', '.join(self.graph_registry.get_graph_names())}"
    )

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):
    """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.
    """
    if prefix is None:
        prefix = settings.OPENAI_API_PREFIX
    else:
        prefix = normalize_openai_api_prefix(prefix)

    docs_url = "/docs" if settings.OPENAI_API_DOCS_ENABLED else None
    redoc_url = "/redoc" if settings.OPENAI_API_DOCS_ENABLED else None
    openapi_url = "/openapi.json" if settings.OPENAI_API_DOCS_ENABLED else None

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

    self.app.mount(prefix, openai_app, name="openai")

    logger.info(f"Bound OpenAI chat completion endpoints with prefix: {prefix}")

    return self

schemas

Models package for the LangGraph OpenAI compatible API.

utils

Utility functions.

fake_llm

Shared fake streaming model helpers for demos and tests.

stream_fake_chat_response async

stream_fake_chat_response(response, prompt)

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

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

message

InvalidChatMessageError

Bases: ValueError

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

convert_to_lc_messages

convert_to_lc_messages(messages)

Convert OpenAI messages to LangChain messages.

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

Parameters:

Name Type Description Default
messages list[ChatCompletionRequestMessage]

A list of OpenAI chat completion request messages to convert.

required

Returns:

Type Description
list[BaseMessage]

A list of LangChain message objects.

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

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

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

    Returns:
        A list of LangChain message objects.
    """

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