Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
182 lines
5.0 KiB
Python
182 lines
5.0 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""OpenAI Decisions API request and response models."""
|
|
|
|
from typing import Annotated, Any, Literal
|
|
|
|
from pydantic import ConfigDict, Field, StrictBool, StrictStr, model_validator
|
|
|
|
from vllm.entrypoints.generate.base.protocol import validate_cache_salt
|
|
from vllm.entrypoints.serve.engine.protocol import OpenAIBaseModel
|
|
|
|
ShortText = Annotated[StrictStr, Field(max_length=1048576)]
|
|
InputText = Annotated[StrictStr, Field(max_length=10485760)]
|
|
ChoiceValue = StrictStr | StrictBool
|
|
|
|
|
|
class DecisionModel(OpenAIBaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
|
class DecisionInputText(DecisionModel):
|
|
type: Literal["input_text"]
|
|
text: InputText
|
|
|
|
|
|
class DecisionInputMessage(DecisionModel):
|
|
role: Literal["user"]
|
|
content: InputText | Annotated[list[DecisionInputText], Field(max_length=16384)]
|
|
type: Literal["message"] = "message"
|
|
|
|
@model_validator(mode="before")
|
|
@classmethod
|
|
def reject_images(cls, data: Any) -> Any:
|
|
if (
|
|
isinstance(data, dict)
|
|
and isinstance(data.get("content"), list)
|
|
and any(
|
|
isinstance(part, dict) and part.get("type") == "input_image"
|
|
for part in data["content"]
|
|
)
|
|
):
|
|
raise ValueError(
|
|
"Decisions supports text input only; images are not supported"
|
|
)
|
|
return data
|
|
|
|
|
|
class QuestionBase(DecisionModel):
|
|
instructions: ShortText
|
|
name: ShortText | None = None
|
|
|
|
@model_validator(mode="after")
|
|
def validate_name(self) -> "QuestionBase":
|
|
if "name" in self.model_fields_set and self.name is None:
|
|
raise ValueError("name must be a string when provided")
|
|
return self
|
|
|
|
|
|
class PredicateQuestion(QuestionBase):
|
|
type: Literal["predicate"]
|
|
|
|
|
|
class ChoiceOption(DecisionModel):
|
|
value: ChoiceValue
|
|
description: ShortText = ""
|
|
|
|
|
|
class ChoiceQuestion(QuestionBase):
|
|
type: Literal["choice"]
|
|
choices: list[ChoiceOption] = Field(min_length=2, max_length=255)
|
|
|
|
@model_validator(mode="after")
|
|
def validate_choices(self) -> "ChoiceQuestion":
|
|
values = [(type(c.value), c.value) for c in self.choices]
|
|
if len(set(values)) != len(values):
|
|
raise ValueError("choice values must be distinct")
|
|
return self
|
|
|
|
|
|
class ScoreLevel(DecisionModel):
|
|
label: ShortText
|
|
description: ShortText = ""
|
|
|
|
|
|
class ScoreQuestion(QuestionBase):
|
|
type: Literal["score"]
|
|
levels: list[ScoreLevel] = Field(min_length=2, max_length=10)
|
|
|
|
|
|
DecisionQuestion = Annotated[
|
|
PredicateQuestion | ChoiceQuestion | ScoreQuestion, Field(discriminator="type")
|
|
]
|
|
|
|
|
|
class DecisionRequest(DecisionModel):
|
|
model: ShortText
|
|
input: InputText | Annotated[list[DecisionInputMessage], Field(max_length=131072)]
|
|
questions: list[DecisionQuestion] = Field(min_length=1, max_length=200)
|
|
safety_identifier: Annotated[StrictStr, Field(max_length=128)] | None = None
|
|
cache_salt: str | None = Field(
|
|
default=None,
|
|
min_length=1,
|
|
max_length=1024,
|
|
description=(
|
|
"If specified, the prefix cache will be salted with the provided "
|
|
"string to prevent an attacker to guess prompts in multi-user "
|
|
"environments. The salt should be random, protected from "
|
|
"access by 3rd parties, and long enough to be "
|
|
"unpredictable (e.g., 43 characters base64-encoded, corresponding "
|
|
"to 256 bit)."
|
|
),
|
|
)
|
|
|
|
@model_validator(mode="before")
|
|
@classmethod
|
|
def check_cache_salt_support(cls, data: Any) -> Any:
|
|
if isinstance(data, dict):
|
|
validate_cache_salt(data.get("cache_salt"))
|
|
return data
|
|
|
|
|
|
class PredicateAnswer(DecisionModel):
|
|
type: Literal["predicate"] = "predicate"
|
|
name: str | None
|
|
probability: float
|
|
|
|
|
|
class ChoiceProbability(DecisionModel):
|
|
value: ChoiceValue
|
|
probability: float
|
|
|
|
|
|
class ChoiceAnswer(DecisionModel):
|
|
type: Literal["choice"] = "choice"
|
|
name: str | None
|
|
choice: ChoiceValue
|
|
probabilities: list[ChoiceProbability]
|
|
confidence: float
|
|
|
|
|
|
class ScoreProbability(DecisionModel):
|
|
value: int
|
|
label: str
|
|
probability: float
|
|
|
|
|
|
class ScoreAnswer(DecisionModel):
|
|
type: Literal["score"] = "score"
|
|
name: str | None
|
|
score: float
|
|
probabilities: list[ScoreProbability]
|
|
confidence: float
|
|
|
|
|
|
DecisionAnswer = Annotated[
|
|
PredicateAnswer | ChoiceAnswer | ScoreAnswer,
|
|
Field(discriminator="type"),
|
|
]
|
|
|
|
|
|
class InputTokensDetails(DecisionModel):
|
|
cached_tokens: int = 0
|
|
cache_write_tokens: int = 0
|
|
|
|
|
|
class OutputTokensDetails(DecisionModel):
|
|
reasoning_tokens: int = 0
|
|
|
|
|
|
class OpenAIDecisionUsage(DecisionModel):
|
|
input_tokens: int
|
|
input_tokens_details: InputTokensDetails
|
|
output_tokens: int
|
|
output_tokens_details: OutputTokensDetails
|
|
total_tokens: int
|
|
|
|
|
|
class DecisionResponse(DecisionModel):
|
|
model: str
|
|
answers: list[DecisionAnswer]
|
|
usage: OpenAIDecisionUsage
|