forked from Karylab-cklius/vllm
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fc60bb26d | ||
|
|
1f2c614c27 | ||
|
|
f24d8d5bb4 | ||
|
|
928e13af5f |
@@ -83,6 +83,20 @@ class MiniMaxM3Tokenizer:
|
|||||||
return "".join(tokens)
|
return "".join(tokens)
|
||||||
|
|
||||||
|
|
||||||
|
class SplitMiniMaxM3Tokenizer(MiniMaxM3Tokenizer):
|
||||||
|
"""Tokenizer that exposes marker vocab entries but encodes them as text."""
|
||||||
|
|
||||||
|
def tokenize(self, text: str) -> list[str]:
|
||||||
|
return list(text)
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeSplitMiniMaxM3Tokenizer(MiniMaxM3Tokenizer):
|
||||||
|
"""Tokenizer whose runtime output splits markers despite atomic encodes."""
|
||||||
|
|
||||||
|
def encode_runtime(self, text: str) -> list[int]:
|
||||||
|
return [self._add_token(token) for token in list(text)]
|
||||||
|
|
||||||
|
|
||||||
def make_parser(
|
def make_parser(
|
||||||
chat_template_kwargs: dict[str, str] | None = None,
|
chat_template_kwargs: dict[str, str] | None = None,
|
||||||
) -> tuple[MiniMaxM3ReasoningParser, MiniMaxM3Tokenizer]:
|
) -> tuple[MiniMaxM3ReasoningParser, MiniMaxM3Tokenizer]:
|
||||||
@@ -105,7 +119,8 @@ def run_streaming(
|
|||||||
reasoning_end_states: list[bool] = []
|
reasoning_end_states: list[bool] = []
|
||||||
|
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
delta_token_ids = tokenizer.encode(chunk, add_special_tokens=False)
|
encode_runtime = getattr(tokenizer, "encode_runtime", tokenizer.encode)
|
||||||
|
delta_token_ids = encode_runtime(chunk)
|
||||||
current_text = previous_text + chunk
|
current_text = previous_text + chunk
|
||||||
current_token_ids = previous_token_ids + delta_token_ids
|
current_token_ids = previous_token_ids + delta_token_ids
|
||||||
delta = parser.extract_reasoning_streaming(
|
delta = parser.extract_reasoning_streaming(
|
||||||
@@ -174,14 +189,14 @@ def test_nonstreaming_drops_leading_end_tag():
|
|||||||
assert content == "answer"
|
assert content == "answer"
|
||||||
|
|
||||||
|
|
||||||
def test_nonstreaming_non_leading_end_tag_is_content():
|
def test_nonstreaming_end_tag_in_content_state_is_dropped():
|
||||||
parser, _ = make_parser()
|
parser, _ = make_parser()
|
||||||
request = ChatCompletionRequest(messages=[], model="test-model")
|
request = ChatCompletionRequest(messages=[], model="test-model")
|
||||||
|
|
||||||
reasoning, content = parser.extract_reasoning("XXX</mm:think>YYY", request)
|
reasoning, content = parser.extract_reasoning("XXX</mm:think>YYY", request)
|
||||||
|
|
||||||
assert reasoning is None
|
assert reasoning is None
|
||||||
assert content == "XXX</mm:think>YYY"
|
assert content == "XXXYYY"
|
||||||
|
|
||||||
|
|
||||||
def test_nonstreaming_enabled_mode_starts_in_reasoning():
|
def test_nonstreaming_enabled_mode_starts_in_reasoning():
|
||||||
@@ -246,7 +261,7 @@ def test_streaming_drops_leading_end_tag():
|
|||||||
assert end_states == [True, True]
|
assert end_states == [True, True]
|
||||||
|
|
||||||
|
|
||||||
def test_streaming_non_leading_end_tag_is_content():
|
def test_streaming_end_tag_in_content_state_is_dropped():
|
||||||
parser, tokenizer = make_parser()
|
parser, tokenizer = make_parser()
|
||||||
|
|
||||||
reasoning, content, end_states = run_streaming(
|
reasoning, content, end_states = run_streaming(
|
||||||
@@ -256,7 +271,7 @@ def test_streaming_non_leading_end_tag_is_content():
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert reasoning is None
|
assert reasoning is None
|
||||||
assert content == "XXX</mm:think>YYY"
|
assert content == "XXXYYY"
|
||||||
assert end_states == [True]
|
assert end_states == [True]
|
||||||
|
|
||||||
|
|
||||||
@@ -288,6 +303,110 @@ def test_streaming_plain_content_ends_reasoning_phase():
|
|||||||
assert end_states == [True, True]
|
assert end_states == [True, True]
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_split_marker_tokens_are_not_returned():
|
||||||
|
tokenizer = RuntimeSplitMiniMaxM3Tokenizer()
|
||||||
|
parser = MiniMaxM3ReasoningParser(tokenizer)
|
||||||
|
|
||||||
|
reasoning, content, end_states = run_streaming(
|
||||||
|
parser,
|
||||||
|
tokenizer,
|
||||||
|
["<mm:think>", "Reasoning", " content", "</mm:think>", "content"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert reasoning == "Reasoning content"
|
||||||
|
assert content == "content"
|
||||||
|
assert end_states == [False, False, False, True, True]
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_split_marker_text_drives_end_state():
|
||||||
|
tokenizer = RuntimeSplitMiniMaxM3Tokenizer()
|
||||||
|
parser = MiniMaxM3ReasoningParser(tokenizer)
|
||||||
|
previous_text = ""
|
||||||
|
previous_token_ids: list[int] = []
|
||||||
|
|
||||||
|
for chunk in ["<mm:think>", "Reasoning", " content", "</mm:think>"]:
|
||||||
|
delta_token_ids = tokenizer.encode_runtime(chunk)
|
||||||
|
current_text = previous_text + chunk
|
||||||
|
current_token_ids = previous_token_ids + delta_token_ids
|
||||||
|
parser.extract_reasoning_streaming(
|
||||||
|
previous_text=previous_text,
|
||||||
|
current_text=current_text,
|
||||||
|
delta_text=chunk,
|
||||||
|
previous_token_ids=previous_token_ids,
|
||||||
|
current_token_ids=current_token_ids,
|
||||||
|
delta_token_ids=delta_token_ids,
|
||||||
|
)
|
||||||
|
previous_text = current_text
|
||||||
|
previous_token_ids = current_token_ids
|
||||||
|
|
||||||
|
assert parser.is_reasoning_end_streaming(previous_token_ids, []) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_split_marker_tokens_enabled_mode():
|
||||||
|
tokenizer = RuntimeSplitMiniMaxM3Tokenizer()
|
||||||
|
parser = MiniMaxM3ReasoningParser(
|
||||||
|
tokenizer, chat_template_kwargs={"thinking_mode": "enabled"}
|
||||||
|
)
|
||||||
|
|
||||||
|
reasoning, content, end_states = run_streaming(
|
||||||
|
parser,
|
||||||
|
tokenizer,
|
||||||
|
["Reasoning", " content", "</mm:think>", "content"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert reasoning == "Reasoning content"
|
||||||
|
assert content == "content"
|
||||||
|
assert end_states == [False, False, True, True]
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_split_marker_text_across_deltas():
|
||||||
|
tokenizer = RuntimeSplitMiniMaxM3Tokenizer()
|
||||||
|
parser = MiniMaxM3ReasoningParser(tokenizer)
|
||||||
|
|
||||||
|
reasoning, content, end_states = run_streaming(
|
||||||
|
parser,
|
||||||
|
tokenizer,
|
||||||
|
["<mm:", "think>", "Reasoning", " content", "</mm:", "think>", "content"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert reasoning == "Reasoning content"
|
||||||
|
assert content == "content"
|
||||||
|
assert end_states == [False, False, False, False, False, True, True]
|
||||||
|
|
||||||
|
|
||||||
|
def test_streaming_split_leading_end_marker_text_across_deltas():
|
||||||
|
tokenizer = RuntimeSplitMiniMaxM3Tokenizer()
|
||||||
|
parser = MiniMaxM3ReasoningParser(tokenizer)
|
||||||
|
|
||||||
|
reasoning, content, end_states = run_streaming(
|
||||||
|
parser,
|
||||||
|
tokenizer,
|
||||||
|
["</mm:", "think>", "content"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert reasoning is None
|
||||||
|
assert content == "content"
|
||||||
|
assert end_states == [False, True, True]
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_id_helpers_with_split_marker_tokens():
|
||||||
|
tokenizer = SplitMiniMaxM3Tokenizer()
|
||||||
|
parser = MiniMaxM3ReasoningParser(tokenizer)
|
||||||
|
output_ids = tokenizer.encode(
|
||||||
|
"<mm:think>abc</mm:think>def", add_special_tokens=False
|
||||||
|
)
|
||||||
|
open_reasoning_ids = tokenizer.encode("<mm:think>abc", add_special_tokens=False)
|
||||||
|
content_ids = tokenizer.encode("plain", add_special_tokens=False)
|
||||||
|
|
||||||
|
assert parser.is_reasoning_end(output_ids)
|
||||||
|
assert not parser.is_reasoning_end(open_reasoning_ids)
|
||||||
|
assert not parser.is_reasoning_end(content_ids)
|
||||||
|
assert tokenizer.decode(parser.extract_content_ids(output_ids)) == "def"
|
||||||
|
assert parser.extract_content_ids(open_reasoning_ids) == []
|
||||||
|
assert parser.extract_content_ids(content_ids) == content_ids
|
||||||
|
assert parser.count_reasoning_tokens(output_ids) == len(tokenizer.encode("abc"))
|
||||||
|
|
||||||
|
|
||||||
def test_token_id_helpers():
|
def test_token_id_helpers():
|
||||||
parser, tokenizer = make_parser()
|
parser, tokenizer = make_parser()
|
||||||
output_ids = tokenizer.encode(
|
output_ids = tokenizer.encode(
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
|
"""MiniMax M3 parser for reasoning markers."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import functools
|
||||||
|
from collections.abc import Iterable, Sequence
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from vllm.parser.engine.events import EventType
|
||||||
|
from vllm.parser.engine.parser_engine import ParserEngine
|
||||||
|
from vllm.parser.engine.parser_engine_config import (
|
||||||
|
ParserEngineConfig,
|
||||||
|
ParserState,
|
||||||
|
Transition,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from vllm.tokenizers import TokenizerLike
|
||||||
|
from vllm.tool_parsers.abstract_tool_parser import Tool
|
||||||
|
|
||||||
|
THINK_START = "<mm:think>"
|
||||||
|
THINK_END = "</mm:think>"
|
||||||
|
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def minimax_m3_config(thinking: bool = False) -> ParserEngineConfig:
|
||||||
|
return ParserEngineConfig(
|
||||||
|
name="minimax_m3",
|
||||||
|
initial_state=ParserState.REASONING if thinking else ParserState.CONTENT,
|
||||||
|
terminals={
|
||||||
|
"THINK_START": THINK_START,
|
||||||
|
"THINK_END": THINK_END,
|
||||||
|
},
|
||||||
|
transitions={
|
||||||
|
(ParserState.CONTENT, "THINK_START"): Transition(
|
||||||
|
ParserState.REASONING,
|
||||||
|
(EventType.REASONING_START,),
|
||||||
|
),
|
||||||
|
(ParserState.REASONING, "THINK_START"): Transition(
|
||||||
|
ParserState.REASONING,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
(ParserState.REASONING, "THINK_END"): Transition(
|
||||||
|
ParserState.CONTENT,
|
||||||
|
(EventType.REASONING_END,),
|
||||||
|
),
|
||||||
|
(ParserState.CONTENT, "THINK_END"): Transition(
|
||||||
|
ParserState.CONTENT,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MiniMaxM3Parser(ParserEngine):
|
||||||
|
"""MiniMax M3 parser backed by the declarative parser engine."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tokenizer: TokenizerLike,
|
||||||
|
tools: list[Tool] | None = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
chat_kwargs = kwargs.get("chat_template_kwargs", {}) or {}
|
||||||
|
self._initial_in_reasoning = chat_kwargs.get("thinking_mode") == "enabled"
|
||||||
|
kwargs.setdefault(
|
||||||
|
"parser_engine_config",
|
||||||
|
minimax_m3_config(thinking=self._initial_in_reasoning),
|
||||||
|
)
|
||||||
|
super().__init__(tokenizer, tools, **kwargs)
|
||||||
|
self._start_token_ids = self._encode_marker(THINK_START)
|
||||||
|
self._end_token_ids = self._encode_marker(THINK_END)
|
||||||
|
|
||||||
|
def _encode_marker(self, marker: str) -> tuple[int, ...]:
|
||||||
|
try:
|
||||||
|
token_ids = self.model_tokenizer.encode(marker, add_special_tokens=False)
|
||||||
|
except TypeError:
|
||||||
|
token_ids = self.model_tokenizer.encode(marker)
|
||||||
|
return tuple(token_ids)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _contains_token_sequence(
|
||||||
|
token_ids: Sequence[int], marker_ids: Sequence[int]
|
||||||
|
) -> bool:
|
||||||
|
if not marker_ids or len(marker_ids) > len(token_ids):
|
||||||
|
return False
|
||||||
|
marker_len = len(marker_ids)
|
||||||
|
return any(
|
||||||
|
tuple(token_ids[i : i + marker_len]) == tuple(marker_ids)
|
||||||
|
for i in range(len(token_ids) - marker_len + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _rfind_token_sequence(
|
||||||
|
token_ids: Sequence[int], marker_ids: Sequence[int]
|
||||||
|
) -> int:
|
||||||
|
if not marker_ids or len(marker_ids) > len(token_ids):
|
||||||
|
return -1
|
||||||
|
marker_len = len(marker_ids)
|
||||||
|
for i in range(len(token_ids) - marker_len, -1, -1):
|
||||||
|
if tuple(token_ids[i : i + marker_len]) == tuple(marker_ids):
|
||||||
|
return i
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def is_reasoning_end(self, input_ids: list[int]) -> bool:
|
||||||
|
start_index = self._rfind_token_sequence(input_ids, self._start_token_ids)
|
||||||
|
end_index = self._rfind_token_sequence(input_ids, self._end_token_ids)
|
||||||
|
if end_index < 0:
|
||||||
|
return False
|
||||||
|
if start_index < 0:
|
||||||
|
return True
|
||||||
|
return end_index > start_index
|
||||||
|
|
||||||
|
def is_reasoning_end_streaming(
|
||||||
|
self, input_ids: Sequence[int], delta_ids: Iterable[int]
|
||||||
|
) -> bool:
|
||||||
|
if self.reasoning_ended:
|
||||||
|
return True
|
||||||
|
if self._engine._lexer.buffer:
|
||||||
|
return False
|
||||||
|
if self._initial_in_reasoning:
|
||||||
|
return False
|
||||||
|
if self._engine.state == ParserState.CONTENT:
|
||||||
|
return bool(input_ids)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def extract_content_ids(self, input_ids: list[int]) -> list[int]:
|
||||||
|
end_index = self._rfind_token_sequence(input_ids, self._end_token_ids)
|
||||||
|
if end_index >= 0:
|
||||||
|
return input_ids[end_index + len(self._end_token_ids) :]
|
||||||
|
|
||||||
|
has_start = self._contains_token_sequence(input_ids, self._start_token_ids)
|
||||||
|
if self._initial_in_reasoning and not has_start:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if not has_start:
|
||||||
|
return input_ids
|
||||||
|
return []
|
||||||
|
|
||||||
|
def count_reasoning_tokens(self, token_ids: Sequence[int]) -> int:
|
||||||
|
count = 0
|
||||||
|
depth = 1 if self._initial_in_reasoning else 0
|
||||||
|
i = 0
|
||||||
|
while i < len(token_ids):
|
||||||
|
if tuple(token_ids[i : i + len(self._start_token_ids)]) == (
|
||||||
|
self._start_token_ids
|
||||||
|
):
|
||||||
|
depth += 1
|
||||||
|
i += len(self._start_token_ids)
|
||||||
|
continue
|
||||||
|
if tuple(token_ids[i : i + len(self._end_token_ids)]) == (
|
||||||
|
self._end_token_ids
|
||||||
|
):
|
||||||
|
if depth > 0:
|
||||||
|
depth -= 1
|
||||||
|
i += len(self._end_token_ids)
|
||||||
|
continue
|
||||||
|
if depth > 0:
|
||||||
|
count += 1
|
||||||
|
i += 1
|
||||||
|
return count
|
||||||
@@ -2,170 +2,19 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
|
|
||||||
from collections.abc import Iterable, Sequence
|
from collections.abc import Iterable, Sequence
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from vllm.entrypoints.openai.engine.protocol import DeltaMessage
|
from vllm.parser.engine.adapters import ParserEngineReasoningAdapter
|
||||||
from vllm.reasoning.basic_parsers import BaseThinkingReasoningParser
|
from vllm.parser.minimax_m3 import MiniMaxM3Parser
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
|
|
||||||
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
|
|
||||||
|
|
||||||
|
|
||||||
class MiniMaxM3ReasoningParser(BaseThinkingReasoningParser):
|
class MiniMaxM3ReasoningParser(ParserEngineReasoningAdapter):
|
||||||
"""Reasoning parser for MiniMax M3 explicit thinking blocks.
|
"""Reasoning parser adapter for MiniMax M3 explicit thinking blocks."""
|
||||||
|
|
||||||
MiniMax M3 emits reasoning as:
|
_parser_engine_cls = MiniMaxM3Parser
|
||||||
|
|
||||||
<mm:think>reasoning text</mm:think>assistant content
|
|
||||||
|
|
||||||
The M3 tokenizer exposes both markers as complete vocabulary tokens. The
|
|
||||||
chat template may also prefill the start marker when
|
|
||||||
``thinking_mode="enabled"``, so generated text can begin directly inside a
|
|
||||||
reasoning block without emitting ``<mm:think>`` again.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def start_token(self) -> str:
|
|
||||||
return "<mm:think>"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def end_token(self) -> str:
|
|
||||||
return "</mm:think>"
|
|
||||||
|
|
||||||
def __init__(self, tokenizer, *args, **kwargs):
|
|
||||||
super().__init__(tokenizer, *args, **kwargs)
|
|
||||||
chat_kwargs = kwargs.get("chat_template_kwargs", {}) or {}
|
|
||||||
self._initial_in_reasoning = chat_kwargs.get("thinking_mode") == "enabled"
|
|
||||||
self._at_response_start = True
|
|
||||||
|
|
||||||
def extract_reasoning(
|
|
||||||
self,
|
|
||||||
model_output: str,
|
|
||||||
request: "ChatCompletionRequest | ResponsesRequest",
|
|
||||||
) -> tuple[str | None, str | None]:
|
|
||||||
# MiniMax M3 can start a response with a stray closer. Drop that first
|
|
||||||
# token only; later unmatched closers stay visible as content.
|
|
||||||
if not self._initial_in_reasoning and model_output.startswith(self.end_token):
|
|
||||||
content = model_output[len(self.end_token) :]
|
|
||||||
return None, content or None
|
|
||||||
|
|
||||||
if self._initial_in_reasoning and self.start_token not in model_output:
|
|
||||||
reasoning, end, content = model_output.partition(self.end_token)
|
|
||||||
if not end:
|
|
||||||
return model_output, None
|
|
||||||
return reasoning, content or None
|
|
||||||
|
|
||||||
if self.start_token not in model_output:
|
|
||||||
return None, model_output
|
|
||||||
|
|
||||||
content_before, _, after_start = model_output.partition(self.start_token)
|
|
||||||
reasoning, end, content_after = after_start.partition(self.end_token)
|
|
||||||
if not end:
|
|
||||||
return reasoning, content_before or None
|
|
||||||
|
|
||||||
return reasoning, (content_before + content_after) or None
|
|
||||||
|
|
||||||
def is_reasoning_end_streaming(
|
def is_reasoning_end_streaming(
|
||||||
self, input_ids: Sequence[int], delta_ids: Iterable[int]
|
self, input_ids: Sequence[int], delta_ids: Iterable[int]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
delta_ids = tuple(delta_ids)
|
return self._parser_engine.is_reasoning_end_streaming(
|
||||||
if self.end_token_id in delta_ids:
|
list(input_ids), tuple(delta_ids)
|
||||||
return True
|
)
|
||||||
if self.end_token_id in input_ids:
|
|
||||||
return True
|
|
||||||
if self._initial_in_reasoning:
|
|
||||||
return False
|
|
||||||
if self.start_token_id not in input_ids:
|
|
||||||
return bool(input_ids)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def extract_content_ids(self, input_ids: list[int]) -> list[int]:
|
|
||||||
if self.end_token_id in input_ids:
|
|
||||||
end_index = len(input_ids) - 1 - input_ids[::-1].index(self.end_token_id)
|
|
||||||
return input_ids[end_index + 1 :]
|
|
||||||
|
|
||||||
if self._initial_in_reasoning and self.start_token_id not in input_ids:
|
|
||||||
return []
|
|
||||||
|
|
||||||
if self.start_token_id not in input_ids:
|
|
||||||
return input_ids
|
|
||||||
return []
|
|
||||||
|
|
||||||
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:
|
|
||||||
if not delta_text:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self._at_response_start and not self._initial_in_reasoning:
|
|
||||||
# Apply the leading-closer tolerance once. Later unmatched closers
|
|
||||||
# stay visible as content.
|
|
||||||
self._at_response_start = False
|
|
||||||
if delta_text.startswith(self.end_token):
|
|
||||||
delta_text = delta_text[len(self.end_token) :]
|
|
||||||
if not delta_text:
|
|
||||||
return None
|
|
||||||
if delta_token_ids and delta_token_ids[0] == self.end_token_id:
|
|
||||||
delta_token_ids = delta_token_ids[1:]
|
|
||||||
|
|
||||||
if self.end_token_id in previous_token_ids:
|
|
||||||
return DeltaMessage(content=delta_text)
|
|
||||||
|
|
||||||
if (
|
|
||||||
self._initial_in_reasoning
|
|
||||||
and self.start_token_id not in previous_token_ids
|
|
||||||
and self.start_token_id not in delta_token_ids
|
|
||||||
):
|
|
||||||
if self.end_token_id in delta_token_ids:
|
|
||||||
reasoning, _, content = delta_text.partition(self.end_token)
|
|
||||||
return DeltaMessage(
|
|
||||||
reasoning=reasoning or None,
|
|
||||||
content=content or None,
|
|
||||||
)
|
|
||||||
return DeltaMessage(reasoning=delta_text)
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.start_token_id not in previous_token_ids
|
|
||||||
and self.start_token_id not in delta_token_ids
|
|
||||||
):
|
|
||||||
return DeltaMessage(content=delta_text)
|
|
||||||
|
|
||||||
if self.end_token_id in delta_token_ids:
|
|
||||||
reasoning_text, _, content = delta_text.partition(self.end_token)
|
|
||||||
if self.start_token_id in delta_token_ids:
|
|
||||||
_, _, reasoning_text = reasoning_text.partition(self.start_token)
|
|
||||||
return DeltaMessage(
|
|
||||||
reasoning=reasoning_text or None,
|
|
||||||
content=content or None,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.start_token_id in delta_token_ids:
|
|
||||||
_, _, reasoning = delta_text.partition(self.start_token)
|
|
||||||
return DeltaMessage(reasoning=reasoning) if reasoning else None
|
|
||||||
|
|
||||||
return DeltaMessage(reasoning=delta_text)
|
|
||||||
|
|
||||||
def count_reasoning_tokens(self, token_ids: Sequence[int]) -> int:
|
|
||||||
if not self._initial_in_reasoning:
|
|
||||||
return super().count_reasoning_tokens(token_ids)
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
depth = 1
|
|
||||||
for token_id in token_ids:
|
|
||||||
if token_id == self.start_token_id:
|
|
||||||
depth += 1
|
|
||||||
continue
|
|
||||||
if token_id == self.end_token_id:
|
|
||||||
if depth > 0:
|
|
||||||
depth -= 1
|
|
||||||
continue
|
|
||||||
if depth > 0:
|
|
||||||
count += 1
|
|
||||||
return count
|
|
||||||
|
|||||||
Reference in New Issue
Block a user