# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project """Contract for `vllm.endpoint_plugins` entry points. An endpoint plugin adds HTTP routes to the OpenAI compatible API server. Its scope is HTTP surface only. It registers routes and optionally per app state used by those routes. It must not open new paths into the engine by reaching the engine the same way an in-tree serving handler does via `EngineClient` (e.g. `engine_client.collective_rpc(...)`). If a plugin also needs engine side behavior (a new worker side RPC method, a custom stat, etc.) pair this entry point with one registered under `vllm.general_plugins` (see `vllm/plugins/__init__.py`). The `general_plugins` entry installs the engine side method and the `endpoint_plugins` entry exposes it over HTTP. The two are registered and loaded independently where neither implies the other. Plugins are opt-in. See `load_endpoint_plugins` in `vllm/plugins/__init__.py` for the loading/gating rules and `docs/usage/security.md` for the security posture of exposing plugin defined routes. The CPU only render server (see `build_and_serve_renderer` in `vllm/entrypoints/openai/api_server.py`) has no `EngineClient`. A plugin eligible for the `render` task (`required_tasks` is `None` or includes `"render"`) still gets `attach_router` called but `init_state` receives `engine_client=None`. Plugins that cannot function without an engine should either exclude `"render"` from `required_tasks` or check for `None` in `init_state`/their route handlers and degrade gracefully. """ from argparse import Namespace from typing import TYPE_CHECKING, Protocol, runtime_checkable from fastapi import FastAPI from starlette.datastructures import State if TYPE_CHECKING: from vllm.engine.protocol import EngineClient from vllm.tasks import SupportedTask @runtime_checkable class EndpointPlugin(Protocol): """Protocol implemented by `vllm.endpoint_plugins` entry point factories. An entry point registered under the `vllm.endpoint_plugins` group must resolve to a zero argument callable (a class or factory function) that returns an object satisfying this protocol. """ name: str """Unique plugin name used in logs and for `VLLM_PLUGINS` allowlisting.""" required_tasks: "tuple[SupportedTask, ...] | None" """Tasks the server must support for this plugin to be loaded. The plugin is loaded only if this set intersects the server's `supported_tasks`. `None` means the plugin has no task requirement and is always eligible (subject to the `VLLM_PLUGINS` allowlist). """ def attach_router(self, app: FastAPI) -> None: """Register this plugin's routes on `app`. Called once during `build_app()` after all core routers have been attached. Routes attached here can shadow core routes with the same path. There is currently no conflict enforcement (see RFC #46565 follow ups). """ ... async def init_state( self, engine_client: "EngineClient | None", state: State, args: Namespace ) -> None: """Initialize per app state consumed by this plugin's routes. Called once during `init_app_state()` after core state has been initialized. Use `engine_client` (e.g. `collective_rpc`) to reach the engine. Do not open new engine access paths. `engine_client` is `None` on the CPU only render server which has no engine. This only happens for plugins eligible for the `render` task (`required_tasks` is `None` or includes `"render"`). Handle `None` explicitly (e.g. skip engine dependent setup, or have route handlers return an error) if the plugin is loadable for `render` but cannot function without an engine. """ ...