Handle K2 reasoning boundaries and streamed tool calls - #12
Conversation
|
LGTM! |
|
Non-blocking note: This does not affect Chat Completions and should not block this PR. It may be worth handling empty reasoning consistently in the Responses path in a future follow-up. |
|
Responses API follow-up note: This PR keeps What remains unsupported is a lossless public channel for every generated token:
If vLLM wants to expose all sampled token IDs and logprobs, that should be a separate opt-in extension, such as a raw-token metadata event or explicit metadata fields on reasoning/tool events. Until then, usage counts all generated tokens, while Responses logprobs intentionally cover output text only. |
|
Two findings: Blocking — forced tool calls can be lost when reasoning and the tool call end in the same engine delta. In the named and is separated into Non-blocking — Responses API truncation behavior. We intentionally buffer output as reasoning until the closing-think token arrives. If the Responses API reaches This means incomplete reasoning can be returned as public assistant content. This is specific to the Responses API path and should not block this PR. |
|
Everything looks good. For posterity, I’m leaving a couple of non-blocking comments about the Responses API behavior:
Both observations are specific to the Responses API and do not affect Chat Completions. |
|
Addressed in
Previously, the named and Now, when reasoning ends, the remainder is passed to forced-tool handling immediately in the same iteration and the resulting tool-call delta is merged with the reasoning delta.
A complete required-tool array received in one chunk could produce The nesting calculation now uses the JSON prefix before |
Summary
reasoningandreasoning_contentat K2 reasoning boundaries, including""for an empty reasoning segmentK2 behavior
Non-streaming reasoning
<ifm|tool_calls>: the prefix is reasoning and the wrapped section is handed to the tool parser<ifm|tool_calls>: the entire output is content, avoiding an empty visible response"", notnullTool-like markup before an explicit reasoning end token remains reasoning and is never executed.
Streaming tool calls
tool_callsfinish reason<ifm|tool_calls>wrapper as an IFM tool boundary; a standalone<ifm|tool_call>remains ordinary contentmulti_formatretains Stream content and IFM tool calls in multi-format parser #10's broader compatibility behaviorThe missing-reasoning-end fallback remains intentionally scoped to non-streaming parsing in this PR.
Testing
tests/reasoning/test_k2_v3_reasoning_parser.py: 105 passedtests/entrypoints/openai/tool_parsers/test_multi_format_tool_parser.py: 42 passedtests/entrypoints/openai/test_serving_responses.py: 10 passedtests/entrypoints/openai/test_tool_parser_kwargs.py: 3 passedgit diff --check: passedThe full Chat test file also ran all 9 CPU-only tests successfully; 6 GPT-OSS server tests could not initialize because the test environment has no detectable accelerator device.