main
py 140 lines 4.37 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Adapted from
5 # https://github.com/lm-sys/FastChat/blob/168ccc29d3f7edc50823016105c024fe2282732a/fastchat/protocol/openai_api_protocol.py
6 import time
7 from typing import ClassVar
8
9 from pydantic import (
10 BaseModel,
11 ConfigDict,
12 Field,
13 model_validator,
14 )
15
16 from vllm.logger import init_logger
17 from vllm.utils import random_uuid
18
19 logger = init_logger(__name__)
20
21 # Legacy guided-decoding fields are treated like any other extra field since
22 # their removal, so requests still using them get HTTP 200 with unconstrained
23 # output and no visible signal (see #53975). Warn instead of burying the
24 # only breadcrumb at DEBUG level.
25 _REMOVED_GUIDED_FIELDS = frozenset(
26 {
27 "guided_json",
28 "guided_regex",
29 "guided_choice",
30 "guided_grammar",
31 "guided_decoding_backend",
32 "guided_whitespace_pattern",
33 }
34 )
35
36
37 class OpenAIBaseModel(BaseModel):
38 # OpenAI API does allow extra fields
39 model_config = ConfigDict(extra="allow")
40
41 # Cache class field names
42 field_names: ClassVar[set[str] | None] = None
43
44 @model_validator(mode="wrap")
45 @classmethod
46 def __log_extra_fields__(cls, data, handler):
47 result = handler(data)
48 if not isinstance(data, dict):
49 return result
50 field_names = cls.field_names
51 if field_names is None:
52 # Get all class field names and their potential aliases
53 field_names = set()
54 for field_name, field in cls.model_fields.items():
55 field_names.add(field_name)
56 if alias := getattr(field, "alias", None):
57 field_names.add(alias)
58 cls.field_names = field_names
59
60 # Compare against both field names and aliases
61 extra_keys = data.keys() - field_names
62 if extra_keys:
63 removed = extra_keys & _REMOVED_GUIDED_FIELDS
64 if removed:
65 logger.warning_once(
66 "Request contains the removed guided-decoding field(s) "
67 "%s, which are ignored; output will NOT be constrained. "
68 "Use `structured_outputs` (or `response_format`) "
69 "instead; see docs/features/structured_outputs.md.",
70 str(sorted(removed)),
71 )
72 logger.debug(
73 "The following fields were present in the request but ignored: %s",
74 extra_keys,
75 )
76 return result
77
78
79 class ErrorInfo(OpenAIBaseModel):
80 message: str
81 type: str
82 param: str | None = None
83 code: int
84
85
86 class ErrorResponse(OpenAIBaseModel):
87 error: ErrorInfo
88
89
90 class ModelPermission(OpenAIBaseModel):
91 id: str = Field(default_factory=lambda: f"modelperm-{random_uuid()}")
92 object: str = "model_permission"
93 created: int = Field(default_factory=lambda: int(time.time()))
94 allow_create_engine: bool = False
95 allow_sampling: bool = True
96 allow_logprobs: bool = True
97 allow_search_indices: bool = False
98 allow_view: bool = True
99 allow_fine_tuning: bool = False
100 organization: str = "*"
101 group: str | None = None
102 is_blocking: bool = False
103
104
105 class ModelCard(OpenAIBaseModel):
106 id: str
107 object: str = "model"
108 created: int = Field(default_factory=lambda: int(time.time()))
109 owned_by: str = "vllm"
110 root: str | None = None
111 parent: str | None = None
112 max_model_len: int | None = None
113 permission: list[ModelPermission] = Field(default_factory=list)
114
115
116 class ModelList(OpenAIBaseModel):
117 object: str = "list"
118 data: list[ModelCard] = Field(default_factory=list)
119
120
121 class PromptTokenUsageInfo(OpenAIBaseModel):
122 cached_tokens: int | None = None
123 created_cache_tokens: int | None = None
124 multimodal_tokens: dict[str, int] | None = None
125 """Prompt tokens contributed by each input modality, keyed by modality name
126 (e.g. `image`, `audio`, `video`). A breakdown of the multimodal
127 placeholder tokens already counted in `prompt_tokens`; `None` when the
128 request has no multimodal input."""
129
130
131 class CompletionTokenUsageInfo(OpenAIBaseModel):
132 reasoning_tokens: int = 0
133
134
135 class UsageInfo(OpenAIBaseModel):
136 prompt_tokens: int = 0
137 total_tokens: int = 0
138 completion_tokens: int | None = 0
139 prompt_tokens_details: PromptTokenUsageInfo | None = None
140 completion_tokens_details: CompletionTokenUsageInfo | None = None