[Bugfix][Parser] Emit buffered post-reasoning text when no tool parser is configured (#58911)
Signed-off-by: Dhairyashil R G <18738735+dhairyashilRG@users.noreply.github.com> Signed-off-by: sfeng33 <4florafeng@gmail.com> Co-authored-by: sfeng33 <4florafeng@gmail.com>
This commit is contained in:
co-authored by
sfeng33
parent
4ac5a36a8e
commit
d6fe5dca68
@@ -663,6 +663,78 @@ def test_engine_reasoning_no_tool_batched_content_passthrough(tokenizer, request
|
||||
assert len(tool_calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text_chunks",
|
||||
[
|
||||
# A trailing "<" that the engine holds back (it may start a tag).
|
||||
["<think>let me think</think>a <", "x> b"],
|
||||
# Whitespace after "</think>" in the same delta as the marker.
|
||||
["<think>let me think</think>\n\n", "<b>bold</b> done"],
|
||||
# The delta that ends reasoning carries only held-back text.
|
||||
["<think>let me think", "</think><", "b>bold</b> done"],
|
||||
],
|
||||
)
|
||||
def test_engine_reasoning_no_tool_batched_holdback_not_dropped(
|
||||
tokenizer, request_obj, text_chunks
|
||||
):
|
||||
"""Text the engine buffers when reasoning ends inside a batched delta is
|
||||
returned by finish_streaming(); with no tool parser it must still be
|
||||
emitted as content, not dropped."""
|
||||
parser = Qwen3ReasoningNoToolParser(tokenizer)
|
||||
chunks = [tokenizer.encode(t, add_special_tokens=False) for t in text_chunks]
|
||||
|
||||
results = stream_chunks(parser, tokenizer, chunks, request_obj)
|
||||
reasoning, content, tool_calls = collect_fields(results)
|
||||
|
||||
assert reasoning == "let me think"
|
||||
assert content == "".join(text_chunks).split("</think>", 1)[1]
|
||||
assert len(tool_calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text_chunks", "include_reasoning", "expected_content"),
|
||||
[
|
||||
# The delta that ends reasoning is also the last one: the held-back
|
||||
# "<" must be emitted exactly once (the end-of-stream flush skips the
|
||||
# reasoning parser once reasoning has ended).
|
||||
(["<think>let me think</think>a <"], True, "a <"),
|
||||
# Whitespace-only content after the marker at the end of the stream.
|
||||
(["<think>let me think</think>\n\n"], True, "\n\n"),
|
||||
# Hiding reasoning must not hide the flushed content.
|
||||
(["<think>let me think</think>\n\n", "<b>bold</b>"], False, "\n\n<b>bold</b>"),
|
||||
],
|
||||
)
|
||||
def test_engine_reasoning_no_tool_holdback_finished_and_hidden_reasoning(
|
||||
tokenizer, text_chunks, include_reasoning, expected_content
|
||||
):
|
||||
request = ChatCompletionRequest(
|
||||
model="test-model",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
include_reasoning=include_reasoning,
|
||||
)
|
||||
parser = Qwen3ReasoningNoToolParser(tokenizer)
|
||||
chunks = [tokenizer.encode(t, add_special_tokens=False) for t in text_chunks]
|
||||
|
||||
results: list[DeltaMessage | None] = []
|
||||
prompt_token_ids: list[int] | None = []
|
||||
for i, chunk in enumerate(chunks):
|
||||
results.append(
|
||||
parser.parse_delta(
|
||||
tokenizer.decode(chunk),
|
||||
chunk,
|
||||
request,
|
||||
prompt_token_ids=prompt_token_ids,
|
||||
finished=i == len(chunks) - 1,
|
||||
)
|
||||
)
|
||||
prompt_token_ids = None
|
||||
reasoning, content, tool_calls = collect_fields(results)
|
||||
|
||||
assert reasoning == ("let me think" if include_reasoning else "")
|
||||
assert content == expected_content
|
||||
assert len(tool_calls) == 0
|
||||
|
||||
|
||||
def _decode_stream_deltas(tokenizer, groups):
|
||||
"""Decode token-ID groups into ``(delta_text, group)`` pairs via the real
|
||||
incremental ``DecodeStream``.
|
||||
|
||||
@@ -904,8 +904,17 @@ class DelegatingParser(Parser):
|
||||
(delta_message.content if delta_message else None) or ""
|
||||
) + ((flush_delta.content if flush_delta else None) or "")
|
||||
if self._engine_based:
|
||||
if delta_message and self._tool_parser is not None:
|
||||
delta_message.content = None
|
||||
if self._tool_parser is not None:
|
||||
# The tool-call phase below consumes current_text.
|
||||
if delta_message:
|
||||
delta_message.content = None
|
||||
elif current_text:
|
||||
# No tool phase follows to consume the flushed text,
|
||||
# and the engine is not fed again after the
|
||||
# transition, so emit it as content here.
|
||||
if delta_message is None:
|
||||
delta_message = DeltaMessage()
|
||||
delta_message.content = current_text
|
||||
else:
|
||||
delta_text = current_text
|
||||
|
||||
|
||||
Reference in New Issue
Block a user