Repository navigation
[Bugfix] Register tool-call parser metrics in omni's init paths - #8212
apex-mochen wants to merge 2 commits into
Conversation
|
This PR appears to belong to: docs/design/module/error_contracts.md, docs/design/module/profiling.md, docs/design/module/entrypoints.md, docs/design/module/observability.md. Module owners: @alex-jw-brooks @linyueqian @NickCao Routing: @alex-jw-brooks via module of the changed files, module named in the PR description, CODEOWNERS; @linyueqian via module of the changed files, module named in the PR description, CODEOWNERS; @NickCao via module of the changed files, module named in the PR description, CODEOWNERS @apex-mochen, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer. Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment. |
Omni ReviewBot triage noteAutomated triage of commit
These are automated triage suggestions only — the final decision belongs to the maintainers. |
Omni ReviewBot routing recordAssigned Direct under experiment |
vllm-omni-review-bot
left a comment
There was a problem hiding this comment.
Omni ReviewBot review
PR description
Omni's shared FastAPI app-state builder now calls upstream init_parser_metrics when --tool-call-parser is set, using the same served model name already computed for the serving objects. The frozen diff matches that claim: a nine-line registration in omni_init_app_state plus a parametrized wiring test. Previously omni still handed the parser into chat, responses, and Anthropic handlers, but record_tool_parser_invocation() stayed a no-op because the Prometheus counter was never created. With the flag set, /metrics should now expose vllm:tool_call_parser_invocations_total on the omni serve path, matching non-omni vllm serve.
Change flow
flowchart TD
A["[EXISTING] Omni serve with --tool-call-parser"]:::existing
B["[CHANGED] omni_init_app_state in api_server.py"]:::changed
C["[EXISTING] vllm.parser.metrics.init_parser_metrics"]:::existing
D["[EXISTING] /metrics vllm:tool_call_parser_invocations_total"]:::existing
E["[NEW] test_parser_metrics_registered_exactly_when_a_tool_parser_is_set"]:::new
A --> B
B --> C
C --> D
E --> B
classDef existing fill:#e5e7eb,stroke:#6b7280,color:#111827
classDef changed fill:#fef3c7,stroke:#d97706,color:#451a03,stroke-width:2px
classDef new fill:#dcfce7,stroke:#16a34a,color:#052e16,stroke-width:2px
classDef removed fill:#fee2e2,stroke:#dc2626,color:#450a0a,stroke-width:2px
CI at
a953a67a2d4e(2026-09-27T20:13:21.661711+00:00): required check(s) blocking:buildkite/vllm-omni(missing), and -5 more.
No actionable findings.
|
Author self-review (@apex-mochen), as the review bot asks. What I checked
|
a953a67 to
8ad9741
Compare
vllm_omni builds its own app state and never calls init_parser_metrics(), so record_tool_parser_invocation() returns early and every tool-call parser invocation is dropped from /metrics in omni modes. Mirror upstream's registration when a tool-call parser is configured. Signed-off-by: apex-mochen <265981728+apex-mochen@users.noreply.github.com>
8ad9741 to
8ea2432
Compare
vllm-omni-review-bot
left a comment
There was a problem hiding this comment.
Omni ReviewBot review
PR description
Omni’s omni_init_app_state now calls upstream init_parser_metrics() when --tool-call-parser is set, matching upstream init_app_state. Without that registration, parser invocations still run but record_tool_parser_invocation() no-ops, so /metrics never shows vllm:tool_call_parser_invocations_total. The change is a single guarded init call (with getattr so partial args namespaces used on early-return paths do not raise), plus a parametrized regression test that asserts registration happens only when a parser name is present and uses the served model alias for the metric label.
Change flow
flowchart TD
A["[EXISTING] args.tool_call_parser / served model name"]:::existing
B["[CHANGED] omni_init_app_state registers parser metrics"]:::changed
C["[EXISTING] vllm.parser.metrics.init_parser_metrics"]:::existing
D["[EXISTING] record_tool_parser_invocation can increment"]:::existing
E["[EXISTING] /metrics exposes tool_call_parser_invocations_total"]:::existing
F["[NEW] test_parser_metrics_registered_exactly_when_a_tool_parser_is_set"]:::new
A --> B
B --> C
C --> D
D --> E
F --> B
classDef existing fill:#e5e7eb,stroke:#6b7280,color:#111827
classDef changed fill:#fef3c7,stroke:#d97706,color:#451a03,stroke-width:2px
classDef new fill:#dcfce7,stroke:#16a34a,color:#052e16,stroke-width:2px
classDef removed fill:#fee2e2,stroke:#dc2626,color:#450a0a,stroke-width:2px
CI at
8ea24324a020(2026-09-30T22:31:59.182475+00:00): required check(s) blocking:buildkite/vllm-omni(missing).
No actionable findings.
🤖 This review was generated by InferMatrix Copilot, an open-source repo-maintenance agent for PR review, CI debugging and issue triage. Try it on your own repo, and ⭐ star it if it helped!
|
Hi @NickCao, software engineering student / open-source contributor. This registers the tool-call parser metrics in omni's init path, currently missing so they never emit. Code and tests ready; would appreciate a review. |
Omni ReviewBot: no human activity for 7 days@apex-mochen this pull request has had no human commit, comment or review since 2026-10-03. Please confirm the current plan and next step. The author or a maintainer decides whether to change the PR state. To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline. |
|
Thanks for the reminder! This PR is still active and ready for further review. The fix addresses missing tool-call parser metrics registration in vLLM-Omni’s initialization paths. I’d appreciate a maintainer review when available. I’m happy to address any feedback or add further tests if needed. Thanks! |
omni_init_app_state no longer references OpenAIServingRealtime - the class was removed when /v1/realtime was reworked - so the monkeypatch in the new test raised AttributeError during setup on current main and all four parametrized cases errored before reaching the assertion. The same line also exists in test_multistage_app_state_key_snapshot, but that one is pre-existing and untouched by this branch; main has already removed it there, so the merge resolves it and it is left alone here. With the mock dropped the guards file passes 69/69, and removing the source registration still fails exactly the two parser-configured cases (2 failed / 2 passed) - the test still measures the fix. Signed-off-by: apex-mochen <265981728+apex-mochen@users.noreply.github.com>
|
Re-verified against current The fix is still needed. The new test had drifted. Verification, on the branch merged into current
DCO is green on |
vllm-omni-review-bot
left a comment
There was a problem hiding this comment.
Omni ReviewBot review
0 actionable finding(s).
CI at
cba4f6d23649(2026-10-10T12:40:52.361080+00:00): required check(s) blocking:buildkite/vllm-omni(missing),docs/readthedocs.org:vllm-omni(pending).
Full review analysis
PR description
When omni_init_app_state is given a configured tool-call parser, it now calls upstream init_parser_metrics with the same served model name the API already computed. That creates the parser counters before duplex, pure-diffusion, and multi-stage setup return, so later parser invocations can show up on /metrics. Servers with no parser attribute, or with the parser set to None, skip registration. A new parametrized guard test checks that call.
Change flow
flowchart LR
flag["[EXISTING] args.tool_call_parser"]:::existing
init["[CHANGED] omni_init_app_state"]:::changed
register["[NEW] init_parser_metrics call"]:::new
handlers["[EXISTING] duplex and chat handlers"]:::existing
counter["[EXISTING] parser invocation counter"]:::existing
test["[NEW] registration guard test"]:::new
flag --> init
init --> register
register --> counter
init --> handlers
handlers --> counter
test --> init
classDef existing fill:#e5e7eb,stroke:#6b7280,color:#111827
classDef changed fill:#fef3c7,stroke:#d97706,color:#451a03,stroke-width:2px
classDef new fill:#dcfce7,stroke:#16a34a,color:#052e16,stroke-width:2px
classDef removed fill:#fee2e2,stroke:#dc2626,color:#450a0a,stroke-width:2px
No actionable findings.
🤖 This review was generated by InferMatrix Copilot, an open-source repo-maintenance agent for PR review, CI debugging and issue triage. Try it on your own repo, and ⭐ star it if it helped!
Omni ReviewBot: no human activity for 7 days@apex-mochen this pull request has had no human commit, comment or review since 2026-10-10. Please confirm the current plan and next step. The author or a maintainer decides whether to change the PR state. To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline. |
Omni ReviewBot: no human activity for 14 days@apex-mochen this pull request has had no human commit, comment or review since 2026-10-10. Please consider marking this PR as draft until work can resume. The author or a maintainer decides whether to change the PR state. To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline. |
|
Closing this PR to free one of the five open-PR slots for a core feature. This is a valid bugfix with a clear root cause and tests; I am not rejecting it on technical grounds. I may reopen or resubmit it if maintainers want it after the core work lands. |
vLLM Version: 0.30.0 · vLLM-Omni commit:
1b97ff11881173d9926c67cb9ace9457f5e57954(this branch is one commit on top of it)Test Plan / Result: the exact commands and their pass counts are tabulated under "Tests" below; the base-vs-head negative control is under "Negative control".
Problem
With a tool-call parser configured (
vllm serve --omni ... --tool-call-parser hermes), omni accepts the flag and runs the parser, but/metricsnever exposesvllm:tool_call_parser_invocations_total. The same server launched through upstreamvllm servedoes expose it. Every parser invocation is silently dropped from the metric.Root cause
record_tool_parser_invocation()invllm/parser/metrics.pyopens with an early return:The counter is created only by
init_parser_metrics(), and upstream'sinit_app_state(vllm/entrypoints/launchers/api_server/app_state.py) calls it exactly when a parser is configured:omni does not use that init path — it has its own
omni_init_app_stateinvllm_omni/entrypoints/openai/api_server.py, which has zero references toinit_parser_metrics(or totool_call_parser_invocations), while at the same time it does hand the parser to its handlers:(line numbers from
main; only the multi-stage ones shift once this patch is applied)So the parser runs, calls
record_tool_parser_invocation(), and the call returns immediately. This is an observability regression relative to upstream for any omni deployment that uses tool calling.Reproduction
vllm serve <model> --omni --tool-call-parser hermes(any model with a chat template that supports tools).toolspayload that triggers a tool call.curl -s localhost:8000/metrics | grep tool_call_parservllm serve: the counter is present withmode/outcome/request_typelabel values incrementing.The same holds for the duplex and multi-stage omni paths, since all of them go through the same app-state builder.
Fix
One registration call in
omni_init_app_state, mirroring upstream's guard and using the same model name the server already computed (served_model_names[0] if served_model_names else args.model):getattr(args, "tool_call_parser", None)rather than upstream's bare attribute access is deliberate:tests/entrypoints/openai_api/test_omni_sleep_wakeup.pybuilds a minimal args namespace without that attribute and exercises the pure-diffusion path, which returns before the latertool_parser=args.tool_call_parsersites. Registration is upstream's own function (idempotent by itsexcept ValueErrorfallback), so re-entry and duplicate registration behave exactly as in upstreamvllm serve.Tests
Added
test_parser_metrics_registered_exactly_when_a_tool_parser_is_setintests/entrypoints/openai_api/test_api_server_guards.py, parametrized over four cases:tool_call_parserattributetool_call_parser=Nonetool_call_parser="hermes_tool_parser"model_name="demo-model"served_model_name=["served-alias"]model_name="served-alias"Executed evidence (local,
vllm0.30.0, CPU-only):test_api_server_guards.py— 68 passedtest_duplex_api_server.py+test_diffusion_request_utils.py— 62 passedtest_omni_sleep_wakeup.py— 146 passedruff checkandruff format --checkclean on both changed filesThe reproduction was verified statically and by the regression test above; I could not start an omni server end-to-end in this environment (no GPU), so the "curl /metrics" step is the operator-facing symptom rather than a log I captured.
Negative control
vllm_omni/entrypoints/openai/api_server.pyrestored at file level to this PR'sbase bytes (md5
7189287bf5789f4fad9a74bb7c492d1f, asserted before the run, withinit_parser_metricsabsent from the file) and the new test left in place:The two failures are exactly the parameterizations that require the registration.
The other two assert that nothing is registered (attribute absent, and
explicitly
None); they pass on both sides by construction, so only the positivecases discriminate. Re-applying the head bytes
(md5
6be274397d2af8feafeb280a71e34830) gives4 passed.Reviews
Patch coverage / branch review. The only new branch is the
is not Noneguard.Trueis covered by cases 3 and 4,Falseby cases 1 and 2 (attribute absent and explicitlyNone). Thegetattrdefault path — attribute genuinely missing — is additionally exercised by the pre-existingtest_omni_sleep_wakeup.py, which passes an args namespace lackingtool_call_parser; that test is what forcedgetattrover upstream's bare access. No other control flow is added or changed.Edge cases. Normal value (a parser name) → registered with the served alias, not the raw
--model;Noneand missing attribute → nothing registered, noAttributeError; emptyserved_model_names→ falls back toargs.model, the same expression the surrounding code already uses for the serving objects, so the metric label can never disagree with the model the server reports. Exception behaviour is inherited frominit_parser_metricsunchanged.Cross-mode / cross-backend impact. The insertion is in the shared app-state builder, so it covers duplex, multi-stage, and pure-diffusion initialisation in one place. It is inert for every deployment without
--tool-call-parser(guard false → zero behaviour change). For pure-diffusion setups the counter gets registered but never incremented, which matches upstream's flag-driven semantics (upstream also registers whenever the flag is set, regardless of whether a request ever reaches the parser). No provider-, engine-, or backend-specific code is touched.Relation to other PRs
The insertion sits in the same function as #8209, but on disjoint lines — #8209 changes the engine-client/metrics-handler region, this adds a registration immediately after
model_nameis computed and before the dispatch block. I have kept the two patches non-overlapping on purpose; The registration sits above both early returns inomni_init_app_state(the duplex delegation and the pure-diffusion path), so every construction path gets the counters.The four wiring-drift PRs (#8209, #8212, #8242, #8244) originally all appended their tests to the tail of
tests/entrypoints/openai_api/test_api_server_guards.py, so any two of them conflicted as soon as one merged. Each now inserts its block at a distinct point in that file, and #8244's fingerprint call no longer shares this PR's anchor. Merging all five into one tree yields 85 collected tests, all passing, withruffclean.AI assistance disclosure
The change was developed with AI assistance (code, tests, and the analysis above). Per the repository's disclosure policy for AI-generated contributions: I have run the tests listed above locally and reviewed the patch and its interaction with upstream's init path; the commit carries a
Signed-off-bytrailer matching the author identity.Alternative approaches
init_parser_metrics: the metric object and the module-level registry thatrecord_tool_parser_invocation()checks both live in upstream's metrics module,so a private second registry would fork the metric.
that changes upstream parser semantics and is out of scope here.
init_app_stateto inherit the call: it also builds theentire generate-side surface, which
omni_init_app_statedeliberately replaces.Compatibility
No new flag, no new dependency, no request-path change: one registration at init,
under upstream's own guard (
args.tool_call_parser is not None)./metricsgainsthe counters upstream exposes for the same configuration; a server without a
tool-call parser is unchanged.