vllm-project--vllm
109 行
3.5 KiB
Python
109 行
3.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Declarative configuration for model tool-call and reasoning formats.
|
|
|
|
Each model format is described by a :class:`ParserEngineConfig` that specifies:
|
|
|
|
* **terminals** – literal strings or regex patterns that delimit the format
|
|
(e.g. ``<tool_call>``, ``</think>``).
|
|
* **token_id_terminals** – terminals that should be matched by token ID
|
|
rather than (or in addition to) text.
|
|
* **transitions** – a state machine mapping
|
|
``(state, terminal) → (new_state, events_to_emit)`` that drives semantic
|
|
event generation during streaming.
|
|
* **content_events** – what :class:`EventType` to emit for plain content
|
|
(non-terminal text) in each state.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum, auto
|
|
from functools import cached_property
|
|
|
|
from vllm.parser.engine.events import EventType
|
|
|
|
|
|
class ParserState(Enum):
|
|
CONTENT = auto()
|
|
REASONING = auto()
|
|
TOOL_PREAMBLE = auto()
|
|
TOOL_NAME = auto()
|
|
TOOL_ARGS = auto()
|
|
TOOL_BETWEEN = auto()
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Transition:
|
|
next_state: ParserState
|
|
events: tuple[EventType, ...] = field(default_factory=tuple)
|
|
skip_in_token_id_mode: bool = False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParserEngineConfig:
|
|
"""Declarative description of a model's tool-call / reasoning format.
|
|
|
|
The engine feeds terminals from the incremental lexer into the
|
|
transition table and emits the corresponding semantic events.
|
|
Content tokens (text between terminals) are classified by the
|
|
current state via ``content_events``.
|
|
"""
|
|
|
|
name: str
|
|
|
|
terminals: dict[str, str] = field(default_factory=dict)
|
|
|
|
token_id_terminals: dict[str, str] = field(default_factory=dict)
|
|
|
|
transitions: dict[tuple[ParserState, str], Transition] = field(
|
|
default_factory=dict,
|
|
)
|
|
|
|
content_events: dict[ParserState, EventType] = field(
|
|
default_factory=lambda: {
|
|
ParserState.CONTENT: EventType.TEXT_CHUNK,
|
|
ParserState.REASONING: EventType.REASONING_CHUNK,
|
|
ParserState.TOOL_NAME: EventType.TOOL_NAME,
|
|
ParserState.TOOL_ARGS: EventType.ARG_VALUE_CHUNK,
|
|
},
|
|
)
|
|
|
|
initial_state: ParserState = ParserState.CONTENT
|
|
|
|
arg_converter: Callable[[str, bool], str] | None = None
|
|
|
|
stream_arg_deltas: bool = True
|
|
|
|
tool_args_json: bool = True
|
|
|
|
arg_structural_chars: frozenset[str] | None = None
|
|
|
|
# Special tokens exempt from auto-drop but not state-machine terminals.
|
|
preserve_tokens: frozenset[str] = field(default_factory=frozenset)
|
|
|
|
# Prevents trailing-whitespace accumulation across multi-turn conversations.
|
|
strip_trailing_reasoning_whitespace: bool = True
|
|
|
|
# Drop content that is entirely whitespace when tool calls follow.
|
|
drop_whitespace_only_content_before_tools: bool = True
|
|
|
|
# .strip() content text when tool calls are present.
|
|
strip_content_whitespace_with_tools: bool = True
|
|
|
|
# Reject tool calls whose names are absent from the request tools.
|
|
validate_tool_names: bool = False
|
|
|
|
@cached_property
|
|
def terminal_defs(self):
|
|
from vllm.parser.engine.incremental_lexer import terminals_from_literals
|
|
|
|
return terminals_from_literals(self.terminals)
|
|
|
|
@cached_property
|
|
def lexer_shape(self):
|
|
from vllm.parser.engine.incremental_lexer import LexerShape
|
|
|
|
return LexerShape(self.terminal_defs)
|