[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