A high-throughput and memory-efficient inference and serving engine for LLMs
Fix included router missing path for `FastAPI >=0.137` (#45629)
Signed-off-by: Roger Wang <hey@rogerw.io> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Roger Wang committed
Jun 14, 2026 at 21:28 UTC
e8d3e22c884e95a7499ffcf37fa932751f0780df
1 file changed
+38
-1
vllm/entrypoints/serve/instrumentator/metrics.py
+38
-1
@@ -7,11 +7,48 @@ import regex as re
7
from fastapi import FastAPI, Response
8
from prometheus_client import make_asgi_app
9
from prometheus_fastapi_instrumentator import Instrumentator
10
-from starlette.routing import Mount
10
+from prometheus_fastapi_instrumentator import routing as _pfi_routing
11
+from starlette.routing import Match, Mount
12
+from starlette.types import Scope
13
14
from vllm.v1.metrics.prometheus import get_prometheus_registry
15
16
17
+def _patch_instrumentator_route_walk() -> None:
18
+ """Make prometheus-fastapi-instrumentator's route walk tolerate routes
19
+ without a ``.path``.
20
+
21
+ FastAPI >= 0.137 stores lazy ``_IncludedRouter`` objects in ``app.routes``;
22
+ these are ``BaseRoute`` subclasses with no ``.path`` attribute. The
23
+ instrumentator's ``_get_route_name`` (up to 8.0.0) reads ``route.path``
24
+ unconditionally, so every request raises ``AttributeError`` in the metrics
25
+ middleware and the server returns 500 (e.g. ``/health`` never goes ready).
26
+ Skip path-less routes; this only affects the metric handler label, not
27
+ request routing. Idempotent.
28
+ """
29
+
30
+ def _get_route_name(scope: Scope, routes, route_name=None):
31
+ for route in routes:
32
+ if getattr(route, "path", None) is None:
33
+ continue
34
+ match, child_scope = route.matches(scope)
35
+ if match == Match.FULL:
36
+ route_name = route.path
37
+ child_scope = {**scope, **child_scope}
38
+ if isinstance(route, Mount) and route.routes:
39
+ child = _get_route_name(child_scope, route.routes, route_name)
40
+ route_name = None if child is None else route_name + child
41
+ return route_name
42
+ elif match == Match.PARTIAL and route_name is None:
43
+ route_name = route.path
44
+ return None
45
+
46
+ _pfi_routing._get_route_name = _get_route_name
47
+
48
+
49
+_patch_instrumentator_route_walk()
50
+
51
+
52
class PrometheusResponse(Response):
53
media_type = prometheus_client.CONTENT_TYPE_LATEST
54