Repository navigation
Conversation
|
This pull request has merge conflicts that must be resolved before it can be |
ce378be to
e97fcac
Compare
|
Rebased onto current main. The conflict was test-file adjacency with #60709, which landed this morning; both test sets are kept and the parser hunk from that fix sits in the argument-diff region, disjoint from the marker handling changed here. |
|
Please test with live model, unit test is not sufficient. |
|
@sfeng33 Live model run, as asked. This branch built from source on the macOS CPU backend (there are no prebuilt arm64 wheels), serving Case 1, the non-plugin action from #60341. A computation prompt puts the model into its trained interpreter format, so the generated stream starts with
Case 2, stream ends immediately after
The plugin-call path is untouched by this change. I could not coax the 1.8B model into emitting its trained plugin format on demand, so that path is asserted by the parser unit tests at this head (16 passed, 8 xfailed) rather than by a server transcript here. |
e97fcac to
afc1e79
Compare
|
Rebased onto current main; no semantic conflicts. The parser and streaming suites pass locally on this head (43 passed, 8 xfailed, CPU). |
afc1e79 to
e1c830a
Compare
smiyamoto3
left a comment
There was a problem hiding this comment.
One problem in the end-of-stream hook, inline. I confirmed the new tests catch the InternLM2 case.
| # a marker completes) get one chance to release it at end of stream, | ||
| # same opt-in shape as the reasoning fallback above. | ||
| tool_finish = getattr(self._tool_parser, "finish_streaming", None) | ||
| if tool_finish is not None: |
There was a problem hiding this comment.
The engine tool adapters already define finish_streaming (adapters.py), so this call drains the engine flush before _flush_engine_parsers runs, and only .content is kept. Held-back tool args get dropped: TestAdapterFinishOnStreamEnd::test_args_buffer_flushed_on_finished passes on main and fails here, and a Qwen3 stream cut off by max_tokens right after </parameter> now ends with {"city": "Tokyo instead of {"city": "Tokyo"}.
Skipping engine parsers here, like _flush_engine_parsers does, fixes both and tests/parser still passes. Renaming the hook would work too.
| if tool_finish is not None: | |
| if tool_finish is not None and not getattr( | |
| self._tool_parser, "engine_based_streaming", False | |
| ): |
There was a problem hiding this comment.
Confirmed and fixed in f519b89, taking your suggestion verbatim. I reproduced the failure first: TestAdapterFinishOnStreamEnd::test_args_buffer_flushed_on_finished fails on the previous head and passes with the guard, and the InternLM2/streaming/parser-engine suites plus the full tests/tool_parsers run stay green locally (1211 passed; the only errors are the llama3 tokenizer fixture needing HF auth for a gated repo, unrelated to this change).
You were right about the mechanism: the engine adapters' finish_streaming drains the engine flush early and only .content would survive in this hook, dropping held-back args. Skipping engine parsers here matches what _flush_engine_parsers already does, so I kept your naming over renaming the hook.
smiyamoto3
left a comment
There was a problem hiding this comment.
Thanks for the fix, LGTM. One small thing: mypy in pre-commit fails on the four new tests (Cannot assign to a method). Switching them to monkeypatch.setattr(...) like the rest of the file (since #62142) fixes it.
f519b89 to
861d860
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
…ead of dropping it Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Co-authored-by: Kimi Code <kimi-code@moonshot.cn>
Their finish_streaming drains the engine flush that _flush_engine_parsers already runs, and only .content survives here, so held-back tool args were dropped. Pointed out by smiyamoto3's review: TestAdapterFinishOnStreamEnd:: test_args_buffer_flushed_on_finished caught the regression. Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
861d860 to
7e6bbd6
Compare
|
Rebased onto current main to clear the conflict. #62142 rewrote the shared fixture to Verified on the pushed head (7e6bbd6): |
smiyamoto3
left a comment
There was a problem hiding this comment.
Thanks for the update. The monkeypatch version looks good to me.
Overview
Fixes #60341. In streaming mode
Internlm2ToolParserstops emitting anything once<|action_start|>appears and waits for the full<|action_start|><|plugin|>marker. When the action turns out to be a non-plugin block (<|interpreter|>), or the generation ends right after<|action_start|>, the rest of the output is never streamed, while the non-streaming path returns the same text verbatim as content.Claims
<|action_start|>is still a prefix of<|plugin|>; anything else streams through as content, matchingextract_tool_calls.finalize_generationgives a legacy tool parser that definesfinish_streaming()one chance to release held text at end of stream (opt-in viagetattr, same shape as the reasoning fallback right above it). This also wires up the existingK2HorizonToolParser.finish_streaming(), which was previously only called from its own test.<|plugin|>the parser waits exactly as before, and the name/argument partial-JSON path is untouched.Validation
Issue reproducer (an
<|interpreter|>block, and a stream ending right after<|action_start|>), run against current main and against this branch:"Let me compute that. "in both casesTests (CPU,
.venv/bin/python -m pytest):tests/tool_parsers/test_internlm2_tool_parser.py: 14 passed, 8 xfailed. New tests cover non-plugin release, hold-then-release across a chunk boundary inside<|plugin|>, end-of-stream flush, and that a truncated plugin call is not re-emitted as content.tests/parser/test_streaming.py tests/tool_parsers/test_k2_horizon_tool_parser.py: all passed, including a new test drivingparse_delta(..., finished=True)throughDelegatingParserfor the finalize wiring.ruff check/ruff format --checkclean on the touched files.Details
Root cause: the streaming guard required the combined marker
<|action_start|><|plugin|>in the unstreamed tail and returnedNoneotherwise, so any non-plugin action wedged the parser for the rest of the stream. There was also no end-of-stream hook on the tool-parser side, so even a legitimately held ambiguous window (a strict prefix of<|plugin|>) was dropped when generation stopped inside it.vllm/parser/abstract_parser.pyis touched on purpose, and thevllm/tool_parsersguidance says to call that out: releasing held text at end of stream is missing for every legacy tool parser with a hold-back, not just InternLM2, so the flush lives infinalize_generationnext to the reasoning fallback. It is opt-in: parsers withoutfinish_streaming()see no behavior change, engine-based parsers keep flushing through_flush_engine_parsers, and the hook name/signature follows the convention already used by the reasoning parsers andK2HorizonToolParser.Tradeoff to be aware of:
K2HorizonToolParserstreams that end mid-parse now emit their unresolved tail as final content instead of dropping it. That is what itsfinish_streaming()was written for, and its test suite passes unchanged.AI assistance was used in preparing this PR (Kimi Code); the change was validated locally as described above. On duplication: no open PR references #60341, and the other active InternLM2 streaming PR (#60363) fixes a different bug (argument characters dropped as partial JSON grows), so the two do not overlap.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.