The OpenAI-compatible API
Code version: vLLM @ 3e44c0d · 2026-10-22
In one sentence
vllm serve accepts OpenAI-style JSON requests on HTTP routes such as /v1/chat/completions and answers with JSON or a stream of SSE events.
The idea#
A shared request format#
Many client tools speak the HTTP API of OpenAI: the openai Python SDK, agent frameworks, benchmark tools and routers. vLLM accepts the same JSON. So you change only the base URL of the client, for example to http://localhost:8000/v1, and the client talks to your own model. The server listens on port 8000 by default (vllm/entrypoints/common/cli_args.py:286-287).
Each route is one HTTP method and one path. Two routes do most of the work:
POST /v1/chat/completionstakes a list of chatmessages. The server turns them into one prompt with the chat template of the model. This is the main route for chat models and the route of all examples on this site.POST /v1/completionstakes a rawprompt: a string, a list of strings, or token ids (vllm/entrypoints/openai/completion/protocol.py:53-59). There is no chat template.
Other routes list the models (GET /v1/models), check health (GET /health), give Prometheus metrics (/metrics) and turn text into tokens (POST /tokenize). The first deep dive lists them all.
Chat roles and the chat template#
A chat request is a list of messages. Each message has a role and content. The usual roles are system, user, assistant and tool. The model does not see JSON. The chat template, a Jinja template from the tokenizer files, writes the messages into one text with special tokens. Then the tokenizer turns that text into token ids (Tokens and tokenizers). The page Renderers and chat templates: from messages to token ids explains how vLLM does this.
Streaming with server-sent events#
With "stream": true, the server keeps the HTTP response open. It sends one SSE event for each new piece of text. Each event is one line data: {...} and a blank line. The last event is data: [DONE]. So the user sees the first words after the time to first token, not after the whole answer (Serving metrics: TTFT, TPOT, ITL, throughput, goodput).
An everyday comparison#
Think of a standard order form in a chain of restaurants. Every kitchen accepts the same form, so a customer can order in any of them. vLLM is a new kitchen that accepts the standard form. It also reads some extra boxes on the form that only vLLM knows, for example top_k.
The comparison stops here: a kitchen sends the whole meal at once. With streaming, vLLM sends the answer in many small parts while it still makes the rest.
The picture#
Read panel 1 from left to right. Fields that the client does not send come from the model's generation_config.json first and from the OpenAI defaults second. Panel 2 shows the two answer forms of the same request. The deep dive "The stream, chunk by chunk" explains each SSE line.
In vLLM#
This is a basics page, so this part gives only pointers. The page The OpenAI-compatible server: launch modes, app, serving classes and streaming explains the server code, and stops 1, 2 and 16 of Tour: Life of a chat request follow one request through it.
- The route.
create_chat_completion()invllm/entrypoints/openai/chat_completion/api_router.pyhandlesPOST /v1/chat/completions. It returns aJSONResponse, or aStreamingResponsewith the media typetext/event-stream(vllm/entrypoints/openai/chat_completion/api_router.py:43-84). - The request class.
ChatCompletionRequestis a pydantic model. Its fields follow the order of the OpenAI documentation, and vLLM's extra fields come after them (vllm/entrypoints/openai/chat_completion/protocol.py:225-228). - The handler.
OpenAIServingChat._create_chat_completion()renders the messages, computesmax_tokens, makesSamplingParamsand callsengine_client.generate()(vllm/entrypoints/openai/chat_completion/serving.py:302-418).
@router.post(
"/v1/chat/completions",
dependencies=[Depends(validate_json_request)],
**json_or_sse_responses(
ChatCompletionResponse,
ChatCompletionStreamResponse,
{
HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
HTTPStatus.NOT_FOUND.value: {"model": ErrorResponse},
HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
HTTPStatus.NOT_IMPLEMENTED.value: {"model": ErrorResponse},
},
),
)
@with_cancellation
@load_aware_call
async def create_chat_completion(request: ChatCompletionRequest, raw_request: Request):
metrics_header_format = raw_request.headers.get(
ENDPOINT_LOAD_METRICS_FORMAT_HEADER_LABEL, ""
)
handler = chat(raw_request)
if handler is None:
raise NotImplementedError("The model does not support Chat Completions API")
generator = await handler.create_chat_completion(request, raw_request)
if isinstance(generator, ErrorResponse):
return JSONResponse(
content=generator.model_dump(), status_code=generator.error.code
)
elif isinstance(generator, ChatCompletionResponse):
return JSONResponse(
content=generator.model_dump(),
headers=metrics_header(metrics_header_format),
)
args = getattr(raw_request.app.state, "args", None)
keep_alive_interval = getattr(args, "sse_keep_alive_interval", 0)
return StreamingResponse(
content=with_sse_keep_alive(generator, float(keep_alive_interval)),
media_type="text/event-stream",ErrorResponse becomes JSON with its own status code. A full response becomes JSON. A generator becomes an SSE stream.Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveThe routes that vLLM registers
build_app() makes the FastAPI app and calls register_api_routers() (vllm/entrypoints/launchers/app.py:19-45). That function adds the routers in groups. Some groups depend on the tasks of the model or on a flag (vllm/entrypoints/launchers/api_server/routers.py:12-90). After that, endpoint plugins can add their own routes (vllm/entrypoints/launchers/app.py:47-51).
The health check. GET /health asks the engine client. It returns 200 when the engine is alive and 503 after an EngineDeadError (vllm/entrypoints/serve/instrumentator/health.py:22-33).
API keys. With --api-key (or VLLM_API_KEY when the flag is not set), a middleware checks the Authorization: Bearer header (vllm/entrypoints/serve/middleware/register.py:32-36). It guards only paths that start with /v1, /v2, /inference or /cohere, and answers 401 otherwise (vllm/entrypoints/serve/middleware/authenticate.py:11, vllm/entrypoints/serve/middleware/authenticate.py:59-60). So /health and /metrics stay open.
Deep diveRequest fields that matter for serving
The table lists the OpenAI fields of ChatCompletionRequest that change how the server works (vllm/entrypoints/openai/chat_completion/protocol.py:228-249).
| Field | Default | What vLLM does with it |
|---|---|---|
model | None | Selects the served model name or a LoRA adapter. A wrong name gives an error response (vllm/entrypoints/openai/chat_completion/serving.py:278-281). |
messages | required | The chat. The renderer applies the chat template (vllm/entrypoints/openai/chat_completion/serving.py:285). |
max_completion_tokens, max_tokens | None | max_tokens is marked as deprecated; the new field wins when both are set (vllm/entrypoints/openai/chat_completion/protocol.py:234-239, vllm/entrypoints/openai/chat_completion/serving.py:349-357). See the next deep dive for the default. |
temperature, top_p | None | Request value, else the server default, else 1.0 (vllm/entrypoints/openai/chat_completion/protocol.py:692-700). |
stream | False | True gives an SSE stream and RequestOutputKind.DELTA; false gives one body and FINAL_ONLY (vllm/entrypoints/openai/chat_completion/protocol.py:797-799). |
stream_options | None | include_usage adds a final usage chunk. continuous_usage_stats adds usage to every chunk, but only together with include_usage. The server flag --enable-force-include-usage turns on both for every stream (vllm/entrypoints/serve/utils/api_utils.py:273-285). Allowed only with stream: true (vllm/entrypoints/openai/chat_completion/protocol.py:845-856). |
n | 1 | The number of answers per request (vllm/entrypoints/openai/chat_completion/protocol.py:240). |
stop | [] | Stop strings (vllm/entrypoints/openai/chat_completion/protocol.py:244). |
tools, tool_choice | None, "none" | Tool definitions. With tools and no tool_choice, the server sets "auto" (vllm/entrypoints/openai/chat_completion/protocol.py:975-978). |
response_format | None | JSON mode or a JSON schema. It becomes structured-output parameters (vllm/entrypoints/openai/chat_completion/protocol.py:728-733); see Structured outputs: JSON schemas, grammars and token bitmasks in vLLM. |
logprobs, top_logprobs | False, 0 | When logprobs is true, the sampler returns top_logprobs alternatives per token (vllm/entrypoints/openai/chat_completion/protocol.py:784-788). |
user | None | Ignored: "this will be ignored by vLLM" (vllm/entrypoints/openai/chat_completion/protocol.py:274-275). |
vLLM's extra fields. These are not in the OpenAI specification. The OpenAI SDK sends them in its extra_body argument.
- Sampling:
top_k,min_p,repetition_penalty,min_tokens,stop_token_ids,ignore_eos,use_beam_searchand others (vllm/entrypoints/openai/chat_completion/protocol.py:278-288). The basics of these are on Sampling. - Chat template:
chat_template,chat_template_kwargs,add_generation_prompt,continue_final_message(vllm/entrypoints/openai/chat_completion/protocol.py:324-377). For example,"chat_template_kwargs": {"enable_thinking": false}reaches a Qwen3 template. - Scheduling:
priority(lower is earlier, default 0) (vllm/entrypoints/openai/chat_completion/protocol.py:393-402). The field description says that a value other than 0 raises an error without priority scheduling. At this commit, no code raises that error: the default first-come-first-served queue does not use the value (vllm/v1/core/sched/request_queue.py:200-205). - Structured output:
structured_outputs(vllm/entrypoints/openai/chat_completion/protocol.py:389-392). - Bookkeeping:
request_id(default: a random id),cache_salt,vllm_xargsfor custom extensions (vllm/entrypoints/openai/chat_completion/protocol.py:403-410,vllm/entrypoints/openai/chat_completion/protocol.py:525-531).
Deep diveDefaults: where temperature and max_tokens come from
Sampling defaults. to_sampling_params() uses this order for each of seven fields: the request value, else the server default, else the OpenAI default (vllm/entrypoints/openai/chat_completion/protocol.py:735-747). The OpenAI defaults are temperature 1.0, top_p 1.0, top_k 0, min_p 0.0 and penalties that do nothing (vllm/entrypoints/openai/chat_completion/protocol.py:692-700). The server default comes from ModelConfig.get_diff_sampling_param() (vllm/entrypoints/openai/chat_completion/serving.py:181):
- With
generation_config = "vllm", it reads no file. Otherwise it reads the model's generation config (vllm/config/model.py:1875-1877). The default is"auto"(vllm/config/model.py:358). --override-generation-configreplaces single values (vllm/config/model.py:1879-1880).- It keeps only nine known keys, and it renames
max_new_tokenstomax_tokens(vllm/config/model.py:1882-1903). - It logs a warning when a generation config file changes the defaults (
vllm/config/model.py:1906-1915).
max_tokens. get_max_tokens() returns the smallest of these values that is set (vllm/entrypoints/serve/utils/api_utils.py:234-270):
max_model_len − prompt length;- the request value, or
max_tokensfrom the server default when the request has none; - an override:
max_new_tokensfrom--override-generation-configwhen--generation-configisautoorvllm, elsemax_new_tokensfrom the generation config folder that you give (vllm/entrypoints/openai/chat_completion/serving.py:183-187); - a platform limit.
Worked example. max_model_len is 4,096 and the rendered prompt has 13 tokens. A request without max_completion_tokens can make up to 4,096 − 13 = 4,083 tokens. With "max_completion_tokens": 32, it makes at most 32. A prompt of 4,100 tokens raises ValueError("Input length (4100) exceeds model's maximum context length (4096).") (vllm/entrypoints/serve/utils/api_utils.py:248-252). The renderer normally rejects such a prompt before this, with HTTP 400.
Deep diveThe stream, chunk by chunk (worked example)
Start a server with vllm serve Qwen/Qwen3-0.6B. Then send this request (example commands, not in the repo). The option -N turns off the output buffer of curl, so you see each event when it comes.
curl -N http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "Qwen/Qwen3-0.6B",
"messages": [{"role": "user", "content": "Hi"}],
"max_completion_tokens": 32,
"stream": true,
"stream_options": {"include_usage": true},
"chat_template_kwargs": {"enable_thinking": false}}'
The reply has this form. The ids, times, text and token counts are illustrative; "..." stands for the object, created and model fields.
data: {"id":"chatcmpl-3f9c...","object":"chat.completion.chunk","created":1761140000,"model":"Qwen/Qwen3-0.6B","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}],"prompt_token_ids":null,"prompt_text":null}
data: {"id":"chatcmpl-3f9c...",...,"choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null,"token_ids":null}]}
data: {"id":"chatcmpl-3f9c...",...,"choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null,"token_ids":null}]}
...
data: {"id":"chatcmpl-3f9c...",...,"choices":[{"index":0,"delta":{"content":""},"logprobs":null,"finish_reason":"stop","stop_reason":null,"token_ids":null,"weight_versions":null}]}
data: {"id":"chatcmpl-3f9c...",...,"choices":[],"usage":{"prompt_tokens":13,"total_tokens":23,"completion_tokens":10},"system_fingerprint":"vllm-<version>-<hash8>"}
data: [DONE]
- The first chunk carries only the role and empty content (
vllm/entrypoints/openai/chat_completion/serving.py:590-598). - Each later chunk carries the new text of one engine output, in
delta.content. Each chunk is serialized withexclude_unset=True, so only the fields that the code sets appear (vllm/entrypoints/openai/chat_completion/serving.py:497-503). - The last content chunk has
finish_reason. Its content is empty because the detokenizer does not decode the end token (vllm/v1/engine/detokenizer.py:107-111). - The usage chunk has
choices: []andusage(vllm/entrypoints/openai/chat_completion/serving.py:854-903). With empty choices, the serializer also drops allNonefields (exclude_none=not chunk.choices). The server sends it only withinclude_usageor--enable-force-include-usage(vllm/entrypoints/serve/utils/api_utils.py:276-285). When usage is on, the fingerprint moves from the last content chunk to this chunk (vllm/entrypoints/openai/chat_completion/serving.py:815-824). data: [DONE]always ends the stream, also after an error chunk (vllm/entrypoints/openai/chat_completion/serving.py:504-508).
try:
async for chunk in self.chat_completion_stream_generator_raw(
request,
result_generator,
request_id,
model_name,
conversation,
tokenizer,
request_metadata,
chat_template_kwargs=chat_template_kwargs,
mm_token_counts=mm_token_counts,
):
if isinstance(chunk, ErrorResponse):
data = chunk.model_dump_json()
else:
data = chunk.model_dump_json(
exclude_unset=True, exclude_none=not chunk.choices
)
yield f"data: {data}\n\n"
except Exception as e:
logger.exception("Error serializing chat completion stream.")
data = self.create_streaming_error_response(e)
yield f"data: {data}\n\n"
yield "data: [DONE]\n\n"data: line and a blank line per chunk, then data: [DONE].Check the numbers. total_tokens = 13 + 10 = 23. The 10 output tokens are nine text tokens and the end token. The 13 prompt tokens come from the Qwen3 chat template around the word "Hi" (inferred, not in the repo).
Keep-alive comments. A proxy can close an idle stream. With --sse-keep-alive-interval N (default 0, off), the server sends the comment line : keep-alive after N idle seconds (vllm/entrypoints/common/cli_args.py:156-160, vllm/entrypoints/serve/utils/sse_keep_alive.py:18-33). SSE clients ignore comment lines.
Deep diveChat roles in vLLM
The type of one message is a union of three types (vllm/renderers/chat_utils/types.py:316-320). They are the OpenAI message types, a custom message type and the Harmony message type of gpt-oss models. The custom type has role: Required[str], so vLLM accepts any role name that the chat template knows (vllm/renderers/chat_utils/types.py:284-288). It also has the extra keys reasoning (earlier thinking text) and tools (vllm/renderers/chat_utils/types.py:306-310).
add_generation_prompt(default true) adds the start of an assistant turn to the prompt.continue_final_messageinstead continues the last assistant message. A request that sends both astruegets an error (vllm/entrypoints/openai/chat_completion/protocol.py:1045-1055).- With
add_generation_prompt, the role of the answer is--response-role, defaultassistant. Without it, the answer takes the role of the last message (vllm/entrypoints/common/cli_args.py:109-110,vllm/entrypoints/openai/chat_completion/serving.py:440-443).
The page Renderers and chat templates: from messages to token ids explains how the renderer finds and applies the template.
Deep diveTool calls and reasoning content at the API level
Tool calls. The client sends tools (function definitions). The model writes a tool call as text in its own format. A tool parser turns that text into the OpenAI form message.tool_calls[i] = {id, type: "function", function: {name, arguments}} (vllm/entrypoints/generate/base/protocol.py:316-328).
- For
tool_choice: "auto", start the server with--enable-auto-tool-choiceand--tool-call-parser <name>. Without them, an"auto"request gets the error"auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set(vllm/renderers/online_renderer.py:260-271). The flag without a parser stops start-up (vllm/entrypoints/common/cli_args.py:524-525). "required"or a named function also needs both flags. The error text names only--tool-call-parser(vllm/renderers/online_renderer.py:272-281). But without--enable-auto-tool-choice, the server loads no tool parser, even when--tool-call-parseris set (vllm/parser/parser_manager.py:34-35).- In a stream, a tool call comes in pieces as
delta.tool_calls. Each piece has anindex, and the name and the arguments can come in separate chunks (vllm/entrypoints/generate/base/protocol.py:331-342).
Reasoning content. Reasoning models write their thinking before the answer, for example between <think> tags. With --reasoning-parser <name> (vllm/engine/arg_utils.py:1136-1139), vLLM moves that text out of content into the vLLM-specific field reasoning of the message or the delta (vllm/entrypoints/openai/chat_completion/protocol.py:79-80, vllm/entrypoints/generate/base/protocol.py:356-360). Without a parser, the thinking text stays in content. The request field include_reasoning (default true) controls whether the server returns it (vllm/entrypoints/openai/chat_completion/protocol.py:271).
The page Tool and reasoning parsers: from model text to tool_calls and reasoning explains the parsers and how to add your own.
Deep diveErrors: HTTP 400 and the error body
Every error body has the same shape: {"error": {"message", "type", "param", "code"}} (vllm/entrypoints/serve/engine/protocol.py:79-87). The app registers its handlers in four layers (vllm/entrypoints/serve/exception_handling/register.py:26-42):
- FastAPI errors. A body that does not match the pydantic model gives
RequestValidationError. The handler answers 400 with at most 10 error entries (vllm/entrypoints/serve/exception_handling/handlers/validation.py:149-195). - vLLM errors. All vLLM error classes derive from
VLLMError(vllm/exceptions.py:10-28). One handler,vllm_error_handler(), gets them all (vllm/entrypoints/serve/exception_handling/register.py:32,vllm/entrypoints/serve/exception_handling/handlers/vllm_error.py:18-25).create_error_response()then selects the status from the class.VLLMValidationErrorbecomes 400BadRequestErrorwithparamset (vllm/entrypoints/serve/exception_handling/error_response.py:43-46).VLLMUnprocessableEntityErrorbecomes 422, and a not-found error becomes 404 (vllm/entrypoints/serve/exception_handling/error_response.py:47-54). AGenerationErrorkeeps its own status: 500, or 503 for a retryable error (vllm/entrypoints/serve/exception_handling/error_response.py:60-67,vllm/exceptions.py:131-144). Other server errors become 500 (vllm/entrypoints/serve/exception_handling/error_response.py:68-77). - Raw Python errors.
ValueError,TypeErrorandOverflowErrorstill become 400;NotImplementedErrorbecomes 501 (vllm/entrypoints/serve/exception_handling/error_response.py:80-87). - Anything else becomes 500. The one exception is a Jinja
TemplateErrorfrom the chat template, which becomes 400 (vllm/entrypoints/serve/exception_handling/error_response.py:88-96).
Which handler answers a validator error. Many request checks are pydantic validators with mode="before" that raise VLLMValidationError. Examples are "Stream options can only be defined when stream=True" (vllm/entrypoints/openai/chat_completion/protocol.py:845-856) and the tool_choice checks (vllm/entrypoints/openai/chat_completion/protocol.py:959-1043). The path of such an error is not the path of a type error:
VLLMValidationErroris not aValueError(vllm/exceptions.py:20,vllm/exceptions.py:28). Pydantic turns only aValueErroror anAssertionErrorof a validator into aValidationError, and lets other exceptions pass unchanged (general pydantic rule). The tests of vLLM show this: building the request raisesVLLMValidationErroritself (tests/tool_use/test_chat_completion_request_validations.py:30-38).- So FastAPI does not make a
RequestValidationError, and the validation handler of layer 1 does not run. The exception goes tovllm_error_handler()of layer 2, which callsexception_handler()and thencreate_error_response()(vllm/entrypoints/serve/exception_handling/handlers/vllm_error.py:24-25,vllm/entrypoints/serve/exception_handling/handlers/exception.py:14-24). - The message is
str(exc), and the class adds the parameter name to it (vllm/entrypoints/serve/exception_handling/error_response.py:22-24,vllm/exceptions.py:49-56). The server test expects aBadRequestError(HTTP 400) for this request (tests/entrypoints/openai/chat_completion/test_chat.py:406-415).
The body for "stream": false with stream_options is then (the field order follows ErrorInfo):
HTTP/1.1 400 Bad Request
{"error": {"message": "Stream options can only be defined when `stream=True`. (parameter=stream_options)",
"type": "BadRequestError", "param": "stream_options", "code": 400}}
A field with a wrong type, for example "n": "x", takes the other path. Pydantic raises a ValidationError, and FastAPI makes a RequestValidationError. The validation handler answers 400 with the message "1 validation error: ..." and a param such as body.n (vllm/entrypoints/serve/exception_handling/handlers/validation.py:167-195). That handler still looks for a VLLMValidationError inside the pydantic errors (vllm/entrypoints/serve/exception_handling/handlers/validation.py:158-165). Because the class is no longer a ValueError, this branch cannot match a validator error any more (derived from the class tree).
What can go wrong#
- Unexpected sampling. The default
--generation-config autotakes temperature, top-p and top-k from the model (vllm/config/model.py:1875-1903). Two servers with different models answer the same request with different defaults. Send the values that you need, or use--generation-config vllm. - Very long answers. Without
max_completion_tokens, a request can run to the end of the context (vllm/entrypoints/serve/utils/api_utils.py:253-270). One such request keeps a slot in the batch and much of the KV cache for a long time. stream_optionswithoutstreamgives HTTP 400 (vllm/entrypoints/openai/chat_completion/protocol.py:845-856).- Tool calls in
content. Without--enable-auto-tool-choiceand a parser, the model's tool-call text is not parsed (vllm/entrypoints/openai/chat_completion/serving.py:1036-1040), and an"auto"request fails (vllm/renderers/online_renderer.py:265-271). - Thinking text in the answer. A reasoning model without
--reasoning-parserputs its thinking intocontent. - Lost connection behind a proxy. A long queue or prefill sends no bytes. Set
--sse-keep-alive-interval(vllm/entrypoints/common/cli_args.py:156-160). - 401 on /v1 routes only. With an API key,
/healthand/metricsstay open (vllm/entrypoints/serve/middleware/authenticate.py:11).
Why it matters. The API is the contract with your users. Its defaults decide how long and how random the answers are, and its stream format decides what the client sees and when.
Check yourself#
- 1Which route takes a list of chat messages, and which takes a raw prompt?easy
Show answer
POST /v1/chat/completionstakesmessagesand applies the chat template (vllm/entrypoints/openai/chat_completion/api_router.py:43-44).POST /v1/completionstakesprompt(vllm/entrypoints/openai/completion/protocol.py:53-59). - 2How does a streamed response end?easy
Show answer
With the line
data: [DONE]and a blank line (vllm/entrypoints/openai/chat_completion/serving.py:508). Withinclude_usage, a usage chunk with emptychoicescomes just before it. - 3A request sets nomedium
temperature. The model'sgeneration_config.jsonhas"temperature": 0.6. Which temperature does vLLM use with the default flags, and with--generation-config vllm?Show answer
0.6 with the default
auto, because the server default comes before the OpenAI default (vllm/entrypoints/openai/chat_completion/protocol.py:741-747,vllm/config/model.py:1875-1877). Withvllm, no file is read, so the OpenAI default 1.0 applies (vllm/entrypoints/openai/chat_completion/protocol.py:692-700). - 4hard
max_model_lenis 8,192, the prompt has 8,000 tokens, the request asks formax_completion_tokens: 500, and the generation config has nomax_new_tokens. How many tokens can the answer have?Show answer
192.
get_max_tokens()takes the minimum of 8,192 − 8,000 = 192 and 500 (and a platform limit, if any) (vllm/entrypoints/serve/utils/api_utils.py:253-270). The request does not fail; the answer ends withfinish_reason: "length"after 192 tokens. - 5A client gets HTTP 200, then a stream that stops with an error. Why is the status not 500, and where is the error?hard
Show answer
The server sends the status with the first byte of the stream. A later error cannot change it. The stream generator catches the exception, sends it as one more
data:chunk with an error object, and then sendsdata: [DONE](vllm/entrypoints/openai/chat_completion/serving.py:504-508).