Files
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

211 lines
7.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Adapters that expose :class:`ParserEngine` through the legacy
:class:`ReasoningParser` and :class:`ToolParser` interfaces.
This lets parser engines flow through the existing serving-layer code
paths that expect separate reasoning and tool parser instances, without
any changes to the serving layer itself.
"""
from __future__ import annotations
from collections.abc import Iterator, Sequence
from contextlib import contextmanager
from typing import TYPE_CHECKING
from vllm.parser.engine.parser_engine_config import ParserState
from vllm.reasoning.abs_reasoning_parsers import ReasoningParser
from vllm.tool_parsers.abstract_tool_parser import ToolParser
if TYPE_CHECKING:
from vllm.entrypoints.openai.chat_completion.protocol import (
ChatCompletionRequest,
)
from vllm.entrypoints.openai.engine.protocol import (
DeltaMessage,
ExtractedToolCallInformation,
)
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
from vllm.parser.engine.parser_engine import ParserEngine
from vllm.tokenizers import TokenizerLike
from vllm.tool_parsers.utils import Tool
class ParserEngineReasoningAdapter(ReasoningParser):
"""Adapts a :class:`ParserEngine` to the :class:`ReasoningParser`
interface so parser engines can be used as reasoning parsers in the
existing serving code.
Subclasses set :attr:`_parser_engine_cls` to the concrete
:class:`ParserEngine` class.
"""
_parser_engine_cls: type[ParserEngine]
engine_based_streaming: bool = True
def __init__(self, tokenizer: TokenizerLike, *args, **kwargs) -> None:
super().__init__(tokenizer, *args, **kwargs)
self._parser_engine = self._parser_engine_cls(tokenizer, **kwargs) # type: ignore[call-arg]
@contextmanager
def _skip_tool_parsing(self) -> Iterator[None]:
saved = self._parser_engine.skip_tool_parsing
self._parser_engine.skip_tool_parsing = True
try:
yield
finally:
self._parser_engine.skip_tool_parsing = saved
def is_reasoning_end(self, input_ids: Sequence[int]) -> bool:
return self._parser_engine.is_reasoning_end(list(input_ids))
def adjust_initial_state_from_prompt(self, prompt_token_ids: Sequence[int]) -> None:
self._parser_engine.adjust_initial_state_from_prompt(prompt_token_ids)
def extract_content_ids(self, input_ids: list[int]) -> list[int]:
return self._parser_engine.extract_content_ids(input_ids)
def extract_reasoning(
self,
model_output: str,
request: ChatCompletionRequest | ResponsesRequest,
) -> tuple[str | None, str | None]:
with self._skip_tool_parsing():
return self._parser_engine.extract_reasoning(model_output, request)
def extract_reasoning_streaming(
self,
previous_text: str,
current_text: str,
delta_text: str,
previous_token_ids: Sequence[int],
current_token_ids: Sequence[int],
delta_token_ids: Sequence[int],
) -> DeltaMessage | None:
with self._skip_tool_parsing():
return self._parser_engine.extract_reasoning_streaming(
previous_text,
current_text,
delta_text,
previous_token_ids,
current_token_ids,
delta_token_ids,
)
@property
def reasoning_start_str(self) -> str | None:
return self._parser_engine.reasoning_start_str
@property
def reasoning_end_str(self) -> str | None:
return self._parser_engine.reasoning_end_str
def adjust_request(
self,
request: ChatCompletionRequest | ResponsesRequest,
) -> ChatCompletionRequest | ResponsesRequest:
return self._parser_engine.adjust_request(request)
def has_engine_confirmed_reasoning_end(self) -> bool:
return self._parser_engine.reasoning_ended
def finish_streaming(self) -> DeltaMessage | None:
with self._skip_tool_parsing():
return self._parser_engine.finish_streaming()
def get_streaming_fallback_content(
self,
text: str,
request: ChatCompletionRequest | ResponsesRequest,
) -> str | None:
return self._parser_engine.get_streaming_fallback_content(text, request)
def count_reasoning_tokens(self, token_ids: Sequence[int]) -> int:
return self._parser_engine.count_reasoning_tokens(token_ids)
class ParserEngineToolAdapter(ToolParser):
"""Adapts a :class:`ParserEngine` to the :class:`ToolParser` interface.
:meth:`extract_tool_calls` starts the parser engine in ``CONTENT``
state so it can parse reasoning-stripped content (i.e. the output of
:meth:`ReasoningParser.extract_reasoning`).
Subclasses set :attr:`_parser_engine_cls` to the concrete
:class:`ParserEngine` class.
"""
_parser_engine_cls: type[ParserEngine]
engine_based_streaming: bool = True
def __init__(
self,
tokenizer: TokenizerLike,
tools: list[Tool] | None = None,
**kwargs,
) -> None:
super().__init__(tokenizer, tools)
self._parser_engine = self._parser_engine_cls(tokenizer, tools, **kwargs) # type: ignore[call-arg]
def adjust_request(
self,
request: ChatCompletionRequest | ResponsesRequest,
) -> ChatCompletionRequest | ResponsesRequest:
request = super().adjust_request(request)
return self._parser_engine.adjust_request(request)
def extract_tool_calls(
self,
model_output: str,
request: ChatCompletionRequest,
) -> ExtractedToolCallInformation:
return self._parser_engine.extract_tool_calls_from_content(
model_output, request
)
def extract_tool_calls_streaming(
self,
previous_text: str,
current_text: str,
delta_text: str,
previous_token_ids: Sequence[int],
current_token_ids: Sequence[int],
delta_token_ids: Sequence[int],
request: ChatCompletionRequest,
) -> DeltaMessage | None:
engine = self._parser_engine
engine.initialize_streaming(initial_state=ParserState.CONTENT)
return engine.extract_tool_calls_streaming(
previous_text,
current_text,
delta_text,
previous_token_ids,
current_token_ids,
delta_token_ids,
request,
)
def finish_streaming(self) -> DeltaMessage | None:
return self._parser_engine.finish_streaming()
def make_adapters(
parser_engine_cls: type[ParserEngine],
) -> tuple[type[ParserEngineReasoningAdapter], type[ParserEngineToolAdapter]]:
reasoning_adapter = type(
f"{parser_engine_cls.__name__}ReasoningAdapter",
(ParserEngineReasoningAdapter,),
{"_parser_engine_cls": parser_engine_cls},
)
tool_adapter = type(
f"{parser_engine_cls.__name__}ToolAdapter",
(ParserEngineToolAdapter,),
{"_parser_engine_cls": parser_engine_cls},
)
# Let the serving layer find the adapters and call adjust_request(),
# which sets skip_special_tokens=False for the detokenizer.
parser_engine_cls.reasoning_parser_cls = reasoning_adapter # type: ignore[attr-defined]
parser_engine_cls.tool_parser_cls = tool_adapter # type: ignore[attr-defined]
return reasoning_adapter, tool_adapter