mudler / mudler/vllm.cpp

fix(SERVE-CHAT-TEMPLATE): a malformed historical tool-call argument answers 400 and makes the conversation unrecoverable

Open
#2,728 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
423
Forks
53
Avg merge
20h 26m
Merged PRs (30d)
310

Description

Row: SERVE-CHAT-TEMPLATE (.agents/engine-matrix.md:219), whose local-anchor column names src/vllm/entrypoints/chat_template.cpp.

A note on the row, because the obvious candidate does not resolve. .agents/specs/tool-call-history-arguments.md:5 names SERVE-TOOL-HISTORY-ARGS, tracked by #526. That ID is not a matrix row: 0 hits in .agents/engine-matrix.md and .agents/roadmap_v1.md, against a positive control of SERVE-CHAT-TEMPLATE returning 1 in the same scope and probe form. So this issue names the row that owns the edited file, not the ID the spec invented.

Found by wave PORTQ-7 (#2717) re-deriving PORT-NOW entry [254], upstream 161ffd37d2 vllm#48922. Nothing was executed.

The divergence

A malformed arguments string on a historical assistant tool call makes this tree answer HTTP 400. Upstream now coerces it to {} with a warning and continues.

There is exactly one site, and it is enumerated rather than sampled: DecodeHistoricalToolArguments (src/vllm/entrypoints/chat_template.cpp:35), called from exactly one place, BuildMessages (:90). grep -rn DecodeHistoricalToolArguments src include tests returns those two lines and nothing else.

Its behaviour today:

  • empty -> {} (:38) — matches upstream;
  • JSON null -> {} — matches upstream;
  • parse failure -> throw ChatTemplateError (:45-49) — upstream warns and coerces;
  • any other valid JSON returned as-is (:50-51) — so "[]", "42" and "true" reach minja as an array, a number and a bool. Upstream coerces every non-object to {} with a warning.

The throw is client-facing: api_server.cpp:361-378 maps ChatTemplateError to BadRequestError. Two handlers reach the site — handle_chat_completions (api_server.cpp:306) and handle_tokenize's chat form, routed through chat_->prompt_fn() at api_server.cpp:816-822. The C-ABI fallback prompt builder does not decode arguments at all (src/capi/chat_prompt.cpp).

Upstream's rationale is the part worth carrying: a malformed argument lives in conversation history, so failing on it fails every subsequent turn and makes the conversation unrecoverable.

This reverses a committed decision, and a reviewer must be told so

.agents/specs/tool-call-history-arguments.md states the fail-closed choice four times — :28 ("malformed JSON raises instead of entering the prompt"), :61, :88, and :119 ("fail closed; do not coerce"). tests/vllm/entrypoints/test_chat_template.cpp:868-881 pins the throw under the name "malformed historical tool arguments fail closed".

So the fix amends that spec and inverts an existing test. A fresh reviewer must not read that inversion as an assertion being deleted — it is the assertion changing sides, because upstream has since converged on the opposite, with a stated reason this project did not have when it chose.

The commit is the whole distance

The pre-commit shape existed at 5559679229 and was ported faithfully. Not a pre-pin hole. git show e126687a9a:vllm/entrypoints/chat_utils.py still carries both warnings at :2082 and :2098; the two later in-range touches of that file (555ea65e8c, and acd0af90e8 which is entry [256] of this queue) narrow neither.

One adjacent divergence, best fixed in the same change

Upstream explicitly passes a dict-valued arguments through. This tree parses arguments into a std::string (src/vllm/entrypoints/openai/protocol.cpp:487, struct at include/vllm/entrypoints/openai/protocol.h:152-155), so a dict makes nlohmann raise type_error.302, which escapes into the from_json catch and answers 400 (api_server.cpp:325-329). Same user-visible symptom, different mechanism, same family.

Size

~20-25 product lines in chat_template.cpp, plus a warning sink — that file has no logger today and would need std::cerr or the request logger. ~60-80 test lines: invert the one existing case and add the non-object cases upstream parametrises. ~6 spec lines in tool-call-history-arguments.md.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with DecodeHistoricalToolArguments and BuildMessages in src/vllm/entrypoints/chat_template.cpp, then inspect the protocol argument types in src/vllm/entrypoints/openai/protocol.cpp and .h. Read the existing case in tests/vllm/entrypoints/test_chat_template.cpp:868-881 and the related spec. Done means malformed or non-object historical arguments no longer produce a 400, the adjacent dict-valued case is handled, and the tests and spec reflect the new behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend-api-design, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.