fix(TOOLS-STREAMING-PARSER): an engine-backed tool parser silently discards the requested reasoning parser, even a mismatched one
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 423
- Forks
- 53
- Avg merge
- 20h 26m
- Merged PRs (30d)
- 310
Description
Row: TOOLS-STREAMING-PARSER (.agents/engine-matrix.md:165), which owns the serving-SSE dispatch swap that introduced the line. Secondary: TOOLS-CALLING-CORE (.agents/engine-matrix.md:164), which owns the two selection flags. Both resolved by line number before filing.
Found by wave PORTQ-7 (#2717) re-deriving PORT-NOW entry [249], upstream 46638857fd vllm#52830. Nothing was executed.
Read the pre-pin section first: porting the commit literally is a no-op
git show 5559679229:vllm/parser/parser_manager.py contains no _get_parser_engine_cls and no shared-engine collapse. The collapse was added after the pin by ac2ae8798c vllm#45802 (2026-08-15, in range, recorded as INVENTORY not PORT-NOW at .agents/sync/2026-09-01-cdefd9d.md:819) and removed by 46638857fd (2026-08-26). git log -S'_get_parser_engine_cls' --oneline 5559679229..e126687a9a returns exactly those two SHAs.
Born and died inside the range. get_parser at e126687a9a is functionally identical to the pin for this concern, and this tree never had the code the commit deletes.
A scheduling consequence: if a future wave queues ac2ae8798c's parser_manager half, it must be skipped, not ported and then deleted.
The gap is real anyway, and it is this tree's own
This tree independently invented the same collapse, by hand, in the serving layer — and in a broader form than upstream ever shipped.
src/vllm/entrypoints/openai/serving_chat.cpp:786-787:
std::unique_ptr<ReasoningParser> reasoning_parser =
engine_parser != nullptr ? nullptr : MakeReasoningParser();
The requested reasoning parser is discarded whenever an engine-backed tool parser is live. The rationale is stated at :785 ("The engine-backed parser does reasoning itself, so it is bypassed there").
Upstream's collapse fired only when both sides resolved to the same engine class. Here it fires unconditionally, so a mismatched pair — say --tool-call-parser qwen3 --reasoning-parser deepseek_r1 — also silently drops the reasoning parser the user asked for.
This is a pre-pin divergence, not this commit's distance. A prior record already saw it: .agents/sync/2026-09-01-cdefd9d.md:1979-1980 reads "46638857fd vllm#52830 — serving_chat drops the reasoning parser when an engine-backed parser is active", and that is still true at bb2da6f97. (The two other .agents/ mentions, 2026-09-02-e126687.md:513-515 and 2026-09-02-db92053.md:200, are set-membership statements about the SHA's position relative to a sync range and read nothing about this tree.)
What is missing
Preserve the reasoning parser when an engine-backed tool parser is selected, and compose the two adapters rather than collapsing to one engine. Both halves already exist here: ParserEngineReasoningAdapter (include/vllm/entrypoints/openai/reasoning_parsers/parser_engine_adapter.h:54) and the tool-side ParserEngineToolAdapter. The shape that mirrors upstream is to drop the third engine_parser slot for the paired case and drive both adapters through the existing parser / reasoning_parser pair, which is what upstream's DelegatingParser(r_cls, t_cls) resolves to at both the pin and the target.
Size
~60-150 product lines, concentrated in src/vllm/entrypoints/openai/serving_chat.cpp: the three factories at :552-587, the wiring at :780-787, and the three engine_parser consumption sites at :465-467, :907-909, :1045-1048. Plus ~120-200 test lines.
Upstream's own tests are not portable as written. Their new assertions lean on count_reasoning_tokens and chat_template_kwargs, both explicitly declined by this seam at parser_engine_adapter.h:32-38 ("the name-only factory cannot carry request kwargs ... W4"). Re-derive them on observable reasoning_content instead.
What would sharpen the priority, and was not done
Whether the collapse is observable for a matched pair. For --tool-call-parser qwen3 --reasoning-parser qwen3 the engine emits reasoning_content itself (serving_chat.cpp:465-467, :907-909, :1045-1048), so end-user output is probably unchanged and only the mismatched pair regresses. One end-to-end run with mismatched parser names would decide whether this is a user-visible defect or a shape divergence. This wave executed nothing, so it is undetermined.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read the three factory sites and engine_parser uses in src/vllm/entrypoints/openai/serving_chat.cpp, together with ParserEngineReasoningAdapter in include/vllm/entrypoints/openai/reasoning_parsers/parser_engine_adapter.h. Run an end-to-end request with mismatched tool and reasoning parser names, then add focused coverage based on observable reasoning_content. Done means the requested reasoning parser remains active while tool parsing still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 64/100