main
py 128 lines 4.21 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 from collections.abc import AsyncGenerator
6 from http import HTTPStatus
7
8 from fastapi import APIRouter, Depends, FastAPI, Request
9 from fastapi.responses import JSONResponse, StreamingResponse
10
11 from vllm.entrypoints.openai.responses.protocol import (
12 ResponsesRequest,
13 ResponsesResponse,
14 StreamingResponsesResponse,
15 )
16 from vllm.entrypoints.openai.responses.serving import OpenAIServingResponses
17 from vllm.entrypoints.serve.engine.protocol import ErrorResponse
18 from vllm.entrypoints.serve.utils.api_utils import (
19 load_aware_call,
20 validate_json_request,
21 with_cancellation,
22 )
23 from vllm.logger import init_logger
24
25 logger = init_logger(__name__)
26
27 router = APIRouter()
28
29
30 def responses(request: Request) -> OpenAIServingResponses | None:
31 return request.app.state.openai_serving_responses
32
33
34 async def _convert_stream_to_sse_events(
35 generator: AsyncGenerator[StreamingResponsesResponse, None],
36 ) -> AsyncGenerator[str, None]:
37 """Convert the generator to a stream of events in SSE format."""
38 async for event in generator:
39 event_type = getattr(event, "type", "unknown")
40 # https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
41 event_data = (
42 f"event: {event_type}\ndata: "
43 f"{event.model_dump_json(indent=None, by_alias=True)}\n\n"
44 )
45 yield event_data
46
47
48 @router.post(
49 "/v1/responses",
50 dependencies=[Depends(validate_json_request)],
51 responses={
52 HTTPStatus.OK.value: {"content": {"text/event-stream": {}}},
53 HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
54 HTTPStatus.NOT_FOUND.value: {"model": ErrorResponse},
55 HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
56 },
57 )
58 @with_cancellation
59 @load_aware_call
60 async def create_responses(request: ResponsesRequest, raw_request: Request):
61 handler = responses(raw_request)
62 if handler is None:
63 raise NotImplementedError("The model does not support Responses API")
64
65 generator = await handler.create_responses(request, raw_request)
66
67 if isinstance(generator, ErrorResponse):
68 return JSONResponse(
69 content=generator.model_dump(mode="json", by_alias=True),
70 status_code=generator.error.code,
71 )
72 elif isinstance(generator, ResponsesResponse):
73 return JSONResponse(content=generator.model_dump(mode="json", by_alias=True))
74
75 return StreamingResponse(
76 content=_convert_stream_to_sse_events(generator), media_type="text/event-stream"
77 )
78
79
80 @router.get("/v1/responses/{response_id}")
81 @load_aware_call
82 async def retrieve_responses(
83 response_id: str,
84 raw_request: Request,
85 starting_after: int | None = None,
86 stream: bool | None = False,
87 ):
88 handler = responses(raw_request)
89 if handler is None:
90 raise NotImplementedError("The model does not support Responses API")
91
92 response = await handler.retrieve_responses(
93 response_id,
94 starting_after=starting_after,
95 stream=stream,
96 )
97
98 if isinstance(response, ErrorResponse):
99 return JSONResponse(
100 content=response.model_dump(mode="json", by_alias=True),
101 status_code=response.error.code,
102 )
103 elif isinstance(response, ResponsesResponse):
104 return JSONResponse(content=response.model_dump(mode="json", by_alias=True))
105 return StreamingResponse(
106 content=_convert_stream_to_sse_events(response), media_type="text/event-stream"
107 )
108
109
110 @router.post("/v1/responses/{response_id}/cancel")
111 @load_aware_call
112 async def cancel_responses(response_id: str, raw_request: Request):
113 handler = responses(raw_request)
114 if handler is None:
115 raise NotImplementedError("The model does not support Responses API")
116
117 response = await handler.cancel_responses(response_id)
118
119 if isinstance(response, ErrorResponse):
120 return JSONResponse(
121 content=response.model_dump(mode="json", by_alias=True),
122 status_code=response.error.code,
123 )
124 return JSONResponse(content=response.model_dump(mode="json", by_alias=True))
125
126
127 def attach_router(app: FastAPI):
128 app.include_router(router)