| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Ling3 parser for reasoning and tool calls. |
| 4 | |
| 5 | Ling3 uses the same XML tool-call format as GLM-4.7 and defaults thinking on. |
| 6 | It follows the GLM-style ``<think>`` / ``</think>`` reasoning format and |
| 7 | treats ``<tool_call>`` as an implicit reasoning terminator. |
| 8 | """ |
| 9 | |
| 10 | from __future__ import annotations |
| 11 | |
| 12 | from dataclasses import replace |
| 13 | from typing import TYPE_CHECKING |
| 14 | |
| 15 | from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest |
| 16 | from vllm.entrypoints.openai.responses.protocol import ResponsesRequest |
| 17 | from vllm.parser.engine.adapters import make_adapters |
| 18 | from vllm.parser.engine.parser_engine import ParserEngine |
| 19 | from vllm.parser.glm47_moe import ( |
| 20 | THINK_END, |
| 21 | THINK_START, |
| 22 | Glm47MoeParser, |
| 23 | glm47_moe_config, |
| 24 | ) |
| 25 | |
| 26 | if TYPE_CHECKING: |
| 27 | from vllm.tokenizers import TokenizerLike |
| 28 | from vllm.tool_parsers.abstract_tool_parser import Tool |
| 29 | |
| 30 | |
| 31 | class Ling3Parser(Glm47MoeParser): |
| 32 | """Ling3 parser backed by the GLM XML parser engine.""" |
| 33 | |
| 34 | def __init__( |
| 35 | self, |
| 36 | tokenizer: TokenizerLike, |
| 37 | tools: list[Tool] | None = None, |
| 38 | **kwargs, |
| 39 | ) -> None: |
| 40 | chat_kwargs = kwargs.get("chat_template_kwargs", {}) or {} |
| 41 | thinking = chat_kwargs.get("thinking", None) |
| 42 | enable_thinking = chat_kwargs.get("enable_thinking", None) |
| 43 | self.thinking_enabled = ( |
| 44 | True |
| 45 | if thinking is None and enable_thinking is None |
| 46 | else bool(thinking) or bool(enable_thinking) |
| 47 | ) |
| 48 | parser_config = replace( |
| 49 | glm47_moe_config(thinking=self.thinking_enabled), |
| 50 | name="ling3", |
| 51 | ) |
| 52 | kwargs.setdefault( |
| 53 | "parser_engine_config", |
| 54 | parser_config, |
| 55 | ) |
| 56 | ParserEngine.__init__(self, tokenizer, tools, **kwargs) |
| 57 | |
| 58 | @property |
| 59 | def reasoning_start_str(self) -> str: |
| 60 | return THINK_START |
| 61 | |
| 62 | @property |
| 63 | def reasoning_end_str(self) -> str: |
| 64 | return THINK_END |
| 65 | |
| 66 | def extract_reasoning( |
| 67 | self, |
| 68 | model_output: str, |
| 69 | request: ChatCompletionRequest | ResponsesRequest, |
| 70 | ) -> tuple[str | None, str | None]: |
| 71 | if not self.thinking_enabled: |
| 72 | return None, model_output |
| 73 | |
| 74 | return super().extract_reasoning(model_output, request) |
| 75 | |
| 76 | |
| 77 | ( |
| 78 | Ling3ParserReasoningAdapter, |
| 79 | Ling3ParserToolAdapter, |
| 80 | ) = make_adapters(Ling3Parser) |