[Agent] Add agent instructions for parser directories (#56883)
Signed-off-by: sfeng33 <4florafeng@gmail.com>
Flora Feng committed
Sep 16, 2026 at 06:55 UTC
3bb782621492711485dc86791b5978128783814a
6 files changed
+143
vllm/parser/AGENTS.md
new
+41
@@ -0,0 +1,41 @@
1
+# vllm/parser
2
+
3
+Model-output parsing for the serving layer. `Parser` (`abstract_parser.py`)
4
+unifies reasoning extraction and tool-call extraction behind one object; the
5
+serving layer calls `parse` / `parse_delta` and `is_reasoning_end` on it.
6
+
7
+## Layout
8
+
9
+- `abstract_parser.py` — `Parser`, and `DelegatingParser` for wrapping a legacy
10
+ `ReasoningParser` + `ToolParser` pair.
11
+- `parser_manager.py` — resolves parser names to classes.
12
+- `engine/` — the declarative parser engine. A `ParserEngineConfig`
13
+ (`parser_engine_config.py`) is a set of terminals, transitions and content
14
+ events; `parser_engine.py` and `streaming_parser_engine.py` drive it for the
15
+ non-streaming and streaming paths; `token_id_scanner.py` pre-lexes
16
+ special-token ids and `incremental_lexer.py` lexes text; `adapters.py`
17
+ exposes an engine through the legacy reasoning/tool parser interfaces.
18
+- `<model>.py` — one file per model family. Most subclass `ParserEngine`
19
+ (`deepseek_v4.py`, `qwen3.py`, ...); a few are `DelegatingParser` wrappers
20
+ over legacy parsers (`harmony.py`, `kimi_k3.py`).
21
+
22
+## Guidelines
23
+
24
+- A new parser either subclasses `ParserEngine` with a declarative config, or
25
+ subclasses `Parser` and implements `parse` / `parse_delta` directly. Use the
26
+ engine when the format is expressible as terminals and transitions, and
27
+ subclass an existing config when it is a variant of one (`seed_oss.py`,
28
+ `ling3.py`). `DelegatingParser` exists for backward compatibility with the
29
+ separate `ReasoningParser` / `ToolParser` classes; do not use it for new
30
+ formats.
31
+- Keep a model-specific fix inside that model's `<model>.py` where possible.
32
+ Change `engine/` or `abstract_parser.py` only when the behavior is wrong for
33
+ every parser, and say so in the PR.
34
+- Text emitted in a state with no `content_events` entry is discarded. Make
35
+ that deliberate when adding a state.
36
+- Special tokens reach the engine as token ids, not text. A text terminal that
37
+ spans a special token never matches in streaming; list it in
38
+ `token_id_terminals`.
39
+- Comment only where the code alone would leave a reader with a question —
40
+ a non-obvious transition, a deliberate swallow, a model quirk being worked
41
+ around. Keep it minimal and concise.
vllm/parser/CLAUDE.md
new
+1
@@ -0,0 +1 @@
1
+@AGENTS.md
vllm/reasoning/AGENTS.md
new
+54
@@ -0,0 +1,54 @@
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.
vllm/reasoning/CLAUDE.md
new
+1
@@ -0,0 +1 @@
1
+@AGENTS.md
vllm/tool_parsers/AGENTS.md
new
+45
@@ -0,0 +1,45 @@
1
+# vllm/tool_parsers
2
+
3
+Every `--tool-call-parser` name resolves here. `ParserManager.get_parser`
4
+composes the tool parser chosen by name with the reasoning parser into the
5
+unified `Parser` the serving layer uses. The `ToolParser` interface itself is
6
+legacy: it is kept so existing in-tree parsers and out-of-tree plugins keep
7
+working, not as the base for new ones.
8
+
9
+## Layout
10
+
11
+- `abstract_tool_parser.py` — `ToolParser` (`extract_tool_calls`,
12
+ `extract_tool_calls_streaming`, `adjust_request`) and `ToolParserManager`.
13
+- `__init__.py` — `_TOOL_PARSERS_TO_REGISTER`, the name → (file, class) table
14
+ that lazily registers every built-in parser.
15
+- `structural_tag_registry.py` — structural-tag builders used to constrain
16
+ tool-call output for `tool_choice="required"` / named tools.
17
+- `<model>_tool_parser.py` — one file per family. Either a subclass of a
18
+ `ParserToolAdapter` generated from a `vllm/parser/<model>.py` engine grammar
19
+ (`mistral_tool_parser.py`, `kimi_k2_tool_parser.py`; the `_engine_` suffix
20
+ is not applied consistently), or a self-contained `ToolParser` subclass.
21
+
22
+## Guidelines
23
+
24
+- Do not change the `ToolParser` interface in `abstract_tool_parser.py`.
25
+ Out-of-tree plugins loaded through `ToolParserManager.import_tool_parser`
26
+ subclass it. New capabilities go on `Parser` in `vllm/parser/`; if a legacy
27
+ parser needs the same capability, `DelegatingParser` supplies the default.
28
+- For a new model, implement a unified `Parser` in `vllm/parser/` first, then
29
+ expose it here as a thin adapter subclass plus an entry in
30
+ `_TOOL_PARSERS_TO_REGISTER`.
31
+- The `_TOOL_PARSERS_TO_REGISTER` key is what users pass on the command line;
32
+ treat renames as breaking.
33
+- Keep adapters thin: registration, `adjust_request` tweaks, and class
34
+ attributes. Parsing logic belongs in `vllm/parser/`.
35
+- Keep a model-specific fix inside that model's `<model>_tool_parser.py`, or
36
+ its grammar in `vllm/parser/<model>.py`, where possible. Touch
37
+ `abstract_tool_parser.py` or `structural_tag_registry.py` only for behavior
38
+ every parser needs, and say so in the PR.
39
+- Set `structural_tag_model` only to a key in `SUPPORTED_STRUCTURAL_TAG_MODELS`
40
+ (an xgrammar builtin, or one registered with `register_vllm_structural_tag`).
41
+ It only takes effect under `VLLM_ENFORCE_STRICT_TOOL_CALLING`, where it also
42
+ disables the generic required/named handling for that parser.
43
+- Comment only where the code alone would leave a reader with a question —
44
+ a request tweak in `adjust_request`, a marker handled unusually, a model
45
+ quirk being worked around. Keep it minimal and concise.
vllm/tool_parsers/CLAUDE.md
new
+1
@@ -0,0 +1 @@
1
+@AGENTS.md