Signed-off-by: Shijin Zhang <75300765+Dovis01@users.noreply.github.com> Co-authored-by: Chauncey <chaunceyjiang@gmail.com>
42 lines
1.7 KiB
Python
42 lines
1.7 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
from openai.types.responses import ToolChoiceCustom, ToolChoiceFunction
|
|
|
|
from vllm.entrypoints.openai.chat_completion.protocol import (
|
|
ChatCompletionNamedToolChoiceParam,
|
|
ChatCompletionRequest,
|
|
)
|
|
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
|
|
from vllm.parser.engine.registered_adapters import Gemma4ParserToolAdapter
|
|
|
|
|
|
class Gemma4EngineToolParser(Gemma4ParserToolAdapter): # type: ignore[valid-type, misc]
|
|
supports_required_and_named = False
|
|
|
|
def adjust_request(
|
|
self, request: ChatCompletionRequest | ResponsesRequest
|
|
) -> ChatCompletionRequest | ResponsesRequest:
|
|
"""Skip structured-output JSON for required/named tool choice.
|
|
|
|
Gemma4 emits its native ``<|tool_call>call:...`` syntax, which the
|
|
parser extracts directly. The base ``ToolParser.adjust_request`` would
|
|
set ``structured_outputs`` for required/named and force JSON via guided
|
|
decoding, conflicting with that native syntax (it leaks as content and
|
|
crashes EngineCore under speculative decoding). Skip it so the model
|
|
emits its native format (mirrors the GLM4 parser).
|
|
"""
|
|
if request.tools:
|
|
tc = request.tool_choice
|
|
if tc == "required" or isinstance(
|
|
tc,
|
|
(
|
|
ChatCompletionNamedToolChoiceParam,
|
|
ToolChoiceFunction,
|
|
ToolChoiceCustom,
|
|
),
|
|
):
|
|
request.skip_special_tokens = False
|
|
return request
|
|
return super().adjust_request(request)
|