| 1 | # vllm/reasoning |
| 2 | |
| 3 | Every `--reasoning-parser` name resolves here. The parser has two consumers: |
| 4 | `ParserManager.get_parser` composes it with the tool parser into the unified |
| 5 | `Parser` the serving layer uses, and the structured-output manager |
| 6 | (`vllm/v1/structured_output/__init__.py`) instantiates it directly and calls |
| 7 | `is_reasoning_end` / `is_reasoning_end_streaming` every decode step to decide |
| 8 | when the grammar bitmask starts applying. The `ReasoningParser` interface |
| 9 | itself is legacy: it is kept so existing in-tree parsers and out-of-tree |
| 10 | plugins keep working, not as the base for new ones. |
| 11 | |
| 12 | ## Layout |
| 13 | |
| 14 | - `abs_reasoning_parsers.py` — `ReasoningParser` (`extract_reasoning`, |
| 15 | `extract_reasoning_streaming`, `is_reasoning_end`, |
| 16 | `is_reasoning_end_streaming`, `extract_content_ids`, `adjust_request`) and |
| 17 | `ReasoningParserManager`. |
| 18 | - `__init__.py` — `_REASONING_PARSERS_TO_REGISTER`, the name → (file, class) |
| 19 | table that lazily registers every built-in parser. |
| 20 | - `basic_parsers.py` — `BaseThinkingReasoningParser`, the base for parsers |
| 21 | that only need a start/end token pair. |
| 22 | - `<model>_reasoning_parser.py` — one file per family. Either a subclass of a |
| 23 | `ParserReasoningAdapter` generated from a `vllm/parser/<model>.py` engine |
| 24 | grammar (`mistral_reasoning_parser.py`, `kimi_k2_reasoning_parser.py`; the |
| 25 | `_engine_` suffix is not applied consistently), or a self-contained |
| 26 | `ReasoningParser` subclass. |
| 27 | |
| 28 | ## Guidelines |
| 29 | |
| 30 | - Do not change the `ReasoningParser` interface in `abs_reasoning_parsers.py`. |
| 31 | Out-of-tree plugins loaded through |
| 32 | `ReasoningParserManager.import_reasoning_parser` subclass it. New |
| 33 | capabilities go on `Parser` in `vllm/parser/`; if a legacy parser needs the |
| 34 | same capability, `DelegatingParser` supplies the default. |
| 35 | - For a new model, implement a unified `Parser` in `vllm/parser/` first, then |
| 36 | expose it here as a thin adapter subclass plus an entry in |
| 37 | `_REASONING_PARSERS_TO_REGISTER`. |
| 38 | - The `_REASONING_PARSERS_TO_REGISTER` key is what users pass on the command |
| 39 | line; treat renames as breaking. |
| 40 | - Keep adapters thin: registration, `adjust_request` tweaks, and class |
| 41 | attributes. Parsing logic belongs in `vllm/parser/`. |
| 42 | - Keep a model-specific fix inside that model's `<model>_reasoning_parser.py`, |
| 43 | or its grammar in `vllm/parser/<model>.py`, where possible. Touch |
| 44 | `abs_reasoning_parsers.py` or `basic_parsers.py` only for behavior every |
| 45 | parser needs, and say so in the PR. |
| 46 | - `is_reasoning_end_streaming` runs on the engine core for every decode step |
| 47 | of every structured-output request. Work on `delta_ids`; re-scanning the |
| 48 | full sequence makes the step cost grow with sequence length. |
| 49 | - The structured-output gate and the frontend's extraction must agree on |
| 50 | where reasoning ends, whether the turn continues with content or a tool |
| 51 | call. Test the end-detection methods with the same inputs as extraction. |
| 52 | - Comment only where the code alone would leave a reader with a question — |
| 53 | a request tweak in `adjust_request`, a marker handled unusually, a model |
| 54 | quirk being worked around. Keep it minimal and concise. |