A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 9, 13:33 UTC
[Frontend] Frontend will only attach supported tasks corresponding entrypoints. (#33139)
Signed-off-by: wang.yuqi <yuqi.wang@daocloud.io> Signed-off-by: wang.yuqi <noooop@126.com> Co-authored-by: Cyrus Leung <cyrus.tl.leung@gmail.com>
wang.yuqi committed
Jan 27, 2026 at 20:15 UTC
76139d0801e95bf6c54352650a6f42e181b510be
10 files changed
+461
-339
tests/entrypoints/openai/test_transcription_validation_whisper.py
+8
-12
@@ -112,18 +112,14 @@ async def test_long_audio_request(mary_had_lamb, whisper_client):
112
@pytest.mark.asyncio
113
async def test_completion_endpoints(whisper_client):
114
# text to text model
115
- res = await whisper_client.chat.completions.create(
116
- model=MODEL_NAME,
117
- messages=[{"role": "system", "content": "You are a helpful assistant."}],
118
- )
119
- err = res.error
120
- assert err["code"] == 400
121
- assert err["message"] == "The model does not support Chat Completions API"
122
-
123
- res = await whisper_client.completions.create(model=MODEL_NAME, prompt="Hello")
124
- err = res.error
125
- assert err["code"] == 400
126
- assert err["message"] == "The model does not support Completions API"
115
+ with pytest.raises(openai.NotFoundError):
116
+ await whisper_client.chat.completions.create(
117
+ model=MODEL_NAME,
118
+ messages=[{"role": "system", "content": "You are a helpful assistant."}],
119
+ )
120
+
121
+ with pytest.raises(openai.NotFoundError):
122
+ await whisper_client.completions.create(model=MODEL_NAME, prompt="Hello")
123
124
125
@pytest.mark.asyncio
tests/entrypoints/openai/test_translation_validation.py
+6
-6
@@ -9,6 +9,7 @@ import json
9
import httpx
10
import librosa
11
import numpy as np
12
+import openai
13
import pytest
14
import pytest_asyncio
15
import soundfile as sf
@@ -52,12 +53,11 @@ async def test_non_asr_model(foscolo, rocm_aiter_fa_attention):
53
model_name, _get_server_args(rocm_aiter_fa_attention)
54
) as remote_server:
55
client = remote_server.get_async_client()
55
- res = await client.audio.translations.create(
56
- model=model_name, file=foscolo, temperature=0.0
57
- )
58
- err = res.error
59
- assert err["code"] == 400 and not res.text
60
- assert err["message"] == "The model does not support Translations API"
56
+
57
+ with pytest.raises(openai.NotFoundError):
58
+ await client.audio.translations.create(
59
+ model=model_name, file=foscolo, temperature=0.0
60
+ )
61
62
63
@pytest.mark.asyncio
tests/entrypoints/pooling/classify/test_online.py
+2
-2
@@ -401,7 +401,7 @@ async def test_score(server: RemoteOpenAIServer, model_name: str):
401
"documents": "pong",
402
},
403
)
404
- assert response.json()["error"]["type"] == "BadRequestError"
404
+ assert response.json()["detail"] == "Not Found"
405
406
407
@pytest.mark.asyncio
@@ -416,7 +416,7 @@ async def test_rerank(server: RemoteOpenAIServer, model_name: str):
416
"documents": ["pong"],
417
},
418
)
419
- assert response.json()["error"]["type"] == "BadRequestError"
419
+ assert response.json()["detail"] == "Not Found"
420
421
422
@pytest.mark.asyncio
vllm/entrypoints/openai/api_server.py
+45
-175
@@ -33,14 +33,10 @@ from starlette.types import ASGIApp, Message, Receive, Scope, Send
33
import vllm.envs as envs
34
from vllm.engine.arg_utils import AsyncEngineArgs
35
from vllm.engine.protocol import EngineClient
36
-from vllm.entrypoints.anthropic.serving import AnthropicServingMessages
36
from vllm.entrypoints.chat_utils import load_chat_template
37
from vllm.entrypoints.launcher import serve_http
38
from vllm.entrypoints.logger import RequestLogger
40
-from vllm.entrypoints.mcp.tool_server import DemoToolServer, MCPToolServer, ToolServer
41
-from vllm.entrypoints.openai.chat_completion.serving import OpenAIServingChat
39
from vllm.entrypoints.openai.cli_args import make_arg_parser, validate_parsed_serve_args
43
-from vllm.entrypoints.openai.completion.serving import OpenAIServingCompletion
40
from vllm.entrypoints.openai.engine.protocol import (
41
ErrorInfo,
42
ErrorResponse,
@@ -50,12 +46,6 @@ from vllm.entrypoints.openai.models.protocol import BaseModelPath
46
from vllm.entrypoints.openai.models.serving import (
47
OpenAIServingModels,
48
)
53
-from vllm.entrypoints.openai.responses.serving import OpenAIServingResponses
54
-from vllm.entrypoints.openai.translations.serving import (
55
- OpenAIServingTranscription,
56
- OpenAIServingTranslation,
57
-)
58
-from vllm.entrypoints.serve.disagg.serving import ServingTokens
49
from vllm.entrypoints.serve.elastic_ep.middleware import (
50
ScalingMiddleware,
51
)
@@ -70,6 +60,7 @@ from vllm.entrypoints.utils import (
60
from vllm.exceptions import VLLMValidationError
61
from vllm.logger import init_logger
62
from vllm.reasoning import ReasoningParserManager
63
+from vllm.tasks import POOLING_TASKS, SupportedTask
64
from vllm.tool_parsers import ToolParserManager
65
from vllm.usage.usage_lib import UsageContext
66
from vllm.utils.argparse_utils import FlexibleArgumentParser
@@ -513,7 +504,7 @@ def _log_non_streaming_response(response_body: list) -> None:
504
logger.info("response_body={<binary_data>}")
505
506
516
-def build_app(args: Namespace) -> FastAPI:
507
+def build_app(args: Namespace, supported_tasks: tuple["SupportedTask", ...]) -> FastAPI:
508
if args.disable_fastapi_docs:
509
app = FastAPI(
510
openapi_url=None, docs_url=None, redoc_url=None, lifespan=lifespan
@@ -523,52 +514,44 @@ def build_app(args: Namespace) -> FastAPI:
514
else:
515
app = FastAPI(lifespan=lifespan)
516
app.state.args = args
517
+ app.include_router(router)
518
+
519
from vllm.entrypoints.serve import register_vllm_serve_api_routers
520
521
register_vllm_serve_api_routers(app)
529
- from vllm.entrypoints.openai.chat_completion.api_router import (
530
- attach_router as register_chat_api_router,
531
- )
532
-
533
- register_chat_api_router(app)
522
535
- from vllm.entrypoints.openai.responses.api_router import (
536
- attach_router as register_responses_api_router,
537
- )
538
-
539
- register_responses_api_router(app)
540
- from vllm.entrypoints.openai.translations.api_router import (
541
- attach_router as register_translations_api_router,
523
+ from vllm.entrypoints.openai.models.api_router import (
524
+ attach_router as register_models_api_router,
525
)
526
544
- register_translations_api_router(app)
527
+ register_models_api_router(app)
528
546
- from vllm.entrypoints.openai.completion.api_router import (
547
- attach_router as register_completion_api_router,
529
+ from vllm.entrypoints.sagemaker.api_router import (
530
+ attach_router as register_sagemaker_api_router,
531
)
532
550
- register_completion_api_router(app)
551
- from vllm.entrypoints.anthropic.api_router import (
552
- attach_router as register_anthropic_api_router,
553
- )
533
+ register_sagemaker_api_router(app, supported_tasks)
534
555
- register_anthropic_api_router(app)
556
- from vllm.entrypoints.openai.models.api_router import (
557
- attach_router as register_models_api_router,
558
- )
535
+ if "generate" in supported_tasks:
536
+ from vllm.entrypoints.openai.generate.api_router import (
537
+ register_generate_api_routers,
538
+ )
539
560
- register_models_api_router(app)
561
- from vllm.entrypoints.sagemaker.routes import register_sagemaker_routes
540
+ register_generate_api_routers(app)
541
563
- register_sagemaker_routes(router)
564
- app.include_router(router)
542
+ if "transcription" in supported_tasks:
543
+ from vllm.entrypoints.openai.translations.api_router import (
544
+ attach_router as register_translations_api_router,
545
+ )
546
566
- app.root_path = args.root_path
547
+ register_translations_api_router(app)
548
568
- from vllm.entrypoints.pooling import register_pooling_api_routers
549
+ if any(task in POOLING_TASKS for task in supported_tasks):
550
+ from vllm.entrypoints.pooling import register_pooling_api_routers
551
570
- register_pooling_api_routers(app)
552
+ register_pooling_api_routers(app, supported_tasks)
553
554
+ app.root_path = args.root_path
555
app.add_middleware(
556
CORSMiddleware,
557
allow_origins=args.allowed_origins,
@@ -673,6 +656,7 @@ async def init_app_state(
656
engine_client: EngineClient,
657
state: State,
658
args: Namespace,
659
+ supported_tasks: tuple["SupportedTask", ...],
660
) -> None:
661
vllm_config = engine_client.vllm_config
662
@@ -694,28 +678,9 @@ async def init_app_state(
678
state.log_stats = not args.disable_log_stats
679
state.vllm_config = vllm_config
680
state.args = args
697
- supported_tasks = await engine_client.get_supported_tasks()
698
- logger.info("Supported tasks: %s", supported_tasks)
699
-
681
resolved_chat_template = load_chat_template(args.chat_template)
682
702
- if args.tool_server == "demo":
703
- tool_server: ToolServer | None = DemoToolServer()
704
- assert isinstance(tool_server, DemoToolServer)
705
- await tool_server.init_and_validate()
706
- elif args.tool_server:
707
- tool_server = MCPToolServer()
708
- await tool_server.add_tool_server(args.tool_server)
709
- else:
710
- tool_server = None
711
-
683
# Merge default_mm_loras into the static lora_modules
713
- default_mm_loras = (
714
- vllm_config.lora_config.default_mm_loras
715
- if vllm_config.lora_config is not None
716
- else {}
717
- )
718
-
684
default_mm_loras = (
685
vllm_config.lora_config.default_mm_loras
686
if vllm_config.lora_config is not None
@@ -729,66 +694,6 @@ async def init_app_state(
694
lora_modules=lora_modules,
695
)
696
await state.openai_serving_models.init_static_loras()
732
- state.openai_serving_responses = (
733
- OpenAIServingResponses(
734
- engine_client,
735
- state.openai_serving_models,
736
- request_logger=request_logger,
737
- chat_template=resolved_chat_template,
738
- chat_template_content_format=args.chat_template_content_format,
739
- return_tokens_as_token_ids=args.return_tokens_as_token_ids,
740
- enable_auto_tools=args.enable_auto_tool_choice,
741
- tool_parser=args.tool_call_parser,
742
- tool_server=tool_server,
743
- reasoning_parser=args.structured_outputs_config.reasoning_parser,
744
- enable_prompt_tokens_details=args.enable_prompt_tokens_details,
745
- enable_force_include_usage=args.enable_force_include_usage,
746
- enable_log_outputs=args.enable_log_outputs,
747
- log_error_stack=args.log_error_stack,
748
- )
749
- if "generate" in supported_tasks
750
- else None
751
- )
752
- state.openai_serving_chat = (
753
- OpenAIServingChat(
754
- engine_client,
755
- state.openai_serving_models,
756
- args.response_role,
757
- request_logger=request_logger,
758
- chat_template=resolved_chat_template,
759
- chat_template_content_format=args.chat_template_content_format,
760
- default_chat_template_kwargs=args.default_chat_template_kwargs,
761
- trust_request_chat_template=args.trust_request_chat_template,
762
- return_tokens_as_token_ids=args.return_tokens_as_token_ids,
763
- enable_auto_tools=args.enable_auto_tool_choice,
764
- exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
765
- tool_parser=args.tool_call_parser,
766
- reasoning_parser=args.structured_outputs_config.reasoning_parser,
767
- enable_prompt_tokens_details=args.enable_prompt_tokens_details,
768
- enable_force_include_usage=args.enable_force_include_usage,
769
- enable_log_outputs=args.enable_log_outputs,
770
- enable_log_deltas=args.enable_log_deltas,
771
- log_error_stack=args.log_error_stack,
772
- )
773
- if "generate" in supported_tasks
774
- else None
775
- )
776
- # Warm up chat template processing to avoid first-request latency
777
- if state.openai_serving_chat is not None:
778
- await state.openai_serving_chat.warmup()
779
- state.openai_serving_completion = (
780
- OpenAIServingCompletion(
781
- engine_client,
782
- state.openai_serving_models,
783
- request_logger=request_logger,
784
- return_tokens_as_token_ids=args.return_tokens_as_token_ids,
785
- enable_prompt_tokens_details=args.enable_prompt_tokens_details,
786
- enable_force_include_usage=args.enable_force_include_usage,
787
- log_error_stack=args.log_error_stack,
788
- )
789
- if "generate" in supported_tasks
790
- else None
791
- )
697
state.openai_serving_tokenization = OpenAIServingTokenization(
698
engine_client,
699
state.openai_serving_models,
@@ -798,64 +703,27 @@ async def init_app_state(
703
trust_request_chat_template=args.trust_request_chat_template,
704
log_error_stack=args.log_error_stack,
705
)
801
- state.openai_serving_transcription = (
802
- OpenAIServingTranscription(
803
- engine_client,
804
- state.openai_serving_models,
805
- request_logger=request_logger,
806
- log_error_stack=args.log_error_stack,
807
- enable_force_include_usage=args.enable_force_include_usage,
808
- )
809
- if "transcription" in supported_tasks
810
- else None
811
- )
812
- state.openai_serving_translation = (
813
- OpenAIServingTranslation(
814
- engine_client,
815
- state.openai_serving_models,
816
- request_logger=request_logger,
817
- log_error_stack=args.log_error_stack,
818
- enable_force_include_usage=args.enable_force_include_usage,
706
+
707
+ if "generate" in supported_tasks:
708
+ from vllm.entrypoints.openai.generate.api_router import init_generate_state
709
+
710
+ await init_generate_state(
711
+ engine_client, state, args, request_logger, supported_tasks
712
)
820
- if "transcription" in supported_tasks
821
- else None
822
- )
823
- state.anthropic_serving_messages = (
824
- AnthropicServingMessages(
825
- engine_client,
826
- state.openai_serving_models,
827
- args.response_role,
828
- request_logger=request_logger,
829
- chat_template=resolved_chat_template,
830
- chat_template_content_format=args.chat_template_content_format,
831
- return_tokens_as_token_ids=args.return_tokens_as_token_ids,
832
- enable_auto_tools=args.enable_auto_tool_choice,
833
- tool_parser=args.tool_call_parser,
834
- reasoning_parser=args.structured_outputs_config.reasoning_parser,
835
- enable_prompt_tokens_details=args.enable_prompt_tokens_details,
836
- enable_force_include_usage=args.enable_force_include_usage,
713
+
714
+ if "transcription" in supported_tasks:
715
+ from vllm.entrypoints.openai.translations.api_router import (
716
+ init_transcription_state,
717
)
838
- if "generate" in supported_tasks
839
- else None
840
- )
841
- state.serving_tokens = (
842
- ServingTokens(
843
- engine_client,
844
- state.openai_serving_models,
845
- request_logger=request_logger,
846
- return_tokens_as_token_ids=args.return_tokens_as_token_ids,
847
- log_error_stack=args.log_error_stack,
848
- enable_prompt_tokens_details=args.enable_prompt_tokens_details,
849
- enable_log_outputs=args.enable_log_outputs,
850
- force_no_detokenize=args.tokens_only,
718
+
719
+ init_transcription_state(
720
+ engine_client, state, args, request_logger, supported_tasks
721
)
852
- if "generate" in supported_tasks
853
- else None
854
- )
722
856
- from vllm.entrypoints.pooling import init_pooling_state
723
+ if any(task in POOLING_TASKS for task in supported_tasks):
724
+ from vllm.entrypoints.pooling import init_pooling_state
725
858
- await init_pooling_state(engine_client, state, args)
726
+ init_pooling_state(engine_client, state, args, request_logger, supported_tasks)
727
728
state.enable_server_load_tracking = args.enable_server_load_tracking
729
state.server_load_metrics = 0
@@ -972,9 +840,11 @@ async def run_server_worker(
840
args,
841
client_config=client_config,
842
) as engine_client:
975
- app = build_app(args)
843
+ supported_tasks = await engine_client.get_supported_tasks()
844
+ logger.info("Supported tasks: %s", supported_tasks)
845
977
- await init_app_state(engine_client, app.state, args)
846
+ app = build_app(args, supported_tasks)
847
+ await init_app_state(engine_client, app.state, args, supported_tasks)
848
849
logger.info(
850
"Starting vLLM API server %d on %s",
vllm/entrypoints/openai/generate/__init__.py
vllm/entrypoints/openai/generate/api_router.py
new
+166
@@ -0,0 +1,166 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from typing import TYPE_CHECKING
4
+
5
+from fastapi import FastAPI
6
+
7
+if TYPE_CHECKING:
8
+ from argparse import Namespace
9
+
10
+ from starlette.datastructures import State
11
+
12
+ from vllm.engine.protocol import EngineClient
13
+ from vllm.entrypoints.logger import RequestLogger
14
+ from vllm.tasks import SupportedTask
15
+else:
16
+ RequestLogger = object
17
+
18
+
19
+def register_generate_api_routers(app: FastAPI):
20
+ from vllm.entrypoints.openai.chat_completion.api_router import (
21
+ attach_router as register_chat_api_router,
22
+ )
23
+
24
+ register_chat_api_router(app)
25
+
26
+ from vllm.entrypoints.openai.responses.api_router import (
27
+ attach_router as register_responses_api_router,
28
+ )
29
+
30
+ register_responses_api_router(app)
31
+
32
+ from vllm.entrypoints.openai.completion.api_router import (
33
+ attach_router as register_completion_api_router,
34
+ )
35
+
36
+ register_completion_api_router(app)
37
+
38
+ from vllm.entrypoints.anthropic.api_router import (
39
+ attach_router as register_anthropic_api_router,
40
+ )
41
+
42
+ register_anthropic_api_router(app)
43
+
44
+
45
+async def init_generate_state(
46
+ engine_client: "EngineClient",
47
+ state: "State",
48
+ args: "Namespace",
49
+ request_logger: RequestLogger | None,
50
+ supported_tasks: tuple["SupportedTask", ...],
51
+):
52
+ from vllm.entrypoints.anthropic.serving import AnthropicServingMessages
53
+ from vllm.entrypoints.chat_utils import load_chat_template
54
+ from vllm.entrypoints.mcp.tool_server import (
55
+ DemoToolServer,
56
+ MCPToolServer,
57
+ ToolServer,
58
+ )
59
+ from vllm.entrypoints.openai.chat_completion.serving import OpenAIServingChat
60
+ from vllm.entrypoints.openai.completion.serving import OpenAIServingCompletion
61
+ from vllm.entrypoints.openai.responses.serving import OpenAIServingResponses
62
+ from vllm.entrypoints.serve.disagg.serving import ServingTokens
63
+
64
+ if args.tool_server == "demo":
65
+ tool_server: ToolServer | None = DemoToolServer()
66
+ assert isinstance(tool_server, DemoToolServer)
67
+ await tool_server.init_and_validate()
68
+ elif args.tool_server:
69
+ tool_server = MCPToolServer()
70
+ await tool_server.add_tool_server(args.tool_server)
71
+ else:
72
+ tool_server = None
73
+ resolved_chat_template = load_chat_template(args.chat_template)
74
+
75
+ state.openai_serving_responses = (
76
+ OpenAIServingResponses(
77
+ engine_client,
78
+ state.openai_serving_models,
79
+ request_logger=request_logger,
80
+ chat_template=resolved_chat_template,
81
+ chat_template_content_format=args.chat_template_content_format,
82
+ return_tokens_as_token_ids=args.return_tokens_as_token_ids,
83
+ enable_auto_tools=args.enable_auto_tool_choice,
84
+ tool_parser=args.tool_call_parser,
85
+ tool_server=tool_server,
86
+ reasoning_parser=args.structured_outputs_config.reasoning_parser,
87
+ enable_prompt_tokens_details=args.enable_prompt_tokens_details,
88
+ enable_force_include_usage=args.enable_force_include_usage,
89
+ enable_log_outputs=args.enable_log_outputs,
90
+ log_error_stack=args.log_error_stack,
91
+ )
92
+ if "generate" in supported_tasks
93
+ else None
94
+ )
95
+ state.openai_serving_chat = (
96
+ OpenAIServingChat(
97
+ engine_client,
98
+ state.openai_serving_models,
99
+ args.response_role,
100
+ request_logger=request_logger,
101
+ chat_template=resolved_chat_template,
102
+ chat_template_content_format=args.chat_template_content_format,
103
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
104
+ trust_request_chat_template=args.trust_request_chat_template,
105
+ return_tokens_as_token_ids=args.return_tokens_as_token_ids,
106
+ enable_auto_tools=args.enable_auto_tool_choice,
107
+ exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
108
+ tool_parser=args.tool_call_parser,
109
+ reasoning_parser=args.structured_outputs_config.reasoning_parser,
110
+ enable_prompt_tokens_details=args.enable_prompt_tokens_details,
111
+ enable_force_include_usage=args.enable_force_include_usage,
112
+ enable_log_outputs=args.enable_log_outputs,
113
+ enable_log_deltas=args.enable_log_deltas,
114
+ log_error_stack=args.log_error_stack,
115
+ )
116
+ if "generate" in supported_tasks
117
+ else None
118
+ )
119
+ # Warm up chat template processing to avoid first-request latency
120
+ if state.openai_serving_chat is not None:
121
+ await state.openai_serving_chat.warmup()
122
+ state.openai_serving_completion = (
123
+ OpenAIServingCompletion(
124
+ engine_client,
125
+ state.openai_serving_models,
126
+ request_logger=request_logger,
127
+ return_tokens_as_token_ids=args.return_tokens_as_token_ids,
128
+ enable_prompt_tokens_details=args.enable_prompt_tokens_details,
129
+ enable_force_include_usage=args.enable_force_include_usage,
130
+ log_error_stack=args.log_error_stack,
131
+ )
132
+ if "generate" in supported_tasks
133
+ else None
134
+ )
135
+ state.anthropic_serving_messages = (
136
+ AnthropicServingMessages(
137
+ engine_client,
138
+ state.openai_serving_models,
139
+ args.response_role,
140
+ request_logger=request_logger,
141
+ chat_template=resolved_chat_template,
142
+ chat_template_content_format=args.chat_template_content_format,
143
+ return_tokens_as_token_ids=args.return_tokens_as_token_ids,
144
+ enable_auto_tools=args.enable_auto_tool_choice,
145
+ tool_parser=args.tool_call_parser,
146
+ reasoning_parser=args.structured_outputs_config.reasoning_parser,
147
+ enable_prompt_tokens_details=args.enable_prompt_tokens_details,
148
+ enable_force_include_usage=args.enable_force_include_usage,
149
+ )
150
+ if "generate" in supported_tasks
151
+ else None
152
+ )
153
+ state.serving_tokens = (
154
+ ServingTokens(
155
+ engine_client,
156
+ state.openai_serving_models,
157
+ request_logger=request_logger,
158
+ return_tokens_as_token_ids=args.return_tokens_as_token_ids,
159
+ log_error_stack=args.log_error_stack,
160
+ enable_prompt_tokens_details=args.enable_prompt_tokens_details,
161
+ enable_log_outputs=args.enable_log_outputs,
162
+ force_no_detokenize=args.tokens_only,
163
+ )
164
+ if "generate" in supported_tasks
165
+ else None
166
+ )
vllm/entrypoints/openai/translations/api_router.py
+43
-1
@@ -3,7 +3,7 @@
3
4
5
from http import HTTPStatus
6
-from typing import Annotated
6
+from typing import TYPE_CHECKING, Annotated
7
8
from fastapi import APIRouter, FastAPI, Form, Request
9
from fastapi.responses import JSONResponse, StreamingResponse
@@ -25,6 +25,17 @@ from vllm.entrypoints.utils import (
25
)
26
from vllm.logger import init_logger
27
28
+if TYPE_CHECKING:
29
+ from argparse import Namespace
30
+
31
+ from starlette.datastructures import State
32
+
33
+ from vllm.engine.protocol import EngineClient
34
+ from vllm.entrypoints.logger import RequestLogger
35
+ from vllm.tasks import SupportedTask
36
+else:
37
+ RequestLogger = object
38
+
39
logger = init_logger(__name__)
40
41
router = APIRouter()
@@ -115,3 +126,34 @@ async def create_translations(
126
127
def attach_router(app: FastAPI):
128
app.include_router(router)
129
+
130
+
131
+def init_transcription_state(
132
+ engine_client: "EngineClient",
133
+ state: "State",
134
+ args: "Namespace",
135
+ request_logger: RequestLogger | None,
136
+ supported_tasks: tuple["SupportedTask", ...],
137
+):
138
+ state.openai_serving_transcription = (
139
+ OpenAIServingTranscription(
140
+ engine_client,
141
+ state.openai_serving_models,
142
+ request_logger=request_logger,
143
+ log_error_stack=args.log_error_stack,
144
+ enable_force_include_usage=args.enable_force_include_usage,
145
+ )
146
+ if "transcription" in supported_tasks
147
+ else None
148
+ )
149
+ state.openai_serving_translation = (
150
+ OpenAIServingTranslation(
151
+ engine_client,
152
+ state.openai_serving_models,
153
+ request_logger=request_logger,
154
+ log_error_stack=args.log_error_stack,
155
+ enable_force_include_usage=args.enable_force_include_usage,
156
+ )
157
+ if "transcription" in supported_tasks
158
+ else None
159
+ )
vllm/entrypoints/pooling/__init__.py
+31
-17
@@ -11,40 +11,54 @@ if TYPE_CHECKING:
11
from starlette.datastructures import State
12
13
from vllm.engine.protocol import EngineClient
14
+ from vllm.entrypoints.logger import RequestLogger
15
+ from vllm.tasks import SupportedTask
16
+else:
17
+ RequestLogger = object
18
+ SupportedTask = object
19
20
16
-def register_pooling_api_routers(app: FastAPI):
17
- from vllm.entrypoints.pooling.classify.api_router import router as classify_router
18
- from vllm.entrypoints.pooling.embed.api_router import router as embed_router
21
+def register_pooling_api_routers(
22
+ app: FastAPI, supported_tasks: tuple["SupportedTask", ...]
23
+):
24
from vllm.entrypoints.pooling.pooling.api_router import router as pooling_router
20
- from vllm.entrypoints.pooling.score.api_router import router as score_router
25
22
- app.include_router(classify_router)
23
- app.include_router(embed_router)
24
- app.include_router(score_router)
26
app.include_router(pooling_router)
27
28
+ if "classify" in supported_tasks:
29
+ from vllm.entrypoints.pooling.classify.api_router import (
30
+ router as classify_router,
31
+ )
32
+
33
+ app.include_router(classify_router)
34
+
35
+ if "embed" in supported_tasks:
36
+ from vllm.entrypoints.pooling.embed.api_router import router as embed_router
37
+
38
+ app.include_router(embed_router)
39
28
-async def init_pooling_state(
29
- engine_client: "EngineClient", state: "State", args: "Namespace"
40
+ if "score" in supported_tasks or "embed" in supported_tasks:
41
+ from vllm.entrypoints.pooling.score.api_router import router as score_router
42
+
43
+ app.include_router(score_router)
44
+
45
+
46
+def init_pooling_state(
47
+ engine_client: "EngineClient",
48
+ state: "State",
49
+ args: "Namespace",
50
+ request_logger: RequestLogger | None,
51
+ supported_tasks: tuple["SupportedTask", ...],
52
):
53
from vllm.entrypoints.chat_utils import load_chat_template
32
- from vllm.entrypoints.logger import RequestLogger
54
from vllm.entrypoints.pooling.classify.serving import ServingClassification
55
from vllm.entrypoints.pooling.embed.serving import OpenAIServingEmbedding
56
from vllm.entrypoints.pooling.pooling.serving import OpenAIServingPooling
57
from vllm.entrypoints.pooling.score.serving import ServingScores
58
from vllm.tasks import POOLING_TASKS
59
39
- supported_tasks = await engine_client.get_supported_tasks()
40
-
60
resolved_chat_template = load_chat_template(args.chat_template)
61
43
- if args.enable_log_requests:
44
- request_logger = RequestLogger(max_log_len=args.max_log_len)
45
- else:
46
- request_logger = None
47
-
62
state.openai_serving_pooling = (
63
(
64
OpenAIServingPooling(
vllm/entrypoints/sagemaker/api_router.py
new
+160
@@ -0,0 +1,160 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import json
4
+from collections.abc import Awaitable, Callable
5
+from http import HTTPStatus
6
+from typing import Any
7
+
8
+import model_hosting_container_standards.sagemaker as sagemaker_standards
9
+import pydantic
10
+from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request
11
+from fastapi.responses import JSONResponse, Response
12
+
13
+from vllm.entrypoints.openai.api_server import base
14
+from vllm.entrypoints.openai.engine.protocol import ErrorResponse
15
+from vllm.entrypoints.openai.engine.serving import OpenAIServing
16
+from vllm.entrypoints.openai.utils import validate_json_request
17
+from vllm.entrypoints.serve.instrumentator.health import health
18
+from vllm.tasks import POOLING_TASKS, SupportedTask
19
+
20
+# TODO: RequestType = TypeForm[BaseModel] when recognized by type checkers
21
+# (requires typing_extensions >= 4.13)
22
+RequestType = Any
23
+GetHandlerFn = Callable[[Request], OpenAIServing | None]
24
+EndpointFn = Callable[[RequestType, Request], Awaitable[Any]]
25
+
26
+
27
+def get_invocation_types(supported_tasks: tuple["SupportedTask", ...]):
28
+ # NOTE: Items defined earlier take higher priority
29
+ INVOCATION_TYPES: list[tuple[RequestType, tuple[GetHandlerFn, EndpointFn]]] = []
30
+
31
+ if "generate" in supported_tasks:
32
+ from vllm.entrypoints.openai.chat_completion.api_router import (
33
+ chat,
34
+ create_chat_completion,
35
+ )
36
+ from vllm.entrypoints.openai.chat_completion.protocol import (
37
+ ChatCompletionRequest,
38
+ )
39
+ from vllm.entrypoints.openai.completion.api_router import (
40
+ completion,
41
+ create_completion,
42
+ )
43
+ from vllm.entrypoints.openai.completion.protocol import CompletionRequest
44
+
45
+ INVOCATION_TYPES += [
46
+ (ChatCompletionRequest, (chat, create_chat_completion)),
47
+ (CompletionRequest, (completion, create_completion)),
48
+ ]
49
+
50
+ if "embed" in supported_tasks:
51
+ from vllm.entrypoints.pooling.embed.api_router import (
52
+ create_embedding,
53
+ embedding,
54
+ )
55
+ from vllm.entrypoints.pooling.embed.protocol import EmbeddingRequest
56
+
57
+ INVOCATION_TYPES += [
58
+ (EmbeddingRequest, (embedding, create_embedding)),
59
+ ]
60
+
61
+ if "classify" in supported_tasks:
62
+ from vllm.entrypoints.pooling.classify.api_router import (
63
+ classify,
64
+ create_classify,
65
+ )
66
+ from vllm.entrypoints.pooling.classify.protocol import ClassificationRequest
67
+
68
+ INVOCATION_TYPES += [
69
+ (ClassificationRequest, (classify, create_classify)),
70
+ ]
71
+
72
+ if "score" in supported_tasks:
73
+ from vllm.entrypoints.pooling.score.api_router import do_rerank, rerank
74
+ from vllm.entrypoints.pooling.score.protocol import RerankRequest
75
+
76
+ INVOCATION_TYPES += [
77
+ (RerankRequest, (rerank, do_rerank)),
78
+ ]
79
+
80
+ if "score" in supported_tasks or "embed" in supported_tasks:
81
+ from vllm.entrypoints.pooling.score.api_router import create_score, score
82
+ from vllm.entrypoints.pooling.score.protocol import ScoreRequest
83
+
84
+ INVOCATION_TYPES += [
85
+ (ScoreRequest, (score, create_score)),
86
+ ]
87
+
88
+ if any(task in POOLING_TASKS for task in supported_tasks):
89
+ from vllm.entrypoints.pooling.pooling.api_router import create_pooling, pooling
90
+ from vllm.entrypoints.pooling.pooling.protocol import PoolingRequest
91
+
92
+ INVOCATION_TYPES += [
93
+ (PoolingRequest, (pooling, create_pooling)),
94
+ ]
95
+
96
+ return INVOCATION_TYPES
97
+
98
+
99
+def attach_router(app: FastAPI, supported_tasks: tuple["SupportedTask", ...]):
100
+ router = APIRouter()
101
+
102
+ # NOTE: Construct the TypeAdapters only once
103
+ INVOCATION_TYPES = get_invocation_types(supported_tasks)
104
+ INVOCATION_VALIDATORS = [
105
+ (pydantic.TypeAdapter(request_type), (get_handler, endpoint))
106
+ for request_type, (get_handler, endpoint) in INVOCATION_TYPES
107
+ ]
108
+
109
+ @router.post("/ping", response_class=Response)
110
+ @router.get("/ping", response_class=Response)
111
+ @sagemaker_standards.register_ping_handler
112
+ async def ping(raw_request: Request) -> Response:
113
+ """Ping check. Endpoint required for SageMaker"""
114
+ return await health(raw_request)
115
+
116
+ @router.post(
117
+ "/invocations",
118
+ dependencies=[Depends(validate_json_request)],
119
+ responses={
120
+ HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
121
+ HTTPStatus.UNSUPPORTED_MEDIA_TYPE.value: {"model": ErrorResponse},
122
+ HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
123
+ },
124
+ )
125
+ @sagemaker_standards.register_invocation_handler
126
+ @sagemaker_standards.stateful_session_manager()
127
+ @sagemaker_standards.inject_adapter_id(adapter_path="model")
128
+ async def invocations(raw_request: Request):
129
+ """For SageMaker, routes requests based on the request type."""
130
+ try:
131
+ body = await raw_request.json()
132
+ except json.JSONDecodeError as e:
133
+ raise HTTPException(
134
+ status_code=HTTPStatus.BAD_REQUEST.value,
135
+ detail=f"JSON decode error: {e}",
136
+ ) from e
137
+
138
+ valid_endpoints = [
139
+ (validator, endpoint)
140
+ for validator, (get_handler, endpoint) in INVOCATION_VALIDATORS
141
+ if get_handler(raw_request) is not None
142
+ ]
143
+
144
+ for request_validator, endpoint in valid_endpoints:
145
+ try:
146
+ request = request_validator.validate_python(body)
147
+ except pydantic.ValidationError:
148
+ continue
149
+
150
+ return await endpoint(request, raw_request)
151
+
152
+ type_names = [
153
+ t.__name__ if isinstance(t := validator._type, type) else str(t)
154
+ for validator, _ in valid_endpoints
155
+ ]
156
+ msg = f"Cannot find suitable handler for request. Expected one of: {type_names}"
157
+ res = base(raw_request).create_error_response(message=msg)
158
+ return JSONResponse(content=res.model_dump(), status_code=res.error.code)
159
+
160
+ app.include_router(router)
vllm/entrypoints/sagemaker/routes.py
deleted
-126
@@ -1,126 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-import json
4
-from collections.abc import Awaitable, Callable
5
-from http import HTTPStatus
6
-from typing import Any
7
-
8
-import model_hosting_container_standards.sagemaker as sagemaker_standards
9
-import pydantic
10
-from fastapi import APIRouter, Depends, HTTPException, Request
11
-from fastapi.responses import JSONResponse, Response
12
-
13
-from vllm.entrypoints.openai.api_server import (
14
- base,
15
-)
16
-from vllm.entrypoints.openai.chat_completion.api_router import (
17
- chat,
18
- create_chat_completion,
19
-)
20
-from vllm.entrypoints.openai.chat_completion.protocol import (
21
- ChatCompletionRequest,
22
-)
23
-from vllm.entrypoints.openai.completion.api_router import (
24
- completion,
25
- create_completion,
26
-)
27
-from vllm.entrypoints.openai.completion.protocol import (
28
- CompletionRequest,
29
-)
30
-from vllm.entrypoints.openai.engine.protocol import (
31
- ErrorResponse,
32
-)
33
-from vllm.entrypoints.openai.engine.serving import OpenAIServing
34
-from vllm.entrypoints.openai.utils import validate_json_request
35
-from vllm.entrypoints.pooling.classify.api_router import classify, create_classify
36
-from vllm.entrypoints.pooling.classify.protocol import ClassificationRequest
37
-from vllm.entrypoints.pooling.embed.api_router import create_embedding, embedding
38
-from vllm.entrypoints.pooling.embed.protocol import EmbeddingRequest
39
-from vllm.entrypoints.pooling.pooling.api_router import create_pooling, pooling
40
-from vllm.entrypoints.pooling.pooling.protocol import PoolingRequest
41
-from vllm.entrypoints.pooling.score.api_router import (
42
- create_score,
43
- do_rerank,
44
- rerank,
45
- score,
46
-)
47
-from vllm.entrypoints.pooling.score.protocol import RerankRequest, ScoreRequest
48
-from vllm.entrypoints.serve.instrumentator.health import health
49
-
50
-# TODO: RequestType = TypeForm[BaseModel] when recognized by type checkers
51
-# (requires typing_extensions >= 4.13)
52
-RequestType = Any
53
-GetHandlerFn = Callable[[Request], OpenAIServing | None]
54
-EndpointFn = Callable[[RequestType, Request], Awaitable[Any]]
55
-
56
-# NOTE: Items defined earlier take higher priority
57
-INVOCATION_TYPES: list[tuple[RequestType, tuple[GetHandlerFn, EndpointFn]]] = [
58
- (ChatCompletionRequest, (chat, create_chat_completion)),
59
- (CompletionRequest, (completion, create_completion)),
60
- (EmbeddingRequest, (embedding, create_embedding)),
61
- (ClassificationRequest, (classify, create_classify)),
62
- (ScoreRequest, (score, create_score)),
63
- (RerankRequest, (rerank, do_rerank)),
64
- (PoolingRequest, (pooling, create_pooling)),
65
-]
66
-
67
-# NOTE: Construct the TypeAdapters only once
68
-INVOCATION_VALIDATORS = [
69
- (pydantic.TypeAdapter(request_type), (get_handler, endpoint))
70
- for request_type, (get_handler, endpoint) in INVOCATION_TYPES
71
-]
72
-
73
-
74
-def register_sagemaker_routes(router: APIRouter):
75
- @router.post("/ping", response_class=Response)
76
- @router.get("/ping", response_class=Response)
77
- @sagemaker_standards.register_ping_handler
78
- async def ping(raw_request: Request) -> Response:
79
- """Ping check. Endpoint required for SageMaker"""
80
- return await health(raw_request)
81
-
82
- @router.post(
83
- "/invocations",
84
- dependencies=[Depends(validate_json_request)],
85
- responses={
86
- HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
87
- HTTPStatus.UNSUPPORTED_MEDIA_TYPE.value: {"model": ErrorResponse},
88
- HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
89
- },
90
- )
91
- @sagemaker_standards.register_invocation_handler
92
- @sagemaker_standards.stateful_session_manager()
93
- @sagemaker_standards.inject_adapter_id(adapter_path="model")
94
- async def invocations(raw_request: Request):
95
- """For SageMaker, routes requests based on the request type."""
96
- try:
97
- body = await raw_request.json()
98
- except json.JSONDecodeError as e:
99
- raise HTTPException(
100
- status_code=HTTPStatus.BAD_REQUEST.value,
101
- detail=f"JSON decode error: {e}",
102
- ) from e
103
-
104
- valid_endpoints = [
105
- (validator, endpoint)
106
- for validator, (get_handler, endpoint) in INVOCATION_VALIDATORS
107
- if get_handler(raw_request) is not None
108
- ]
109
-
110
- for request_validator, endpoint in valid_endpoints:
111
- try:
112
- request = request_validator.validate_python(body)
113
- except pydantic.ValidationError:
114
- continue
115
-
116
- return await endpoint(request, raw_request)
117
-
118
- type_names = [
119
- t.__name__ if isinstance(t := validator._type, type) else str(t)
120
- for validator, _ in valid_endpoints
121
- ]
122
- msg = f"Cannot find suitable handler for request. Expected one of: {type_names}"
123
- res = base(raw_request).create_error_response(message=msg)
124
- return JSONResponse(content=res.model_dump(), status_code=res.error.code)
125
-
126
- return router